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