diff options
| author | Anna Zaks <ganna@apple.com> | 2015-08-08 01:49:26 +0000 |
|---|---|---|
| committer | Anna Zaks <ganna@apple.com> | 2015-08-08 01:49:26 +0000 |
| commit | 38b496a05d504d06563b391fa82fc31f1099ff1e (patch) | |
| tree | d586175275d27521aebd246b55ad9e0f068c4f71 /clang/test/Analysis | |
| parent | e44962e9bb40d19000fdc361ad143f6d51ff0a01 (diff) | |
| download | bcm5719-llvm-38b496a05d504d06563b391fa82fc31f1099ff1e.tar.gz bcm5719-llvm-38b496a05d504d06563b391fa82fc31f1099ff1e.zip | |
[analyzer] Add checkers for OS X / iOS localizability issues
Add checkers that detect code-level localizability issues for OS X / iOS:
- A path sensitive checker that warns about uses of non-localized
NSStrings passed to UI methods expecting localized strings.
- A syntax checker that warns against not including a comment in
NSLocalizedString macros.
A patch by Kulpreet Chilana!
llvm-svn: 244389
Diffstat (limited to 'clang/test/Analysis')
| -rw-r--r-- | clang/test/Analysis/localization-aggressive.m | 243 | ||||
| -rw-r--r-- | clang/test/Analysis/localization.m | 86 |
2 files changed, 329 insertions, 0 deletions
diff --git a/clang/test/Analysis/localization-aggressive.m b/clang/test/Analysis/localization-aggressive.m new file mode 100644 index 00000000000..eba6e15ea08 --- /dev/null +++ b/clang/test/Analysis/localization-aggressive.m @@ -0,0 +1,243 @@ +// RUN: %clang_cc1 -analyze -fblocks -analyzer-store=region -analyzer-checker=alpha.osx.cocoa.NonLocalizedStringChecker -analyzer-checker=alpha.osx.cocoa.EmptyLocalizationContextChecker -verify -analyzer-config AggressiveReport=true %s + +// These declarations were reduced using Delta-Debugging from Foundation.h +// on Mac OS X. + +#define nil ((id)0) +#define NSLocalizedString(key, comment) \ + [[NSBundle mainBundle] localizedStringForKey:(key) value:@"" table:nil] +#define NSLocalizedStringFromTable(key, tbl, comment) \ + [[NSBundle mainBundle] localizedStringForKey:(key) value:@"" table:(tbl)] +#define NSLocalizedStringFromTableInBundle(key, tbl, bundle, comment) \ + [bundle localizedStringForKey:(key) value:@"" table:(tbl)] +#define NSLocalizedStringWithDefaultValue(key, tbl, bundle, val, comment) \ + [bundle localizedStringForKey:(key) value:(val) table:(tbl)] +#define CGFLOAT_TYPE double +typedef CGFLOAT_TYPE CGFloat; +struct CGPoint { + CGFloat x; + CGFloat y; +}; +typedef struct CGPoint CGPoint; +@interface NSObject ++ (id)alloc; +- (id)init; +@end +@class NSDictionary; +@interface NSString : NSObject +- (void)drawAtPoint:(CGPoint)point withAttributes:(NSDictionary *)attrs; ++ (instancetype)localizedStringWithFormat:(NSString *)format, ...; +@end +@interface NSBundle : NSObject ++ (NSBundle *)mainBundle; +- (NSString *)localizedStringForKey:(NSString *)key + value:(NSString *)value + table:(NSString *)tableName; +@end +@interface UILabel : NSObject +@property(nullable, nonatomic, copy) NSString *text; +- (void)accessibilitySetIdentification:(NSString *)ident; +@end +@interface TestObject : NSObject +@property(strong) NSString *text; +@end + +@interface LocalizationTestSuite : NSObject +NSString *ForceLocalized(NSString *str) + __attribute__((annotate("returns_localized_nsstring"))); +CGPoint CGPointMake(CGFloat x, CGFloat y); +int random(); +// This next one is a made up API +NSString *CFNumberFormatterCreateStringWithNumber(float x); ++ (NSString *)forceLocalized:(NSString *)str + __attribute__((annotate("returns_localized_nsstring"))); +@end + +// Test cases begin here +@implementation LocalizationTestSuite + +// A C-Funtion that returns a localized string because it has the +// "returns_localized_nsstring" annotation +NSString *ForceLocalized(NSString *str) { return str; } +// An ObjC method that returns a localized string because it has the +// "returns_localized_nsstring" annotation ++ (NSString *)forceLocalized:(NSString *)str { + return str; +} + +// An ObjC method that returns a localized string ++ (NSString *)unLocalizedStringMethod { + return @"UnlocalizedString"; +} + +- (void)testLocalizationErrorDetectedOnPathway { + UILabel *testLabel = [[UILabel alloc] init]; + NSString *bar = NSLocalizedString(@"Hello", @"Comment"); + + if (random()) { + bar = @"Unlocalized string"; + } + + [testLabel setText:bar]; // expected-warning {{String should be localized}} +} + +- (void)testLocalizationErrorDetectedOnNSString { + NSString *bar = NSLocalizedString(@"Hello", @"Comment"); + + if (random()) { + bar = @"Unlocalized string"; + } + + [bar drawAtPoint:CGPointMake(0, 0) withAttributes:nil]; // expected-warning {{String should be localized}} +} + +- (void)testNoLocalizationErrorDetectedFromCFunction { + UILabel *testLabel = [[UILabel alloc] init]; + NSString *bar = CFNumberFormatterCreateStringWithNumber(1); + + [testLabel setText:bar]; // no-warning +} + +- (void)testAnnotationAddsLocalizedStateForCFunction { + UILabel *testLabel = [[UILabel alloc] init]; + NSString *bar = NSLocalizedString(@"Hello", @"Comment"); + + if (random()) { + bar = @"Unlocalized string"; + } + + [testLabel setText:ForceLocalized(bar)]; // no-warning +} + +- (void)testAnnotationAddsLocalizedStateForObjCMethod { + UILabel *testLabel = [[UILabel alloc] init]; + NSString *bar = NSLocalizedString(@"Hello", @"Comment"); + + if (random()) { + bar = @"Unlocalized string"; + } + + [testLabel setText:[LocalizationTestSuite forceLocalized:bar]]; // no-warning +} + +// An empty string literal @"" should not raise an error +- (void)testEmptyStringLiteralHasLocalizedState { + UILabel *testLabel = [[UILabel alloc] init]; + NSString *bar = @""; + + [testLabel setText:bar]; // no-warning +} + +// An empty string literal @"" inline should not raise an error +- (void)testInlineEmptyStringLiteralHasLocalizedState { + UILabel *testLabel = [[UILabel alloc] init]; + [testLabel setText:@""]; // no-warning +} + +// An string literal @"Hello" inline should raise an error +- (void)testInlineStringLiteralHasLocalizedState { + UILabel *testLabel = [[UILabel alloc] init]; + [testLabel setText:@"Hello"]; // expected-warning {{String should be localized}} +} + +// A nil string should not raise an error +- (void)testNilStringIsNotMarkedAsUnlocalized { + UILabel *testLabel = [[UILabel alloc] init]; + [testLabel setText:nil]; // no-warning +} + +// A method that takes in a localized string and returns a string +// most likely that string is localized. +- (void)testLocalizedStringArgument { + UILabel *testLabel = [[UILabel alloc] init]; + NSString *localizedString = NSLocalizedString(@"Hello", @"Comment"); + + NSString *combinedString = + [NSString localizedStringWithFormat:@"%@", localizedString]; + + [testLabel setText:combinedString]; // no-warning +} + +// A String passed in as a an parameter should not be considered +// unlocalized +- (void)testLocalizedStringAsArgument:(NSString *)argumentString { + UILabel *testLabel = [[UILabel alloc] init]; + + [testLabel setText:argumentString]; // no-warning +} + +// A String passed into another method that calls a method that +// requires a localized string should give an error +- (void)localizedStringAsArgument:(NSString *)argumentString { + UILabel *testLabel = [[UILabel alloc] init]; + + [testLabel setText:argumentString]; // expected-warning {{String should be localized}} +} + +// The warning is expected to be seen in localizedStringAsArgument: body +- (void)testLocalizedStringAsArgumentOtherMethod:(NSString *)argumentString { + [self localizedStringAsArgument:@"UnlocalizedString"]; +} + +// [LocalizationTestSuite unLocalizedStringMethod] returns an unlocalized string +// so we expect an error. Unfrtunately, it probably doesn't make a difference +// what [LocalizationTestSuite unLocalizedStringMethod] returns since all +// string values returned are marked as Unlocalized in aggressive reporting. +- (void)testUnLocalizedStringMethod { + UILabel *testLabel = [[UILabel alloc] init]; + NSString *bar = NSLocalizedString(@"Hello", @"Comment"); + + [testLabel setText:[LocalizationTestSuite unLocalizedStringMethod]]; // expected-warning {{String should be localized}} +} + +// This is the reverse situation: accessibilitySetIdentification: doesn't care +// about localization so we don't expect a warning +- (void)testMethodNotInRequiresLocalizedStringMethods { + UILabel *testLabel = [[UILabel alloc] init]; + + [testLabel accessibilitySetIdentification:@"UnlocalizedString"]; // no-warning +} + +// EmptyLocalizationContextChecker tests +#define HOM(s) YOLOC(s) +#define YOLOC(x) NSLocalizedString(x, nil) + +- (void)testNilLocalizationContext { + NSString *string = NSLocalizedString(@"LocalizedString", nil); // expected-warning {{Localized string macro should include a non-empty comment for translators}} + NSString *string2 = NSLocalizedString(@"LocalizedString", nil); // expected-warning {{Localized string macro should include a non-empty comment for translators}} + NSString *string3 = NSLocalizedString(@"LocalizedString", nil); // expected-warning {{Localized string macro should include a non-empty comment for translators}} +} + +- (void)testEmptyLocalizationContext { + NSString *string = NSLocalizedString(@"LocalizedString", @""); // expected-warning {{Localized string macro should include a non-empty comment for translators}} + NSString *string2 = NSLocalizedString(@"LocalizedString", @" "); // expected-warning {{Localized string macro should include a non-empty comment for translators}} + NSString *string3 = NSLocalizedString(@"LocalizedString", @" "); // expected-warning {{Localized string macro should include a non-empty comment for translators}} +} + +- (void)testNSLocalizedStringVariants { + NSString *string = NSLocalizedStringFromTable(@"LocalizedString", nil, @""); // expected-warning {{Localized string macro should include a non-empty comment for translators}} + NSString *string2 = NSLocalizedStringFromTableInBundle(@"LocalizedString", nil, [[NSBundle alloc] init],@""); // expected-warning {{Localized string macro should include a non-empty comment for translators}} + NSString *string3 = NSLocalizedStringWithDefaultValue(@"LocalizedString", nil, [[NSBundle alloc] init], nil,@""); // expected-warning {{Localized string macro should include a non-empty comment for translators}} +} + +- (void)testMacroExpansionNilString { + NSString *string = YOLOC(@"Hello"); // expected-warning {{Localized string macro should include a non-empty comment for translators}} + NSString *string2 = HOM(@"Hello"); // expected-warning {{Localized string macro should include a non-empty comment for translators}} + NSString *string3 = NSLocalizedString((0 ? @"Critical" : @"Current"),nil); // expected-warning {{Localized string macro should include a non-empty comment for translators}} +} + +#define KCLocalizedString(x,comment) NSLocalizedString(x, comment) +#define POSSIBLE_FALSE_POSITIVE(s,other) KCLocalizedString(s,@"Comment") + +- (void)testNoWarningForNilCommentPassedIntoOtherMacro { + NSString *string = KCLocalizedString(@"Hello",@""); // no-warning + NSString *string2 = KCLocalizedString(@"Hello",nil); // no-warning + NSString *string3 = KCLocalizedString(@"Hello",@"Comment"); // no-warning +} + +- (void)testPossibleFalsePositiveSituationAbove { + NSString *string = POSSIBLE_FALSE_POSITIVE(@"Hello", nil); // no-warning + NSString *string2 = POSSIBLE_FALSE_POSITIVE(@"Hello", @"Hello"); // no-warning +} + +@end diff --git a/clang/test/Analysis/localization.m b/clang/test/Analysis/localization.m new file mode 100644 index 00000000000..83ba1e6f24b --- /dev/null +++ b/clang/test/Analysis/localization.m @@ -0,0 +1,86 @@ +// RUN: %clang_cc1 -analyze -fblocks -analyzer-store=region -analyzer-checker=alpha.osx.cocoa.NonLocalizedStringChecker -analyzer-checker=alpha.osx.cocoa.EmptyLocalizationContextChecker -verify %s + +// The larger set of tests in located in localization.m. These are tests +// specific for non-aggressive reporting. + +// These declarations were reduced using Delta-Debugging from Foundation.h +// on Mac OS X. + +#define nil ((id)0) +#define NSLocalizedString(key, comment) \ + [[NSBundle mainBundle] localizedStringForKey:(key) value:@"" table:nil] +#define NSLocalizedStringFromTable(key, tbl, comment) \ + [[NSBundle mainBundle] localizedStringForKey:(key) value:@"" table:(tbl)] +#define NSLocalizedStringFromTableInBundle(key, tbl, bundle, comment) \ + [bundle localizedStringForKey:(key) value:@"" table:(tbl)] +#define NSLocalizedStringWithDefaultValue(key, tbl, bundle, val, comment) \ + [bundle localizedStringForKey:(key) value:(val) table:(tbl)] +@interface NSObject ++ (id)alloc; +- (id)init; +@end +@interface NSString : NSObject +@end +@interface NSBundle : NSObject ++ (NSBundle *)mainBundle; +- (NSString *)localizedStringForKey:(NSString *)key + value:(NSString *)value + table:(NSString *)tableName; +@end +@interface UILabel : NSObject +@property(nullable, nonatomic, copy) NSString *text; +@end +@interface TestObject : NSObject +@property(strong) NSString *text; +@end + +@interface LocalizationTestSuite : NSObject +int random(); +@end + +// Test cases begin here +@implementation LocalizationTestSuite + +// An object passed in as an parameter's string member +// should not be considered unlocalized +- (void)testObjectAsArgument:(TestObject *)argumentObject { + UILabel *testLabel = [[UILabel alloc] init]; + + [testLabel setText:[argumentObject text]]; // no-warning + [testLabel setText:argumentObject.text]; // no-warning +} + +- (void)testLocalizationErrorDetectedOnPathway { + UILabel *testLabel = [[UILabel alloc] init]; + NSString *bar = NSLocalizedString(@"Hello", @"Comment"); + + if (random()) { + bar = @"Unlocalized string"; + } + + [testLabel setText:bar]; // expected-warning {{String should be localized}} +} + +- (void)testOneCharacterStringsDoNotGiveAWarning { + UILabel *testLabel = [[UILabel alloc] init]; + NSString *bar = NSLocalizedString(@"Hello", @"Comment"); + + if (random()) { + bar = @"-"; + } + + [testLabel setText:bar]; // no-warning +} + +- (void)testOneCharacterUTFStringsDoNotGiveAWarning { + UILabel *testLabel = [[UILabel alloc] init]; + NSString *bar = NSLocalizedString(@"Hello", @"Comment"); + + if (random()) { + bar = @"—"; + } + + [testLabel setText:bar]; // no-warning +} + +@end |

