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