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