blob: 97249cd72118b9288f3e843a86759452393cb8d5 [file] [log] [blame]
Anna Zakse5d74ca2015-08-14 20:22:22 +00001//=- 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"
Anna Zakse5d74ca2015-08-14 20:22:22 +000019#include "clang/AST/Attr.h"
20#include "clang/AST/Decl.h"
21#include "clang/AST/DeclObjC.h"
Mehdi Amini9670f842016-07-18 19:02:11 +000022#include "clang/AST/RecursiveASTVisitor.h"
23#include "clang/AST/StmtVisitor.h"
24#include "clang/Lex/Lexer.h"
Anna Zakse5d74ca2015-08-14 20:22:22 +000025#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
26#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
27#include "clang/StaticAnalyzer/Core/Checker.h"
28#include "clang/StaticAnalyzer/Core/CheckerManager.h"
29#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
30#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
31#include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h"
Anna Zakse5d74ca2015-08-14 20:22:22 +000032#include "llvm/Support/Unicode.h"
Anna Zakse5d74ca2015-08-14 20:22:22 +000033
34using namespace clang;
35using namespace ento;
36
37namespace {
38struct LocalizedState {
39private:
40 enum Kind { NonLocalized, Localized } K;
41 LocalizedState(Kind InK) : K(InK) {}
42
43public:
44 bool isLocalized() const { return K == Localized; }
45 bool isNonLocalized() const { return K == NonLocalized; }
46
47 static LocalizedState getLocalized() { return LocalizedState(Localized); }
48 static LocalizedState getNonLocalized() {
49 return LocalizedState(NonLocalized);
50 }
51
52 // Overload the == operator
53 bool operator==(const LocalizedState &X) const { return K == X.K; }
54
55 // LLVMs equivalent of a hash function
56 void Profile(llvm::FoldingSetNodeID &ID) const { ID.AddInteger(K); }
57};
58
59class NonLocalizedStringChecker
60 : public Checker<check::PostCall, check::PreObjCMessage,
61 check::PostObjCMessage,
62 check::PostStmt<ObjCStringLiteral>> {
63
64 mutable std::unique_ptr<BugType> BT;
65
66 // Methods that require a localized string
Devin Coughlin9f21f682015-09-23 21:43:21 +000067 mutable llvm::DenseMap<const IdentifierInfo *,
68 llvm::DenseMap<Selector, uint8_t>> UIMethods;
Anna Zakse5d74ca2015-08-14 20:22:22 +000069 // Methods that return a localized string
Devin Coughlin9f21f682015-09-23 21:43:21 +000070 mutable llvm::SmallSet<std::pair<const IdentifierInfo *, Selector>, 12> LSM;
Anna Zakse5d74ca2015-08-14 20:22:22 +000071 // C Functions that return a localized string
Devin Coughlin9f21f682015-09-23 21:43:21 +000072 mutable llvm::SmallSet<const IdentifierInfo *, 5> LSF;
Anna Zakse5d74ca2015-08-14 20:22:22 +000073
74 void initUIMethods(ASTContext &Ctx) const;
75 void initLocStringsMethods(ASTContext &Ctx) const;
76
77 bool hasNonLocalizedState(SVal S, CheckerContext &C) const;
78 bool hasLocalizedState(SVal S, CheckerContext &C) const;
79 void setNonLocalizedState(SVal S, CheckerContext &C) const;
80 void setLocalizedState(SVal S, CheckerContext &C) const;
81
82 bool isAnnotatedAsLocalized(const Decl *D) const;
83 void reportLocalizationError(SVal S, const ObjCMethodCall &M,
84 CheckerContext &C, int argumentNumber = 0) const;
85
Devin Coughlin9f21f682015-09-23 21:43:21 +000086 int getLocalizedArgumentForSelector(const IdentifierInfo *Receiver,
87 Selector S) const;
88
Anna Zakse5d74ca2015-08-14 20:22:22 +000089public:
90 NonLocalizedStringChecker();
91
92 // When this parameter is set to true, the checker assumes all
93 // methods that return NSStrings are unlocalized. Thus, more false
94 // positives will be reported.
95 DefaultBool IsAggressive;
96
97 void checkPreObjCMessage(const ObjCMethodCall &msg, CheckerContext &C) const;
98 void checkPostObjCMessage(const ObjCMethodCall &msg, CheckerContext &C) const;
99 void checkPostStmt(const ObjCStringLiteral *SL, CheckerContext &C) const;
100 void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
101};
102
103} // end anonymous namespace
104
105REGISTER_MAP_WITH_PROGRAMSTATE(LocalizedMemMap, const MemRegion *,
106 LocalizedState)
107
108NonLocalizedStringChecker::NonLocalizedStringChecker() {
Devin Coughlin9f21f682015-09-23 21:43:21 +0000109 BT.reset(new BugType(this, "Unlocalizable string",
110 "Localizability Issue (Apple)"));
Anna Zakse5d74ca2015-08-14 20:22:22 +0000111}
112
Devin Coughlin97dc0c82016-04-28 19:44:40 +0000113namespace {
114class NonLocalizedStringBRVisitor final
115 : public BugReporterVisitorImpl<NonLocalizedStringBRVisitor> {
116
117 const MemRegion *NonLocalizedString;
118 bool Satisfied;
119
120public:
121 NonLocalizedStringBRVisitor(const MemRegion *NonLocalizedString)
122 : NonLocalizedString(NonLocalizedString), Satisfied(false) {
123 assert(NonLocalizedString);
124 }
125
126 PathDiagnosticPiece *VisitNode(const ExplodedNode *Succ,
127 const ExplodedNode *Pred,
128 BugReporterContext &BRC,
129 BugReport &BR) override;
130
131 void Profile(llvm::FoldingSetNodeID &ID) const override {
132 ID.Add(NonLocalizedString);
133 }
134};
135} // End anonymous namespace.
136
Devin Coughlin9f21f682015-09-23 21:43:21 +0000137#define NEW_RECEIVER(receiver) \
138 llvm::DenseMap<Selector, uint8_t> &receiver##M = \
139 UIMethods.insert({&Ctx.Idents.get(#receiver), \
140 llvm::DenseMap<Selector, uint8_t>()}) \
141 .first->second;
142#define ADD_NULLARY_METHOD(receiver, method, argument) \
143 receiver##M.insert( \
144 {Ctx.Selectors.getNullarySelector(&Ctx.Idents.get(#method)), argument});
145#define ADD_UNARY_METHOD(receiver, method, argument) \
146 receiver##M.insert( \
147 {Ctx.Selectors.getUnarySelector(&Ctx.Idents.get(#method)), argument});
148#define ADD_METHOD(receiver, method_list, count, argument) \
149 receiver##M.insert({Ctx.Selectors.getSelector(count, method_list), argument});
150
Anna Zakse5d74ca2015-08-14 20:22:22 +0000151/// Initializes a list of methods that require a localized string
152/// Format: {"ClassName", {{"selectorName:", LocStringArg#}, ...}, ...}
153void NonLocalizedStringChecker::initUIMethods(ASTContext &Ctx) const {
154 if (!UIMethods.empty())
155 return;
156
Devin Coughlin9f21f682015-09-23 21:43:21 +0000157 // UI Methods
158 NEW_RECEIVER(UISearchDisplayController)
159 ADD_UNARY_METHOD(UISearchDisplayController, setSearchResultsTitle, 0)
Anna Zakse5d74ca2015-08-14 20:22:22 +0000160
Devin Coughlin9f21f682015-09-23 21:43:21 +0000161 NEW_RECEIVER(UITabBarItem)
162 IdentifierInfo *initWithTitleUITabBarItemTag[] = {
163 &Ctx.Idents.get("initWithTitle"), &Ctx.Idents.get("image"),
164 &Ctx.Idents.get("tag")};
165 ADD_METHOD(UITabBarItem, initWithTitleUITabBarItemTag, 3, 0)
166 IdentifierInfo *initWithTitleUITabBarItemImage[] = {
167 &Ctx.Idents.get("initWithTitle"), &Ctx.Idents.get("image"),
168 &Ctx.Idents.get("selectedImage")};
169 ADD_METHOD(UITabBarItem, initWithTitleUITabBarItemImage, 3, 0)
Anna Zakse5d74ca2015-08-14 20:22:22 +0000170
Devin Coughlin9f21f682015-09-23 21:43:21 +0000171 NEW_RECEIVER(NSDockTile)
172 ADD_UNARY_METHOD(NSDockTile, setBadgeLabel, 0)
Anna Zakse5d74ca2015-08-14 20:22:22 +0000173
Devin Coughlin9f21f682015-09-23 21:43:21 +0000174 NEW_RECEIVER(NSStatusItem)
175 ADD_UNARY_METHOD(NSStatusItem, setTitle, 0)
176 ADD_UNARY_METHOD(NSStatusItem, setToolTip, 0)
Anna Zakse5d74ca2015-08-14 20:22:22 +0000177
Devin Coughlin9f21f682015-09-23 21:43:21 +0000178 NEW_RECEIVER(UITableViewRowAction)
179 IdentifierInfo *rowActionWithStyleUITableViewRowAction[] = {
180 &Ctx.Idents.get("rowActionWithStyle"), &Ctx.Idents.get("title"),
181 &Ctx.Idents.get("handler")};
182 ADD_METHOD(UITableViewRowAction, rowActionWithStyleUITableViewRowAction, 3, 1)
183 ADD_UNARY_METHOD(UITableViewRowAction, setTitle, 0)
Anna Zakse5d74ca2015-08-14 20:22:22 +0000184
Devin Coughlin9f21f682015-09-23 21:43:21 +0000185 NEW_RECEIVER(NSBox)
186 ADD_UNARY_METHOD(NSBox, setTitle, 0)
Anna Zakse5d74ca2015-08-14 20:22:22 +0000187
Devin Coughlin9f21f682015-09-23 21:43:21 +0000188 NEW_RECEIVER(NSButton)
189 ADD_UNARY_METHOD(NSButton, setTitle, 0)
190 ADD_UNARY_METHOD(NSButton, setAlternateTitle, 0)
Anna Zakse5d74ca2015-08-14 20:22:22 +0000191
Devin Coughlin9f21f682015-09-23 21:43:21 +0000192 NEW_RECEIVER(NSSavePanel)
193 ADD_UNARY_METHOD(NSSavePanel, setPrompt, 0)
194 ADD_UNARY_METHOD(NSSavePanel, setTitle, 0)
195 ADD_UNARY_METHOD(NSSavePanel, setNameFieldLabel, 0)
196 ADD_UNARY_METHOD(NSSavePanel, setNameFieldStringValue, 0)
197 ADD_UNARY_METHOD(NSSavePanel, setMessage, 0)
Anna Zakse5d74ca2015-08-14 20:22:22 +0000198
Devin Coughlin9f21f682015-09-23 21:43:21 +0000199 NEW_RECEIVER(UIPrintInfo)
200 ADD_UNARY_METHOD(UIPrintInfo, setJobName, 0)
201
202 NEW_RECEIVER(NSTabViewItem)
203 ADD_UNARY_METHOD(NSTabViewItem, setLabel, 0)
204 ADD_UNARY_METHOD(NSTabViewItem, setToolTip, 0)
205
206 NEW_RECEIVER(NSBrowser)
207 IdentifierInfo *setTitleNSBrowser[] = {&Ctx.Idents.get("setTitle"),
208 &Ctx.Idents.get("ofColumn")};
209 ADD_METHOD(NSBrowser, setTitleNSBrowser, 2, 0)
210
211 NEW_RECEIVER(UIAccessibilityElement)
212 ADD_UNARY_METHOD(UIAccessibilityElement, setAccessibilityLabel, 0)
213 ADD_UNARY_METHOD(UIAccessibilityElement, setAccessibilityHint, 0)
214 ADD_UNARY_METHOD(UIAccessibilityElement, setAccessibilityValue, 0)
215
216 NEW_RECEIVER(UIAlertAction)
217 IdentifierInfo *actionWithTitleUIAlertAction[] = {
218 &Ctx.Idents.get("actionWithTitle"), &Ctx.Idents.get("style"),
219 &Ctx.Idents.get("handler")};
220 ADD_METHOD(UIAlertAction, actionWithTitleUIAlertAction, 3, 0)
221
222 NEW_RECEIVER(NSPopUpButton)
223 ADD_UNARY_METHOD(NSPopUpButton, addItemWithTitle, 0)
224 IdentifierInfo *insertItemWithTitleNSPopUpButton[] = {
225 &Ctx.Idents.get("insertItemWithTitle"), &Ctx.Idents.get("atIndex")};
226 ADD_METHOD(NSPopUpButton, insertItemWithTitleNSPopUpButton, 2, 0)
227 ADD_UNARY_METHOD(NSPopUpButton, removeItemWithTitle, 0)
228 ADD_UNARY_METHOD(NSPopUpButton, selectItemWithTitle, 0)
229 ADD_UNARY_METHOD(NSPopUpButton, setTitle, 0)
230
231 NEW_RECEIVER(NSTableViewRowAction)
232 IdentifierInfo *rowActionWithStyleNSTableViewRowAction[] = {
233 &Ctx.Idents.get("rowActionWithStyle"), &Ctx.Idents.get("title"),
234 &Ctx.Idents.get("handler")};
235 ADD_METHOD(NSTableViewRowAction, rowActionWithStyleNSTableViewRowAction, 3, 1)
236 ADD_UNARY_METHOD(NSTableViewRowAction, setTitle, 0)
237
238 NEW_RECEIVER(NSImage)
239 ADD_UNARY_METHOD(NSImage, setAccessibilityDescription, 0)
240
241 NEW_RECEIVER(NSUserActivity)
242 ADD_UNARY_METHOD(NSUserActivity, setTitle, 0)
243
244 NEW_RECEIVER(NSPathControlItem)
245 ADD_UNARY_METHOD(NSPathControlItem, setTitle, 0)
246
247 NEW_RECEIVER(NSCell)
248 ADD_UNARY_METHOD(NSCell, initTextCell, 0)
249 ADD_UNARY_METHOD(NSCell, setTitle, 0)
250 ADD_UNARY_METHOD(NSCell, setStringValue, 0)
251
252 NEW_RECEIVER(NSPathControl)
253 ADD_UNARY_METHOD(NSPathControl, setPlaceholderString, 0)
254
255 NEW_RECEIVER(UIAccessibility)
256 ADD_UNARY_METHOD(UIAccessibility, setAccessibilityLabel, 0)
257 ADD_UNARY_METHOD(UIAccessibility, setAccessibilityHint, 0)
258 ADD_UNARY_METHOD(UIAccessibility, setAccessibilityValue, 0)
259
260 NEW_RECEIVER(NSTableColumn)
261 ADD_UNARY_METHOD(NSTableColumn, setTitle, 0)
262 ADD_UNARY_METHOD(NSTableColumn, setHeaderToolTip, 0)
263
264 NEW_RECEIVER(NSSegmentedControl)
265 IdentifierInfo *setLabelNSSegmentedControl[] = {
266 &Ctx.Idents.get("setLabel"), &Ctx.Idents.get("forSegment")};
267 ADD_METHOD(NSSegmentedControl, setLabelNSSegmentedControl, 2, 0)
268
269 NEW_RECEIVER(NSButtonCell)
270 ADD_UNARY_METHOD(NSButtonCell, setTitle, 0)
271 ADD_UNARY_METHOD(NSButtonCell, setAlternateTitle, 0)
272
273 NEW_RECEIVER(NSSliderCell)
274 ADD_UNARY_METHOD(NSSliderCell, setTitle, 0)
275
276 NEW_RECEIVER(NSControl)
277 ADD_UNARY_METHOD(NSControl, setStringValue, 0)
278
279 NEW_RECEIVER(NSAccessibility)
280 ADD_UNARY_METHOD(NSAccessibility, setAccessibilityValueDescription, 0)
281 ADD_UNARY_METHOD(NSAccessibility, setAccessibilityLabel, 0)
282 ADD_UNARY_METHOD(NSAccessibility, setAccessibilityTitle, 0)
283 ADD_UNARY_METHOD(NSAccessibility, setAccessibilityPlaceholderValue, 0)
284 ADD_UNARY_METHOD(NSAccessibility, setAccessibilityHelp, 0)
285
286 NEW_RECEIVER(NSMatrix)
287 IdentifierInfo *setToolTipNSMatrix[] = {&Ctx.Idents.get("setToolTip"),
288 &Ctx.Idents.get("forCell")};
289 ADD_METHOD(NSMatrix, setToolTipNSMatrix, 2, 0)
290
291 NEW_RECEIVER(NSPrintPanel)
292 ADD_UNARY_METHOD(NSPrintPanel, setDefaultButtonTitle, 0)
293
294 NEW_RECEIVER(UILocalNotification)
295 ADD_UNARY_METHOD(UILocalNotification, setAlertBody, 0)
296 ADD_UNARY_METHOD(UILocalNotification, setAlertAction, 0)
297 ADD_UNARY_METHOD(UILocalNotification, setAlertTitle, 0)
298
299 NEW_RECEIVER(NSSlider)
300 ADD_UNARY_METHOD(NSSlider, setTitle, 0)
301
302 NEW_RECEIVER(UIMenuItem)
303 IdentifierInfo *initWithTitleUIMenuItem[] = {&Ctx.Idents.get("initWithTitle"),
304 &Ctx.Idents.get("action")};
305 ADD_METHOD(UIMenuItem, initWithTitleUIMenuItem, 2, 0)
306 ADD_UNARY_METHOD(UIMenuItem, setTitle, 0)
307
308 NEW_RECEIVER(UIAlertController)
309 IdentifierInfo *alertControllerWithTitleUIAlertController[] = {
310 &Ctx.Idents.get("alertControllerWithTitle"), &Ctx.Idents.get("message"),
311 &Ctx.Idents.get("preferredStyle")};
312 ADD_METHOD(UIAlertController, alertControllerWithTitleUIAlertController, 3, 1)
313 ADD_UNARY_METHOD(UIAlertController, setTitle, 0)
314 ADD_UNARY_METHOD(UIAlertController, setMessage, 0)
315
316 NEW_RECEIVER(UIApplicationShortcutItem)
317 IdentifierInfo *initWithTypeUIApplicationShortcutItemIcon[] = {
318 &Ctx.Idents.get("initWithType"), &Ctx.Idents.get("localizedTitle"),
319 &Ctx.Idents.get("localizedSubtitle"), &Ctx.Idents.get("icon"),
320 &Ctx.Idents.get("userInfo")};
321 ADD_METHOD(UIApplicationShortcutItem,
322 initWithTypeUIApplicationShortcutItemIcon, 5, 1)
323 IdentifierInfo *initWithTypeUIApplicationShortcutItem[] = {
324 &Ctx.Idents.get("initWithType"), &Ctx.Idents.get("localizedTitle")};
325 ADD_METHOD(UIApplicationShortcutItem, initWithTypeUIApplicationShortcutItem,
326 2, 1)
327
328 NEW_RECEIVER(UIActionSheet)
329 IdentifierInfo *initWithTitleUIActionSheet[] = {
330 &Ctx.Idents.get("initWithTitle"), &Ctx.Idents.get("delegate"),
331 &Ctx.Idents.get("cancelButtonTitle"),
332 &Ctx.Idents.get("destructiveButtonTitle"),
333 &Ctx.Idents.get("otherButtonTitles")};
334 ADD_METHOD(UIActionSheet, initWithTitleUIActionSheet, 5, 0)
335 ADD_UNARY_METHOD(UIActionSheet, addButtonWithTitle, 0)
336 ADD_UNARY_METHOD(UIActionSheet, setTitle, 0)
337
338 NEW_RECEIVER(NSURLSessionTask)
339 ADD_UNARY_METHOD(NSURLSessionTask, setTaskDescription, 0)
340
341 NEW_RECEIVER(UIAccessibilityCustomAction)
342 IdentifierInfo *initWithNameUIAccessibilityCustomAction[] = {
343 &Ctx.Idents.get("initWithName"), &Ctx.Idents.get("target"),
344 &Ctx.Idents.get("selector")};
345 ADD_METHOD(UIAccessibilityCustomAction,
346 initWithNameUIAccessibilityCustomAction, 3, 0)
347 ADD_UNARY_METHOD(UIAccessibilityCustomAction, setName, 0)
348
349 NEW_RECEIVER(UISearchBar)
350 ADD_UNARY_METHOD(UISearchBar, setText, 0)
351 ADD_UNARY_METHOD(UISearchBar, setPrompt, 0)
352 ADD_UNARY_METHOD(UISearchBar, setPlaceholder, 0)
353
354 NEW_RECEIVER(UIBarItem)
355 ADD_UNARY_METHOD(UIBarItem, setTitle, 0)
356
357 NEW_RECEIVER(UITextView)
358 ADD_UNARY_METHOD(UITextView, setText, 0)
359
360 NEW_RECEIVER(NSView)
361 ADD_UNARY_METHOD(NSView, setToolTip, 0)
362
363 NEW_RECEIVER(NSTextField)
364 ADD_UNARY_METHOD(NSTextField, setPlaceholderString, 0)
365
366 NEW_RECEIVER(NSAttributedString)
367 ADD_UNARY_METHOD(NSAttributedString, initWithString, 0)
368 IdentifierInfo *initWithStringNSAttributedString[] = {
369 &Ctx.Idents.get("initWithString"), &Ctx.Idents.get("attributes")};
370 ADD_METHOD(NSAttributedString, initWithStringNSAttributedString, 2, 0)
371
372 NEW_RECEIVER(NSText)
373 ADD_UNARY_METHOD(NSText, setString, 0)
374
375 NEW_RECEIVER(UIKeyCommand)
376 IdentifierInfo *keyCommandWithInputUIKeyCommand[] = {
377 &Ctx.Idents.get("keyCommandWithInput"), &Ctx.Idents.get("modifierFlags"),
378 &Ctx.Idents.get("action"), &Ctx.Idents.get("discoverabilityTitle")};
379 ADD_METHOD(UIKeyCommand, keyCommandWithInputUIKeyCommand, 4, 3)
380 ADD_UNARY_METHOD(UIKeyCommand, setDiscoverabilityTitle, 0)
381
382 NEW_RECEIVER(UILabel)
383 ADD_UNARY_METHOD(UILabel, setText, 0)
384
385 NEW_RECEIVER(NSAlert)
386 IdentifierInfo *alertWithMessageTextNSAlert[] = {
387 &Ctx.Idents.get("alertWithMessageText"), &Ctx.Idents.get("defaultButton"),
388 &Ctx.Idents.get("alternateButton"), &Ctx.Idents.get("otherButton"),
389 &Ctx.Idents.get("informativeTextWithFormat")};
390 ADD_METHOD(NSAlert, alertWithMessageTextNSAlert, 5, 0)
391 ADD_UNARY_METHOD(NSAlert, addButtonWithTitle, 0)
392 ADD_UNARY_METHOD(NSAlert, setMessageText, 0)
393 ADD_UNARY_METHOD(NSAlert, setInformativeText, 0)
394 ADD_UNARY_METHOD(NSAlert, setHelpAnchor, 0)
395
396 NEW_RECEIVER(UIMutableApplicationShortcutItem)
397 ADD_UNARY_METHOD(UIMutableApplicationShortcutItem, setLocalizedTitle, 0)
398 ADD_UNARY_METHOD(UIMutableApplicationShortcutItem, setLocalizedSubtitle, 0)
399
400 NEW_RECEIVER(UIButton)
401 IdentifierInfo *setTitleUIButton[] = {&Ctx.Idents.get("setTitle"),
402 &Ctx.Idents.get("forState")};
403 ADD_METHOD(UIButton, setTitleUIButton, 2, 0)
404
405 NEW_RECEIVER(NSWindow)
406 ADD_UNARY_METHOD(NSWindow, setTitle, 0)
407 IdentifierInfo *minFrameWidthWithTitleNSWindow[] = {
408 &Ctx.Idents.get("minFrameWidthWithTitle"), &Ctx.Idents.get("styleMask")};
409 ADD_METHOD(NSWindow, minFrameWidthWithTitleNSWindow, 2, 0)
410 ADD_UNARY_METHOD(NSWindow, setMiniwindowTitle, 0)
411
412 NEW_RECEIVER(NSPathCell)
413 ADD_UNARY_METHOD(NSPathCell, setPlaceholderString, 0)
414
415 NEW_RECEIVER(UIDocumentMenuViewController)
416 IdentifierInfo *addOptionWithTitleUIDocumentMenuViewController[] = {
417 &Ctx.Idents.get("addOptionWithTitle"), &Ctx.Idents.get("image"),
418 &Ctx.Idents.get("order"), &Ctx.Idents.get("handler")};
419 ADD_METHOD(UIDocumentMenuViewController,
420 addOptionWithTitleUIDocumentMenuViewController, 4, 0)
421
422 NEW_RECEIVER(UINavigationItem)
423 ADD_UNARY_METHOD(UINavigationItem, initWithTitle, 0)
424 ADD_UNARY_METHOD(UINavigationItem, setTitle, 0)
425 ADD_UNARY_METHOD(UINavigationItem, setPrompt, 0)
426
427 NEW_RECEIVER(UIAlertView)
428 IdentifierInfo *initWithTitleUIAlertView[] = {
429 &Ctx.Idents.get("initWithTitle"), &Ctx.Idents.get("message"),
430 &Ctx.Idents.get("delegate"), &Ctx.Idents.get("cancelButtonTitle"),
431 &Ctx.Idents.get("otherButtonTitles")};
432 ADD_METHOD(UIAlertView, initWithTitleUIAlertView, 5, 0)
433 ADD_UNARY_METHOD(UIAlertView, addButtonWithTitle, 0)
434 ADD_UNARY_METHOD(UIAlertView, setTitle, 0)
435 ADD_UNARY_METHOD(UIAlertView, setMessage, 0)
436
437 NEW_RECEIVER(NSFormCell)
438 ADD_UNARY_METHOD(NSFormCell, initTextCell, 0)
439 ADD_UNARY_METHOD(NSFormCell, setTitle, 0)
440 ADD_UNARY_METHOD(NSFormCell, setPlaceholderString, 0)
441
442 NEW_RECEIVER(NSUserNotification)
443 ADD_UNARY_METHOD(NSUserNotification, setTitle, 0)
444 ADD_UNARY_METHOD(NSUserNotification, setSubtitle, 0)
445 ADD_UNARY_METHOD(NSUserNotification, setInformativeText, 0)
446 ADD_UNARY_METHOD(NSUserNotification, setActionButtonTitle, 0)
447 ADD_UNARY_METHOD(NSUserNotification, setOtherButtonTitle, 0)
448 ADD_UNARY_METHOD(NSUserNotification, setResponsePlaceholder, 0)
449
450 NEW_RECEIVER(NSToolbarItem)
451 ADD_UNARY_METHOD(NSToolbarItem, setLabel, 0)
452 ADD_UNARY_METHOD(NSToolbarItem, setPaletteLabel, 0)
453 ADD_UNARY_METHOD(NSToolbarItem, setToolTip, 0)
454
455 NEW_RECEIVER(NSProgress)
456 ADD_UNARY_METHOD(NSProgress, setLocalizedDescription, 0)
457 ADD_UNARY_METHOD(NSProgress, setLocalizedAdditionalDescription, 0)
458
459 NEW_RECEIVER(NSSegmentedCell)
460 IdentifierInfo *setLabelNSSegmentedCell[] = {&Ctx.Idents.get("setLabel"),
461 &Ctx.Idents.get("forSegment")};
462 ADD_METHOD(NSSegmentedCell, setLabelNSSegmentedCell, 2, 0)
463 IdentifierInfo *setToolTipNSSegmentedCell[] = {&Ctx.Idents.get("setToolTip"),
464 &Ctx.Idents.get("forSegment")};
465 ADD_METHOD(NSSegmentedCell, setToolTipNSSegmentedCell, 2, 0)
466
467 NEW_RECEIVER(NSUndoManager)
468 ADD_UNARY_METHOD(NSUndoManager, setActionName, 0)
469 ADD_UNARY_METHOD(NSUndoManager, undoMenuTitleForUndoActionName, 0)
470 ADD_UNARY_METHOD(NSUndoManager, redoMenuTitleForUndoActionName, 0)
471
472 NEW_RECEIVER(NSMenuItem)
473 IdentifierInfo *initWithTitleNSMenuItem[] = {
474 &Ctx.Idents.get("initWithTitle"), &Ctx.Idents.get("action"),
475 &Ctx.Idents.get("keyEquivalent")};
476 ADD_METHOD(NSMenuItem, initWithTitleNSMenuItem, 3, 0)
477 ADD_UNARY_METHOD(NSMenuItem, setTitle, 0)
478 ADD_UNARY_METHOD(NSMenuItem, setToolTip, 0)
479
480 NEW_RECEIVER(NSPopUpButtonCell)
481 IdentifierInfo *initTextCellNSPopUpButtonCell[] = {
482 &Ctx.Idents.get("initTextCell"), &Ctx.Idents.get("pullsDown")};
483 ADD_METHOD(NSPopUpButtonCell, initTextCellNSPopUpButtonCell, 2, 0)
484 ADD_UNARY_METHOD(NSPopUpButtonCell, addItemWithTitle, 0)
485 IdentifierInfo *insertItemWithTitleNSPopUpButtonCell[] = {
486 &Ctx.Idents.get("insertItemWithTitle"), &Ctx.Idents.get("atIndex")};
487 ADD_METHOD(NSPopUpButtonCell, insertItemWithTitleNSPopUpButtonCell, 2, 0)
488 ADD_UNARY_METHOD(NSPopUpButtonCell, removeItemWithTitle, 0)
489 ADD_UNARY_METHOD(NSPopUpButtonCell, selectItemWithTitle, 0)
490 ADD_UNARY_METHOD(NSPopUpButtonCell, setTitle, 0)
491
492 NEW_RECEIVER(NSViewController)
493 ADD_UNARY_METHOD(NSViewController, setTitle, 0)
494
495 NEW_RECEIVER(NSMenu)
496 ADD_UNARY_METHOD(NSMenu, initWithTitle, 0)
497 IdentifierInfo *insertItemWithTitleNSMenu[] = {
498 &Ctx.Idents.get("insertItemWithTitle"), &Ctx.Idents.get("action"),
499 &Ctx.Idents.get("keyEquivalent"), &Ctx.Idents.get("atIndex")};
500 ADD_METHOD(NSMenu, insertItemWithTitleNSMenu, 4, 0)
501 IdentifierInfo *addItemWithTitleNSMenu[] = {
502 &Ctx.Idents.get("addItemWithTitle"), &Ctx.Idents.get("action"),
503 &Ctx.Idents.get("keyEquivalent")};
504 ADD_METHOD(NSMenu, addItemWithTitleNSMenu, 3, 0)
505 ADD_UNARY_METHOD(NSMenu, setTitle, 0)
506
507 NEW_RECEIVER(UIMutableUserNotificationAction)
508 ADD_UNARY_METHOD(UIMutableUserNotificationAction, setTitle, 0)
509
510 NEW_RECEIVER(NSForm)
511 ADD_UNARY_METHOD(NSForm, addEntry, 0)
512 IdentifierInfo *insertEntryNSForm[] = {&Ctx.Idents.get("insertEntry"),
513 &Ctx.Idents.get("atIndex")};
514 ADD_METHOD(NSForm, insertEntryNSForm, 2, 0)
515
516 NEW_RECEIVER(NSTextFieldCell)
517 ADD_UNARY_METHOD(NSTextFieldCell, setPlaceholderString, 0)
518
519 NEW_RECEIVER(NSUserNotificationAction)
520 IdentifierInfo *actionWithIdentifierNSUserNotificationAction[] = {
521 &Ctx.Idents.get("actionWithIdentifier"), &Ctx.Idents.get("title")};
522 ADD_METHOD(NSUserNotificationAction,
523 actionWithIdentifierNSUserNotificationAction, 2, 1)
524
525 NEW_RECEIVER(NSURLSession)
526 ADD_UNARY_METHOD(NSURLSession, setSessionDescription, 0)
527
528 NEW_RECEIVER(UITextField)
529 ADD_UNARY_METHOD(UITextField, setText, 0)
530 ADD_UNARY_METHOD(UITextField, setPlaceholder, 0)
531
532 NEW_RECEIVER(UIBarButtonItem)
533 IdentifierInfo *initWithTitleUIBarButtonItem[] = {
534 &Ctx.Idents.get("initWithTitle"), &Ctx.Idents.get("style"),
535 &Ctx.Idents.get("target"), &Ctx.Idents.get("action")};
536 ADD_METHOD(UIBarButtonItem, initWithTitleUIBarButtonItem, 4, 0)
537
538 NEW_RECEIVER(UIViewController)
539 ADD_UNARY_METHOD(UIViewController, setTitle, 0)
540
541 NEW_RECEIVER(UISegmentedControl)
542 IdentifierInfo *insertSegmentWithTitleUISegmentedControl[] = {
543 &Ctx.Idents.get("insertSegmentWithTitle"), &Ctx.Idents.get("atIndex"),
544 &Ctx.Idents.get("animated")};
545 ADD_METHOD(UISegmentedControl, insertSegmentWithTitleUISegmentedControl, 3, 0)
546 IdentifierInfo *setTitleUISegmentedControl[] = {
547 &Ctx.Idents.get("setTitle"), &Ctx.Idents.get("forSegmentAtIndex")};
548 ADD_METHOD(UISegmentedControl, setTitleUISegmentedControl, 2, 0)
Anna Zakse5d74ca2015-08-14 20:22:22 +0000549}
550
Devin Coughlin9f21f682015-09-23 21:43:21 +0000551#define LSF_INSERT(function_name) LSF.insert(&Ctx.Idents.get(function_name));
552#define LSM_INSERT_NULLARY(receiver, method_name) \
553 LSM.insert({&Ctx.Idents.get(receiver), Ctx.Selectors.getNullarySelector( \
554 &Ctx.Idents.get(method_name))});
555#define LSM_INSERT_UNARY(receiver, method_name) \
556 LSM.insert({&Ctx.Idents.get(receiver), \
557 Ctx.Selectors.getUnarySelector(&Ctx.Idents.get(method_name))});
558#define LSM_INSERT_SELECTOR(receiver, method_list, arguments) \
559 LSM.insert({&Ctx.Idents.get(receiver), \
560 Ctx.Selectors.getSelector(arguments, method_list)});
561
Anna Zakse5d74ca2015-08-14 20:22:22 +0000562/// Initializes a list of methods and C functions that return a localized string
563void NonLocalizedStringChecker::initLocStringsMethods(ASTContext &Ctx) const {
564 if (!LSM.empty())
565 return;
566
Devin Coughlin9f21f682015-09-23 21:43:21 +0000567 IdentifierInfo *LocalizedStringMacro[] = {
568 &Ctx.Idents.get("localizedStringForKey"), &Ctx.Idents.get("value"),
569 &Ctx.Idents.get("table")};
570 LSM_INSERT_SELECTOR("NSBundle", LocalizedStringMacro, 3)
571 LSM_INSERT_UNARY("NSDateFormatter", "stringFromDate")
572 IdentifierInfo *LocalizedStringFromDate[] = {
573 &Ctx.Idents.get("localizedStringFromDate"), &Ctx.Idents.get("dateStyle"),
574 &Ctx.Idents.get("timeStyle")};
575 LSM_INSERT_SELECTOR("NSDateFormatter", LocalizedStringFromDate, 3)
576 LSM_INSERT_UNARY("NSNumberFormatter", "stringFromNumber")
577 LSM_INSERT_NULLARY("UITextField", "text")
578 LSM_INSERT_NULLARY("UITextView", "text")
579 LSM_INSERT_NULLARY("UILabel", "text")
Anna Zakse5d74ca2015-08-14 20:22:22 +0000580
Devin Coughlin9f21f682015-09-23 21:43:21 +0000581 LSF_INSERT("CFDateFormatterCreateStringWithDate");
582 LSF_INSERT("CFDateFormatterCreateStringWithAbsoluteTime");
583 LSF_INSERT("CFNumberFormatterCreateStringWithNumber");
Anna Zakse5d74ca2015-08-14 20:22:22 +0000584}
585
586/// Checks to see if the method / function declaration includes
587/// __attribute__((annotate("returns_localized_nsstring")))
588bool NonLocalizedStringChecker::isAnnotatedAsLocalized(const Decl *D) const {
Devin Coughlin9f21f682015-09-23 21:43:21 +0000589 if (!D)
590 return false;
Anna Zakse5d74ca2015-08-14 20:22:22 +0000591 return std::any_of(
592 D->specific_attr_begin<AnnotateAttr>(),
593 D->specific_attr_end<AnnotateAttr>(), [](const AnnotateAttr *Ann) {
594 return Ann->getAnnotation() == "returns_localized_nsstring";
595 });
596}
597
598/// Returns true if the given SVal is marked as Localized in the program state
599bool NonLocalizedStringChecker::hasLocalizedState(SVal S,
600 CheckerContext &C) const {
601 const MemRegion *mt = S.getAsRegion();
602 if (mt) {
603 const LocalizedState *LS = C.getState()->get<LocalizedMemMap>(mt);
604 if (LS && LS->isLocalized())
605 return true;
606 }
607 return false;
608}
609
610/// Returns true if the given SVal is marked as NonLocalized in the program
611/// state
612bool NonLocalizedStringChecker::hasNonLocalizedState(SVal S,
613 CheckerContext &C) const {
614 const MemRegion *mt = S.getAsRegion();
615 if (mt) {
616 const LocalizedState *LS = C.getState()->get<LocalizedMemMap>(mt);
617 if (LS && LS->isNonLocalized())
618 return true;
619 }
620 return false;
621}
622
623/// Marks the given SVal as Localized in the program state
624void NonLocalizedStringChecker::setLocalizedState(const SVal S,
625 CheckerContext &C) const {
626 const MemRegion *mt = S.getAsRegion();
627 if (mt) {
628 ProgramStateRef State =
629 C.getState()->set<LocalizedMemMap>(mt, LocalizedState::getLocalized());
630 C.addTransition(State);
631 }
632}
633
634/// Marks the given SVal as NonLocalized in the program state
635void NonLocalizedStringChecker::setNonLocalizedState(const SVal S,
636 CheckerContext &C) const {
637 const MemRegion *mt = S.getAsRegion();
638 if (mt) {
639 ProgramStateRef State = C.getState()->set<LocalizedMemMap>(
640 mt, LocalizedState::getNonLocalized());
641 C.addTransition(State);
642 }
643}
644
Devin Coughlin084e3632016-02-05 04:22:15 +0000645
646static bool isDebuggingName(std::string name) {
647 return StringRef(name).lower().find("debug") != StringRef::npos;
648}
649
650/// Returns true when, heuristically, the analyzer may be analyzing debugging
651/// code. We use this to suppress localization diagnostics in un-localized user
652/// interfaces that are only used for debugging and are therefore not user
653/// facing.
654static bool isDebuggingContext(CheckerContext &C) {
655 const Decl *D = C.getCurrentAnalysisDeclContext()->getDecl();
656 if (!D)
657 return false;
658
659 if (auto *ND = dyn_cast<NamedDecl>(D)) {
660 if (isDebuggingName(ND->getNameAsString()))
661 return true;
662 }
663
664 const DeclContext *DC = D->getDeclContext();
665
666 if (auto *CD = dyn_cast<ObjCContainerDecl>(DC)) {
667 if (isDebuggingName(CD->getNameAsString()))
668 return true;
669 }
670
671 return false;
672}
673
674
Anna Zakse5d74ca2015-08-14 20:22:22 +0000675/// Reports a localization error for the passed in method call and SVal
676void NonLocalizedStringChecker::reportLocalizationError(
677 SVal S, const ObjCMethodCall &M, CheckerContext &C,
678 int argumentNumber) const {
679
Devin Coughlin084e3632016-02-05 04:22:15 +0000680 // Don't warn about localization errors in classes and methods that
681 // may be debug code.
682 if (isDebuggingContext(C))
683 return;
684
Anna Zakse5d74ca2015-08-14 20:22:22 +0000685 ExplodedNode *ErrNode = C.getPredecessor();
686 static CheckerProgramPointTag Tag("NonLocalizedStringChecker",
687 "UnlocalizedString");
688 ErrNode = C.addTransition(C.getState(), C.getPredecessor(), &Tag);
689
690 if (!ErrNode)
691 return;
692
693 // Generate the bug report.
Devin Coughlin9f21f682015-09-23 21:43:21 +0000694 std::unique_ptr<BugReport> R(new BugReport(
695 *BT, "User-facing text should use localized string macro", ErrNode));
Anna Zakse5d74ca2015-08-14 20:22:22 +0000696 if (argumentNumber) {
697 R->addRange(M.getArgExpr(argumentNumber - 1)->getSourceRange());
698 } else {
699 R->addRange(M.getSourceRange());
700 }
701 R->markInteresting(S);
Devin Coughlin97dc0c82016-04-28 19:44:40 +0000702
703 const MemRegion *StringRegion = S.getAsRegion();
704 if (StringRegion)
705 R->addVisitor(llvm::make_unique<NonLocalizedStringBRVisitor>(StringRegion));
706
Anna Zakse5d74ca2015-08-14 20:22:22 +0000707 C.emitReport(std::move(R));
708}
709
Devin Coughlin9f21f682015-09-23 21:43:21 +0000710/// Returns the argument number requiring localized string if it exists
711/// otherwise, returns -1
712int NonLocalizedStringChecker::getLocalizedArgumentForSelector(
713 const IdentifierInfo *Receiver, Selector S) const {
714 auto method = UIMethods.find(Receiver);
715
716 if (method == UIMethods.end())
717 return -1;
718
719 auto argumentIterator = method->getSecond().find(S);
720
721 if (argumentIterator == method->getSecond().end())
722 return -1;
723
724 int argumentNumber = argumentIterator->getSecond();
725 return argumentNumber;
726}
727
Anna Zakse5d74ca2015-08-14 20:22:22 +0000728/// Check if the string being passed in has NonLocalized state
729void NonLocalizedStringChecker::checkPreObjCMessage(const ObjCMethodCall &msg,
730 CheckerContext &C) const {
731 initUIMethods(C.getASTContext());
732
733 const ObjCInterfaceDecl *OD = msg.getReceiverInterface();
734 if (!OD)
735 return;
736 const IdentifierInfo *odInfo = OD->getIdentifier();
737
738 Selector S = msg.getSelector();
739
740 std::string SelectorString = S.getAsString();
741 StringRef SelectorName = SelectorString;
742 assert(!SelectorName.empty());
743
Anna Zakse5d74ca2015-08-14 20:22:22 +0000744 if (odInfo->isStr("NSString")) {
745 // Handle the case where the receiver is an NSString
746 // These special NSString methods draw to the screen
747
748 if (!(SelectorName.startswith("drawAtPoint") ||
749 SelectorName.startswith("drawInRect") ||
750 SelectorName.startswith("drawWithRect")))
751 return;
752
753 SVal svTitle = msg.getReceiverSVal();
754
755 bool isNonLocalized = hasNonLocalizedState(svTitle, C);
756
757 if (isNonLocalized) {
758 reportLocalizationError(svTitle, msg, C);
759 }
Devin Coughlin9f21f682015-09-23 21:43:21 +0000760 }
Anna Zakse5d74ca2015-08-14 20:22:22 +0000761
Devin Coughlin9f21f682015-09-23 21:43:21 +0000762 int argumentNumber = getLocalizedArgumentForSelector(odInfo, S);
763 // Go up each hierarchy of superclasses and their protocols
764 while (argumentNumber < 0 && OD->getSuperClass() != nullptr) {
765 for (const auto *P : OD->all_referenced_protocols()) {
766 argumentNumber = getLocalizedArgumentForSelector(P->getIdentifier(), S);
767 if (argumentNumber >= 0)
768 break;
769 }
770 if (argumentNumber < 0) {
771 OD = OD->getSuperClass();
772 argumentNumber = getLocalizedArgumentForSelector(OD->getIdentifier(), S);
773 }
774 }
Anna Zakse5d74ca2015-08-14 20:22:22 +0000775
Devin Coughlin9f21f682015-09-23 21:43:21 +0000776 if (argumentNumber < 0) // There was no match in UIMethods
777 return;
778
779 SVal svTitle = msg.getArgSVal(argumentNumber);
780
781 if (const ObjCStringRegion *SR =
782 dyn_cast_or_null<ObjCStringRegion>(svTitle.getAsRegion())) {
783 StringRef stringValue =
784 SR->getObjCStringLiteral()->getString()->getString();
785 if ((stringValue.trim().size() == 0 && stringValue.size() > 0) ||
786 stringValue.empty())
Anna Zakse5d74ca2015-08-14 20:22:22 +0000787 return;
Devin Coughlin9f21f682015-09-23 21:43:21 +0000788 if (!IsAggressive && llvm::sys::unicode::columnWidthUTF8(stringValue) < 2)
789 return;
790 }
Anna Zakse5d74ca2015-08-14 20:22:22 +0000791
Devin Coughlin9f21f682015-09-23 21:43:21 +0000792 bool isNonLocalized = hasNonLocalizedState(svTitle, C);
Anna Zakse5d74ca2015-08-14 20:22:22 +0000793
Devin Coughlin9f21f682015-09-23 21:43:21 +0000794 if (isNonLocalized) {
795 reportLocalizationError(svTitle, msg, C, argumentNumber + 1);
Anna Zakse5d74ca2015-08-14 20:22:22 +0000796 }
797}
798
799static inline bool isNSStringType(QualType T, ASTContext &Ctx) {
800
801 const ObjCObjectPointerType *PT = T->getAs<ObjCObjectPointerType>();
802 if (!PT)
803 return false;
804
805 ObjCInterfaceDecl *Cls = PT->getObjectType()->getInterface();
806 if (!Cls)
807 return false;
808
809 IdentifierInfo *ClsName = Cls->getIdentifier();
810
811 // FIXME: Should we walk the chain of classes?
812 return ClsName == &Ctx.Idents.get("NSString") ||
813 ClsName == &Ctx.Idents.get("NSMutableString");
814}
815
816/// Marks a string being returned by any call as localized
817/// if it is in LocStringFunctions (LSF) or the function is annotated.
818/// Otherwise, we mark it as NonLocalized (Aggressive) or
819/// NonLocalized only if it is not backed by a SymRegion (Non-Aggressive),
820/// basically leaving only string literals as NonLocalized.
821void NonLocalizedStringChecker::checkPostCall(const CallEvent &Call,
822 CheckerContext &C) const {
823 initLocStringsMethods(C.getASTContext());
824
825 if (!Call.getOriginExpr())
826 return;
827
828 // Anything that takes in a localized NSString as an argument
829 // and returns an NSString will be assumed to be returning a
830 // localized NSString. (Counter: Incorrectly combining two LocalizedStrings)
831 const QualType RT = Call.getResultType();
832 if (isNSStringType(RT, C.getASTContext())) {
833 for (unsigned i = 0; i < Call.getNumArgs(); ++i) {
834 SVal argValue = Call.getArgSVal(i);
835 if (hasLocalizedState(argValue, C)) {
836 SVal sv = Call.getReturnValue();
837 setLocalizedState(sv, C);
838 return;
839 }
840 }
841 }
842
843 const Decl *D = Call.getDecl();
844 if (!D)
845 return;
846
Devin Coughlin9f21f682015-09-23 21:43:21 +0000847 const IdentifierInfo *Identifier = Call.getCalleeIdentifier();
Anna Zakse5d74ca2015-08-14 20:22:22 +0000848
849 SVal sv = Call.getReturnValue();
Devin Coughlin9f21f682015-09-23 21:43:21 +0000850 if (isAnnotatedAsLocalized(D) || LSF.count(Identifier) != 0) {
Anna Zakse5d74ca2015-08-14 20:22:22 +0000851 setLocalizedState(sv, C);
852 } else if (isNSStringType(RT, C.getASTContext()) &&
853 !hasLocalizedState(sv, C)) {
854 if (IsAggressive) {
855 setNonLocalizedState(sv, C);
856 } else {
857 const SymbolicRegion *SymReg =
858 dyn_cast_or_null<SymbolicRegion>(sv.getAsRegion());
859 if (!SymReg)
860 setNonLocalizedState(sv, C);
861 }
862 }
863}
864
865/// Marks a string being returned by an ObjC method as localized
866/// if it is in LocStringMethods or the method is annotated
867void NonLocalizedStringChecker::checkPostObjCMessage(const ObjCMethodCall &msg,
868 CheckerContext &C) const {
869 initLocStringsMethods(C.getASTContext());
870
871 if (!msg.isInstanceMessage())
872 return;
873
874 const ObjCInterfaceDecl *OD = msg.getReceiverInterface();
875 if (!OD)
876 return;
877 const IdentifierInfo *odInfo = OD->getIdentifier();
878
Anna Zakse5d74ca2015-08-14 20:22:22 +0000879 Selector S = msg.getSelector();
880 std::string SelectorName = S.getAsString();
881
Devin Coughlin9f21f682015-09-23 21:43:21 +0000882 std::pair<const IdentifierInfo *, Selector> MethodDescription = {odInfo, S};
Anna Zakse5d74ca2015-08-14 20:22:22 +0000883
884 if (LSM.count(MethodDescription) || isAnnotatedAsLocalized(msg.getDecl())) {
885 SVal sv = msg.getReturnValue();
886 setLocalizedState(sv, C);
887 }
888}
889
890/// Marks all empty string literals as localized
891void NonLocalizedStringChecker::checkPostStmt(const ObjCStringLiteral *SL,
892 CheckerContext &C) const {
893 SVal sv = C.getSVal(SL);
894 setNonLocalizedState(sv, C);
895}
896
Devin Coughlin97dc0c82016-04-28 19:44:40 +0000897PathDiagnosticPiece *
898NonLocalizedStringBRVisitor::VisitNode(const ExplodedNode *Succ,
899 const ExplodedNode *Pred,
900 BugReporterContext &BRC, BugReport &BR) {
901 if (Satisfied)
902 return nullptr;
903
904 Optional<StmtPoint> Point = Succ->getLocation().getAs<StmtPoint>();
905 if (!Point.hasValue())
906 return nullptr;
907
908 auto *LiteralExpr = dyn_cast<ObjCStringLiteral>(Point->getStmt());
909 if (!LiteralExpr)
910 return nullptr;
911
912 ProgramStateRef State = Succ->getState();
913 SVal LiteralSVal = State->getSVal(LiteralExpr, Succ->getLocationContext());
914 if (LiteralSVal.getAsRegion() != NonLocalizedString)
915 return nullptr;
916
917 Satisfied = true;
918
919 PathDiagnosticLocation L =
920 PathDiagnosticLocation::create(*Point, BRC.getSourceManager());
921
922 if (!L.isValid() || !L.asLocation().isValid())
923 return nullptr;
924
925 auto *Piece = new PathDiagnosticEventPiece(L,
926 "Non-localized string literal here");
927 Piece->addRange(LiteralExpr->getSourceRange());
928
929 return Piece;
930}
931
Anna Zakse5d74ca2015-08-14 20:22:22 +0000932namespace {
933class EmptyLocalizationContextChecker
934 : public Checker<check::ASTDecl<ObjCImplementationDecl>> {
935
936 // A helper class, which walks the AST
937 class MethodCrawler : public ConstStmtVisitor<MethodCrawler> {
938 const ObjCMethodDecl *MD;
939 BugReporter &BR;
940 AnalysisManager &Mgr;
941 const CheckerBase *Checker;
942 LocationOrAnalysisDeclContext DCtx;
943
944 public:
945 MethodCrawler(const ObjCMethodDecl *InMD, BugReporter &InBR,
946 const CheckerBase *Checker, AnalysisManager &InMgr,
947 AnalysisDeclContext *InDCtx)
948 : MD(InMD), BR(InBR), Mgr(InMgr), Checker(Checker), DCtx(InDCtx) {}
949
950 void VisitStmt(const Stmt *S) { VisitChildren(S); }
951
952 void VisitObjCMessageExpr(const ObjCMessageExpr *ME);
953
954 void reportEmptyContextError(const ObjCMessageExpr *M) const;
955
956 void VisitChildren(const Stmt *S) {
957 for (const Stmt *Child : S->children()) {
958 if (Child)
959 this->Visit(Child);
960 }
961 }
962 };
963
964public:
965 void checkASTDecl(const ObjCImplementationDecl *D, AnalysisManager &Mgr,
966 BugReporter &BR) const;
967};
968} // end anonymous namespace
969
970void EmptyLocalizationContextChecker::checkASTDecl(
971 const ObjCImplementationDecl *D, AnalysisManager &Mgr,
972 BugReporter &BR) const {
973
974 for (const ObjCMethodDecl *M : D->methods()) {
975 AnalysisDeclContext *DCtx = Mgr.getAnalysisDeclContext(M);
976
977 const Stmt *Body = M->getBody();
978 assert(Body);
979
980 MethodCrawler MC(M->getCanonicalDecl(), BR, this, Mgr, DCtx);
981 MC.VisitStmt(Body);
982 }
983}
984
985/// This check attempts to match these macros, assuming they are defined as
986/// follows:
987///
988/// #define NSLocalizedString(key, comment) \
989/// [[NSBundle mainBundle] localizedStringForKey:(key) value:@"" table:nil]
990/// #define NSLocalizedStringFromTable(key, tbl, comment) \
991/// [[NSBundle mainBundle] localizedStringForKey:(key) value:@"" table:(tbl)]
992/// #define NSLocalizedStringFromTableInBundle(key, tbl, bundle, comment) \
993/// [bundle localizedStringForKey:(key) value:@"" table:(tbl)]
994/// #define NSLocalizedStringWithDefaultValue(key, tbl, bundle, val, comment)
995///
996/// We cannot use the path sensitive check because the macro argument we are
997/// checking for (comment) is not used and thus not present in the AST,
998/// so we use Lexer on the original macro call and retrieve the value of
999/// the comment. If it's empty or nil, we raise a warning.
1000void EmptyLocalizationContextChecker::MethodCrawler::VisitObjCMessageExpr(
1001 const ObjCMessageExpr *ME) {
1002
1003 const ObjCInterfaceDecl *OD = ME->getReceiverInterface();
1004 if (!OD)
1005 return;
1006
1007 const IdentifierInfo *odInfo = OD->getIdentifier();
1008
Devin Coughlin9f21f682015-09-23 21:43:21 +00001009 if (!(odInfo->isStr("NSBundle") &&
Anna Zakse5d74ca2015-08-14 20:22:22 +00001010 ME->getSelector().getAsString() ==
1011 "localizedStringForKey:value:table:")) {
1012 return;
1013 }
1014
1015 SourceRange R = ME->getSourceRange();
1016 if (!R.getBegin().isMacroID())
1017 return;
1018
1019 // getImmediateMacroCallerLoc gets the location of the immediate macro
1020 // caller, one level up the stack toward the initial macro typed into the
1021 // source, so SL should point to the NSLocalizedString macro.
1022 SourceLocation SL =
1023 Mgr.getSourceManager().getImmediateMacroCallerLoc(R.getBegin());
1024 std::pair<FileID, unsigned> SLInfo =
1025 Mgr.getSourceManager().getDecomposedLoc(SL);
1026
1027 SrcMgr::SLocEntry SE = Mgr.getSourceManager().getSLocEntry(SLInfo.first);
1028
1029 // If NSLocalizedString macro is wrapped in another macro, we need to
1030 // unwrap the expansion until we get to the NSLocalizedStringMacro.
1031 while (SE.isExpansion()) {
1032 SL = SE.getExpansion().getSpellingLoc();
1033 SLInfo = Mgr.getSourceManager().getDecomposedLoc(SL);
1034 SE = Mgr.getSourceManager().getSLocEntry(SLInfo.first);
1035 }
1036
1037 llvm::MemoryBuffer *BF = SE.getFile().getContentCache()->getRawBuffer();
1038 Lexer TheLexer(SL, LangOptions(), BF->getBufferStart(),
1039 BF->getBufferStart() + SLInfo.second, BF->getBufferEnd());
1040
1041 Token I;
1042 Token Result; // This will hold the token just before the last ')'
1043 int p_count = 0; // This is for parenthesis matching
1044 while (!TheLexer.LexFromRawLexer(I)) {
1045 if (I.getKind() == tok::l_paren)
1046 ++p_count;
1047 if (I.getKind() == tok::r_paren) {
1048 if (p_count == 1)
1049 break;
1050 --p_count;
1051 }
1052 Result = I;
1053 }
1054
1055 if (isAnyIdentifier(Result.getKind())) {
1056 if (Result.getRawIdentifier().equals("nil")) {
1057 reportEmptyContextError(ME);
1058 return;
1059 }
1060 }
1061
1062 if (!isStringLiteral(Result.getKind()))
1063 return;
1064
1065 StringRef Comment =
Vedant Kumar409506e2016-02-16 02:14:44 +00001066 StringRef(Result.getLiteralData(), Result.getLength()).trim('"');
Anna Zakse5d74ca2015-08-14 20:22:22 +00001067
1068 if ((Comment.trim().size() == 0 && Comment.size() > 0) || // Is Whitespace
1069 Comment.empty()) {
1070 reportEmptyContextError(ME);
1071 }
1072}
1073
1074void EmptyLocalizationContextChecker::MethodCrawler::reportEmptyContextError(
1075 const ObjCMessageExpr *ME) const {
1076 // Generate the bug report.
Devin Coughlin9f21f682015-09-23 21:43:21 +00001077 BR.EmitBasicReport(MD, Checker, "Context Missing",
1078 "Localizability Issue (Apple)",
Anna Zakse5d74ca2015-08-14 20:22:22 +00001079 "Localized string macro should include a non-empty "
1080 "comment for translators",
1081 PathDiagnosticLocation(ME, BR.getSourceManager(), DCtx));
1082}
1083
Devin Coughlin9f21f682015-09-23 21:43:21 +00001084namespace {
1085class PluralMisuseChecker : public Checker<check::ASTCodeBody> {
1086
1087 // A helper class, which walks the AST
1088 class MethodCrawler : public RecursiveASTVisitor<MethodCrawler> {
1089 BugReporter &BR;
1090 const CheckerBase *Checker;
1091 AnalysisDeclContext *AC;
1092
1093 // This functions like a stack. We push on any IfStmt or
1094 // ConditionalOperator that matches the condition
1095 // and pop it off when we leave that statement
1096 llvm::SmallVector<const clang::Stmt *, 8> MatchingStatements;
1097 // This is true when we are the direct-child of a
1098 // matching statement
1099 bool InMatchingStatement = false;
1100
1101 public:
1102 explicit MethodCrawler(BugReporter &InBR, const CheckerBase *Checker,
1103 AnalysisDeclContext *InAC)
1104 : BR(InBR), Checker(Checker), AC(InAC) {}
1105
1106 bool VisitIfStmt(const IfStmt *I);
1107 bool EndVisitIfStmt(IfStmt *I);
1108 bool TraverseIfStmt(IfStmt *x);
1109 bool VisitConditionalOperator(const ConditionalOperator *C);
1110 bool TraverseConditionalOperator(ConditionalOperator *C);
1111 bool VisitCallExpr(const CallExpr *CE);
1112 bool VisitObjCMessageExpr(const ObjCMessageExpr *ME);
1113
1114 private:
1115 void reportPluralMisuseError(const Stmt *S) const;
1116 bool isCheckingPlurality(const Expr *E) const;
1117 };
1118
1119public:
1120 void checkASTCodeBody(const Decl *D, AnalysisManager &Mgr,
1121 BugReporter &BR) const {
1122 MethodCrawler Visitor(BR, this, Mgr.getAnalysisDeclContext(D));
1123 Visitor.TraverseDecl(const_cast<Decl *>(D));
1124 }
1125};
1126} // end anonymous namespace
1127
1128// Checks the condition of the IfStmt and returns true if one
1129// of the following heuristics are met:
1130// 1) The conidtion is a variable with "singular" or "plural" in the name
1131// 2) The condition is a binary operator with 1 or 2 on the right-hand side
1132bool PluralMisuseChecker::MethodCrawler::isCheckingPlurality(
1133 const Expr *Condition) const {
1134 const BinaryOperator *BO = nullptr;
1135 // Accounts for when a VarDecl represents a BinaryOperator
1136 if (const DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(Condition)) {
1137 if (const VarDecl *VD = dyn_cast<VarDecl>(DRE->getDecl())) {
1138 const Expr *InitExpr = VD->getInit();
1139 if (InitExpr) {
1140 if (const BinaryOperator *B =
1141 dyn_cast<BinaryOperator>(InitExpr->IgnoreParenImpCasts())) {
1142 BO = B;
1143 }
1144 }
1145 if (VD->getName().lower().find("plural") != StringRef::npos ||
1146 VD->getName().lower().find("singular") != StringRef::npos) {
1147 return true;
1148 }
1149 }
1150 } else if (const BinaryOperator *B = dyn_cast<BinaryOperator>(Condition)) {
1151 BO = B;
1152 }
1153
1154 if (BO == nullptr)
1155 return false;
1156
1157 if (IntegerLiteral *IL = dyn_cast_or_null<IntegerLiteral>(
1158 BO->getRHS()->IgnoreParenImpCasts())) {
1159 llvm::APInt Value = IL->getValue();
1160 if (Value == 1 || Value == 2) {
1161 return true;
1162 }
1163 }
1164 return false;
1165}
1166
1167// A CallExpr with "LOC" in its identifier that takes in a string literal
1168// has been shown to almost always be a function that returns a localized
1169// string. Raise a diagnostic when this is in a statement that matches
1170// the condition.
1171bool PluralMisuseChecker::MethodCrawler::VisitCallExpr(const CallExpr *CE) {
1172 if (InMatchingStatement) {
1173 if (const FunctionDecl *FD = CE->getDirectCallee()) {
1174 std::string NormalizedName =
1175 StringRef(FD->getNameInfo().getAsString()).lower();
1176 if (NormalizedName.find("loc") != std::string::npos) {
1177 for (const Expr *Arg : CE->arguments()) {
1178 if (isa<ObjCStringLiteral>(Arg))
1179 reportPluralMisuseError(CE);
1180 }
1181 }
1182 }
1183 }
1184 return true;
1185}
1186
1187// The other case is for NSLocalizedString which also returns
1188// a localized string. It's a macro for the ObjCMessageExpr
1189// [NSBundle localizedStringForKey:value:table:] Raise a
1190// diagnostic when this is in a statement that matches
1191// the condition.
1192bool PluralMisuseChecker::MethodCrawler::VisitObjCMessageExpr(
1193 const ObjCMessageExpr *ME) {
1194 const ObjCInterfaceDecl *OD = ME->getReceiverInterface();
1195 if (!OD)
1196 return true;
1197
1198 const IdentifierInfo *odInfo = OD->getIdentifier();
1199
1200 if (odInfo->isStr("NSBundle") &&
1201 ME->getSelector().getAsString() == "localizedStringForKey:value:table:") {
1202 if (InMatchingStatement) {
1203 reportPluralMisuseError(ME);
1204 }
1205 }
1206 return true;
1207}
1208
1209/// Override TraverseIfStmt so we know when we are done traversing an IfStmt
1210bool PluralMisuseChecker::MethodCrawler::TraverseIfStmt(IfStmt *I) {
1211 RecursiveASTVisitor<MethodCrawler>::TraverseIfStmt(I);
1212 return EndVisitIfStmt(I);
1213}
1214
1215// EndVisit callbacks are not provided by the RecursiveASTVisitor
1216// so we override TraverseIfStmt and make a call to EndVisitIfStmt
1217// after traversing the IfStmt
1218bool PluralMisuseChecker::MethodCrawler::EndVisitIfStmt(IfStmt *I) {
1219 MatchingStatements.pop_back();
1220 if (!MatchingStatements.empty()) {
1221 if (MatchingStatements.back() != nullptr) {
1222 InMatchingStatement = true;
1223 return true;
1224 }
1225 }
1226 InMatchingStatement = false;
1227 return true;
1228}
1229
1230bool PluralMisuseChecker::MethodCrawler::VisitIfStmt(const IfStmt *I) {
1231 const Expr *Condition = I->getCond()->IgnoreParenImpCasts();
1232 if (isCheckingPlurality(Condition)) {
1233 MatchingStatements.push_back(I);
1234 InMatchingStatement = true;
1235 } else {
1236 MatchingStatements.push_back(nullptr);
1237 InMatchingStatement = false;
1238 }
1239
1240 return true;
1241}
1242
1243// Preliminary support for conditional operators.
1244bool PluralMisuseChecker::MethodCrawler::TraverseConditionalOperator(
1245 ConditionalOperator *C) {
1246 RecursiveASTVisitor<MethodCrawler>::TraverseConditionalOperator(C);
1247 MatchingStatements.pop_back();
1248 if (!MatchingStatements.empty()) {
1249 if (MatchingStatements.back() != nullptr)
1250 InMatchingStatement = true;
1251 else
1252 InMatchingStatement = false;
1253 } else {
1254 InMatchingStatement = false;
1255 }
1256 return true;
1257}
1258
1259bool PluralMisuseChecker::MethodCrawler::VisitConditionalOperator(
1260 const ConditionalOperator *C) {
1261 const Expr *Condition = C->getCond()->IgnoreParenImpCasts();
1262 if (isCheckingPlurality(Condition)) {
1263 MatchingStatements.push_back(C);
1264 InMatchingStatement = true;
1265 } else {
1266 MatchingStatements.push_back(nullptr);
1267 InMatchingStatement = false;
1268 }
1269 return true;
1270}
1271
1272void PluralMisuseChecker::MethodCrawler::reportPluralMisuseError(
1273 const Stmt *S) const {
1274 // Generate the bug report.
1275 BR.EmitBasicReport(AC->getDecl(), Checker, "Plural Misuse",
1276 "Localizability Issue (Apple)",
1277 "Plural cases are not supported accross all languages. "
1278 "Use a .stringsdict file instead",
1279 PathDiagnosticLocation(S, BR.getSourceManager(), AC));
1280}
1281
Anna Zakse5d74ca2015-08-14 20:22:22 +00001282//===----------------------------------------------------------------------===//
1283// Checker registration.
1284//===----------------------------------------------------------------------===//
1285
1286void ento::registerNonLocalizedStringChecker(CheckerManager &mgr) {
1287 NonLocalizedStringChecker *checker =
1288 mgr.registerChecker<NonLocalizedStringChecker>();
1289 checker->IsAggressive =
1290 mgr.getAnalyzerOptions().getBooleanOption("AggressiveReport", false);
1291}
1292
1293void ento::registerEmptyLocalizationContextChecker(CheckerManager &mgr) {
1294 mgr.registerChecker<EmptyLocalizationContextChecker>();
Ted Kremenek9589caf2015-08-26 03:11:31 +00001295}
Devin Coughlin9f21f682015-09-23 21:43:21 +00001296
1297void ento::registerPluralMisuseChecker(CheckerManager &mgr) {
1298 mgr.registerChecker<PluralMisuseChecker>();
Devin Coughlin0500c702015-11-04 21:33:41 +00001299}