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