1// RUN: %clang_cc1 -analyze -fblocks -analyzer-store=region -analyzer-checker=optin.osx.cocoa.localizability.NonLocalizedStringChecker -analyzer-checker=optin.osx.cocoa.localizability.EmptyLocalizationContextChecker -verify -analyzer-config AggressiveReport=true %s 2 3// These declarations were reduced using Delta-Debugging from Foundation.h 4// on Mac OS X. 5 6#define nil ((id)0) 7#define NSLocalizedString(key, comment) \ 8 [[NSBundle mainBundle] localizedStringForKey:(key) value:@"" table:nil] 9#define NSLocalizedStringFromTable(key, tbl, comment) \ 10 [[NSBundle mainBundle] localizedStringForKey:(key) value:@"" table:(tbl)] 11#define NSLocalizedStringFromTableInBundle(key, tbl, bundle, comment) \ 12 [bundle localizedStringForKey:(key) value:@"" table:(tbl)] 13#define NSLocalizedStringWithDefaultValue(key, tbl, bundle, val, comment) \ 14 [bundle localizedStringForKey:(key) value:(val) table:(tbl)] 15#define CGFLOAT_TYPE double 16typedef CGFLOAT_TYPE CGFloat; 17struct CGPoint { 18 CGFloat x; 19 CGFloat y; 20}; 21typedef struct CGPoint CGPoint; 22@interface NSObject 23+ (id)alloc; 24- (id)init; 25@end 26@class NSDictionary; 27@interface NSString : NSObject 28- (void)drawAtPoint:(CGPoint)point withAttributes:(NSDictionary *)attrs; 29+ (instancetype)localizedStringWithFormat:(NSString *)format, ...; 30@end 31@interface NSBundle : NSObject 32+ (NSBundle *)mainBundle; 33- (NSString *)localizedStringForKey:(NSString *)key 34 value:(NSString *)value 35 table:(NSString *)tableName; 36@end 37@protocol UIAccessibility 38- (void)accessibilitySetIdentification:(NSString *)ident; 39- (void)setAccessibilityLabel:(NSString *)label; 40@end 41@interface UILabel : NSObject <UIAccessibility> 42@property(nullable, nonatomic, copy) NSString *text; 43@end 44@interface TestObject : NSObject 45@property(strong) NSString *text; 46@end 47@interface NSView : NSObject 48@property (strong) NSString *toolTip; 49@end 50@interface NSViewSubclass : NSView 51@end 52 53@interface LocalizationTestSuite : NSObject 54NSString *ForceLocalized(NSString *str) 55 __attribute__((annotate("returns_localized_nsstring"))); 56CGPoint CGPointMake(CGFloat x, CGFloat y); 57int random(); 58// This next one is a made up API 59NSString *CFNumberFormatterCreateStringWithNumber(float x); 60+ (NSString *)forceLocalized:(NSString *)str 61 __attribute__((annotate("returns_localized_nsstring"))); 62@end 63 64// Test cases begin here 65@implementation LocalizationTestSuite 66 67// A C-Funtion that returns a localized string because it has the 68// "returns_localized_nsstring" annotation 69NSString *ForceLocalized(NSString *str) { return str; } 70// An ObjC method that returns a localized string because it has the 71// "returns_localized_nsstring" annotation 72+ (NSString *)forceLocalized:(NSString *)str { 73 return str; 74} 75 76// An ObjC method that returns a localized string 77+ (NSString *)unLocalizedStringMethod { 78 return @"UnlocalizedString"; 79} 80 81- (void)testLocalizationErrorDetectedOnPathway { 82 UILabel *testLabel = [[UILabel alloc] init]; 83 NSString *bar = NSLocalizedString(@"Hello", @"Comment"); 84 85 if (random()) { 86 bar = @"Unlocalized string"; 87 } 88 89 [testLabel setText:bar]; // expected-warning {{User-facing text should use localized string macro}} 90} 91 92- (void)testLocalizationErrorDetectedOnNSString { 93 NSString *bar = NSLocalizedString(@"Hello", @"Comment"); 94 95 if (random()) { 96 bar = @"Unlocalized string"; 97 } 98 99 [bar drawAtPoint:CGPointMake(0, 0) withAttributes:nil]; // expected-warning {{User-facing text should use localized string macro}} 100} 101 102- (void)testNoLocalizationErrorDetectedFromCFunction { 103 UILabel *testLabel = [[UILabel alloc] init]; 104 NSString *bar = CFNumberFormatterCreateStringWithNumber(1); 105 106 [testLabel setText:bar]; // no-warning 107} 108 109- (void)testAnnotationAddsLocalizedStateForCFunction { 110 UILabel *testLabel = [[UILabel alloc] init]; 111 NSString *bar = NSLocalizedString(@"Hello", @"Comment"); 112 113 if (random()) { 114 bar = @"Unlocalized string"; 115 } 116 117 [testLabel setText:ForceLocalized(bar)]; // no-warning 118} 119 120- (void)testAnnotationAddsLocalizedStateForObjCMethod { 121 UILabel *testLabel = [[UILabel alloc] init]; 122 NSString *bar = NSLocalizedString(@"Hello", @"Comment"); 123 124 if (random()) { 125 bar = @"Unlocalized string"; 126 } 127 128 [testLabel setText:[LocalizationTestSuite forceLocalized:bar]]; // no-warning 129} 130 131// An empty string literal @"" should not raise an error 132- (void)testEmptyStringLiteralHasLocalizedState { 133 UILabel *testLabel = [[UILabel alloc] init]; 134 NSString *bar = @""; 135 136 [testLabel setText:bar]; // no-warning 137} 138 139// An empty string literal @"" inline should not raise an error 140- (void)testInlineEmptyStringLiteralHasLocalizedState { 141 UILabel *testLabel = [[UILabel alloc] init]; 142 [testLabel setText:@""]; // no-warning 143} 144 145// An string literal @"Hello" inline should raise an error 146- (void)testInlineStringLiteralHasLocalizedState { 147 UILabel *testLabel = [[UILabel alloc] init]; 148 [testLabel setText:@"Hello"]; // expected-warning {{User-facing text should use localized string macro}} 149} 150 151// A nil string should not raise an error 152- (void)testNilStringIsNotMarkedAsUnlocalized { 153 UILabel *testLabel = [[UILabel alloc] init]; 154 [testLabel setText:nil]; // no-warning 155} 156 157// A method that takes in a localized string and returns a string 158// most likely that string is localized. 159- (void)testLocalizedStringArgument { 160 UILabel *testLabel = [[UILabel alloc] init]; 161 NSString *localizedString = NSLocalizedString(@"Hello", @"Comment"); 162 163 NSString *combinedString = 164 [NSString localizedStringWithFormat:@"%@", localizedString]; 165 166 [testLabel setText:combinedString]; // no-warning 167} 168 169// A String passed in as a an parameter should not be considered 170// unlocalized 171- (void)testLocalizedStringAsArgument:(NSString *)argumentString { 172 UILabel *testLabel = [[UILabel alloc] init]; 173 174 [testLabel setText:argumentString]; // no-warning 175} 176 177// The warning is expected to be seen in localizedStringAsArgument: body 178- (void)testLocalizedStringAsArgumentOtherMethod:(NSString *)argumentString { 179 [self localizedStringAsArgument:@"UnlocalizedString"]; 180} 181 182// A String passed into another method that calls a method that 183// requires a localized string should give an error 184- (void)localizedStringAsArgument:(NSString *)argumentString { 185 UILabel *testLabel = [[UILabel alloc] init]; 186 187 [testLabel setText:argumentString]; // expected-warning {{User-facing text should use localized string macro}} 188} 189 190// [LocalizationTestSuite unLocalizedStringMethod] returns an unlocalized string 191// so we expect an error. Unfrtunately, it probably doesn't make a difference 192// what [LocalizationTestSuite unLocalizedStringMethod] returns since all 193// string values returned are marked as Unlocalized in aggressive reporting. 194- (void)testUnLocalizedStringMethod { 195 UILabel *testLabel = [[UILabel alloc] init]; 196 NSString *bar = NSLocalizedString(@"Hello", @"Comment"); 197 198 [testLabel setText:[LocalizationTestSuite unLocalizedStringMethod]]; // expected-warning {{User-facing text should use localized string macro}} 199} 200 201// This is the reverse situation: accessibilitySetIdentification: doesn't care 202// about localization so we don't expect a warning 203- (void)testMethodNotInRequiresLocalizedStringMethods { 204 UILabel *testLabel = [[UILabel alloc] init]; 205 206 [testLabel accessibilitySetIdentification:@"UnlocalizedString"]; // no-warning 207} 208 209// An NSView subclass should raise a warning for methods in NSView that 210// require localized strings 211- (void)testRequiresLocalizationMethodFromSuperclass { 212 NSViewSubclass *s = [[NSViewSubclass alloc] init]; 213 NSString *bar = @"UnlocalizedString"; 214 215 [s setToolTip:bar]; // expected-warning {{User-facing text should use localized string macro}} 216} 217 218- (void)testRequiresLocalizationMethodFromProtocol { 219 UILabel *testLabel = [[UILabel alloc] init]; 220 221 [testLabel setAccessibilityLabel:@"UnlocalizedString"]; // expected-warning {{User-facing text should use localized string macro}} 222} 223 224// EmptyLocalizationContextChecker tests 225#define HOM(s) YOLOC(s) 226#define YOLOC(x) NSLocalizedString(x, nil) 227 228- (void)testNilLocalizationContext { 229 NSString *string = NSLocalizedString(@"LocalizedString", nil); // expected-warning {{Localized string macro should include a non-empty comment for translators}} 230 NSString *string2 = NSLocalizedString(@"LocalizedString", nil); // expected-warning {{Localized string macro should include a non-empty comment for translators}} 231 NSString *string3 = NSLocalizedString(@"LocalizedString", nil); // expected-warning {{Localized string macro should include a non-empty comment for translators}} 232} 233 234- (void)testEmptyLocalizationContext { 235 NSString *string = NSLocalizedString(@"LocalizedString", @""); // expected-warning {{Localized string macro should include a non-empty comment for translators}} 236 NSString *string2 = NSLocalizedString(@"LocalizedString", @" "); // expected-warning {{Localized string macro should include a non-empty comment for translators}} 237 NSString *string3 = NSLocalizedString(@"LocalizedString", @" "); // expected-warning {{Localized string macro should include a non-empty comment for translators}} 238} 239 240- (void)testNSLocalizedStringVariants { 241 NSString *string = NSLocalizedStringFromTable(@"LocalizedString", nil, @""); // expected-warning {{Localized string macro should include a non-empty comment for translators}} 242 NSString *string2 = NSLocalizedStringFromTableInBundle(@"LocalizedString", nil, [[NSBundle alloc] init],@""); // expected-warning {{Localized string macro should include a non-empty comment for translators}} 243 NSString *string3 = NSLocalizedStringWithDefaultValue(@"LocalizedString", nil, [[NSBundle alloc] init], nil,@""); // expected-warning {{Localized string macro should include a non-empty comment for translators}} 244} 245 246- (void)testMacroExpansionNilString { 247 NSString *string = YOLOC(@"Hello"); // expected-warning {{Localized string macro should include a non-empty comment for translators}} 248 NSString *string2 = HOM(@"Hello"); // expected-warning {{Localized string macro should include a non-empty comment for translators}} 249 NSString *string3 = NSLocalizedString((0 ? @"Critical" : @"Current"),nil); // expected-warning {{Localized string macro should include a non-empty comment for translators}} 250} 251 252#define KCLocalizedString(x,comment) NSLocalizedString(x, comment) 253#define POSSIBLE_FALSE_POSITIVE(s,other) KCLocalizedString(s,@"Comment") 254 255- (void)testNoWarningForNilCommentPassedIntoOtherMacro { 256 NSString *string = KCLocalizedString(@"Hello",@""); // no-warning 257 NSString *string2 = KCLocalizedString(@"Hello",nil); // no-warning 258 NSString *string3 = KCLocalizedString(@"Hello",@"Comment"); // no-warning 259} 260 261- (void)testPossibleFalsePositiveSituationAbove { 262 NSString *string = POSSIBLE_FALSE_POSITIVE(@"Hello", nil); // no-warning 263 NSString *string2 = POSSIBLE_FALSE_POSITIVE(@"Hello", @"Hello"); // no-warning 264} 265 266@end 267