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

69 mutable llvm::DenseMap<const IdentifierInfo *,

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

71

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

73

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

75

76 void initUIMethods(ASTContext &Ctx) const;

77 void initLocStringsMethods(ASTContext &Ctx) const;

78

79 bool hasNonLocalizedState(SVal S, CheckerContext &C) const;

80 bool hasLocalizedState(SVal S, CheckerContext &C) const;

81 void setNonLocalizedState(SVal S, CheckerContext &C) const;

82 void setLocalizedState(SVal S, CheckerContext &C) const;

83

84 bool isAnnotatedAsReturningLocalized(const Decl *D) const;

85 bool isAnnotatedAsTakingLocalized(const Decl *D) const;

86 void reportLocalizationError(SVal S, const CallEvent &M, CheckerContext &C,

87 int argumentNumber = 0) const;

88

89 int getLocalizedArgumentForSelector(const IdentifierInfo *Receiver,

90 Selector S) const;

91

92public:

93

94

95

96 bool IsAggressive = false;

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 checkPreCall(const CallEvent &Call, CheckerContext &C) const;

102 void checkPostCall(const CallEvent &Call, CheckerContext &C) const;

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(

646 D->specific_attr_end(), [](const AnnotateAttr *Ann) {

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(

659 D->specific_attr_end(), [](const AnnotateAttr *Ann) {

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

661 });

662}

663

664

665bool NonLocalizedStringChecker::hasLocalizedState(SVal S,

666 CheckerContext &C) const {

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,

679 CheckerContext &C) const {

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,

691 CheckerContext &C) const {

693 if (mt) {

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

696 C.addTransition(State);

697 }

698}

699

700

701void NonLocalizedStringChecker::setNonLocalizedState(const SVal S,

702 CheckerContext &C) const {

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(

743 SVal S, const CallEvent &M, CheckerContext &C, int argumentNumber) const {

744

745

746

748 return;

749

750 ExplodedNode *ErrNode = C.generateNonFatalErrorNode();

751

752 if (!ErrNode)

753 return;

754

755

756 auto R = std::make_unique(

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

758 if (argumentNumber) {

760 } else {

762 }

763 R->markInteresting(S);

764

765 const MemRegion *StringRegion = S.getAsRegion();

766 if (StringRegion)

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

768

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

770}

771

772

773

774int NonLocalizedStringChecker::getLocalizedArgumentForSelector(

775 const IdentifierInfo *Receiver, Selector S) const {

776 auto method = UIMethods.find(Receiver);

777

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

779 return -1;

780

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

782

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

784 return -1;

785

786 int argumentNumber = argumentIterator->getSecond();

787 return argumentNumber;

788}

789

790

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

792 CheckerContext &C) const {

793 initUIMethods(C.getASTContext());

794

796 if (!OD)

797 return;

798 const IdentifierInfo *odInfo = OD->getIdentifier();

799

801

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

803 StringRef SelectorName = SelectorString;

804 assert(!SelectorName.empty());

805

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

807

808

809

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

811 SelectorName.starts_with("drawInRect") ||

812 SelectorName.starts_with("drawWithRect")))

813 return;

814

816

817 bool isNonLocalized = hasNonLocalizedState(svTitle, C);

818

819 if (isNonLocalized) {

820 reportLocalizationError(svTitle, msg, C);

821 }

822 }

823

824 int argumentNumber = getLocalizedArgumentForSelector(odInfo, S);

825

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

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

829 if (argumentNumber >= 0)

830 break;

831 }

832 if (argumentNumber < 0) {

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

835 }

836 }

837

838 if (argumentNumber < 0) {

839 if (const Decl *D = msg.getDecl()) {

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

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

842 if (isAnnotatedAsTakingLocalized(FormalParam)) {

843 argumentNumber = Idx;

844 break;

845 }

846 }

847 }

848 }

849 }

850

851 if (argumentNumber < 0)

852 return;

853

854 SVal svTitle = msg.getArgSVal(argumentNumber);

855

856 if (const ObjCStringRegion *SR =

857 dyn_cast_or_null(svTitle.getAsRegion())) {

858 StringRef stringValue =

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

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

861 stringValue.empty())

862 return;

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

864 return;

865 }

866

867 bool isNonLocalized = hasNonLocalizedState(svTitle, C);

868

869 if (isNonLocalized) {

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

871 }

872}

873

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

875 CheckerContext &C) const {

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

877 if (!FD)

878 return;

879

880 auto formals = FD->parameters();

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

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

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

884 auto actual = Call.getArgSVal(i);

885 if (hasNonLocalizedState(actual, C)) {

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

887 }

888 }

889 }

890}

891

893

895 if (!PT)

896 return false;

897

899 if (!Cls)

900 return false;

901

903

904

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

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

907}

908

