1 //=- LocalizationChecker.cpp -------------------------------------*- C++ -*-==//
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
7 //===----------------------------------------------------------------------===//
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.
15 //===----------------------------------------------------------------------===//
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"
35 using namespace clang
;
39 struct LocalizedState
{
41 enum Kind
{ NonLocalized
, Localized
} K
;
42 LocalizedState(Kind InK
) : K(InK
) {}
45 bool isLocalized() const { return K
== Localized
; }
46 bool isNonLocalized() const { return K
== NonLocalized
; }
48 static LocalizedState
getLocalized() { return LocalizedState(Localized
); }
49 static LocalizedState
getNonLocalized() {
50 return LocalizedState(NonLocalized
);
53 // Overload the == operator
54 bool operator==(const LocalizedState
&X
) const { return K
== X
.K
; }
56 // LLVMs equivalent of a hash function
57 void Profile(llvm::FoldingSetNodeID
&ID
) const { ID
.AddInteger(K
); }
60 class NonLocalizedStringChecker
61 : public Checker
<check::PreCall
, check::PostCall
, check::PreObjCMessage
,
62 check::PostObjCMessage
,
63 check::PostStmt
<ObjCStringLiteral
>> {
65 mutable std::unique_ptr
<BugType
> BT
;
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
;
75 void initUIMethods(ASTContext
&Ctx
) const;
76 void initLocStringsMethods(ASTContext
&Ctx
) const;
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;
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;
88 int getLocalizedArgumentForSelector(const IdentifierInfo
*Receiver
,
92 NonLocalizedStringChecker();
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;
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;
106 } // end anonymous namespace
108 REGISTER_MAP_WITH_PROGRAMSTATE(LocalizedMemMap
, const MemRegion
*,
111 NonLocalizedStringChecker::NonLocalizedStringChecker() {
112 BT
.reset(new BugType(this, "Unlocalizable string",
113 "Localizability Issue (Apple)"));
117 class NonLocalizedStringBRVisitor final
: public BugReporterVisitor
{
119 const MemRegion
*NonLocalizedString
;
123 NonLocalizedStringBRVisitor(const MemRegion
*NonLocalizedString
)
124 : NonLocalizedString(NonLocalizedString
), Satisfied(false) {
125 assert(NonLocalizedString
);
128 PathDiagnosticPieceRef
VisitNode(const ExplodedNode
*Succ
,
129 BugReporterContext
&BRC
,
130 PathSensitiveBugReport
&BR
) override
;
132 void Profile(llvm::FoldingSetNodeID
&ID
) const override
{
133 ID
.Add(NonLocalizedString
);
136 } // End anonymous namespace.
138 #define NEW_RECEIVER(receiver) \
139 llvm::DenseMap<Selector, uint8_t> &receiver##M = \
140 UIMethods.insert({&Ctx.Idents.get(#receiver), \
141 llvm::DenseMap<Selector, uint8_t>()}) \
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 receiver##M.insert( \
148 {Ctx.Selectors.getUnarySelector(&Ctx.Idents.get(#method)), argument});
149 #define ADD_METHOD(receiver, method_list, count, argument) \
150 receiver##M.insert({Ctx.Selectors.getSelector(count, method_list), argument});
152 /// Initializes a list of methods that require a localized string
153 /// Format: {"ClassName", {{"selectorName:", LocStringArg#}, ...}, ...}
154 void NonLocalizedStringChecker::initUIMethods(ASTContext
&Ctx
) const {
155 if (!UIMethods
.empty())
159 NEW_RECEIVER(UISearchDisplayController
)
160 ADD_UNARY_METHOD(UISearchDisplayController
, setSearchResultsTitle
, 0)
162 NEW_RECEIVER(UITabBarItem
)
163 IdentifierInfo
*initWithTitleUITabBarItemTag
[] = {
164 &Ctx
.Idents
.get("initWithTitle"), &Ctx
.Idents
.get("image"),
165 &Ctx
.Idents
.get("tag")};
166 ADD_METHOD(UITabBarItem
, initWithTitleUITabBarItemTag
, 3, 0)
167 IdentifierInfo
*initWithTitleUITabBarItemImage
[] = {
168 &Ctx
.Idents
.get("initWithTitle"), &Ctx
.Idents
.get("image"),
169 &Ctx
.Idents
.get("selectedImage")};
170 ADD_METHOD(UITabBarItem
, initWithTitleUITabBarItemImage
, 3, 0)
172 NEW_RECEIVER(NSDockTile
)
173 ADD_UNARY_METHOD(NSDockTile
, setBadgeLabel
, 0)
175 NEW_RECEIVER(NSStatusItem
)
176 ADD_UNARY_METHOD(NSStatusItem
, setTitle
, 0)
177 ADD_UNARY_METHOD(NSStatusItem
, setToolTip
, 0)
179 NEW_RECEIVER(UITableViewRowAction
)
180 IdentifierInfo
*rowActionWithStyleUITableViewRowAction
[] = {
181 &Ctx
.Idents
.get("rowActionWithStyle"), &Ctx
.Idents
.get("title"),
182 &Ctx
.Idents
.get("handler")};
183 ADD_METHOD(UITableViewRowAction
, rowActionWithStyleUITableViewRowAction
, 3, 1)
184 ADD_UNARY_METHOD(UITableViewRowAction
, setTitle
, 0)
187 ADD_UNARY_METHOD(NSBox
, setTitle
, 0)
189 NEW_RECEIVER(NSButton
)
190 ADD_UNARY_METHOD(NSButton
, setTitle
, 0)
191 ADD_UNARY_METHOD(NSButton
, setAlternateTitle
, 0)
192 IdentifierInfo
*radioButtonWithTitleNSButton
[] = {
193 &Ctx
.Idents
.get("radioButtonWithTitle"), &Ctx
.Idents
.get("target"),
194 &Ctx
.Idents
.get("action")};
195 ADD_METHOD(NSButton
, radioButtonWithTitleNSButton
, 3, 0)
196 IdentifierInfo
*buttonWithTitleNSButtonImage
[] = {
197 &Ctx
.Idents
.get("buttonWithTitle"), &Ctx
.Idents
.get("image"),
198 &Ctx
.Idents
.get("target"), &Ctx
.Idents
.get("action")};
199 ADD_METHOD(NSButton
, buttonWithTitleNSButtonImage
, 4, 0)
200 IdentifierInfo
*checkboxWithTitleNSButton
[] = {
201 &Ctx
.Idents
.get("checkboxWithTitle"), &Ctx
.Idents
.get("target"),
202 &Ctx
.Idents
.get("action")};
203 ADD_METHOD(NSButton
, checkboxWithTitleNSButton
, 3, 0)
204 IdentifierInfo
*buttonWithTitleNSButtonTarget
[] = {
205 &Ctx
.Idents
.get("buttonWithTitle"), &Ctx
.Idents
.get("target"),
206 &Ctx
.Idents
.get("action")};
207 ADD_METHOD(NSButton
, buttonWithTitleNSButtonTarget
, 3, 0)
209 NEW_RECEIVER(NSSavePanel
)
210 ADD_UNARY_METHOD(NSSavePanel
, setPrompt
, 0)
211 ADD_UNARY_METHOD(NSSavePanel
, setTitle
, 0)
212 ADD_UNARY_METHOD(NSSavePanel
, setNameFieldLabel
, 0)
213 ADD_UNARY_METHOD(NSSavePanel
, setNameFieldStringValue
, 0)
214 ADD_UNARY_METHOD(NSSavePanel
, setMessage
, 0)
216 NEW_RECEIVER(UIPrintInfo
)
217 ADD_UNARY_METHOD(UIPrintInfo
, setJobName
, 0)
219 NEW_RECEIVER(NSTabViewItem
)
220 ADD_UNARY_METHOD(NSTabViewItem
, setLabel
, 0)
221 ADD_UNARY_METHOD(NSTabViewItem
, setToolTip
, 0)
223 NEW_RECEIVER(NSBrowser
)
224 IdentifierInfo
*setTitleNSBrowser
[] = {&Ctx
.Idents
.get("setTitle"),
225 &Ctx
.Idents
.get("ofColumn")};
226 ADD_METHOD(NSBrowser
, setTitleNSBrowser
, 2, 0)
228 NEW_RECEIVER(UIAccessibilityElement
)
229 ADD_UNARY_METHOD(UIAccessibilityElement
, setAccessibilityLabel
, 0)
230 ADD_UNARY_METHOD(UIAccessibilityElement
, setAccessibilityHint
, 0)
231 ADD_UNARY_METHOD(UIAccessibilityElement
, setAccessibilityValue
, 0)
233 NEW_RECEIVER(UIAlertAction
)
234 IdentifierInfo
*actionWithTitleUIAlertAction
[] = {
235 &Ctx
.Idents
.get("actionWithTitle"), &Ctx
.Idents
.get("style"),
236 &Ctx
.Idents
.get("handler")};
237 ADD_METHOD(UIAlertAction
, actionWithTitleUIAlertAction
, 3, 0)
239 NEW_RECEIVER(NSPopUpButton
)
240 ADD_UNARY_METHOD(NSPopUpButton
, addItemWithTitle
, 0)
241 IdentifierInfo
*insertItemWithTitleNSPopUpButton
[] = {
242 &Ctx
.Idents
.get("insertItemWithTitle"), &Ctx
.Idents
.get("atIndex")};
243 ADD_METHOD(NSPopUpButton
, insertItemWithTitleNSPopUpButton
, 2, 0)
244 ADD_UNARY_METHOD(NSPopUpButton
, removeItemWithTitle
, 0)
245 ADD_UNARY_METHOD(NSPopUpButton
, selectItemWithTitle
, 0)
246 ADD_UNARY_METHOD(NSPopUpButton
, setTitle
, 0)
248 NEW_RECEIVER(NSTableViewRowAction
)
249 IdentifierInfo
*rowActionWithStyleNSTableViewRowAction
[] = {
250 &Ctx
.Idents
.get("rowActionWithStyle"), &Ctx
.Idents
.get("title"),
251 &Ctx
.Idents
.get("handler")};
252 ADD_METHOD(NSTableViewRowAction
, rowActionWithStyleNSTableViewRowAction
, 3, 1)
253 ADD_UNARY_METHOD(NSTableViewRowAction
, setTitle
, 0)
255 NEW_RECEIVER(NSImage
)
256 ADD_UNARY_METHOD(NSImage
, setAccessibilityDescription
, 0)
258 NEW_RECEIVER(NSUserActivity
)
259 ADD_UNARY_METHOD(NSUserActivity
, setTitle
, 0)
261 NEW_RECEIVER(NSPathControlItem
)
262 ADD_UNARY_METHOD(NSPathControlItem
, setTitle
, 0)
265 ADD_UNARY_METHOD(NSCell
, initTextCell
, 0)
266 ADD_UNARY_METHOD(NSCell
, setTitle
, 0)
267 ADD_UNARY_METHOD(NSCell
, setStringValue
, 0)
269 NEW_RECEIVER(NSPathControl
)
270 ADD_UNARY_METHOD(NSPathControl
, setPlaceholderString
, 0)
272 NEW_RECEIVER(UIAccessibility
)
273 ADD_UNARY_METHOD(UIAccessibility
, setAccessibilityLabel
, 0)
274 ADD_UNARY_METHOD(UIAccessibility
, setAccessibilityHint
, 0)
275 ADD_UNARY_METHOD(UIAccessibility
, setAccessibilityValue
, 0)
277 NEW_RECEIVER(NSTableColumn
)
278 ADD_UNARY_METHOD(NSTableColumn
, setTitle
, 0)
279 ADD_UNARY_METHOD(NSTableColumn
, setHeaderToolTip
, 0)
281 NEW_RECEIVER(NSSegmentedControl
)
282 IdentifierInfo
*setLabelNSSegmentedControl
[] = {
283 &Ctx
.Idents
.get("setLabel"), &Ctx
.Idents
.get("forSegment")};
284 ADD_METHOD(NSSegmentedControl
, setLabelNSSegmentedControl
, 2, 0)
285 IdentifierInfo
*setToolTipNSSegmentedControl
[] = {
286 &Ctx
.Idents
.get("setToolTip"), &Ctx
.Idents
.get("forSegment")};
287 ADD_METHOD(NSSegmentedControl
, setToolTipNSSegmentedControl
, 2, 0)
289 NEW_RECEIVER(NSButtonCell
)
290 ADD_UNARY_METHOD(NSButtonCell
, setTitle
, 0)
291 ADD_UNARY_METHOD(NSButtonCell
, setAlternateTitle
, 0)
293 NEW_RECEIVER(NSDatePickerCell
)
294 ADD_UNARY_METHOD(NSDatePickerCell
, initTextCell
, 0)
296 NEW_RECEIVER(NSSliderCell
)
297 ADD_UNARY_METHOD(NSSliderCell
, setTitle
, 0)
299 NEW_RECEIVER(NSControl
)
300 ADD_UNARY_METHOD(NSControl
, setStringValue
, 0)
302 NEW_RECEIVER(NSAccessibility
)
303 ADD_UNARY_METHOD(NSAccessibility
, setAccessibilityValueDescription
, 0)
304 ADD_UNARY_METHOD(NSAccessibility
, setAccessibilityLabel
, 0)
305 ADD_UNARY_METHOD(NSAccessibility
, setAccessibilityTitle
, 0)
306 ADD_UNARY_METHOD(NSAccessibility
, setAccessibilityPlaceholderValue
, 0)
307 ADD_UNARY_METHOD(NSAccessibility
, setAccessibilityHelp
, 0)
309 NEW_RECEIVER(NSMatrix
)
310 IdentifierInfo
*setToolTipNSMatrix
[] = {&Ctx
.Idents
.get("setToolTip"),
311 &Ctx
.Idents
.get("forCell")};
312 ADD_METHOD(NSMatrix
, setToolTipNSMatrix
, 2, 0)
314 NEW_RECEIVER(NSPrintPanel
)
315 ADD_UNARY_METHOD(NSPrintPanel
, setDefaultButtonTitle
, 0)
317 NEW_RECEIVER(UILocalNotification
)
318 ADD_UNARY_METHOD(UILocalNotification
, setAlertBody
, 0)
319 ADD_UNARY_METHOD(UILocalNotification
, setAlertAction
, 0)
320 ADD_UNARY_METHOD(UILocalNotification
, setAlertTitle
, 0)
322 NEW_RECEIVER(NSSlider
)
323 ADD_UNARY_METHOD(NSSlider
, setTitle
, 0)
325 NEW_RECEIVER(UIMenuItem
)
326 IdentifierInfo
*initWithTitleUIMenuItem
[] = {&Ctx
.Idents
.get("initWithTitle"),
327 &Ctx
.Idents
.get("action")};
328 ADD_METHOD(UIMenuItem
, initWithTitleUIMenuItem
, 2, 0)
329 ADD_UNARY_METHOD(UIMenuItem
, setTitle
, 0)
331 NEW_RECEIVER(UIAlertController
)
332 IdentifierInfo
*alertControllerWithTitleUIAlertController
[] = {
333 &Ctx
.Idents
.get("alertControllerWithTitle"), &Ctx
.Idents
.get("message"),
334 &Ctx
.Idents
.get("preferredStyle")};
335 ADD_METHOD(UIAlertController
, alertControllerWithTitleUIAlertController
, 3, 1)
336 ADD_UNARY_METHOD(UIAlertController
, setTitle
, 0)
337 ADD_UNARY_METHOD(UIAlertController
, setMessage
, 0)
339 NEW_RECEIVER(UIApplicationShortcutItem
)
340 IdentifierInfo
*initWithTypeUIApplicationShortcutItemIcon
[] = {
341 &Ctx
.Idents
.get("initWithType"), &Ctx
.Idents
.get("localizedTitle"),
342 &Ctx
.Idents
.get("localizedSubtitle"), &Ctx
.Idents
.get("icon"),
343 &Ctx
.Idents
.get("userInfo")};
344 ADD_METHOD(UIApplicationShortcutItem
,
345 initWithTypeUIApplicationShortcutItemIcon
, 5, 1)
346 IdentifierInfo
*initWithTypeUIApplicationShortcutItem
[] = {
347 &Ctx
.Idents
.get("initWithType"), &Ctx
.Idents
.get("localizedTitle")};
348 ADD_METHOD(UIApplicationShortcutItem
, initWithTypeUIApplicationShortcutItem
,
351 NEW_RECEIVER(UIActionSheet
)
352 IdentifierInfo
*initWithTitleUIActionSheet
[] = {
353 &Ctx
.Idents
.get("initWithTitle"), &Ctx
.Idents
.get("delegate"),
354 &Ctx
.Idents
.get("cancelButtonTitle"),
355 &Ctx
.Idents
.get("destructiveButtonTitle"),
356 &Ctx
.Idents
.get("otherButtonTitles")};
357 ADD_METHOD(UIActionSheet
, initWithTitleUIActionSheet
, 5, 0)
358 ADD_UNARY_METHOD(UIActionSheet
, addButtonWithTitle
, 0)
359 ADD_UNARY_METHOD(UIActionSheet
, setTitle
, 0)
361 NEW_RECEIVER(UIAccessibilityCustomAction
)
362 IdentifierInfo
*initWithNameUIAccessibilityCustomAction
[] = {
363 &Ctx
.Idents
.get("initWithName"), &Ctx
.Idents
.get("target"),
364 &Ctx
.Idents
.get("selector")};
365 ADD_METHOD(UIAccessibilityCustomAction
,
366 initWithNameUIAccessibilityCustomAction
, 3, 0)
367 ADD_UNARY_METHOD(UIAccessibilityCustomAction
, setName
, 0)
369 NEW_RECEIVER(UISearchBar
)
370 ADD_UNARY_METHOD(UISearchBar
, setText
, 0)
371 ADD_UNARY_METHOD(UISearchBar
, setPrompt
, 0)
372 ADD_UNARY_METHOD(UISearchBar
, setPlaceholder
, 0)
374 NEW_RECEIVER(UIBarItem
)
375 ADD_UNARY_METHOD(UIBarItem
, setTitle
, 0)
377 NEW_RECEIVER(UITextView
)
378 ADD_UNARY_METHOD(UITextView
, setText
, 0)
381 ADD_UNARY_METHOD(NSView
, setToolTip
, 0)
383 NEW_RECEIVER(NSTextField
)
384 ADD_UNARY_METHOD(NSTextField
, setPlaceholderString
, 0)
385 ADD_UNARY_METHOD(NSTextField
, textFieldWithString
, 0)
386 ADD_UNARY_METHOD(NSTextField
, wrappingLabelWithString
, 0)
387 ADD_UNARY_METHOD(NSTextField
, labelWithString
, 0)
389 NEW_RECEIVER(NSAttributedString
)
390 ADD_UNARY_METHOD(NSAttributedString
, initWithString
, 0)
391 IdentifierInfo
*initWithStringNSAttributedString
[] = {
392 &Ctx
.Idents
.get("initWithString"), &Ctx
.Idents
.get("attributes")};
393 ADD_METHOD(NSAttributedString
, initWithStringNSAttributedString
, 2, 0)
396 ADD_UNARY_METHOD(NSText
, setString
, 0)
398 NEW_RECEIVER(UIKeyCommand
)
399 IdentifierInfo
*keyCommandWithInputUIKeyCommand
[] = {
400 &Ctx
.Idents
.get("keyCommandWithInput"), &Ctx
.Idents
.get("modifierFlags"),
401 &Ctx
.Idents
.get("action"), &Ctx
.Idents
.get("discoverabilityTitle")};
402 ADD_METHOD(UIKeyCommand
, keyCommandWithInputUIKeyCommand
, 4, 3)
403 ADD_UNARY_METHOD(UIKeyCommand
, setDiscoverabilityTitle
, 0)
405 NEW_RECEIVER(UILabel
)
406 ADD_UNARY_METHOD(UILabel
, setText
, 0)
408 NEW_RECEIVER(NSAlert
)
409 IdentifierInfo
*alertWithMessageTextNSAlert
[] = {
410 &Ctx
.Idents
.get("alertWithMessageText"), &Ctx
.Idents
.get("defaultButton"),
411 &Ctx
.Idents
.get("alternateButton"), &Ctx
.Idents
.get("otherButton"),
412 &Ctx
.Idents
.get("informativeTextWithFormat")};
413 ADD_METHOD(NSAlert
, alertWithMessageTextNSAlert
, 5, 0)
414 ADD_UNARY_METHOD(NSAlert
, addButtonWithTitle
, 0)
415 ADD_UNARY_METHOD(NSAlert
, setMessageText
, 0)
416 ADD_UNARY_METHOD(NSAlert
, setInformativeText
, 0)
417 ADD_UNARY_METHOD(NSAlert
, setHelpAnchor
, 0)
419 NEW_RECEIVER(UIMutableApplicationShortcutItem
)
420 ADD_UNARY_METHOD(UIMutableApplicationShortcutItem
, setLocalizedTitle
, 0)
421 ADD_UNARY_METHOD(UIMutableApplicationShortcutItem
, setLocalizedSubtitle
, 0)
423 NEW_RECEIVER(UIButton
)
424 IdentifierInfo
*setTitleUIButton
[] = {&Ctx
.Idents
.get("setTitle"),
425 &Ctx
.Idents
.get("forState")};
426 ADD_METHOD(UIButton
, setTitleUIButton
, 2, 0)
428 NEW_RECEIVER(NSWindow
)
429 ADD_UNARY_METHOD(NSWindow
, setTitle
, 0)
430 IdentifierInfo
*minFrameWidthWithTitleNSWindow
[] = {
431 &Ctx
.Idents
.get("minFrameWidthWithTitle"), &Ctx
.Idents
.get("styleMask")};
432 ADD_METHOD(NSWindow
, minFrameWidthWithTitleNSWindow
, 2, 0)
433 ADD_UNARY_METHOD(NSWindow
, setMiniwindowTitle
, 0)
435 NEW_RECEIVER(NSPathCell
)
436 ADD_UNARY_METHOD(NSPathCell
, setPlaceholderString
, 0)
438 NEW_RECEIVER(UIDocumentMenuViewController
)
439 IdentifierInfo
*addOptionWithTitleUIDocumentMenuViewController
[] = {
440 &Ctx
.Idents
.get("addOptionWithTitle"), &Ctx
.Idents
.get("image"),
441 &Ctx
.Idents
.get("order"), &Ctx
.Idents
.get("handler")};
442 ADD_METHOD(UIDocumentMenuViewController
,
443 addOptionWithTitleUIDocumentMenuViewController
, 4, 0)
445 NEW_RECEIVER(UINavigationItem
)
446 ADD_UNARY_METHOD(UINavigationItem
, initWithTitle
, 0)
447 ADD_UNARY_METHOD(UINavigationItem
, setTitle
, 0)
448 ADD_UNARY_METHOD(UINavigationItem
, setPrompt
, 0)
450 NEW_RECEIVER(UIAlertView
)
451 IdentifierInfo
*initWithTitleUIAlertView
[] = {
452 &Ctx
.Idents
.get("initWithTitle"), &Ctx
.Idents
.get("message"),
453 &Ctx
.Idents
.get("delegate"), &Ctx
.Idents
.get("cancelButtonTitle"),
454 &Ctx
.Idents
.get("otherButtonTitles")};
455 ADD_METHOD(UIAlertView
, initWithTitleUIAlertView
, 5, 0)
456 ADD_UNARY_METHOD(UIAlertView
, addButtonWithTitle
, 0)
457 ADD_UNARY_METHOD(UIAlertView
, setTitle
, 0)
458 ADD_UNARY_METHOD(UIAlertView
, setMessage
, 0)
460 NEW_RECEIVER(NSFormCell
)
461 ADD_UNARY_METHOD(NSFormCell
, initTextCell
, 0)
462 ADD_UNARY_METHOD(NSFormCell
, setTitle
, 0)
463 ADD_UNARY_METHOD(NSFormCell
, setPlaceholderString
, 0)
465 NEW_RECEIVER(NSUserNotification
)
466 ADD_UNARY_METHOD(NSUserNotification
, setTitle
, 0)
467 ADD_UNARY_METHOD(NSUserNotification
, setSubtitle
, 0)
468 ADD_UNARY_METHOD(NSUserNotification
, setInformativeText
, 0)
469 ADD_UNARY_METHOD(NSUserNotification
, setActionButtonTitle
, 0)
470 ADD_UNARY_METHOD(NSUserNotification
, setOtherButtonTitle
, 0)
471 ADD_UNARY_METHOD(NSUserNotification
, setResponsePlaceholder
, 0)
473 NEW_RECEIVER(NSToolbarItem
)
474 ADD_UNARY_METHOD(NSToolbarItem
, setLabel
, 0)
475 ADD_UNARY_METHOD(NSToolbarItem
, setPaletteLabel
, 0)
476 ADD_UNARY_METHOD(NSToolbarItem
, setToolTip
, 0)
478 NEW_RECEIVER(NSProgress
)
479 ADD_UNARY_METHOD(NSProgress
, setLocalizedDescription
, 0)
480 ADD_UNARY_METHOD(NSProgress
, setLocalizedAdditionalDescription
, 0)
482 NEW_RECEIVER(NSSegmentedCell
)
483 IdentifierInfo
*setLabelNSSegmentedCell
[] = {&Ctx
.Idents
.get("setLabel"),
484 &Ctx
.Idents
.get("forSegment")};
485 ADD_METHOD(NSSegmentedCell
, setLabelNSSegmentedCell
, 2, 0)
486 IdentifierInfo
*setToolTipNSSegmentedCell
[] = {&Ctx
.Idents
.get("setToolTip"),
487 &Ctx
.Idents
.get("forSegment")};
488 ADD_METHOD(NSSegmentedCell
, setToolTipNSSegmentedCell
, 2, 0)
490 NEW_RECEIVER(NSUndoManager
)
491 ADD_UNARY_METHOD(NSUndoManager
, setActionName
, 0)
492 ADD_UNARY_METHOD(NSUndoManager
, undoMenuTitleForUndoActionName
, 0)
493 ADD_UNARY_METHOD(NSUndoManager
, redoMenuTitleForUndoActionName
, 0)
495 NEW_RECEIVER(NSMenuItem
)
496 IdentifierInfo
*initWithTitleNSMenuItem
[] = {
497 &Ctx
.Idents
.get("initWithTitle"), &Ctx
.Idents
.get("action"),
498 &Ctx
.Idents
.get("keyEquivalent")};
499 ADD_METHOD(NSMenuItem
, initWithTitleNSMenuItem
, 3, 0)
500 ADD_UNARY_METHOD(NSMenuItem
, setTitle
, 0)
501 ADD_UNARY_METHOD(NSMenuItem
, setToolTip
, 0)
503 NEW_RECEIVER(NSPopUpButtonCell
)
504 IdentifierInfo
*initTextCellNSPopUpButtonCell
[] = {
505 &Ctx
.Idents
.get("initTextCell"), &Ctx
.Idents
.get("pullsDown")};
506 ADD_METHOD(NSPopUpButtonCell
, initTextCellNSPopUpButtonCell
, 2, 0)
507 ADD_UNARY_METHOD(NSPopUpButtonCell
, addItemWithTitle
, 0)
508 IdentifierInfo
*insertItemWithTitleNSPopUpButtonCell
[] = {
509 &Ctx
.Idents
.get("insertItemWithTitle"), &Ctx
.Idents
.get("atIndex")};
510 ADD_METHOD(NSPopUpButtonCell
, insertItemWithTitleNSPopUpButtonCell
, 2, 0)
511 ADD_UNARY_METHOD(NSPopUpButtonCell
, removeItemWithTitle
, 0)
512 ADD_UNARY_METHOD(NSPopUpButtonCell
, selectItemWithTitle
, 0)
513 ADD_UNARY_METHOD(NSPopUpButtonCell
, setTitle
, 0)
515 NEW_RECEIVER(NSViewController
)
516 ADD_UNARY_METHOD(NSViewController
, setTitle
, 0)
519 ADD_UNARY_METHOD(NSMenu
, initWithTitle
, 0)
520 IdentifierInfo
*insertItemWithTitleNSMenu
[] = {
521 &Ctx
.Idents
.get("insertItemWithTitle"), &Ctx
.Idents
.get("action"),
522 &Ctx
.Idents
.get("keyEquivalent"), &Ctx
.Idents
.get("atIndex")};
523 ADD_METHOD(NSMenu
, insertItemWithTitleNSMenu
, 4, 0)
524 IdentifierInfo
*addItemWithTitleNSMenu
[] = {
525 &Ctx
.Idents
.get("addItemWithTitle"), &Ctx
.Idents
.get("action"),
526 &Ctx
.Idents
.get("keyEquivalent")};
527 ADD_METHOD(NSMenu
, addItemWithTitleNSMenu
, 3, 0)
528 ADD_UNARY_METHOD(NSMenu
, setTitle
, 0)
530 NEW_RECEIVER(UIMutableUserNotificationAction
)
531 ADD_UNARY_METHOD(UIMutableUserNotificationAction
, setTitle
, 0)
534 ADD_UNARY_METHOD(NSForm
, addEntry
, 0)
535 IdentifierInfo
*insertEntryNSForm
[] = {&Ctx
.Idents
.get("insertEntry"),
536 &Ctx
.Idents
.get("atIndex")};
537 ADD_METHOD(NSForm
, insertEntryNSForm
, 2, 0)
539 NEW_RECEIVER(NSTextFieldCell
)
540 ADD_UNARY_METHOD(NSTextFieldCell
, setPlaceholderString
, 0)
542 NEW_RECEIVER(NSUserNotificationAction
)
543 IdentifierInfo
*actionWithIdentifierNSUserNotificationAction
[] = {
544 &Ctx
.Idents
.get("actionWithIdentifier"), &Ctx
.Idents
.get("title")};
545 ADD_METHOD(NSUserNotificationAction
,
546 actionWithIdentifierNSUserNotificationAction
, 2, 1)
548 NEW_RECEIVER(UITextField
)
549 ADD_UNARY_METHOD(UITextField
, setText
, 0)
550 ADD_UNARY_METHOD(UITextField
, setPlaceholder
, 0)
552 NEW_RECEIVER(UIBarButtonItem
)
553 IdentifierInfo
*initWithTitleUIBarButtonItem
[] = {
554 &Ctx
.Idents
.get("initWithTitle"), &Ctx
.Idents
.get("style"),
555 &Ctx
.Idents
.get("target"), &Ctx
.Idents
.get("action")};
556 ADD_METHOD(UIBarButtonItem
, initWithTitleUIBarButtonItem
, 4, 0)
558 NEW_RECEIVER(UIViewController
)
559 ADD_UNARY_METHOD(UIViewController
, setTitle
, 0)
561 NEW_RECEIVER(UISegmentedControl
)
562 IdentifierInfo
*insertSegmentWithTitleUISegmentedControl
[] = {
563 &Ctx
.Idents
.get("insertSegmentWithTitle"), &Ctx
.Idents
.get("atIndex"),
564 &Ctx
.Idents
.get("animated")};
565 ADD_METHOD(UISegmentedControl
, insertSegmentWithTitleUISegmentedControl
, 3, 0)
566 IdentifierInfo
*setTitleUISegmentedControl
[] = {
567 &Ctx
.Idents
.get("setTitle"), &Ctx
.Idents
.get("forSegmentAtIndex")};
568 ADD_METHOD(UISegmentedControl
, setTitleUISegmentedControl
, 2, 0)
570 NEW_RECEIVER(NSAccessibilityCustomRotorItemResult
)
572 *initWithItemLoadingTokenNSAccessibilityCustomRotorItemResult
[] = {
573 &Ctx
.Idents
.get("initWithItemLoadingToken"),
574 &Ctx
.Idents
.get("customLabel")};
575 ADD_METHOD(NSAccessibilityCustomRotorItemResult
,
576 initWithItemLoadingTokenNSAccessibilityCustomRotorItemResult
, 2, 1)
577 ADD_UNARY_METHOD(NSAccessibilityCustomRotorItemResult
, setCustomLabel
, 0)
579 NEW_RECEIVER(UIContextualAction
)
580 IdentifierInfo
*contextualActionWithStyleUIContextualAction
[] = {
581 &Ctx
.Idents
.get("contextualActionWithStyle"), &Ctx
.Idents
.get("title"),
582 &Ctx
.Idents
.get("handler")};
583 ADD_METHOD(UIContextualAction
, contextualActionWithStyleUIContextualAction
, 3,
585 ADD_UNARY_METHOD(UIContextualAction
, setTitle
, 0)
587 NEW_RECEIVER(NSAccessibilityCustomRotor
)
588 IdentifierInfo
*initWithLabelNSAccessibilityCustomRotor
[] = {
589 &Ctx
.Idents
.get("initWithLabel"), &Ctx
.Idents
.get("itemSearchDelegate")};
590 ADD_METHOD(NSAccessibilityCustomRotor
,
591 initWithLabelNSAccessibilityCustomRotor
, 2, 0)
592 ADD_UNARY_METHOD(NSAccessibilityCustomRotor
, setLabel
, 0)
594 NEW_RECEIVER(NSWindowTab
)
595 ADD_UNARY_METHOD(NSWindowTab
, setTitle
, 0)
596 ADD_UNARY_METHOD(NSWindowTab
, setToolTip
, 0)
598 NEW_RECEIVER(NSAccessibilityCustomAction
)
599 IdentifierInfo
*initWithNameNSAccessibilityCustomAction
[] = {
600 &Ctx
.Idents
.get("initWithName"), &Ctx
.Idents
.get("handler")};
601 ADD_METHOD(NSAccessibilityCustomAction
,
602 initWithNameNSAccessibilityCustomAction
, 2, 0)
603 IdentifierInfo
*initWithNameTargetNSAccessibilityCustomAction
[] = {
604 &Ctx
.Idents
.get("initWithName"), &Ctx
.Idents
.get("target"),
605 &Ctx
.Idents
.get("selector")};
606 ADD_METHOD(NSAccessibilityCustomAction
,
607 initWithNameTargetNSAccessibilityCustomAction
, 3, 0)
608 ADD_UNARY_METHOD(NSAccessibilityCustomAction
, setName
, 0)
611 #define LSF_INSERT(function_name) LSF.insert(&Ctx.Idents.get(function_name));
612 #define LSM_INSERT_NULLARY(receiver, method_name) \
613 LSM.insert({&Ctx.Idents.get(receiver), Ctx.Selectors.getNullarySelector( \
614 &Ctx.Idents.get(method_name))});
615 #define LSM_INSERT_UNARY(receiver, method_name) \
616 LSM.insert({&Ctx.Idents.get(receiver), \
617 Ctx.Selectors.getUnarySelector(&Ctx.Idents.get(method_name))});
618 #define LSM_INSERT_SELECTOR(receiver, method_list, arguments) \
619 LSM.insert({&Ctx.Idents.get(receiver), \
620 Ctx.Selectors.getSelector(arguments, method_list)});
622 /// Initializes a list of methods and C functions that return a localized string
623 void NonLocalizedStringChecker::initLocStringsMethods(ASTContext
&Ctx
) const {
627 IdentifierInfo
*LocalizedStringMacro
[] = {
628 &Ctx
.Idents
.get("localizedStringForKey"), &Ctx
.Idents
.get("value"),
629 &Ctx
.Idents
.get("table")};
630 LSM_INSERT_SELECTOR("NSBundle", LocalizedStringMacro
, 3)
631 LSM_INSERT_UNARY("NSDateFormatter", "stringFromDate")
632 IdentifierInfo
*LocalizedStringFromDate
[] = {
633 &Ctx
.Idents
.get("localizedStringFromDate"), &Ctx
.Idents
.get("dateStyle"),
634 &Ctx
.Idents
.get("timeStyle")};
635 LSM_INSERT_SELECTOR("NSDateFormatter", LocalizedStringFromDate
, 3)
636 LSM_INSERT_UNARY("NSNumberFormatter", "stringFromNumber")
637 LSM_INSERT_NULLARY("UITextField", "text")
638 LSM_INSERT_NULLARY("UITextView", "text")
639 LSM_INSERT_NULLARY("UILabel", "text")
641 LSF_INSERT("CFDateFormatterCreateStringWithDate");
642 LSF_INSERT("CFDateFormatterCreateStringWithAbsoluteTime");
643 LSF_INSERT("CFNumberFormatterCreateStringWithNumber");
646 /// Checks to see if the method / function declaration includes
647 /// __attribute__((annotate("returns_localized_nsstring")))
648 bool NonLocalizedStringChecker::isAnnotatedAsReturningLocalized(
649 const Decl
*D
) const {
653 D
->specific_attr_begin
<AnnotateAttr
>(),
654 D
->specific_attr_end
<AnnotateAttr
>(), [](const AnnotateAttr
*Ann
) {
655 return Ann
->getAnnotation() == "returns_localized_nsstring";
659 /// Checks to see if the method / function declaration includes
660 /// __attribute__((annotate("takes_localized_nsstring")))
661 bool NonLocalizedStringChecker::isAnnotatedAsTakingLocalized(
662 const Decl
*D
) const {
666 D
->specific_attr_begin
<AnnotateAttr
>(),
667 D
->specific_attr_end
<AnnotateAttr
>(), [](const AnnotateAttr
*Ann
) {
668 return Ann
->getAnnotation() == "takes_localized_nsstring";
672 /// Returns true if the given SVal is marked as Localized in the program state
673 bool NonLocalizedStringChecker::hasLocalizedState(SVal S
,
674 CheckerContext
&C
) const {
675 const MemRegion
*mt
= S
.getAsRegion();
677 const LocalizedState
*LS
= C
.getState()->get
<LocalizedMemMap
>(mt
);
678 if (LS
&& LS
->isLocalized())
684 /// Returns true if the given SVal is marked as NonLocalized in the program
686 bool NonLocalizedStringChecker::hasNonLocalizedState(SVal S
,
687 CheckerContext
&C
) const {
688 const MemRegion
*mt
= S
.getAsRegion();
690 const LocalizedState
*LS
= C
.getState()->get
<LocalizedMemMap
>(mt
);
691 if (LS
&& LS
->isNonLocalized())
697 /// Marks the given SVal as Localized in the program state
698 void NonLocalizedStringChecker::setLocalizedState(const SVal S
,
699 CheckerContext
&C
) const {
700 const MemRegion
*mt
= S
.getAsRegion();
702 ProgramStateRef State
=
703 C
.getState()->set
<LocalizedMemMap
>(mt
, LocalizedState::getLocalized());
704 C
.addTransition(State
);
708 /// Marks the given SVal as NonLocalized in the program state
709 void NonLocalizedStringChecker::setNonLocalizedState(const SVal S
,
710 CheckerContext
&C
) const {
711 const MemRegion
*mt
= S
.getAsRegion();
713 ProgramStateRef State
= C
.getState()->set
<LocalizedMemMap
>(
714 mt
, LocalizedState::getNonLocalized());
715 C
.addTransition(State
);
720 static bool isDebuggingName(std::string name
) {
721 return StringRef(name
).lower().find("debug") != StringRef::npos
;
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
728 static bool isDebuggingContext(CheckerContext
&C
) {
729 const Decl
*D
= C
.getCurrentAnalysisDeclContext()->getDecl();
733 if (auto *ND
= dyn_cast
<NamedDecl
>(D
)) {
734 if (isDebuggingName(ND
->getNameAsString()))
738 const DeclContext
*DC
= D
->getDeclContext();
740 if (auto *CD
= dyn_cast
<ObjCContainerDecl
>(DC
)) {
741 if (isDebuggingName(CD
->getNameAsString()))
749 /// Reports a localization error for the passed in method call and SVal
750 void NonLocalizedStringChecker::reportLocalizationError(
751 SVal S
, const CallEvent
&M
, CheckerContext
&C
, int argumentNumber
) const {
753 // Don't warn about localization errors in classes and methods that
754 // may be debug code.
755 if (isDebuggingContext(C
))
758 static CheckerProgramPointTag
Tag("NonLocalizedStringChecker",
759 "UnlocalizedString");
760 ExplodedNode
*ErrNode
= C
.addTransition(C
.getState(), C
.getPredecessor(), &Tag
);
765 // Generate the bug report.
766 auto R
= std::make_unique
<PathSensitiveBugReport
>(
767 *BT
, "User-facing text should use localized string macro", ErrNode
);
768 if (argumentNumber
) {
769 R
->addRange(M
.getArgExpr(argumentNumber
- 1)->getSourceRange());
771 R
->addRange(M
.getSourceRange());
773 R
->markInteresting(S
);
775 const MemRegion
*StringRegion
= S
.getAsRegion();
777 R
->addVisitor(std::make_unique
<NonLocalizedStringBRVisitor
>(StringRegion
));
779 C
.emitReport(std::move(R
));
782 /// Returns the argument number requiring localized string if it exists
783 /// otherwise, returns -1
784 int NonLocalizedStringChecker::getLocalizedArgumentForSelector(
785 const IdentifierInfo
*Receiver
, Selector S
) const {
786 auto method
= UIMethods
.find(Receiver
);
788 if (method
== UIMethods
.end())
791 auto argumentIterator
= method
->getSecond().find(S
);
793 if (argumentIterator
== method
->getSecond().end())
796 int argumentNumber
= argumentIterator
->getSecond();
797 return argumentNumber
;
800 /// Check if the string being passed in has NonLocalized state
801 void NonLocalizedStringChecker::checkPreObjCMessage(const ObjCMethodCall
&msg
,
802 CheckerContext
&C
) const {
803 initUIMethods(C
.getASTContext());
805 const ObjCInterfaceDecl
*OD
= msg
.getReceiverInterface();
808 const IdentifierInfo
*odInfo
= OD
->getIdentifier();
810 Selector S
= msg
.getSelector();
812 std::string SelectorString
= S
.getAsString();
813 StringRef SelectorName
= SelectorString
;
814 assert(!SelectorName
.empty());
816 if (odInfo
->isStr("NSString")) {
817 // Handle the case where the receiver is an NSString
818 // These special NSString methods draw to the screen
820 if (!(SelectorName
.startswith("drawAtPoint") ||
821 SelectorName
.startswith("drawInRect") ||
822 SelectorName
.startswith("drawWithRect")))
825 SVal svTitle
= msg
.getReceiverSVal();
827 bool isNonLocalized
= hasNonLocalizedState(svTitle
, C
);
829 if (isNonLocalized
) {
830 reportLocalizationError(svTitle
, msg
, C
);
834 int argumentNumber
= getLocalizedArgumentForSelector(odInfo
, S
);
835 // Go up each hierarchy of superclasses and their protocols
836 while (argumentNumber
< 0 && OD
->getSuperClass() != nullptr) {
837 for (const auto *P
: OD
->all_referenced_protocols()) {
838 argumentNumber
= getLocalizedArgumentForSelector(P
->getIdentifier(), S
);
839 if (argumentNumber
>= 0)
842 if (argumentNumber
< 0) {
843 OD
= OD
->getSuperClass();
844 argumentNumber
= getLocalizedArgumentForSelector(OD
->getIdentifier(), S
);
848 if (argumentNumber
< 0) { // There was no match in UIMethods
849 if (const Decl
*D
= msg
.getDecl()) {
850 if (const ObjCMethodDecl
*OMD
= dyn_cast_or_null
<ObjCMethodDecl
>(D
)) {
851 for (auto [Idx
, FormalParam
] : llvm::enumerate(OMD
->parameters())) {
852 if (isAnnotatedAsTakingLocalized(FormalParam
)) {
853 argumentNumber
= Idx
;
861 if (argumentNumber
< 0) // Still no match
864 SVal svTitle
= msg
.getArgSVal(argumentNumber
);
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) ||
873 if (!IsAggressive
&& llvm::sys::unicode::columnWidthUTF8(stringValue
) < 2)
877 bool isNonLocalized
= hasNonLocalizedState(svTitle
, C
);
879 if (isNonLocalized
) {
880 reportLocalizationError(svTitle
, msg
, C
, argumentNumber
+ 1);
884 void NonLocalizedStringChecker::checkPreCall(const CallEvent
&Call
,
885 CheckerContext
&C
) const {
886 const auto *FD
= dyn_cast_or_null
<FunctionDecl
>(Call
.getDecl());
890 auto formals
= FD
->parameters();
891 for (unsigned i
= 0, ei
= std::min(static_cast<unsigned>(formals
.size()),
892 Call
.getNumArgs()); 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);
902 static inline bool isNSStringType(QualType T
, ASTContext
&Ctx
) {
904 const ObjCObjectPointerType
*PT
= T
->getAs
<ObjCObjectPointerType
>();
908 ObjCInterfaceDecl
*Cls
= PT
->getObjectType()->getInterface();
912 IdentifierInfo
*ClsName
= Cls
->getIdentifier();
914 // FIXME: Should we walk the chain of classes?
915 return ClsName
== &Ctx
.Idents
.get("NSString") ||
916 ClsName
== &Ctx
.Idents
.get("NSMutableString");
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 CheckerContext
&C
) const {
926 initLocStringsMethods(C
.getASTContext());
928 if (!Call
.getOriginExpr())
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 const QualType RT
= Call
.getResultType();
935 if (isNSStringType(RT
, C
.getASTContext())) {
936 for (unsigned i
= 0; i
< Call
.getNumArgs(); ++i
) {
937 SVal argValue
= Call
.getArgSVal(i
);
938 if (hasLocalizedState(argValue
, C
)) {
939 SVal sv
= Call
.getReturnValue();
940 setLocalizedState(sv
, C
);
946 const Decl
*D
= Call
.getDecl();
950 const IdentifierInfo
*Identifier
= Call
.getCalleeIdentifier();
952 SVal sv
= Call
.getReturnValue();
953 if (isAnnotatedAsReturningLocalized(D
) || LSF
.contains(Identifier
)) {
954 setLocalizedState(sv
, C
);
955 } else if (isNSStringType(RT
, C
.getASTContext()) &&
956 !hasLocalizedState(sv
, C
)) {
958 setNonLocalizedState(sv
, C
);
960 const SymbolicRegion
*SymReg
=
961 dyn_cast_or_null
<SymbolicRegion
>(sv
.getAsRegion());
963 setNonLocalizedState(sv
, C
);
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 CheckerContext
&C
) const {
972 initLocStringsMethods(C
.getASTContext());
974 if (!msg
.isInstanceMessage())
977 const ObjCInterfaceDecl
*OD
= msg
.getReceiverInterface();
980 const IdentifierInfo
*odInfo
= OD
->getIdentifier();
982 Selector S
= msg
.getSelector();
983 std::string SelectorName
= S
.getAsString();
985 std::pair
<const IdentifierInfo
*, Selector
> MethodDescription
= {odInfo
, S
};
987 if (LSM
.count(MethodDescription
) ||
988 isAnnotatedAsReturningLocalized(msg
.getDecl())) {
989 SVal sv
= msg
.getReturnValue();
990 setLocalizedState(sv
, C
);
994 /// Marks all empty string literals as localized
995 void NonLocalizedStringChecker::checkPostStmt(const ObjCStringLiteral
*SL
,
996 CheckerContext
&C
) const {
997 SVal sv
= C
.getSVal(SL
);
998 setNonLocalizedState(sv
, C
);
1001 PathDiagnosticPieceRef
1002 NonLocalizedStringBRVisitor::VisitNode(const ExplodedNode
*Succ
,
1003 BugReporterContext
&BRC
,
1004 PathSensitiveBugReport
&BR
) {
1008 std::optional
<StmtPoint
> Point
= Succ
->getLocation().getAs
<StmtPoint
>();
1012 auto *LiteralExpr
= dyn_cast
<ObjCStringLiteral
>(Point
->getStmt());
1016 SVal LiteralSVal
= Succ
->getSVal(LiteralExpr
);
1017 if (LiteralSVal
.getAsRegion() != NonLocalizedString
)
1022 PathDiagnosticLocation L
=
1023 PathDiagnosticLocation::create(*Point
, BRC
.getSourceManager());
1025 if (!L
.isValid() || !L
.asLocation().isValid())
1028 auto Piece
= std::make_shared
<PathDiagnosticEventPiece
>(
1029 L
, "Non-localized string literal here");
1030 Piece
->addRange(LiteralExpr
->getSourceRange());
1032 return std::move(Piece
);
1036 class EmptyLocalizationContextChecker
1037 : public Checker
<check::ASTDecl
<ObjCImplementationDecl
>> {
1039 // A helper class, which walks the AST
1040 class MethodCrawler
: public ConstStmtVisitor
<MethodCrawler
> {
1041 const ObjCMethodDecl
*MD
;
1043 AnalysisManager
&Mgr
;
1044 const CheckerBase
*Checker
;
1045 LocationOrAnalysisDeclContext DCtx
;
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
) {}
1053 void VisitStmt(const Stmt
*S
) { VisitChildren(S
); }
1055 void VisitObjCMessageExpr(const ObjCMessageExpr
*ME
);
1057 void reportEmptyContextError(const ObjCMessageExpr
*M
) const;
1059 void VisitChildren(const Stmt
*S
) {
1060 for (const Stmt
*Child
: S
->children()) {
1068 void checkASTDecl(const ObjCImplementationDecl
*D
, AnalysisManager
&Mgr
,
1069 BugReporter
&BR
) const;
1071 } // end anonymous namespace
1073 void EmptyLocalizationContextChecker::checkASTDecl(
1074 const ObjCImplementationDecl
*D
, AnalysisManager
&Mgr
,
1075 BugReporter
&BR
) const {
1077 for (const ObjCMethodDecl
*M
: D
->methods()) {
1078 AnalysisDeclContext
*DCtx
= Mgr
.getAnalysisDeclContext(M
);
1080 const Stmt
*Body
= M
->getBody();
1082 assert(M
->isSynthesizedAccessorStub());
1086 MethodCrawler
MC(M
->getCanonicalDecl(), BR
, this, Mgr
, DCtx
);
1091 /// This check attempts to match these macros, assuming they are defined as
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)
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 const ObjCMessageExpr
*ME
) {
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 const ObjCInterfaceDecl
*OD
= ME
->getReceiverInterface();
1115 const IdentifierInfo
*odInfo
= OD
->getIdentifier();
1117 if (!(odInfo
->isStr("NSBundle") &&
1118 ME
->getSelector().getAsString() ==
1119 "localizedStringForKey:value:table:")) {
1123 SourceRange R
= ME
->getSourceRange();
1124 if (!R
.getBegin().isMacroID())
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.
1131 Mgr
.getSourceManager().getImmediateMacroCallerLoc(R
.getBegin());
1132 std::pair
<FileID
, unsigned> SLInfo
=
1133 Mgr
.getSourceManager().getDecomposedLoc(SL
);
1135 SrcMgr::SLocEntry SE
= Mgr
.getSourceManager().getSLocEntry(SLInfo
.first
);
1137 // If NSLocalizedString macro is wrapped in another macro, we need to
1138 // unwrap the expansion until we get to the NSLocalizedStringMacro.
1139 while (SE
.isExpansion()) {
1140 SL
= SE
.getExpansion().getSpellingLoc();
1141 SLInfo
= Mgr
.getSourceManager().getDecomposedLoc(SL
);
1142 SE
= Mgr
.getSourceManager().getSLocEntry(SLInfo
.first
);
1145 std::optional
<llvm::MemoryBufferRef
> BF
=
1146 Mgr
.getSourceManager().getBufferOrNone(SLInfo
.first
, SL
);
1149 LangOptions LangOpts
;
1150 Lexer
TheLexer(SL
, LangOpts
, BF
->getBufferStart(),
1151 BF
->getBufferStart() + SLInfo
.second
, BF
->getBufferEnd());
1154 Token Result
; // This will hold the token just before the last ')'
1155 int p_count
= 0; // This is for parenthesis matching
1156 while (!TheLexer
.LexFromRawLexer(I
)) {
1157 if (I
.getKind() == tok::l_paren
)
1159 if (I
.getKind() == tok::r_paren
) {
1167 if (isAnyIdentifier(Result
.getKind())) {
1168 if (Result
.getRawIdentifier().equals("nil")) {
1169 reportEmptyContextError(ME
);
1174 if (!isStringLiteral(Result
.getKind()))
1178 StringRef(Result
.getLiteralData(), Result
.getLength()).trim('"');
1180 if ((Comment
.trim().size() == 0 && Comment
.size() > 0) || // Is Whitespace
1182 reportEmptyContextError(ME
);
1186 void EmptyLocalizationContextChecker::MethodCrawler::reportEmptyContextError(
1187 const ObjCMessageExpr
*ME
) const {
1188 // Generate the bug report.
1189 BR
.EmitBasicReport(MD
, Checker
, "Context Missing",
1190 "Localizability Issue (Apple)",
1191 "Localized string macro should include a non-empty "
1192 "comment for translators",
1193 PathDiagnosticLocation(ME
, BR
.getSourceManager(), DCtx
));
1197 class PluralMisuseChecker
: public Checker
<check::ASTCodeBody
> {
1199 // A helper class, which walks the AST
1200 class MethodCrawler
: public RecursiveASTVisitor
<MethodCrawler
> {
1202 const CheckerBase
*Checker
;
1203 AnalysisDeclContext
*AC
;
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;
1214 explicit MethodCrawler(BugReporter
&InBR
, const CheckerBase
*Checker
,
1215 AnalysisDeclContext
*InAC
)
1216 : BR(InBR
), Checker(Checker
), AC(InAC
) {}
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
);
1227 void reportPluralMisuseError(const Stmt
*S
) const;
1228 bool isCheckingPlurality(const Expr
*E
) const;
1232 void checkASTCodeBody(const Decl
*D
, AnalysisManager
&Mgr
,
1233 BugReporter
&BR
) const {
1234 MethodCrawler
Visitor(BR
, this, Mgr
.getAnalysisDeclContext(D
));
1235 Visitor
.TraverseDecl(const_cast<Decl
*>(D
));
1238 } // end anonymous namespace
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 const Expr
*Condition
) const {
1246 const BinaryOperator
*BO
= nullptr;
1247 // Accounts for when a VarDecl represents a BinaryOperator
1248 if (const DeclRefExpr
*DRE
= dyn_cast
<DeclRefExpr
>(Condition
)) {
1249 if (const VarDecl
*VD
= dyn_cast
<VarDecl
>(DRE
->getDecl())) {
1250 const Expr
*InitExpr
= VD
->getInit();
1252 if (const BinaryOperator
*B
=
1253 dyn_cast
<BinaryOperator
>(InitExpr
->IgnoreParenImpCasts())) {
1257 if (VD
->getName().lower().find("plural") != StringRef::npos
||
1258 VD
->getName().lower().find("singular") != StringRef::npos
) {
1262 } else if (const BinaryOperator
*B
= dyn_cast
<BinaryOperator
>(Condition
)) {
1269 if (IntegerLiteral
*IL
= dyn_cast_or_null
<IntegerLiteral
>(
1270 BO
->getRHS()->IgnoreParenImpCasts())) {
1271 llvm::APInt Value
= IL
->getValue();
1272 if (Value
== 1 || Value
== 2) {
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
1283 bool PluralMisuseChecker::MethodCrawler::VisitCallExpr(const CallExpr
*CE
) {
1284 if (InMatchingStatement
) {
1285 if (const FunctionDecl
*FD
= CE
->getDirectCallee()) {
1286 std::string NormalizedName
=
1287 StringRef(FD
->getNameInfo().getAsString()).lower();
1288 if (NormalizedName
.find("loc") != std::string::npos
) {
1289 for (const Expr
*Arg
: CE
->arguments()) {
1290 if (isa
<ObjCStringLiteral
>(Arg
))
1291 reportPluralMisuseError(CE
);
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
1304 bool PluralMisuseChecker::MethodCrawler::VisitObjCMessageExpr(
1305 const ObjCMessageExpr
*ME
) {
1306 const ObjCInterfaceDecl
*OD
= ME
->getReceiverInterface();
1310 const IdentifierInfo
*odInfo
= OD
->getIdentifier();
1312 if (odInfo
->isStr("NSBundle") &&
1313 ME
->getSelector().getAsString() == "localizedStringForKey:value:table:") {
1314 if (InMatchingStatement
) {
1315 reportPluralMisuseError(ME
);
1321 /// Override TraverseIfStmt so we know when we are done traversing an IfStmt
1322 bool PluralMisuseChecker::MethodCrawler::TraverseIfStmt(IfStmt
*I
) {
1323 RecursiveASTVisitor
<MethodCrawler
>::TraverseIfStmt(I
);
1324 return EndVisitIfStmt(I
);
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 bool PluralMisuseChecker::MethodCrawler::EndVisitIfStmt(IfStmt
*I
) {
1331 MatchingStatements
.pop_back();
1332 if (!MatchingStatements
.empty()) {
1333 if (MatchingStatements
.back() != nullptr) {
1334 InMatchingStatement
= true;
1338 InMatchingStatement
= false;
1342 bool PluralMisuseChecker::MethodCrawler::VisitIfStmt(const IfStmt
*I
) {
1343 const Expr
*Condition
= I
->getCond();
1346 Condition
= Condition
->IgnoreParenImpCasts();
1347 if (isCheckingPlurality(Condition
)) {
1348 MatchingStatements
.push_back(I
);
1349 InMatchingStatement
= true;
1351 MatchingStatements
.push_back(nullptr);
1352 InMatchingStatement
= false;
1358 // Preliminary support for conditional operators.
1359 bool PluralMisuseChecker::MethodCrawler::TraverseConditionalOperator(
1360 ConditionalOperator
*C
) {
1361 RecursiveASTVisitor
<MethodCrawler
>::TraverseConditionalOperator(C
);
1362 MatchingStatements
.pop_back();
1363 if (!MatchingStatements
.empty()) {
1364 if (MatchingStatements
.back() != nullptr)
1365 InMatchingStatement
= true;
1367 InMatchingStatement
= false;
1369 InMatchingStatement
= false;
1374 bool PluralMisuseChecker::MethodCrawler::VisitConditionalOperator(
1375 const ConditionalOperator
*C
) {
1376 const Expr
*Condition
= C
->getCond()->IgnoreParenImpCasts();
1377 if (isCheckingPlurality(Condition
)) {
1378 MatchingStatements
.push_back(C
);
1379 InMatchingStatement
= true;
1381 MatchingStatements
.push_back(nullptr);
1382 InMatchingStatement
= false;
1387 void PluralMisuseChecker::MethodCrawler::reportPluralMisuseError(
1388 const Stmt
*S
) const {
1389 // Generate the bug report.
1390 BR
.EmitBasicReport(AC
->getDecl(), Checker
, "Plural Misuse",
1391 "Localizability Issue (Apple)",
1392 "Plural cases are not supported across all languages. "
1393 "Use a .stringsdict file instead",
1394 PathDiagnosticLocation(S
, BR
.getSourceManager(), AC
));
1397 //===----------------------------------------------------------------------===//
1398 // Checker registration.
1399 //===----------------------------------------------------------------------===//
1401 void ento::registerNonLocalizedStringChecker(CheckerManager
&mgr
) {
1402 NonLocalizedStringChecker
*checker
=
1403 mgr
.registerChecker
<NonLocalizedStringChecker
>();
1404 checker
->IsAggressive
=
1405 mgr
.getAnalyzerOptions().getCheckerBooleanOption(
1406 checker
, "AggressiveReport");
1409 bool ento::shouldRegisterNonLocalizedStringChecker(const CheckerManager
&mgr
) {
1413 void ento::registerEmptyLocalizationContextChecker(CheckerManager
&mgr
) {
1414 mgr
.registerChecker
<EmptyLocalizationContextChecker
>();
1417 bool ento::shouldRegisterEmptyLocalizationContextChecker(
1418 const CheckerManager
&mgr
) {
1422 void ento::registerPluralMisuseChecker(CheckerManager
&mgr
) {
1423 mgr
.registerChecker
<PluralMisuseChecker
>();
1426 bool ento::shouldRegisterPluralMisuseChecker(const CheckerManager
&mgr
) {