From 97dc0c8c29f857fcffffc2b1ebaad459e91dc150 Mon Sep 17 00:00:00 2001 From: Devin Coughlin Date: Thu, 28 Apr 2016 19:44:40 +0000 Subject: [PATCH] [analyzer] Add path note for localizability checker. Add a path note indicating the location of the non-localized string literal in NonLocalizedStringChecker. rdar://problem/25981525 llvm-svn: 267924 --- .../Checkers/LocalizationChecker.cpp | 64 ++++++++++++++++++++++ clang/test/Analysis/localization.m | 43 ++++++++++----- 2 files changed, 93 insertions(+), 14 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp index a25f3e0..7be2f57 100644 --- a/clang/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp @@ -111,6 +111,30 @@ NonLocalizedStringChecker::NonLocalizedStringChecker() { "Localizability Issue (Apple)")); } +namespace { +class NonLocalizedStringBRVisitor final + : public BugReporterVisitorImpl { + + const MemRegion *NonLocalizedString; + bool Satisfied; + +public: + NonLocalizedStringBRVisitor(const MemRegion *NonLocalizedString) + : NonLocalizedString(NonLocalizedString), Satisfied(false) { + assert(NonLocalizedString); + } + + PathDiagnosticPiece *VisitNode(const ExplodedNode *Succ, + const ExplodedNode *Pred, + BugReporterContext &BRC, + BugReport &BR) override; + + void Profile(llvm::FoldingSetNodeID &ID) const override { + ID.Add(NonLocalizedString); + } +}; +} // End anonymous namespace. + #define NEW_RECEIVER(receiver) \ llvm::DenseMap &receiver##M = \ UIMethods.insert({&Ctx.Idents.get(#receiver), \ @@ -676,6 +700,11 @@ void NonLocalizedStringChecker::reportLocalizationError( R->addRange(M.getSourceRange()); } R->markInteresting(S); + + const MemRegion *StringRegion = S.getAsRegion(); + if (StringRegion) + R->addVisitor(llvm::make_unique(StringRegion)); + C.emitReport(std::move(R)); } @@ -866,6 +895,41 @@ void NonLocalizedStringChecker::checkPostStmt(const ObjCStringLiteral *SL, setNonLocalizedState(sv, C); } +PathDiagnosticPiece * +NonLocalizedStringBRVisitor::VisitNode(const ExplodedNode *Succ, + const ExplodedNode *Pred, + BugReporterContext &BRC, BugReport &BR) { + if (Satisfied) + return nullptr; + + Optional Point = Succ->getLocation().getAs(); + if (!Point.hasValue()) + return nullptr; + + auto *LiteralExpr = dyn_cast(Point->getStmt()); + if (!LiteralExpr) + return nullptr; + + ProgramStateRef State = Succ->getState(); + SVal LiteralSVal = State->getSVal(LiteralExpr, Succ->getLocationContext()); + if (LiteralSVal.getAsRegion() != NonLocalizedString) + return nullptr; + + Satisfied = true; + + PathDiagnosticLocation L = + PathDiagnosticLocation::create(*Point, BRC.getSourceManager()); + + if (!L.isValid() || !L.asLocation().isValid()) + return nullptr; + + auto *Piece = new PathDiagnosticEventPiece(L, + "Non-localized string literal here"); + Piece->addRange(LiteralExpr->getSourceRange()); + + return Piece; +} + namespace { class EmptyLocalizationContextChecker : public Checker> { diff --git a/clang/test/Analysis/localization.m b/clang/test/Analysis/localization.m index cf0697c..dc80705 100644 --- a/clang/test/Analysis/localization.m +++ b/clang/test/Analysis/localization.m @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -analyze -fblocks -analyzer-store=region -analyzer-checker=optin.osx.cocoa.localizability.NonLocalizedStringChecker -analyzer-checker=alpha.osx.cocoa.localizability.PluralMisuseChecker -verify %s +// RUN: %clang_cc1 -analyze -fblocks -analyzer-store=region -analyzer-output=text -analyzer-checker=optin.osx.cocoa.localizability.NonLocalizedStringChecker -analyzer-checker=alpha.osx.cocoa.localizability.PluralMisuseChecker -verify %s // The larger set of tests in located in localization.m. These are tests // specific for non-aggressive reporting. @@ -61,11 +61,26 @@ NSString *KHLocalizedString(NSString* key, NSString* comment) { UILabel *testLabel = [[UILabel alloc] init]; NSString *bar = NSLocalizedString(@"Hello", @"Comment"); - if (random()) { - bar = @"Unlocalized string"; + if (random()) { // expected-note {{Taking true branch}} + bar = @"Unlocalized string"; // expected-note {{Non-localized string literal here}} } - [testLabel setText:bar]; // expected-warning {{User-facing text should use localized string macro}} + [testLabel setText:bar]; // expected-warning {{User-facing text should use localized string macro}} expected-note {{User-facing}} +} + +- (void)testMultipleUnlocalizedStringsInSamePath { + UILabel *testLabel = [[UILabel alloc] init]; + NSString *bar = @"Unlocalized string"; // no-note + + bar = @"Unlocalized string"; // expected-note {{Non-localized string literal here}} + + NSString *other = @"Other unlocalized string."; // no-note + (void)other; + + NSString *same = @"Unlocalized string"; // no-note + (void)same; + + [testLabel setText:bar]; // expected-warning {{User-facing text should use localized string macro}} expected-note {{User-facing}} } - (void)testOneCharacterStringsDoNotGiveAWarning { @@ -103,14 +118,14 @@ NSString *KHLocalizedString(NSString* key, NSString* comment) { - (NSString *)test1:(int)plural { if (plural) { - return MCLocalizedString(@"TYPE_PLURAL"); // expected-warning {{Plural cases are not supported accross all languages. Use a .stringsdict file}} + return MCLocalizedString(@"TYPE_PLURAL"); // expected-warning {{Plural cases are not supported accross all languages. Use a .stringsdict file}} expected-note {{Plural}} } return MCLocalizedString(@"TYPE"); } - (NSString *)test2:(int)numOfReminders { if (numOfReminders > 0) { - return [NSString stringWithFormat:@"%@, %@", @"Test", (numOfReminders != 1) ? [NSString stringWithFormat:NSLocalizedString(@"%@ Reminders", @"Plural count of reminders"), numOfReminders] : [NSString stringWithFormat:NSLocalizedString(@"1 reminder", @"One reminder")]]; // expected-warning {{Plural cases are not supported accross all languages. Use a .stringsdict file}} expected-warning {{Plural cases are not supported accross all languages. Use a .stringsdict file}} + return [NSString stringWithFormat:@"%@, %@", @"Test", (numOfReminders != 1) ? [NSString stringWithFormat:NSLocalizedString(@"%@ Reminders", @"Plural count of reminders"), numOfReminders] : [NSString stringWithFormat:NSLocalizedString(@"1 reminder", @"One reminder")]]; // expected-warning 2 {{Plural cases are not supported accross all languages. Use a .stringsdict file}} expected-note 2 {{Plural}} } return nil; } @@ -119,18 +134,18 @@ NSString *KHLocalizedString(NSString* key, NSString* comment) { NSString *count; if (self.unreadArticlesCount > 1) { - count = [count stringByAppendingFormat:@"%@", KHLocalizedString(@"New Stories", @"Plural count for new stories")]; // expected-warning {{Plural cases are not supported accross all languages. Use a .stringsdict file}} + count = [count stringByAppendingFormat:@"%@", KHLocalizedString(@"New Stories", @"Plural count for new stories")]; // expected-warning {{Plural cases are not supported accross all languages. Use a .stringsdict file}} expected-note {{Plural}} } else { - count = [count stringByAppendingFormat:@"%@", KHLocalizedString(@"New Story", @"One new story")]; // expected-warning {{Plural cases are not supported accross all languages. Use a .stringsdict file}} + count = [count stringByAppendingFormat:@"%@", KHLocalizedString(@"New Story", @"One new story")]; // expected-warning {{Plural cases are not supported accross all languages. Use a .stringsdict file}} expected-note {{Plural}} } } - (NSString *)test4:(int)count { if ( count == 1 ) { - return [NSString stringWithFormat:KHLocalizedString(@"value.singular",nil), count]; // expected-warning {{Plural cases are not supported accross all languages. Use a .stringsdict file}} + return [NSString stringWithFormat:KHLocalizedString(@"value.singular",nil), count]; // expected-warning {{Plural cases are not supported accross all languages. Use a .stringsdict file}} expected-note {{Plural}} } else { - return [NSString stringWithFormat:KHLocalizedString(@"value.plural",nil), count]; // expected-warning {{Plural cases are not supported accross all languages. Use a .stringsdict file}} + return [NSString stringWithFormat:KHLocalizedString(@"value.plural",nil), count]; // expected-warning {{Plural cases are not supported accross all languages. Use a .stringsdict file}} expected-note {{Plural}} } } @@ -138,9 +153,9 @@ NSString *KHLocalizedString(NSString* key, NSString* comment) { int test = count == 1; if (test) { - return [NSString stringWithFormat:KHLocalizedString(@"value.singular",nil), count]; // expected-warning {{Plural cases are not supported accross all languages. Use a .stringsdict file}} + return [NSString stringWithFormat:KHLocalizedString(@"value.singular",nil), count]; // expected-warning {{Plural cases are not supported accross all languages. Use a .stringsdict file}} expected-note {{Plural}} } else { - return [NSString stringWithFormat:KHLocalizedString(@"value.plural",nil), count]; // expected-warning {{Plural cases are not supported accross all languages. Use a .stringsdict file}} + return [NSString stringWithFormat:KHLocalizedString(@"value.plural",nil), count]; // expected-warning {{Plural cases are not supported accross all languages. Use a .stringsdict file}} expected-note {{Plural}} } } @@ -154,7 +169,7 @@ NSString *KHLocalizedString(NSString* key, NSString* comment) { if (someOtherVariable) return KHLocalizedString(@"OK",nil); // no-warning } else { - return KHLocalizedString(@"value.plural",nil); // expected-warning {{Plural cases are not supported accross all languages. Use a .stringsdict file}} + return KHLocalizedString(@"value.plural",nil); // expected-warning {{Plural cases are not supported accross all languages. Use a .stringsdict file}} expected-note {{Plural}} } return nil; } @@ -221,6 +236,6 @@ NSString *KHLocalizedString(NSString* key, NSString* comment) { @implementation MyDebugView - (void)setupScreen:(UILabel *)label { - label.text = @"Unlocalized"; + label.text = @"Unlocalized"; // no-warning } @end -- 2.7.4