909

910

911

912

913

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

915 CheckerContext &C) const {

916 initLocStringsMethods(C.getASTContext());

917

918 if (Call.getOriginExpr())

919 return;

920

921

922

923

924 const QualType RT = Call.getResultType();

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

927 SVal argValue = Call.getArgSVal(i);

928 if (hasLocalizedState(argValue, C)) {

929 SVal sv = Call.getReturnValue();

930 setLocalizedState(sv, C);

931 return;

932 }

933 }

934 }

935

936 const Decl *D = Call.getDecl();

937 if (!D)

938 return;

939

940 const IdentifierInfo *Identifier = Call.getCalleeIdentifier();

941

942 SVal sv = Call.getReturnValue();

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

944 setLocalizedState(sv, C);

946 !hasLocalizedState(sv, C)) {

947 if (IsAggressive) {

948 setNonLocalizedState(sv, C);

949 } else {

950 const SymbolicRegion *SymReg =

951 dyn_cast_or_null(sv.getAsRegion());

952 if (!SymReg)

953 setNonLocalizedState(sv, C);

954 }

955 }

956}

957

958

959

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

961 CheckerContext &C) const {

962 initLocStringsMethods(C.getASTContext());

963

965 return;

966

968 if (!OD)

969 return;

970 const IdentifierInfo *odInfo = OD->getIdentifier();

971

973

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

975

976 if (LSM.count(MethodDescription) ||

977 isAnnotatedAsReturningLocalized(msg.getDecl())) {

979 setLocalizedState(sv, C);

980 }

981}

982

983

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

985 CheckerContext &C) const {

986 SVal sv = C.getSVal(SL);

987 setNonLocalizedState(sv, C);

988}

989

991NonLocalizedStringBRVisitor::VisitNode(const ExplodedNode *Succ,

992 BugReporterContext &BRC,

993 PathSensitiveBugReport &BR) {

994 if (Satisfied)

995 return nullptr;

996

997 std::optional Point = Succ->getLocation().getAs();

998 if (!Point)

999 return nullptr;

1000

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

1002 if (!LiteralExpr)

1003 return nullptr;

1004

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

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

1007 return nullptr;

1008

1009 Satisfied = true;

1010

1011 PathDiagnosticLocation L =

1013

1015 return nullptr;

1016

1017 auto Piece = std::make_shared(

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

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

1020

1021 return std::move(Piece);

1022}

1023

1024namespace {

1025class EmptyLocalizationContextChecker

1026 : public Checker<check::ASTDecl> {

1027

1028

1029 class MethodCrawler : public ConstStmtVisitor {

1030 const ObjCMethodDecl *MD;

1031 BugReporter &BR;

1032 AnalysisManager &Mgr;

1033 const CheckerBase *Checker;

1035

1036 public:

1037 MethodCrawler(const ObjCMethodDecl *InMD, BugReporter &InBR,

1038 const CheckerBase *Checker, AnalysisManager &InMgr,

1039 AnalysisDeclContext *InDCtx)

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

1041

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

1043

1044 void VisitObjCMessageExpr(const ObjCMessageExpr *ME);

1045

1046 void reportEmptyContextError(const ObjCMessageExpr *M) const;

1047

1048 void VisitChildren(const Stmt *S) {

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

1050 if (Child)

1051 this->Visit(Child);

1052 }

1053 }

1054 };

1055

1056public:

1057 void checkASTDecl(const ObjCImplementationDecl *D, AnalysisManager &Mgr,

1058 BugReporter &BR) const;

1059};

1060}

1061

1062void EmptyLocalizationContextChecker::checkASTDecl(

1063 const ObjCImplementationDecl *D, AnalysisManager &Mgr,

1064 BugReporter &BR) const {

1065

1066 for (const ObjCMethodDecl *M : D->methods()) {

1068

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

1070 if (!Body) {

1071 assert(M->isSynthesizedAccessorStub());

1072 continue;

1073 }

1074

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

1076 MC.VisitStmt(Body);

1077 }

1078}

1079

1080

1081

1082

1083

1084

1085

1086

1087

1088

1089

1090

1091

1092

1093

1094

1095void EmptyLocalizationContextChecker::MethodCrawler::VisitObjCMessageExpr(

1096 const ObjCMessageExpr *ME) {

1097

1098

1099

1101 if (!OD)

1102 return;

1103

1104 const IdentifierInfo *odInfo = OD->getIdentifier();

1105

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

1108 "localizedStringForKey:value:table:")) {

1109 return;

1110 }

1111

1114 return;

1115

1116

1117

1118

1119 SourceLocation SL =

1122

1124

1125

1126

1131 }

1132

1133 std::optionalllvm::MemoryBufferRef BF =

1135 if (!BF)

1136 return;

