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 | 1412 | 
1 files changed, 1412 insertions, 0 deletions
diff --git a/contrib/llvm/tools/clang/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp b/contrib/llvm/tools/clang/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp new file mode 100644 index 000000000000..849b1193c042 --- /dev/null +++ b/contrib/llvm/tools/clang/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp @@ -0,0 +1,1412 @@ +//=- 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 "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, +                                                 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<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; + +  ExplodedNode *ErrNode = C.getPredecessor(); +  static CheckerProgramPointTag Tag("NonLocalizedStringChecker", +                                    "UnlocalizedString"); +  ErrNode = C.addTransition(C.getState(), C.getPredecessor(), &Tag); + +  if (!ErrNode) +    return; + +  // Generate the bug report. +  std::unique_ptr<BugReport> R(new BugReport( +      *BT, "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, +                                       const ExplodedNode *Pred, +                                       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; +  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().getBooleanOption("AggressiveReport", false); +} + +void ento::registerEmptyLocalizationContextChecker(CheckerManager &mgr) { +  mgr.registerChecker<EmptyLocalizationContextChecker>(); +} + +void ento::registerPluralMisuseChecker(CheckerManager &mgr) { +  mgr.registerChecker<PluralMisuseChecker>(); +}  | 
