xref: /aosp_15_r20/external/clang/test/Analysis/localization.m (revision 67e74705e28f6214e480b399dd47ea732279e315)
1*67e74705SXin Li// RUN: %clang_cc1 -analyze -fblocks -analyzer-store=region -analyzer-output=text -analyzer-checker=optin.osx.cocoa.localizability.NonLocalizedStringChecker -analyzer-checker=alpha.osx.cocoa.localizability.PluralMisuseChecker -verify  %s
2*67e74705SXin Li
3*67e74705SXin Li// The larger set of tests in located in localization.m. These are tests
4*67e74705SXin Li// specific for non-aggressive reporting.
5*67e74705SXin Li
6*67e74705SXin Li// These declarations were reduced using Delta-Debugging from Foundation.h
7*67e74705SXin Li// on Mac OS X.
8*67e74705SXin Li
9*67e74705SXin Li#define nil ((id)0)
10*67e74705SXin Li#define NSLocalizedString(key, comment)                                        \
11*67e74705SXin Li  [[NSBundle mainBundle] localizedStringForKey:(key) value:@"" table:nil]
12*67e74705SXin Li#define NSLocalizedStringFromTable(key, tbl, comment)                          \
13*67e74705SXin Li  [[NSBundle mainBundle] localizedStringForKey:(key) value:@"" table:(tbl)]
14*67e74705SXin Li#define NSLocalizedStringFromTableInBundle(key, tbl, bundle, comment)          \
15*67e74705SXin Li  [bundle localizedStringForKey:(key) value:@"" table:(tbl)]
16*67e74705SXin Li#define NSLocalizedStringWithDefaultValue(key, tbl, bundle, val, comment)      \
17*67e74705SXin Li  [bundle localizedStringForKey:(key) value:(val) table:(tbl)]
18*67e74705SXin Li@interface NSObject
19*67e74705SXin Li+ (id)alloc;
20*67e74705SXin Li- (id)init;
21*67e74705SXin Li@end
22*67e74705SXin Li@interface NSString : NSObject
23*67e74705SXin Li- (NSString *)stringByAppendingFormat:(NSString *)format, ...;
24*67e74705SXin Li+ (instancetype)stringWithFormat:(NSString *)format, ...;
25*67e74705SXin Li@end
26*67e74705SXin Li@interface NSBundle : NSObject
27*67e74705SXin Li+ (NSBundle *)mainBundle;
28*67e74705SXin Li- (NSString *)localizedStringForKey:(NSString *)key
29*67e74705SXin Li                              value:(NSString *)value
30*67e74705SXin Li                              table:(NSString *)tableName;
31*67e74705SXin Li@end
32*67e74705SXin Li@interface UILabel : NSObject
33*67e74705SXin Li@property(nullable, nonatomic, copy) NSString *text;
34*67e74705SXin Li@end
35*67e74705SXin Li@interface TestObject : NSObject
36*67e74705SXin Li@property(strong) NSString *text;
37*67e74705SXin Li@end
38*67e74705SXin Li
39*67e74705SXin Li@interface LocalizationTestSuite : NSObject
40*67e74705SXin Liint random();
41*67e74705SXin Li@property (assign) int unreadArticlesCount;
42*67e74705SXin Li@end
43*67e74705SXin Li#define MCLocalizedString(s) NSLocalizedString(s,nil);
44*67e74705SXin Li// Test cases begin here
45*67e74705SXin Li@implementation LocalizationTestSuite
46*67e74705SXin Li
47*67e74705SXin LiNSString *KHLocalizedString(NSString* key, NSString* comment) {
48*67e74705SXin Li    return NSLocalizedString(key, comment);
49*67e74705SXin Li}
50*67e74705SXin Li
51*67e74705SXin Li// An object passed in as an parameter's string member
52*67e74705SXin Li// should not be considered unlocalized
53*67e74705SXin Li- (void)testObjectAsArgument:(TestObject *)argumentObject {
54*67e74705SXin Li  UILabel *testLabel = [[UILabel alloc] init];
55*67e74705SXin Li
56*67e74705SXin Li  [testLabel setText:[argumentObject text]]; // no-warning
57*67e74705SXin Li  [testLabel setText:argumentObject.text];   // no-warning
58*67e74705SXin Li}
59*67e74705SXin Li
60*67e74705SXin Li- (void)testLocalizationErrorDetectedOnPathway {
61*67e74705SXin Li  UILabel *testLabel = [[UILabel alloc] init];
62*67e74705SXin Li  NSString *bar = NSLocalizedString(@"Hello", @"Comment");
63*67e74705SXin Li
64*67e74705SXin Li  if (random()) { // expected-note {{Taking true branch}}
65*67e74705SXin Li    bar = @"Unlocalized string"; // expected-note {{Non-localized string literal here}}
66*67e74705SXin Li  }
67*67e74705SXin Li
68*67e74705SXin Li  [testLabel setText:bar]; // expected-warning {{User-facing text should use localized string macro}} expected-note {{User-facing}}
69*67e74705SXin Li}
70*67e74705SXin Li
71*67e74705SXin Li- (void)testMultipleUnlocalizedStringsInSamePath {
72*67e74705SXin Li  UILabel *testLabel = [[UILabel alloc] init];
73*67e74705SXin Li  NSString *bar = @"Unlocalized string"; // no-note
74*67e74705SXin Li
75*67e74705SXin Li  bar = @"Unlocalized string"; // expected-note {{Non-localized string literal here}}
76*67e74705SXin Li
77*67e74705SXin Li  NSString *other = @"Other unlocalized string."; // no-note
78*67e74705SXin Li  (void)other;
79*67e74705SXin Li
80*67e74705SXin Li  NSString *same = @"Unlocalized string"; // no-note
81*67e74705SXin Li  (void)same;
82*67e74705SXin Li
83*67e74705SXin Li  [testLabel setText:bar]; // expected-warning {{User-facing text should use localized string macro}} expected-note {{User-facing}}
84*67e74705SXin Li}
85*67e74705SXin Li
86*67e74705SXin Li- (void)testOneCharacterStringsDoNotGiveAWarning {
87*67e74705SXin Li  UILabel *testLabel = [[UILabel alloc] init];
88*67e74705SXin Li  NSString *bar = NSLocalizedString(@"Hello", @"Comment");
89*67e74705SXin Li
90*67e74705SXin Li  if (random()) {
91*67e74705SXin Li    bar = @"-";
92*67e74705SXin Li  }
93*67e74705SXin Li
94*67e74705SXin Li  [testLabel setText:bar]; // no-warning
95*67e74705SXin Li}
96*67e74705SXin Li
97*67e74705SXin Li- (void)testOneCharacterUTFStringsDoNotGiveAWarning {
98*67e74705SXin Li  UILabel *testLabel = [[UILabel alloc] init];
99*67e74705SXin Li  NSString *bar = NSLocalizedString(@"Hello", @"Comment");
100*67e74705SXin Li
101*67e74705SXin Li  if (random()) {
102*67e74705SXin Li    bar = @"\u2014";
103*67e74705SXin Li  }
104*67e74705SXin Li
105*67e74705SXin Li  [testLabel setText:bar]; // no-warning
106*67e74705SXin Li}
107*67e74705SXin Li
108*67e74705SXin Li
109*67e74705SXin Li// Suppress diagnostic about user-facing string constants when the method name
110*67e74705SXin Li// contains the term "Debug".
111*67e74705SXin Li- (void)debugScreen:(UILabel *)label {
112*67e74705SXin Li  label.text = @"Unlocalized";
113*67e74705SXin Li}
114*67e74705SXin Li
115*67e74705SXin Li// Plural Misuse Checker Tests
116*67e74705SXin Li// These tests are modeled off incorrect uses of the many-one pattern
117*67e74705SXin Li// from real projects.
118*67e74705SXin Li
119*67e74705SXin Li- (NSString *)test1:(int)plural {
120*67e74705SXin Li    if (plural) {
121*67e74705SXin Li        return MCLocalizedString(@"TYPE_PLURAL"); // expected-warning {{Plural cases are not supported accross all languages. Use a .stringsdict file}} expected-note {{Plural}}
122*67e74705SXin Li    }
123*67e74705SXin Li    return MCLocalizedString(@"TYPE");
124*67e74705SXin Li}
125*67e74705SXin Li
126*67e74705SXin Li- (NSString *)test2:(int)numOfReminders {
127*67e74705SXin Li    if (numOfReminders > 0) {
128*67e74705SXin Li        return [NSString stringWithFormat:@"%@, %@", @"Test", (numOfReminders != 1) ? [NSString stringWithFormat:NSLocalizedString(@"%@ Reminders", @"Plural count of reminders"), numOfReminders] : [NSString stringWithFormat:NSLocalizedString(@"1 reminder", @"One reminder")]]; // expected-warning 2 {{Plural cases are not supported accross all languages. Use a .stringsdict file}} expected-note 2 {{Plural}}
129*67e74705SXin Li    }
130*67e74705SXin Li    return nil;
131*67e74705SXin Li}
132*67e74705SXin Li
133*67e74705SXin Li- (void)test3 {
134*67e74705SXin Li    NSString *count;
135*67e74705SXin Li    if (self.unreadArticlesCount > 1)
136*67e74705SXin Li    {
137*67e74705SXin Li        count = [count stringByAppendingFormat:@"%@", KHLocalizedString(@"New Stories", @"Plural count for new stories")]; // expected-warning {{Plural cases are not supported accross all languages. Use a .stringsdict file}} expected-note {{Plural}}
138*67e74705SXin Li    } else {
139*67e74705SXin Li        count = [count stringByAppendingFormat:@"%@",  KHLocalizedString(@"New Story", @"One new story")]; // expected-warning {{Plural cases are not supported accross all languages. Use a .stringsdict file}} expected-note {{Plural}}
140*67e74705SXin Li    }
141*67e74705SXin Li}
142*67e74705SXin Li
143*67e74705SXin Li- (NSString *)test4:(int)count {
144*67e74705SXin Li    if ( count == 1 )
145*67e74705SXin Li    {
146*67e74705SXin Li        return [NSString stringWithFormat:KHLocalizedString(@"value.singular",nil), count]; // expected-warning {{Plural cases are not supported accross all languages. Use a .stringsdict file}} expected-note {{Plural}}
147*67e74705SXin Li    } else {
148*67e74705SXin Li        return [NSString stringWithFormat:KHLocalizedString(@"value.plural",nil), count]; // expected-warning {{Plural cases are not supported accross all languages. Use a .stringsdict file}} expected-note {{Plural}}
149*67e74705SXin Li    }
150*67e74705SXin Li}
151*67e74705SXin Li
152*67e74705SXin Li- (NSString *)test5:(int)count {
153*67e74705SXin Li	int test = count == 1;
154*67e74705SXin Li    if (test)
155*67e74705SXin Li    {
156*67e74705SXin Li        return [NSString stringWithFormat:KHLocalizedString(@"value.singular",nil), count]; // expected-warning {{Plural cases are not supported accross all languages. Use a .stringsdict file}} expected-note {{Plural}}
157*67e74705SXin Li    } else {
158*67e74705SXin Li        return [NSString stringWithFormat:KHLocalizedString(@"value.plural",nil), count]; // expected-warning {{Plural cases are not supported accross all languages. Use a .stringsdict file}} expected-note {{Plural}}
159*67e74705SXin Li    }
160*67e74705SXin Li}
161*67e74705SXin Li
162*67e74705SXin Li// This tests the heuristic that the direct parent IfStmt must match the isCheckingPlurality confition to avoid false positives generated from complex code (generally the pattern we're looking for is simple If-Else)
163*67e74705SXin Li
164*67e74705SXin Li- (NSString *)test6:(int)sectionIndex {
165*67e74705SXin Li	int someOtherVariable = 0;
166*67e74705SXin Li    if (sectionIndex == 1)
167*67e74705SXin Li    {
168*67e74705SXin Li		// Do some other crazy stuff
169*67e74705SXin Li		if (someOtherVariable)
170*67e74705SXin Li        	return KHLocalizedString(@"OK",nil); // no-warning
171*67e74705SXin Li    } else {
172*67e74705SXin Li        return KHLocalizedString(@"value.plural",nil); // expected-warning {{Plural cases are not supported accross all languages. Use a .stringsdict file}} expected-note {{Plural}}
173*67e74705SXin Li    }
174*67e74705SXin Li	return nil;
175*67e74705SXin Li}
176*67e74705SXin Li
177*67e74705SXin Li// False positives that we are not accounting for involve matching the heuristic
178*67e74705SXin Li// of having 1 or 2 in the RHS of a BinaryOperator and having a localized string
179*67e74705SXin Li// in the body of the IfStmt. This is seen a lot when checking for the section
180*67e74705SXin Li// indexpath of something like a UITableView
181*67e74705SXin Li
182*67e74705SXin Li// - (NSString *)testNotAccountedFor:(int)sectionIndex {
183*67e74705SXin Li//     if (sectionIndex == 1)
184*67e74705SXin Li//     {
185*67e74705SXin Li//         return KHLocalizedString(@"1",nil); // false-positive
186*67e74705SXin Li//     } else if (sectionIndex == 2) {
187*67e74705SXin Li//     	return KHLocalizedString(@"2",nil); // false-positive
188*67e74705SXin Li//     } else if (sectionIndex == 3) {
189*67e74705SXin Li// 		return KHLocalizedString(@"3",nil); // no-false-positive
190*67e74705SXin Li// 	}
191*67e74705SXin Li// }
192*67e74705SXin Li
193*67e74705SXin Li// Potential test-cases to support in the future
194*67e74705SXin Li
195*67e74705SXin Li// - (NSString *)test7:(int)count {
196*67e74705SXin Li//     BOOL plural = count != 1;
197*67e74705SXin Li//     return KHLocalizedString(plural ? @"PluralString" : @"SingularString", @"");
198*67e74705SXin Li// }
199*67e74705SXin Li//
200*67e74705SXin Li// - (NSString *)test8:(BOOL)plural {
201*67e74705SXin Li//     return KHLocalizedString(([NSString stringWithFormat:@"RELATIVE_DATE_%@_%@", ((1 == 1) ? @"FUTURE" : @"PAST"), plural ? @"PLURAL" : @"SINGULAR"]));
202*67e74705SXin Li// }
203*67e74705SXin Li//
204*67e74705SXin Li//
205*67e74705SXin Li//
206*67e74705SXin Li// - (void)test9:(int)numberOfTimesEarned {
207*67e74705SXin Li//     NSString* localizedDescriptionKey;
208*67e74705SXin Li//     if (numberOfTimesEarned == 1) {
209*67e74705SXin Li//         localizedDescriptionKey = @"SINGULAR_%@";
210*67e74705SXin Li//     } else {
211*67e74705SXin Li//         localizedDescriptionKey = @"PLURAL_%@_%@";
212*67e74705SXin Li//     }
213*67e74705SXin Li//     NSLocalizedString(localizedDescriptionKey, nil);
214*67e74705SXin Li// }
215*67e74705SXin Li//
216*67e74705SXin Li// - (NSString *)test10 {
217*67e74705SXin Li//     NSInteger count = self.problems.count;
218*67e74705SXin Li//     NSString *title = [NSString stringWithFormat:@"%ld Problems", (long) count];
219*67e74705SXin Li//     if (count < 2) {
220*67e74705SXin Li//         if (count == 0) {
221*67e74705SXin Li//             title = [NSString stringWithFormat:@"No Problems Found"];
222*67e74705SXin Li//         } else {
223*67e74705SXin Li//             title = [NSString stringWithFormat:@"%ld Problem", (long) count];
224*67e74705SXin Li//         }
225*67e74705SXin Li//     }
226*67e74705SXin Li//     return title;
227*67e74705SXin Li// }
228*67e74705SXin Li
229*67e74705SXin Li@end
230*67e74705SXin Li
231*67e74705SXin Li
232*67e74705SXin Li// Suppress diagnostic about user-facing string constants when the class name
233*67e74705SXin Li// contains "Debug"
234*67e74705SXin Li@interface MyDebugView : NSObject
235*67e74705SXin Li@end
236*67e74705SXin Li
237*67e74705SXin Li@implementation MyDebugView
238*67e74705SXin Li- (void)setupScreen:(UILabel *)label {
239*67e74705SXin Li  label.text = @"Unlocalized"; // no-warning
240*67e74705SXin Li}
241*67e74705SXin Li@end
242