1137 LangOptions LangOpts;

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

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

1140

1141 Token I;

1142 Token Result;

1143 int p_count = 0;

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

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

1146 ++p_count;

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

1148 if (p_count == 1)

1149 break;

1150 --p_count;

1151 }

1153 }

1154

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

1157 reportEmptyContextError(ME);

1158 return;

1159 }

1160 }

1161

1163 return;

1164

1165 StringRef Comment =

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

1167

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

1169 Comment.empty()) {

1170 reportEmptyContextError(ME);

1171 }

1172}

1173

1174void EmptyLocalizationContextChecker::MethodCrawler::reportEmptyContextError(

1175 const ObjCMessageExpr *ME) const {

1176

1178 "Localizability Issue (Apple)",

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

1180 "comment for translators",

1182}

1183

1184namespace {

1185class PluralMisuseChecker : public Checkercheck::ASTCodeBody {

1186

1187

1189 BugReporter &BR;

1190 const CheckerBase *Checker;

1191 AnalysisDeclContext *AC;

1192

1193

1194

1195

1196 llvm::SmallVector<const clang::Stmt *, 8> MatchingStatements;

1197

1198

1199 bool InMatchingStatement = false;

1200

1201 public:

1202 explicit MethodCrawler(BugReporter &InBR, const CheckerBase *Checker,

1203 AnalysisDeclContext *InAC)

1204 : BR(InBR), Checker(Checker), AC(InAC) {}

1205

1206 bool VisitIfStmt(IfStmt *I) override;

1207 bool EndVisitIfStmt(IfStmt *I);

1208 bool TraverseIfStmt(IfStmt *x) override;

1209 bool VisitConditionalOperator(ConditionalOperator *C) override;

1210 bool TraverseConditionalOperator(ConditionalOperator *C) override;

1211 bool VisitCallExpr(CallExpr *CE) override;

1212 bool VisitObjCMessageExpr(ObjCMessageExpr *ME) override;

1213

1214 private:

1215 void reportPluralMisuseError(const Stmt *S) const;

1216 bool isCheckingPlurality(const Expr *E) const;

1217 };

1218

1219public:

1220 void checkASTCodeBody(const Decl *D, AnalysisManager &Mgr,

1221 BugReporter &BR) const {

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

1224 }

1225};

1226}

1227

1228

1229

1230

1231

1232bool PluralMisuseChecker::MethodCrawler::isCheckingPlurality(

1234 const BinaryOperator *BO = nullptr;

1235

1236 if (const DeclRefExpr *DRE = dyn_cast(Condition)) {

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

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

1239 if (InitExpr) {

1240 if (const BinaryOperator *B =

1242 BO = B;

1243 }

1244 }

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

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

1247 return true;

1248 }

1249 }

1250 } else if (const BinaryOperator *B = dyn_cast(Condition)) {

1251 BO = B;

1252 }

1253

1254 if (BO == nullptr)

1255 return false;

1256

1257 if (IntegerLiteral *IL = dyn_cast_or_null(

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

1261 return true;

1262 }

1263 }

1264 return false;

1265}

1266

1267

1268

1269

1270

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

1272 if (InMatchingStatement) {

1274 std::string NormalizedName =

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

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

1277 for (const Expr *Arg : CE->arguments()) {

1279 reportPluralMisuseError(CE);

1280 }

1281 }

1282 }

1283 }

1284 return true;

1285}

1286

1287

1288

1289

1290

1291

1292bool PluralMisuseChecker::MethodCrawler::VisitObjCMessageExpr(

1293 ObjCMessageExpr *ME) {

1295 if (!OD)

1296 return true;

1297

1298 const IdentifierInfo *odInfo = OD->getIdentifier();

1299

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

1302 if (InMatchingStatement) {

1303 reportPluralMisuseError(ME);

1304 }

1305 }

1306 return true;

1307}

1308

1309

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

1311 DynamicRecursiveASTVisitor::TraverseIfStmt(I);

1312 return EndVisitIfStmt(I);

1313}

1314

1315

1316

1317

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

1319 MatchingStatements.pop_back();

1320 if (!MatchingStatements.empty()) {

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

1322 InMatchingStatement = true;

1323 return true;

1324 }

1325 }

1326 InMatchingStatement = false;

1327 return true;

1328}

1329

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

1333 return true;

1335 if (isCheckingPlurality(Condition)) {

1336 MatchingStatements.push_back(I);

1337 InMatchingStatement = true;

1338 } else {

1339 MatchingStatements.push_back(nullptr);

1340 InMatchingStatement = false;

1341 }

1342

1343 return true;

1344}

1345

1346

