diff options
Diffstat (limited to 'contrib/llvm/tools/clang/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp')
| -rw-r--r-- | contrib/llvm/tools/clang/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp | 1422 |
1 files changed, 0 insertions, 1422 deletions
diff --git a/contrib/llvm/tools/clang/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp b/contrib/llvm/tools/clang/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp deleted file mode 100644 index 46067ecbca99..000000000000 --- a/contrib/llvm/tools/clang/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp +++ /dev/null @@ -1,1422 +0,0 @@ -//=- LocalizationChecker.cpp -------------------------------------*- C++ -*-==// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// 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 "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" -#include "clang/AST/Attr.h" -#include "clang/AST/Decl.h" -#include "clang/AST/DeclObjC.h" -#include "clang/AST/RecursiveASTVisitor.h" -#include "clang/AST/StmtVisitor.h" -#include "clang/Lex/Lexer.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 "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::PreCall, check::PostCall, check::PreObjCMessage, - check::PostObjCMessage, - check::PostStmt<ObjCStringLiteral>> { - - mutable std::unique_ptr<BugType> BT; - - // Methods that require a localized string - mutable llvm::DenseMap<const IdentifierInfo *, - llvm::DenseMap<Selector, uint8_t>> UIMethods; - // Methods that return a localized string - mutable llvm::SmallSet<std::pair<const IdentifierInfo *, Selector>, 12> LSM; - // C Functions that return a localized string - mutable llvm::SmallSet<const IdentifierInfo *, 5> 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 isAnnotatedAsReturningLocalized(const Decl *D) const; - bool isAnnotatedAsTakingLocalized(const Decl *D) const; - void reportLocalizationError(SVal S, const CallEvent &M, CheckerContext &C, - int argumentNumber = 0) const; - - int getLocalizedArgumentForSelector(const IdentifierInfo *Receiver, - Selector S) 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 checkPreCall(const CallEvent &Call, 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, "Unlocalizable string", - "Localizability Issue (Apple)")); -} - -namespace { -class NonLocalizedStringBRVisitor final : public BugReporterVisitor { - - const MemRegion *NonLocalizedString; - bool Satisfied; - -public: - NonLocalizedStringBRVisitor(const MemRegion *NonLocalizedString) - : NonLocalizedString(NonLocalizedString), Satisfied(false) { - assert(NonLocalizedString); - } - - std::shared_ptr<PathDiagnosticPiece> VisitNode(const ExplodedNode *Succ, - BugReporterContext &BRC, - BugReport &BR) override; - - void Profile(llvm::FoldingSetNodeID &ID) const override { - ID.Add(NonLocalizedString); - } -}; -} // End anonymous namespace. - -#define NEW_RECEIVER(receiver) \ - llvm::DenseMap<Selector, uint8_t> &receiver##M = \ - UIMethods.insert({&Ctx.Idents.get(#receiver), \ - llvm::DenseMap<Selector, uint8_t>()}) \ - .first->second; -#define ADD_NULLARY_METHOD(receiver, method, argument) \ - receiver##M.insert( \ - {Ctx.Selectors.getNullarySelector(&Ctx.Idents.get(#method)), argument}); -#define ADD_UNARY_METHOD(receiver, method, argument) \ - receiver##M.insert( \ - {Ctx.Selectors.getUnarySelector(&Ctx.Idents.get(#method)), argument}); -#define ADD_METHOD(receiver, method_list, count, argument) \ - receiver##M.insert({Ctx.Selectors.getSelector(count, method_list), argument}); - -/// Initializes a list of methods that require a localized string -/// Format: {"ClassName", {{"selectorName:", LocStringArg#}, ...}, ...} -void NonLocalizedStringChecker::initUIMethods(ASTContext &Ctx) const { - if (!UIMethods.empty()) - return; - - // UI Methods - NEW_RECEIVER(UISearchDisplayController) - ADD_UNARY_METHOD(UISearchDisplayController, setSearchResultsTitle, 0) - - NEW_RECEIVER(UITabBarItem) - IdentifierInfo *initWithTitleUITabBarItemTag[] = { - &Ctx.Idents.get("initWithTitle"), &Ctx.Idents.get("image"), - &Ctx.Idents.get("tag")}; - ADD_METHOD(UITabBarItem, initWithTitleUITabBarItemTag, 3, 0) - IdentifierInfo *initWithTitleUITabBarItemImage[] = { - &Ctx.Idents.get("initWithTitle"), &Ctx.Idents.get("image"), - &Ctx.Idents.get("selectedImage")}; - ADD_METHOD(UITabBarItem, initWithTitleUITabBarItemImage, 3, 0) - - NEW_RECEIVER(NSDockTile) - ADD_UNARY_METHOD(NSDockTile, setBadgeLabel, 0) - - NEW_RECEIVER(NSStatusItem) - ADD_UNARY_METHOD(NSStatusItem, setTitle, 0) - ADD_UNARY_METHOD(NSStatusItem, setToolTip, 0) - - NEW_RECEIVER(UITableViewRowAction) - IdentifierInfo *rowActionWithStyleUITableViewRowAction[] = { - &Ctx.Idents.get("rowActionWithStyle"), &Ctx.Idents.get("title"), - &Ctx.Idents.get("handler")}; - ADD_METHOD(UITableViewRowAction, rowActionWithStyleUITableViewRowAction, 3, 1) - ADD_UNARY_METHOD(UITableViewRowAction, setTitle, 0) - - NEW_RECEIVER(NSBox) - ADD_UNARY_METHOD(NSBox, setTitle, 0) - - NEW_RECEIVER(NSButton) - ADD_UNARY_METHOD(NSButton, setTitle, 0) - ADD_UNARY_METHOD(NSButton, setAlternateTitle, 0) - IdentifierInfo *radioButtonWithTitleNSButton[] = { - &Ctx.Idents.get("radioButtonWithTitle"), &Ctx.Idents.get("target"), - &Ctx.Idents.get("action")}; - ADD_METHOD(NSButton, radioButtonWithTitleNSButton, 3, 0) - IdentifierInfo *buttonWithTitleNSButtonImage[] = { - &Ctx.Idents.get("buttonWithTitle"), &Ctx.Idents.get("image"), - &Ctx.Idents.get("target"), &Ctx.Idents.get("action")}; - ADD_METHOD(NSButton, buttonWithTitleNSButtonImage, 4, 0) - IdentifierInfo *checkboxWithTitleNSButton[] = { - &Ctx.Idents.get("checkboxWithTitle"), &Ctx.Idents.get("target"), - &Ctx.Idents.get("action")}; - ADD_METHOD(NSButton, checkboxWithTitleNSButton, 3, 0) - IdentifierInfo *buttonWithTitleNSButtonTarget[] = { - &Ctx.Idents.get("buttonWithTitle"), &Ctx.Idents.get("target"), - &Ctx.Idents.get("action")}; - ADD_METHOD(NSButton, buttonWithTitleNSButtonTarget, 3, 0) - - NEW_RECEIVER(NSSavePanel) - ADD_UNARY_METHOD(NSSavePanel, setPrompt, 0) - ADD_UNARY_METHOD(NSSavePanel, setTitle, 0) - ADD_UNARY_METHOD(NSSavePanel, setNameFieldLabel, 0) - ADD_UNARY_METHOD(NSSavePanel, setNameFieldStringValue, 0) - ADD_UNARY_METHOD(NSSavePanel, setMessage, 0) - - NEW_RECEIVER(UIPrintInfo) - ADD_UNARY_METHOD(UIPrintInfo, setJobName, 0) - - NEW_RECEIVER(NSTabViewItem) - ADD_UNARY_METHOD(NSTabViewItem, setLabel, 0) - ADD_UNARY_METHOD(NSTabViewItem, setToolTip, 0) - - NEW_RECEIVER(NSBrowser) - IdentifierInfo *setTitleNSBrowser[] = {&Ctx.Idents.get("setTitle"), - &Ctx.Idents.get("ofColumn")}; - ADD_METHOD(NSBrowser, setTitleNSBrowser, 2, 0) - - NEW_RECEIVER(UIAccessibilityElement) - ADD_UNARY_METHOD(UIAccessibilityElement, setAccessibilityLabel, 0) - ADD_UNARY_METHOD(UIAccessibilityElement, setAccessibilityHint, 0) - ADD_UNARY_METHOD(UIAccessibilityElement, setAccessibilityValue, 0) - - NEW_RECEIVER(UIAlertAction) - IdentifierInfo *actionWithTitleUIAlertAction[] = { - &Ctx.Idents.get("actionWithTitle"), &Ctx.Idents.get("style"), - &Ctx.Idents.get("handler")}; - ADD_METHOD(UIAlertAction, actionWithTitleUIAlertAction, 3, 0) - - NEW_RECEIVER(NSPopUpButton) - ADD_UNARY_METHOD(NSPopUpButton, addItemWithTitle, 0) - IdentifierInfo *insertItemWithTitleNSPopUpButton[] = { - &Ctx.Idents.get("insertItemWithTitle"), &Ctx.Idents.get("atIndex")}; - ADD_METHOD(NSPopUpButton, insertItemWithTitleNSPopUpButton, 2, 0) - ADD_UNARY_METHOD(NSPopUpButton, removeItemWithTitle, 0) - ADD_UNARY_METHOD(NSPopUpButton, selectItemWithTitle, 0) - ADD_UNARY_METHOD(NSPopUpButton, setTitle, 0) - - NEW_RECEIVER(NSTableViewRowAction) - IdentifierInfo *rowActionWithStyleNSTableViewRowAction[] = { - &Ctx.Idents.get("rowActionWithStyle"), &Ctx.Idents.get("title"), - &Ctx.Idents.get("handler")}; - ADD_METHOD(NSTableViewRowAction, rowActionWithStyleNSTableViewRowAction, 3, 1) - ADD_UNARY_METHOD(NSTableViewRowAction, setTitle, 0) - - NEW_RECEIVER(NSImage) - ADD_UNARY_METHOD(NSImage, setAccessibilityDescription, 0) - - NEW_RECEIVER(NSUserActivity) - ADD_UNARY_METHOD(NSUserActivity, setTitle, 0) - - NEW_RECEIVER(NSPathControlItem) - ADD_UNARY_METHOD(NSPathControlItem, setTitle, 0) - - NEW_RECEIVER(NSCell) - ADD_UNARY_METHOD(NSCell, initTextCell, 0) - ADD_UNARY_METHOD(NSCell, setTitle, 0) - ADD_UNARY_METHOD(NSCell, setStringValue, 0) - - NEW_RECEIVER(NSPathControl) - ADD_UNARY_METHOD(NSPathControl, setPlaceholderString, 0) - - NEW_RECEIVER(UIAccessibility) - ADD_UNARY_METHOD(UIAccessibility, setAccessibilityLabel, 0) - ADD_UNARY_METHOD(UIAccessibility, setAccessibilityHint, 0) - ADD_UNARY_METHOD(UIAccessibility, setAccessibilityValue, 0) - - NEW_RECEIVER(NSTableColumn) - ADD_UNARY_METHOD(NSTableColumn, setTitle, 0) - ADD_UNARY_METHOD(NSTableColumn, setHeaderToolTip, 0) - - NEW_RECEIVER(NSSegmentedControl) - IdentifierInfo *setLabelNSSegmentedControl[] = { - &Ctx.Idents.get("setLabel"), &Ctx.Idents.get("forSegment")}; - ADD_METHOD(NSSegmentedControl, setLabelNSSegmentedControl, 2, 0) - IdentifierInfo *setToolTipNSSegmentedControl[] = { - &Ctx.Idents.get("setToolTip"), &Ctx.Idents.get("forSegment")}; - ADD_METHOD(NSSegmentedControl, setToolTipNSSegmentedControl, 2, 0) - - NEW_RECEIVER(NSButtonCell) - ADD_UNARY_METHOD(NSButtonCell, setTitle, 0) - ADD_UNARY_METHOD(NSButtonCell, setAlternateTitle, 0) - - NEW_RECEIVER(NSDatePickerCell) - ADD_UNARY_METHOD(NSDatePickerCell, initTextCell, 0) - - NEW_RECEIVER(NSSliderCell) - ADD_UNARY_METHOD(NSSliderCell, setTitle, 0) - - NEW_RECEIVER(NSControl) - ADD_UNARY_METHOD(NSControl, setStringValue, 0) - - NEW_RECEIVER(NSAccessibility) - ADD_UNARY_METHOD(NSAccessibility, setAccessibilityValueDescription, 0) - ADD_UNARY_METHOD(NSAccessibility, setAccessibilityLabel, 0) - ADD_UNARY_METHOD(NSAccessibility, setAccessibilityTitle, 0) - ADD_UNARY_METHOD(NSAccessibility, setAccessibilityPlaceholderValue, 0) - ADD_UNARY_METHOD(NSAccessibility, setAccessibilityHelp, 0) - - NEW_RECEIVER(NSMatrix) - IdentifierInfo *setToolTipNSMatrix[] = {&Ctx.Idents.get("setToolTip"), - &Ctx.Idents.get("forCell")}; - ADD_METHOD(NSMatrix, setToolTipNSMatrix, 2, 0) - - NEW_RECEIVER(NSPrintPanel) - ADD_UNARY_METHOD(NSPrintPanel, setDefaultButtonTitle, 0) - - NEW_RECEIVER(UILocalNotification) - ADD_UNARY_METHOD(UILocalNotification, setAlertBody, 0) - ADD_UNARY_METHOD(UILocalNotification, setAlertAction, 0) - ADD_UNARY_METHOD(UILocalNotification, setAlertTitle, 0) - - NEW_RECEIVER(NSSlider) - ADD_UNARY_METHOD(NSSlider, setTitle, 0) - - NEW_RECEIVER(UIMenuItem) - IdentifierInfo *initWithTitleUIMenuItem[] = {&Ctx.Idents.get("initWithTitle"), - &Ctx.Idents.get("action")}; - ADD_METHOD(UIMenuItem, initWithTitleUIMenuItem, 2, 0) - ADD_UNARY_METHOD(UIMenuItem, setTitle, 0) - - NEW_RECEIVER(UIAlertController) - IdentifierInfo *alertControllerWithTitleUIAlertController[] = { - &Ctx.Idents.get("alertControllerWithTitle"), &Ctx.Idents.get("message"), - &Ctx.Idents.get("preferredStyle")}; - ADD_METHOD(UIAlertController, alertControllerWithTitleUIAlertController, 3, 1) - ADD_UNARY_METHOD(UIAlertController, setTitle, 0) - ADD_UNARY_METHOD(UIAlertController, setMessage, 0) - - NEW_RECEIVER(UIApplicationShortcutItem) - IdentifierInfo *initWithTypeUIApplicationShortcutItemIcon[] = { - &Ctx.Idents.get("initWithType"), &Ctx.Idents.get("localizedTitle"), - &Ctx.Idents.get("localizedSubtitle"), &Ctx.Idents.get("icon"), - &Ctx.Idents.get("userInfo")}; - ADD_METHOD(UIApplicationShortcutItem, - initWithTypeUIApplicationShortcutItemIcon, 5, 1) - IdentifierInfo *initWithTypeUIApplicationShortcutItem[] = { - &Ctx.Idents.get("initWithType"), &Ctx.Idents.get("localizedTitle")}; - ADD_METHOD(UIApplicationShortcutItem, initWithTypeUIApplicationShortcutItem, - 2, 1) - - NEW_RECEIVER(UIActionSheet) - IdentifierInfo *initWithTitleUIActionSheet[] = { - &Ctx.Idents.get("initWithTitle"), &Ctx.Idents.get("delegate"), - &Ctx.Idents.get("cancelButtonTitle"), - &Ctx.Idents.get("destructiveButtonTitle"), - &Ctx.Idents.get("otherButtonTitles")}; - ADD_METHOD(UIActionSheet, initWithTitleUIActionSheet, 5, 0) - ADD_UNARY_METHOD(UIActionSheet, addButtonWithTitle, 0) - ADD_UNARY_METHOD(UIActionSheet, setTitle, 0) - - NEW_RECEIVER(UIAccessibilityCustomAction) - IdentifierInfo *initWithNameUIAccessibilityCustomAction[] = { - &Ctx.Idents.get("initWithName"), &Ctx.Idents.get("target"), - &Ctx.Idents.get("selector")}; - ADD_METHOD(UIAccessibilityCustomAction, - initWithNameUIAccessibilityCustomAction, 3, 0) - ADD_UNARY_METHOD(UIAccessibilityCustomAction, setName, 0) - - NEW_RECEIVER(UISearchBar) - ADD_UNARY_METHOD(UISearchBar, setText, 0) - ADD_UNARY_METHOD(UISearchBar, setPrompt, 0) - ADD_UNARY_METHOD(UISearchBar, setPlaceholder, 0) - - NEW_RECEIVER(UIBarItem) - ADD_UNARY_METHOD(UIBarItem, setTitle, 0) - - NEW_RECEIVER(UITextView) - ADD_UNARY_METHOD(UITextView, setText, 0) - - NEW_RECEIVER(NSView) - ADD_UNARY_METHOD(NSView, setToolTip, 0) - - NEW_RECEIVER(NSTextField) - ADD_UNARY_METHOD(NSTextField, setPlaceholderString, 0) - ADD_UNARY_METHOD(NSTextField, textFieldWithString, 0) - ADD_UNARY_METHOD(NSTextField, wrappingLabelWithString, 0) - ADD_UNARY_METHOD(NSTextField, labelWithString, 0) - - NEW_RECEIVER(NSAttributedString) - ADD_UNARY_METHOD(NSAttributedString, initWithString, 0) - IdentifierInfo *initWithStringNSAttributedString[] = { - &Ctx.Idents.get("initWithString"), &Ctx.Idents.get("attributes")}; - ADD_METHOD(NSAttributedString, initWithStringNSAttributedString, 2, 0) - - NEW_RECEIVER(NSText) - ADD_UNARY_METHOD(NSText, setString, 0) - - NEW_RECEIVER(UIKeyCommand) - IdentifierInfo *keyCommandWithInputUIKeyCommand[] = { - &Ctx.Idents.get("keyCommandWithInput"), &Ctx.Idents.get("modifierFlags"), - &Ctx.Idents.get("action"), &Ctx.Idents.get("discoverabilityTitle")}; - ADD_METHOD(UIKeyCommand, keyCommandWithInputUIKeyCommand, 4, 3) - ADD_UNARY_METHOD(UIKeyCommand, setDiscoverabilityTitle, 0) - - NEW_RECEIVER(UILabel) - ADD_UNARY_METHOD(UILabel, setText, 0) - - NEW_RECEIVER(NSAlert) - IdentifierInfo *alertWithMessageTextNSAlert[] = { - &Ctx.Idents.get("alertWithMessageText"), &Ctx.Idents.get("defaultButton"), - &Ctx.Idents.get("alternateButton"), &Ctx.Idents.get("otherButton"), - &Ctx.Idents.get("informativeTextWithFormat")}; - ADD_METHOD(NSAlert, alertWithMessageTextNSAlert, 5, 0) - ADD_UNARY_METHOD(NSAlert, addButtonWithTitle, 0) - ADD_UNARY_METHOD(NSAlert, setMessageText, 0) - ADD_UNARY_METHOD(NSAlert, setInformativeText, 0) - ADD_UNARY_METHOD(NSAlert, setHelpAnchor, 0) - - NEW_RECEIVER(UIMutableApplicationShortcutItem) - ADD_UNARY_METHOD(UIMutableApplicationShortcutItem, setLocalizedTitle, 0) - ADD_UNARY_METHOD(UIMutableApplicationShortcutItem, setLocalizedSubtitle, 0) - - NEW_RECEIVER(UIButton) - IdentifierInfo *setTitleUIButton[] = {&Ctx.Idents.get("setTitle"), - &Ctx.Idents.get("forState")}; - ADD_METHOD(UIButton, setTitleUIButton, 2, 0) - - NEW_RECEIVER(NSWindow) - ADD_UNARY_METHOD(NSWindow, setTitle, 0) - IdentifierInfo *minFrameWidthWithTitleNSWindow[] = { - &Ctx.Idents.get("minFrameWidthWithTitle"), &Ctx.Idents.get("styleMask")}; - ADD_METHOD(NSWindow, minFrameWidthWithTitleNSWindow, 2, 0) - ADD_UNARY_METHOD(NSWindow, setMiniwindowTitle, 0) - - NEW_RECEIVER(NSPathCell) - ADD_UNARY_METHOD(NSPathCell, setPlaceholderString, 0) - - NEW_RECEIVER(UIDocumentMenuViewController) - IdentifierInfo *addOptionWithTitleUIDocumentMenuViewController[] = { - &Ctx.Idents.get("addOptionWithTitle"), &Ctx.Idents.get("image"), - &Ctx.Idents.get("order"), &Ctx.Idents.get("handler")}; - ADD_METHOD(UIDocumentMenuViewController, - addOptionWithTitleUIDocumentMenuViewController, 4, 0) - - NEW_RECEIVER(UINavigationItem) - ADD_UNARY_METHOD(UINavigationItem, initWithTitle, 0) - ADD_UNARY_METHOD(UINavigationItem, setTitle, 0) - ADD_UNARY_METHOD(UINavigationItem, setPrompt, 0) - - NEW_RECEIVER(UIAlertView) - IdentifierInfo *initWithTitleUIAlertView[] = { - &Ctx.Idents.get("initWithTitle"), &Ctx.Idents.get("message"), - &Ctx.Idents.get("delegate"), &Ctx.Idents.get("cancelButtonTitle"), - &Ctx.Idents.get("otherButtonTitles")}; - ADD_METHOD(UIAlertView, initWithTitleUIAlertView, 5, 0) - ADD_UNARY_METHOD(UIAlertView, addButtonWithTitle, 0) - ADD_UNARY_METHOD(UIAlertView, setTitle, 0) - ADD_UNARY_METHOD(UIAlertView, setMessage, 0) - - NEW_RECEIVER(NSFormCell) - ADD_UNARY_METHOD(NSFormCell, initTextCell, 0) - ADD_UNARY_METHOD(NSFormCell, setTitle, 0) - ADD_UNARY_METHOD(NSFormCell, setPlaceholderString, 0) - - NEW_RECEIVER(NSUserNotification) - ADD_UNARY_METHOD(NSUserNotification, setTitle, 0) - ADD_UNARY_METHOD(NSUserNotification, setSubtitle, 0) - ADD_UNARY_METHOD(NSUserNotification, setInformativeText, 0) - ADD_UNARY_METHOD(NSUserNotification, setActionButtonTitle, 0) - ADD_UNARY_METHOD(NSUserNotification, setOtherButtonTitle, 0) - ADD_UNARY_METHOD(NSUserNotification, setResponsePlaceholder, 0) - - NEW_RECEIVER(NSToolbarItem) - ADD_UNARY_METHOD(NSToolbarItem, setLabel, 0) - ADD_UNARY_METHOD(NSToolbarItem, setPaletteLabel, 0) - ADD_UNARY_METHOD(NSToolbarItem, setToolTip, 0) - - NEW_RECEIVER(NSProgress) - ADD_UNARY_METHOD(NSProgress, setLocalizedDescription, 0) - ADD_UNARY_METHOD(NSProgress, setLocalizedAdditionalDescription, 0) - - NEW_RECEIVER(NSSegmentedCell) - IdentifierInfo *setLabelNSSegmentedCell[] = {&Ctx.Idents.get("setLabel"), - &Ctx.Idents.get("forSegment")}; - ADD_METHOD(NSSegmentedCell, setLabelNSSegmentedCell, 2, 0) - IdentifierInfo *setToolTipNSSegmentedCell[] = {&Ctx.Idents.get("setToolTip"), - &Ctx.Idents.get("forSegment")}; - ADD_METHOD(NSSegmentedCell, setToolTipNSSegmentedCell, 2, 0) - - NEW_RECEIVER(NSUndoManager) - ADD_UNARY_METHOD(NSUndoManager, setActionName, 0) - ADD_UNARY_METHOD(NSUndoManager, undoMenuTitleForUndoActionName, 0) - ADD_UNARY_METHOD(NSUndoManager, redoMenuTitleForUndoActionName, 0) - - NEW_RECEIVER(NSMenuItem) - IdentifierInfo *initWithTitleNSMenuItem[] = { - &Ctx.Idents.get("initWithTitle"), &Ctx.Idents.get("action"), - &Ctx.Idents.get("keyEquivalent")}; - ADD_METHOD(NSMenuItem, initWithTitleNSMenuItem, 3, 0) - ADD_UNARY_METHOD(NSMenuItem, setTitle, 0) - ADD_UNARY_METHOD(NSMenuItem, setToolTip, 0) - - NEW_RECEIVER(NSPopUpButtonCell) - IdentifierInfo *initTextCellNSPopUpButtonCell[] = { - &Ctx.Idents.get("initTextCell"), &Ctx.Idents.get("pullsDown")}; - ADD_METHOD(NSPopUpButtonCell, initTextCellNSPopUpButtonCell, 2, 0) - ADD_UNARY_METHOD(NSPopUpButtonCell, addItemWithTitle, 0) - IdentifierInfo *insertItemWithTitleNSPopUpButtonCell[] = { - &Ctx.Idents.get("insertItemWithTitle"), &Ctx.Idents.get("atIndex")}; - ADD_METHOD(NSPopUpButtonCell, insertItemWithTitleNSPopUpButtonCell, 2, 0) - ADD_UNARY_METHOD(NSPopUpButtonCell, removeItemWithTitle, 0) - ADD_UNARY_METHOD(NSPopUpButtonCell, selectItemWithTitle, 0) - ADD_UNARY_METHOD(NSPopUpButtonCell, setTitle, 0) - - NEW_RECEIVER(NSViewController) - ADD_UNARY_METHOD(NSViewController, setTitle, 0) - - NEW_RECEIVER(NSMenu) - ADD_UNARY_METHOD(NSMenu, initWithTitle, 0) - IdentifierInfo *insertItemWithTitleNSMenu[] = { - &Ctx.Idents.get("insertItemWithTitle"), &Ctx.Idents.get("action"), - &Ctx.Idents.get("keyEquivalent"), &Ctx.Idents.get("atIndex")}; - ADD_METHOD(NSMenu, insertItemWithTitleNSMenu, 4, 0) - IdentifierInfo *addItemWithTitleNSMenu[] = { - &Ctx.Idents.get("addItemWithTitle"), &Ctx.Idents.get("action"), - &Ctx.Idents.get("keyEquivalent")}; - ADD_METHOD(NSMenu, addItemWithTitleNSMenu, 3, 0) - ADD_UNARY_METHOD(NSMenu, setTitle, 0) - - NEW_RECEIVER(UIMutableUserNotificationAction) - ADD_UNARY_METHOD(UIMutableUserNotificationAction, setTitle, 0) - - NEW_RECEIVER(NSForm) - ADD_UNARY_METHOD(NSForm, addEntry, 0) - IdentifierInfo *insertEntryNSForm[] = {&Ctx.Idents.get("insertEntry"), - &Ctx.Idents.get("atIndex")}; - ADD_METHOD(NSForm, insertEntryNSForm, 2, 0) - - NEW_RECEIVER(NSTextFieldCell) - ADD_UNARY_METHOD(NSTextFieldCell, setPlaceholderString, 0) - - NEW_RECEIVER(NSUserNotificationAction) - IdentifierInfo *actionWithIdentifierNSUserNotificationAction[] = { - &Ctx.Idents.get("actionWithIdentifier"), &Ctx.Idents.get("title")}; - ADD_METHOD(NSUserNotificationAction, - actionWithIdentifierNSUserNotificationAction, 2, 1) - - NEW_RECEIVER(UITextField) - ADD_UNARY_METHOD(UITextField, setText, 0) - ADD_UNARY_METHOD(UITextField, setPlaceholder, 0) - - NEW_RECEIVER(UIBarButtonItem) - IdentifierInfo *initWithTitleUIBarButtonItem[] = { - &Ctx.Idents.get("initWithTitle"), &Ctx.Idents.get("style"), - &Ctx.Idents.get("target"), &Ctx.Idents.get("action")}; - ADD_METHOD(UIBarButtonItem, initWithTitleUIBarButtonItem, 4, 0) - - NEW_RECEIVER(UIViewController) - ADD_UNARY_METHOD(UIViewController, setTitle, 0) - - NEW_RECEIVER(UISegmentedControl) - IdentifierInfo *insertSegmentWithTitleUISegmentedControl[] = { - &Ctx.Idents.get("insertSegmentWithTitle"), &Ctx.Idents.get("atIndex"), - &Ctx.Idents.get("animated")}; - ADD_METHOD(UISegmentedControl, insertSegmentWithTitleUISegmentedControl, 3, 0) - IdentifierInfo *setTitleUISegmentedControl[] = { - &Ctx.Idents.get("setTitle"), &Ctx.Idents.get("forSegmentAtIndex")}; - ADD_METHOD(UISegmentedControl, setTitleUISegmentedControl, 2, 0) - - NEW_RECEIVER(NSAccessibilityCustomRotorItemResult) - IdentifierInfo - *initWithItemLoadingTokenNSAccessibilityCustomRotorItemResult[] = { - &Ctx.Idents.get("initWithItemLoadingToken"), - &Ctx.Idents.get("customLabel")}; - ADD_METHOD(NSAccessibilityCustomRotorItemResult, - initWithItemLoadingTokenNSAccessibilityCustomRotorItemResult, 2, 1) - ADD_UNARY_METHOD(NSAccessibilityCustomRotorItemResult, setCustomLabel, 0) - - NEW_RECEIVER(UIContextualAction) - IdentifierInfo *contextualActionWithStyleUIContextualAction[] = { - &Ctx.Idents.get("contextualActionWithStyle"), &Ctx.Idents.get("title"), - &Ctx.Idents.get("handler")}; - ADD_METHOD(UIContextualAction, contextualActionWithStyleUIContextualAction, 3, - 1) - ADD_UNARY_METHOD(UIContextualAction, setTitle, 0) - - NEW_RECEIVER(NSAccessibilityCustomRotor) - IdentifierInfo *initWithLabelNSAccessibilityCustomRotor[] = { - &Ctx.Idents.get("initWithLabel"), &Ctx.Idents.get("itemSearchDelegate")}; - ADD_METHOD(NSAccessibilityCustomRotor, - initWithLabelNSAccessibilityCustomRotor, 2, 0) - ADD_UNARY_METHOD(NSAccessibilityCustomRotor, setLabel, 0) - - NEW_RECEIVER(NSWindowTab) - ADD_UNARY_METHOD(NSWindowTab, setTitle, 0) - ADD_UNARY_METHOD(NSWindowTab, setToolTip, 0) - - NEW_RECEIVER(NSAccessibilityCustomAction) - IdentifierInfo *initWithNameNSAccessibilityCustomAction[] = { - &Ctx.Idents.get("initWithName"), &Ctx.Idents.get("handler")}; - ADD_METHOD(NSAccessibilityCustomAction, - initWithNameNSAccessibilityCustomAction, 2, 0) - IdentifierInfo *initWithNameTargetNSAccessibilityCustomAction[] = { - &Ctx.Idents.get("initWithName"), &Ctx.Idents.get("target"), - &Ctx.Idents.get("selector")}; - ADD_METHOD(NSAccessibilityCustomAction, - initWithNameTargetNSAccessibilityCustomAction, 3, 0) - ADD_UNARY_METHOD(NSAccessibilityCustomAction, setName, 0) -} - -#define LSF_INSERT(function_name) LSF.insert(&Ctx.Idents.get(function_name)); -#define LSM_INSERT_NULLARY(receiver, method_name) \ - LSM.insert({&Ctx.Idents.get(receiver), Ctx.Selectors.getNullarySelector( \ - &Ctx.Idents.get(method_name))}); -#define LSM_INSERT_UNARY(receiver, method_name) \ - LSM.insert({&Ctx.Idents.get(receiver), \ - Ctx.Selectors.getUnarySelector(&Ctx.Idents.get(method_name))}); -#define LSM_INSERT_SELECTOR(receiver, method_list, arguments) \ - LSM.insert({&Ctx.Idents.get(receiver), \ - Ctx.Selectors.getSelector(arguments, method_list)}); - -/// Initializes a list of methods and C functions that return a localized string -void NonLocalizedStringChecker::initLocStringsMethods(ASTContext &Ctx) const { - if (!LSM.empty()) - return; - - IdentifierInfo *LocalizedStringMacro[] = { - &Ctx.Idents.get("localizedStringForKey"), &Ctx.Idents.get("value"), - &Ctx.Idents.get("table")}; - LSM_INSERT_SELECTOR("NSBundle", LocalizedStringMacro, 3) - LSM_INSERT_UNARY("NSDateFormatter", "stringFromDate") - IdentifierInfo *LocalizedStringFromDate[] = { - &Ctx.Idents.get("localizedStringFromDate"), &Ctx.Idents.get("dateStyle"), - &Ctx.Idents.get("timeStyle")}; - LSM_INSERT_SELECTOR("NSDateFormatter", LocalizedStringFromDate, 3) - LSM_INSERT_UNARY("NSNumberFormatter", "stringFromNumber") - LSM_INSERT_NULLARY("UITextField", "text") - LSM_INSERT_NULLARY("UITextView", "text") - LSM_INSERT_NULLARY("UILabel", "text") - - LSF_INSERT("CFDateFormatterCreateStringWithDate"); - LSF_INSERT("CFDateFormatterCreateStringWithAbsoluteTime"); - LSF_INSERT("CFNumberFormatterCreateStringWithNumber"); -} - -/// Checks to see if the method / function declaration includes -/// __attribute__((annotate("returns_localized_nsstring"))) -bool NonLocalizedStringChecker::isAnnotatedAsReturningLocalized( - const Decl *D) const { - if (!D) - return false; - return std::any_of( - D->specific_attr_begin<AnnotateAttr>(), - D->specific_attr_end<AnnotateAttr>(), [](const AnnotateAttr *Ann) { - return Ann->getAnnotation() == "returns_localized_nsstring"; - }); -} - -/// Checks to see if the method / function declaration includes -/// __attribute__((annotate("takes_localized_nsstring"))) -bool NonLocalizedStringChecker::isAnnotatedAsTakingLocalized( - const Decl *D) const { - if (!D) - return false; - return std::any_of( - D->specific_attr_begin<AnnotateAttr>(), - D->specific_attr_end<AnnotateAttr>(), [](const AnnotateAttr *Ann) { - return Ann->getAnnotation() == "takes_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); - } -} - - -static bool isDebuggingName(std::string name) { - return StringRef(name).lower().find("debug") != StringRef::npos; -} - -/// Returns true when, heuristically, the analyzer may be analyzing debugging -/// code. We use this to suppress localization diagnostics in un-localized user -/// interfaces that are only used for debugging and are therefore not user -/// facing. -static bool isDebuggingContext(CheckerContext &C) { - const Decl *D = C.getCurrentAnalysisDeclContext()->getDecl(); - if (!D) - return false; - - if (auto *ND = dyn_cast<NamedDecl>(D)) { - if (isDebuggingName(ND->getNameAsString())) - return true; - } - - const DeclContext *DC = D->getDeclContext(); - - if (auto *CD = dyn_cast<ObjCContainerDecl>(DC)) { - if (isDebuggingName(CD->getNameAsString())) - return true; - } - - return false; -} - - -/// Reports a localization error for the passed in method call and SVal -void NonLocalizedStringChecker::reportLocalizationError( - SVal S, const CallEvent &M, CheckerContext &C, int argumentNumber) const { - - // Don't warn about localization errors in classes and methods that - // may be debug code. - if (isDebuggingContext(C)) - return; - - static CheckerProgramPointTag Tag("NonLocalizedStringChecker", - "UnlocalizedString"); - ExplodedNode *ErrNode = C.addTransition(C.getState(), C.getPredecessor(), &Tag); - - if (!ErrNode) - return; - - // Generate the bug report. - std::unique_ptr<BugReport> R(new BugReport( - *BT, "User-facing text should use localized string macro", ErrNode)); - if (argumentNumber) { - R->addRange(M.getArgExpr(argumentNumber - 1)->getSourceRange()); - } else { - R->addRange(M.getSourceRange()); - } - R->markInteresting(S); - - const MemRegion *StringRegion = S.getAsRegion(); - if (StringRegion) - R->addVisitor(llvm::make_unique<NonLocalizedStringBRVisitor>(StringRegion)); - - C.emitReport(std::move(R)); -} - -/// Returns the argument number requiring localized string if it exists -/// otherwise, returns -1 -int NonLocalizedStringChecker::getLocalizedArgumentForSelector( - const IdentifierInfo *Receiver, Selector S) const { - auto method = UIMethods.find(Receiver); - - if (method == UIMethods.end()) - return -1; - - auto argumentIterator = method->getSecond().find(S); - - if (argumentIterator == method->getSecond().end()) - return -1; - - int argumentNumber = argumentIterator->getSecond(); - return argumentNumber; -} - -/// 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(); - - std::string SelectorString = S.getAsString(); - StringRef SelectorName = SelectorString; - assert(!SelectorName.empty()); - - if (odInfo->isStr("NSString")) { - // Handle the case where the receiver is an NSString - // These special NSString methods draw to the screen - - if (!(SelectorName.startswith("drawAtPoint") || - SelectorName.startswith("drawInRect") || - SelectorName.startswith("drawWithRect"))) - return; - - SVal svTitle = msg.getReceiverSVal(); - - bool isNonLocalized = hasNonLocalizedState(svTitle, C); - - if (isNonLocalized) { - reportLocalizationError(svTitle, msg, C); - } - } - - int argumentNumber = getLocalizedArgumentForSelector(odInfo, S); - // Go up each hierarchy of superclasses and their protocols - while (argumentNumber < 0 && OD->getSuperClass() != nullptr) { - for (const auto *P : OD->all_referenced_protocols()) { - argumentNumber = getLocalizedArgumentForSelector(P->getIdentifier(), S); - if (argumentNumber >= 0) - break; - } - if (argumentNumber < 0) { - OD = OD->getSuperClass(); - argumentNumber = getLocalizedArgumentForSelector(OD->getIdentifier(), S); - } - } - - if (argumentNumber < 0) { // There was no match in UIMethods - if (const Decl *D = msg.getDecl()) { - if (const ObjCMethodDecl *OMD = dyn_cast_or_null<ObjCMethodDecl>(D)) { - auto formals = OMD->parameters(); - for (unsigned i = 0, ei = formals.size(); i != ei; ++i) { - if (isAnnotatedAsTakingLocalized(formals[i])) { - argumentNumber = i; - break; - } - } - } - } - } - - if (argumentNumber < 0) // Still no match - return; - - 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); - } -} - -void NonLocalizedStringChecker::checkPreCall(const CallEvent &Call, - CheckerContext &C) const { - const Decl *D = Call.getDecl(); - if (D && isa<FunctionDecl>(D)) { - const FunctionDecl *FD = dyn_cast<FunctionDecl>(D); - auto formals = FD->parameters(); - for (unsigned i = 0, - ei = std::min(unsigned(formals.size()), Call.getNumArgs()); - i != ei; ++i) { - if (isAnnotatedAsTakingLocalized(formals[i])) { - auto actual = Call.getArgSVal(i); - if (hasNonLocalizedState(actual, C)) { - reportLocalizationError(actual, Call, C, i + 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; - - const IdentifierInfo *Identifier = Call.getCalleeIdentifier(); - - SVal sv = Call.getReturnValue(); - if (isAnnotatedAsReturningLocalized(D) || LSF.count(Identifier) != 0) { - 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(); - - Selector S = msg.getSelector(); - std::string SelectorName = S.getAsString(); - - std::pair<const IdentifierInfo *, Selector> MethodDescription = {odInfo, S}; - - if (LSM.count(MethodDescription) || - isAnnotatedAsReturningLocalized(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); -} - -std::shared_ptr<PathDiagnosticPiece> -NonLocalizedStringBRVisitor::VisitNode(const ExplodedNode *Succ, - BugReporterContext &BRC, BugReport &BR) { - if (Satisfied) - return nullptr; - - Optional<StmtPoint> Point = Succ->getLocation().getAs<StmtPoint>(); - if (!Point.hasValue()) - return nullptr; - - auto *LiteralExpr = dyn_cast<ObjCStringLiteral>(Point->getStmt()); - if (!LiteralExpr) - return nullptr; - - SVal LiteralSVal = Succ->getSVal(LiteralExpr); - 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 = std::make_shared<PathDiagnosticEventPiece>( - L, "Non-localized string literal here"); - Piece->addRange(LiteralExpr->getSourceRange()); - - return std::move(Piece); -} - -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) { - - // FIXME: We may be able to use PPCallbacks to check for empty context - // comments as part of preprocessing and avoid this re-lexing hack. - const ObjCInterfaceDecl *OD = ME->getReceiverInterface(); - if (!OD) - return; - - const IdentifierInfo *odInfo = OD->getIdentifier(); - - if (!(odInfo->isStr("NSBundle") && - ME->getSelector().getAsString() == - "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); - } - - bool Invalid = false; - const llvm::MemoryBuffer *BF = - Mgr.getSourceManager().getBuffer(SLInfo.first, SL, &Invalid); - if (Invalid) - return; - - 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 Issue (Apple)", - "Localized string macro should include a non-empty " - "comment for translators", - PathDiagnosticLocation(ME, BR.getSourceManager(), DCtx)); -} - -namespace { -class PluralMisuseChecker : public Checker<check::ASTCodeBody> { - - // A helper class, which walks the AST - class MethodCrawler : public RecursiveASTVisitor<MethodCrawler> { - BugReporter &BR; - const CheckerBase *Checker; - AnalysisDeclContext *AC; - - // This functions like a stack. We push on any IfStmt or - // ConditionalOperator that matches the condition - // and pop it off when we leave that statement - llvm::SmallVector<const clang::Stmt *, 8> MatchingStatements; - // This is true when we are the direct-child of a - // matching statement - bool InMatchingStatement = false; - - public: - explicit MethodCrawler(BugReporter &InBR, const CheckerBase *Checker, - AnalysisDeclContext *InAC) - : BR(InBR), Checker(Checker), AC(InAC) {} - - bool VisitIfStmt(const IfStmt *I); - bool EndVisitIfStmt(IfStmt *I); - bool TraverseIfStmt(IfStmt *x); - bool VisitConditionalOperator(const ConditionalOperator *C); - bool TraverseConditionalOperator(ConditionalOperator *C); - bool VisitCallExpr(const CallExpr *CE); - bool VisitObjCMessageExpr(const ObjCMessageExpr *ME); - - private: - void reportPluralMisuseError(const Stmt *S) const; - bool isCheckingPlurality(const Expr *E) const; - }; - -public: - void checkASTCodeBody(const Decl *D, AnalysisManager &Mgr, - BugReporter &BR) const { - MethodCrawler Visitor(BR, this, Mgr.getAnalysisDeclContext(D)); - Visitor.TraverseDecl(const_cast<Decl *>(D)); - } -}; -} // end anonymous namespace - -// Checks the condition of the IfStmt and returns true if one -// of the following heuristics are met: -// 1) The conidtion is a variable with "singular" or "plural" in the name -// 2) The condition is a binary operator with 1 or 2 on the right-hand side -bool PluralMisuseChecker::MethodCrawler::isCheckingPlurality( - const Expr *Condition) const { - const BinaryOperator *BO = nullptr; - // Accounts for when a VarDecl represents a BinaryOperator - if (const DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(Condition)) { - if (const VarDecl *VD = dyn_cast<VarDecl>(DRE->getDecl())) { - const Expr *InitExpr = VD->getInit(); - if (InitExpr) { - if (const BinaryOperator *B = - dyn_cast<BinaryOperator>(InitExpr->IgnoreParenImpCasts())) { - BO = B; - } - } - if (VD->getName().lower().find("plural") != StringRef::npos || - VD->getName().lower().find("singular") != StringRef::npos) { - return true; - } - } - } else if (const BinaryOperator *B = dyn_cast<BinaryOperator>(Condition)) { - BO = B; - } - - if (BO == nullptr) - return false; - - if (IntegerLiteral *IL = dyn_cast_or_null<IntegerLiteral>( - BO->getRHS()->IgnoreParenImpCasts())) { - llvm::APInt Value = IL->getValue(); - if (Value == 1 || Value == 2) { - return true; - } - } - return false; -} - -// A CallExpr with "LOC" in its identifier that takes in a string literal -// has been shown to almost always be a function that returns a localized -// string. Raise a diagnostic when this is in a statement that matches -// the condition. -bool PluralMisuseChecker::MethodCrawler::VisitCallExpr(const CallExpr *CE) { - if (InMatchingStatement) { - if (const FunctionDecl *FD = CE->getDirectCallee()) { - std::string NormalizedName = - StringRef(FD->getNameInfo().getAsString()).lower(); - if (NormalizedName.find("loc") != std::string::npos) { - for (const Expr *Arg : CE->arguments()) { - if (isa<ObjCStringLiteral>(Arg)) - reportPluralMisuseError(CE); - } - } - } - } - return true; -} - -// The other case is for NSLocalizedString which also returns -// a localized string. It's a macro for the ObjCMessageExpr -// [NSBundle localizedStringForKey:value:table:] Raise a -// diagnostic when this is in a statement that matches -// the condition. -bool PluralMisuseChecker::MethodCrawler::VisitObjCMessageExpr( - const ObjCMessageExpr *ME) { - const ObjCInterfaceDecl *OD = ME->getReceiverInterface(); - if (!OD) - return true; - - const IdentifierInfo *odInfo = OD->getIdentifier(); - - if (odInfo->isStr("NSBundle") && - ME->getSelector().getAsString() == "localizedStringForKey:value:table:") { - if (InMatchingStatement) { - reportPluralMisuseError(ME); - } - } - return true; -} - -/// Override TraverseIfStmt so we know when we are done traversing an IfStmt -bool PluralMisuseChecker::MethodCrawler::TraverseIfStmt(IfStmt *I) { - RecursiveASTVisitor<MethodCrawler>::TraverseIfStmt(I); - return EndVisitIfStmt(I); -} - -// EndVisit callbacks are not provided by the RecursiveASTVisitor -// so we override TraverseIfStmt and make a call to EndVisitIfStmt -// after traversing the IfStmt -bool PluralMisuseChecker::MethodCrawler::EndVisitIfStmt(IfStmt *I) { - MatchingStatements.pop_back(); - if (!MatchingStatements.empty()) { - if (MatchingStatements.back() != nullptr) { - InMatchingStatement = true; - return true; - } - } - InMatchingStatement = false; - return true; -} - -bool PluralMisuseChecker::MethodCrawler::VisitIfStmt(const IfStmt *I) { - const Expr *Condition = I->getCond()->IgnoreParenImpCasts(); - if (isCheckingPlurality(Condition)) { - MatchingStatements.push_back(I); - InMatchingStatement = true; - } else { - MatchingStatements.push_back(nullptr); - InMatchingStatement = false; - } - - return true; -} - -// Preliminary support for conditional operators. -bool PluralMisuseChecker::MethodCrawler::TraverseConditionalOperator( - ConditionalOperator *C) { - RecursiveASTVisitor<MethodCrawler>::TraverseConditionalOperator(C); - MatchingStatements.pop_back(); - if (!MatchingStatements.empty()) { - if (MatchingStatements.back() != nullptr) - InMatchingStatement = true; - else - InMatchingStatement = false; - } else { - InMatchingStatement = false; - } - return true; -} - -bool PluralMisuseChecker::MethodCrawler::VisitConditionalOperator( - const ConditionalOperator *C) { - const Expr *Condition = C->getCond()->IgnoreParenImpCasts(); - if (isCheckingPlurality(Condition)) { - MatchingStatements.push_back(C); - InMatchingStatement = true; - } else { - MatchingStatements.push_back(nullptr); - InMatchingStatement = false; - } - return true; -} - -void PluralMisuseChecker::MethodCrawler::reportPluralMisuseError( - const Stmt *S) const { - // Generate the bug report. - BR.EmitBasicReport(AC->getDecl(), Checker, "Plural Misuse", - "Localizability Issue (Apple)", - "Plural cases are not supported across all languages. " - "Use a .stringsdict file instead", - PathDiagnosticLocation(S, BR.getSourceManager(), AC)); -} - -//===----------------------------------------------------------------------===// -// Checker registration. -//===----------------------------------------------------------------------===// - -void ento::registerNonLocalizedStringChecker(CheckerManager &mgr) { - NonLocalizedStringChecker *checker = - mgr.registerChecker<NonLocalizedStringChecker>(); - checker->IsAggressive = - mgr.getAnalyzerOptions().getCheckerBooleanOption( - checker, "AggressiveReport"); -} - -bool ento::shouldRegisterNonLocalizedStringChecker(const LangOptions &LO) { - return true; -} - -void ento::registerEmptyLocalizationContextChecker(CheckerManager &mgr) { - mgr.registerChecker<EmptyLocalizationContextChecker>(); -} - -bool ento::shouldRegisterEmptyLocalizationContextChecker( - const LangOptions &LO) { - return true; -} - -void ento::registerPluralMisuseChecker(CheckerManager &mgr) { - mgr.registerChecker<PluralMisuseChecker>(); -} - -bool ento::shouldRegisterPluralMisuseChecker(const LangOptions &LO) { - return true; -} |
