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