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