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@end
65
66// Test cases begin here
67@implementation LocalizationTestSuite
68
69// A C-Funtion that returns a localized string because it has the
70// "returns_localized_nsstring" annotation
71NSString *ForceLocalized(NSString *str) { return str; }
72// An ObjC method that returns a localized string because it has the
73// "returns_localized_nsstring" annotation
74+ (NSString *)forceLocalized:(NSString *)str {
75  return str;
76}
77
78// An ObjC method that returns a localized string
79+ (NSString *)unLocalizedStringMethod {
80  return @"UnlocalizedString";
81}
82
83- (void)testLocalizationErrorDetectedOnPathway {
84  UILabel *testLabel = [[UILabel alloc] init];
85  NSString *bar = NSLocalizedString(@"Hello", @"Comment");
86
87  if (random()) {
88    bar = @"Unlocalized string";
89  }
90
91  [testLabel setText:bar]; // expected-warning {{User-facing text should use localized string macro}}
92}
93
94- (void)testLocalizationErrorDetectedOnNSString {
95  NSString *bar = NSLocalizedString(@"Hello", @"Comment");
96
97  if (random()) {
98    bar = @"Unlocalized string";
99  }
100
101  [bar drawAtPoint:CGPointMake(0, 0) withAttributes:nil]; // expected-warning {{User-facing text should use localized string macro}}
102}
103
104- (void)testNoLocalizationErrorDetectedFromCFunction {
105  UILabel *testLabel = [[UILabel alloc] init];
106  NSString *bar = CFNumberFormatterCreateStringWithNumber(1);
107
108  [testLabel setText:bar]; // no-warning
109}
110
111- (void)testAnnotationAddsLocalizedStateForCFunction {
112  UILabel *testLabel = [[UILabel alloc] init];
113  NSString *bar = NSLocalizedString(@"Hello", @"Comment");
114
115  if (random()) {
116    bar = @"Unlocalized string";
117  }
118
119  [testLabel setText:ForceLocalized(bar)]; // no-warning
120}
121
122- (void)testAnnotationAddsLocalizedStateForObjCMethod {
123  UILabel *testLabel = [[UILabel alloc] init];
124  NSString *bar = NSLocalizedString(@"Hello", @"Comment");
125
126  if (random()) {
127    bar = @"Unlocalized string";
128  }
129
130  [testLabel setText:[LocalizationTestSuite forceLocalized:bar]]; // no-warning
131}
132
133// An empty string literal @"" should not raise an error
134- (void)testEmptyStringLiteralHasLocalizedState {
135  UILabel *testLabel = [[UILabel alloc] init];
136  NSString *bar = @"";
137
138  [testLabel setText:bar]; // no-warning
139}
140
141// An empty string literal @"" inline should not raise an error
142- (void)testInlineEmptyStringLiteralHasLocalizedState {
143  UILabel *testLabel = [[UILabel alloc] init];
144  [testLabel setText:@""]; // no-warning
145}
146
147// An string literal @"Hello" inline should raise an error
148- (void)testInlineStringLiteralHasLocalizedState {
149  UILabel *testLabel = [[UILabel alloc] init];
150  [testLabel setText:@"Hello"]; // expected-warning {{User-facing text should use localized string macro}}
151}
152
153// A nil string should not raise an error
154- (void)testNilStringIsNotMarkedAsUnlocalized {
155  UILabel *testLabel = [[UILabel alloc] init];
156  [testLabel setText:nil]; // no-warning
157}
158
159// A method that takes in a localized string and returns a string
160// most likely that string is localized.
161- (void)testLocalizedStringArgument {
162  UILabel *testLabel = [[UILabel alloc] init];
163  NSString *localizedString = NSLocalizedString(@"Hello", @"Comment");
164
165  NSString *combinedString =
166      [NSString localizedStringWithFormat:@"%@", localizedString];
167
168  [testLabel setText:combinedString]; // no-warning
169}
170
171// A String passed in as a an parameter should not be considered
172// unlocalized
173- (void)testLocalizedStringAsArgument:(NSString *)argumentString {
174  UILabel *testLabel = [[UILabel alloc] init];
175
176  [testLabel setText:argumentString]; // no-warning
177}
178
179// The warning is expected to be seen in localizedStringAsArgument: body
180- (void)testLocalizedStringAsArgumentOtherMethod:(NSString *)argumentString {
181  [self localizedStringAsArgument:@"UnlocalizedString"];
182}
183
184// A String passed into another method that calls a method that
185// requires a localized string should give an error
186- (void)localizedStringAsArgument:(NSString *)argumentString {
187  UILabel *testLabel = [[UILabel alloc] init];
188
189  [testLabel setText:argumentString]; // expected-warning {{User-facing text should use localized string macro}}
190}
191
192// [LocalizationTestSuite unLocalizedStringMethod] returns an unlocalized string
193// so we expect an error. Unfrtunately, it probably doesn't make a difference
194// what [LocalizationTestSuite unLocalizedStringMethod] returns since all
195// string values returned are marked as Unlocalized in aggressive reporting.
196- (void)testUnLocalizedStringMethod {
197  UILabel *testLabel = [[UILabel alloc] init];
198  NSString *bar = NSLocalizedString(@"Hello", @"Comment");
199
200  [testLabel setText:[LocalizationTestSuite unLocalizedStringMethod]]; // expected-warning {{User-facing text should use localized string macro}}
201}
202
203// This is the reverse situation: accessibilitySetIdentification: doesn't care
204// about localization so we don't expect a warning
205- (void)testMethodNotInRequiresLocalizedStringMethods {
206  UILabel *testLabel = [[UILabel alloc] init];
207
208  [testLabel accessibilitySetIdentification:@"UnlocalizedString"]; // no-warning
209}
210
211// An NSView subclass should raise a warning for methods in NSView that
212// require localized strings
213- (void)testRequiresLocalizationMethodFromSuperclass {
214  NSViewSubclass *s = [[NSViewSubclass alloc] init];
215  NSString *bar = @"UnlocalizedString";
216
217  [s setToolTip:bar]; // expected-warning {{User-facing text should use localized string macro}}
218}
219
220- (void)testRequiresLocalizationMethodFromProtocol {
221  UILabel *testLabel = [[UILabel alloc] init];
222
223  [testLabel setAccessibilityLabel:@"UnlocalizedString"]; // expected-warning {{User-facing text should use localized string macro}}
224}
225
226// EmptyLocalizationContextChecker tests
227#define HOM(s) YOLOC(s)
228#define YOLOC(x) NSLocalizedString(x, nil)
229
230- (void)testNilLocalizationContext {
231  NSString *string = NSLocalizedString(@"LocalizedString", nil); // expected-warning {{Localized string macro should include a non-empty comment for translators}}
232  NSString *string2 = NSLocalizedString(@"LocalizedString", nil); // expected-warning {{Localized string macro should include a non-empty comment for translators}}
233  NSString *string3 = NSLocalizedString(@"LocalizedString", nil); // expected-warning {{Localized string macro should include a non-empty comment for translators}}
234}
235
236- (void)testEmptyLocalizationContext {
237  NSString *string = NSLocalizedString(@"LocalizedString", @""); // expected-warning {{Localized string macro should include a non-empty comment for translators}}
238  NSString *string2 = NSLocalizedString(@"LocalizedString", @" "); // expected-warning {{Localized string macro should include a non-empty comment for translators}}
239  NSString *string3 = NSLocalizedString(@"LocalizedString", @"	 "); // expected-warning {{Localized string macro should include a non-empty comment for translators}}
240}
241
242- (void)testNSLocalizedStringVariants {
243  NSString *string = NSLocalizedStringFromTable(@"LocalizedString", nil, @""); // expected-warning {{Localized string macro should include a non-empty comment for translators}}
244  NSString *string2 = NSLocalizedStringFromTableInBundle(@"LocalizedString", nil, [[NSBundle alloc] init],@""); // expected-warning {{Localized string macro should include a non-empty comment for translators}}
245  NSString *string3 = NSLocalizedStringWithDefaultValue(@"LocalizedString", nil, [[NSBundle alloc] init], nil,@""); // expected-warning {{Localized string macro should include a non-empty comment for translators}}
246}
247
248- (void)testMacroExpansionNilString {
249  NSString *string = YOLOC(@"Hello"); // expected-warning {{Localized string macro should include a non-empty comment for translators}}
250  NSString *string2 = HOM(@"Hello");  // expected-warning {{Localized string macro should include a non-empty comment for translators}}
251  NSString *string3 = NSLocalizedString((0 ? @"Critical" : @"Current"),nil); // expected-warning {{Localized string macro should include a non-empty comment for translators}}
252}
253
254- (void)testMacroExpansionDefinedInPCH {
255  NSString *string = MyLocalizedStringInPCH(@"Hello"); // expected-warning {{Localized string macro should include a non-empty comment for translators}}
256}
257
258#define KCLocalizedString(x,comment) NSLocalizedString(x, comment)
259#define POSSIBLE_FALSE_POSITIVE(s,other) KCLocalizedString(s,@"Comment")
260
261- (void)testNoWarningForNilCommentPassedIntoOtherMacro {
262  NSString *string = KCLocalizedString(@"Hello",@""); // no-warning
263  NSString *string2 = KCLocalizedString(@"Hello",nil); // no-warning
264  NSString *string3 = KCLocalizedString(@"Hello",@"Comment"); // no-warning
265}
266
267- (void)testPossibleFalsePositiveSituationAbove {
268  NSString *string = POSSIBLE_FALSE_POSITIVE(@"Hello", nil); // no-warning
269  NSString *string2 = POSSIBLE_FALSE_POSITIVE(@"Hello", @"Hello"); // no-warning
270}
271
272@end
273