[analyzer] Add annotation for functions taking user-facing strings

There was already a returns_localized_nsstring annotation to indicate
that the return value could be passed to UIKit methods that would
display them. However, those UIKit methods were hard-coded, and it was
not possible to indicate that other classes/methods in a code-base would
do the same.

The takes_localized_nsstring annotation can be put on function
parameters and selector parameters to indicate that those will also show
the string to the user.

Differential Revision: https://reviews.llvm.org/D35186

llvm-svn: 308012
This commit is contained in:
Erik Verbruggen 2017-07-14 10:24:36 +00:00
parent d374c5993b
commit 49db030626
2 changed files with 78 additions and 10 deletions

View File

@ -57,7 +57,7 @@ public:
};
class NonLocalizedStringChecker
: public Checker<check::PostCall, check::PreObjCMessage,
: public Checker<check::PreCall, check::PostCall, check::PreObjCMessage,
check::PostObjCMessage,
check::PostStmt<ObjCStringLiteral>> {
@ -79,9 +79,10 @@ class NonLocalizedStringChecker
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 @@ public:
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 @@ void NonLocalizedStringChecker::initLocStringsMethods(ASTContext &Ctx) const {
/// 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 @@ bool NonLocalizedStringChecker::isAnnotatedAsLocalized(const Decl *D) const {
});
}
/// 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<AnnotateAttr>(),
D->specific_attr_end<AnnotateAttr>(), [](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 @@ static bool isDebuggingContext(CheckerContext &C) {
/// 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 @@ void NonLocalizedStringChecker::checkPreObjCMessage(const ObjCMethodCall &msg,
}
}
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<ObjCMethodDecl>(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::checkPreObjCMessage(const ObjCMethodCall &msg,
}
}
void NonLocalizedStringChecker::checkPreCall(const CallEvent &Call,
CheckerContext &C) const {
const Decl *D = Call.getDecl();
if (D && isa<FunctionDecl>(D)) {
const FunctionDecl *FD = dyn_cast<FunctionDecl>(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<ObjCObjectPointerType>();
@ -906,7 +954,7 @@ void NonLocalizedStringChecker::checkPostCall(const CallEvent &Call,
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 @@ void NonLocalizedStringChecker::checkPostObjCMessage(const ObjCMethodCall &msg,
std::pair<const IdentifierInfo *, Selector> MethodDescription = {odInfo, S};
if (LSM.count(MethodDescription) || isAnnotatedAsLocalized(msg.getDecl())) {
if (LSM.count(MethodDescription) ||
isAnnotatedAsReturningLocalized(msg.getDecl())) {
SVal sv = msg.getReturnValue();
setLocalizedState(sv, C);
}

View File

@ -61,8 +61,16 @@ int random();
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 @@ NSString *ForceLocalized(NSString *str) { return str; }
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 *ForceLocalized(NSString *str) { return str; }
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