NP, thanks for the quick fixes! On Fri, Aug 7, 2015 at 9:56 PM Anna Zaks <ga...@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 <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 > > On Fri, Aug 7, 2015 at 6:50 PM Anna Zaks via cfe-commits < > 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 >> 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; >> +@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 >> >> ============================================================================== >> --- 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 >> 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