Skip to content

Commit 5161a3f

Browse files
authored
[clang][dataflow] Rewrite getReferencedDecls() with a RecursiveASTVisitor. (#93461)
We previously had a hand-rolled recursive traversal here that was exactly what `RecursiveASTVistor` does anyway. Using the visitor not only eliminates the explicit traversal logic but also allows us to introduce a common visitor base class for `getReferencedDecls()` and `ResultObjectVisitor`, ensuring that the two are consistent in terms of the nodes they visit. Inconsistency between these two has caused crashes in the past when `ResultObjectVisitor` tried to propagate result object locations to entities that weren't modeled becasue `getReferencedDecls()` didn't visit them.
1 parent aaa4ff8 commit 5161a3f

File tree

3 files changed

+108
-90
lines changed

3 files changed

+108
-90
lines changed

clang/include/clang/Analysis/FlowSensitive/ASTOps.h

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
#include "clang/AST/Decl.h"
1717
#include "clang/AST/Expr.h"
18+
#include "clang/AST/RecursiveASTVisitor.h"
1819
#include "clang/AST/Type.h"
1920
#include "clang/Analysis/FlowSensitive/StorageLocation.h"
2021
#include "llvm/ADT/DenseSet.h"
@@ -80,6 +81,52 @@ class RecordInitListHelper {
8081
std::optional<ImplicitValueInitExpr> ImplicitValueInitForUnion;
8182
};
8283

84+
/// Specialization of `RecursiveASTVisitor` that visits those nodes that are
85+
/// relevant to the dataflow analysis; generally, these are the ones that also
86+
/// appear in the CFG.
87+
/// To start the traversal, call `TraverseStmt()` on the statement or body of
88+
/// the function to analyze. Don't call `TraverseDecl()` on the function itself;
89+
/// this won't work as `TraverseDecl()` contains code to avoid traversing nested
90+
/// functions.
91+
template <class Derived>
92+
class AnalysisASTVisitor : public RecursiveASTVisitor<Derived> {
93+
public:
94+
bool shouldVisitImplicitCode() { return true; }
95+
96+
bool shouldVisitLambdaBody() const { return false; }
97+
98+
bool TraverseDecl(Decl *D) {
99+
// Don't traverse nested record or function declarations.
100+
// - We won't be analyzing code contained in these anyway
101+
// - We don't model fields that are used only in these nested declaration,
102+
// so trying to propagate a result object to initializers of such fields
103+
// would cause an error.
104+
if (isa_and_nonnull<RecordDecl>(D) || isa_and_nonnull<FunctionDecl>(D))
105+
return true;
106+
107+
return RecursiveASTVisitor<Derived>::TraverseDecl(D);
108+
}
109+
110+
// Don't traverse expressions in unevaluated contexts, as we don't model
111+
// fields that are only used in these.
112+
// Note: The operand of the `noexcept` operator is an unevaluated operand, but
113+
// nevertheless it appears in the Clang CFG, so we don't exclude it here.
114+
bool TraverseDecltypeTypeLoc(DecltypeTypeLoc) { return true; }
115+
bool TraverseTypeOfExprTypeLoc(TypeOfExprTypeLoc) { return true; }
116+
bool TraverseCXXTypeidExpr(CXXTypeidExpr *) { return true; }
117+
bool TraverseUnaryExprOrTypeTraitExpr(UnaryExprOrTypeTraitExpr *) {
118+
return true;
119+
}
120+
121+
bool TraverseBindingDecl(BindingDecl *BD) {
122+
// `RecursiveASTVisitor` doesn't traverse holding variables for
123+
// `BindingDecl`s by itself, so we need to tell it to.
124+
if (VarDecl *HoldingVar = BD->getHoldingVar())
125+
TraverseDecl(HoldingVar);
126+
return RecursiveASTVisitor<Derived>::TraverseBindingDecl(BD);
127+
}
128+
};
129+
83130
/// A collection of several types of declarations, all referenced from the same
84131
/// function.
85132
struct ReferencedDecls {

clang/lib/Analysis/FlowSensitive/ASTOps.cpp

Lines changed: 60 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -188,90 +188,96 @@ static MemberExpr *getMemberForAccessor(const CXXMemberCallExpr &C) {
188188
return nullptr;
189189
}
190190

191-
static void getReferencedDecls(const Decl &D, ReferencedDecls &Referenced) {
192-
insertIfGlobal(D, Referenced.Globals);
193-
insertIfFunction(D, Referenced.Functions);
194-
if (const auto *Decomp = dyn_cast<DecompositionDecl>(&D))
195-
for (const auto *B : Decomp->bindings())
196-
if (auto *ME = dyn_cast_or_null<MemberExpr>(B->getBinding()))
197-
// FIXME: should we be using `E->getFoundDecl()`?
198-
if (const auto *FD = dyn_cast<FieldDecl>(ME->getMemberDecl()))
199-
Referenced.Fields.insert(FD);
200-
}
191+
class ReferencedDeclsVisitor
192+
: public AnalysisASTVisitor<ReferencedDeclsVisitor> {
193+
public:
194+
ReferencedDeclsVisitor(ReferencedDecls &Referenced)
195+
: Referenced(Referenced) {}
196+
197+
void TraverseConstructorInits(const CXXConstructorDecl *Ctor) {
198+
for (const CXXCtorInitializer *Init : Ctor->inits()) {
199+
if (Init->isMemberInitializer()) {
200+
Referenced.Fields.insert(Init->getMember());
201+
} else if (Init->isIndirectMemberInitializer()) {
202+
for (const auto *I : Init->getIndirectMember()->chain())
203+
Referenced.Fields.insert(cast<FieldDecl>(I));
204+
}
205+
206+
Expr *InitExpr = Init->getInit();
207+
208+
// Also collect declarations referenced in `InitExpr`.
209+
TraverseStmt(InitExpr);
201210

202-
/// Traverses `S` and inserts into `Referenced` any declarations that are
203-
/// declared in or referenced from sub-statements.
204-
static void getReferencedDecls(const Stmt &S, ReferencedDecls &Referenced) {
205-
for (auto *Child : S.children())
206-
if (Child != nullptr)
207-
getReferencedDecls(*Child, Referenced);
208-
if (const auto *DefaultArg = dyn_cast<CXXDefaultArgExpr>(&S))
209-
getReferencedDecls(*DefaultArg->getExpr(), Referenced);
210-
if (const auto *DefaultInit = dyn_cast<CXXDefaultInitExpr>(&S))
211-
getReferencedDecls(*DefaultInit->getExpr(), Referenced);
212-
213-
if (auto *DS = dyn_cast<DeclStmt>(&S)) {
214-
if (DS->isSingleDecl())
215-
getReferencedDecls(*DS->getSingleDecl(), Referenced);
216-
else
217-
for (auto *D : DS->getDeclGroup())
218-
getReferencedDecls(*D, Referenced);
219-
} else if (auto *E = dyn_cast<DeclRefExpr>(&S)) {
211+
// If this is a `CXXDefaultInitExpr`, also collect declarations referenced
212+
// within the default expression.
213+
if (auto *DefaultInit = dyn_cast<CXXDefaultInitExpr>(InitExpr))
214+
TraverseStmt(DefaultInit->getExpr());
215+
}
216+
}
217+
218+
bool VisitDecl(Decl *D) {
219+
insertIfGlobal(*D, Referenced.Globals);
220+
insertIfFunction(*D, Referenced.Functions);
221+
return true;
222+
}
223+
224+
bool VisitDeclRefExpr(DeclRefExpr *E) {
220225
insertIfGlobal(*E->getDecl(), Referenced.Globals);
221226
insertIfFunction(*E->getDecl(), Referenced.Functions);
222-
} else if (const auto *C = dyn_cast<CXXMemberCallExpr>(&S)) {
227+
return true;
228+
}
229+
230+
bool VisitCXXMemberCallExpr(CXXMemberCallExpr *C) {
223231
// If this is a method that returns a member variable but does nothing else,
224232
// model the field of the return value.
225233
if (MemberExpr *E = getMemberForAccessor(*C))
226234
if (const auto *FD = dyn_cast<FieldDecl>(E->getMemberDecl()))
227235
Referenced.Fields.insert(FD);
228-
} else if (auto *E = dyn_cast<MemberExpr>(&S)) {
236+
return true;
237+
}
238+
239+
bool VisitMemberExpr(MemberExpr *E) {
229240
// FIXME: should we be using `E->getFoundDecl()`?
230241
const ValueDecl *VD = E->getMemberDecl();
231242
insertIfGlobal(*VD, Referenced.Globals);
232243
insertIfFunction(*VD, Referenced.Functions);
233244
if (const auto *FD = dyn_cast<FieldDecl>(VD))
234245
Referenced.Fields.insert(FD);
235-
} else if (auto *InitList = dyn_cast<InitListExpr>(&S)) {
246+
return true;
247+
}
248+
249+
bool VisitInitListExpr(InitListExpr *InitList) {
236250
if (InitList->getType()->isRecordType())
237251
for (const auto *FD : getFieldsForInitListExpr(InitList))
238252
Referenced.Fields.insert(FD);
239-
} else if (auto *ParenInitList = dyn_cast<CXXParenListInitExpr>(&S)) {
253+
return true;
254+
}
255+
256+
bool VisitCXXParenListInitExpr(CXXParenListInitExpr *ParenInitList) {
240257
if (ParenInitList->getType()->isRecordType())
241258
for (const auto *FD : getFieldsForInitListExpr(ParenInitList))
242259
Referenced.Fields.insert(FD);
260+
return true;
243261
}
244-
}
262+
263+
private:
264+
ReferencedDecls &Referenced;
265+
};
245266

246267
ReferencedDecls getReferencedDecls(const FunctionDecl &FD) {
247268
ReferencedDecls Result;
248-
// Look for global variable and field references in the
249-
// constructor-initializers.
250-
if (const auto *CtorDecl = dyn_cast<CXXConstructorDecl>(&FD)) {
251-
for (const auto *Init : CtorDecl->inits()) {
252-
if (Init->isMemberInitializer()) {
253-
Result.Fields.insert(Init->getMember());
254-
} else if (Init->isIndirectMemberInitializer()) {
255-
for (const auto *I : Init->getIndirectMember()->chain())
256-
Result.Fields.insert(cast<FieldDecl>(I));
257-
}
258-
const Expr *E = Init->getInit();
259-
assert(E != nullptr);
260-
getReferencedDecls(*E, Result);
261-
}
262-
// Add all fields mentioned in default member initializers.
263-
for (const FieldDecl *F : CtorDecl->getParent()->fields())
264-
if (const auto *I = F->getInClassInitializer())
265-
getReferencedDecls(*I, Result);
266-
}
267-
getReferencedDecls(*FD.getBody(), Result);
269+
ReferencedDeclsVisitor Visitor(Result);
270+
Visitor.TraverseStmt(FD.getBody());
271+
if (const auto *CtorDecl = dyn_cast<CXXConstructorDecl>(&FD))
272+
Visitor.TraverseConstructorInits(CtorDecl);
268273

269274
return Result;
270275
}
271276

272277
ReferencedDecls getReferencedDecls(const Stmt &S) {
273278
ReferencedDecls Result;
274-
getReferencedDecls(S, Result);
279+
ReferencedDeclsVisitor Visitor(Result);
280+
Visitor.TraverseStmt(const_cast<Stmt *>(&S));
275281
return Result;
276282
}
277283

clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp

Lines changed: 1 addition & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ namespace {
297297
// Visitor that builds a map from record prvalues to result objects.
298298
// For each result object that it encounters, it propagates the storage location
299299
// of the result object to all record prvalues that can initialize it.
300-
class ResultObjectVisitor : public RecursiveASTVisitor<ResultObjectVisitor> {
300+
class ResultObjectVisitor : public AnalysisASTVisitor<ResultObjectVisitor> {
301301
public:
302302
// `ResultObjectMap` will be filled with a map from record prvalues to result
303303
// object. If this visitor will traverse a function that returns a record by
@@ -310,10 +310,6 @@ class ResultObjectVisitor : public RecursiveASTVisitor<ResultObjectVisitor> {
310310
: ResultObjectMap(ResultObjectMap),
311311
LocForRecordReturnVal(LocForRecordReturnVal), DACtx(DACtx) {}
312312

313-
bool shouldVisitImplicitCode() { return true; }
314-
315-
bool shouldVisitLambdaBody() const { return false; }
316-
317313
// Traverse all member and base initializers of `Ctor`. This function is not
318314
// called by `RecursiveASTVisitor`; it should be called manually if we are
319315
// analyzing a constructor. `ThisPointeeLoc` is the storage location that
@@ -342,37 +338,6 @@ class ResultObjectVisitor : public RecursiveASTVisitor<ResultObjectVisitor> {
342338
}
343339
}
344340

345-
bool TraverseDecl(Decl *D) {
346-
// Don't traverse nested record or function declarations.
347-
// - We won't be analyzing code contained in these anyway
348-
// - We don't model fields that are used only in these nested declaration,
349-
// so trying to propagate a result object to initializers of such fields
350-
// would cause an error.
351-
if (isa_and_nonnull<RecordDecl>(D) || isa_and_nonnull<FunctionDecl>(D))
352-
return true;
353-
354-
return RecursiveASTVisitor<ResultObjectVisitor>::TraverseDecl(D);
355-
}
356-
357-
// Don't traverse expressions in unevaluated contexts, as we don't model
358-
// fields that are only used in these.
359-
// Note: The operand of the `noexcept` operator is an unevaluated operand, but
360-
// nevertheless it appears in the Clang CFG, so we don't exclude it here.
361-
bool TraverseDecltypeTypeLoc(DecltypeTypeLoc) { return true; }
362-
bool TraverseTypeOfExprTypeLoc(TypeOfExprTypeLoc) { return true; }
363-
bool TraverseCXXTypeidExpr(CXXTypeidExpr *) { return true; }
364-
bool TraverseUnaryExprOrTypeTraitExpr(UnaryExprOrTypeTraitExpr *) {
365-
return true;
366-
}
367-
368-
bool TraverseBindingDecl(BindingDecl *BD) {
369-
// `RecursiveASTVisitor` doesn't traverse holding variables for
370-
// `BindingDecl`s by itself, so we need to tell it to.
371-
if (VarDecl *HoldingVar = BD->getHoldingVar())
372-
TraverseDecl(HoldingVar);
373-
return RecursiveASTVisitor<ResultObjectVisitor>::TraverseBindingDecl(BD);
374-
}
375-
376341
bool VisitVarDecl(VarDecl *VD) {
377342
if (VD->getType()->isRecordType() && VD->hasInit())
378343
PropagateResultObject(

0 commit comments

Comments
 (0)