1347bool PluralMisuseChecker::MethodCrawler::TraverseConditionalOperator(

1348 ConditionalOperator *C) {

1349 DynamicRecursiveASTVisitor::TraverseConditionalOperator(C);

1350 MatchingStatements.pop_back();

1351 if (!MatchingStatements.empty()) {

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

1353 InMatchingStatement = true;

1354 else

1355 InMatchingStatement = false;

1356 } else {

1357 InMatchingStatement = false;

1358 }

1359 return true;

1360}

1361

1362bool PluralMisuseChecker::MethodCrawler::VisitConditionalOperator(

1363 ConditionalOperator *C) {

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

1365 if (isCheckingPlurality(Condition)) {

1366 MatchingStatements.push_back(C);

1367 InMatchingStatement = true;

1368 } else {

1369 MatchingStatements.push_back(nullptr);

1370 InMatchingStatement = false;

1371 }

1372 return true;

1373}

1374

1375void PluralMisuseChecker::MethodCrawler::reportPluralMisuseError(

1376 const Stmt *S) const {

1377

1378 BR.EmitBasicReport(AC->getDecl(), Checker, "Plural Misuse",

1379 "Localizability Issue (Apple)",

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

1381 "Use a .stringsdict file instead",

1383}

1384

1385

1386

1387

1388

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

1390 NonLocalizedStringChecker *checker =

1392 checker->IsAggressive =

1394 checker, "AggressiveReport");

1395}

1396

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

1398 return true;

1399}

1400

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

1403}

1404

1405bool ento::shouldRegisterEmptyLocalizationContextChecker(

1406 const CheckerManager &mgr) {

1407 return true;

1408}

1409

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

1412}

1413

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

1415 return true;

1416}

#define LSM_INSERT_SELECTOR(receiver, method_list, arguments)

Definition LocalizationChecker.cpp:610

#define NEW_RECEIVER(receiver)

Definition LocalizationChecker.cpp:132

#define LSM_INSERT_NULLARY(receiver, method_name)

Definition LocalizationChecker.cpp:604

#define LSF_INSERT(function_name)

Definition LocalizationChecker.cpp:603

#define ADD_UNARY_METHOD(receiver, method, argument)

Definition LocalizationChecker.cpp:138

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

Definition LocalizationChecker.cpp:141

#define LSM_INSERT_UNARY(receiver, method_name)

Definition LocalizationChecker.cpp:607

static bool isDebuggingContext(CheckerContext &C)

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

Definition LocalizationChecker.cpp:720

static bool isDebuggingName(std::string name)

Definition LocalizationChecker.cpp:712

static bool isNSStringType(QualType T, ASTContext &Ctx)

Definition LocalizationChecker.cpp:892

#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 ...

FunctionDecl * getDirectCallee()

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

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

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

Expr * IgnoreParenImpCasts() LLVM_READONLY

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

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.

IdentifierInfo * getIdentifier() const

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

method_range methods() const

Represents an ObjC class declaration.

all_protocol_range all_referenced_protocols() const

ObjCInterfaceDecl * getSuperClass() const

Selector getSelector() const

ObjCInterfaceDecl * getReceiverInterface() const

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

Represents a pointer to an Objective C object.

const ObjCObjectType * getObjectType() const

Gets the type pointed to by this ObjC pointer.

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.

std::string getAsString() const

Derive the full selector name (e.g.

bool isValid() const

Return true if this is a valid SourceLocation object.

FileIDAndOffset getDecomposedLoc(SourceLocation Loc) const

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

SourceLocation getImmediateMacroCallerLoc(SourceLocation Loc) const

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

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

SourceLocation getBegin() const

SourceLocation getSpellingLoc() const

const ExpansionInfo & getExpansion() const

SourceRange getSourceRange() const LLVM_READONLY

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

tok::TokenKind getKind() const

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.

const SourceManager & getSourceManager()

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

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)

Register a single-part checker (derived from Checker): construct its singleton instance,...

Simple checker classes that implement one frontend (i.e.

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.

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

FullSourceLoc asLocation() const

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

Create a location corresponding to the given declaration.

const MemRegion * getAsRegion() const

IntrusiveRefCntPtr< const ProgramState > ProgramStateRef

std::shared_ptr< PathDiagnosticPiece > PathDiagnosticPieceRef

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

std::variant< struct RequiresDecl, struct HeaderDecl, struct UmbrellaDirDecl, struct ModuleDecl, struct ExcludeDecl, struct ExportDecl, struct ExportAsDecl, struct ExternModuleDecl, struct UseDecl, struct LinkDecl, struct ConfigMacrosDecl, struct ConflictDecl > Decl

All declarations that can appear in a module declaration.

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 isa(CodeGen::Address addr)

std::pair< FileID, unsigned > FileIDAndOffset

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

@ Result

The result type of a method or function.

const FunctionProtoType * T

DynamicRecursiveASTVisitorBase< false > DynamicRecursiveASTVisitor