clang: lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp Source File (original) (raw)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

31#include "llvm/ADT/STLExtras.h"

32#include "llvm/Support/Unicode.h"

33#include

34

35using namespace clang;

36using namespace ento;

37

38namespace {

39struct LocalizedState {

40private:

41 enum Kind { NonLocalized, Localized } K;

42 LocalizedState(Kind InK) : K(InK) {}

43

44public:

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

54 bool operator==(const LocalizedState &X) const { return K == X.K; }

55

56

57 void Profile(llvm::FoldingSetNodeID &ID) const { ID.AddInteger(K); }

58};

59

60class NonLocalizedStringChecker

61 : public Checker<check::PreCall, check::PostCall, check::PreObjCMessage,

62 check::PostObjCMessage,

63 check::PostStmt> {

64

65 const BugType BT{this, "Unlocalizable string",

66 "Localizability Issue (Apple)"};

67

68

70 llvm::DenseMap<Selector, uint8_t>> UIMethods;

71

72 mutable llvm::SmallSet<std::pair<const IdentifierInfo *, Selector>, 12> LSM;

73

74 mutable llvm::SmallSet<const IdentifierInfo *, 5> LSF;

75

76 void initUIMethods(ASTContext &Ctx) const;

77 void initLocStringsMethods(ASTContext &Ctx) const;

78

83

84 bool isAnnotatedAsReturningLocalized(const Decl *D) const;

85 bool isAnnotatedAsTakingLocalized(const Decl *D) const;

87 int argumentNumber = 0) const;

88

89 int getLocalizedArgumentForSelector(const IdentifierInfo *Receiver,

91

92public:

93

94

95

96 bool IsAggressive = false;

97

103};

104

105}

106

108 LocalizedState)

109

110namespace {

112

113 const MemRegion *NonLocalizedString;

114 bool Satisfied;

115

116public:

117 NonLocalizedStringBRVisitor(const MemRegion *NonLocalizedString)

118 : NonLocalizedString(NonLocalizedString), Satisfied(false) {

119 assert(NonLocalizedString);

120 }

121

125

126 void Profile(llvm::FoldingSetNodeID &ID) const override {

127 ID.Add(NonLocalizedString);

128 }

129};

130}

131

132#define NEW_RECEIVER(receiver) \

133 llvm::DenseMap<Selector, uint8_t> &receiver##M = \

