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