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 | |