134 UIMethods[&Ctx.Idents.get(#receiver)];

135#define ADD_NULLARY_METHOD(receiver, method, argument) \

136 receiver##M.insert( \

137 {Ctx.Selectors.getNullarySelector(&Ctx.Idents.get(#method)), argument});

138#define ADD_UNARY_METHOD(receiver, method, argument) \

139 receiver##M.insert( \

140 {Ctx.Selectors.getUnarySelector(&Ctx.Idents.get(#method)), argument});

141#define ADD_METHOD(receiver, method_list, count, argument) \

142 receiver##M.insert({Ctx.Selectors.getSelector(count, method_list), argument});

143

144

145

146void NonLocalizedStringChecker::initUIMethods(ASTContext &Ctx) const {

147 if (!UIMethods.empty())

148 return;

149

150

152 ADD_UNARY_METHOD(UISearchDisplayController, setSearchResultsTitle, 0)

153

155 const IdentifierInfo *initWithTitleUITabBarItemTag[] = {

158 ADD_METHOD(UITabBarItem, initWithTitleUITabBarItemTag, 3, 0)

159 const IdentifierInfo *initWithTitleUITabBarItemImage[] = {

161 &Ctx.Idents.get("selectedImage")};

162 ADD_METHOD(UITabBarItem, initWithTitleUITabBarItemImage, 3, 0)

163

166

170

172 const IdentifierInfo *rowActionWithStyleUITableViewRowAction[] = {

175 ADD_METHOD(UITableViewRowAction, rowActionWithStyleUITableViewRowAction, 3, 1)

177

180

184 const IdentifierInfo *radioButtonWithTitleNSButton[] = {

187 ADD_METHOD(NSButton, radioButtonWithTitleNSButton, 3, 0)

188 const IdentifierInfo *buttonWithTitleNSButtonImage[] = {

191 ADD_METHOD(NSButton, buttonWithTitleNSButtonImage, 4, 0)

192 const IdentifierInfo *checkboxWithTitleNSButton[] = {

195 ADD_METHOD(NSButton, checkboxWithTitleNSButton, 3, 0)

196 const IdentifierInfo *buttonWithTitleNSButtonTarget[] = {

199 ADD_METHOD(NSButton, buttonWithTitleNSButtonTarget, 3, 0)

200

207

210

214

218 ADD_METHOD(NSBrowser, setTitleNSBrowser, 2, 0)

219

221 ADD_UNARY_METHOD(UIAccessibilityElement, setAccessibilityLabel, 0)

222 ADD_UNARY_METHOD(UIAccessibilityElement, setAccessibilityHint, 0)

223 ADD_UNARY_METHOD(UIAccessibilityElement, setAccessibilityValue, 0)

224

226 const IdentifierInfo *actionWithTitleUIAlertAction[] = {

229 ADD_METHOD(UIAlertAction, actionWithTitleUIAlertAction, 3, 0)

230

233 const IdentifierInfo *insertItemWithTitleNSPopUpButton[] = {

235 ADD_METHOD(NSPopUpButton, insertItemWithTitleNSPopUpButton, 2, 0)

239

241 const IdentifierInfo *rowActionWithStyleNSTableViewRowAction[] = {

244 ADD_METHOD(NSTableViewRowAction, rowActionWithStyleNSTableViewRowAction, 3, 1)

246

249

252

255

260

263

268

272

274 const IdentifierInfo *setLabelNSSegmentedControl[] = {

276 ADD_METHOD(NSSegmentedControl, setLabelNSSegmentedControl, 2, 0)

277 const IdentifierInfo *setToolTipNSSegmentedControl[] = {

279 ADD_METHOD(NSSegmentedControl, setToolTipNSSegmentedControl, 2, 0)

280

284

287

290

293

295 ADD_UNARY_METHOD(NSAccessibility, setAccessibilityValueDescription, 0)

298 ADD_UNARY_METHOD(NSAccessibility, setAccessibilityPlaceholderValue, 0)

300

304 ADD_METHOD(NSMatrix, setToolTipNSMatrix, 2, 0)

305

308

313

316

320 ADD_METHOD(UIMenuItem, initWithTitleUIMenuItem, 2, 0)

322

324 const IdentifierInfo *alertControllerWithTitleUIAlertController[] = {

325 &Ctx.Idents.get("alertControllerWithTitle"), &Ctx.Idents.get("message"),

326 &Ctx.Idents.get("preferredStyle")};

327 ADD_METHOD(UIAlertController, alertControllerWithTitleUIAlertController, 3, 1)

330

332 const IdentifierInfo *initWithTypeUIApplicationShortcutItemIcon[] = {

336 ADD_METHOD(UIApplicationShortcutItem,

337 initWithTypeUIApplicationShortcutItemIcon, 5, 1)

338 const IdentifierInfo *initWithTypeUIApplicationShortcutItem[] = {

340 ADD_METHOD(UIApplicationShortcutItem, initWithTypeUIApplicationShortcutItem,

341 2, 1)

342

344 const IdentifierInfo *initWithTitleUIActionSheet[] = {

346 &Ctx.Idents.get("cancelButtonTitle"),

347 &Ctx.Idents.get("destructiveButtonTitle"),

348 &Ctx.Idents.get("otherButtonTitles")};

349 ADD_METHOD(UIActionSheet, initWithTitleUIActionSheet, 5, 0)

352

354 const IdentifierInfo *initWithNameUIAccessibilityCustomAction[] = {

357 ADD_METHOD(UIAccessibilityCustomAction,

358 initWithNameUIAccessibilityCustomAction, 3, 0)

360

365

368

371

374

380

383 const IdentifierInfo *initWithStringNSAttributedString[] = {

385 ADD_METHOD(NSAttributedString, initWithStringNSAttributedString, 2, 0)

386

389

391 const IdentifierInfo *keyCommandWithInputUIKeyCommand[] = {

392 &Ctx.Idents.get("keyCommandWithInput"), &Ctx.Idents.get("modifierFlags"),

394 ADD_METHOD(UIKeyCommand, keyCommandWithInputUIKeyCommand, 4, 3)

396

399

401 const IdentifierInfo *alertWithMessageTextNSAlert[] = {

402 &Ctx.Idents.get("alertWithMessageText"), &Ctx.Idents.get("defaultButton"),

404 &Ctx.Idents.get("informativeTextWithFormat")};

405 ADD_METHOD(NSAlert, alertWithMessageTextNSAlert, 5, 0)

410

411 NEW_RECEIVER(UIMutableApplicationShortcutItem)

412 ADD_UNARY_METHOD(UIMutableApplicationShortcutItem, setLocalizedTitle, 0)

413 ADD_UNARY_METHOD(UIMutableApplicationShortcutItem, setLocalizedSubtitle, 0)

414

418 ADD_METHOD(UIButton, setTitleUIButton, 2, 0)

419

422 const IdentifierInfo *minFrameWidthWithTitleNSWindow[] = {

423 &Ctx.Idents.get("minFrameWidthWithTitle"), &Ctx.Idents.get("styleMask")};

424 ADD_METHOD(NSWindow, minFrameWidthWithTitleNSWindow, 2, 0)

426

429

431 const IdentifierInfo *addOptionWithTitleUIDocumentMenuViewController[] = {

434 ADD_METHOD(UIDocumentMenuViewController,

435 addOptionWithTitleUIDocumentMenuViewController, 4, 0)

436

441

446 &Ctx.Idents.get("otherButtonTitles")};

447 ADD_METHOD(UIAlertView, initWithTitleUIAlertView, 5, 0)

451

456

461 ADD_UNARY_METHOD(NSUserNotification, setActionButtonTitle, 0)

463 ADD_UNARY_METHOD(NSUserNotification, setResponsePlaceholder, 0)

464

469

472 ADD_UNARY_METHOD(NSProgress, setLocalizedAdditionalDescription, 0)

473

477 ADD_METHOD(NSSegmentedCell, setLabelNSSegmentedCell, 2, 0)

478 const IdentifierInfo *setToolTipNSSegmentedCell[] = {

480 ADD_METHOD(NSSegmentedCell, setToolTipNSSegmentedCell, 2, 0)

481

484 ADD_UNARY_METHOD(NSUndoManager, undoMenuTitleForUndoActionName, 0)

485 ADD_UNARY_METHOD(NSUndoManager, redoMenuTitleForUndoActionName, 0)

486

490 &Ctx.Idents.get("keyEquivalent")};

491 ADD_METHOD(NSMenuItem, initWithTitleNSMenuItem, 3, 0)

494

496 const IdentifierInfo *initTextCellNSPopUpButtonCell[] = {

498 ADD_METHOD(NSPopUpButtonCell, initTextCellNSPopUpButtonCell, 2, 0)

500 const IdentifierInfo *insertItemWithTitleNSPopUpButtonCell[] = {

502 ADD_METHOD(NSPopUpButtonCell, insertItemWithTitleNSPopUpButtonCell, 2, 0)

506

509

512 const IdentifierInfo *insertItemWithTitleNSMenu[] = {

515 ADD_METHOD(NSMenu, insertItemWithTitleNSMenu, 4, 0)

518 &Ctx.Idents.get("keyEquivalent")};

519 ADD_METHOD(NSMenu, addItemWithTitleNSMenu, 3, 0)

521

522 NEW_RECEIVER(UIMutableUserNotificationAction)

523 ADD_UNARY_METHOD(UIMutableUserNotificationAction, setTitle, 0)

524

529 ADD_METHOD(NSForm, insertEntryNSForm, 2, 0)

530

533

535 const IdentifierInfo *actionWithIdentifierNSUserNotificationAction[] = {

538 actionWithIdentifierNSUserNotificationAction, 2, 1)

539

543

545 const IdentifierInfo *initWithTitleUIBarButtonItem[] = {

548 ADD_METHOD(UIBarButtonItem, initWithTitleUIBarButtonItem, 4, 0)

549

552

554 const IdentifierInfo *insertSegmentWithTitleUISegmentedControl[] = {

555 &Ctx.Idents.get("insertSegmentWithTitle"), &Ctx.Idents.get("atIndex"),

557 ADD_METHOD(UISegmentedControl, insertSegmentWithTitleUISegmentedControl, 3, 0)

558 const IdentifierInfo *setTitleUISegmentedControl[] = {

560 ADD_METHOD(UISegmentedControl, setTitleUISegmentedControl, 2, 0)

561

562 NEW_RECEIVER(NSAccessibilityCustomRotorItemResult)

564 *initWithItemLoadingTokenNSAccessibilityCustomRotorItemResult[] = {

565 &Ctx.Idents.get("initWithItemLoadingToken"),

567 ADD_METHOD(NSAccessibilityCustomRotorItemResult,

568 initWithItemLoadingTokenNSAccessibilityCustomRotorItemResult, 2, 1)

569 ADD_UNARY_METHOD(NSAccessibilityCustomRotorItemResult, setCustomLabel, 0)

570

572 const IdentifierInfo *contextualActionWithStyleUIContextualAction[] = {

573 &Ctx.Idents.get("contextualActionWithStyle"), &Ctx.Idents.get("title"),

575 ADD_METHOD(UIContextualAction, contextualActionWithStyleUIContextualAction, 3,

576 1)

578

580 const IdentifierInfo *initWithLabelNSAccessibilityCustomRotor[] = {

581 &Ctx.Idents.get("initWithLabel"), &Ctx.Idents.get("itemSearchDelegate")};

582 ADD_METHOD(NSAccessibilityCustomRotor,

583 initWithLabelNSAccessibilityCustomRotor, 2, 0)

585

589

591 const IdentifierInfo *initWithNameNSAccessibilityCustomAction[] = {

593 ADD_METHOD(NSAccessibilityCustomAction,

594 initWithNameNSAccessibilityCustomAction, 2, 0)

595 const IdentifierInfo *initWithNameTargetNSAccessibilityCustomAction[] = {

598 ADD_METHOD(NSAccessibilityCustomAction,

599 initWithNameTargetNSAccessibilityCustomAction, 3, 0)

601}

602

603#define LSF_INSERT(function_name) LSF.insert(&Ctx.Idents.get(function_name));

604#define LSM_INSERT_NULLARY(receiver, method_name) \

605 LSM.insert({&Ctx.Idents.get(receiver), Ctx.Selectors.getNullarySelector( \

606 &Ctx.Idents.get(method_name))});

607#define LSM_INSERT_UNARY(receiver, method_name) \

608 LSM.insert({&Ctx.Idents.get(receiver), \

609 Ctx.Selectors.getUnarySelector(&Ctx.Idents.get(method_name))});

610#define LSM_INSERT_SELECTOR(receiver, method_list, arguments) \

611 LSM.insert({&Ctx.Idents.get(receiver), \

612 Ctx.Selectors.getSelector(arguments, method_list)});

613

614

615void NonLocalizedStringChecker::initLocStringsMethods(ASTContext &Ctx) const {

616 if (!LSM.empty())

617 return;

618

625 &Ctx.Idents.get("localizedStringFromDate"), &Ctx.Idents.get("dateStyle"),

632

633 LSF_INSERT("CFDateFormatterCreateStringWithDate");

634 LSF_INSERT("CFDateFormatterCreateStringWithAbsoluteTime");

635 LSF_INSERT("CFNumberFormatterCreateStringWithNumber");

636}

637

638

639

640bool NonLocalizedStringChecker::isAnnotatedAsReturningLocalized(

641 const Decl *D) const {

642 if (D)

643 return false;

644 return std::any_of(

647 return Ann->getAnnotation() == "returns_localized_nsstring";

648 });

649}

650

651

652

653bool NonLocalizedStringChecker::isAnnotatedAsTakingLocalized(

654 const Decl *D) const {

655 if (D)

656 return false;

657 return std::any_of(

660 return Ann->getAnnotation() == "takes_localized_nsstring";

661 });

662}

663

664

665bool NonLocalizedStringChecker::hasLocalizedState(SVal S,

667 const MemRegion *mt = S.getAsRegion();

668 if (mt) {

669 const LocalizedState *LS = C.getState()->get(mt);

670 if (LS && LS->isLocalized())

671 return true;

672 }

673 return false;

674}

675

676

677

678bool NonLocalizedStringChecker::hasNonLocalizedState(SVal S,

680 const MemRegion *mt = S.getAsRegion();

681 if (mt) {

682 const LocalizedState *LS = C.getState()->get(mt);

683 if (LS && LS->isNonLocalized())

684 return true;

685 }

686 return false;

687}

688

689

690void NonLocalizedStringChecker::setLocalizedState(const SVal S,

692 const MemRegion *mt = S.getAsRegion();

693 if (mt) {

695 C.getState()->set(mt, LocalizedState::getLocalized());

696 C.addTransition(State);

697 }

698}

699

700

701void NonLocalizedStringChecker::setNonLocalizedState(const SVal S,

703 const MemRegion *mt = S.getAsRegion();

704 if (mt) {

706 mt, LocalizedState::getNonLocalized());

707 C.addTransition(State);

708 }

709}

710

711

713 return StringRef(name).contains_insensitive("debug");

714}

715

716

717

718

719

721 const Decl *D = C.getCurrentAnalysisDeclContext()->getDecl();

722 if (D)

723 return false;

724

725 if (auto *ND = dyn_cast(D)) {

727 return true;

728 }

729

731

732 if (auto *CD = dyn_cast(DC)) {

734 return true;

735 }

736

737 return false;

738}

739

740

741

742void NonLocalizedStringChecker::reportLocalizationError(

744

745

746

748 return;

749

751 "UnlocalizedString");

752 ExplodedNode *ErrNode = C.addTransition(C.getState(), C.getPredecessor(), &Tag);

753

754 if (!ErrNode)

755 return;

756

757

758 auto R = std::make_unique(

759 BT, "User-facing text should use localized string macro", ErrNode);

760 if (argumentNumber) {

762 } else {

764 }

765 R->markInteresting(S);

766

769 R->addVisitor(std::make_unique(StringRegion));

770

771 C.emitReport(std::move(R));

772}

773

774

775

776int NonLocalizedStringChecker::getLocalizedArgumentForSelector(

778 auto method = UIMethods.find(Receiver);

779

780 if (method == UIMethods.end())

781 return -1;

782

783 auto argumentIterator = method->getSecond().find(S);

784

785 if (argumentIterator == method->getSecond().end())

786 return -1;

787

788 int argumentNumber = argumentIterator->getSecond();

789 return argumentNumber;

790}

791

792

793void NonLocalizedStringChecker::checkPreObjCMessage(const ObjCMethodCall &msg,

795 initUIMethods(C.getASTContext());

796

798 if (!OD)

799 return;

801

803

804 std::string SelectorString = S.getAsString();

805 StringRef SelectorName = SelectorString;

806 assert(!SelectorName.empty());

807

808 if (odInfo->isStr("NSString")) {

809

810

811

812 if (!(SelectorName.starts_with("drawAtPoint") ||

813 SelectorName.starts_with("drawInRect") ||

814 SelectorName.starts_with("drawWithRect")))

815 return;

816

818

819 bool isNonLocalized = hasNonLocalizedState(svTitle, C);

820

821 if (isNonLocalized) {

822 reportLocalizationError(svTitle, msg, C);

823 }

824 }

825

826 int argumentNumber = getLocalizedArgumentForSelector(odInfo, S);

827

828 while (argumentNumber < 0 && OD->getSuperClass() != nullptr) {

830 argumentNumber = getLocalizedArgumentForSelector(P->getIdentifier(), S);

831 if (argumentNumber >= 0)

832 break;

833 }

834 if (argumentNumber < 0) {

836 argumentNumber = getLocalizedArgumentForSelector(OD->getIdentifier(), S);

837 }

838 }

839

840 if (argumentNumber < 0) {

842 if (const ObjCMethodDecl *OMD = dyn_cast_or_null(D)) {

843 for (auto [Idx, FormalParam] : llvm::enumerate(OMD->parameters())) {

844 if (isAnnotatedAsTakingLocalized(FormalParam)) {

845 argumentNumber = Idx;

846 break;

847 }

848 }

849 }

850 }

851 }

852

853 if (argumentNumber < 0)

854 return;

855

857

859 dyn_cast_or_null(svTitle.getAsRegion())) {

860 StringRef stringValue =

861 SR->getObjCStringLiteral()->getString()->getString();

862 if ((stringValue.trim().size() == 0 && stringValue.size() > 0) ||

863 stringValue.empty())

864 return;

865 if (!IsAggressive && llvm::sys::unicode::columnWidthUTF8(stringValue) < 2)

866 return;

867 }

868

869 bool isNonLocalized = hasNonLocalizedState(svTitle, C);

870

871 if (isNonLocalized) {

872 reportLocalizationError(svTitle, msg, C, argumentNumber + 1);

873 }

874}

875

876void NonLocalizedStringChecker::checkPreCall(const CallEvent &Call,

878 const auto *FD = dyn_cast_or_null(Call.getDecl());

879 if (!FD)

880 return;

881

882 auto formals = FD->parameters();

883 for (unsigned i = 0, ei = std::min(static_cast<unsigned>(formals.size()),

884 Call.getNumArgs()); i != ei; ++i) {

885 if (isAnnotatedAsTakingLocalized(formals[i])) {

886 auto actual = Call.getArgSVal(i);

887 if (hasNonLocalizedState(actual, C)) {

888 reportLocalizationError(actual, Call, C, i + 1);

889 }

890 }

891 }

892}

893

895

897 if (!PT)

898 return false;

899

901 if (!Cls)

902 return false;

903

905

906

907 return ClsName == &Ctx.Idents.get("NSString") ||

908 ClsName == &Ctx.Idents.get("NSMutableString");

909}

910

911

912

913

914

915

916void NonLocalizedStringChecker::checkPostCall(const CallEvent &Call,

918 initLocStringsMethods(C.getASTContext());

919

920 if (Call.getOriginExpr())

921 return;

922

923

924

925

928 for (unsigned i = 0; i < Call.getNumArgs(); ++i) {

929 SVal argValue = Call.getArgSVal(i);

930 if (hasLocalizedState(argValue, C)) {

931 SVal sv = Call.getReturnValue();

932 setLocalizedState(sv, C);

933 return;

934 }

935 }

936 }

937

939 if (D)

940 return;

941

943

944 SVal sv = Call.getReturnValue();

945 if (isAnnotatedAsReturningLocalized(D) || LSF.contains(Identifier)) {

946 setLocalizedState(sv, C);

948 !hasLocalizedState(sv, C)) {

949 if (IsAggressive) {

950 setNonLocalizedState(sv, C);

951 } else {

953 dyn_cast_or_null(sv.getAsRegion());

954 if (!SymReg)

955 setNonLocalizedState(sv, C);

956 }

957 }

958}

959

960

961

962void NonLocalizedStringChecker::checkPostObjCMessage(const ObjCMethodCall &msg,

964 initLocStringsMethods(C.getASTContext());

965

967 return;

968

970 if (!OD)

971 return;

973

975 std::string SelectorName = S.getAsString();

976

977 std::pair<const IdentifierInfo *, Selector> MethodDescription = {odInfo, S};

978

979 if (LSM.count(MethodDescription) ||

980 isAnnotatedAsReturningLocalized(msg.getDecl())) {

982 setLocalizedState(sv, C);

983 }

984}

985

986

987void NonLocalizedStringChecker::checkPostStmt(const ObjCStringLiteral *SL,

989 SVal sv = C.getSVal(SL);

990 setNonLocalizedState(sv, C);

991}

992

994NonLocalizedStringBRVisitor::VisitNode(const ExplodedNode *Succ,

997 if (Satisfied)

998 return nullptr;

999

1001 if (!Point)

1002 return nullptr;

1003

1004 auto *LiteralExpr = dyn_cast(Point->getStmt());

1005 if (!LiteralExpr)

1006 return nullptr;

1007

1008 SVal LiteralSVal = Succ->getSVal(LiteralExpr);

1009 if (LiteralSVal.getAsRegion() != NonLocalizedString)

1010 return nullptr;

1011

1012 Satisfied = true;

1013

1016

1018 return nullptr;

1019

1020 auto Piece = std::make_shared(

1021 L, "Non-localized string literal here");

1022 Piece->addRange(LiteralExpr->getSourceRange());

1023

1024 return std::move(Piece);

1025}

1026

1027namespace {

1028class EmptyLocalizationContextChecker

1029 : public Checker<check::ASTDecl> {

1030

1031

1032 class MethodCrawler : public ConstStmtVisitor {

1038

1039 public:

1043 : MD(InMD), BR(InBR), Mgr(InMgr), Checker(Checker), DCtx(InDCtx) {}

1044

1045 void VisitStmt(const Stmt *S) { VisitChildren(S); }

1046

1048

1049 void reportEmptyContextError(const ObjCMessageExpr *M) const;

1050

1051 void VisitChildren(const Stmt *S) {

1052 for (const Stmt *Child : S->children()) {

1053 if (Child)

1054 this->Visit(Child);

1055 }

1056 }

1057 };

1058

1059public:

1062};

1063}

1064

1065void EmptyLocalizationContextChecker::checkASTDecl(

1068

1071

1072 const Stmt *Body = M->getBody();

1073 if (!Body) {

1074 assert(M->isSynthesizedAccessorStub());

1075 continue;

1076 }

1077

1078 MethodCrawler MC(M->getCanonicalDecl(), BR, this, Mgr, DCtx);

1079 MC.VisitStmt(Body);

1080 }

1081}

1082

1083

1084

1085

1086

1087

1088

1089

1090

1091

1092

1093

1094

1095

1096

1097

1098void EmptyLocalizationContextChecker::MethodCrawler::VisitObjCMessageExpr(

1100

1101

1102

1104 if (!OD)

1105 return;

1106

1108

1109 if (!(odInfo->isStr("NSBundle") &&

1111 "localizedStringForKey:value:table:")) {

1112 return;

1113 }

1114

1117 return;

1118

1119

1120

1121

1124 std::pair<FileID, unsigned> SLInfo =

1126

1128

1129

1130

1135 }

1136

1137 std::optionalllvm::MemoryBufferRef BF =

1139 if (!BF)

1140 return;

1142 Lexer TheLexer(SL, LangOpts, BF->getBufferStart(),

1143 BF->getBufferStart() + SLInfo.second, BF->getBufferEnd());

1144

1146 Token Result;

1147 int p_count = 0;

1148 while (!TheLexer.LexFromRawLexer(I)) {

1149 if (I.getKind() == tok::l_paren)

1150 ++p_count;

1151 if (I.getKind() == tok::r_paren) {

1152 if (p_count == 1)

1153 break;

1154 --p_count;

1155 }

1156 Result = I;

1157 }

1158

1160 if (Result.getRawIdentifier() == "nil") {

1161 reportEmptyContextError(ME);

1162 return;

1163 }

1164 }

1165

1167 return;

1168

1169 StringRef Comment =

1170 StringRef(Result.getLiteralData(), Result.getLength()).trim('"');

1171

1172 if ((Comment.trim().size() == 0 && Comment.size() > 0) ||

1173 Comment.empty()) {

1174 reportEmptyContextError(ME);

1175 }

1176}

1177

1178void EmptyLocalizationContextChecker::MethodCrawler::reportEmptyContextError(

1180

1182 "Localizability Issue (Apple)",

1183 "Localized string macro should include a non-empty "

1184 "comment for translators",

1186}

1187

1188namespace {

1189class PluralMisuseChecker : public Checkercheck::ASTCodeBody {

1190

1191

1196

1197

1198

1199

1201

1202

1203 bool InMatchingStatement = false;

1204

1205 public:

1209

1210 bool VisitIfStmt(IfStmt *I) override;

1211 bool EndVisitIfStmt(IfStmt *I);

1212 bool TraverseIfStmt(IfStmt *x) override;

1215 bool VisitCallExpr(CallExpr *CE) override;

1216 bool VisitObjCMessageExpr(ObjCMessageExpr *ME) override;

1217

1218 private:

1219 void reportPluralMisuseError(const Stmt *S) const;

1220 bool isCheckingPlurality(const Expr *E) const;

1221 };

1222

1223public:

1227 Visitor.TraverseDecl(const_cast<Decl *>(D));

1228 }

1229};

1230}

1231

1232

1233

1234

1235

1236bool PluralMisuseChecker::MethodCrawler::isCheckingPlurality(

1239

1241 if (const VarDecl *VD = dyn_cast(DRE->getDecl())) {

1242 const Expr *InitExpr = VD->getInit();

1243 if (InitExpr) {

1246 BO = B;

1247 }

1248 }

1249 if (VD->getName().contains_insensitive("plural") ||

1250 VD->getName().contains_insensitive("singular")) {

1251 return true;

1252 }

1253 }

1255 BO = B;

1256 }

1257

1258 if (BO == nullptr)

1259 return false;

1260

1261 if (IntegerLiteral *IL = dyn_cast_or_null(

1263 llvm::APInt Value = IL->getValue();

1265 return true;

1266 }

1267 }

1268 return false;

1269}

1270

1271

1272

1273

1274

1275bool PluralMisuseChecker::MethodCrawler::VisitCallExpr(CallExpr *CE) {

1276 if (InMatchingStatement) {

1278 std::string NormalizedName =

1279 StringRef(FD->getNameInfo().getAsString()).lower();

1280 if (NormalizedName.find("loc") != std:🧵:npos) {

1282 if (isa(Arg))

1283 reportPluralMisuseError(CE);

1284 }

1285 }

1286 }

1287 }

1288 return true;

1289}

1290

1291

1292

1293

1294

1295

1296bool PluralMisuseChecker::MethodCrawler::VisitObjCMessageExpr(

1299 if (!OD)

1300 return true;

1301

1303

1304 if (odInfo->isStr("NSBundle") &&

1306 if (InMatchingStatement) {

1307 reportPluralMisuseError(ME);

1308 }

1309 }

1310 return true;

1311}

1312

1313

1314bool PluralMisuseChecker::MethodCrawler::TraverseIfStmt(IfStmt *I) {

1315 DynamicRecursiveASTVisitor::TraverseIfStmt(I);

1316 return EndVisitIfStmt(I);

1317}

1318

1319

1320

1321

1322bool PluralMisuseChecker::MethodCrawler::EndVisitIfStmt(IfStmt *I) {

1323 MatchingStatements.pop_back();

1324 if (!MatchingStatements.empty()) {

1325 if (MatchingStatements.back() != nullptr) {

1326 InMatchingStatement = true;

1327 return true;

1328 }

1329 }

1330 InMatchingStatement = false;

1331 return true;

1332}

1333

1334bool PluralMisuseChecker::MethodCrawler::VisitIfStmt(IfStmt *I) {

1337 return true;

1339 if (isCheckingPlurality(Condition)) {

1340 MatchingStatements.push_back(I);

1341 InMatchingStatement = true;

1342 } else {

1343 MatchingStatements.push_back(nullptr);

1344 InMatchingStatement = false;

1345 }

1346

1347 return true;

1348}

1349

1350

1351bool PluralMisuseChecker::MethodCrawler::TraverseConditionalOperator(

1353 DynamicRecursiveASTVisitor::TraverseConditionalOperator(C);

1354 MatchingStatements.pop_back();

1355 if (!MatchingStatements.empty()) {

1356 if (MatchingStatements.back() != nullptr)

1357 InMatchingStatement = true;

1358 else

1359 InMatchingStatement = false;

1360 } else {

1361 InMatchingStatement = false;

1362 }

1363 return true;

1364}

1365

1366bool PluralMisuseChecker::MethodCrawler::VisitConditionalOperator(

1368 const Expr *Condition = C->getCond()->IgnoreParenImpCasts();

1369 if (isCheckingPlurality(Condition)) {

1370 MatchingStatements.push_back(C);

1371 InMatchingStatement = true;

1372 } else {

1373 MatchingStatements.push_back(nullptr);

1374 InMatchingStatement = false;

1375 }

1376 return true;

1377}

1378

1379void PluralMisuseChecker::MethodCrawler::reportPluralMisuseError(

1380 const Stmt *S) const {

1381

1383 "Localizability Issue (Apple)",

1384 "Plural cases are not supported across all languages. "

1385 "Use a .stringsdict file instead",

1387}

1388

1389

1390

1391

1392

1393void ento::registerNonLocalizedStringChecker(CheckerManager &mgr) {

1394 NonLocalizedStringChecker *checker =

1396 checker->IsAggressive =

1398 checker, "AggressiveReport");

1399}

1400

1401bool ento::shouldRegisterNonLocalizedStringChecker(const CheckerManager &mgr) {

1402 return true;

1403}

1404

1405void ento::registerEmptyLocalizationContextChecker(CheckerManager &mgr) {

1407}

1408

1409bool ento::shouldRegisterEmptyLocalizationContextChecker(

1411 return true;

1412}

1413

1414void ento::registerPluralMisuseChecker(CheckerManager &mgr) {

1416}

1417

1418bool ento::shouldRegisterPluralMisuseChecker(const CheckerManager &mgr) {

1419 return true;

1420}

#define LSM_INSERT_SELECTOR(receiver, method_list, arguments)

#define NEW_RECEIVER(receiver)

#define LSM_INSERT_NULLARY(receiver, method_name)

#define LSF_INSERT(function_name)

#define ADD_UNARY_METHOD(receiver, method, argument)

#define ADD_METHOD(receiver, method_list, count, argument)

#define LSM_INSERT_UNARY(receiver, method_name)

static bool isDebuggingContext(CheckerContext &C)

Returns true when, heuristically, the analyzer may be analyzing debugging code.

static bool isDebuggingName(std::string name)

static bool isNSStringType(QualType T, ASTContext &Ctx)

#define REGISTER_MAP_WITH_PROGRAMSTATE(Name, Key, Value)

Declares an immutable map of type NameTy, suitable for placement into the ProgramState.

Holds long-lived AST nodes (such as types and decls) that can be referred to throughout the semantic ...

AnalysisDeclContext contains the context data for the function, method or block under analysis.

bool getCheckerBooleanOption(StringRef CheckerName, StringRef OptionName, bool SearchInParents=false) const

Interprets an option's string value as a boolean.

A builtin binary operation expression such as "x + y" or "x <= y".

CallExpr - Represents a function call (C99 6.5.2.2, C++ [expr.call]).

FunctionDecl * getDirectCallee()

If the callee is a FunctionDecl, return it. Otherwise return null.

ConditionalOperator - The ?: ternary operator.

ConstStmtVisitor - This class implements a simple visitor for Stmt subclasses.

DeclContext - This is used only as base class of specific decl types that can act as declaration cont...

A reference to a declared variable, function, enum, etc.

Decl - This represents one declaration (or definition), e.g.

DeclContext * getDeclContext()

specific_attr_iterator< T > specific_attr_end() const

specific_attr_iterator< T > specific_attr_begin() const

Recursive AST visitor that supports extension via dynamic dispatch.

This represents one expression.

Expr * IgnoreParenImpCasts() LLVM_READONLY

Skip past any parentheses and implicit casts which might surround this expression until reaching a fi...

Represents a function declaration or definition.

One of these records is kept for each identifier that is lexed.

bool isStr(const char(&Str)[StrLen]) const

Return true if this is the identifier for the specified string.

IdentifierInfo & get(StringRef Name)

Return the identifier token info for the specified named identifier.

IfStmt - This represents an if/then/else.

Keeps track of the various options that can be enabled, which controls the dialect of C or C++ that i...

Lexer - This provides a simple interface that turns a text buffer into a stream of tokens.

IdentifierInfo * getIdentifier() const

Get the identifier that names this declaration, if there is one.

ObjCImplementationDecl - Represents a class definition - this is where method definitions are specifi...

Represents an ObjC class declaration.

all_protocol_range all_referenced_protocols() const

ObjCInterfaceDecl * getSuperClass() const

An expression that sends a message to the given Objective-C object or class.

Selector getSelector() const

ObjCInterfaceDecl * getReceiverInterface() const

Retrieve the Objective-C interface to which this message is being directed, if known.

ObjCMethodDecl - Represents an instance or class method declaration.

Represents a pointer to an Objective C object.

const ObjCObjectType * getObjectType() const

Gets the type pointed to by this ObjC pointer.

ObjCInterfaceDecl * getInterface() const

Gets the interface declaration for this object type, if the base type really is an interface.

ObjCStringLiteral, used for Objective-C string literals i.e.

std::optional< T > getAs() const

Convert to the specified ProgramPoint type, returning std::nullopt if this ProgramPoint is not of the...

A (possibly-)qualified type.

Smart pointer class that efficiently represents Objective-C method names.

std::string getAsString() const

Derive the full selector name (e.g.

Encodes a location in the source.

bool isValid() const

Return true if this is a valid SourceLocation object.

SourceLocation getImmediateMacroCallerLoc(SourceLocation Loc) const

Gets the location of the immediate macro caller, one level up the stack toward the initial macro type...

std::pair< FileID, unsigned > getDecomposedLoc(SourceLocation Loc) const

Decompose the specified location into a raw FileID + Offset pair.

std::optional< llvm::MemoryBufferRef > getBufferOrNone(FileID FID, SourceLocation Loc=SourceLocation()) const

Return the buffer for the specified FileID.

const SrcMgr::SLocEntry & getSLocEntry(FileID FID, bool *Invalid=nullptr) const

A trivial tuple used to represent a source range.

SourceLocation getBegin() const

SourceLocation getSpellingLoc() const

This is a discriminated union of FileInfo and ExpansionInfo.

const ExpansionInfo & getExpansion() const

Stmt - This represents one statement.

SourceRange getSourceRange() const LLVM_READONLY

SourceLocation tokens are not useful in isolation - they are low level value objects created/interpre...

Token - This structure provides full information about a lexed token.

tok::TokenKind getKind() const

const T * getAs() const

Member-template getAs'.

Represents a variable declaration or definition.

AnalysisDeclContext * getAnalysisDeclContext(const Decl *D)

SourceManager & getSourceManager() override

const SourceManager & getSourceManager() const

BugReporterVisitors are used to add custom diagnostics along a path.

virtual void Profile(llvm::FoldingSetNodeID &ID) const =0

virtual PathDiagnosticPieceRef VisitNode(const ExplodedNode *Succ, BugReporterContext &BRC, PathSensitiveBugReport &BR)=0

Return a diagnostic piece which should be associated with the given node.

BugReporter is a utility class for generating PathDiagnostics for analysis.

const SourceManager & getSourceManager()

void EmitBasicReport(const Decl *DeclWithIssue, const CheckerBase *Checker, StringRef BugName, StringRef BugCategory, StringRef BugStr, PathDiagnosticLocation Loc, ArrayRef< SourceRange > Ranges={}, ArrayRef< FixItHint > Fixits={})

Represents an abstract call to a function or method along a particular path.

virtual SVal getArgSVal(unsigned Index) const

Returns the value of a given argument at the time of the call.

virtual const Expr * getArgExpr(unsigned Index) const

Returns the expression associated with a given argument.

SVal getReturnValue() const

Returns the return value of the call.

virtual SourceRange getSourceRange() const

Returns a source range for the entire call, suitable for outputting in diagnostics.

const AnalyzerOptions & getAnalyzerOptions() const

CHECKER * registerChecker(AT &&... Args)

Used to register checkers.

Tag that can use a checker name as a message provider (see SimpleProgramPointTag).

ProgramPoint getLocation() const

getLocation - Returns the edge associated with the given node.

SVal getSVal(const Stmt *S) const

Get the value of an arbitrary expression at this node.

MemRegion - The root abstract class for all memory regions.

Represents any expression that calls an Objective-C method.

const ObjCMethodDecl * getDecl() const override

Returns the declaration of the function or method that will be called.

bool isInstanceMessage() const

SVal getReceiverSVal() const

Returns the value of the receiver at the time of this call.

const ObjCInterfaceDecl * getReceiverInterface() const

Get the interface for the receiver.

Selector getSelector() const

The region associated with an ObjCStringLiteral.

FullSourceLoc asLocation() const

static PathDiagnosticLocation create(const Decl *D, const SourceManager &SM)

Create a location corresponding to the given declaration.

SVal - This represents a symbolic expression, which can be either an L-value or an R-value.

const MemRegion * getAsRegion() const

StringRegion - Region associated with a StringLiteral.

SymbolicRegion - A special, "non-concrete" region.

llvm::PointerUnion< const LocationContext *, AnalysisDeclContext * > LocationOrAnalysisDeclContext

std::shared_ptr< PathDiagnosticPiece > PathDiagnosticPieceRef

bool isStringLiteral(TokenKind K)

Return true if this is a C or C++ string-literal (or C++11 user-defined-string-literal) token.

bool isAnyIdentifier(TokenKind K)

Return true if this is a raw identifier or an identifier kind.

The JSON file list parser is used to communicate input to InstallAPI.

bool operator==(const CallGraphNode::CallRecord &LHS, const CallGraphNode::CallRecord &RHS)

const FunctionProtoType * T