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