mirror of https://github.com/microsoft/clang.git
[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 git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@308012 91177308-0d34-0410-b5e6-96231b3b80d8
This commit is contained in:
parent
baa1507823
commit
6e00f9f02b
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue