Index: cfe/trunk/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp =================================================================== --- cfe/trunk/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp +++ cfe/trunk/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp @@ -57,7 +57,7 @@ }; class NonLocalizedStringChecker - : public Checker> { @@ -79,9 +79,10 @@ void setNonLocalizedState(SVal S, CheckerContext &C) const; void setLocalizedState(SVal S, CheckerContext &C) const; - bool isAnnotatedAsLocalized(const Decl *D) const; - void reportLocalizationError(SVal S, const ObjCMethodCall &M, - CheckerContext &C, int argumentNumber = 0) const; + bool isAnnotatedAsReturningLocalized(const Decl *D) const; + bool isAnnotatedAsTakingLocalized(const Decl *D) const; + void reportLocalizationError(SVal S, const CallEvent &M, CheckerContext &C, + int argumentNumber = 0) const; int getLocalizedArgumentForSelector(const IdentifierInfo *Receiver, Selector S) const; @@ -97,6 +98,7 @@ void checkPreObjCMessage(const ObjCMethodCall &msg, CheckerContext &C) const; void checkPostObjCMessage(const ObjCMethodCall &msg, CheckerContext &C) const; void checkPostStmt(const ObjCStringLiteral *SL, CheckerContext &C) const; + void checkPreCall(const CallEvent &Call, CheckerContext &C) const; void checkPostCall(const CallEvent &Call, CheckerContext &C) const; }; @@ -644,7 +646,8 @@ /// Checks to see if the method / function declaration includes /// __attribute__((annotate("returns_localized_nsstring"))) -bool NonLocalizedStringChecker::isAnnotatedAsLocalized(const Decl *D) const { +bool NonLocalizedStringChecker::isAnnotatedAsReturningLocalized( + const Decl *D) const { if (!D) return false; return std::any_of( @@ -654,6 +657,19 @@ }); } +/// Checks to see if the method / function declaration includes +/// __attribute__((annotate("takes_localized_nsstring"))) +bool NonLocalizedStringChecker::isAnnotatedAsTakingLocalized( + const Decl *D) const { + if (!D) + return false; + return std::any_of( + D->specific_attr_begin(), + D->specific_attr_end(), [](const AnnotateAttr *Ann) { + return Ann->getAnnotation() == "takes_localized_nsstring"; + }); +} + /// Returns true if the given SVal is marked as Localized in the program state bool NonLocalizedStringChecker::hasLocalizedState(SVal S, CheckerContext &C) const { @@ -733,8 +749,7 @@ /// Reports a localization error for the passed in method call and SVal void NonLocalizedStringChecker::reportLocalizationError( - SVal S, const ObjCMethodCall &M, CheckerContext &C, - int argumentNumber) const { + SVal S, const CallEvent &M, CheckerContext &C, int argumentNumber) const { // Don't warn about localization errors in classes and methods that // may be debug code. @@ -832,7 +847,21 @@ } } - if (argumentNumber < 0) // There was no match in UIMethods + if (argumentNumber < 0) { // There was no match in UIMethods + if (const Decl *D = msg.getDecl()) { + if (const ObjCMethodDecl *OMD = dyn_cast_or_null(D)) { + auto formals = OMD->parameters(); + for (unsigned i = 0, ei = formals.size(); i != ei; ++i) { + if (isAnnotatedAsTakingLocalized(formals[i])) { + argumentNumber = i; + break; + } + } + } + } + } + + if (argumentNumber < 0) // Still no match return; SVal svTitle = msg.getArgSVal(argumentNumber); @@ -855,6 +884,25 @@ } } +void NonLocalizedStringChecker::checkPreCall(const CallEvent &Call, + CheckerContext &C) const { + const Decl *D = Call.getDecl(); + if (D && isa(D)) { + const FunctionDecl *FD = dyn_cast(D); + auto formals = FD->parameters(); + for (unsigned i = 0, + ei = std::min(unsigned(formals.size()), Call.getNumArgs()); + i != ei; ++i) { + if (isAnnotatedAsTakingLocalized(formals[i])) { + auto actual = Call.getArgSVal(i); + if (hasNonLocalizedState(actual, C)) { + reportLocalizationError(actual, Call, C, i + 1); + } + } + } + } +} + static inline bool isNSStringType(QualType T, ASTContext &Ctx) { const ObjCObjectPointerType *PT = T->getAs(); @@ -906,7 +954,7 @@ const IdentifierInfo *Identifier = Call.getCalleeIdentifier(); SVal sv = Call.getReturnValue(); - if (isAnnotatedAsLocalized(D) || LSF.count(Identifier) != 0) { + if (isAnnotatedAsReturningLocalized(D) || LSF.count(Identifier) != 0) { setLocalizedState(sv, C); } else if (isNSStringType(RT, C.getASTContext()) && !hasLocalizedState(sv, C)) { @@ -940,7 +988,8 @@ std::pair MethodDescription = {odInfo, S}; - if (LSM.count(MethodDescription) || isAnnotatedAsLocalized(msg.getDecl())) { + if (LSM.count(MethodDescription) || + isAnnotatedAsReturningLocalized(msg.getDecl())) { SVal sv = msg.getReturnValue(); setLocalizedState(sv, C); } Index: cfe/trunk/test/Analysis/localization-aggressive.m =================================================================== --- cfe/trunk/test/Analysis/localization-aggressive.m +++ cfe/trunk/test/Analysis/localization-aggressive.m @@ -61,8 +61,16 @@ NSString *CFNumberFormatterCreateStringWithNumber(float x); + (NSString *)forceLocalized:(NSString *)str __attribute__((annotate("returns_localized_nsstring"))); ++ (NSString *)takesLocalizedString: + (NSString *)__attribute__((annotate("takes_localized_nsstring")))str; @end +NSString * +takesLocalizedString(NSString *str + __attribute__((annotate("takes_localized_nsstring")))) { + return str; +} + // Test cases begin here @implementation LocalizationTestSuite @@ -75,6 +83,8 @@ return str; } ++ (NSString *) takesLocalizedString:(NSString *)str { return str; } + // An ObjC method that returns a localized string + (NSString *)unLocalizedStringMethod { return @"UnlocalizedString"; @@ -269,4 +279,13 @@ NSString *string2 = POSSIBLE_FALSE_POSITIVE(@"Hello", @"Hello"); // no-warning } +- (void)testTakesLocalizedString { + NSString *localized = NSLocalizedString(@"Hello", @"World"); + NSString *alsoLocalized = [LocalizationTestSuite takesLocalizedString:localized]; // no-warning + NSString *stillLocalized = [LocalizationTestSuite takesLocalizedString:alsoLocalized]; // no-warning + takesLocalizedString(stillLocalized); // no-warning + + [LocalizationTestSuite takesLocalizedString:@"not localized"]; // expected-warning {{User-facing text should use localized string macro}} + takesLocalizedString(@"not localized"); // expected-warning {{User-facing text should use localized string macro}} +} @end