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