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