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 623 static bool isDebuggingName(std::string name) { 624 return StringRef(name).lower().find("debug") != StringRef::npos; 625 } 626 627 /// Returns true when, heuristically, the analyzer may be analyzing debugging 628 /// code. We use this to suppress localization diagnostics in un-localized user 629 /// interfaces that are only used for debugging and are therefore not user 630 /// facing. 631 static bool isDebuggingContext(CheckerContext &C) { 632 const Decl *D = C.getCurrentAnalysisDeclContext()->getDecl(); 633 if (!D) 634 return false; 635 636 if (auto *ND = dyn_cast<NamedDecl>(D)) { 637 if (isDebuggingName(ND->getNameAsString())) 638 return true; 639 } 640 641 const DeclContext *DC = D->getDeclContext(); 642 643 if (auto *CD = dyn_cast<ObjCContainerDecl>(DC)) { 644 if (isDebuggingName(CD->getNameAsString())) 645 return true; 646 } 647 648 return false; 649 } 650 651 652 /// Reports a localization error for the passed in method call and SVal 653 void NonLocalizedStringChecker::reportLocalizationError( 654 SVal S, const ObjCMethodCall &M, CheckerContext &C, 655 int argumentNumber) const { 656 657 // Don't warn about localization errors in classes and methods that 658 // may be debug code. 659 if (isDebuggingContext(C)) 660 return; 661 662 ExplodedNode *ErrNode = C.getPredecessor(); 663 static CheckerProgramPointTag Tag("NonLocalizedStringChecker", 664 "UnlocalizedString"); 665 ErrNode = C.addTransition(C.getState(), C.getPredecessor(), &Tag); 666 667 if (!ErrNode) 668 return; 669 670 // Generate the bug report. 671 std::unique_ptr<BugReport> R(new BugReport( 672 *BT, "User-facing text should use localized string macro", ErrNode)); 673 if (argumentNumber) { 674 R->addRange(M.getArgExpr(argumentNumber - 1)->getSourceRange()); 675 } else { 676 R->addRange(M.getSourceRange()); 677 } 678 R->markInteresting(S); 679 C.emitReport(std::move(R)); 680 } 681 682 /// Returns the argument number requiring localized string if it exists 683 /// otherwise, returns -1 684 int NonLocalizedStringChecker::getLocalizedArgumentForSelector( 685 const IdentifierInfo *Receiver, Selector S) const { 686 auto method = UIMethods.find(Receiver); 687 688 if (method == UIMethods.end()) 689 return -1; 690 691 auto argumentIterator = method->getSecond().find(S); 692 693 if (argumentIterator == method->getSecond().end()) 694 return -1; 695 696 int argumentNumber = argumentIterator->getSecond(); 697 return argumentNumber; 698 } 699 700 /// Check if the string being passed in has NonLocalized state 701 void NonLocalizedStringChecker::checkPreObjCMessage(const ObjCMethodCall &msg, 702 CheckerContext &C) const { 703 initUIMethods(C.getASTContext()); 704 705 const ObjCInterfaceDecl *OD = msg.getReceiverInterface(); 706 if (!OD) 707 return; 708 const IdentifierInfo *odInfo = OD->getIdentifier(); 709 710 Selector S = msg.getSelector(); 711 712 std::string SelectorString = S.getAsString(); 713 StringRef SelectorName = SelectorString; 714 assert(!SelectorName.empty()); 715 716 if (odInfo->isStr("NSString")) { 717 // Handle the case where the receiver is an NSString 718 // These special NSString methods draw to the screen 719 720 if (!(SelectorName.startswith("drawAtPoint") || 721 SelectorName.startswith("drawInRect") || 722 SelectorName.startswith("drawWithRect"))) 723 return; 724 725 SVal svTitle = msg.getReceiverSVal(); 726 727 bool isNonLocalized = hasNonLocalizedState(svTitle, C); 728 729 if (isNonLocalized) { 730 reportLocalizationError(svTitle, msg, C); 731 } 732 } 733 734 int argumentNumber = getLocalizedArgumentForSelector(odInfo, S); 735 // Go up each hierarchy of superclasses and their protocols 736 while (argumentNumber < 0 && OD->getSuperClass() != nullptr) { 737 for (const auto *P : OD->all_referenced_protocols()) { 738 argumentNumber = getLocalizedArgumentForSelector(P->getIdentifier(), S); 739 if (argumentNumber >= 0) 740 break; 741 } 742 if (argumentNumber < 0) { 743 OD = OD->getSuperClass(); 744 argumentNumber = getLocalizedArgumentForSelector(OD->getIdentifier(), S); 745 } 746 } 747 748 if (argumentNumber < 0) // There was no match in UIMethods 749 return; 750 751 SVal svTitle = msg.getArgSVal(argumentNumber); 752 753 if (const ObjCStringRegion *SR = 754 dyn_cast_or_null<ObjCStringRegion>(svTitle.getAsRegion())) { 755 StringRef stringValue = 756 SR->getObjCStringLiteral()->getString()->getString(); 757 if ((stringValue.trim().size() == 0 && stringValue.size() > 0) || 758 stringValue.empty()) 759 return; 760 if (!IsAggressive && llvm::sys::unicode::columnWidthUTF8(stringValue) < 2) 761 return; 762 } 763 764 bool isNonLocalized = hasNonLocalizedState(svTitle, C); 765 766 if (isNonLocalized) { 767 reportLocalizationError(svTitle, msg, C, argumentNumber + 1); 768 } 769 } 770 771 static inline bool isNSStringType(QualType T, ASTContext &Ctx) { 772 773 const ObjCObjectPointerType *PT = T->getAs<ObjCObjectPointerType>(); 774 if (!PT) 775 return false; 776 777 ObjCInterfaceDecl *Cls = PT->getObjectType()->getInterface(); 778 if (!Cls) 779 return false; 780 781 IdentifierInfo *ClsName = Cls->getIdentifier(); 782 783 // FIXME: Should we walk the chain of classes? 784 return ClsName == &Ctx.Idents.get("NSString") || 785 ClsName == &Ctx.Idents.get("NSMutableString"); 786 } 787 788 /// Marks a string being returned by any call as localized 789 /// if it is in LocStringFunctions (LSF) or the function is annotated. 790 /// Otherwise, we mark it as NonLocalized (Aggressive) or 791 /// NonLocalized only if it is not backed by a SymRegion (Non-Aggressive), 792 /// basically leaving only string literals as NonLocalized. 793 void NonLocalizedStringChecker::checkPostCall(const CallEvent &Call, 794 CheckerContext &C) const { 795 initLocStringsMethods(C.getASTContext()); 796 797 if (!Call.getOriginExpr()) 798 return; 799 800 // Anything that takes in a localized NSString as an argument 801 // and returns an NSString will be assumed to be returning a 802 // localized NSString. (Counter: Incorrectly combining two LocalizedStrings) 803 const QualType RT = Call.getResultType(); 804 if (isNSStringType(RT, C.getASTContext())) { 805 for (unsigned i = 0; i < Call.getNumArgs(); ++i) { 806 SVal argValue = Call.getArgSVal(i); 807 if (hasLocalizedState(argValue, C)) { 808 SVal sv = Call.getReturnValue(); 809 setLocalizedState(sv, C); 810 return; 811 } 812 } 813 } 814 815 const Decl *D = Call.getDecl(); 816 if (!D) 817 return; 818 819 const IdentifierInfo *Identifier = Call.getCalleeIdentifier(); 820 821 SVal sv = Call.getReturnValue(); 822 if (isAnnotatedAsLocalized(D) || LSF.count(Identifier) != 0) { 823 setLocalizedState(sv, C); 824 } else if (isNSStringType(RT, C.getASTContext()) && 825 !hasLocalizedState(sv, C)) { 826 if (IsAggressive) { 827 setNonLocalizedState(sv, C); 828 } else { 829 const SymbolicRegion *SymReg = 830 dyn_cast_or_null<SymbolicRegion>(sv.getAsRegion()); 831 if (!SymReg) 832 setNonLocalizedState(sv, C); 833 } 834 } 835 } 836 837 /// Marks a string being returned by an ObjC method as localized 838 /// if it is in LocStringMethods or the method is annotated 839 void NonLocalizedStringChecker::checkPostObjCMessage(const ObjCMethodCall &msg, 840 CheckerContext &C) const { 841 initLocStringsMethods(C.getASTContext()); 842 843 if (!msg.isInstanceMessage()) 844 return; 845 846 const ObjCInterfaceDecl *OD = msg.getReceiverInterface(); 847 if (!OD) 848 return; 849 const IdentifierInfo *odInfo = OD->getIdentifier(); 850 851 Selector S = msg.getSelector(); 852 std::string SelectorName = S.getAsString(); 853 854 std::pair<const IdentifierInfo *, Selector> MethodDescription = {odInfo, S}; 855 856 if (LSM.count(MethodDescription) || isAnnotatedAsLocalized(msg.getDecl())) { 857 SVal sv = msg.getReturnValue(); 858 setLocalizedState(sv, C); 859 } 860 } 861 862 /// Marks all empty string literals as localized 863 void NonLocalizedStringChecker::checkPostStmt(const ObjCStringLiteral *SL, 864 CheckerContext &C) const { 865 SVal sv = C.getSVal(SL); 866 setNonLocalizedState(sv, C); 867 } 868 869 namespace { 870 class EmptyLocalizationContextChecker 871 : public Checker<check::ASTDecl<ObjCImplementationDecl>> { 872 873 // A helper class, which walks the AST 874 class MethodCrawler : public ConstStmtVisitor<MethodCrawler> { 875 const ObjCMethodDecl *MD; 876 BugReporter &BR; 877 AnalysisManager &Mgr; 878 const CheckerBase *Checker; 879 LocationOrAnalysisDeclContext DCtx; 880 881 public: 882 MethodCrawler(const ObjCMethodDecl *InMD, BugReporter &InBR, 883 const CheckerBase *Checker, AnalysisManager &InMgr, 884 AnalysisDeclContext *InDCtx) 885 : MD(InMD), BR(InBR), Mgr(InMgr), Checker(Checker), DCtx(InDCtx) {} 886 887 void VisitStmt(const Stmt *S) { VisitChildren(S); } 888 889 void VisitObjCMessageExpr(const ObjCMessageExpr *ME); 890 891 void reportEmptyContextError(const ObjCMessageExpr *M) const; 892 893 void VisitChildren(const Stmt *S) { 894 for (const Stmt *Child : S->children()) { 895 if (Child) 896 this->Visit(Child); 897 } 898 } 899 }; 900 901 public: 902 void checkASTDecl(const ObjCImplementationDecl *D, AnalysisManager &Mgr, 903 BugReporter &BR) const; 904 }; 905 } // end anonymous namespace 906 907 void EmptyLocalizationContextChecker::checkASTDecl( 908 const ObjCImplementationDecl *D, AnalysisManager &Mgr, 909 BugReporter &BR) const { 910 911 for (const ObjCMethodDecl *M : D->methods()) { 912 AnalysisDeclContext *DCtx = Mgr.getAnalysisDeclContext(M); 913 914 const Stmt *Body = M->getBody(); 915 assert(Body); 916 917 MethodCrawler MC(M->getCanonicalDecl(), BR, this, Mgr, DCtx); 918 MC.VisitStmt(Body); 919 } 920 } 921 922 /// This check attempts to match these macros, assuming they are defined as 923 /// follows: 924 /// 925 /// #define NSLocalizedString(key, comment) \ 926 /// [[NSBundle mainBundle] localizedStringForKey:(key) value:@"" table:nil] 927 /// #define NSLocalizedStringFromTable(key, tbl, comment) \ 928 /// [[NSBundle mainBundle] localizedStringForKey:(key) value:@"" table:(tbl)] 929 /// #define NSLocalizedStringFromTableInBundle(key, tbl, bundle, comment) \ 930 /// [bundle localizedStringForKey:(key) value:@"" table:(tbl)] 931 /// #define NSLocalizedStringWithDefaultValue(key, tbl, bundle, val, comment) 932 /// 933 /// We cannot use the path sensitive check because the macro argument we are 934 /// checking for (comment) is not used and thus not present in the AST, 935 /// so we use Lexer on the original macro call and retrieve the value of 936 /// the comment. If it's empty or nil, we raise a warning. 937 void EmptyLocalizationContextChecker::MethodCrawler::VisitObjCMessageExpr( 938 const ObjCMessageExpr *ME) { 939 940 const ObjCInterfaceDecl *OD = ME->getReceiverInterface(); 941 if (!OD) 942 return; 943 944 const IdentifierInfo *odInfo = OD->getIdentifier(); 945 946 if (!(odInfo->isStr("NSBundle") && 947 ME->getSelector().getAsString() == 948 "localizedStringForKey:value:table:")) { 949 return; 950 } 951 952 SourceRange R = ME->getSourceRange(); 953 if (!R.getBegin().isMacroID()) 954 return; 955 956 // getImmediateMacroCallerLoc gets the location of the immediate macro 957 // caller, one level up the stack toward the initial macro typed into the 958 // source, so SL should point to the NSLocalizedString macro. 959 SourceLocation SL = 960 Mgr.getSourceManager().getImmediateMacroCallerLoc(R.getBegin()); 961 std::pair<FileID, unsigned> SLInfo = 962 Mgr.getSourceManager().getDecomposedLoc(SL); 963 964 SrcMgr::SLocEntry SE = Mgr.getSourceManager().getSLocEntry(SLInfo.first); 965 966 // If NSLocalizedString macro is wrapped in another macro, we need to 967 // unwrap the expansion until we get to the NSLocalizedStringMacro. 968 while (SE.isExpansion()) { 969 SL = SE.getExpansion().getSpellingLoc(); 970 SLInfo = Mgr.getSourceManager().getDecomposedLoc(SL); 971 SE = Mgr.getSourceManager().getSLocEntry(SLInfo.first); 972 } 973 974 llvm::MemoryBuffer *BF = SE.getFile().getContentCache()->getRawBuffer(); 975 Lexer TheLexer(SL, LangOptions(), BF->getBufferStart(), 976 BF->getBufferStart() + SLInfo.second, BF->getBufferEnd()); 977 978 Token I; 979 Token Result; // This will hold the token just before the last ')' 980 int p_count = 0; // This is for parenthesis matching 981 while (!TheLexer.LexFromRawLexer(I)) { 982 if (I.getKind() == tok::l_paren) 983 ++p_count; 984 if (I.getKind() == tok::r_paren) { 985 if (p_count == 1) 986 break; 987 --p_count; 988 } 989 Result = I; 990 } 991 992 if (isAnyIdentifier(Result.getKind())) { 993 if (Result.getRawIdentifier().equals("nil")) { 994 reportEmptyContextError(ME); 995 return; 996 } 997 } 998 999 if (!isStringLiteral(Result.getKind())) 1000 return; 1001 1002 StringRef Comment = 1003 StringRef(Result.getLiteralData(), Result.getLength()).trim('"'); 1004 1005 if ((Comment.trim().size() == 0 && Comment.size() > 0) || // Is Whitespace 1006 Comment.empty()) { 1007 reportEmptyContextError(ME); 1008 } 1009 } 1010 1011 void EmptyLocalizationContextChecker::MethodCrawler::reportEmptyContextError( 1012 const ObjCMessageExpr *ME) const { 1013 // Generate the bug report. 1014 BR.EmitBasicReport(MD, Checker, "Context Missing", 1015 "Localizability Issue (Apple)", 1016 "Localized string macro should include a non-empty " 1017 "comment for translators", 1018 PathDiagnosticLocation(ME, BR.getSourceManager(), DCtx)); 1019 } 1020 1021 namespace { 1022 class PluralMisuseChecker : public Checker<check::ASTCodeBody> { 1023 1024 // A helper class, which walks the AST 1025 class MethodCrawler : public RecursiveASTVisitor<MethodCrawler> { 1026 BugReporter &BR; 1027 const CheckerBase *Checker; 1028 AnalysisDeclContext *AC; 1029 1030 // This functions like a stack. We push on any IfStmt or 1031 // ConditionalOperator that matches the condition 1032 // and pop it off when we leave that statement 1033 llvm::SmallVector<const clang::Stmt *, 8> MatchingStatements; 1034 // This is true when we are the direct-child of a 1035 // matching statement 1036 bool InMatchingStatement = false; 1037 1038 public: 1039 explicit MethodCrawler(BugReporter &InBR, const CheckerBase *Checker, 1040 AnalysisDeclContext *InAC) 1041 : BR(InBR), Checker(Checker), AC(InAC) {} 1042 1043 bool VisitIfStmt(const IfStmt *I); 1044 bool EndVisitIfStmt(IfStmt *I); 1045 bool TraverseIfStmt(IfStmt *x); 1046 bool VisitConditionalOperator(const ConditionalOperator *C); 1047 bool TraverseConditionalOperator(ConditionalOperator *C); 1048 bool VisitCallExpr(const CallExpr *CE); 1049 bool VisitObjCMessageExpr(const ObjCMessageExpr *ME); 1050 1051 private: 1052 void reportPluralMisuseError(const Stmt *S) const; 1053 bool isCheckingPlurality(const Expr *E) const; 1054 }; 1055 1056 public: 1057 void checkASTCodeBody(const Decl *D, AnalysisManager &Mgr, 1058 BugReporter &BR) const { 1059 MethodCrawler Visitor(BR, this, Mgr.getAnalysisDeclContext(D)); 1060 Visitor.TraverseDecl(const_cast<Decl *>(D)); 1061 } 1062 }; 1063 } // end anonymous namespace 1064 1065 // Checks the condition of the IfStmt and returns true if one 1066 // of the following heuristics are met: 1067 // 1) The conidtion is a variable with "singular" or "plural" in the name 1068 // 2) The condition is a binary operator with 1 or 2 on the right-hand side 1069 bool PluralMisuseChecker::MethodCrawler::isCheckingPlurality( 1070 const Expr *Condition) const { 1071 const BinaryOperator *BO = nullptr; 1072 // Accounts for when a VarDecl represents a BinaryOperator 1073 if (const DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(Condition)) { 1074 if (const VarDecl *VD = dyn_cast<VarDecl>(DRE->getDecl())) { 1075 const Expr *InitExpr = VD->getInit(); 1076 if (InitExpr) { 1077 if (const BinaryOperator *B = 1078 dyn_cast<BinaryOperator>(InitExpr->IgnoreParenImpCasts())) { 1079 BO = B; 1080 } 1081 } 1082 if (VD->getName().lower().find("plural") != StringRef::npos || 1083 VD->getName().lower().find("singular") != StringRef::npos) { 1084 return true; 1085 } 1086 } 1087 } else if (const BinaryOperator *B = dyn_cast<BinaryOperator>(Condition)) { 1088 BO = B; 1089 } 1090 1091 if (BO == nullptr) 1092 return false; 1093 1094 if (IntegerLiteral *IL = dyn_cast_or_null<IntegerLiteral>( 1095 BO->getRHS()->IgnoreParenImpCasts())) { 1096 llvm::APInt Value = IL->getValue(); 1097 if (Value == 1 || Value == 2) { 1098 return true; 1099 } 1100 } 1101 return false; 1102 } 1103 1104 // A CallExpr with "LOC" in its identifier that takes in a string literal 1105 // has been shown to almost always be a function that returns a localized 1106 // string. Raise a diagnostic when this is in a statement that matches 1107 // the condition. 1108 bool PluralMisuseChecker::MethodCrawler::VisitCallExpr(const CallExpr *CE) { 1109 if (InMatchingStatement) { 1110 if (const FunctionDecl *FD = CE->getDirectCallee()) { 1111 std::string NormalizedName = 1112 StringRef(FD->getNameInfo().getAsString()).lower(); 1113 if (NormalizedName.find("loc") != std::string::npos) { 1114 for (const Expr *Arg : CE->arguments()) { 1115 if (isa<ObjCStringLiteral>(Arg)) 1116 reportPluralMisuseError(CE); 1117 } 1118 } 1119 } 1120 } 1121 return true; 1122 } 1123 1124 // The other case is for NSLocalizedString which also returns 1125 // a localized string. It's a macro for the ObjCMessageExpr 1126 // [NSBundle localizedStringForKey:value:table:] Raise a 1127 // diagnostic when this is in a statement that matches 1128 // the condition. 1129 bool PluralMisuseChecker::MethodCrawler::VisitObjCMessageExpr( 1130 const ObjCMessageExpr *ME) { 1131 const ObjCInterfaceDecl *OD = ME->getReceiverInterface(); 1132 if (!OD) 1133 return true; 1134 1135 const IdentifierInfo *odInfo = OD->getIdentifier(); 1136 1137 if (odInfo->isStr("NSBundle") && 1138 ME->getSelector().getAsString() == "localizedStringForKey:value:table:") { 1139 if (InMatchingStatement) { 1140 reportPluralMisuseError(ME); 1141 } 1142 } 1143 return true; 1144 } 1145 1146 /// Override TraverseIfStmt so we know when we are done traversing an IfStmt 1147 bool PluralMisuseChecker::MethodCrawler::TraverseIfStmt(IfStmt *I) { 1148 RecursiveASTVisitor<MethodCrawler>::TraverseIfStmt(I); 1149 return EndVisitIfStmt(I); 1150 } 1151 1152 // EndVisit callbacks are not provided by the RecursiveASTVisitor 1153 // so we override TraverseIfStmt and make a call to EndVisitIfStmt 1154 // after traversing the IfStmt 1155 bool PluralMisuseChecker::MethodCrawler::EndVisitIfStmt(IfStmt *I) { 1156 MatchingStatements.pop_back(); 1157 if (!MatchingStatements.empty()) { 1158 if (MatchingStatements.back() != nullptr) { 1159 InMatchingStatement = true; 1160 return true; 1161 } 1162 } 1163 InMatchingStatement = false; 1164 return true; 1165 } 1166 1167 bool PluralMisuseChecker::MethodCrawler::VisitIfStmt(const IfStmt *I) { 1168 const Expr *Condition = I->getCond()->IgnoreParenImpCasts(); 1169 if (isCheckingPlurality(Condition)) { 1170 MatchingStatements.push_back(I); 1171 InMatchingStatement = true; 1172 } else { 1173 MatchingStatements.push_back(nullptr); 1174 InMatchingStatement = false; 1175 } 1176 1177 return true; 1178 } 1179 1180 // Preliminary support for conditional operators. 1181 bool PluralMisuseChecker::MethodCrawler::TraverseConditionalOperator( 1182 ConditionalOperator *C) { 1183 RecursiveASTVisitor<MethodCrawler>::TraverseConditionalOperator(C); 1184 MatchingStatements.pop_back(); 1185 if (!MatchingStatements.empty()) { 1186 if (MatchingStatements.back() != nullptr) 1187 InMatchingStatement = true; 1188 else 1189 InMatchingStatement = false; 1190 } else { 1191 InMatchingStatement = false; 1192 } 1193 return true; 1194 } 1195 1196 bool PluralMisuseChecker::MethodCrawler::VisitConditionalOperator( 1197 const ConditionalOperator *C) { 1198 const Expr *Condition = C->getCond()->IgnoreParenImpCasts(); 1199 if (isCheckingPlurality(Condition)) { 1200 MatchingStatements.push_back(C); 1201 InMatchingStatement = true; 1202 } else { 1203 MatchingStatements.push_back(nullptr); 1204 InMatchingStatement = false; 1205 } 1206 return true; 1207 } 1208 1209 void PluralMisuseChecker::MethodCrawler::reportPluralMisuseError( 1210 const Stmt *S) const { 1211 // Generate the bug report. 1212 BR.EmitBasicReport(AC->getDecl(), Checker, "Plural Misuse", 1213 "Localizability Issue (Apple)", 1214 "Plural cases are not supported accross all languages. " 1215 "Use a .stringsdict file instead", 1216 PathDiagnosticLocation(S, BR.getSourceManager(), AC)); 1217 } 1218 1219 //===----------------------------------------------------------------------===// 1220 // Checker registration. 1221 //===----------------------------------------------------------------------===// 1222 1223 void ento::registerNonLocalizedStringChecker(CheckerManager &mgr) { 1224 NonLocalizedStringChecker *checker = 1225 mgr.registerChecker<NonLocalizedStringChecker>(); 1226 checker->IsAggressive = 1227 mgr.getAnalyzerOptions().getBooleanOption("AggressiveReport", false); 1228 } 1229 1230 void ento::registerEmptyLocalizationContextChecker(CheckerManager &mgr) { 1231 mgr.registerChecker<EmptyLocalizationContextChecker>(); 1232 } 1233 1234 void ento::registerPluralMisuseChecker(CheckerManager &mgr) { 1235 mgr.registerChecker<PluralMisuseChecker>(); 1236 } 1237