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:08:05 PDT 2015
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/846104d0/attachment-0001.html>
More information about the cfe-commits
mailing list