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