r244389 - [analyzer] Add checkers for OS X / iOS localizability issues
Chandler Carruth via cfe-commits
cfe-commits at lists.llvm.org
Fri Aug 7 21:57:22 PDT 2015
NP, thanks for the quick fixes!
On Fri, Aug 7, 2015 at 9:56 PM Anna Zaks <ganna at apple.com> wrote:
> Sorry about the breakage! I’ve reverted the commit in r244394.
>
> Anna.
>
> On Aug 7, 2015, at 9:08 PM, Chandler Carruth <chandlerc at google.com> wrote:
>
> Note that this is still failing on bots even after the windows fix:
>
> http://lab.llvm.org:8011/builders/clang-x86_64-ubuntu-gdb-75/builds/24169
>
> On Fri, Aug 7, 2015 at 6:50 PM Anna Zaks via cfe-commits <
> cfe-commits at lists.llvm.org> wrote:
>
>> Author: zaks
>> Date: Fri Aug 7 20:49:26 2015
>> New Revision: 244389
>>
>> URL: http://llvm.org/viewvc/llvm-project?rev=244389&view=rev
>> Log:
>> [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!
>>
>> Added:
>> cfe/trunk/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp
>> cfe/trunk/test/Analysis/localization-aggressive.m
>> cfe/trunk/test/Analysis/localization.m
>> Modified:
>> cfe/trunk/lib/StaticAnalyzer/Checkers/CMakeLists.txt
>> cfe/trunk/lib/StaticAnalyzer/Checkers/Checkers.td
>>
>> Modified: cfe/trunk/lib/StaticAnalyzer/Checkers/CMakeLists.txt
>> URL:
>> http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/StaticAnalyzer/Checkers/CMakeLists.txt?rev=244389&r1=244388&r2=244389&view=diff
>>
>> ==============================================================================
>> --- cfe/trunk/lib/StaticAnalyzer/Checkers/CMakeLists.txt (original)
>> +++ cfe/trunk/lib/StaticAnalyzer/Checkers/CMakeLists.txt Fri Aug 7
>> 20:49:26 2015
>> @@ -39,6 +39,7 @@ add_clang_library(clangStaticAnalyzerChe
>> IdenticalExprChecker.cpp
>> IvarInvalidationChecker.cpp
>> LLVMConventionsChecker.cpp
>> + LocalizationChecker.cpp
>> MacOSKeychainAPIChecker.cpp
>> MacOSXAPIChecker.cpp
>> MallocChecker.cpp
>>
>> Modified: cfe/trunk/lib/StaticAnalyzer/Checkers/Checkers.td
>> URL:
>> http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/StaticAnalyzer/Checkers/Checkers.td?rev=244389&r1=244388&r2=244389&view=diff
>>
>> ==============================================================================
>> --- cfe/trunk/lib/StaticAnalyzer/Checkers/Checkers.td (original)
>> +++ cfe/trunk/lib/StaticAnalyzer/Checkers/Checkers.td Fri Aug 7 20:49:26
>> 2015
>> @@ -452,6 +452,14 @@ def DirectIvarAssignmentForAnnotatedFunc
>> HelpText<"Check for direct assignments to instance variables in the
>> methods annotated with objc_no_direct_instance_variable_assignment">,
>> DescFile<"DirectIvarAssignment.cpp">;
>>
>> +def NonLocalizedStringChecker : Checker<"NonLocalizedStringChecker">,
>> + HelpText<"Warns about uses of non-localized NSStrings passed to UI
>> methods expecting localized NSStrings">,
>> + DescFile<"LocalizationChecker.cpp">;
>> +
>> +def EmptyLocalizationContextChecker :
>> Checker<"EmptyLocalizationContextChecker">,
>> + HelpText<"Check that NSLocalizedString macros include a comment for
>> context">,
>> + DescFile<"LocalizationChecker.cpp">;
>> +
>> } // end "alpha.osx.cocoa"
>>
>> let ParentPackage = CoreFoundation in {
>>
>> Added: cfe/trunk/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp
>> URL:
>> http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp?rev=244389&view=auto
>>
>> ==============================================================================
>> --- cfe/trunk/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp (added)
>> +++ cfe/trunk/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp Fri
>> Aug 7 20:49:26 2015
>> @@ -0,0 +1,559 @@
>> +//=- LocalizationChecker.cpp -------------------------------------*- C++
>> -*-==//
>> +//
>> +// The LLVM Compiler Infrastructure
>> +//
>> +// This file is distributed under the University of Illinois Open Source
>> +// License. See LICENSE.TXT for details.
>> +//
>>
>> +//===----------------------------------------------------------------------===//
>> +//
>> +// This file defines a set of checks for localizability including:
>> +// 1) A checker that warns about uses of non-localized NSStrings passed
>> to
>> +// UI methods expecting localized strings
>> +// 2) A syntactic checker that warns against the bad practice of
>> +// not including a comment in NSLocalizedString macros.
>> +//
>>
>> +//===----------------------------------------------------------------------===//
>> +
>> +#include "ClangSACheckers.h"
>> +#include "SelectorExtras.h"
>> +#include "clang/AST/Attr.h"
>> +#include "clang/AST/Decl.h"
>> +#include "clang/AST/DeclObjC.h"
>> +#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
>> +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
>> +#include "clang/StaticAnalyzer/Core/Checker.h"
>> +#include "clang/StaticAnalyzer/Core/CheckerManager.h"
>> +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
>> +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
>> +#include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h"
>> +#include "clang/Lex/Lexer.h"
>> +#include "clang/AST/RecursiveASTVisitor.h"
>> +#include "clang/AST/StmtVisitor.h"
>> +#include "llvm/Support/Unicode.h"
>> +
>> +using namespace clang;
>> +using namespace ento;
>> +
>> +namespace {
>> +struct LocalizedState {
>> +private:
>> + enum Kind { NonLocalized, Localized } K;
>> + LocalizedState(Kind InK) : K(InK) {}
>> +
>> +public:
>> + bool isLocalized() const { return K == Localized; }
>> + bool isNonLocalized() const { return K == NonLocalized; }
>> +
>> + static LocalizedState getLocalized() { return
>> LocalizedState(Localized); }
>> + static LocalizedState getNonLocalized() {
>> + return LocalizedState(NonLocalized);
>> + }
>> +
>> + // Overload the == operator
>> + bool operator==(const LocalizedState &X) const { return K == X.K; }
>> +
>> + // LLVMs equivalent of a hash function
>> + void Profile(llvm::FoldingSetNodeID &ID) const { ID.AddInteger(K); }
>> +};
>> +
>> +class NonLocalizedStringChecker
>> + : public Checker<check::PostCall, check::PreObjCMessage,
>> + check::PostObjCMessage,
>> + check::PostStmt<ObjCStringLiteral>> {
>> +
>> + mutable std::unique_ptr<BugType> BT;
>> +
>> + // Methods that require a localized string
>> + mutable std::map<StringRef, std::map<StringRef, uint8_t>> UIMethods;
>> + // Methods that return a localized string
>> + mutable llvm::SmallSet<std::pair<StringRef, StringRef>, 10> LSM;
>> + // C Functions that return a localized string
>> + mutable llvm::StringMap<char> LSF;
>> +
>> + void initUIMethods(ASTContext &Ctx) const;
>> + void initLocStringsMethods(ASTContext &Ctx) const;
>> +
>> + bool hasNonLocalizedState(SVal S, CheckerContext &C) const;
>> + bool hasLocalizedState(SVal S, CheckerContext &C) const;
>> + 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;
>> +
>> +public:
>> + NonLocalizedStringChecker();
>> +
>> + // When this parameter is set to true, the checker assumes all
>> + // methods that return NSStrings are unlocalized. Thus, more false
>> + // positives will be reported.
>> + DefaultBool IsAggressive;
>> +
>> + 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 checkPostCall(const CallEvent &Call, CheckerContext &C) const;
>> +};
>> +
>> +} // end anonymous namespace
>> +
>> +REGISTER_MAP_WITH_PROGRAMSTATE(LocalizedMemMap, const MemRegion *,
>> + LocalizedState)
>> +
>> +NonLocalizedStringChecker::NonLocalizedStringChecker() {
>> + BT.reset(new BugType(this, "Unlocalized string", "Localizability
>> Error"));
>> +}
>> +
>> +/// Initializes a list of methods that require a localized string
>> +/// Format: {"ClassName", {{"selectorName:", LocStringArg#}, ...}, ...}
>> +void NonLocalizedStringChecker::initUIMethods(ASTContext &Ctx) const {
>> + if (!UIMethods.empty())
>> + return;
>> +
>> + // TODO: This should eventually be a comprehensive list of UIKit
>> methods
>> +
>> + UIMethods = {{"UILabel", {{"setText:", 0}}},
>> + {"UIButton", {{"setText:", 0}}},
>> + {"NSButton", {{"setTitle:", 0}}},
>> + {"NSButtonCell", {{"setTitle:", 0}}},
>> + {"NSMenuItem", {{"setTitle:", 0}}},
>> + {"UIAlertAction", {{"actionWithTitle:style:handler:",
>> 0}}},
>> + {"UIAlertController",
>> + {{"alertControllerWithTitle:message:preferredStyle:",
>> 1}}},
>> + {"NSAttributedString",
>> + {{"initWithString:", 0}, {"initWithString:attributes:",
>> 0}}}};
>> +}
>> +
>> +/// Initializes a list of methods and C functions that return a
>> localized string
>> +void NonLocalizedStringChecker::initLocStringsMethods(ASTContext &Ctx)
>> const {
>> + if (!LSM.empty())
>> + return;
>> +
>> + LSM.insert({"NSBundle", "localizedStringForKey:value:table:"});
>> + LSM.insert({"NSDateFormatter", "stringFromDate:"});
>> + LSM.insert(
>> + {"NSDateFormatter",
>> "localizedStringFromDate:dateStyle:timeStyle:"});
>> + LSM.insert({"NSNumberFormatter", "stringFromNumber:"});
>> + LSM.insert({"UITextField", "text"});
>> + LSM.insert({"UITextView", "text"});
>> + LSM.insert({"UILabel", "text"});
>> +
>> + LSF.insert({"CFDateFormatterCreateStringWithDate", '\0'});
>> + LSF.insert({"CFDateFormatterCreateStringWithAbsoluteTime", '\0'});
>> + LSF.insert({"CFNumberFormatterCreateStringWithNumber", '\0'});
>> +}
>> +
>> +/// Checks to see if the method / function declaration includes
>> +/// __attribute__((annotate("returns_localized_nsstring")))
>> +bool NonLocalizedStringChecker::isAnnotatedAsLocalized(const Decl *D)
>> const {
>> + return std::any_of(
>> + D->specific_attr_begin<AnnotateAttr>(),
>> + D->specific_attr_end<AnnotateAttr>(), [](const AnnotateAttr *Ann) {
>> + return Ann->getAnnotation() == "returns_localized_nsstring";
>> + });
>> +}
>> +
>> +/// Returns true if the given SVal is marked as Localized in the program
>> state
>> +bool NonLocalizedStringChecker::hasLocalizedState(SVal S,
>> + CheckerContext &C)
>> const {
>> + const MemRegion *mt = S.getAsRegion();
>> + if (mt) {
>> + const LocalizedState *LS = C.getState()->get<LocalizedMemMap>(mt);
>> + if (LS && LS->isLocalized())
>> + return true;
>> + }
>> + return false;
>> +}
>> +
>> +/// Returns true if the given SVal is marked as NonLocalized in the
>> program
>> +/// state
>> +bool NonLocalizedStringChecker::hasNonLocalizedState(SVal S,
>> + CheckerContext &C)
>> const {
>> + const MemRegion *mt = S.getAsRegion();
>> + if (mt) {
>> + const LocalizedState *LS = C.getState()->get<LocalizedMemMap>(mt);
>> + if (LS && LS->isNonLocalized())
>> + return true;
>> + }
>> + return false;
>> +}
>> +
>> +/// Marks the given SVal as Localized in the program state
>> +void NonLocalizedStringChecker::setLocalizedState(const SVal S,
>> + CheckerContext &C)
>> const {
>> + const MemRegion *mt = S.getAsRegion();
>> + if (mt) {
>> + ProgramStateRef State =
>> + C.getState()->set<LocalizedMemMap>(mt,
>> LocalizedState::getLocalized());
>> + C.addTransition(State);
>> + }
>> +}
>> +
>> +/// Marks the given SVal as NonLocalized in the program state
>> +void NonLocalizedStringChecker::setNonLocalizedState(const SVal S,
>> + CheckerContext &C)
>> const {
>> + const MemRegion *mt = S.getAsRegion();
>> + if (mt) {
>> + ProgramStateRef State = C.getState()->set<LocalizedMemMap>(
>> + mt, LocalizedState::getNonLocalized());
>> + C.addTransition(State);
>> + }
>> +}
>> +
>> +/// 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 {
>> +
>> + ExplodedNode *ErrNode = C.getPredecessor();
>> + static CheckerProgramPointTag Tag("NonLocalizedStringChecker",
>> + "UnlocalizedString");
>> + ErrNode = C.addTransition(C.getState(), C.getPredecessor(), &Tag);
>> +
>> + if (!ErrNode)
>> + return;
>> +
>> + // Generate the bug report.
>> + std::unique_ptr<BugReport> R(
>> + new BugReport(*BT, "String should be localized", ErrNode));
>> + if (argumentNumber) {
>> + R->addRange(M.getArgExpr(argumentNumber - 1)->getSourceRange());
>> + } else {
>> + R->addRange(M.getSourceRange());
>> + }
>> + R->markInteresting(S);
>> + C.emitReport(std::move(R));
>> +}
>> +
>> +/// Check if the string being passed in has NonLocalized state
>> +void NonLocalizedStringChecker::checkPreObjCMessage(const ObjCMethodCall
>> &msg,
>> + CheckerContext &C)
>> const {
>> + initUIMethods(C.getASTContext());
>> +
>> + const ObjCInterfaceDecl *OD = msg.getReceiverInterface();
>> + if (!OD)
>> + return;
>> + const IdentifierInfo *odInfo = OD->getIdentifier();
>> +
>> + Selector S = msg.getSelector();
>> +
>> + StringRef Name(S.getAsString());
>> + assert(!Name.empty());
>> +
>> + auto method = UIMethods.find(odInfo->getName());
>> + if (odInfo->isStr("NSString")) {
>> + // Handle the case where the receiver is an NSString
>> + // These special NSString methods draw to the screen
>> +
>> + if (!(Name.startswith("drawAtPoint") ||
>> Name.startswith("drawInRect") ||
>> + Name.startswith("drawWithRect")))
>> + return;
>> +
>> + SVal svTitle = msg.getReceiverSVal();
>> +
>> + bool isNonLocalized = hasNonLocalizedState(svTitle, C);
>> +
>> + if (isNonLocalized) {
>> + reportLocalizationError(svTitle, msg, C);
>> + }
>> + } else if (method != UIMethods.end()) {
>> +
>> + std::map<StringRef, uint8_t> m = method->second;
>> +
>> + auto argumentIterator = m.find(Name);
>> +
>> + if (argumentIterator == m.end())
>> + return;
>> +
>> + int argumentNumber = argumentIterator->second;
>> +
>> + SVal svTitle = msg.getArgSVal(argumentNumber);
>> +
>> + if (const ObjCStringRegion *SR =
>> + dyn_cast_or_null<ObjCStringRegion>(svTitle.getAsRegion())) {
>> + StringRef stringValue =
>> + SR->getObjCStringLiteral()->getString()->getString();
>> + if ((stringValue.trim().size() == 0 && stringValue.size() > 0) ||
>> + stringValue.empty())
>> + return;
>> + if (!IsAggressive &&
>> llvm::sys::unicode::columnWidthUTF8(stringValue) < 2)
>> + return;
>> + }
>> +
>> + bool isNonLocalized = hasNonLocalizedState(svTitle, C);
>> +
>> + if (isNonLocalized) {
>> + reportLocalizationError(svTitle, msg, C, argumentNumber + 1);
>> + }
>> + }
>> +}
>> +
>> +static inline bool isNSStringType(QualType T, ASTContext &Ctx) {
>> +
>> + const ObjCObjectPointerType *PT = T->getAs<ObjCObjectPointerType>();
>> + if (!PT)
>> + return false;
>> +
>> + ObjCInterfaceDecl *Cls = PT->getObjectType()->getInterface();
>> + if (!Cls)
>> + return false;
>> +
>> + IdentifierInfo *ClsName = Cls->getIdentifier();
>> +
>> + // FIXME: Should we walk the chain of classes?
>> + return ClsName == &Ctx.Idents.get("NSString") ||
>> + ClsName == &Ctx.Idents.get("NSMutableString");
>> +}
>> +
>> +/// Marks a string being returned by any call as localized
>> +/// if it is in LocStringFunctions (LSF) or the function is annotated.
>> +/// Otherwise, we mark it as NonLocalized (Aggressive) or
>> +/// NonLocalized only if it is not backed by a SymRegion
>> (Non-Aggressive),
>> +/// basically leaving only string literals as NonLocalized.
>> +void NonLocalizedStringChecker::checkPostCall(const CallEvent &Call,
>> + CheckerContext &C) const {
>> + initLocStringsMethods(C.getASTContext());
>> +
>> + if (!Call.getOriginExpr())
>> + return;
>> +
>> + // Anything that takes in a localized NSString as an argument
>> + // and returns an NSString will be assumed to be returning a
>> + // localized NSString. (Counter: Incorrectly combining two
>> LocalizedStrings)
>> + const QualType RT = Call.getResultType();
>> + if (isNSStringType(RT, C.getASTContext())) {
>> + for (unsigned i = 0; i < Call.getNumArgs(); ++i) {
>> + SVal argValue = Call.getArgSVal(i);
>> + if (hasLocalizedState(argValue, C)) {
>> + SVal sv = Call.getReturnValue();
>> + setLocalizedState(sv, C);
>> + return;
>> + }
>> + }
>> + }
>> +
>> + const Decl *D = Call.getDecl();
>> + if (!D)
>> + return;
>> +
>> + StringRef IdentifierName = C.getCalleeName(D->getAsFunction());
>> +
>> + SVal sv = Call.getReturnValue();
>> + if (isAnnotatedAsLocalized(D) || LSF.find(IdentifierName) !=
>> LSF.end()) {
>> + setLocalizedState(sv, C);
>> + } else if (isNSStringType(RT, C.getASTContext()) &&
>> + !hasLocalizedState(sv, C)) {
>> + if (IsAggressive) {
>> + setNonLocalizedState(sv, C);
>> + } else {
>> + const SymbolicRegion *SymReg =
>> + dyn_cast_or_null<SymbolicRegion>(sv.getAsRegion());
>> + if (!SymReg)
>> + setNonLocalizedState(sv, C);
>> + }
>> + }
>> +}
>> +
>> +/// Marks a string being returned by an ObjC method as localized
>> +/// if it is in LocStringMethods or the method is annotated
>> +void NonLocalizedStringChecker::checkPostObjCMessage(const
>> ObjCMethodCall &msg,
>> + CheckerContext &C)
>> const {
>> + initLocStringsMethods(C.getASTContext());
>> +
>> + if (!msg.isInstanceMessage())
>> + return;
>> +
>> + const ObjCInterfaceDecl *OD = msg.getReceiverInterface();
>> + if (!OD)
>> + return;
>> + const IdentifierInfo *odInfo = OD->getIdentifier();
>> +
>> + StringRef IdentifierName = odInfo->getName();
>> +
>> + Selector S = msg.getSelector();
>> + StringRef SelectorName = S.getAsString();
>> + assert(!SelectorName.empty());
>> +
>> + std::pair<StringRef, StringRef> MethodDescription = {IdentifierName,
>> + SelectorName};
>> +
>> + if (LSM.count(MethodDescription) ||
>> isAnnotatedAsLocalized(msg.getDecl())) {
>> + SVal sv = msg.getReturnValue();
>> + setLocalizedState(sv, C);
>> + }
>> +}
>> +
>> +/// Marks all empty string literals as localized
>> +void NonLocalizedStringChecker::checkPostStmt(const ObjCStringLiteral
>> *SL,
>> + CheckerContext &C) const {
>> + SVal sv = C.getSVal(SL);
>> + setNonLocalizedState(sv, C);
>> +}
>> +
>> +namespace {
>> +class EmptyLocalizationContextChecker
>> + : public Checker<check::ASTDecl<ObjCImplementationDecl>> {
>> +
>> + // A helper class, which walks the AST
>> + class MethodCrawler : public ConstStmtVisitor<MethodCrawler> {
>> + const ObjCMethodDecl *MD;
>> + BugReporter &BR;
>> + AnalysisManager &Mgr;
>> + const CheckerBase *Checker;
>> + LocationOrAnalysisDeclContext DCtx;
>> +
>> + public:
>> + MethodCrawler(const ObjCMethodDecl *InMD, BugReporter &InBR,
>> + const CheckerBase *Checker, AnalysisManager &InMgr,
>> + AnalysisDeclContext *InDCtx)
>> + : MD(InMD), BR(InBR), Mgr(InMgr), Checker(Checker), DCtx(InDCtx)
>> {}
>> +
>> + void VisitStmt(const Stmt *S) { VisitChildren(S); }
>> +
>> + void VisitObjCMessageExpr(const ObjCMessageExpr *ME);
>> +
>> + void reportEmptyContextError(const ObjCMessageExpr *M) const;
>> +
>> + void VisitChildren(const Stmt *S) {
>> + for (const Stmt *Child : S->children()) {
>> + if (Child)
>> + this->Visit(Child);
>> + }
>> + }
>> + };
>> +
>> +public:
>> + void checkASTDecl(const ObjCImplementationDecl *D, AnalysisManager
>> &Mgr,
>> + BugReporter &BR) const;
>> +};
>> +} // end anonymous namespace
>> +
>> +void EmptyLocalizationContextChecker::checkASTDecl(
>> + const ObjCImplementationDecl *D, AnalysisManager &Mgr,
>> + BugReporter &BR) const {
>> +
>> + for (const ObjCMethodDecl *M : D->methods()) {
>> + AnalysisDeclContext *DCtx = Mgr.getAnalysisDeclContext(M);
>> +
>> + const Stmt *Body = M->getBody();
>> + assert(Body);
>> +
>> + MethodCrawler MC(M->getCanonicalDecl(), BR, this, Mgr, DCtx);
>> + MC.VisitStmt(Body);
>> + }
>> +}
>> +
>> +/// This check attempts to match these macros, assuming they are defined
>> as
>> +/// follows:
>> +///
>> +/// #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)
>> +///
>> +/// We cannot use the path sensitive check because the macro argument we
>> are
>> +/// checking for (comment) is not used and thus not present in the AST,
>> +/// so we use Lexer on the original macro call and retrieve the value of
>> +/// the comment. If it's empty or nil, we raise a warning.
>> +void
>> EmptyLocalizationContextChecker::MethodCrawler::VisitObjCMessageExpr(
>> + const ObjCMessageExpr *ME) {
>> +
>> + const ObjCInterfaceDecl *OD = ME->getReceiverInterface();
>> + if (!OD)
>> + return;
>> +
>> + const IdentifierInfo *odInfo = OD->getIdentifier();
>> +
>> + if (!(odInfo->isStr("NSBundle") ||
>> + StringRef(ME->getSelector().getAsString())
>> + .equals("localizedStringForKey:value:table:"))) {
>> + return;
>> + }
>> +
>> + SourceRange R = ME->getSourceRange();
>> + if (!R.getBegin().isMacroID())
>> + return;
>> +
>> + // getImmediateMacroCallerLoc gets the location of the immediate macro
>> + // caller, one level up the stack toward the initial macro typed into
>> the
>> + // source, so SL should point to the NSLocalizedString macro.
>> + SourceLocation SL =
>> + Mgr.getSourceManager().getImmediateMacroCallerLoc(R.getBegin());
>> + std::pair<FileID, unsigned> SLInfo =
>> + Mgr.getSourceManager().getDecomposedLoc(SL);
>> +
>> + SrcMgr::SLocEntry SE =
>> Mgr.getSourceManager().getSLocEntry(SLInfo.first);
>> +
>> + // If NSLocalizedString macro is wrapped in another macro, we need to
>> + // unwrap the expansion until we get to the NSLocalizedStringMacro.
>> + while (SE.isExpansion()) {
>> + SL = SE.getExpansion().getSpellingLoc();
>> + SLInfo = Mgr.getSourceManager().getDecomposedLoc(SL);
>> + SE = Mgr.getSourceManager().getSLocEntry(SLInfo.first);
>> + }
>> +
>> + llvm::MemoryBuffer *BF =
>> SE.getFile().getContentCache()->getRawBuffer();
>> + Lexer TheLexer(SL, LangOptions(), BF->getBufferStart(),
>> + BF->getBufferStart() + SLInfo.second,
>> BF->getBufferEnd());
>> +
>> + Token I;
>> + Token Result; // This will hold the token just before the last ')'
>> + int p_count = 0; // This is for parenthesis matching
>> + while (!TheLexer.LexFromRawLexer(I)) {
>> + if (I.getKind() == tok::l_paren)
>> + ++p_count;
>> + if (I.getKind() == tok::r_paren) {
>> + if (p_count == 1)
>> + break;
>> + --p_count;
>> + }
>> + Result = I;
>> + }
>> +
>> + if (isAnyIdentifier(Result.getKind())) {
>> + if (Result.getRawIdentifier().equals("nil")) {
>> + reportEmptyContextError(ME);
>> + return;
>> + }
>> + }
>> +
>> + if (!isStringLiteral(Result.getKind()))
>> + return;
>> +
>> + StringRef Comment =
>> + StringRef(Result.getLiteralData(), Result.getLength()).trim("\"");
>> +
>> + if ((Comment.trim().size() == 0 && Comment.size() > 0) || // Is
>> Whitespace
>> + Comment.empty()) {
>> + reportEmptyContextError(ME);
>> + }
>> +}
>> +
>> +void
>> EmptyLocalizationContextChecker::MethodCrawler::reportEmptyContextError(
>> + const ObjCMessageExpr *ME) const {
>> + // Generate the bug report.
>> + BR.EmitBasicReport(MD, Checker, "Context Missing", "Localizability
>> Error",
>> + "Localized string macro should include a non-empty "
>> + "comment for translators",
>> + PathDiagnosticLocation(ME, BR.getSourceManager(),
>> DCtx));
>> +}
>> +
>>
>> +//===----------------------------------------------------------------------===//
>> +// Checker registration.
>>
>> +//===----------------------------------------------------------------------===//
>> +
>> +void ento::registerNonLocalizedStringChecker(CheckerManager &mgr) {
>> + NonLocalizedStringChecker *checker =
>> + mgr.registerChecker<NonLocalizedStringChecker>();
>> + checker->IsAggressive =
>> + mgr.getAnalyzerOptions().getBooleanOption("AggressiveReport",
>> false);
>> +}
>> +
>> +void ento::registerEmptyLocalizationContextChecker(CheckerManager &mgr) {
>> + mgr.registerChecker<EmptyLocalizationContextChecker>();
>> +}
>> \ No newline at end of file
>>
>> Added: cfe/trunk/test/Analysis/localization-aggressive.m
>> URL:
>> http://llvm.org/viewvc/llvm-project/cfe/trunk/test/Analysis/localization-aggressive.m?rev=244389&view=auto
>>
>> ==============================================================================
>> --- cfe/trunk/test/Analysis/localization-aggressive.m (added)
>> +++ cfe/trunk/test/Analysis/localization-aggressive.m Fri Aug 7 20:49:26
>> 2015
>> @@ -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;
>> + at interface NSObject
>> ++ (id)alloc;
>> +- (id)init;
>> + at end
>> + at class NSDictionary;
>> + at interface NSString : NSObject
>> +- (void)drawAtPoint:(CGPoint)point withAttributes:(NSDictionary *)attrs;
>> ++ (instancetype)localizedStringWithFormat:(NSString *)format, ...;
>> + at end
>> + at interface NSBundle : NSObject
>> ++ (NSBundle *)mainBundle;
>> +- (NSString *)localizedStringForKey:(NSString *)key
>> + value:(NSString *)value
>> + table:(NSString *)tableName;
>> + at end
>> + at interface UILabel : NSObject
>> + at property(nullable, nonatomic, copy) NSString *text;
>> +- (void)accessibilitySetIdentification:(NSString *)ident;
>> + at end
>> + at interface TestObject : NSObject
>> + at property(strong) NSString *text;
>> + at end
>> +
>> + at 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")));
>> + at end
>> +
>> +// Test cases begin here
>> + at 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
>> +}
>> +
>> + at end
>>
>> Added: cfe/trunk/test/Analysis/localization.m
>> URL:
>> http://llvm.org/viewvc/llvm-project/cfe/trunk/test/Analysis/localization.m?rev=244389&view=auto
>>
>> ==============================================================================
>> --- cfe/trunk/test/Analysis/localization.m (added)
>> +++ cfe/trunk/test/Analysis/localization.m Fri Aug 7 20:49:26 2015
>> @@ -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)]
>> + at interface NSObject
>> ++ (id)alloc;
>> +- (id)init;
>> + at end
>> + at interface NSString : NSObject
>> + at end
>> + at interface NSBundle : NSObject
>> ++ (NSBundle *)mainBundle;
>> +- (NSString *)localizedStringForKey:(NSString *)key
>> + value:(NSString *)value
>> + table:(NSString *)tableName;
>> + at end
>> + at interface UILabel : NSObject
>> + at property(nullable, nonatomic, copy) NSString *text;
>> + at end
>> + at interface TestObject : NSObject
>> + at property(strong) NSString *text;
>> + at end
>> +
>> + at interface LocalizationTestSuite : NSObject
>> +int random();
>> + at end
>> +
>> +// Test cases begin here
>> + at 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
>> +}
>> +
>> + at end
>>
>>
>> _______________________________________________
>> cfe-commits mailing list
>> cfe-commits at lists.llvm.org
>> http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
>>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/cfe-commits/attachments/20150808/5d7d6e06/attachment-0001.html>
More information about the cfe-commits
mailing list