Skip to content

Commit 8095b9c

Browse files
authored
[clang analysis] ExprMutationAnalyzer avoid infinite recursion for recursive forwarding reference (#87954)
1 parent 6254b6d commit 8095b9c

File tree

5 files changed

+84
-15
lines changed

5 files changed

+84
-15
lines changed

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,10 @@ Changes in existing checks
221221
<clang-tidy/checks/llvm/header-guard>` check by replacing the local
222222
option `HeaderFileExtensions` by the global option of the same name.
223223

224+
- Improved :doc:`misc-const-correctness
225+
<clang-tidy/checks/misc/const-correctness>` check by avoiding infinite recursion
226+
for recursive forwarding reference.
227+
224228
- Improved :doc:`misc-definitions-in-headers
225229
<clang-tidy/checks/misc/definitions-in-headers>` check by replacing the local
226230
option `HeaderFileExtensions` by the global option of the same name.

clang-tools-extra/test/clang-tidy/checkers/misc/const-correctness-templates.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,18 @@ void concatenate3(Args... args)
5858
(..., (stream << args));
5959
}
6060
} // namespace gh70323
61+
62+
namespace gh60895 {
63+
64+
template <class T> void f1(T &&a);
65+
template <class T> void f2(T &&a);
66+
template <class T> void f1(T &&a) { f2<T>(a); }
67+
template <class T> void f2(T &&a) { f1<T>(a); }
68+
void f() {
69+
int x = 0;
70+
// CHECK-MESSAGES:[[@LINE-1]]:3: warning: variable 'x' of type 'int' can be declared 'const'
71+
// CHECK-FIXES: int const x = 0;
72+
f1(x);
73+
}
74+
75+
} // namespace gh60895

clang/include/clang/Analysis/Analyses/ExprMutationAnalyzer.h

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,10 @@
88
#ifndef LLVM_CLANG_ANALYSIS_ANALYSES_EXPRMUTATIONANALYZER_H
99
#define LLVM_CLANG_ANALYSIS_ANALYSES_EXPRMUTATIONANALYZER_H
1010

11-
#include <type_traits>
12-
1311
#include "clang/AST/AST.h"
1412
#include "clang/ASTMatchers/ASTMatchers.h"
1513
#include "llvm/ADT/DenseMap.h"
14+
#include <variant>
1615

1716
namespace clang {
1817

@@ -22,8 +21,15 @@ class FunctionParmMutationAnalyzer;
2221
/// a given statement.
2322
class ExprMutationAnalyzer {
2423
public:
24+
friend class FunctionParmMutationAnalyzer;
25+
struct Cache {
26+
llvm::SmallDenseMap<const FunctionDecl *,
27+
std::unique_ptr<FunctionParmMutationAnalyzer>>
28+
FuncParmAnalyzer;
29+
};
30+
2531
ExprMutationAnalyzer(const Stmt &Stm, ASTContext &Context)
26-
: Stm(Stm), Context(Context) {}
32+
: ExprMutationAnalyzer(Stm, Context, std::make_shared<Cache>()) {}
2733

2834
bool isMutated(const Expr *Exp) { return findMutation(Exp) != nullptr; }
2935
bool isMutated(const Decl *Dec) { return findMutation(Dec) != nullptr; }
@@ -45,6 +51,11 @@ class ExprMutationAnalyzer {
4551
using MutationFinder = const Stmt *(ExprMutationAnalyzer::*)(const Expr *);
4652
using ResultMap = llvm::DenseMap<const Expr *, const Stmt *>;
4753

54+
ExprMutationAnalyzer(const Stmt &Stm, ASTContext &Context,
55+
std::shared_ptr<Cache> CrossAnalysisCache)
56+
: Stm(Stm), Context(Context),
57+
CrossAnalysisCache(std::move(CrossAnalysisCache)) {}
58+
4859
const Stmt *findMutationMemoized(const Expr *Exp,
4960
llvm::ArrayRef<MutationFinder> Finders,
5061
ResultMap &MemoizedResults);
@@ -69,9 +80,7 @@ class ExprMutationAnalyzer {
6980

7081
const Stmt &Stm;
7182
ASTContext &Context;
72-
llvm::DenseMap<const FunctionDecl *,
73-
std::unique_ptr<FunctionParmMutationAnalyzer>>
74-
FuncParmAnalyzer;
83+
std::shared_ptr<Cache> CrossAnalysisCache;
7584
ResultMap Results;
7685
ResultMap PointeeResults;
7786
};
@@ -80,7 +89,12 @@ class ExprMutationAnalyzer {
8089
// params.
8190
class FunctionParmMutationAnalyzer {
8291
public:
83-
FunctionParmMutationAnalyzer(const FunctionDecl &Func, ASTContext &Context);
92+
FunctionParmMutationAnalyzer(const FunctionDecl &Func, ASTContext &Context)
93+
: FunctionParmMutationAnalyzer(
94+
Func, Context, std::make_shared<ExprMutationAnalyzer::Cache>()) {}
95+
FunctionParmMutationAnalyzer(
96+
const FunctionDecl &Func, ASTContext &Context,
97+
std::shared_ptr<ExprMutationAnalyzer::Cache> CrossAnalysisCache);
8498

8599
bool isMutated(const ParmVarDecl *Parm) {
86100
return findMutation(Parm) != nullptr;

clang/lib/Analysis/ExprMutationAnalyzer.cpp

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -638,9 +638,10 @@ const Stmt *ExprMutationAnalyzer::findFunctionArgMutation(const Expr *Exp) {
638638
if (!RefType->getPointeeType().getQualifiers() &&
639639
RefType->getPointeeType()->getAs<TemplateTypeParmType>()) {
640640
std::unique_ptr<FunctionParmMutationAnalyzer> &Analyzer =
641-
FuncParmAnalyzer[Func];
641+
CrossAnalysisCache->FuncParmAnalyzer[Func];
642642
if (!Analyzer)
643-
Analyzer.reset(new FunctionParmMutationAnalyzer(*Func, Context));
643+
Analyzer.reset(new FunctionParmMutationAnalyzer(*Func, Context,
644+
CrossAnalysisCache));
644645
if (Analyzer->findMutation(Parm))
645646
return Exp;
646647
continue;
@@ -653,13 +654,15 @@ const Stmt *ExprMutationAnalyzer::findFunctionArgMutation(const Expr *Exp) {
653654
}
654655

655656
FunctionParmMutationAnalyzer::FunctionParmMutationAnalyzer(
656-
const FunctionDecl &Func, ASTContext &Context)
657-
: BodyAnalyzer(*Func.getBody(), Context) {
657+
const FunctionDecl &Func, ASTContext &Context,
658+
std::shared_ptr<ExprMutationAnalyzer::Cache> CrossAnalysisCache)
659+
: BodyAnalyzer(*Func.getBody(), Context, CrossAnalysisCache) {
658660
if (const auto *Ctor = dyn_cast<CXXConstructorDecl>(&Func)) {
659661
// CXXCtorInitializer might also mutate Param but they're not part of
660662
// function body, check them eagerly here since they're typically trivial.
661663
for (const CXXCtorInitializer *Init : Ctor->inits()) {
662-
ExprMutationAnalyzer InitAnalyzer(*Init->getInit(), Context);
664+
ExprMutationAnalyzer InitAnalyzer(*Init->getInit(), Context,
665+
CrossAnalysisCache);
663666
for (const ParmVarDecl *Parm : Ctor->parameters()) {
664667
if (Results.contains(Parm))
665668
continue;
@@ -675,11 +678,14 @@ FunctionParmMutationAnalyzer::findMutation(const ParmVarDecl *Parm) {
675678
const auto Memoized = Results.find(Parm);
676679
if (Memoized != Results.end())
677680
return Memoized->second;
678-
681+
// To handle call A -> call B -> call A. Assume parameters of A is not mutated
682+
// before analyzing parameters of A. Then when analyzing the second "call A",
683+
// FunctionParmMutationAnalyzer can use this memoized value to avoid infinite
684+
// recursion.
685+
Results[Parm] = nullptr;
679686
if (const Stmt *S = BodyAnalyzer.findMutation(Parm))
680687
return Results[Parm] = S;
681-
682-
return Results[Parm] = nullptr;
688+
return Results[Parm];
683689
}
684690

685691
} // namespace clang

clang/unittests/Analysis/ExprMutationAnalyzerTest.cpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -977,6 +977,36 @@ TEST(ExprMutationAnalyzerTest, FollowFuncArgModified) {
977977
"void f() { int x; g(x); }");
978978
Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
979979
EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("g(x)"));
980+
981+
AST = buildASTFromCode(
982+
StdRemoveReference + StdForward +
983+
"template <class T> void f1(T &&a);"
984+
"template <class T> void f2(T &&a);"
985+
"template <class T> void f1(T &&a) { f2<T>(std::forward<T>(a)); }"
986+
"template <class T> void f2(T &&a) { f1<T>(std::forward<T>(a)); }"
987+
"void f() { int x; f1(x); }");
988+
Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
989+
EXPECT_FALSE(isMutated(Results, AST.get()));
990+
991+
AST = buildASTFromCode(
992+
StdRemoveReference + StdForward +
993+
"template <class T> void f1(T &&a);"
994+
"template <class T> void f2(T &&a);"
995+
"template <class T> void f1(T &&a) { f2<T>(std::forward<T>(a)); }"
996+
"template <class T> void f2(T &&a) { f1<T>(std::forward<T>(a)); a++; }"
997+
"void f() { int x; f1(x); }");
998+
Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
999+
EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("f1(x)"));
1000+
1001+
AST = buildASTFromCode(
1002+
StdRemoveReference + StdForward +
1003+
"template <class T> void f1(T &&a);"
1004+
"template <class T> void f2(T &&a);"
1005+
"template <class T> void f1(T &&a) { f2<T>(std::forward<T>(a)); a++; }"
1006+
"template <class T> void f2(T &&a) { f1<T>(std::forward<T>(a)); }"
1007+
"void f() { int x; f1(x); }");
1008+
Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
1009+
EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("f1(x)"));
9801010
}
9811011

9821012
TEST(ExprMutationAnalyzerTest, FollowFuncArgNotModified) {

0 commit comments

Comments
 (0)