1//===---- OverlappingReplacementsTest.cpp - clang-tidy --------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "ClangTidyTest.h"
10#include "clang/AST/RecursiveASTVisitor.h"
11#include "gtest/gtest.h"
12
13namespace clang {
14namespace tidy {
15namespace test {
16namespace {
17
18const char BoundDecl[] = "decl";
19const char BoundIf[] = "if";
20
21// We define a reduced set of very small checks that allow to test different
22// overlapping situations (no overlapping, replacements partially overlap, etc),
23// as well as different kinds of diagnostics (one check produces several errors,
24// several replacement ranges in an error, etc).
25class UseCharCheck : public ClangTidyCheck {
26public:
27 UseCharCheck(StringRef CheckName, ClangTidyContext *Context)
28 : ClangTidyCheck(CheckName, Context) {}
29 void registerMatchers(ast_matchers::MatchFinder *Finder) override {
30 using namespace ast_matchers;
31 Finder->addMatcher(NodeMatch: varDecl(hasType(InnerMatcher: isInteger())).bind(ID: BoundDecl), Action: this);
32 }
33 void check(const ast_matchers::MatchFinder::MatchResult &Result) override {
34 auto *VD = Result.Nodes.getNodeAs<VarDecl>(ID: BoundDecl);
35 diag(VD->getBeginLoc(), "use char") << FixItHint::CreateReplacement(
36 CharSourceRange::getTokenRange(VD->getBeginLoc(), VD->getBeginLoc()),
37 "char");
38 }
39};
40
41class IfFalseCheck : public ClangTidyCheck {
42public:
43 IfFalseCheck(StringRef CheckName, ClangTidyContext *Context)
44 : ClangTidyCheck(CheckName, Context) {}
45 void registerMatchers(ast_matchers::MatchFinder *Finder) override {
46 using namespace ast_matchers;
47 Finder->addMatcher(NodeMatch: ifStmt().bind(ID: BoundIf), Action: this);
48 }
49 void check(const ast_matchers::MatchFinder::MatchResult &Result) override {
50 auto *If = Result.Nodes.getNodeAs<IfStmt>(ID: BoundIf);
51 auto *Cond = If->getCond();
52 SourceRange Range = Cond->getSourceRange();
53 if (auto *D = If->getConditionVariable()) {
54 Range = SourceRange(D->getBeginLoc(), D->getEndLoc());
55 }
56 diag(Loc: Range.getBegin(), Description: "the cake is a lie") << FixItHint::CreateReplacement(
57 RemoveRange: CharSourceRange::getTokenRange(R: Range), Code: "false");
58 }
59};
60
61class RefactorCheck : public ClangTidyCheck {
62public:
63 RefactorCheck(StringRef CheckName, ClangTidyContext *Context)
64 : ClangTidyCheck(CheckName, Context), NamePattern("::$") {}
65 RefactorCheck(StringRef CheckName, ClangTidyContext *Context,
66 StringRef NamePattern)
67 : ClangTidyCheck(CheckName, Context), NamePattern(NamePattern) {}
68 virtual std::string newName(StringRef OldName) = 0;
69
70 void registerMatchers(ast_matchers::MatchFinder *Finder) final {
71 using namespace ast_matchers;
72 Finder->addMatcher(NodeMatch: varDecl(matchesName(RegExp: NamePattern)).bind(ID: BoundDecl), Action: this);
73 }
74
75 void check(const ast_matchers::MatchFinder::MatchResult &Result) final {
76 auto *VD = Result.Nodes.getNodeAs<VarDecl>(ID: BoundDecl);
77 std::string NewName = newName(OldName: VD->getName());
78
79 auto Diag = diag(VD->getLocation(), "refactor %0 into %1")
80 << VD->getName() << NewName
81 << FixItHint::CreateReplacement(
82 CharSourceRange::getTokenRange(VD->getLocation(),
83 VD->getLocation()),
84 NewName);
85
86 class UsageVisitor : public RecursiveASTVisitor<UsageVisitor> {
87 public:
88 UsageVisitor(const ValueDecl *VD, StringRef NewName,
89 DiagnosticBuilder &Diag)
90 : VD(VD), NewName(NewName), Diag(Diag) {}
91 bool VisitDeclRefExpr(DeclRefExpr *E) {
92 if (const ValueDecl *D = E->getDecl()) {
93 if (VD->getCanonicalDecl() == D->getCanonicalDecl()) {
94 Diag << FixItHint::CreateReplacement(
95 CharSourceRange::getTokenRange(E->getSourceRange()), NewName);
96 }
97 }
98 return RecursiveASTVisitor<UsageVisitor>::VisitDeclRefExpr(E);
99 }
100
101 private:
102 const ValueDecl *VD;
103 StringRef NewName;
104 DiagnosticBuilder &Diag;
105 };
106
107 UsageVisitor(VD, NewName, Diag)
108 .TraverseDecl(Result.Context->getTranslationUnitDecl());
109 }
110
111protected:
112 const std::string NamePattern;
113};
114
115class StartsWithPotaCheck : public RefactorCheck {
116public:
117 StartsWithPotaCheck(StringRef CheckName, ClangTidyContext *Context)
118 : RefactorCheck(CheckName, Context, "::pota") {}
119
120 std::string newName(StringRef OldName) override {
121 return "toma" + OldName.substr(Start: 4).str();
122 }
123};
124
125class EndsWithTatoCheck : public RefactorCheck {
126public:
127 EndsWithTatoCheck(StringRef CheckName, ClangTidyContext *Context)
128 : RefactorCheck(CheckName, Context, "tato$") {}
129
130 std::string newName(StringRef OldName) override {
131 return OldName.substr(Start: 0, N: OldName.size() - 4).str() + "melo";
132 }
133};
134
135} // namespace
136
137TEST(OverlappingReplacementsTest, UseCharCheckTest) {
138 const char Code[] =
139 R"(void f() {
140 int a = 0;
141 if (int b = 0) {
142 int c = a;
143 }
144})";
145
146 const char CharFix[] =
147 R"(void f() {
148 char a = 0;
149 if (char b = 0) {
150 char c = a;
151 }
152})";
153 EXPECT_EQ(CharFix, runCheckOnCode<UseCharCheck>(Code));
154}
155
156TEST(OverlappingReplacementsTest, IfFalseCheckTest) {
157 const char Code[] =
158 R"(void f() {
159 int potato = 0;
160 if (int b = 0) {
161 int c = potato;
162 } else if (true) {
163 int d = 0;
164 }
165})";
166
167 const char IfFix[] =
168 R"(void f() {
169 int potato = 0;
170 if (false) {
171 int c = potato;
172 } else if (false) {
173 int d = 0;
174 }
175})";
176 EXPECT_EQ(IfFix, runCheckOnCode<IfFalseCheck>(Code));
177}
178
179TEST(OverlappingReplacementsTest, StartsWithCheckTest) {
180 const char Code[] =
181 R"(void f() {
182 int a = 0;
183 int potato = 0;
184 if (int b = 0) {
185 int c = potato;
186 } else if (true) {
187 int d = 0;
188 }
189})";
190
191 const char StartsFix[] =
192 R"(void f() {
193 int a = 0;
194 int tomato = 0;
195 if (int b = 0) {
196 int c = tomato;
197 } else if (true) {
198 int d = 0;
199 }
200})";
201 EXPECT_EQ(StartsFix, runCheckOnCode<StartsWithPotaCheck>(Code));
202}
203
204TEST(OverlappingReplacementsTest, EndsWithCheckTest) {
205 const char Code[] =
206 R"(void f() {
207 int a = 0;
208 int potato = 0;
209 if (int b = 0) {
210 int c = potato;
211 } else if (true) {
212 int d = 0;
213 }
214})";
215
216 const char EndsFix[] =
217 R"(void f() {
218 int a = 0;
219 int pomelo = 0;
220 if (int b = 0) {
221 int c = pomelo;
222 } else if (true) {
223 int d = 0;
224 }
225})";
226 EXPECT_EQ(EndsFix, runCheckOnCode<EndsWithTatoCheck>(Code));
227}
228
229TEST(OverlappingReplacementTest, ReplacementsDoNotOverlap) {
230 std::string Res;
231 const char Code[] =
232 R"(void f() {
233 int potassium = 0;
234 if (true) {
235 int Potato = potassium;
236 }
237})";
238
239 const char CharIfFix[] =
240 R"(void f() {
241 char potassium = 0;
242 if (false) {
243 char Potato = potassium;
244 }
245})";
246 Res = runCheckOnCode<UseCharCheck, IfFalseCheck>(Code);
247 EXPECT_EQ(CharIfFix, Res);
248
249 const char StartsEndsFix[] =
250 R"(void f() {
251 int tomassium = 0;
252 if (true) {
253 int Pomelo = tomassium;
254 }
255})";
256 Res = runCheckOnCode<StartsWithPotaCheck, EndsWithTatoCheck>(Code);
257 EXPECT_EQ(StartsEndsFix, Res);
258
259 const char CharIfStartsEndsFix[] =
260 R"(void f() {
261 char tomassium = 0;
262 if (false) {
263 char Pomelo = tomassium;
264 }
265})";
266 Res = runCheckOnCode<UseCharCheck, IfFalseCheck, StartsWithPotaCheck,
267 EndsWithTatoCheck>(Code);
268 EXPECT_EQ(CharIfStartsEndsFix, Res);
269}
270
271TEST(OverlappingReplacementsTest, ReplacementInsideOtherReplacement) {
272 std::string Res;
273 const char Code[] =
274 R"(void f() {
275 if (char potato = 0) {
276 } else if (int a = 0) {
277 char potato = 0;
278 if (potato) potato;
279 }
280})";
281
282 // Apply the UseCharCheck together with the IfFalseCheck.
283 //
284 // The 'If' fix contains the other, so that is the one that has to be applied.
285 // } else if (int a = 0) {
286 // ^^^ -> char
287 // ~~~~~~~~~ -> false
288 const char CharIfFix[] =
289 R"(void f() {
290 if (false) {
291 } else if (false) {
292 char potato = 0;
293 if (false) potato;
294 }
295})";
296 Res = runCheckOnCode<UseCharCheck, IfFalseCheck>(Code);
297 EXPECT_EQ(CharIfFix, Res);
298 Res = runCheckOnCode<IfFalseCheck, UseCharCheck>(Code);
299 EXPECT_EQ(CharIfFix, Res);
300
301 // Apply the IfFalseCheck with the StartsWithPotaCheck.
302 //
303 // The 'If' replacement is bigger here.
304 // if (char potato = 0) {
305 // ^^^^^^ -> tomato
306 // ~~~~~~~~~~~~~~~ -> false
307 //
308 // But the refactoring is the one that contains the other here:
309 // char potato = 0;
310 // ^^^^^^ -> tomato
311 // if (potato) potato;
312 // ^^^^^^ ^^^^^^ -> tomato, tomato
313 // ~~~~~~ -> false
314 const char IfStartsFix[] =
315 R"(void f() {
316 if (false) {
317 } else if (false) {
318 char tomato = 0;
319 if (tomato) tomato;
320 }
321})";
322 Res = runCheckOnCode<IfFalseCheck, StartsWithPotaCheck>(Code);
323 EXPECT_EQ(IfStartsFix, Res);
324 Res = runCheckOnCode<StartsWithPotaCheck, IfFalseCheck>(Code);
325 EXPECT_EQ(IfStartsFix, Res);
326}
327
328TEST(OverlappingReplacements, TwoReplacementsInsideOne) {
329 std::string Res;
330 const char Code[] =
331 R"(void f() {
332 if (int potato = 0) {
333 int a = 0;
334 }
335})";
336
337 // The two smallest replacements should not be applied.
338 // if (int potato = 0) {
339 // ^^^^^^ -> tomato
340 // *** -> char
341 // ~~~~~~~~~~~~~~ -> false
342 // But other errors from the same checks should not be affected.
343 // int a = 0;
344 // *** -> char
345 const char Fix[] =
346 R"(void f() {
347 if (false) {
348 char a = 0;
349 }
350})";
351 Res = runCheckOnCode<UseCharCheck, IfFalseCheck, StartsWithPotaCheck>(Code);
352 EXPECT_EQ(Fix, Res);
353 Res = runCheckOnCode<StartsWithPotaCheck, IfFalseCheck, UseCharCheck>(Code);
354 EXPECT_EQ(Fix, Res);
355}
356
357TEST(OverlappingReplacementsTest,
358 ApplyAtMostOneOfTheChangesWhenPartialOverlapping) {
359 std::string Res;
360 const char Code[] =
361 R"(void f() {
362 if (int potato = 0) {
363 int a = potato;
364 }
365})";
366
367 // These two replacements overlap, but none of them is completely contained
368 // inside the other.
369 // if (int potato = 0) {
370 // ^^^^^^ -> tomato
371 // ~~~~~~~~~~~~~~ -> false
372 // int a = potato;
373 // ^^^^^^ -> tomato
374 //
375 // The 'StartsWithPotaCheck' fix has endpoints inside the 'IfFalseCheck' fix,
376 // so it is going to be set as inapplicable. The 'if' fix will be applied.
377 const char IfFix[] =
378 R"(void f() {
379 if (false) {
380 int a = potato;
381 }
382})";
383 Res = runCheckOnCode<IfFalseCheck, StartsWithPotaCheck>(Code);
384 EXPECT_EQ(IfFix, Res);
385}
386
387TEST(OverlappingReplacementsTest, TwoErrorsHavePerfectOverlapping) {
388 std::string Res;
389 const char Code[] =
390 R"(void f() {
391 int potato = 0;
392 potato += potato * potato;
393 if (char a = potato) potato;
394})";
395
396 // StartsWithPotaCheck will try to refactor 'potato' into 'tomato', and
397 // EndsWithTatoCheck will try to use 'pomelo'. Both fixes have the same set of
398 // ranges. This is a corner case of one error completely containing another:
399 // the other completely contains the first one as well. Both errors are
400 // discarded.
401
402 Res = runCheckOnCode<StartsWithPotaCheck, EndsWithTatoCheck>(Code);
403 EXPECT_EQ(Code, Res);
404}
405
406} // namespace test
407} // namespace tidy
408} // namespace clang
409

source code of clang-tools-extra/unittests/clang-tidy/OverlappingReplacementsTest.cpp