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