clang  3.8.0
LocalizationChecker.cpp
Go to the documentation of this file.
1 //=- LocalizationChecker.cpp -------------------------------------*- C++ -*-==//
2 //
3 // The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9 //
10 // This file defines a set of checks for localizability including:
11 // 1) A checker that warns about uses of non-localized NSStrings passed to
12 // UI methods expecting localized strings
13 // 2) A syntactic checker that warns against the bad practice of
14 // not including a comment in NSLocalizedString macros.
15 //
16 //===----------------------------------------------------------------------===//
17 
18 #include "ClangSACheckers.h"
19 #include "clang/AST/Attr.h"
20 #include "clang/AST/Decl.h"
21 #include "clang/AST/DeclObjC.h"
29 #include "clang/Lex/Lexer.h"
31 #include "clang/AST/StmtVisitor.h"
32 #include "llvm/Support/Unicode.h"
33 #include "llvm/ADT/StringSet.h"
34 
35 using namespace clang;
36 using namespace ento;
37 
38 namespace {
39 struct LocalizedState {
40 private:
41  enum Kind { NonLocalized, Localized } K;
42  LocalizedState(Kind InK) : K(InK) {}
43 
44 public:
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 == operator
54  bool operator==(const LocalizedState &X) const { return K == X.K; }
55 
56  // LLVMs equivalent of a hash function
57  void Profile(llvm::FoldingSetNodeID &ID) const { ID.AddInteger(K); }
58 };
59 
60 class NonLocalizedStringChecker
61  : public Checker<check::PostCall, check::PreObjCMessage,
62  check::PostObjCMessage,
63  check::PostStmt<ObjCStringLiteral>> {
64 
65  mutable std::unique_ptr<BugType> BT;
66 
67  // Methods that require a localized string
68  mutable llvm::DenseMap<const IdentifierInfo *,
69  llvm::DenseMap<Selector, uint8_t>> UIMethods;
70  // Methods that return a localized string
71  mutable llvm::SmallSet<std::pair<const IdentifierInfo *, Selector>, 12> LSM;
72  // C Functions that return a localized string
73  mutable llvm::SmallSet<const IdentifierInfo *, 5> LSF;
74 
75  void initUIMethods(ASTContext &Ctx) const;
76  void initLocStringsMethods(ASTContext &Ctx) const;
77 
78  bool hasNonLocalizedState(SVal S, CheckerContext &C) const;
79  bool hasLocalizedState(SVal S, CheckerContext &C) const;
80  void setNonLocalizedState(SVal S, CheckerContext &C) const;
81  void setLocalizedState(SVal S, CheckerContext &C) const;
82 
83  bool isAnnotatedAsLocalized(const Decl *D) const;
84  void reportLocalizationError(SVal S, const ObjCMethodCall &M,
85  CheckerContext &C, int argumentNumber = 0) const;
86 
87  int getLocalizedArgumentForSelector(const IdentifierInfo *Receiver,
88  Selector S) const;
89 
90 public:
91  NonLocalizedStringChecker();
92 
93  // When this parameter is set to true, the checker assumes all
94  // methods that return NSStrings are unlocalized. Thus, more false
95  // positives will be reported.
96  DefaultBool IsAggressive;
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 checkPostCall(const CallEvent &Call, CheckerContext &C) const;
102 };
103 
104 } // end anonymous namespace
105 
106 REGISTER_MAP_WITH_PROGRAMSTATE(LocalizedMemMap, const MemRegion *,
107  LocalizedState)
108 
109 NonLocalizedStringChecker::NonLocalizedStringChecker() {
110  BT.reset(new BugType(this, "Unlocalizable string",
111  "Localizability Issue (Apple)"));
112 }
113 
114 #define NEW_RECEIVER(receiver) \
115  llvm::DenseMap<Selector, uint8_t> &receiver##M = \
116  UIMethods.insert({&Ctx.Idents.get(#receiver), \
117  llvm::DenseMap<Selector, uint8_t>()}) \
118  .first->second;
119 #define ADD_NULLARY_METHOD(receiver, method, argument) \
120  receiver##M.insert( \
121  {Ctx.Selectors.getNullarySelector(&Ctx.Idents.get(#method)), argument});
122 #define ADD_UNARY_METHOD(receiver, method, argument) \
123  receiver##M.insert( \
124  {Ctx.Selectors.getUnarySelector(&Ctx.Idents.get(#method)), argument});
125 #define ADD_METHOD(receiver, method_list, count, argument) \
126  receiver##M.insert({Ctx.Selectors.getSelector(count, method_list), argument});
127 
128 /// Initializes a list of methods that require a localized string
129 /// Format: {"ClassName", {{"selectorName:", LocStringArg#}, ...}, ...}
130 void NonLocalizedStringChecker::initUIMethods(ASTContext &Ctx) const {
131  if (!UIMethods.empty())
132  return;
133 
134  // UI Methods
135  NEW_RECEIVER(UISearchDisplayController)
136  ADD_UNARY_METHOD(UISearchDisplayController, setSearchResultsTitle, 0)
137 
138  NEW_RECEIVER(UITabBarItem)
139  IdentifierInfo *initWithTitleUITabBarItemTag[] = {
140  &Ctx.Idents.get("initWithTitle"), &Ctx.Idents.get("image"),
141  &Ctx.Idents.get("tag")};
142  ADD_METHOD(UITabBarItem, initWithTitleUITabBarItemTag, 3, 0)
143  IdentifierInfo *initWithTitleUITabBarItemImage[] = {
144  &Ctx.Idents.get("initWithTitle"), &Ctx.Idents.get("image"),
145  &Ctx.Idents.get("selectedImage")};
146  ADD_METHOD(UITabBarItem, initWithTitleUITabBarItemImage, 3, 0)
147 
148  NEW_RECEIVER(NSDockTile)
149  ADD_UNARY_METHOD(NSDockTile, setBadgeLabel, 0)
150 
151  NEW_RECEIVER(NSStatusItem)
152  ADD_UNARY_METHOD(NSStatusItem, setTitle, 0)
153  ADD_UNARY_METHOD(NSStatusItem, setToolTip, 0)
154 
155  NEW_RECEIVER(UITableViewRowAction)
156  IdentifierInfo *rowActionWithStyleUITableViewRowAction[] = {
157  &Ctx.Idents.get("rowActionWithStyle"), &Ctx.Idents.get("title"),
158  &Ctx.Idents.get("handler")};
159  ADD_METHOD(UITableViewRowAction, rowActionWithStyleUITableViewRowAction, 3, 1)
160  ADD_UNARY_METHOD(UITableViewRowAction, setTitle, 0)
161 
162  NEW_RECEIVER(NSBox)
163  ADD_UNARY_METHOD(NSBox, setTitle, 0)
164 
165  NEW_RECEIVER(NSButton)
166  ADD_UNARY_METHOD(NSButton, setTitle, 0)
167  ADD_UNARY_METHOD(NSButton, setAlternateTitle, 0)
168 
169  NEW_RECEIVER(NSSavePanel)
170  ADD_UNARY_METHOD(NSSavePanel, setPrompt, 0)
171  ADD_UNARY_METHOD(NSSavePanel, setTitle, 0)
172  ADD_UNARY_METHOD(NSSavePanel, setNameFieldLabel, 0)
173  ADD_UNARY_METHOD(NSSavePanel, setNameFieldStringValue, 0)
174  ADD_UNARY_METHOD(NSSavePanel, setMessage, 0)
175 
176  NEW_RECEIVER(UIPrintInfo)
177  ADD_UNARY_METHOD(UIPrintInfo, setJobName, 0)
178 
179  NEW_RECEIVER(NSTabViewItem)
180  ADD_UNARY_METHOD(NSTabViewItem, setLabel, 0)
181  ADD_UNARY_METHOD(NSTabViewItem, setToolTip, 0)
182 
183  NEW_RECEIVER(NSBrowser)
184  IdentifierInfo *setTitleNSBrowser[] = {&Ctx.Idents.get("setTitle"),
185  &Ctx.Idents.get("ofColumn")};
186  ADD_METHOD(NSBrowser, setTitleNSBrowser, 2, 0)
187 
188  NEW_RECEIVER(UIAccessibilityElement)
189  ADD_UNARY_METHOD(UIAccessibilityElement, setAccessibilityLabel, 0)
190  ADD_UNARY_METHOD(UIAccessibilityElement, setAccessibilityHint, 0)
191  ADD_UNARY_METHOD(UIAccessibilityElement, setAccessibilityValue, 0)
192 
193  NEW_RECEIVER(UIAlertAction)
194  IdentifierInfo *actionWithTitleUIAlertAction[] = {
195  &Ctx.Idents.get("actionWithTitle"), &Ctx.Idents.get("style"),
196  &Ctx.Idents.get("handler")};
197  ADD_METHOD(UIAlertAction, actionWithTitleUIAlertAction, 3, 0)
198 
199  NEW_RECEIVER(NSPopUpButton)
200  ADD_UNARY_METHOD(NSPopUpButton, addItemWithTitle, 0)
201  IdentifierInfo *insertItemWithTitleNSPopUpButton[] = {
202  &Ctx.Idents.get("insertItemWithTitle"), &Ctx.Idents.get("atIndex")};
203  ADD_METHOD(NSPopUpButton, insertItemWithTitleNSPopUpButton, 2, 0)
204  ADD_UNARY_METHOD(NSPopUpButton, removeItemWithTitle, 0)
205  ADD_UNARY_METHOD(NSPopUpButton, selectItemWithTitle, 0)
206  ADD_UNARY_METHOD(NSPopUpButton, setTitle, 0)
207 
208  NEW_RECEIVER(NSTableViewRowAction)
209  IdentifierInfo *rowActionWithStyleNSTableViewRowAction[] = {
210  &Ctx.Idents.get("rowActionWithStyle"), &Ctx.Idents.get("title"),
211  &Ctx.Idents.get("handler")};
212  ADD_METHOD(NSTableViewRowAction, rowActionWithStyleNSTableViewRowAction, 3, 1)
213  ADD_UNARY_METHOD(NSTableViewRowAction, setTitle, 0)
214 
215  NEW_RECEIVER(NSImage)
216  ADD_UNARY_METHOD(NSImage, setAccessibilityDescription, 0)
217 
218  NEW_RECEIVER(NSUserActivity)
219  ADD_UNARY_METHOD(NSUserActivity, setTitle, 0)
220 
221  NEW_RECEIVER(NSPathControlItem)
222  ADD_UNARY_METHOD(NSPathControlItem, setTitle, 0)
223 
224  NEW_RECEIVER(NSCell)
225  ADD_UNARY_METHOD(NSCell, initTextCell, 0)
226  ADD_UNARY_METHOD(NSCell, setTitle, 0)
227  ADD_UNARY_METHOD(NSCell, setStringValue, 0)
228 
229  NEW_RECEIVER(NSPathControl)
230  ADD_UNARY_METHOD(NSPathControl, setPlaceholderString, 0)
231 
232  NEW_RECEIVER(UIAccessibility)
233  ADD_UNARY_METHOD(UIAccessibility, setAccessibilityLabel, 0)
234  ADD_UNARY_METHOD(UIAccessibility, setAccessibilityHint, 0)
235  ADD_UNARY_METHOD(UIAccessibility, setAccessibilityValue, 0)
236 
237  NEW_RECEIVER(NSTableColumn)
238  ADD_UNARY_METHOD(NSTableColumn, setTitle, 0)
239  ADD_UNARY_METHOD(NSTableColumn, setHeaderToolTip, 0)
240 
241  NEW_RECEIVER(NSSegmentedControl)
242  IdentifierInfo *setLabelNSSegmentedControl[] = {
243  &Ctx.Idents.get("setLabel"), &Ctx.Idents.get("forSegment")};
244  ADD_METHOD(NSSegmentedControl, setLabelNSSegmentedControl, 2, 0)
245 
246  NEW_RECEIVER(NSButtonCell)
247  ADD_UNARY_METHOD(NSButtonCell, setTitle, 0)
248  ADD_UNARY_METHOD(NSButtonCell, setAlternateTitle, 0)
249 
250  NEW_RECEIVER(NSSliderCell)
251  ADD_UNARY_METHOD(NSSliderCell, setTitle, 0)
252 
253  NEW_RECEIVER(NSControl)
254  ADD_UNARY_METHOD(NSControl, setStringValue, 0)
255 
256  NEW_RECEIVER(NSAccessibility)
257  ADD_UNARY_METHOD(NSAccessibility, setAccessibilityValueDescription, 0)
258  ADD_UNARY_METHOD(NSAccessibility, setAccessibilityLabel, 0)
259  ADD_UNARY_METHOD(NSAccessibility, setAccessibilityTitle, 0)
260  ADD_UNARY_METHOD(NSAccessibility, setAccessibilityPlaceholderValue, 0)
261  ADD_UNARY_METHOD(NSAccessibility, setAccessibilityHelp, 0)
262 
263  NEW_RECEIVER(NSMatrix)
264  IdentifierInfo *setToolTipNSMatrix[] = {&Ctx.Idents.get("setToolTip"),
265  &Ctx.Idents.get("forCell")};
266  ADD_METHOD(NSMatrix, setToolTipNSMatrix, 2, 0)
267 
268  NEW_RECEIVER(NSPrintPanel)
269  ADD_UNARY_METHOD(NSPrintPanel, setDefaultButtonTitle, 0)
270 
271  NEW_RECEIVER(UILocalNotification)
272  ADD_UNARY_METHOD(UILocalNotification, setAlertBody, 0)
273  ADD_UNARY_METHOD(UILocalNotification, setAlertAction, 0)
274  ADD_UNARY_METHOD(UILocalNotification, setAlertTitle, 0)
275 
276  NEW_RECEIVER(NSSlider)
277  ADD_UNARY_METHOD(NSSlider, setTitle, 0)
278 
279  NEW_RECEIVER(UIMenuItem)
280  IdentifierInfo *initWithTitleUIMenuItem[] = {&Ctx.Idents.get("initWithTitle"),
281  &Ctx.Idents.get("action")};
282  ADD_METHOD(UIMenuItem, initWithTitleUIMenuItem, 2, 0)
283  ADD_UNARY_METHOD(UIMenuItem, setTitle, 0)
284 
285  NEW_RECEIVER(UIAlertController)
286  IdentifierInfo *alertControllerWithTitleUIAlertController[] = {
287  &Ctx.Idents.get("alertControllerWithTitle"), &Ctx.Idents.get("message"),
288  &Ctx.Idents.get("preferredStyle")};
289  ADD_METHOD(UIAlertController, alertControllerWithTitleUIAlertController, 3, 1)
290  ADD_UNARY_METHOD(UIAlertController, setTitle, 0)
291  ADD_UNARY_METHOD(UIAlertController, setMessage, 0)
292 
293  NEW_RECEIVER(UIApplicationShortcutItem)
294  IdentifierInfo *initWithTypeUIApplicationShortcutItemIcon[] = {
295  &Ctx.Idents.get("initWithType"), &Ctx.Idents.get("localizedTitle"),
296  &Ctx.Idents.get("localizedSubtitle"), &Ctx.Idents.get("icon"),
297  &Ctx.Idents.get("userInfo")};
298  ADD_METHOD(UIApplicationShortcutItem,
299  initWithTypeUIApplicationShortcutItemIcon, 5, 1)
300  IdentifierInfo *initWithTypeUIApplicationShortcutItem[] = {
301  &Ctx.Idents.get("initWithType"), &Ctx.Idents.get("localizedTitle")};
302  ADD_METHOD(UIApplicationShortcutItem, initWithTypeUIApplicationShortcutItem,
303  2, 1)
304 
305  NEW_RECEIVER(UIActionSheet)
306  IdentifierInfo *initWithTitleUIActionSheet[] = {
307  &Ctx.Idents.get("initWithTitle"), &Ctx.Idents.get("delegate"),
308  &Ctx.Idents.get("cancelButtonTitle"),
309  &Ctx.Idents.get("destructiveButtonTitle"),
310  &Ctx.Idents.get("otherButtonTitles")};
311  ADD_METHOD(UIActionSheet, initWithTitleUIActionSheet, 5, 0)
312  ADD_UNARY_METHOD(UIActionSheet, addButtonWithTitle, 0)
313  ADD_UNARY_METHOD(UIActionSheet, setTitle, 0)
314 
315  NEW_RECEIVER(NSURLSessionTask)
316  ADD_UNARY_METHOD(NSURLSessionTask, setTaskDescription, 0)
317 
318  NEW_RECEIVER(UIAccessibilityCustomAction)
319  IdentifierInfo *initWithNameUIAccessibilityCustomAction[] = {
320  &Ctx.Idents.get("initWithName"), &Ctx.Idents.get("target"),
321  &Ctx.Idents.get("selector")};
322  ADD_METHOD(UIAccessibilityCustomAction,
323  initWithNameUIAccessibilityCustomAction, 3, 0)
324  ADD_UNARY_METHOD(UIAccessibilityCustomAction, setName, 0)
325 
326  NEW_RECEIVER(UISearchBar)
327  ADD_UNARY_METHOD(UISearchBar, setText, 0)
328  ADD_UNARY_METHOD(UISearchBar, setPrompt, 0)
329  ADD_UNARY_METHOD(UISearchBar, setPlaceholder, 0)
330 
331  NEW_RECEIVER(UIBarItem)
332  ADD_UNARY_METHOD(UIBarItem, setTitle, 0)
333 
334  NEW_RECEIVER(UITextView)
335  ADD_UNARY_METHOD(UITextView, setText, 0)
336 
337  NEW_RECEIVER(NSView)
338  ADD_UNARY_METHOD(NSView, setToolTip, 0)
339 
340  NEW_RECEIVER(NSTextField)
341  ADD_UNARY_METHOD(NSTextField, setPlaceholderString, 0)
342 
343  NEW_RECEIVER(NSAttributedString)
344  ADD_UNARY_METHOD(NSAttributedString, initWithString, 0)
345  IdentifierInfo *initWithStringNSAttributedString[] = {
346  &Ctx.Idents.get("initWithString"), &Ctx.Idents.get("attributes")};
347  ADD_METHOD(NSAttributedString, initWithStringNSAttributedString, 2, 0)
348 
349  NEW_RECEIVER(NSText)
350  ADD_UNARY_METHOD(NSText, setString, 0)
351 
352  NEW_RECEIVER(UIKeyCommand)
353  IdentifierInfo *keyCommandWithInputUIKeyCommand[] = {
354  &Ctx.Idents.get("keyCommandWithInput"), &Ctx.Idents.get("modifierFlags"),
355  &Ctx.Idents.get("action"), &Ctx.Idents.get("discoverabilityTitle")};
356  ADD_METHOD(UIKeyCommand, keyCommandWithInputUIKeyCommand, 4, 3)
357  ADD_UNARY_METHOD(UIKeyCommand, setDiscoverabilityTitle, 0)
358 
359  NEW_RECEIVER(UILabel)
360  ADD_UNARY_METHOD(UILabel, setText, 0)
361 
362  NEW_RECEIVER(NSAlert)
363  IdentifierInfo *alertWithMessageTextNSAlert[] = {
364  &Ctx.Idents.get("alertWithMessageText"), &Ctx.Idents.get("defaultButton"),
365  &Ctx.Idents.get("alternateButton"), &Ctx.Idents.get("otherButton"),
366  &Ctx.Idents.get("informativeTextWithFormat")};
367  ADD_METHOD(NSAlert, alertWithMessageTextNSAlert, 5, 0)
368  ADD_UNARY_METHOD(NSAlert, addButtonWithTitle, 0)
369  ADD_UNARY_METHOD(NSAlert, setMessageText, 0)
370  ADD_UNARY_METHOD(NSAlert, setInformativeText, 0)
371  ADD_UNARY_METHOD(NSAlert, setHelpAnchor, 0)
372 
373  NEW_RECEIVER(UIMutableApplicationShortcutItem)
374  ADD_UNARY_METHOD(UIMutableApplicationShortcutItem, setLocalizedTitle, 0)
375  ADD_UNARY_METHOD(UIMutableApplicationShortcutItem, setLocalizedSubtitle, 0)
376 
377  NEW_RECEIVER(UIButton)
378  IdentifierInfo *setTitleUIButton[] = {&Ctx.Idents.get("setTitle"),
379  &Ctx.Idents.get("forState")};
380  ADD_METHOD(UIButton, setTitleUIButton, 2, 0)
381 
382  NEW_RECEIVER(NSWindow)
383  ADD_UNARY_METHOD(NSWindow, setTitle, 0)
384  IdentifierInfo *minFrameWidthWithTitleNSWindow[] = {
385  &Ctx.Idents.get("minFrameWidthWithTitle"), &Ctx.Idents.get("styleMask")};
386  ADD_METHOD(NSWindow, minFrameWidthWithTitleNSWindow, 2, 0)
387  ADD_UNARY_METHOD(NSWindow, setMiniwindowTitle, 0)
388 
389  NEW_RECEIVER(NSPathCell)
390  ADD_UNARY_METHOD(NSPathCell, setPlaceholderString, 0)
391 
392  NEW_RECEIVER(UIDocumentMenuViewController)
393  IdentifierInfo *addOptionWithTitleUIDocumentMenuViewController[] = {
394  &Ctx.Idents.get("addOptionWithTitle"), &Ctx.Idents.get("image"),
395  &Ctx.Idents.get("order"), &Ctx.Idents.get("handler")};
396  ADD_METHOD(UIDocumentMenuViewController,
397  addOptionWithTitleUIDocumentMenuViewController, 4, 0)
398 
399  NEW_RECEIVER(UINavigationItem)
400  ADD_UNARY_METHOD(UINavigationItem, initWithTitle, 0)
401  ADD_UNARY_METHOD(UINavigationItem, setTitle, 0)
402  ADD_UNARY_METHOD(UINavigationItem, setPrompt, 0)
403 
404  NEW_RECEIVER(UIAlertView)
405  IdentifierInfo *initWithTitleUIAlertView[] = {
406  &Ctx.Idents.get("initWithTitle"), &Ctx.Idents.get("message"),
407  &Ctx.Idents.get("delegate"), &Ctx.Idents.get("cancelButtonTitle"),
408  &Ctx.Idents.get("otherButtonTitles")};
409  ADD_METHOD(UIAlertView, initWithTitleUIAlertView, 5, 0)
410  ADD_UNARY_METHOD(UIAlertView, addButtonWithTitle, 0)
411  ADD_UNARY_METHOD(UIAlertView, setTitle, 0)
412  ADD_UNARY_METHOD(UIAlertView, setMessage, 0)
413 
414  NEW_RECEIVER(NSFormCell)
415  ADD_UNARY_METHOD(NSFormCell, initTextCell, 0)
416  ADD_UNARY_METHOD(NSFormCell, setTitle, 0)
417  ADD_UNARY_METHOD(NSFormCell, setPlaceholderString, 0)
418 
419  NEW_RECEIVER(NSUserNotification)
420  ADD_UNARY_METHOD(NSUserNotification, setTitle, 0)
421  ADD_UNARY_METHOD(NSUserNotification, setSubtitle, 0)
422  ADD_UNARY_METHOD(NSUserNotification, setInformativeText, 0)
423  ADD_UNARY_METHOD(NSUserNotification, setActionButtonTitle, 0)
424  ADD_UNARY_METHOD(NSUserNotification, setOtherButtonTitle, 0)
425  ADD_UNARY_METHOD(NSUserNotification, setResponsePlaceholder, 0)
426 
427  NEW_RECEIVER(NSToolbarItem)
428  ADD_UNARY_METHOD(NSToolbarItem, setLabel, 0)
429  ADD_UNARY_METHOD(NSToolbarItem, setPaletteLabel, 0)
430  ADD_UNARY_METHOD(NSToolbarItem, setToolTip, 0)
431 
432  NEW_RECEIVER(NSProgress)
433  ADD_UNARY_METHOD(NSProgress, setLocalizedDescription, 0)
434  ADD_UNARY_METHOD(NSProgress, setLocalizedAdditionalDescription, 0)
435 
436  NEW_RECEIVER(NSSegmentedCell)
437  IdentifierInfo *setLabelNSSegmentedCell[] = {&Ctx.Idents.get("setLabel"),
438  &Ctx.Idents.get("forSegment")};
439  ADD_METHOD(NSSegmentedCell, setLabelNSSegmentedCell, 2, 0)
440  IdentifierInfo *setToolTipNSSegmentedCell[] = {&Ctx.Idents.get("setToolTip"),
441  &Ctx.Idents.get("forSegment")};
442  ADD_METHOD(NSSegmentedCell, setToolTipNSSegmentedCell, 2, 0)
443 
444  NEW_RECEIVER(NSUndoManager)
445  ADD_UNARY_METHOD(NSUndoManager, setActionName, 0)
446  ADD_UNARY_METHOD(NSUndoManager, undoMenuTitleForUndoActionName, 0)
447  ADD_UNARY_METHOD(NSUndoManager, redoMenuTitleForUndoActionName, 0)
448 
449  NEW_RECEIVER(NSMenuItem)
450  IdentifierInfo *initWithTitleNSMenuItem[] = {
451  &Ctx.Idents.get("initWithTitle"), &Ctx.Idents.get("action"),
452  &Ctx.Idents.get("keyEquivalent")};
453  ADD_METHOD(NSMenuItem, initWithTitleNSMenuItem, 3, 0)
454  ADD_UNARY_METHOD(NSMenuItem, setTitle, 0)
455  ADD_UNARY_METHOD(NSMenuItem, setToolTip, 0)
456 
457  NEW_RECEIVER(NSPopUpButtonCell)
458  IdentifierInfo *initTextCellNSPopUpButtonCell[] = {
459  &Ctx.Idents.get("initTextCell"), &Ctx.Idents.get("pullsDown")};
460  ADD_METHOD(NSPopUpButtonCell, initTextCellNSPopUpButtonCell, 2, 0)
461  ADD_UNARY_METHOD(NSPopUpButtonCell, addItemWithTitle, 0)
462  IdentifierInfo *insertItemWithTitleNSPopUpButtonCell[] = {
463  &Ctx.Idents.get("insertItemWithTitle"), &Ctx.Idents.get("atIndex")};
464  ADD_METHOD(NSPopUpButtonCell, insertItemWithTitleNSPopUpButtonCell, 2, 0)
465  ADD_UNARY_METHOD(NSPopUpButtonCell, removeItemWithTitle, 0)
466  ADD_UNARY_METHOD(NSPopUpButtonCell, selectItemWithTitle, 0)
467  ADD_UNARY_METHOD(NSPopUpButtonCell, setTitle, 0)
468 
469  NEW_RECEIVER(NSViewController)
470  ADD_UNARY_METHOD(NSViewController, setTitle, 0)
471 
472  NEW_RECEIVER(NSMenu)
473  ADD_UNARY_METHOD(NSMenu, initWithTitle, 0)
474  IdentifierInfo *insertItemWithTitleNSMenu[] = {
475  &Ctx.Idents.get("insertItemWithTitle"), &Ctx.Idents.get("action"),
476  &Ctx.Idents.get("keyEquivalent"), &Ctx.Idents.get("atIndex")};
477  ADD_METHOD(NSMenu, insertItemWithTitleNSMenu, 4, 0)
478  IdentifierInfo *addItemWithTitleNSMenu[] = {
479  &Ctx.Idents.get("addItemWithTitle"), &Ctx.Idents.get("action"),
480  &Ctx.Idents.get("keyEquivalent")};
481  ADD_METHOD(NSMenu, addItemWithTitleNSMenu, 3, 0)
482  ADD_UNARY_METHOD(NSMenu, setTitle, 0)
483 
484  NEW_RECEIVER(UIMutableUserNotificationAction)
485  ADD_UNARY_METHOD(UIMutableUserNotificationAction, setTitle, 0)
486 
487  NEW_RECEIVER(NSForm)
488  ADD_UNARY_METHOD(NSForm, addEntry, 0)
489  IdentifierInfo *insertEntryNSForm[] = {&Ctx.Idents.get("insertEntry"),
490  &Ctx.Idents.get("atIndex")};
491  ADD_METHOD(NSForm, insertEntryNSForm, 2, 0)
492 
493  NEW_RECEIVER(NSTextFieldCell)
494  ADD_UNARY_METHOD(NSTextFieldCell, setPlaceholderString, 0)
495 
496  NEW_RECEIVER(NSUserNotificationAction)
497  IdentifierInfo *actionWithIdentifierNSUserNotificationAction[] = {
498  &Ctx.Idents.get("actionWithIdentifier"), &Ctx.Idents.get("title")};
499  ADD_METHOD(NSUserNotificationAction,
500  actionWithIdentifierNSUserNotificationAction, 2, 1)
501 
502  NEW_RECEIVER(NSURLSession)
503  ADD_UNARY_METHOD(NSURLSession, setSessionDescription, 0)
504 
505  NEW_RECEIVER(UITextField)
506  ADD_UNARY_METHOD(UITextField, setText, 0)
507  ADD_UNARY_METHOD(UITextField, setPlaceholder, 0)
508 
509  NEW_RECEIVER(UIBarButtonItem)
510  IdentifierInfo *initWithTitleUIBarButtonItem[] = {
511  &Ctx.Idents.get("initWithTitle"), &Ctx.Idents.get("style"),
512  &Ctx.Idents.get("target"), &Ctx.Idents.get("action")};
513  ADD_METHOD(UIBarButtonItem, initWithTitleUIBarButtonItem, 4, 0)
514 
515  NEW_RECEIVER(UIViewController)
516  ADD_UNARY_METHOD(UIViewController, setTitle, 0)
517 
518  NEW_RECEIVER(UISegmentedControl)
519  IdentifierInfo *insertSegmentWithTitleUISegmentedControl[] = {
520  &Ctx.Idents.get("insertSegmentWithTitle"), &Ctx.Idents.get("atIndex"),
521  &Ctx.Idents.get("animated")};
522  ADD_METHOD(UISegmentedControl, insertSegmentWithTitleUISegmentedControl, 3, 0)
523  IdentifierInfo *setTitleUISegmentedControl[] = {
524  &Ctx.Idents.get("setTitle"), &Ctx.Idents.get("forSegmentAtIndex")};
525  ADD_METHOD(UISegmentedControl, setTitleUISegmentedControl, 2, 0)
526 }
527 
528 #define LSF_INSERT(function_name) LSF.insert(&Ctx.Idents.get(function_name));
529 #define LSM_INSERT_NULLARY(receiver, method_name) \
530  LSM.insert({&Ctx.Idents.get(receiver), Ctx.Selectors.getNullarySelector( \
531  &Ctx.Idents.get(method_name))});
532 #define LSM_INSERT_UNARY(receiver, method_name) \
533  LSM.insert({&Ctx.Idents.get(receiver), \
534  Ctx.Selectors.getUnarySelector(&Ctx.Idents.get(method_name))});
535 #define LSM_INSERT_SELECTOR(receiver, method_list, arguments) \
536  LSM.insert({&Ctx.Idents.get(receiver), \
537  Ctx.Selectors.getSelector(arguments, method_list)});
538 
539 /// Initializes a list of methods and C functions that return a localized string
540 void NonLocalizedStringChecker::initLocStringsMethods(ASTContext &Ctx) const {
541  if (!LSM.empty())
542  return;
543 
544  IdentifierInfo *LocalizedStringMacro[] = {
545  &Ctx.Idents.get("localizedStringForKey"), &Ctx.Idents.get("value"),
546  &Ctx.Idents.get("table")};
547  LSM_INSERT_SELECTOR("NSBundle", LocalizedStringMacro, 3)
548  LSM_INSERT_UNARY("NSDateFormatter", "stringFromDate")
549  IdentifierInfo *LocalizedStringFromDate[] = {
550  &Ctx.Idents.get("localizedStringFromDate"), &Ctx.Idents.get("dateStyle"),
551  &Ctx.Idents.get("timeStyle")};
552  LSM_INSERT_SELECTOR("NSDateFormatter", LocalizedStringFromDate, 3)
553  LSM_INSERT_UNARY("NSNumberFormatter", "stringFromNumber")
554  LSM_INSERT_NULLARY("UITextField", "text")
555  LSM_INSERT_NULLARY("UITextView", "text")
556  LSM_INSERT_NULLARY("UILabel", "text")
557 
558  LSF_INSERT("CFDateFormatterCreateStringWithDate");
559  LSF_INSERT("CFDateFormatterCreateStringWithAbsoluteTime");
560  LSF_INSERT("CFNumberFormatterCreateStringWithNumber");
561 }
562 
563 /// Checks to see if the method / function declaration includes
564 /// __attribute__((annotate("returns_localized_nsstring")))
565 bool NonLocalizedStringChecker::isAnnotatedAsLocalized(const Decl *D) const {
566  if (!D)
567  return false;
568  return std::any_of(
569  D->specific_attr_begin<AnnotateAttr>(),
570  D->specific_attr_end<AnnotateAttr>(), [](const AnnotateAttr *Ann) {
571  return Ann->getAnnotation() == "returns_localized_nsstring";
572  });
573 }
574 
575 /// Returns true if the given SVal is marked as Localized in the program state
576 bool NonLocalizedStringChecker::hasLocalizedState(SVal S,
577  CheckerContext &C) const {
578  const MemRegion *mt = S.getAsRegion();
579  if (mt) {
580  const LocalizedState *LS = C.getState()->get<LocalizedMemMap>(mt);
581  if (LS && LS->isLocalized())
582  return true;
583  }
584  return false;
585 }
586 
587 /// Returns true if the given SVal is marked as NonLocalized in the program
588 /// state
589 bool NonLocalizedStringChecker::hasNonLocalizedState(SVal S,
590  CheckerContext &C) const {
591  const MemRegion *mt = S.getAsRegion();
592  if (mt) {
593  const LocalizedState *LS = C.getState()->get<LocalizedMemMap>(mt);
594  if (LS && LS->isNonLocalized())
595  return true;
596  }
597  return false;
598 }
599 
600 /// Marks the given SVal as Localized in the program state
601 void NonLocalizedStringChecker::setLocalizedState(const SVal S,
602  CheckerContext &C) const {
603  const MemRegion *mt = S.getAsRegion();
604  if (mt) {
606  C.getState()->set<LocalizedMemMap>(mt, LocalizedState::getLocalized());
607  C.addTransition(State);
608  }
609 }
610 
611 /// Marks the given SVal as NonLocalized in the program state
612 void NonLocalizedStringChecker::setNonLocalizedState(const SVal S,
613  CheckerContext &C) const {
614  const MemRegion *mt = S.getAsRegion();
615  if (mt) {
616  ProgramStateRef State = C.getState()->set<LocalizedMemMap>(
617  mt, LocalizedState::getNonLocalized());
618  C.addTransition(State);
619  }
620 }
621 
622 /// Reports a localization error for the passed in method call and SVal
623 void NonLocalizedStringChecker::reportLocalizationError(
624  SVal S, const ObjCMethodCall &M, CheckerContext &C,
625  int argumentNumber) const {
626 
627  ExplodedNode *ErrNode = C.getPredecessor();
628  static CheckerProgramPointTag Tag("NonLocalizedStringChecker",
629  "UnlocalizedString");
630  ErrNode = C.addTransition(C.getState(), C.getPredecessor(), &Tag);
631 
632  if (!ErrNode)
633  return;
634 
635  // Generate the bug report.
636  std::unique_ptr<BugReport> R(new BugReport(
637  *BT, "User-facing text should use localized string macro", ErrNode));
638  if (argumentNumber) {
639  R->addRange(M.getArgExpr(argumentNumber - 1)->getSourceRange());
640  } else {
641  R->addRange(M.getSourceRange());
642  }
643  R->markInteresting(S);
644  C.emitReport(std::move(R));
645 }
646 
647 /// Returns the argument number requiring localized string if it exists
648 /// otherwise, returns -1
649 int NonLocalizedStringChecker::getLocalizedArgumentForSelector(
650  const IdentifierInfo *Receiver, Selector S) const {
651  auto method = UIMethods.find(Receiver);
652 
653  if (method == UIMethods.end())
654  return -1;
655 
656  auto argumentIterator = method->getSecond().find(S);
657 
658  if (argumentIterator == method->getSecond().end())
659  return -1;
660 
661  int argumentNumber = argumentIterator->getSecond();
662  return argumentNumber;
663 }
664 
665 /// Check if the string being passed in has NonLocalized state
666 void NonLocalizedStringChecker::checkPreObjCMessage(const ObjCMethodCall &msg,
667  CheckerContext &C) const {
668  initUIMethods(C.getASTContext());
669 
670  const ObjCInterfaceDecl *OD = msg.getReceiverInterface();
671  if (!OD)
672  return;
673  const IdentifierInfo *odInfo = OD->getIdentifier();
674 
675  Selector S = msg.getSelector();
676 
677  std::string SelectorString = S.getAsString();
678  StringRef SelectorName = SelectorString;
679  assert(!SelectorName.empty());
680 
681  if (odInfo->isStr("NSString")) {
682  // Handle the case where the receiver is an NSString
683  // These special NSString methods draw to the screen
684 
685  if (!(SelectorName.startswith("drawAtPoint") ||
686  SelectorName.startswith("drawInRect") ||
687  SelectorName.startswith("drawWithRect")))
688  return;
689 
690  SVal svTitle = msg.getReceiverSVal();
691 
692  bool isNonLocalized = hasNonLocalizedState(svTitle, C);
693 
694  if (isNonLocalized) {
695  reportLocalizationError(svTitle, msg, C);
696  }
697  }
698 
699  int argumentNumber = getLocalizedArgumentForSelector(odInfo, S);
700  // Go up each hierarchy of superclasses and their protocols
701  while (argumentNumber < 0 && OD->getSuperClass() != nullptr) {
702  for (const auto *P : OD->all_referenced_protocols()) {
703  argumentNumber = getLocalizedArgumentForSelector(P->getIdentifier(), S);
704  if (argumentNumber >= 0)
705  break;
706  }
707  if (argumentNumber < 0) {
708  OD = OD->getSuperClass();
709  argumentNumber = getLocalizedArgumentForSelector(OD->getIdentifier(), S);
710  }
711  }
712 
713  if (argumentNumber < 0) // There was no match in UIMethods
714  return;
715 
716  SVal svTitle = msg.getArgSVal(argumentNumber);
717 
718  if (const ObjCStringRegion *SR =
719  dyn_cast_or_null<ObjCStringRegion>(svTitle.getAsRegion())) {
720  StringRef stringValue =
721  SR->getObjCStringLiteral()->getString()->getString();
722  if ((stringValue.trim().size() == 0 && stringValue.size() > 0) ||
723  stringValue.empty())
724  return;
725  if (!IsAggressive && llvm::sys::unicode::columnWidthUTF8(stringValue) < 2)
726  return;
727  }
728 
729  bool isNonLocalized = hasNonLocalizedState(svTitle, C);
730 
731  if (isNonLocalized) {
732  reportLocalizationError(svTitle, msg, C, argumentNumber + 1);
733  }
734 }
735 
736 static inline bool isNSStringType(QualType T, ASTContext &Ctx) {
737 
739  if (!PT)
740  return false;
741 
743  if (!Cls)
744  return false;
745 
746  IdentifierInfo *ClsName = Cls->getIdentifier();
747 
748  // FIXME: Should we walk the chain of classes?
749  return ClsName == &Ctx.Idents.get("NSString") ||
750  ClsName == &Ctx.Idents.get("NSMutableString");
751 }
752 
753 /// Marks a string being returned by any call as localized
754 /// if it is in LocStringFunctions (LSF) or the function is annotated.
755 /// Otherwise, we mark it as NonLocalized (Aggressive) or
756 /// NonLocalized only if it is not backed by a SymRegion (Non-Aggressive),
757 /// basically leaving only string literals as NonLocalized.
758 void NonLocalizedStringChecker::checkPostCall(const CallEvent &Call,
759  CheckerContext &C) const {
760  initLocStringsMethods(C.getASTContext());
761 
762  if (!Call.getOriginExpr())
763  return;
764 
765  // Anything that takes in a localized NSString as an argument
766  // and returns an NSString will be assumed to be returning a
767  // localized NSString. (Counter: Incorrectly combining two LocalizedStrings)
768  const QualType RT = Call.getResultType();
769  if (isNSStringType(RT, C.getASTContext())) {
770  for (unsigned i = 0; i < Call.getNumArgs(); ++i) {
771  SVal argValue = Call.getArgSVal(i);
772  if (hasLocalizedState(argValue, C)) {
773  SVal sv = Call.getReturnValue();
774  setLocalizedState(sv, C);
775  return;
776  }
777  }
778  }
779 
780  const Decl *D = Call.getDecl();
781  if (!D)
782  return;
783 
784  const IdentifierInfo *Identifier = Call.getCalleeIdentifier();
785 
786  SVal sv = Call.getReturnValue();
787  if (isAnnotatedAsLocalized(D) || LSF.count(Identifier) != 0) {
788  setLocalizedState(sv, C);
789  } else if (isNSStringType(RT, C.getASTContext()) &&
790  !hasLocalizedState(sv, C)) {
791  if (IsAggressive) {
792  setNonLocalizedState(sv, C);
793  } else {
794  const SymbolicRegion *SymReg =
795  dyn_cast_or_null<SymbolicRegion>(sv.getAsRegion());
796  if (!SymReg)
797  setNonLocalizedState(sv, C);
798  }
799  }
800 }
801 
802 /// Marks a string being returned by an ObjC method as localized
803 /// if it is in LocStringMethods or the method is annotated
804 void NonLocalizedStringChecker::checkPostObjCMessage(const ObjCMethodCall &msg,
805  CheckerContext &C) const {
806  initLocStringsMethods(C.getASTContext());
807 
808  if (!msg.isInstanceMessage())
809  return;
810 
811  const ObjCInterfaceDecl *OD = msg.getReceiverInterface();
812  if (!OD)
813  return;
814  const IdentifierInfo *odInfo = OD->getIdentifier();
815 
816  Selector S = msg.getSelector();
817  std::string SelectorName = S.getAsString();
818 
819  std::pair<const IdentifierInfo *, Selector> MethodDescription = {odInfo, S};
820 
821  if (LSM.count(MethodDescription) || isAnnotatedAsLocalized(msg.getDecl())) {
822  SVal sv = msg.getReturnValue();
823  setLocalizedState(sv, C);
824  }
825 }
826 
827 /// Marks all empty string literals as localized
828 void NonLocalizedStringChecker::checkPostStmt(const ObjCStringLiteral *SL,
829  CheckerContext &C) const {
830  SVal sv = C.getSVal(SL);
831  setNonLocalizedState(sv, C);
832 }
833 
834 namespace {
835 class EmptyLocalizationContextChecker
836  : public Checker<check::ASTDecl<ObjCImplementationDecl>> {
837 
838  // A helper class, which walks the AST
839  class MethodCrawler : public ConstStmtVisitor<MethodCrawler> {
840  const ObjCMethodDecl *MD;
841  BugReporter &BR;
842  AnalysisManager &Mgr;
843  const CheckerBase *Checker;
845 
846  public:
847  MethodCrawler(const ObjCMethodDecl *InMD, BugReporter &InBR,
848  const CheckerBase *Checker, AnalysisManager &InMgr,
849  AnalysisDeclContext *InDCtx)
850  : MD(InMD), BR(InBR), Mgr(InMgr), Checker(Checker), DCtx(InDCtx) {}
851 
852  void VisitStmt(const Stmt *S) { VisitChildren(S); }
853 
854  void VisitObjCMessageExpr(const ObjCMessageExpr *ME);
855 
856  void reportEmptyContextError(const ObjCMessageExpr *M) const;
857 
858  void VisitChildren(const Stmt *S) {
859  for (const Stmt *Child : S->children()) {
860  if (Child)
861  this->Visit(Child);
862  }
863  }
864  };
865 
866 public:
867  void checkASTDecl(const ObjCImplementationDecl *D, AnalysisManager &Mgr,
868  BugReporter &BR) const;
869 };
870 } // end anonymous namespace
871 
872 void EmptyLocalizationContextChecker::checkASTDecl(
874  BugReporter &BR) const {
875 
876  for (const ObjCMethodDecl *M : D->methods()) {
878 
879  const Stmt *Body = M->getBody();
880  assert(Body);
881 
882  MethodCrawler MC(M->getCanonicalDecl(), BR, this, Mgr, DCtx);
883  MC.VisitStmt(Body);
884  }
885 }
886 
887 /// This check attempts to match these macros, assuming they are defined as
888 /// follows:
889 ///
890 /// #define NSLocalizedString(key, comment) \
891 /// [[NSBundle mainBundle] localizedStringForKey:(key) value:@"" table:nil]
892 /// #define NSLocalizedStringFromTable(key, tbl, comment) \
893 /// [[NSBundle mainBundle] localizedStringForKey:(key) value:@"" table:(tbl)]
894 /// #define NSLocalizedStringFromTableInBundle(key, tbl, bundle, comment) \
895 /// [bundle localizedStringForKey:(key) value:@"" table:(tbl)]
896 /// #define NSLocalizedStringWithDefaultValue(key, tbl, bundle, val, comment)
897 ///
898 /// We cannot use the path sensitive check because the macro argument we are
899 /// checking for (comment) is not used and thus not present in the AST,
900 /// so we use Lexer on the original macro call and retrieve the value of
901 /// the comment. If it's empty or nil, we raise a warning.
902 void EmptyLocalizationContextChecker::MethodCrawler::VisitObjCMessageExpr(
903  const ObjCMessageExpr *ME) {
904 
905  const ObjCInterfaceDecl *OD = ME->getReceiverInterface();
906  if (!OD)
907  return;
908 
909  const IdentifierInfo *odInfo = OD->getIdentifier();
910 
911  if (!(odInfo->isStr("NSBundle") &&
912  ME->getSelector().getAsString() ==
913  "localizedStringForKey:value:table:")) {
914  return;
915  }
916 
917  SourceRange R = ME->getSourceRange();
918  if (!R.getBegin().isMacroID())
919  return;
920 
921  // getImmediateMacroCallerLoc gets the location of the immediate macro
922  // caller, one level up the stack toward the initial macro typed into the
923  // source, so SL should point to the NSLocalizedString macro.
924  SourceLocation SL =
926  std::pair<FileID, unsigned> SLInfo =
928 
929  SrcMgr::SLocEntry SE = Mgr.getSourceManager().getSLocEntry(SLInfo.first);
930 
931  // If NSLocalizedString macro is wrapped in another macro, we need to
932  // unwrap the expansion until we get to the NSLocalizedStringMacro.
933  while (SE.isExpansion()) {
934  SL = SE.getExpansion().getSpellingLoc();
935  SLInfo = Mgr.getSourceManager().getDecomposedLoc(SL);
936  SE = Mgr.getSourceManager().getSLocEntry(SLInfo.first);
937  }
938 
939  llvm::MemoryBuffer *BF = SE.getFile().getContentCache()->getRawBuffer();
940  Lexer TheLexer(SL, LangOptions(), BF->getBufferStart(),
941  BF->getBufferStart() + SLInfo.second, BF->getBufferEnd());
942 
943  Token I;
944  Token Result; // This will hold the token just before the last ')'
945  int p_count = 0; // This is for parenthesis matching
946  while (!TheLexer.LexFromRawLexer(I)) {
947  if (I.getKind() == tok::l_paren)
948  ++p_count;
949  if (I.getKind() == tok::r_paren) {
950  if (p_count == 1)
951  break;
952  --p_count;
953  }
954  Result = I;
955  }
956 
957  if (isAnyIdentifier(Result.getKind())) {
958  if (Result.getRawIdentifier().equals("nil")) {
959  reportEmptyContextError(ME);
960  return;
961  }
962  }
963 
964  if (!isStringLiteral(Result.getKind()))
965  return;
966 
967  StringRef Comment =
968  StringRef(Result.getLiteralData(), Result.getLength()).trim("\"");
969 
970  if ((Comment.trim().size() == 0 && Comment.size() > 0) || // Is Whitespace
971  Comment.empty()) {
972  reportEmptyContextError(ME);
973  }
974 }
975 
976 void EmptyLocalizationContextChecker::MethodCrawler::reportEmptyContextError(
977  const ObjCMessageExpr *ME) const {
978  // Generate the bug report.
979  BR.EmitBasicReport(MD, Checker, "Context Missing",
980  "Localizability Issue (Apple)",
981  "Localized string macro should include a non-empty "
982  "comment for translators",
983  PathDiagnosticLocation(ME, BR.getSourceManager(), DCtx));
984 }
985 
986 namespace {
987 class PluralMisuseChecker : public Checker<check::ASTCodeBody> {
988 
989  // A helper class, which walks the AST
990  class MethodCrawler : public RecursiveASTVisitor<MethodCrawler> {
991  BugReporter &BR;
992  const CheckerBase *Checker;
994 
995  // This functions like a stack. We push on any IfStmt or
996  // ConditionalOperator that matches the condition
997  // and pop it off when we leave that statement
999  // This is true when we are the direct-child of a
1000  // matching statement
1001  bool InMatchingStatement = false;
1002 
1003  public:
1004  explicit MethodCrawler(BugReporter &InBR, const CheckerBase *Checker,
1005  AnalysisDeclContext *InAC)
1006  : BR(InBR), Checker(Checker), AC(InAC) {}
1007 
1008  bool VisitIfStmt(const IfStmt *I);
1009  bool EndVisitIfStmt(IfStmt *I);
1010  bool TraverseIfStmt(IfStmt *x);
1011  bool VisitConditionalOperator(const ConditionalOperator *C);
1012  bool TraverseConditionalOperator(ConditionalOperator *C);
1013  bool VisitCallExpr(const CallExpr *CE);
1014  bool VisitObjCMessageExpr(const ObjCMessageExpr *ME);
1015 
1016  private:
1017  void reportPluralMisuseError(const Stmt *S) const;
1018  bool isCheckingPlurality(const Expr *E) const;
1019  };
1020 
1021 public:
1022  void checkASTCodeBody(const Decl *D, AnalysisManager &Mgr,
1023  BugReporter &BR) const {
1024  MethodCrawler Visitor(BR, this, Mgr.getAnalysisDeclContext(D));
1025  Visitor.TraverseDecl(const_cast<Decl *>(D));
1026  }
1027 };
1028 } // end anonymous namespace
1029 
1030 // Checks the condition of the IfStmt and returns true if one
1031 // of the following heuristics are met:
1032 // 1) The conidtion is a variable with "singular" or "plural" in the name
1033 // 2) The condition is a binary operator with 1 or 2 on the right-hand side
1034 bool PluralMisuseChecker::MethodCrawler::isCheckingPlurality(
1035  const Expr *Condition) const {
1036  const BinaryOperator *BO = nullptr;
1037  // Accounts for when a VarDecl represents a BinaryOperator
1038  if (const DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(Condition)) {
1039  if (const VarDecl *VD = dyn_cast<VarDecl>(DRE->getDecl())) {
1040  const Expr *InitExpr = VD->getInit();
1041  if (InitExpr) {
1042  if (const BinaryOperator *B =
1043  dyn_cast<BinaryOperator>(InitExpr->IgnoreParenImpCasts())) {
1044  BO = B;
1045  }
1046  }
1047  if (VD->getName().lower().find("plural") != StringRef::npos ||
1048  VD->getName().lower().find("singular") != StringRef::npos) {
1049  return true;
1050  }
1051  }
1052  } else if (const BinaryOperator *B = dyn_cast<BinaryOperator>(Condition)) {
1053  BO = B;
1054  }
1055 
1056  if (BO == nullptr)
1057  return false;
1058 
1059  if (IntegerLiteral *IL = dyn_cast_or_null<IntegerLiteral>(
1060  BO->getRHS()->IgnoreParenImpCasts())) {
1061  llvm::APInt Value = IL->getValue();
1062  if (Value == 1 || Value == 2) {
1063  return true;
1064  }
1065  }
1066  return false;
1067 }
1068 
1069 // A CallExpr with "LOC" in its identifier that takes in a string literal
1070 // has been shown to almost always be a function that returns a localized
1071 // string. Raise a diagnostic when this is in a statement that matches
1072 // the condition.
1073 bool PluralMisuseChecker::MethodCrawler::VisitCallExpr(const CallExpr *CE) {
1074  if (InMatchingStatement) {
1075  if (const FunctionDecl *FD = CE->getDirectCallee()) {
1076  std::string NormalizedName =
1077  StringRef(FD->getNameInfo().getAsString()).lower();
1078  if (NormalizedName.find("loc") != std::string::npos) {
1079  for (const Expr *Arg : CE->arguments()) {
1080  if (isa<ObjCStringLiteral>(Arg))
1081  reportPluralMisuseError(CE);
1082  }
1083  }
1084  }
1085  }
1086  return true;
1087 }
1088 
1089 // The other case is for NSLocalizedString which also returns
1090 // a localized string. It's a macro for the ObjCMessageExpr
1091 // [NSBundle localizedStringForKey:value:table:] Raise a
1092 // diagnostic when this is in a statement that matches
1093 // the condition.
1094 bool PluralMisuseChecker::MethodCrawler::VisitObjCMessageExpr(
1095  const ObjCMessageExpr *ME) {
1096  const ObjCInterfaceDecl *OD = ME->getReceiverInterface();
1097  if (!OD)
1098  return true;
1099 
1100  const IdentifierInfo *odInfo = OD->getIdentifier();
1101 
1102  if (odInfo->isStr("NSBundle") &&
1103  ME->getSelector().getAsString() == "localizedStringForKey:value:table:") {
1104  if (InMatchingStatement) {
1105  reportPluralMisuseError(ME);
1106  }
1107  }
1108  return true;
1109 }
1110 
1111 /// Override TraverseIfStmt so we know when we are done traversing an IfStmt
1112 bool PluralMisuseChecker::MethodCrawler::TraverseIfStmt(IfStmt *I) {
1114  return EndVisitIfStmt(I);
1115 }
1116 
1117 // EndVisit callbacks are not provided by the RecursiveASTVisitor
1118 // so we override TraverseIfStmt and make a call to EndVisitIfStmt
1119 // after traversing the IfStmt
1120 bool PluralMisuseChecker::MethodCrawler::EndVisitIfStmt(IfStmt *I) {
1121  MatchingStatements.pop_back();
1122  if (!MatchingStatements.empty()) {
1123  if (MatchingStatements.back() != nullptr) {
1124  InMatchingStatement = true;
1125  return true;
1126  }
1127  }
1128  InMatchingStatement = false;
1129  return true;
1130 }
1131 
1132 bool PluralMisuseChecker::MethodCrawler::VisitIfStmt(const IfStmt *I) {
1133  const Expr *Condition = I->getCond()->IgnoreParenImpCasts();
1134  if (isCheckingPlurality(Condition)) {
1135  MatchingStatements.push_back(I);
1136  InMatchingStatement = true;
1137  } else {
1138  MatchingStatements.push_back(nullptr);
1139  InMatchingStatement = false;
1140  }
1141 
1142  return true;
1143 }
1144 
1145 // Preliminary support for conditional operators.
1146 bool PluralMisuseChecker::MethodCrawler::TraverseConditionalOperator(
1147  ConditionalOperator *C) {
1149  MatchingStatements.pop_back();
1150  if (!MatchingStatements.empty()) {
1151  if (MatchingStatements.back() != nullptr)
1152  InMatchingStatement = true;
1153  else
1154  InMatchingStatement = false;
1155  } else {
1156  InMatchingStatement = false;
1157  }
1158  return true;
1159 }
1160 
1161 bool PluralMisuseChecker::MethodCrawler::VisitConditionalOperator(
1162  const ConditionalOperator *C) {
1163  const Expr *Condition = C->getCond()->IgnoreParenImpCasts();
1164  if (isCheckingPlurality(Condition)) {
1165  MatchingStatements.push_back(C);
1166  InMatchingStatement = true;
1167  } else {
1168  MatchingStatements.push_back(nullptr);
1169  InMatchingStatement = false;
1170  }
1171  return true;
1172 }
1173 
1174 void PluralMisuseChecker::MethodCrawler::reportPluralMisuseError(
1175  const Stmt *S) const {
1176  // Generate the bug report.
1177  BR.EmitBasicReport(AC->getDecl(), Checker, "Plural Misuse",
1178  "Localizability Issue (Apple)",
1179  "Plural cases are not supported accross all languages. "
1180  "Use a .stringsdict file instead",
1182 }
1183 
1184 //===----------------------------------------------------------------------===//
1185 // Checker registration.
1186 //===----------------------------------------------------------------------===//
1187 
1188 void ento::registerNonLocalizedStringChecker(CheckerManager &mgr) {
1189  NonLocalizedStringChecker *checker =
1190  mgr.registerChecker<NonLocalizedStringChecker>();
1191  checker->IsAggressive =
1192  mgr.getAnalyzerOptions().getBooleanOption("AggressiveReport", false);
1193 }
1194 
1195 void ento::registerEmptyLocalizationContextChecker(CheckerManager &mgr) {
1196  mgr.registerChecker<EmptyLocalizationContextChecker>();
1197 }
1198 
1199 void ento::registerPluralMisuseChecker(CheckerManager &mgr) {
1200  mgr.registerChecker<PluralMisuseChecker>();
1201 }
virtual SVal getArgSVal(unsigned Index) const
Returns the value of a given argument at the time of the call.
Definition: CallEvent.cpp:213
#define NEW_RECEIVER(receiver)
FunctionDecl - An instance of this class is created to represent a function declaration or definition...
Definition: Decl.h:1483
Lexer - This provides a simple interface that turns a text buffer into a stream of tokens...
Definition: Lexer.h:46
Smart pointer class that efficiently represents Objective-C method names.
This is a discriminated union of FileInfo and ExpansionInfo.
A (possibly-)qualified type.
Definition: Type.h:575
MemRegion - The root abstract class for all memory regions.
Definition: MemRegion.h:78
bool isMacroID() const
bool isInstanceMessage() const
Definition: CallEvent.h:885
specific_attr_iterator< T > specific_attr_begin() const
Definition: DeclBase.h:487
bool operator==(CanQual< T > x, CanQual< U > y)
ConstStmtVisitor - This class implements a simple visitor for Stmt subclasses.
Definition: StmtVisitor.h:187
IdentifierInfo * getIdentifier() const
getIdentifier - Get the identifier that names this declaration, if there is one.
Definition: Decl.h:164
#define ADD_METHOD(receiver, method_list, count, argument)
IfStmt - This represents an if/then/else.
Definition: Stmt.h:869
A helper class which wraps a boolean value set to false by default.
Definition: Checker.h:542
ExplodedNode * addTransition(ProgramStateRef State=nullptr, const ProgramPointTag *Tag=nullptr)
Generates a new transition in the program state graph (ExplodedGraph).
Decl - This represents one declaration (or definition), e.g.
Definition: DeclBase.h:77
const SrcMgr::SLocEntry & getSLocEntry(FileID FID, bool *Invalid=nullptr) const
REGISTER_MAP_WITH_PROGRAMSTATE(LocalizedMemMap, const MemRegion *, LocalizedState) NonLocalizedStringChecker
const ExpansionInfo & getExpansion() const
const ObjCObjectType * getObjectType() const
Gets the type pointed to by this ObjC pointer.
Definition: Type.h:4861
SourceRange getSourceRange() const override
Definition: CallEvent.cpp:714
VarDecl - An instance of this class is created to represent a variable declaration or definition...
Definition: Decl.h:699
bool isStringLiteral(TokenKind K)
Return true if this is a C or C++ string-literal (or C++11 user-defined-string-literal) token...
Definition: TokenKinds.h:79
ObjCMethodDecl - Represents an instance or class method declaration.
Definition: DeclObjC.h:113
ExplodedNode * getPredecessor()
Returns the previous node in the exploded graph, which includes the state of the program before the c...
const ObjCInterfaceDecl * getReceiverInterface() const
Get the interface for the receiver.
Definition: CallEvent.h:907
One of these records is kept for each identifier that is lexed.
The region associated with an ObjCStringLiteral.
Definition: MemRegion.h:789
Holds long-lived AST nodes (such as types and decls) that can be referred to throughout the semantic ...
Definition: ASTContext.h:91
LineState State
AnalysisDeclContext contains the context data for the function or method under analysis.
const Expr * getOriginExpr() const
Returns the expression whose value will be the result of this call.
Definition: CallEvent.h:198
Token - This structure provides full information about a lexed token.
Definition: Token.h:37
method_range methods() const
Definition: DeclObjC.h:729
Keeps track of the various options that can be enabled, which controls the dialect of C or C++ that i...
Definition: LangOptions.h:48
IdentifierTable & Idents
Definition: ASTContext.h:451
Represents any expression that calls an Objective-C method.
Definition: CallEvent.h:849
SVal getReceiverSVal() const
Returns the value of the receiver at the time of this call.
Definition: CallEvent.cpp:685
SourceLocation getImmediateMacroCallerLoc(SourceLocation Loc) const
Gets the location of the immediate macro caller, one level up the stack toward the initial macro type...
A builtin binary operation expression such as "x + y" or "x <= y".
Definition: Expr.h:2875
Selector getSelector() const
Definition: ExprObjC.cpp:306
ObjCInterfaceDecl * getInterface() const
Gets the interface declaration for this object type, if the base type really is an interface...
Definition: Type.h:4800
ObjCStringLiteral, used for Objective-C string literals i.e.
Definition: ExprObjC.h:29
tok::TokenKind getKind() const
Definition: Token.h:90
A class that does preorder depth-first traversal on the entire Clang AST and visits each node...
Represents an ObjC class declaration.
Definition: DeclObjC.h:853
detail::InMemoryDirectory::const_iterator I
AnalysisDeclContext * getAnalysisDeclContext(const Decl *D)
AnnotatingParser & P
specific_attr_iterator< T > specific_attr_end() const
Definition: DeclBase.h:491
StringRef getRawIdentifier() const
getRawIdentifier - For a raw identifier token (i.e., an identifier lexed in raw mode), returns a reference to the text substring in the buffer if known.
Definition: Token.h:203
ConditionalOperator - The ?: ternary operator.
Definition: Expr.h:3148
#define LSM_INSERT_NULLARY(receiver, method_name)
Expr * getCond() const
Definition: Expr.h:3182
ID
Defines the set of possible language-specific address spaces.
Definition: AddressSpaces.h:27
SymbolicRegion - A special, "non-concrete" region.
Definition: MemRegion.h:723
Expr - This represents one expression.
Definition: Expr.h:104
const ProgramStateRef & getState() const
ObjCInterfaceDecl * getReceiverInterface() const
Retrieve the Objective-C interface to which this message is being directed, if known.
Definition: ExprObjC.cpp:327
An expression that sends a message to the given Objective-C object or class.
Definition: ExprObjC.h:860
The result type of a method or function.
bool getBooleanOption(StringRef Name, bool DefaultVal, const ento::CheckerBase *C=nullptr, bool SearchInParents=false)
Interprets an option's string value as a boolean.
#define ADD_UNARY_METHOD(receiver, method, argument)
llvm::PointerUnion< const LocationContext *, AnalysisDeclContext * > LocationOrAnalysisDeclContext
void emitReport(std::unique_ptr< BugReport > R)
Emit the diagnostics report.
const char * getLiteralData() const
getLiteralData - For a literal token (numeric constant, string, etc), this returns a pointer to the s...
Definition: Token.h:215
BugReporter is a utility class for generating PathDiagnostics for analysis.
Definition: BugReporter.h:388
Kind
CHECKER * registerChecker()
Used to register checkers.
static bool isNSStringType(QualType T, ASTContext &Ctx)
Encodes a location in the source.
IdentifierInfo & get(StringRef Name)
Return the identifier token info for the specified named identifier.
void EmitBasicReport(const Decl *DeclWithIssue, const CheckerBase *Checker, StringRef BugName, StringRef BugCategory, StringRef BugStr, PathDiagnosticLocation Loc, ArrayRef< SourceRange > Ranges=None)
SVal - This represents a symbolic expression, which can be either an L-value or an R-value...
Definition: SVals.h:44
Selector getSelector() const
Definition: CallEvent.h:891
void Profile(llvm::FoldingSetNodeID &ID, const ASTContext &Ctx)
Definition: Type.h:4095
#define LSF_INSERT(function_name)
arg_range arguments()
Definition: Expr.h:2224
const FileInfo & getFile() const
std::string getAsString() const
Derive the full selector name (e.g.
SourceLocation getBegin() const
const IdentifierInfo * getCalleeIdentifier() const
Returns the name of the callee, if its name is a simple identifier.
Definition: CallEvent.h:301
const ObjCMethodDecl * getDecl() const override
Definition: CallEvent.h:875
SourceManager & getSourceManager() override
virtual const Decl * getDecl() const
Returns the declaration of the function or method that will be called.
Definition: CallEvent.h:178
bool isStr(const char(&Str)[StrLen]) const
Return true if this is the identifier for the specified string.
FunctionDecl * getDirectCallee()
If the callee is a FunctionDecl, return it. Otherwise return 0.
Definition: Expr.cpp:1210
const ContentCache * getContentCache() const
detail::InMemoryDirectory::const_iterator E
const MemRegion * getAsRegion() const
Definition: SVals.cpp:135
Represents an abstract call to a function or method along a particular path.
Definition: CallEvent.h:113
AnalyzerOptions & getAnalyzerOptions()
Expr * IgnoreParenImpCasts() LLVM_READONLY
IgnoreParenImpCasts - Ignore parentheses and implicit casts.
Definition: Expr.cpp:2551
Represents a pointer to an Objective C object.
Definition: Type.h:4821
QualType getResultType() const
Returns the result type, adjusted for references.
Definition: CallEvent.cpp:28
ObjCImplementationDecl - Represents a class definition - this is where method definitions are specifi...
Definition: DeclObjC.h:2220
const T * getAs() const
Member-template getAs<specific type>'.
Definition: Type.h:5675
X
Add a minimal nested name specifier fixit hint to allow lookup of a tag name from an outer enclosing ...
Definition: SemaDecl.cpp:11761
SourceManager & getSourceManager()
Definition: BugReporter.h:448
#define LSM_INSERT_UNARY(receiver, method_name)
const Expr * getArgExpr(unsigned Index) const override
Definition: CallEvent.h:881
virtual unsigned getNumArgs() const =0
Returns the number of arguments (explicit and implicit).
const Expr * getCond() const
Definition: Stmt.h:901
CallExpr - Represents a function call (C99 6.5.2.2, C++ [expr.call]).
Definition: Expr.h:2134
Expr * getRHS() const
Definition: Expr.h:2923
A reference to a declared variable, function, enum, etc.
Definition: Expr.h:922
unsigned getLength() const
Definition: Token.h:127
#define LSM_INSERT_SELECTOR(receiver, method_list, arguments)
A trivial tuple used to represent a source range.
Tag that can use a checker name as a message provider (see SimpleProgramPointTag).
Definition: Checker.h:484
This class provides an interface through which checkers can create individual bug reports...
Definition: BugReporter.h:55
std::pair< FileID, unsigned > getDecomposedLoc(SourceLocation Loc) const
Decompose the specified location into a raw FileID + Offset pair.
SVal getReturnValue() const
Returns the return value of the call.
Definition: CallEvent.cpp:227
bool isAnyIdentifier(TokenKind K)
Return true if this is a raw identifier or an identifier kind.
Definition: TokenKinds.h:73
SourceLocation getSpellingLoc() const
SVal getSVal(const Stmt *S) const
Get the value of arbitrary expressions at this point in the path.