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