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