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