1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 | |
14 | |
15 | #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" |
16 | #include "clang/Analysis/CloneDetection.h" |
17 | #include "clang/Basic/Diagnostic.h" |
18 | #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" |
19 | #include "clang/StaticAnalyzer/Core/Checker.h" |
20 | #include "clang/StaticAnalyzer/Core/CheckerManager.h" |
21 | #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" |
22 | #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" |
23 | |
24 | using namespace clang; |
25 | using namespace ento; |
26 | |
27 | namespace { |
28 | class CloneChecker |
29 | : public Checker<check::ASTCodeBody, check::EndOfTranslationUnit> { |
30 | public: |
31 | |
32 | int MinComplexity; |
33 | bool ReportNormalClones; |
34 | StringRef IgnoredFilesPattern; |
35 | |
36 | private: |
37 | mutable CloneDetector Detector; |
38 | mutable std::unique_ptr<BugType> BT_Exact, BT_Suspicious; |
39 | |
40 | public: |
41 | void checkASTCodeBody(const Decl *D, AnalysisManager &Mgr, |
42 | BugReporter &BR) const; |
43 | |
44 | void checkEndOfTranslationUnit(const TranslationUnitDecl *TU, |
45 | AnalysisManager &Mgr, BugReporter &BR) const; |
46 | |
47 | |
48 | void reportClones(BugReporter &BR, AnalysisManager &Mgr, |
49 | std::vector<CloneDetector::CloneGroup> &CloneGroups) const; |
50 | |
51 | |
52 | |
53 | void reportSuspiciousClones( |
54 | BugReporter &BR, AnalysisManager &Mgr, |
55 | std::vector<CloneDetector::CloneGroup> &CloneGroups) const; |
56 | }; |
57 | } |
58 | |
59 | void CloneChecker::checkASTCodeBody(const Decl *D, AnalysisManager &Mgr, |
60 | BugReporter &BR) const { |
61 | |
62 | |
63 | Detector.analyzeCodeBody(D); |
64 | } |
65 | |
66 | void CloneChecker::checkEndOfTranslationUnit(const TranslationUnitDecl *TU, |
67 | AnalysisManager &Mgr, |
68 | BugReporter &BR) const { |
69 | |
70 | |
71 | |
72 | |
73 | |
74 | |
75 | std::vector<CloneDetector::CloneGroup> AllCloneGroups; |
76 | |
77 | Detector.findClones( |
78 | AllCloneGroups, FilenamePatternConstraint(IgnoredFilesPattern), |
79 | RecursiveCloneTypeIIHashConstraint(), MinGroupSizeConstraint(2), |
80 | MinComplexityConstraint(MinComplexity), |
81 | RecursiveCloneTypeIIVerifyConstraint(), OnlyLargestCloneConstraint()); |
82 | |
83 | reportSuspiciousClones(BR, Mgr, AllCloneGroups); |
84 | |
85 | |
86 | |
87 | if (!ReportNormalClones) |
88 | return; |
89 | |
90 | |
91 | |
92 | CloneDetector::constrainClones(AllCloneGroups, |
93 | MatchingVariablePatternConstraint(), |
94 | MinGroupSizeConstraint(2)); |
95 | |
96 | reportClones(BR, Mgr, AllCloneGroups); |
97 | } |
98 | |
99 | static PathDiagnosticLocation makeLocation(const StmtSequence &S, |
100 | AnalysisManager &Mgr) { |
101 | ASTContext &ACtx = Mgr.getASTContext(); |
102 | return PathDiagnosticLocation::createBegin( |
103 | S.front(), ACtx.getSourceManager(), |
104 | Mgr.getAnalysisDeclContext(ACtx.getTranslationUnitDecl())); |
105 | } |
106 | |
107 | void CloneChecker::reportClones( |
108 | BugReporter &BR, AnalysisManager &Mgr, |
109 | std::vector<CloneDetector::CloneGroup> &CloneGroups) const { |
110 | |
111 | if (!BT_Exact) |
112 | BT_Exact.reset(new BugType(this, "Exact code clone", "Code clone")); |
113 | |
114 | for (const CloneDetector::CloneGroup &Group : CloneGroups) { |
115 | |
116 | |
117 | auto R = llvm::make_unique<BugReport>(*BT_Exact, "Duplicate code detected", |
118 | makeLocation(Group.front(), Mgr)); |
119 | R->addRange(Group.front().getSourceRange()); |
120 | |
121 | for (unsigned i = 1; i < Group.size(); ++i) |
122 | R->addNote("Similar code here", makeLocation(Group[i], Mgr), |
123 | Group[i].getSourceRange()); |
124 | BR.emitReport(std::move(R)); |
125 | } |
126 | } |
127 | |
128 | void CloneChecker::reportSuspiciousClones( |
129 | BugReporter &BR, AnalysisManager &Mgr, |
130 | std::vector<CloneDetector::CloneGroup> &CloneGroups) const { |
131 | std::vector<VariablePattern::SuspiciousClonePair> Pairs; |
132 | |
133 | for (const CloneDetector::CloneGroup &Group : CloneGroups) { |
134 | for (unsigned i = 0; i < Group.size(); ++i) { |
135 | VariablePattern PatternA(Group[i]); |
136 | |
137 | for (unsigned j = i + 1; j < Group.size(); ++j) { |
138 | VariablePattern PatternB(Group[j]); |
139 | |
140 | VariablePattern::SuspiciousClonePair ClonePair; |
141 | |
142 | |
143 | |
144 | |
145 | |
146 | |
147 | |
148 | |
149 | if (PatternA.countPatternDifferences(PatternB, &ClonePair) == 1) { |
150 | Pairs.push_back(ClonePair); |
151 | break; |
152 | } |
153 | } |
154 | } |
155 | } |
156 | |
157 | if (!BT_Suspicious) |
158 | BT_Suspicious.reset( |
159 | new BugType(this, "Suspicious code clone", "Code clone")); |
160 | |
161 | ASTContext &ACtx = BR.getContext(); |
162 | SourceManager &SM = ACtx.getSourceManager(); |
163 | AnalysisDeclContext *ADC = |
164 | Mgr.getAnalysisDeclContext(ACtx.getTranslationUnitDecl()); |
165 | |
166 | for (VariablePattern::SuspiciousClonePair &Pair : Pairs) { |
167 | |
168 | |
169 | |
170 | |
171 | |
172 | auto R = llvm::make_unique<BugReport>( |
173 | *BT_Suspicious, |
174 | "Potential copy-paste error; did you really mean to use '" + |
175 | Pair.FirstCloneInfo.Variable->getNameAsString() + "' here?", |
176 | PathDiagnosticLocation::createBegin(Pair.FirstCloneInfo.Mention, SM, |
177 | ADC)); |
178 | R->addRange(Pair.FirstCloneInfo.Mention->getSourceRange()); |
179 | |
180 | R->addNote("Similar code using '" + |
181 | Pair.SecondCloneInfo.Variable->getNameAsString() + "' here", |
182 | PathDiagnosticLocation::createBegin(Pair.SecondCloneInfo.Mention, |
183 | SM, ADC), |
184 | Pair.SecondCloneInfo.Mention->getSourceRange()); |
185 | |
186 | BR.emitReport(std::move(R)); |
187 | } |
188 | } |
189 | |
190 | |
191 | |
192 | |
193 | |
194 | void ento::registerCloneChecker(CheckerManager &Mgr) { |
195 | auto *Checker = Mgr.registerChecker<CloneChecker>(); |
196 | |
197 | Checker->MinComplexity = Mgr.getAnalyzerOptions().getCheckerIntegerOption( |
198 | Checker, "MinimumCloneComplexity", 50); |
199 | |
200 | if (Checker->MinComplexity < 0) |
201 | Mgr.reportInvalidCheckerOptionValue( |
202 | Checker, "MinimumCloneComplexity", "a non-negative value"); |
203 | |
204 | Checker->ReportNormalClones = Mgr.getAnalyzerOptions().getCheckerBooleanOption( |
205 | Checker, "ReportNormalClones", true); |
206 | |
207 | Checker->IgnoredFilesPattern = Mgr.getAnalyzerOptions() |
208 | .getCheckerStringOption(Checker, "IgnoredFilesPattern", ""); |
209 | } |
210 | |
211 | bool ento::shouldRegisterCloneChecker(const LangOptions &LO) { |
212 | return true; |
213 | } |
214 | |