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