From df20ae37bc7c79af4a9b50653690dbcdb7c695e5 Mon Sep 17 00:00:00 2001 From: Anna Zaks Date: Sat, 8 Aug 2015 04:53:04 +0000 Subject: [PATCH] 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. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@244394 91177308-0d34-0410-b5e6-96231b3b80d8 --- lib/StaticAnalyzer/Checkers/CMakeLists.txt | 1 - lib/StaticAnalyzer/Checkers/Checkers.td | 8 - .../Checkers/LocalizationChecker.cpp | 559 ------------------ test/Analysis/localization-aggressive.m | 243 -------- test/Analysis/localization.m | 86 --- 5 files changed, 897 deletions(-) delete mode 100644 lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp delete mode 100644 test/Analysis/localization-aggressive.m delete mode 100644 test/Analysis/localization.m diff --git a/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/lib/StaticAnalyzer/Checkers/CMakeLists.txt index 69f57325ff..9fb22ecc85 100644 --- a/lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ b/lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -39,7 +39,6 @@ add_clang_library(clangStaticAnalyzerCheckers IdenticalExprChecker.cpp IvarInvalidationChecker.cpp LLVMConventionsChecker.cpp - LocalizationChecker.cpp MacOSKeychainAPIChecker.cpp MacOSXAPIChecker.cpp MallocChecker.cpp diff --git a/lib/StaticAnalyzer/Checkers/Checkers.td b/lib/StaticAnalyzer/Checkers/Checkers.td index ccbfe4ea97..d1d6ac277f 100644 --- a/lib/StaticAnalyzer/Checkers/Checkers.td +++ b/lib/StaticAnalyzer/Checkers/Checkers.td @@ -452,14 +452,6 @@ def DirectIvarAssignmentForAnnotatedFunctions : Checker<"DirectIvarAssignmentFor 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 { diff --git a/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp b/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp deleted file mode 100644 index 7c4e184296..0000000000 --- a/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> { - - mutable std::unique_ptr BT; - - // Methods that require a localized string - mutable std::map> UIMethods; - // Methods that return a localized string - mutable llvm::SmallSet, 10> LSM; - // C Functions that return a localized string - mutable llvm::StringMap 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(), - D->specific_attr_end(), [](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(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(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(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( - 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 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 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(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(); - 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(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 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> { - - // A helper class, which walks the AST - class MethodCrawler : public ConstStmtVisitor { - 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 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(); - checker->IsAggressive = - mgr.getAnalyzerOptions().getBooleanOption("AggressiveReport", false); -} - -void ento::registerEmptyLocalizationContextChecker(CheckerManager &mgr) { - mgr.registerChecker(); -} \ No newline at end of file diff --git a/test/Analysis/localization-aggressive.m b/test/Analysis/localization-aggressive.m deleted file mode 100644 index eba6e15ea0..0000000000 --- a/test/Analysis/localization-aggressive.m +++ /dev/null @@ -1,243 +0,0 @@ -// 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 diff --git a/test/Analysis/localization.m b/test/Analysis/localization.m deleted file mode 100644 index 83ba1e6f24..0000000000 --- a/test/Analysis/localization.m +++ /dev/null @@ -1,86 +0,0 @@ -// 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 -- 2.40.0