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