brintos

brintos / llvm-project-archived public Read only

0
0
Text · 52.1 KiB · 62d568f Raw
1417 lines · cpp
1//=- LocalizationChecker.cpp -------------------------------------*- C++ -*-==//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8//9//  This file defines a set of checks for localizability including:10//  1) A checker that warns about uses of non-localized NSStrings passed to11//     UI methods expecting localized strings12//  2) A syntactic checker that warns against the bad practice of13//     not including a comment in NSLocalizedString macros.14//15//===----------------------------------------------------------------------===//16 17#include "clang/AST/Attr.h"18#include "clang/AST/Decl.h"19#include "clang/AST/DeclObjC.h"20#include "clang/AST/DynamicRecursiveASTVisitor.h"21#include "clang/AST/StmtVisitor.h"22#include "clang/Lex/Lexer.h"23#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"24#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"25#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"26#include "clang/StaticAnalyzer/Core/Checker.h"27#include "clang/StaticAnalyzer/Core/CheckerManager.h"28#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"29#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"30#include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h"31#include "llvm/ADT/STLExtras.h"32#include "llvm/Support/Unicode.h"33#include <optional>34 35using namespace clang;36using namespace ento;37 38namespace {39struct LocalizedState {40private:41  enum Kind { NonLocalized, Localized } K;42  LocalizedState(Kind InK) : K(InK) {}43 44public:45  bool isLocalized() const { return K == Localized; }46  bool isNonLocalized() const { return K == NonLocalized; }47 48  static LocalizedState getLocalized() { return LocalizedState(Localized); }49  static LocalizedState getNonLocalized() {50    return LocalizedState(NonLocalized);51  }52 53  // Overload the == operator54  bool operator==(const LocalizedState &X) const { return K == X.K; }55 56  // LLVMs equivalent of a hash function57  void Profile(llvm::FoldingSetNodeID &ID) const { ID.AddInteger(K); }58};59 60class NonLocalizedStringChecker61    : public Checker<check::PreCall, check::PostCall, check::PreObjCMessage,62                     check::PostObjCMessage,63                     check::PostStmt<ObjCStringLiteral>> {64 65  const BugType BT{this, "Unlocalizable string",66                   "Localizability Issue (Apple)"};67 68  // Methods that require a localized string69  mutable llvm::DenseMap<const IdentifierInfo *,70                         llvm::DenseMap<Selector, uint8_t>> UIMethods;71  // Methods that return a localized string72  mutable llvm::SmallSet<std::pair<const IdentifierInfo *, Selector>, 12> LSM;73  // C Functions that return a localized string74  mutable llvm::SmallPtrSet<const IdentifierInfo *, 5> LSF;75 76  void initUIMethods(ASTContext &Ctx) const;77  void initLocStringsMethods(ASTContext &Ctx) const;78 79  bool hasNonLocalizedState(SVal S, CheckerContext &C) const;80  bool hasLocalizedState(SVal S, CheckerContext &C) const;81  void setNonLocalizedState(SVal S, CheckerContext &C) const;82  void setLocalizedState(SVal S, CheckerContext &C) const;83 84  bool isAnnotatedAsReturningLocalized(const Decl *D) const;85  bool isAnnotatedAsTakingLocalized(const Decl *D) const;86  void reportLocalizationError(SVal S, const CallEvent &M, CheckerContext &C,87                               int argumentNumber = 0) const;88 89  int getLocalizedArgumentForSelector(const IdentifierInfo *Receiver,90                                      Selector S) const;91 92public:93  // When this parameter is set to true, the checker assumes all94  // methods that return NSStrings are unlocalized. Thus, more false95  // positives will be reported.96  bool IsAggressive = false;97 98  void checkPreObjCMessage(const ObjCMethodCall &msg, CheckerContext &C) const;99  void checkPostObjCMessage(const ObjCMethodCall &msg, CheckerContext &C) const;100  void checkPostStmt(const ObjCStringLiteral *SL, CheckerContext &C) const;101  void checkPreCall(const CallEvent &Call, CheckerContext &C) const;102  void checkPostCall(const CallEvent &Call, CheckerContext &C) const;103};104 105} // end anonymous namespace106 107REGISTER_MAP_WITH_PROGRAMSTATE(LocalizedMemMap, const MemRegion *,108                               LocalizedState)109 110namespace {111class NonLocalizedStringBRVisitor final : public BugReporterVisitor {112 113  const MemRegion *NonLocalizedString;114  bool Satisfied;115 116public:117  NonLocalizedStringBRVisitor(const MemRegion *NonLocalizedString)118      : NonLocalizedString(NonLocalizedString), Satisfied(false) {119    assert(NonLocalizedString);120  }121 122  PathDiagnosticPieceRef VisitNode(const ExplodedNode *Succ,123                                   BugReporterContext &BRC,124                                   PathSensitiveBugReport &BR) override;125 126  void Profile(llvm::FoldingSetNodeID &ID) const override {127    ID.Add(NonLocalizedString);128  }129};130} // End anonymous namespace.131 132#define NEW_RECEIVER(receiver)                                                 \133  llvm::DenseMap<Selector, uint8_t> &receiver##M =                             \134      UIMethods[&Ctx.Idents.get(#receiver)];135#define ADD_NULLARY_METHOD(receiver, method, argument)                         \136  receiver##M.insert(                                                          \137      {Ctx.Selectors.getNullarySelector(&Ctx.Idents.get(#method)), argument});138#define ADD_UNARY_METHOD(receiver, method, argument)                           \139  receiver##M.insert(                                                          \140      {Ctx.Selectors.getUnarySelector(&Ctx.Idents.get(#method)), argument});141#define ADD_METHOD(receiver, method_list, count, argument)                     \142  receiver##M.insert({Ctx.Selectors.getSelector(count, method_list), argument});143 144/// Initializes a list of methods that require a localized string145/// Format: {"ClassName", {{"selectorName:", LocStringArg#}, ...}, ...}146void NonLocalizedStringChecker::initUIMethods(ASTContext &Ctx) const {147  if (!UIMethods.empty())148    return;149 150  // UI Methods151  NEW_RECEIVER(UISearchDisplayController)152  ADD_UNARY_METHOD(UISearchDisplayController, setSearchResultsTitle, 0)153 154  NEW_RECEIVER(UITabBarItem)155  const IdentifierInfo *initWithTitleUITabBarItemTag[] = {156      &Ctx.Idents.get("initWithTitle"), &Ctx.Idents.get("image"),157      &Ctx.Idents.get("tag")};158  ADD_METHOD(UITabBarItem, initWithTitleUITabBarItemTag, 3, 0)159  const IdentifierInfo *initWithTitleUITabBarItemImage[] = {160      &Ctx.Idents.get("initWithTitle"), &Ctx.Idents.get("image"),161      &Ctx.Idents.get("selectedImage")};162  ADD_METHOD(UITabBarItem, initWithTitleUITabBarItemImage, 3, 0)163 164  NEW_RECEIVER(NSDockTile)165  ADD_UNARY_METHOD(NSDockTile, setBadgeLabel, 0)166 167  NEW_RECEIVER(NSStatusItem)168  ADD_UNARY_METHOD(NSStatusItem, setTitle, 0)169  ADD_UNARY_METHOD(NSStatusItem, setToolTip, 0)170 171  NEW_RECEIVER(UITableViewRowAction)172  const IdentifierInfo *rowActionWithStyleUITableViewRowAction[] = {173      &Ctx.Idents.get("rowActionWithStyle"), &Ctx.Idents.get("title"),174      &Ctx.Idents.get("handler")};175  ADD_METHOD(UITableViewRowAction, rowActionWithStyleUITableViewRowAction, 3, 1)176  ADD_UNARY_METHOD(UITableViewRowAction, setTitle, 0)177 178  NEW_RECEIVER(NSBox)179  ADD_UNARY_METHOD(NSBox, setTitle, 0)180 181  NEW_RECEIVER(NSButton)182  ADD_UNARY_METHOD(NSButton, setTitle, 0)183  ADD_UNARY_METHOD(NSButton, setAlternateTitle, 0)184  const IdentifierInfo *radioButtonWithTitleNSButton[] = {185      &Ctx.Idents.get("radioButtonWithTitle"), &Ctx.Idents.get("target"),186      &Ctx.Idents.get("action")};187  ADD_METHOD(NSButton, radioButtonWithTitleNSButton, 3, 0)188  const IdentifierInfo *buttonWithTitleNSButtonImage[] = {189      &Ctx.Idents.get("buttonWithTitle"), &Ctx.Idents.get("image"),190      &Ctx.Idents.get("target"), &Ctx.Idents.get("action")};191  ADD_METHOD(NSButton, buttonWithTitleNSButtonImage, 4, 0)192  const IdentifierInfo *checkboxWithTitleNSButton[] = {193      &Ctx.Idents.get("checkboxWithTitle"), &Ctx.Idents.get("target"),194      &Ctx.Idents.get("action")};195  ADD_METHOD(NSButton, checkboxWithTitleNSButton, 3, 0)196  const IdentifierInfo *buttonWithTitleNSButtonTarget[] = {197      &Ctx.Idents.get("buttonWithTitle"), &Ctx.Idents.get("target"),198      &Ctx.Idents.get("action")};199  ADD_METHOD(NSButton, buttonWithTitleNSButtonTarget, 3, 0)200 201  NEW_RECEIVER(NSSavePanel)202  ADD_UNARY_METHOD(NSSavePanel, setPrompt, 0)203  ADD_UNARY_METHOD(NSSavePanel, setTitle, 0)204  ADD_UNARY_METHOD(NSSavePanel, setNameFieldLabel, 0)205  ADD_UNARY_METHOD(NSSavePanel, setNameFieldStringValue, 0)206  ADD_UNARY_METHOD(NSSavePanel, setMessage, 0)207 208  NEW_RECEIVER(UIPrintInfo)209  ADD_UNARY_METHOD(UIPrintInfo, setJobName, 0)210 211  NEW_RECEIVER(NSTabViewItem)212  ADD_UNARY_METHOD(NSTabViewItem, setLabel, 0)213  ADD_UNARY_METHOD(NSTabViewItem, setToolTip, 0)214 215  NEW_RECEIVER(NSBrowser)216  const IdentifierInfo *setTitleNSBrowser[] = {&Ctx.Idents.get("setTitle"),217                                               &Ctx.Idents.get("ofColumn")};218  ADD_METHOD(NSBrowser, setTitleNSBrowser, 2, 0)219 220  NEW_RECEIVER(UIAccessibilityElement)221  ADD_UNARY_METHOD(UIAccessibilityElement, setAccessibilityLabel, 0)222  ADD_UNARY_METHOD(UIAccessibilityElement, setAccessibilityHint, 0)223  ADD_UNARY_METHOD(UIAccessibilityElement, setAccessibilityValue, 0)224 225  NEW_RECEIVER(UIAlertAction)226  const IdentifierInfo *actionWithTitleUIAlertAction[] = {227      &Ctx.Idents.get("actionWithTitle"), &Ctx.Idents.get("style"),228      &Ctx.Idents.get("handler")};229  ADD_METHOD(UIAlertAction, actionWithTitleUIAlertAction, 3, 0)230 231  NEW_RECEIVER(NSPopUpButton)232  ADD_UNARY_METHOD(NSPopUpButton, addItemWithTitle, 0)233  const IdentifierInfo *insertItemWithTitleNSPopUpButton[] = {234      &Ctx.Idents.get("insertItemWithTitle"), &Ctx.Idents.get("atIndex")};235  ADD_METHOD(NSPopUpButton, insertItemWithTitleNSPopUpButton, 2, 0)236  ADD_UNARY_METHOD(NSPopUpButton, removeItemWithTitle, 0)237  ADD_UNARY_METHOD(NSPopUpButton, selectItemWithTitle, 0)238  ADD_UNARY_METHOD(NSPopUpButton, setTitle, 0)239 240  NEW_RECEIVER(NSTableViewRowAction)241  const IdentifierInfo *rowActionWithStyleNSTableViewRowAction[] = {242      &Ctx.Idents.get("rowActionWithStyle"), &Ctx.Idents.get("title"),243      &Ctx.Idents.get("handler")};244  ADD_METHOD(NSTableViewRowAction, rowActionWithStyleNSTableViewRowAction, 3, 1)245  ADD_UNARY_METHOD(NSTableViewRowAction, setTitle, 0)246 247  NEW_RECEIVER(NSImage)248  ADD_UNARY_METHOD(NSImage, setAccessibilityDescription, 0)249 250  NEW_RECEIVER(NSUserActivity)251  ADD_UNARY_METHOD(NSUserActivity, setTitle, 0)252 253  NEW_RECEIVER(NSPathControlItem)254  ADD_UNARY_METHOD(NSPathControlItem, setTitle, 0)255 256  NEW_RECEIVER(NSCell)257  ADD_UNARY_METHOD(NSCell, initTextCell, 0)258  ADD_UNARY_METHOD(NSCell, setTitle, 0)259  ADD_UNARY_METHOD(NSCell, setStringValue, 0)260 261  NEW_RECEIVER(NSPathControl)262  ADD_UNARY_METHOD(NSPathControl, setPlaceholderString, 0)263 264  NEW_RECEIVER(UIAccessibility)265  ADD_UNARY_METHOD(UIAccessibility, setAccessibilityLabel, 0)266  ADD_UNARY_METHOD(UIAccessibility, setAccessibilityHint, 0)267  ADD_UNARY_METHOD(UIAccessibility, setAccessibilityValue, 0)268 269  NEW_RECEIVER(NSTableColumn)270  ADD_UNARY_METHOD(NSTableColumn, setTitle, 0)271  ADD_UNARY_METHOD(NSTableColumn, setHeaderToolTip, 0)272 273  NEW_RECEIVER(NSSegmentedControl)274  const IdentifierInfo *setLabelNSSegmentedControl[] = {275      &Ctx.Idents.get("setLabel"), &Ctx.Idents.get("forSegment")};276  ADD_METHOD(NSSegmentedControl, setLabelNSSegmentedControl, 2, 0)277  const IdentifierInfo *setToolTipNSSegmentedControl[] = {278      &Ctx.Idents.get("setToolTip"), &Ctx.Idents.get("forSegment")};279  ADD_METHOD(NSSegmentedControl, setToolTipNSSegmentedControl, 2, 0)280 281  NEW_RECEIVER(NSButtonCell)282  ADD_UNARY_METHOD(NSButtonCell, setTitle, 0)283  ADD_UNARY_METHOD(NSButtonCell, setAlternateTitle, 0)284 285  NEW_RECEIVER(NSDatePickerCell)286  ADD_UNARY_METHOD(NSDatePickerCell, initTextCell, 0)287 288  NEW_RECEIVER(NSSliderCell)289  ADD_UNARY_METHOD(NSSliderCell, setTitle, 0)290 291  NEW_RECEIVER(NSControl)292  ADD_UNARY_METHOD(NSControl, setStringValue, 0)293 294  NEW_RECEIVER(NSAccessibility)295  ADD_UNARY_METHOD(NSAccessibility, setAccessibilityValueDescription, 0)296  ADD_UNARY_METHOD(NSAccessibility, setAccessibilityLabel, 0)297  ADD_UNARY_METHOD(NSAccessibility, setAccessibilityTitle, 0)298  ADD_UNARY_METHOD(NSAccessibility, setAccessibilityPlaceholderValue, 0)299  ADD_UNARY_METHOD(NSAccessibility, setAccessibilityHelp, 0)300 301  NEW_RECEIVER(NSMatrix)302  const IdentifierInfo *setToolTipNSMatrix[] = {&Ctx.Idents.get("setToolTip"),303                                                &Ctx.Idents.get("forCell")};304  ADD_METHOD(NSMatrix, setToolTipNSMatrix, 2, 0)305 306  NEW_RECEIVER(NSPrintPanel)307  ADD_UNARY_METHOD(NSPrintPanel, setDefaultButtonTitle, 0)308 309  NEW_RECEIVER(UILocalNotification)310  ADD_UNARY_METHOD(UILocalNotification, setAlertBody, 0)311  ADD_UNARY_METHOD(UILocalNotification, setAlertAction, 0)312  ADD_UNARY_METHOD(UILocalNotification, setAlertTitle, 0)313 314  NEW_RECEIVER(NSSlider)315  ADD_UNARY_METHOD(NSSlider, setTitle, 0)316 317  NEW_RECEIVER(UIMenuItem)318  const IdentifierInfo *initWithTitleUIMenuItem[] = {319      &Ctx.Idents.get("initWithTitle"), &Ctx.Idents.get("action")};320  ADD_METHOD(UIMenuItem, initWithTitleUIMenuItem, 2, 0)321  ADD_UNARY_METHOD(UIMenuItem, setTitle, 0)322 323  NEW_RECEIVER(UIAlertController)324  const IdentifierInfo *alertControllerWithTitleUIAlertController[] = {325      &Ctx.Idents.get("alertControllerWithTitle"), &Ctx.Idents.get("message"),326      &Ctx.Idents.get("preferredStyle")};327  ADD_METHOD(UIAlertController, alertControllerWithTitleUIAlertController, 3, 1)328  ADD_UNARY_METHOD(UIAlertController, setTitle, 0)329  ADD_UNARY_METHOD(UIAlertController, setMessage, 0)330 331  NEW_RECEIVER(UIApplicationShortcutItem)332  const IdentifierInfo *initWithTypeUIApplicationShortcutItemIcon[] = {333      &Ctx.Idents.get("initWithType"), &Ctx.Idents.get("localizedTitle"),334      &Ctx.Idents.get("localizedSubtitle"), &Ctx.Idents.get("icon"),335      &Ctx.Idents.get("userInfo")};336  ADD_METHOD(UIApplicationShortcutItem,337             initWithTypeUIApplicationShortcutItemIcon, 5, 1)338  const IdentifierInfo *initWithTypeUIApplicationShortcutItem[] = {339      &Ctx.Idents.get("initWithType"), &Ctx.Idents.get("localizedTitle")};340  ADD_METHOD(UIApplicationShortcutItem, initWithTypeUIApplicationShortcutItem,341             2, 1)342 343  NEW_RECEIVER(UIActionSheet)344  const IdentifierInfo *initWithTitleUIActionSheet[] = {345      &Ctx.Idents.get("initWithTitle"), &Ctx.Idents.get("delegate"),346      &Ctx.Idents.get("cancelButtonTitle"),347      &Ctx.Idents.get("destructiveButtonTitle"),348      &Ctx.Idents.get("otherButtonTitles")};349  ADD_METHOD(UIActionSheet, initWithTitleUIActionSheet, 5, 0)350  ADD_UNARY_METHOD(UIActionSheet, addButtonWithTitle, 0)351  ADD_UNARY_METHOD(UIActionSheet, setTitle, 0)352 353  NEW_RECEIVER(UIAccessibilityCustomAction)354  const IdentifierInfo *initWithNameUIAccessibilityCustomAction[] = {355      &Ctx.Idents.get("initWithName"), &Ctx.Idents.get("target"),356      &Ctx.Idents.get("selector")};357  ADD_METHOD(UIAccessibilityCustomAction,358             initWithNameUIAccessibilityCustomAction, 3, 0)359  ADD_UNARY_METHOD(UIAccessibilityCustomAction, setName, 0)360 361  NEW_RECEIVER(UISearchBar)362  ADD_UNARY_METHOD(UISearchBar, setText, 0)363  ADD_UNARY_METHOD(UISearchBar, setPrompt, 0)364  ADD_UNARY_METHOD(UISearchBar, setPlaceholder, 0)365 366  NEW_RECEIVER(UIBarItem)367  ADD_UNARY_METHOD(UIBarItem, setTitle, 0)368 369  NEW_RECEIVER(UITextView)370  ADD_UNARY_METHOD(UITextView, setText, 0)371 372  NEW_RECEIVER(NSView)373  ADD_UNARY_METHOD(NSView, setToolTip, 0)374 375  NEW_RECEIVER(NSTextField)376  ADD_UNARY_METHOD(NSTextField, setPlaceholderString, 0)377  ADD_UNARY_METHOD(NSTextField, textFieldWithString, 0)378  ADD_UNARY_METHOD(NSTextField, wrappingLabelWithString, 0)379  ADD_UNARY_METHOD(NSTextField, labelWithString, 0)380 381  NEW_RECEIVER(NSAttributedString)382  ADD_UNARY_METHOD(NSAttributedString, initWithString, 0)383  const IdentifierInfo *initWithStringNSAttributedString[] = {384      &Ctx.Idents.get("initWithString"), &Ctx.Idents.get("attributes")};385  ADD_METHOD(NSAttributedString, initWithStringNSAttributedString, 2, 0)386 387  NEW_RECEIVER(NSText)388  ADD_UNARY_METHOD(NSText, setString, 0)389 390  NEW_RECEIVER(UIKeyCommand)391  const IdentifierInfo *keyCommandWithInputUIKeyCommand[] = {392      &Ctx.Idents.get("keyCommandWithInput"), &Ctx.Idents.get("modifierFlags"),393      &Ctx.Idents.get("action"), &Ctx.Idents.get("discoverabilityTitle")};394  ADD_METHOD(UIKeyCommand, keyCommandWithInputUIKeyCommand, 4, 3)395  ADD_UNARY_METHOD(UIKeyCommand, setDiscoverabilityTitle, 0)396 397  NEW_RECEIVER(UILabel)398  ADD_UNARY_METHOD(UILabel, setText, 0)399 400  NEW_RECEIVER(NSAlert)401  const IdentifierInfo *alertWithMessageTextNSAlert[] = {402      &Ctx.Idents.get("alertWithMessageText"), &Ctx.Idents.get("defaultButton"),403      &Ctx.Idents.get("alternateButton"), &Ctx.Idents.get("otherButton"),404      &Ctx.Idents.get("informativeTextWithFormat")};405  ADD_METHOD(NSAlert, alertWithMessageTextNSAlert, 5, 0)406  ADD_UNARY_METHOD(NSAlert, addButtonWithTitle, 0)407  ADD_UNARY_METHOD(NSAlert, setMessageText, 0)408  ADD_UNARY_METHOD(NSAlert, setInformativeText, 0)409  ADD_UNARY_METHOD(NSAlert, setHelpAnchor, 0)410 411  NEW_RECEIVER(UIMutableApplicationShortcutItem)412  ADD_UNARY_METHOD(UIMutableApplicationShortcutItem, setLocalizedTitle, 0)413  ADD_UNARY_METHOD(UIMutableApplicationShortcutItem, setLocalizedSubtitle, 0)414 415  NEW_RECEIVER(UIButton)416  const IdentifierInfo *setTitleUIButton[] = {&Ctx.Idents.get("setTitle"),417                                              &Ctx.Idents.get("forState")};418  ADD_METHOD(UIButton, setTitleUIButton, 2, 0)419 420  NEW_RECEIVER(NSWindow)421  ADD_UNARY_METHOD(NSWindow, setTitle, 0)422  const IdentifierInfo *minFrameWidthWithTitleNSWindow[] = {423      &Ctx.Idents.get("minFrameWidthWithTitle"), &Ctx.Idents.get("styleMask")};424  ADD_METHOD(NSWindow, minFrameWidthWithTitleNSWindow, 2, 0)425  ADD_UNARY_METHOD(NSWindow, setMiniwindowTitle, 0)426 427  NEW_RECEIVER(NSPathCell)428  ADD_UNARY_METHOD(NSPathCell, setPlaceholderString, 0)429 430  NEW_RECEIVER(UIDocumentMenuViewController)431  const IdentifierInfo *addOptionWithTitleUIDocumentMenuViewController[] = {432      &Ctx.Idents.get("addOptionWithTitle"), &Ctx.Idents.get("image"),433      &Ctx.Idents.get("order"), &Ctx.Idents.get("handler")};434  ADD_METHOD(UIDocumentMenuViewController,435             addOptionWithTitleUIDocumentMenuViewController, 4, 0)436 437  NEW_RECEIVER(UINavigationItem)438  ADD_UNARY_METHOD(UINavigationItem, initWithTitle, 0)439  ADD_UNARY_METHOD(UINavigationItem, setTitle, 0)440  ADD_UNARY_METHOD(UINavigationItem, setPrompt, 0)441 442  NEW_RECEIVER(UIAlertView)443  const IdentifierInfo *initWithTitleUIAlertView[] = {444      &Ctx.Idents.get("initWithTitle"), &Ctx.Idents.get("message"),445      &Ctx.Idents.get("delegate"), &Ctx.Idents.get("cancelButtonTitle"),446      &Ctx.Idents.get("otherButtonTitles")};447  ADD_METHOD(UIAlertView, initWithTitleUIAlertView, 5, 0)448  ADD_UNARY_METHOD(UIAlertView, addButtonWithTitle, 0)449  ADD_UNARY_METHOD(UIAlertView, setTitle, 0)450  ADD_UNARY_METHOD(UIAlertView, setMessage, 0)451 452  NEW_RECEIVER(NSFormCell)453  ADD_UNARY_METHOD(NSFormCell, initTextCell, 0)454  ADD_UNARY_METHOD(NSFormCell, setTitle, 0)455  ADD_UNARY_METHOD(NSFormCell, setPlaceholderString, 0)456 457  NEW_RECEIVER(NSUserNotification)458  ADD_UNARY_METHOD(NSUserNotification, setTitle, 0)459  ADD_UNARY_METHOD(NSUserNotification, setSubtitle, 0)460  ADD_UNARY_METHOD(NSUserNotification, setInformativeText, 0)461  ADD_UNARY_METHOD(NSUserNotification, setActionButtonTitle, 0)462  ADD_UNARY_METHOD(NSUserNotification, setOtherButtonTitle, 0)463  ADD_UNARY_METHOD(NSUserNotification, setResponsePlaceholder, 0)464 465  NEW_RECEIVER(NSToolbarItem)466  ADD_UNARY_METHOD(NSToolbarItem, setLabel, 0)467  ADD_UNARY_METHOD(NSToolbarItem, setPaletteLabel, 0)468  ADD_UNARY_METHOD(NSToolbarItem, setToolTip, 0)469 470  NEW_RECEIVER(NSProgress)471  ADD_UNARY_METHOD(NSProgress, setLocalizedDescription, 0)472  ADD_UNARY_METHOD(NSProgress, setLocalizedAdditionalDescription, 0)473 474  NEW_RECEIVER(NSSegmentedCell)475  const IdentifierInfo *setLabelNSSegmentedCell[] = {476      &Ctx.Idents.get("setLabel"), &Ctx.Idents.get("forSegment")};477  ADD_METHOD(NSSegmentedCell, setLabelNSSegmentedCell, 2, 0)478  const IdentifierInfo *setToolTipNSSegmentedCell[] = {479      &Ctx.Idents.get("setToolTip"), &Ctx.Idents.get("forSegment")};480  ADD_METHOD(NSSegmentedCell, setToolTipNSSegmentedCell, 2, 0)481 482  NEW_RECEIVER(NSUndoManager)483  ADD_UNARY_METHOD(NSUndoManager, setActionName, 0)484  ADD_UNARY_METHOD(NSUndoManager, undoMenuTitleForUndoActionName, 0)485  ADD_UNARY_METHOD(NSUndoManager, redoMenuTitleForUndoActionName, 0)486 487  NEW_RECEIVER(NSMenuItem)488  const IdentifierInfo *initWithTitleNSMenuItem[] = {489      &Ctx.Idents.get("initWithTitle"), &Ctx.Idents.get("action"),490      &Ctx.Idents.get("keyEquivalent")};491  ADD_METHOD(NSMenuItem, initWithTitleNSMenuItem, 3, 0)492  ADD_UNARY_METHOD(NSMenuItem, setTitle, 0)493  ADD_UNARY_METHOD(NSMenuItem, setToolTip, 0)494 495  NEW_RECEIVER(NSPopUpButtonCell)496  const IdentifierInfo *initTextCellNSPopUpButtonCell[] = {497      &Ctx.Idents.get("initTextCell"), &Ctx.Idents.get("pullsDown")};498  ADD_METHOD(NSPopUpButtonCell, initTextCellNSPopUpButtonCell, 2, 0)499  ADD_UNARY_METHOD(NSPopUpButtonCell, addItemWithTitle, 0)500  const IdentifierInfo *insertItemWithTitleNSPopUpButtonCell[] = {501      &Ctx.Idents.get("insertItemWithTitle"), &Ctx.Idents.get("atIndex")};502  ADD_METHOD(NSPopUpButtonCell, insertItemWithTitleNSPopUpButtonCell, 2, 0)503  ADD_UNARY_METHOD(NSPopUpButtonCell, removeItemWithTitle, 0)504  ADD_UNARY_METHOD(NSPopUpButtonCell, selectItemWithTitle, 0)505  ADD_UNARY_METHOD(NSPopUpButtonCell, setTitle, 0)506 507  NEW_RECEIVER(NSViewController)508  ADD_UNARY_METHOD(NSViewController, setTitle, 0)509 510  NEW_RECEIVER(NSMenu)511  ADD_UNARY_METHOD(NSMenu, initWithTitle, 0)512  const IdentifierInfo *insertItemWithTitleNSMenu[] = {513      &Ctx.Idents.get("insertItemWithTitle"), &Ctx.Idents.get("action"),514      &Ctx.Idents.get("keyEquivalent"), &Ctx.Idents.get("atIndex")};515  ADD_METHOD(NSMenu, insertItemWithTitleNSMenu, 4, 0)516  const IdentifierInfo *addItemWithTitleNSMenu[] = {517      &Ctx.Idents.get("addItemWithTitle"), &Ctx.Idents.get("action"),518      &Ctx.Idents.get("keyEquivalent")};519  ADD_METHOD(NSMenu, addItemWithTitleNSMenu, 3, 0)520  ADD_UNARY_METHOD(NSMenu, setTitle, 0)521 522  NEW_RECEIVER(UIMutableUserNotificationAction)523  ADD_UNARY_METHOD(UIMutableUserNotificationAction, setTitle, 0)524 525  NEW_RECEIVER(NSForm)526  ADD_UNARY_METHOD(NSForm, addEntry, 0)527  const IdentifierInfo *insertEntryNSForm[] = {&Ctx.Idents.get("insertEntry"),528                                               &Ctx.Idents.get("atIndex")};529  ADD_METHOD(NSForm, insertEntryNSForm, 2, 0)530 531  NEW_RECEIVER(NSTextFieldCell)532  ADD_UNARY_METHOD(NSTextFieldCell, setPlaceholderString, 0)533 534  NEW_RECEIVER(NSUserNotificationAction)535  const IdentifierInfo *actionWithIdentifierNSUserNotificationAction[] = {536      &Ctx.Idents.get("actionWithIdentifier"), &Ctx.Idents.get("title")};537  ADD_METHOD(NSUserNotificationAction,538             actionWithIdentifierNSUserNotificationAction, 2, 1)539 540  NEW_RECEIVER(UITextField)541  ADD_UNARY_METHOD(UITextField, setText, 0)542  ADD_UNARY_METHOD(UITextField, setPlaceholder, 0)543 544  NEW_RECEIVER(UIBarButtonItem)545  const IdentifierInfo *initWithTitleUIBarButtonItem[] = {546      &Ctx.Idents.get("initWithTitle"), &Ctx.Idents.get("style"),547      &Ctx.Idents.get("target"), &Ctx.Idents.get("action")};548  ADD_METHOD(UIBarButtonItem, initWithTitleUIBarButtonItem, 4, 0)549 550  NEW_RECEIVER(UIViewController)551  ADD_UNARY_METHOD(UIViewController, setTitle, 0)552 553  NEW_RECEIVER(UISegmentedControl)554  const IdentifierInfo *insertSegmentWithTitleUISegmentedControl[] = {555      &Ctx.Idents.get("insertSegmentWithTitle"), &Ctx.Idents.get("atIndex"),556      &Ctx.Idents.get("animated")};557  ADD_METHOD(UISegmentedControl, insertSegmentWithTitleUISegmentedControl, 3, 0)558  const IdentifierInfo *setTitleUISegmentedControl[] = {559      &Ctx.Idents.get("setTitle"), &Ctx.Idents.get("forSegmentAtIndex")};560  ADD_METHOD(UISegmentedControl, setTitleUISegmentedControl, 2, 0)561 562  NEW_RECEIVER(NSAccessibilityCustomRotorItemResult)563  const IdentifierInfo564      *initWithItemLoadingTokenNSAccessibilityCustomRotorItemResult[] = {565          &Ctx.Idents.get("initWithItemLoadingToken"),566          &Ctx.Idents.get("customLabel")};567  ADD_METHOD(NSAccessibilityCustomRotorItemResult,568             initWithItemLoadingTokenNSAccessibilityCustomRotorItemResult, 2, 1)569  ADD_UNARY_METHOD(NSAccessibilityCustomRotorItemResult, setCustomLabel, 0)570 571  NEW_RECEIVER(UIContextualAction)572  const IdentifierInfo *contextualActionWithStyleUIContextualAction[] = {573      &Ctx.Idents.get("contextualActionWithStyle"), &Ctx.Idents.get("title"),574      &Ctx.Idents.get("handler")};575  ADD_METHOD(UIContextualAction, contextualActionWithStyleUIContextualAction, 3,576             1)577  ADD_UNARY_METHOD(UIContextualAction, setTitle, 0)578 579  NEW_RECEIVER(NSAccessibilityCustomRotor)580  const IdentifierInfo *initWithLabelNSAccessibilityCustomRotor[] = {581      &Ctx.Idents.get("initWithLabel"), &Ctx.Idents.get("itemSearchDelegate")};582  ADD_METHOD(NSAccessibilityCustomRotor,583             initWithLabelNSAccessibilityCustomRotor, 2, 0)584  ADD_UNARY_METHOD(NSAccessibilityCustomRotor, setLabel, 0)585 586  NEW_RECEIVER(NSWindowTab)587  ADD_UNARY_METHOD(NSWindowTab, setTitle, 0)588  ADD_UNARY_METHOD(NSWindowTab, setToolTip, 0)589 590  NEW_RECEIVER(NSAccessibilityCustomAction)591  const IdentifierInfo *initWithNameNSAccessibilityCustomAction[] = {592      &Ctx.Idents.get("initWithName"), &Ctx.Idents.get("handler")};593  ADD_METHOD(NSAccessibilityCustomAction,594             initWithNameNSAccessibilityCustomAction, 2, 0)595  const IdentifierInfo *initWithNameTargetNSAccessibilityCustomAction[] = {596      &Ctx.Idents.get("initWithName"), &Ctx.Idents.get("target"),597      &Ctx.Idents.get("selector")};598  ADD_METHOD(NSAccessibilityCustomAction,599             initWithNameTargetNSAccessibilityCustomAction, 3, 0)600  ADD_UNARY_METHOD(NSAccessibilityCustomAction, setName, 0)601}602 603#define LSF_INSERT(function_name) LSF.insert(&Ctx.Idents.get(function_name));604#define LSM_INSERT_NULLARY(receiver, method_name)                              \605  LSM.insert({&Ctx.Idents.get(receiver), Ctx.Selectors.getNullarySelector(     \606                                             &Ctx.Idents.get(method_name))});607#define LSM_INSERT_UNARY(receiver, method_name)                                \608  LSM.insert({&Ctx.Idents.get(receiver),                                       \609              Ctx.Selectors.getUnarySelector(&Ctx.Idents.get(method_name))});610#define LSM_INSERT_SELECTOR(receiver, method_list, arguments)                  \611  LSM.insert({&Ctx.Idents.get(receiver),                                       \612              Ctx.Selectors.getSelector(arguments, method_list)});613 614/// Initializes a list of methods and C functions that return a localized string615void NonLocalizedStringChecker::initLocStringsMethods(ASTContext &Ctx) const {616  if (!LSM.empty())617    return;618 619  const IdentifierInfo *LocalizedStringMacro[] = {620      &Ctx.Idents.get("localizedStringForKey"), &Ctx.Idents.get("value"),621      &Ctx.Idents.get("table")};622  LSM_INSERT_SELECTOR("NSBundle", LocalizedStringMacro, 3)623  LSM_INSERT_UNARY("NSDateFormatter", "stringFromDate")624  const IdentifierInfo *LocalizedStringFromDate[] = {625      &Ctx.Idents.get("localizedStringFromDate"), &Ctx.Idents.get("dateStyle"),626      &Ctx.Idents.get("timeStyle")};627  LSM_INSERT_SELECTOR("NSDateFormatter", LocalizedStringFromDate, 3)628  LSM_INSERT_UNARY("NSNumberFormatter", "stringFromNumber")629  LSM_INSERT_NULLARY("UITextField", "text")630  LSM_INSERT_NULLARY("UITextView", "text")631  LSM_INSERT_NULLARY("UILabel", "text")632 633  LSF_INSERT("CFDateFormatterCreateStringWithDate");634  LSF_INSERT("CFDateFormatterCreateStringWithAbsoluteTime");635  LSF_INSERT("CFNumberFormatterCreateStringWithNumber");636}637 638/// Checks to see if the method / function declaration includes639/// __attribute__((annotate("returns_localized_nsstring")))640bool NonLocalizedStringChecker::isAnnotatedAsReturningLocalized(641    const Decl *D) const {642  if (!D)643    return false;644  return std::any_of(645      D->specific_attr_begin<AnnotateAttr>(),646      D->specific_attr_end<AnnotateAttr>(), [](const AnnotateAttr *Ann) {647        return Ann->getAnnotation() == "returns_localized_nsstring";648      });649}650 651/// Checks to see if the method / function declaration includes652/// __attribute__((annotate("takes_localized_nsstring")))653bool NonLocalizedStringChecker::isAnnotatedAsTakingLocalized(654    const Decl *D) const {655  if (!D)656    return false;657  return std::any_of(658      D->specific_attr_begin<AnnotateAttr>(),659      D->specific_attr_end<AnnotateAttr>(), [](const AnnotateAttr *Ann) {660        return Ann->getAnnotation() == "takes_localized_nsstring";661      });662}663 664/// Returns true if the given SVal is marked as Localized in the program state665bool NonLocalizedStringChecker::hasLocalizedState(SVal S,666                                                  CheckerContext &C) const {667  const MemRegion *mt = S.getAsRegion();668  if (mt) {669    const LocalizedState *LS = C.getState()->get<LocalizedMemMap>(mt);670    if (LS && LS->isLocalized())671      return true;672  }673  return false;674}675 676/// Returns true if the given SVal is marked as NonLocalized in the program677/// state678bool NonLocalizedStringChecker::hasNonLocalizedState(SVal S,679                                                     CheckerContext &C) const {680  const MemRegion *mt = S.getAsRegion();681  if (mt) {682    const LocalizedState *LS = C.getState()->get<LocalizedMemMap>(mt);683    if (LS && LS->isNonLocalized())684      return true;685  }686  return false;687}688 689/// Marks the given SVal as Localized in the program state690void NonLocalizedStringChecker::setLocalizedState(const SVal S,691                                                  CheckerContext &C) const {692  const MemRegion *mt = S.getAsRegion();693  if (mt) {694    ProgramStateRef State =695        C.getState()->set<LocalizedMemMap>(mt, LocalizedState::getLocalized());696    C.addTransition(State);697  }698}699 700/// Marks the given SVal as NonLocalized in the program state701void NonLocalizedStringChecker::setNonLocalizedState(const SVal S,702                                                     CheckerContext &C) const {703  const MemRegion *mt = S.getAsRegion();704  if (mt) {705    ProgramStateRef State = C.getState()->set<LocalizedMemMap>(706        mt, LocalizedState::getNonLocalized());707    C.addTransition(State);708  }709}710 711 712static bool isDebuggingName(std::string name) {713  return StringRef(name).contains_insensitive("debug");714}715 716/// Returns true when, heuristically, the analyzer may be analyzing debugging717/// code. We use this to suppress localization diagnostics in un-localized user718/// interfaces that are only used for debugging and are therefore not user719/// facing.720static bool isDebuggingContext(CheckerContext &C) {721  const Decl *D = C.getCurrentAnalysisDeclContext()->getDecl();722  if (!D)723    return false;724 725  if (auto *ND = dyn_cast<NamedDecl>(D)) {726    if (isDebuggingName(ND->getNameAsString()))727      return true;728  }729 730  const DeclContext *DC = D->getDeclContext();731 732  if (auto *CD = dyn_cast<ObjCContainerDecl>(DC)) {733    if (isDebuggingName(CD->getNameAsString()))734      return true;735  }736 737  return false;738}739 740 741/// Reports a localization error for the passed in method call and SVal742void NonLocalizedStringChecker::reportLocalizationError(743    SVal S, const CallEvent &M, CheckerContext &C, int argumentNumber) const {744 745  // Don't warn about localization errors in classes and methods that746  // may be debug code.747  if (isDebuggingContext(C))748    return;749 750  ExplodedNode *ErrNode = C.generateNonFatalErrorNode();751 752  if (!ErrNode)753    return;754 755  // Generate the bug report.756  auto R = std::make_unique<PathSensitiveBugReport>(757      BT, "User-facing text should use localized string macro", ErrNode);758  if (argumentNumber) {759    R->addRange(M.getArgExpr(argumentNumber - 1)->getSourceRange());760  } else {761    R->addRange(M.getSourceRange());762  }763  R->markInteresting(S);764 765  const MemRegion *StringRegion = S.getAsRegion();766  if (StringRegion)767    R->addVisitor(std::make_unique<NonLocalizedStringBRVisitor>(StringRegion));768 769  C.emitReport(std::move(R));770}771 772/// Returns the argument number requiring localized string if it exists773/// otherwise, returns -1774int NonLocalizedStringChecker::getLocalizedArgumentForSelector(775    const IdentifierInfo *Receiver, Selector S) const {776  auto method = UIMethods.find(Receiver);777 778  if (method == UIMethods.end())779    return -1;780 781  auto argumentIterator = method->getSecond().find(S);782 783  if (argumentIterator == method->getSecond().end())784    return -1;785 786  int argumentNumber = argumentIterator->getSecond();787  return argumentNumber;788}789 790/// Check if the string being passed in has NonLocalized state791void NonLocalizedStringChecker::checkPreObjCMessage(const ObjCMethodCall &msg,792                                                    CheckerContext &C) const {793  initUIMethods(C.getASTContext());794 795  const ObjCInterfaceDecl *OD = msg.getReceiverInterface();796  if (!OD)797    return;798  const IdentifierInfo *odInfo = OD->getIdentifier();799 800  Selector S = msg.getSelector();801 802  std::string SelectorString = S.getAsString();803  StringRef SelectorName = SelectorString;804  assert(!SelectorName.empty());805 806  if (odInfo->isStr("NSString")) {807    // Handle the case where the receiver is an NSString808    // These special NSString methods draw to the screen809 810    if (!(SelectorName.starts_with("drawAtPoint") ||811          SelectorName.starts_with("drawInRect") ||812          SelectorName.starts_with("drawWithRect")))813      return;814 815    SVal svTitle = msg.getReceiverSVal();816 817    bool isNonLocalized = hasNonLocalizedState(svTitle, C);818 819    if (isNonLocalized) {820      reportLocalizationError(svTitle, msg, C);821    }822  }823 824  int argumentNumber = getLocalizedArgumentForSelector(odInfo, S);825  // Go up each hierarchy of superclasses and their protocols826  while (argumentNumber < 0 && OD->getSuperClass() != nullptr) {827    for (const auto *P : OD->all_referenced_protocols()) {828      argumentNumber = getLocalizedArgumentForSelector(P->getIdentifier(), S);829      if (argumentNumber >= 0)830        break;831    }832    if (argumentNumber < 0) {833      OD = OD->getSuperClass();834      argumentNumber = getLocalizedArgumentForSelector(OD->getIdentifier(), S);835    }836  }837 838  if (argumentNumber < 0) { // There was no match in UIMethods839    if (const Decl *D = msg.getDecl()) {840      if (const ObjCMethodDecl *OMD = dyn_cast_or_null<ObjCMethodDecl>(D)) {841        for (auto [Idx, FormalParam] : llvm::enumerate(OMD->parameters())) {842          if (isAnnotatedAsTakingLocalized(FormalParam)) {843            argumentNumber = Idx;844            break;845          }846        }847      }848    }849  }850 851  if (argumentNumber < 0) // Still no match852    return;853 854  SVal svTitle = msg.getArgSVal(argumentNumber);855 856  if (const ObjCStringRegion *SR =857          dyn_cast_or_null<ObjCStringRegion>(svTitle.getAsRegion())) {858    StringRef stringValue =859        SR->getObjCStringLiteral()->getString()->getString();860    if ((stringValue.trim().size() == 0 && stringValue.size() > 0) ||861        stringValue.empty())862      return;863    if (!IsAggressive && llvm::sys::unicode::columnWidthUTF8(stringValue) < 2)864      return;865  }866 867  bool isNonLocalized = hasNonLocalizedState(svTitle, C);868 869  if (isNonLocalized) {870    reportLocalizationError(svTitle, msg, C, argumentNumber + 1);871  }872}873 874void NonLocalizedStringChecker::checkPreCall(const CallEvent &Call,875                                             CheckerContext &C) const {876  const auto *FD = dyn_cast_or_null<FunctionDecl>(Call.getDecl());877  if (!FD)878    return;879 880  auto formals = FD->parameters();881  for (unsigned i = 0, ei = std::min(static_cast<unsigned>(formals.size()),882                                     Call.getNumArgs()); i != ei; ++i) {883    if (isAnnotatedAsTakingLocalized(formals[i])) {884      auto actual = Call.getArgSVal(i);885      if (hasNonLocalizedState(actual, C)) {886        reportLocalizationError(actual, Call, C, i + 1);887      }888    }889  }890}891 892static inline bool isNSStringType(QualType T, ASTContext &Ctx) {893 894  const ObjCObjectPointerType *PT = T->getAs<ObjCObjectPointerType>();895  if (!PT)896    return false;897 898  ObjCInterfaceDecl *Cls = PT->getObjectType()->getInterface();899  if (!Cls)900    return false;901 902  const IdentifierInfo *ClsName = Cls->getIdentifier();903 904  // FIXME: Should we walk the chain of classes?905  return ClsName == &Ctx.Idents.get("NSString") ||906         ClsName == &Ctx.Idents.get("NSMutableString");907}908 909/// Marks a string being returned by any call as localized910/// if it is in LocStringFunctions (LSF) or the function is annotated.911/// Otherwise, we mark it as NonLocalized (Aggressive) or912/// NonLocalized only if it is not backed by a SymRegion (Non-Aggressive),913/// basically leaving only string literals as NonLocalized.914void NonLocalizedStringChecker::checkPostCall(const CallEvent &Call,915                                              CheckerContext &C) const {916  initLocStringsMethods(C.getASTContext());917 918  if (!Call.getOriginExpr())919    return;920 921  // Anything that takes in a localized NSString as an argument922  // and returns an NSString will be assumed to be returning a923  // localized NSString. (Counter: Incorrectly combining two LocalizedStrings)924  const QualType RT = Call.getResultType();925  if (isNSStringType(RT, C.getASTContext())) {926    for (unsigned i = 0; i < Call.getNumArgs(); ++i) {927      SVal argValue = Call.getArgSVal(i);928      if (hasLocalizedState(argValue, C)) {929        SVal sv = Call.getReturnValue();930        setLocalizedState(sv, C);931        return;932      }933    }934  }935 936  const Decl *D = Call.getDecl();937  if (!D)938    return;939 940  const IdentifierInfo *Identifier = Call.getCalleeIdentifier();941 942  SVal sv = Call.getReturnValue();943  if (isAnnotatedAsReturningLocalized(D) || LSF.contains(Identifier)) {944    setLocalizedState(sv, C);945  } else if (isNSStringType(RT, C.getASTContext()) &&946             !hasLocalizedState(sv, C)) {947    if (IsAggressive) {948      setNonLocalizedState(sv, C);949    } else {950      const SymbolicRegion *SymReg =951          dyn_cast_or_null<SymbolicRegion>(sv.getAsRegion());952      if (!SymReg)953        setNonLocalizedState(sv, C);954    }955  }956}957 958/// Marks a string being returned by an ObjC method as localized959/// if it is in LocStringMethods or the method is annotated960void NonLocalizedStringChecker::checkPostObjCMessage(const ObjCMethodCall &msg,961                                                     CheckerContext &C) const {962  initLocStringsMethods(C.getASTContext());963 964  if (!msg.isInstanceMessage())965    return;966 967  const ObjCInterfaceDecl *OD = msg.getReceiverInterface();968  if (!OD)969    return;970  const IdentifierInfo *odInfo = OD->getIdentifier();971 972  Selector S = msg.getSelector();973 974  std::pair<const IdentifierInfo *, Selector> MethodDescription = {odInfo, S};975 976  if (LSM.count(MethodDescription) ||977      isAnnotatedAsReturningLocalized(msg.getDecl())) {978    SVal sv = msg.getReturnValue();979    setLocalizedState(sv, C);980  }981}982 983/// Marks all empty string literals as localized984void NonLocalizedStringChecker::checkPostStmt(const ObjCStringLiteral *SL,985                                              CheckerContext &C) const {986  SVal sv = C.getSVal(SL);987  setNonLocalizedState(sv, C);988}989 990PathDiagnosticPieceRef991NonLocalizedStringBRVisitor::VisitNode(const ExplodedNode *Succ,992                                       BugReporterContext &BRC,993                                       PathSensitiveBugReport &BR) {994  if (Satisfied)995    return nullptr;996 997  std::optional<StmtPoint> Point = Succ->getLocation().getAs<StmtPoint>();998  if (!Point)999    return nullptr;1000 1001  auto *LiteralExpr = dyn_cast<ObjCStringLiteral>(Point->getStmt());1002  if (!LiteralExpr)1003    return nullptr;1004 1005  SVal LiteralSVal = Succ->getSVal(LiteralExpr);1006  if (LiteralSVal.getAsRegion() != NonLocalizedString)1007    return nullptr;1008 1009  Satisfied = true;1010 1011  PathDiagnosticLocation L =1012      PathDiagnosticLocation::create(*Point, BRC.getSourceManager());1013 1014  if (!L.isValid() || !L.asLocation().isValid())1015    return nullptr;1016 1017  auto Piece = std::make_shared<PathDiagnosticEventPiece>(1018      L, "Non-localized string literal here");1019  Piece->addRange(LiteralExpr->getSourceRange());1020 1021  return std::move(Piece);1022}1023 1024namespace {1025class EmptyLocalizationContextChecker1026    : public Checker<check::ASTDecl<ObjCImplementationDecl>> {1027 1028  // A helper class, which walks the AST1029  class MethodCrawler : public ConstStmtVisitor<MethodCrawler> {1030    const ObjCMethodDecl *MD;1031    BugReporter &BR;1032    AnalysisManager &Mgr;1033    const CheckerBase *Checker;1034    LocationOrAnalysisDeclContext DCtx;1035 1036  public:1037    MethodCrawler(const ObjCMethodDecl *InMD, BugReporter &InBR,1038                  const CheckerBase *Checker, AnalysisManager &InMgr,1039                  AnalysisDeclContext *InDCtx)1040        : MD(InMD), BR(InBR), Mgr(InMgr), Checker(Checker), DCtx(InDCtx) {}1041 1042    void VisitStmt(const Stmt *S) { VisitChildren(S); }1043 1044    void VisitObjCMessageExpr(const ObjCMessageExpr *ME);1045 1046    void reportEmptyContextError(const ObjCMessageExpr *M) const;1047 1048    void VisitChildren(const Stmt *S) {1049      for (const Stmt *Child : S->children()) {1050        if (Child)1051          this->Visit(Child);1052      }1053    }1054  };1055 1056public:1057  void checkASTDecl(const ObjCImplementationDecl *D, AnalysisManager &Mgr,1058                    BugReporter &BR) const;1059};1060} // end anonymous namespace1061 1062void EmptyLocalizationContextChecker::checkASTDecl(1063    const ObjCImplementationDecl *D, AnalysisManager &Mgr,1064    BugReporter &BR) const {1065 1066  for (const ObjCMethodDecl *M : D->methods()) {1067    AnalysisDeclContext *DCtx = Mgr.getAnalysisDeclContext(M);1068 1069    const Stmt *Body = M->getBody();1070    if (!Body) {1071      assert(M->isSynthesizedAccessorStub());1072      continue;1073    }1074 1075    MethodCrawler MC(M->getCanonicalDecl(), BR, this, Mgr, DCtx);1076    MC.VisitStmt(Body);1077  }1078}1079 1080/// This check attempts to match these macros, assuming they are defined as1081/// follows:1082///1083/// #define NSLocalizedString(key, comment) \1084/// [[NSBundle mainBundle] localizedStringForKey:(key) value:@"" table:nil]1085/// #define NSLocalizedStringFromTable(key, tbl, comment) \1086/// [[NSBundle mainBundle] localizedStringForKey:(key) value:@"" table:(tbl)]1087/// #define NSLocalizedStringFromTableInBundle(key, tbl, bundle, comment) \1088/// [bundle localizedStringForKey:(key) value:@"" table:(tbl)]1089/// #define NSLocalizedStringWithDefaultValue(key, tbl, bundle, val, comment)1090///1091/// We cannot use the path sensitive check because the macro argument we are1092/// checking for (comment) is not used and thus not present in the AST,1093/// so we use Lexer on the original macro call and retrieve the value of1094/// the comment. If it's empty or nil, we raise a warning.1095void EmptyLocalizationContextChecker::MethodCrawler::VisitObjCMessageExpr(1096    const ObjCMessageExpr *ME) {1097 1098  // FIXME: We may be able to use PPCallbacks to check for empty context1099  // comments as part of preprocessing and avoid this re-lexing hack.1100  const ObjCInterfaceDecl *OD = ME->getReceiverInterface();1101  if (!OD)1102    return;1103 1104  const IdentifierInfo *odInfo = OD->getIdentifier();1105 1106  if (!(odInfo->isStr("NSBundle") &&1107        ME->getSelector().getAsString() ==1108            "localizedStringForKey:value:table:")) {1109    return;1110  }1111 1112  SourceRange R = ME->getSourceRange();1113  if (!R.getBegin().isMacroID())1114    return;1115 1116  // getImmediateMacroCallerLoc gets the location of the immediate macro1117  // caller, one level up the stack toward the initial macro typed into the1118  // source, so SL should point to the NSLocalizedString macro.1119  SourceLocation SL =1120      Mgr.getSourceManager().getImmediateMacroCallerLoc(R.getBegin());1121  FileIDAndOffset SLInfo = Mgr.getSourceManager().getDecomposedLoc(SL);1122 1123  SrcMgr::SLocEntry SE = Mgr.getSourceManager().getSLocEntry(SLInfo.first);1124 1125  // If NSLocalizedString macro is wrapped in another macro, we need to1126  // unwrap the expansion until we get to the NSLocalizedStringMacro.1127  while (SE.isExpansion()) {1128    SL = SE.getExpansion().getSpellingLoc();1129    SLInfo = Mgr.getSourceManager().getDecomposedLoc(SL);1130    SE = Mgr.getSourceManager().getSLocEntry(SLInfo.first);1131  }1132 1133  std::optional<llvm::MemoryBufferRef> BF =1134      Mgr.getSourceManager().getBufferOrNone(SLInfo.first, SL);1135  if (!BF)1136    return;1137  LangOptions LangOpts;1138  Lexer TheLexer(SL, LangOpts, BF->getBufferStart(),1139                 BF->getBufferStart() + SLInfo.second, BF->getBufferEnd());1140 1141  Token I;1142  Token Result;    // This will hold the token just before the last ')'1143  int p_count = 0; // This is for parenthesis matching1144  while (!TheLexer.LexFromRawLexer(I)) {1145    if (I.getKind() == tok::l_paren)1146      ++p_count;1147    if (I.getKind() == tok::r_paren) {1148      if (p_count == 1)1149        break;1150      --p_count;1151    }1152    Result = I;1153  }1154 1155  if (isAnyIdentifier(Result.getKind())) {1156    if (Result.getRawIdentifier() == "nil") {1157      reportEmptyContextError(ME);1158      return;1159    }1160  }1161 1162  if (!isStringLiteral(Result.getKind()))1163    return;1164 1165  StringRef Comment =1166      StringRef(Result.getLiteralData(), Result.getLength()).trim('"');1167 1168  if ((Comment.trim().size() == 0 && Comment.size() > 0) || // Is Whitespace1169      Comment.empty()) {1170    reportEmptyContextError(ME);1171  }1172}1173 1174void EmptyLocalizationContextChecker::MethodCrawler::reportEmptyContextError(1175    const ObjCMessageExpr *ME) const {1176  // Generate the bug report.1177  BR.EmitBasicReport(MD, Checker, "Context Missing",1178                     "Localizability Issue (Apple)",1179                     "Localized string macro should include a non-empty "1180                     "comment for translators",1181                     PathDiagnosticLocation(ME, BR.getSourceManager(), DCtx));1182}1183 1184namespace {1185class PluralMisuseChecker : public Checker<check::ASTCodeBody> {1186 1187  // A helper class, which walks the AST1188  class MethodCrawler : public DynamicRecursiveASTVisitor {1189    BugReporter &BR;1190    const CheckerBase *Checker;1191    AnalysisDeclContext *AC;1192 1193    // This functions like a stack. We push on any IfStmt or1194    // ConditionalOperator that matches the condition1195    // and pop it off when we leave that statement1196    llvm::SmallVector<const clang::Stmt *, 8> MatchingStatements;1197    // This is true when we are the direct-child of a1198    // matching statement1199    bool InMatchingStatement = false;1200 1201  public:1202    explicit MethodCrawler(BugReporter &InBR, const CheckerBase *Checker,1203                           AnalysisDeclContext *InAC)1204        : BR(InBR), Checker(Checker), AC(InAC) {}1205 1206    bool VisitIfStmt(IfStmt *I) override;1207    bool EndVisitIfStmt(IfStmt *I);1208    bool TraverseIfStmt(IfStmt *x) override;1209    bool VisitConditionalOperator(ConditionalOperator *C) override;1210    bool TraverseConditionalOperator(ConditionalOperator *C) override;1211    bool VisitCallExpr(CallExpr *CE) override;1212    bool VisitObjCMessageExpr(ObjCMessageExpr *ME) override;1213 1214  private:1215    void reportPluralMisuseError(const Stmt *S) const;1216    bool isCheckingPlurality(const Expr *E) const;1217  };1218 1219public:1220  void checkASTCodeBody(const Decl *D, AnalysisManager &Mgr,1221                        BugReporter &BR) const {1222    MethodCrawler Visitor(BR, this, Mgr.getAnalysisDeclContext(D));1223    Visitor.TraverseDecl(const_cast<Decl *>(D));1224  }1225};1226} // end anonymous namespace1227 1228// Checks the condition of the IfStmt and returns true if one1229// of the following heuristics are met:1230// 1) The conidtion is a variable with "singular" or "plural" in the name1231// 2) The condition is a binary operator with 1 or 2 on the right-hand side1232bool PluralMisuseChecker::MethodCrawler::isCheckingPlurality(1233    const Expr *Condition) const {1234  const BinaryOperator *BO = nullptr;1235  // Accounts for when a VarDecl represents a BinaryOperator1236  if (const DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(Condition)) {1237    if (const VarDecl *VD = dyn_cast<VarDecl>(DRE->getDecl())) {1238      const Expr *InitExpr = VD->getInit();1239      if (InitExpr) {1240        if (const BinaryOperator *B =1241                dyn_cast<BinaryOperator>(InitExpr->IgnoreParenImpCasts())) {1242          BO = B;1243        }1244      }1245      if (VD->getName().contains_insensitive("plural") ||1246          VD->getName().contains_insensitive("singular")) {1247        return true;1248      }1249    }1250  } else if (const BinaryOperator *B = dyn_cast<BinaryOperator>(Condition)) {1251    BO = B;1252  }1253 1254  if (BO == nullptr)1255    return false;1256 1257  if (IntegerLiteral *IL = dyn_cast_or_null<IntegerLiteral>(1258          BO->getRHS()->IgnoreParenImpCasts())) {1259    llvm::APInt Value = IL->getValue();1260    if (Value == 1 || Value == 2) {1261      return true;1262    }1263  }1264  return false;1265}1266 1267// A CallExpr with "LOC" in its identifier that takes in a string literal1268// has been shown to almost always be a function that returns a localized1269// string. Raise a diagnostic when this is in a statement that matches1270// the condition.1271bool PluralMisuseChecker::MethodCrawler::VisitCallExpr(CallExpr *CE) {1272  if (InMatchingStatement) {1273    if (const FunctionDecl *FD = CE->getDirectCallee()) {1274      std::string NormalizedName =1275          StringRef(FD->getNameInfo().getAsString()).lower();1276      if (NormalizedName.find("loc") != std::string::npos) {1277        for (const Expr *Arg : CE->arguments()) {1278          if (isa<ObjCStringLiteral>(Arg))1279            reportPluralMisuseError(CE);1280        }1281      }1282    }1283  }1284  return true;1285}1286 1287// The other case is for NSLocalizedString which also returns1288// a localized string. It's a macro for the ObjCMessageExpr1289// [NSBundle localizedStringForKey:value:table:] Raise a1290// diagnostic when this is in a statement that matches1291// the condition.1292bool PluralMisuseChecker::MethodCrawler::VisitObjCMessageExpr(1293    ObjCMessageExpr *ME) {1294  const ObjCInterfaceDecl *OD = ME->getReceiverInterface();1295  if (!OD)1296    return true;1297 1298  const IdentifierInfo *odInfo = OD->getIdentifier();1299 1300  if (odInfo->isStr("NSBundle") &&1301      ME->getSelector().getAsString() == "localizedStringForKey:value:table:") {1302    if (InMatchingStatement) {1303      reportPluralMisuseError(ME);1304    }1305  }1306  return true;1307}1308 1309/// Override TraverseIfStmt so we know when we are done traversing an IfStmt1310bool PluralMisuseChecker::MethodCrawler::TraverseIfStmt(IfStmt *I) {1311  DynamicRecursiveASTVisitor::TraverseIfStmt(I);1312  return EndVisitIfStmt(I);1313}1314 1315// EndVisit callbacks are not provided by the RecursiveASTVisitor1316// so we override TraverseIfStmt and make a call to EndVisitIfStmt1317// after traversing the IfStmt1318bool PluralMisuseChecker::MethodCrawler::EndVisitIfStmt(IfStmt *I) {1319  MatchingStatements.pop_back();1320  if (!MatchingStatements.empty()) {1321    if (MatchingStatements.back() != nullptr) {1322      InMatchingStatement = true;1323      return true;1324    }1325  }1326  InMatchingStatement = false;1327  return true;1328}1329 1330bool PluralMisuseChecker::MethodCrawler::VisitIfStmt(IfStmt *I) {1331  const Expr *Condition = I->getCond();1332  if (!Condition)1333    return true;1334  Condition = Condition->IgnoreParenImpCasts();1335  if (isCheckingPlurality(Condition)) {1336    MatchingStatements.push_back(I);1337    InMatchingStatement = true;1338  } else {1339    MatchingStatements.push_back(nullptr);1340    InMatchingStatement = false;1341  }1342 1343  return true;1344}1345 1346// Preliminary support for conditional operators.1347bool PluralMisuseChecker::MethodCrawler::TraverseConditionalOperator(1348    ConditionalOperator *C) {1349  DynamicRecursiveASTVisitor::TraverseConditionalOperator(C);1350  MatchingStatements.pop_back();1351  if (!MatchingStatements.empty()) {1352    if (MatchingStatements.back() != nullptr)1353      InMatchingStatement = true;1354    else1355      InMatchingStatement = false;1356  } else {1357    InMatchingStatement = false;1358  }1359  return true;1360}1361 1362bool PluralMisuseChecker::MethodCrawler::VisitConditionalOperator(1363    ConditionalOperator *C) {1364  const Expr *Condition = C->getCond()->IgnoreParenImpCasts();1365  if (isCheckingPlurality(Condition)) {1366    MatchingStatements.push_back(C);1367    InMatchingStatement = true;1368  } else {1369    MatchingStatements.push_back(nullptr);1370    InMatchingStatement = false;1371  }1372  return true;1373}1374 1375void PluralMisuseChecker::MethodCrawler::reportPluralMisuseError(1376    const Stmt *S) const {1377  // Generate the bug report.1378  BR.EmitBasicReport(AC->getDecl(), Checker, "Plural Misuse",1379                     "Localizability Issue (Apple)",1380                     "Plural cases are not supported across all languages. "1381                     "Use a .stringsdict file instead",1382                     PathDiagnosticLocation(S, BR.getSourceManager(), AC));1383}1384 1385//===----------------------------------------------------------------------===//1386// Checker registration.1387//===----------------------------------------------------------------------===//1388 1389void ento::registerNonLocalizedStringChecker(CheckerManager &mgr) {1390  NonLocalizedStringChecker *checker =1391      mgr.registerChecker<NonLocalizedStringChecker>();1392  checker->IsAggressive =1393      mgr.getAnalyzerOptions().getCheckerBooleanOption(1394          checker, "AggressiveReport");1395}1396 1397bool ento::shouldRegisterNonLocalizedStringChecker(const CheckerManager &mgr) {1398  return true;1399}1400 1401void ento::registerEmptyLocalizationContextChecker(CheckerManager &mgr) {1402  mgr.registerChecker<EmptyLocalizationContextChecker>();1403}1404 1405bool ento::shouldRegisterEmptyLocalizationContextChecker(1406                                                    const CheckerManager &mgr) {1407  return true;1408}1409 1410void ento::registerPluralMisuseChecker(CheckerManager &mgr) {1411  mgr.registerChecker<PluralMisuseChecker>();1412}1413 1414bool ento::shouldRegisterPluralMisuseChecker(const CheckerManager &mgr) {1415  return true;1416}1417