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
22typedef CGFLOAT_TYPE CGFloat;
23struct CGPoint {
24  CGFloat x;
25  CGFloat y;
26};
27typedef 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
60NSString *ForceLocalized(NSString *str)
61    __attribute__((annotate("returns_localized_nsstring")));
62CGPoint CGPointMake(CGFloat x, CGFloat y);
63int random();
64// This next one is a made up API
65NSString *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
72NSString *
73takesLocalizedString(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
83NSString *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
297@interface SynthesizedAccessors : NSObject
298@property (assign) NSObject *obj;
299@end
300
301@implementation SynthesizedAccessors
302// no-crash
303@end
304