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