1*67e74705SXin Li// RUN: %clang_cc1 -analyze -fblocks -analyzer-store=region -analyzer-checker=optin.osx.cocoa.localizability.NonLocalizedStringChecker -analyzer-checker=optin.osx.cocoa.localizability.EmptyLocalizationContextChecker -verify -analyzer-config AggressiveReport=true %s 2*67e74705SXin Li 3*67e74705SXin Li// These declarations were reduced using Delta-Debugging from Foundation.h 4*67e74705SXin Li// on Mac OS X. 5*67e74705SXin Li 6*67e74705SXin Li#define nil ((id)0) 7*67e74705SXin Li#define NSLocalizedString(key, comment) \ 8*67e74705SXin Li [[NSBundle mainBundle] localizedStringForKey:(key) value:@"" table:nil] 9*67e74705SXin Li#define NSLocalizedStringFromTable(key, tbl, comment) \ 10*67e74705SXin Li [[NSBundle mainBundle] localizedStringForKey:(key) value:@"" table:(tbl)] 11*67e74705SXin Li#define NSLocalizedStringFromTableInBundle(key, tbl, bundle, comment) \ 12*67e74705SXin Li [bundle localizedStringForKey:(key) value:@"" table:(tbl)] 13*67e74705SXin Li#define NSLocalizedStringWithDefaultValue(key, tbl, bundle, val, comment) \ 14*67e74705SXin Li [bundle localizedStringForKey:(key) value:(val) table:(tbl)] 15*67e74705SXin Li#define CGFLOAT_TYPE double 16*67e74705SXin Litypedef CGFLOAT_TYPE CGFloat; 17*67e74705SXin Listruct CGPoint { 18*67e74705SXin Li CGFloat x; 19*67e74705SXin Li CGFloat y; 20*67e74705SXin Li}; 21*67e74705SXin Litypedef struct CGPoint CGPoint; 22*67e74705SXin Li@interface NSObject 23*67e74705SXin Li+ (id)alloc; 24*67e74705SXin Li- (id)init; 25*67e74705SXin Li@end 26*67e74705SXin Li@class NSDictionary; 27*67e74705SXin Li@interface NSString : NSObject 28*67e74705SXin Li- (void)drawAtPoint:(CGPoint)point withAttributes:(NSDictionary *)attrs; 29*67e74705SXin Li+ (instancetype)localizedStringWithFormat:(NSString *)format, ...; 30*67e74705SXin Li@end 31*67e74705SXin Li@interface NSBundle : NSObject 32*67e74705SXin Li+ (NSBundle *)mainBundle; 33*67e74705SXin Li- (NSString *)localizedStringForKey:(NSString *)key 34*67e74705SXin Li value:(NSString *)value 35*67e74705SXin Li table:(NSString *)tableName; 36*67e74705SXin Li@end 37*67e74705SXin Li@protocol UIAccessibility 38*67e74705SXin Li- (void)accessibilitySetIdentification:(NSString *)ident; 39*67e74705SXin Li- (void)setAccessibilityLabel:(NSString *)label; 40*67e74705SXin Li@end 41*67e74705SXin Li@interface UILabel : NSObject <UIAccessibility> 42*67e74705SXin Li@property(nullable, nonatomic, copy) NSString *text; 43*67e74705SXin Li@end 44*67e74705SXin Li@interface TestObject : NSObject 45*67e74705SXin Li@property(strong) NSString *text; 46*67e74705SXin Li@end 47*67e74705SXin Li@interface NSView : NSObject 48*67e74705SXin Li@property (strong) NSString *toolTip; 49*67e74705SXin Li@end 50*67e74705SXin Li@interface NSViewSubclass : NSView 51*67e74705SXin Li@end 52*67e74705SXin Li 53*67e74705SXin Li@interface LocalizationTestSuite : NSObject 54*67e74705SXin LiNSString *ForceLocalized(NSString *str) 55*67e74705SXin Li __attribute__((annotate("returns_localized_nsstring"))); 56*67e74705SXin LiCGPoint CGPointMake(CGFloat x, CGFloat y); 57*67e74705SXin Liint random(); 58*67e74705SXin Li// This next one is a made up API 59*67e74705SXin LiNSString *CFNumberFormatterCreateStringWithNumber(float x); 60*67e74705SXin Li+ (NSString *)forceLocalized:(NSString *)str 61*67e74705SXin Li __attribute__((annotate("returns_localized_nsstring"))); 62*67e74705SXin Li@end 63*67e74705SXin Li 64*67e74705SXin Li// Test cases begin here 65*67e74705SXin Li@implementation LocalizationTestSuite 66*67e74705SXin Li 67*67e74705SXin Li// A C-Funtion that returns a localized string because it has the 68*67e74705SXin Li// "returns_localized_nsstring" annotation 69*67e74705SXin LiNSString *ForceLocalized(NSString *str) { return str; } 70*67e74705SXin Li// An ObjC method that returns a localized string because it has the 71*67e74705SXin Li// "returns_localized_nsstring" annotation 72*67e74705SXin Li+ (NSString *)forceLocalized:(NSString *)str { 73*67e74705SXin Li return str; 74*67e74705SXin Li} 75*67e74705SXin Li 76*67e74705SXin Li// An ObjC method that returns a localized string 77*67e74705SXin Li+ (NSString *)unLocalizedStringMethod { 78*67e74705SXin Li return @"UnlocalizedString"; 79*67e74705SXin Li} 80*67e74705SXin Li 81*67e74705SXin Li- (void)testLocalizationErrorDetectedOnPathway { 82*67e74705SXin Li UILabel *testLabel = [[UILabel alloc] init]; 83*67e74705SXin Li NSString *bar = NSLocalizedString(@"Hello", @"Comment"); 84*67e74705SXin Li 85*67e74705SXin Li if (random()) { 86*67e74705SXin Li bar = @"Unlocalized string"; 87*67e74705SXin Li } 88*67e74705SXin Li 89*67e74705SXin Li [testLabel setText:bar]; // expected-warning {{User-facing text should use localized string macro}} 90*67e74705SXin Li} 91*67e74705SXin Li 92*67e74705SXin Li- (void)testLocalizationErrorDetectedOnNSString { 93*67e74705SXin Li NSString *bar = NSLocalizedString(@"Hello", @"Comment"); 94*67e74705SXin Li 95*67e74705SXin Li if (random()) { 96*67e74705SXin Li bar = @"Unlocalized string"; 97*67e74705SXin Li } 98*67e74705SXin Li 99*67e74705SXin Li [bar drawAtPoint:CGPointMake(0, 0) withAttributes:nil]; // expected-warning {{User-facing text should use localized string macro}} 100*67e74705SXin Li} 101*67e74705SXin Li 102*67e74705SXin Li- (void)testNoLocalizationErrorDetectedFromCFunction { 103*67e74705SXin Li UILabel *testLabel = [[UILabel alloc] init]; 104*67e74705SXin Li NSString *bar = CFNumberFormatterCreateStringWithNumber(1); 105*67e74705SXin Li 106*67e74705SXin Li [testLabel setText:bar]; // no-warning 107*67e74705SXin Li} 108*67e74705SXin Li 109*67e74705SXin Li- (void)testAnnotationAddsLocalizedStateForCFunction { 110*67e74705SXin Li UILabel *testLabel = [[UILabel alloc] init]; 111*67e74705SXin Li NSString *bar = NSLocalizedString(@"Hello", @"Comment"); 112*67e74705SXin Li 113*67e74705SXin Li if (random()) { 114*67e74705SXin Li bar = @"Unlocalized string"; 115*67e74705SXin Li } 116*67e74705SXin Li 117*67e74705SXin Li [testLabel setText:ForceLocalized(bar)]; // no-warning 118*67e74705SXin Li} 119*67e74705SXin Li 120*67e74705SXin Li- (void)testAnnotationAddsLocalizedStateForObjCMethod { 121*67e74705SXin Li UILabel *testLabel = [[UILabel alloc] init]; 122*67e74705SXin Li NSString *bar = NSLocalizedString(@"Hello", @"Comment"); 123*67e74705SXin Li 124*67e74705SXin Li if (random()) { 125*67e74705SXin Li bar = @"Unlocalized string"; 126*67e74705SXin Li } 127*67e74705SXin Li 128*67e74705SXin Li [testLabel setText:[LocalizationTestSuite forceLocalized:bar]]; // no-warning 129*67e74705SXin Li} 130*67e74705SXin Li 131*67e74705SXin Li// An empty string literal @"" should not raise an error 132*67e74705SXin Li- (void)testEmptyStringLiteralHasLocalizedState { 133*67e74705SXin Li UILabel *testLabel = [[UILabel alloc] init]; 134*67e74705SXin Li NSString *bar = @""; 135*67e74705SXin Li 136*67e74705SXin Li [testLabel setText:bar]; // no-warning 137*67e74705SXin Li} 138*67e74705SXin Li 139*67e74705SXin Li// An empty string literal @"" inline should not raise an error 140*67e74705SXin Li- (void)testInlineEmptyStringLiteralHasLocalizedState { 141*67e74705SXin Li UILabel *testLabel = [[UILabel alloc] init]; 142*67e74705SXin Li [testLabel setText:@""]; // no-warning 143*67e74705SXin Li} 144*67e74705SXin Li 145*67e74705SXin Li// An string literal @"Hello" inline should raise an error 146*67e74705SXin Li- (void)testInlineStringLiteralHasLocalizedState { 147*67e74705SXin Li UILabel *testLabel = [[UILabel alloc] init]; 148*67e74705SXin Li [testLabel setText:@"Hello"]; // expected-warning {{User-facing text should use localized string macro}} 149*67e74705SXin Li} 150*67e74705SXin Li 151*67e74705SXin Li// A nil string should not raise an error 152*67e74705SXin Li- (void)testNilStringIsNotMarkedAsUnlocalized { 153*67e74705SXin Li UILabel *testLabel = [[UILabel alloc] init]; 154*67e74705SXin Li [testLabel setText:nil]; // no-warning 155*67e74705SXin Li} 156*67e74705SXin Li 157*67e74705SXin Li// A method that takes in a localized string and returns a string 158*67e74705SXin Li// most likely that string is localized. 159*67e74705SXin Li- (void)testLocalizedStringArgument { 160*67e74705SXin Li UILabel *testLabel = [[UILabel alloc] init]; 161*67e74705SXin Li NSString *localizedString = NSLocalizedString(@"Hello", @"Comment"); 162*67e74705SXin Li 163*67e74705SXin Li NSString *combinedString = 164*67e74705SXin Li [NSString localizedStringWithFormat:@"%@", localizedString]; 165*67e74705SXin Li 166*67e74705SXin Li [testLabel setText:combinedString]; // no-warning 167*67e74705SXin Li} 168*67e74705SXin Li 169*67e74705SXin Li// A String passed in as a an parameter should not be considered 170*67e74705SXin Li// unlocalized 171*67e74705SXin Li- (void)testLocalizedStringAsArgument:(NSString *)argumentString { 172*67e74705SXin Li UILabel *testLabel = [[UILabel alloc] init]; 173*67e74705SXin Li 174*67e74705SXin Li [testLabel setText:argumentString]; // no-warning 175*67e74705SXin Li} 176*67e74705SXin Li 177*67e74705SXin Li// The warning is expected to be seen in localizedStringAsArgument: body 178*67e74705SXin Li- (void)testLocalizedStringAsArgumentOtherMethod:(NSString *)argumentString { 179*67e74705SXin Li [self localizedStringAsArgument:@"UnlocalizedString"]; 180*67e74705SXin Li} 181*67e74705SXin Li 182*67e74705SXin Li// A String passed into another method that calls a method that 183*67e74705SXin Li// requires a localized string should give an error 184*67e74705SXin Li- (void)localizedStringAsArgument:(NSString *)argumentString { 185*67e74705SXin Li UILabel *testLabel = [[UILabel alloc] init]; 186*67e74705SXin Li 187*67e74705SXin Li [testLabel setText:argumentString]; // expected-warning {{User-facing text should use localized string macro}} 188*67e74705SXin Li} 189*67e74705SXin Li 190*67e74705SXin Li// [LocalizationTestSuite unLocalizedStringMethod] returns an unlocalized string 191*67e74705SXin Li// so we expect an error. Unfrtunately, it probably doesn't make a difference 192*67e74705SXin Li// what [LocalizationTestSuite unLocalizedStringMethod] returns since all 193*67e74705SXin Li// string values returned are marked as Unlocalized in aggressive reporting. 194*67e74705SXin Li- (void)testUnLocalizedStringMethod { 195*67e74705SXin Li UILabel *testLabel = [[UILabel alloc] init]; 196*67e74705SXin Li NSString *bar = NSLocalizedString(@"Hello", @"Comment"); 197*67e74705SXin Li 198*67e74705SXin Li [testLabel setText:[LocalizationTestSuite unLocalizedStringMethod]]; // expected-warning {{User-facing text should use localized string macro}} 199*67e74705SXin Li} 200*67e74705SXin Li 201*67e74705SXin Li// This is the reverse situation: accessibilitySetIdentification: doesn't care 202*67e74705SXin Li// about localization so we don't expect a warning 203*67e74705SXin Li- (void)testMethodNotInRequiresLocalizedStringMethods { 204*67e74705SXin Li UILabel *testLabel = [[UILabel alloc] init]; 205*67e74705SXin Li 206*67e74705SXin Li [testLabel accessibilitySetIdentification:@"UnlocalizedString"]; // no-warning 207*67e74705SXin Li} 208*67e74705SXin Li 209*67e74705SXin Li// An NSView subclass should raise a warning for methods in NSView that 210*67e74705SXin Li// require localized strings 211*67e74705SXin Li- (void)testRequiresLocalizationMethodFromSuperclass { 212*67e74705SXin Li NSViewSubclass *s = [[NSViewSubclass alloc] init]; 213*67e74705SXin Li NSString *bar = @"UnlocalizedString"; 214*67e74705SXin Li 215*67e74705SXin Li [s setToolTip:bar]; // expected-warning {{User-facing text should use localized string macro}} 216*67e74705SXin Li} 217*67e74705SXin Li 218*67e74705SXin Li- (void)testRequiresLocalizationMethodFromProtocol { 219*67e74705SXin Li UILabel *testLabel = [[UILabel alloc] init]; 220*67e74705SXin Li 221*67e74705SXin Li [testLabel setAccessibilityLabel:@"UnlocalizedString"]; // expected-warning {{User-facing text should use localized string macro}} 222*67e74705SXin Li} 223*67e74705SXin Li 224*67e74705SXin Li// EmptyLocalizationContextChecker tests 225*67e74705SXin Li#define HOM(s) YOLOC(s) 226*67e74705SXin Li#define YOLOC(x) NSLocalizedString(x, nil) 227*67e74705SXin Li 228*67e74705SXin Li- (void)testNilLocalizationContext { 229*67e74705SXin Li NSString *string = NSLocalizedString(@"LocalizedString", nil); // expected-warning {{Localized string macro should include a non-empty comment for translators}} 230*67e74705SXin Li NSString *string2 = NSLocalizedString(@"LocalizedString", nil); // expected-warning {{Localized string macro should include a non-empty comment for translators}} 231*67e74705SXin Li NSString *string3 = NSLocalizedString(@"LocalizedString", nil); // expected-warning {{Localized string macro should include a non-empty comment for translators}} 232*67e74705SXin Li} 233*67e74705SXin Li 234*67e74705SXin Li- (void)testEmptyLocalizationContext { 235*67e74705SXin Li NSString *string = NSLocalizedString(@"LocalizedString", @""); // expected-warning {{Localized string macro should include a non-empty comment for translators}} 236*67e74705SXin Li NSString *string2 = NSLocalizedString(@"LocalizedString", @" "); // expected-warning {{Localized string macro should include a non-empty comment for translators}} 237*67e74705SXin Li NSString *string3 = NSLocalizedString(@"LocalizedString", @" "); // expected-warning {{Localized string macro should include a non-empty comment for translators}} 238*67e74705SXin Li} 239*67e74705SXin Li 240*67e74705SXin Li- (void)testNSLocalizedStringVariants { 241*67e74705SXin Li NSString *string = NSLocalizedStringFromTable(@"LocalizedString", nil, @""); // expected-warning {{Localized string macro should include a non-empty comment for translators}} 242*67e74705SXin Li NSString *string2 = NSLocalizedStringFromTableInBundle(@"LocalizedString", nil, [[NSBundle alloc] init],@""); // expected-warning {{Localized string macro should include a non-empty comment for translators}} 243*67e74705SXin Li NSString *string3 = NSLocalizedStringWithDefaultValue(@"LocalizedString", nil, [[NSBundle alloc] init], nil,@""); // expected-warning {{Localized string macro should include a non-empty comment for translators}} 244*67e74705SXin Li} 245*67e74705SXin Li 246*67e74705SXin Li- (void)testMacroExpansionNilString { 247*67e74705SXin Li NSString *string = YOLOC(@"Hello"); // expected-warning {{Localized string macro should include a non-empty comment for translators}} 248*67e74705SXin Li NSString *string2 = HOM(@"Hello"); // expected-warning {{Localized string macro should include a non-empty comment for translators}} 249*67e74705SXin Li NSString *string3 = NSLocalizedString((0 ? @"Critical" : @"Current"),nil); // expected-warning {{Localized string macro should include a non-empty comment for translators}} 250*67e74705SXin Li} 251*67e74705SXin Li 252*67e74705SXin Li#define KCLocalizedString(x,comment) NSLocalizedString(x, comment) 253*67e74705SXin Li#define POSSIBLE_FALSE_POSITIVE(s,other) KCLocalizedString(s,@"Comment") 254*67e74705SXin Li 255*67e74705SXin Li- (void)testNoWarningForNilCommentPassedIntoOtherMacro { 256*67e74705SXin Li NSString *string = KCLocalizedString(@"Hello",@""); // no-warning 257*67e74705SXin Li NSString *string2 = KCLocalizedString(@"Hello",nil); // no-warning 258*67e74705SXin Li NSString *string3 = KCLocalizedString(@"Hello",@"Comment"); // no-warning 259*67e74705SXin Li} 260*67e74705SXin Li 261*67e74705SXin Li- (void)testPossibleFalsePositiveSituationAbove { 262*67e74705SXin Li NSString *string = POSSIBLE_FALSE_POSITIVE(@"Hello", nil); // no-warning 263*67e74705SXin Li NSString *string2 = POSSIBLE_FALSE_POSITIVE(@"Hello", @"Hello"); // no-warning 264*67e74705SXin Li} 265*67e74705SXin Li 266*67e74705SXin Li@end 267