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