Sorry about the breakage! I’ve reverted the commit in r244394. Anna.
> On Aug 7, 2015, at 9:08 PM, Chandler Carruth <chandl...@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 > <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@lists.llvm.org <mailto:cfe-commits@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 > <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 > > <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 > > <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 > > <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 > > <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; > +@interface NSObject > ++ (id)alloc; > +- (id)init; > +@end > +@class NSDictionary; > +@interface NSString : NSObject > +- (void)drawAtPoint:(CGPoint)point withAttributes:(NSDictionary *)attrs; > ++ (instancetype)localizedStringWithFormat:(NSString *)format, ...; > +@end > +@interface NSBundle : NSObject > ++ (NSBundle *)mainBundle; > +- (NSString *)localizedStringForKey:(NSString *)key > + value:(NSString *)value > + table:(NSString *)tableName; > +@end > +@interface UILabel : NSObject > +@property(nullable, nonatomic, copy) NSString *text; > +- (void)accessibilitySetIdentification:(NSString *)ident; > +@end > +@interface TestObject : NSObject > +@property(strong) NSString *text; > +@end > + > +@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"))); > +@end > + > +// Test cases begin here > +@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 > +} > + > +@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 > > <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)] > +@interface NSObject > ++ (id)alloc; > +- (id)init; > +@end > +@interface NSString : NSObject > +@end > +@interface NSBundle : NSObject > ++ (NSBundle *)mainBundle; > +- (NSString *)localizedStringForKey:(NSString *)key > + value:(NSString *)value > + table:(NSString *)tableName; > +@end > +@interface UILabel : NSObject > +@property(nullable, nonatomic, copy) NSString *text; > +@end > +@interface TestObject : NSObject > +@property(strong) NSString *text; > +@end > + > +@interface LocalizationTestSuite : NSObject > +int random(); > +@end > + > +// Test cases begin here > +@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 > +} > + > +@end > > > _______________________________________________ > cfe-commits mailing list > cfe-commits@lists.llvm.org <mailto:cfe-commits@lists.llvm.org> > http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits > <http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits>
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits