diff options
author | Anna Zaks <ganna@apple.com> | 2015-08-08 04:53:04 +0000 |
---|---|---|
committer | Anna Zaks <ganna@apple.com> | 2015-08-08 04:53:04 +0000 |
commit | 9592df79019f8fc77fa6ff68bed63689e7b24eb0 (patch) | |
tree | 72edc0990f381d3fecb657b0ba8da7b634ac8d5a /clang/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp | |
parent | 08f3c1e12ac46e0ff55ed3c67c09cc2c432ea726 (diff) | |
download | llvm-9592df79019f8fc77fa6ff68bed63689e7b24eb0.zip llvm-9592df79019f8fc77fa6ff68bed63689e7b24eb0.tar.gz llvm-9592df79019f8fc77fa6ff68bed63689e7b24eb0.tar.bz2 |
Revert "[analyzer] Add checkers for OS X / iOS localizability issues"
This reverts commit fc885033a30b6e30ccf82398ae7c30e646727b10.
Revert all localization checker commits until the proper fix is implemented.
llvm-svn: 244394
Diffstat (limited to 'clang/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp')
-rw-r--r-- | clang/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp | 559 |
1 files changed, 0 insertions, 559 deletions
diff --git a/clang/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp deleted file mode 100644 index 7c4e184..0000000 --- a/clang/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp +++ /dev/null @@ -1,559 +0,0 @@ -//=- 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 |