Skip to content

Commit 6aca603

Browse files
committed
[AST] Include the TranslationUnitDecl when traversing with TraversalScope
Given `int foo, bar;`, TraverseAST reveals this tree: TranslationUnitDecl - foo - bar Before this patch, with the TraversalScope set to {foo}, TraverseAST yields: foo After this patch it yields: TranslationUnitDecl - foo Also, TraverseDecl(TranslationUnitDecl) now respects the traversal scope. --- The main effect of this today is that clang-tidy checks that match the translationUnitDecl(), either in order to traverse it or check parentage, should work. Differential Revision: https://reviews.llvm.org/D104071
1 parent 61cdaf6 commit 6aca603

File tree

8 files changed

+86
-29
lines changed

8 files changed

+86
-29
lines changed

clang-tools-extra/clangd/DumpAST.cpp

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -337,12 +337,8 @@ class DumpVisitor : public RecursiveASTVisitor<DumpVisitor> {
337337
// Generally, these are nodes with position information (TypeLoc, not Type).
338338

339339
bool TraverseDecl(Decl *D) {
340-
return !D || isInjectedClassName(D) || traverseNode("declaration", D, [&] {
341-
if (isa<TranslationUnitDecl>(D))
342-
Base::TraverseAST(const_cast<ASTContext &>(Ctx));
343-
else
344-
Base::TraverseDecl(D);
345-
});
340+
return !D || isInjectedClassName(D) ||
341+
traverseNode("declaration", D, [&] { Base::TraverseDecl(D); });
346342
}
347343
bool TraverseTypeLoc(TypeLoc TL) {
348344
return !TL || traverseNode("type", TL, [&] { Base::TraverseTypeLoc(TL); });

clang-tools-extra/clangd/refactor/tweaks/AddUsing.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,8 @@ class UsingFinder : public RecursiveASTVisitor<UsingFinder> {
8282
// There is no need to go deeper into nodes that do not enclose selection,
8383
// since "using" there will not affect selection, nor would it make a good
8484
// insertion point.
85-
if (Node->getDeclContext()->Encloses(SelectionDeclContext)) {
85+
if (!Node->getDeclContext() ||
86+
Node->getDeclContext()->Encloses(SelectionDeclContext)) {
8687
return RecursiveASTVisitor<UsingFinder>::TraverseDecl(Node);
8788
}
8889
return true;

clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -248,13 +248,23 @@ TEST(DiagnosticsTest, ClangTidy) {
248248
return SQUARE($macroarg[[++]]y);
249249
return $doubled[[sizeof]](sizeof(int));
250250
}
251+
252+
// misc-no-recursion uses a custom traversal from the TUDecl
253+
void foo();
254+
void $bar[[bar]]() {
255+
foo();
256+
}
257+
void $foo[[foo]]() {
258+
bar();
259+
}
251260
)cpp");
252261
auto TU = TestTU::withCode(Test.code());
253262
TU.HeaderFilename = "assert.h"; // Suppress "not found" error.
254263
TU.ClangTidyProvider = addTidyChecks("bugprone-sizeof-expression,"
255264
"bugprone-macro-repeated-side-effects,"
256265
"modernize-deprecated-headers,"
257-
"modernize-use-trailing-return-type");
266+
"modernize-use-trailing-return-type,"
267+
"misc-no-recursion");
258268
EXPECT_THAT(
259269
*TU.build().getDiagnostics(),
260270
UnorderedElementsAre(
@@ -283,8 +293,12 @@ TEST(DiagnosticsTest, ClangTidy) {
283293
DiagSource(Diag::ClangTidy),
284294
DiagName("modernize-use-trailing-return-type"),
285295
// Verify that we don't have "[check-name]" suffix in the message.
286-
WithFix(FixMessage(
287-
"use a trailing return type for this function")))));
296+
WithFix(
297+
FixMessage("use a trailing return type for this function"))),
298+
Diag(Test.range("foo"),
299+
"function 'foo' is within a recursive call chain"),
300+
Diag(Test.range("bar"),
301+
"function 'bar' is within a recursive call chain")));
288302
}
289303

290304
TEST(DiagnosticsTest, ClangTidyEOF) {

clang-tools-extra/clangd/unittests/TestTU.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,19 @@ const Symbol &findSymbol(const SymbolSlab &Slab, llvm::StringRef QName) {
195195
return *Result;
196196
}
197197

198+
// RAII scoped class to disable TraversalScope for a ParsedAST.
199+
class TraverseHeadersToo {
200+
ASTContext &Ctx;
201+
std::vector<Decl *> ScopeToRestore;
202+
203+
public:
204+
TraverseHeadersToo(ParsedAST &AST)
205+
: Ctx(AST.getASTContext()), ScopeToRestore(Ctx.getTraversalScope()) {
206+
Ctx.setTraversalScope({Ctx.getTranslationUnitDecl()});
207+
}
208+
~TraverseHeadersToo() { Ctx.setTraversalScope(std::move(ScopeToRestore)); }
209+
};
210+
198211
const NamedDecl &findDecl(ParsedAST &AST, llvm::StringRef QName) {
199212
auto &Ctx = AST.getASTContext();
200213
auto LookupDecl = [&Ctx](const DeclContext &Scope,
@@ -217,6 +230,7 @@ const NamedDecl &findDecl(ParsedAST &AST, llvm::StringRef QName) {
217230

218231
const NamedDecl &findDecl(ParsedAST &AST,
219232
std::function<bool(const NamedDecl &)> Filter) {
233+
TraverseHeadersToo Too(AST);
220234
struct Visitor : RecursiveASTVisitor<Visitor> {
221235
decltype(Filter) F;
222236
llvm::SmallVector<const NamedDecl *, 1> Decls;

clang/include/clang/AST/ASTContext.h

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -635,11 +635,22 @@ class ASTContext : public RefCountedBase<ASTContext> {
635635
ParentMapContext &getParentMapContext();
636636

637637
// A traversal scope limits the parts of the AST visible to certain analyses.
638-
// RecursiveASTVisitor::TraverseAST will only visit reachable nodes, and
638+
// RecursiveASTVisitor only visits specified children of TranslationUnitDecl.
639639
// getParents() will only observe reachable parent edges.
640640
//
641-
// The scope is defined by a set of "top-level" declarations.
642-
// Initially, it is the entire TU: {getTranslationUnitDecl()}.
641+
// The scope is defined by a set of "top-level" declarations which will be
642+
// visible under the TranslationUnitDecl.
643+
// Initially, it is the entire TU, represented by {getTranslationUnitDecl()}.
644+
//
645+
// After setTraversalScope({foo, bar}), the exposed AST looks like:
646+
// TranslationUnitDecl
647+
// - foo
648+
// - ...
649+
// - bar
650+
// - ...
651+
// All other siblings of foo and bar are pruned from the tree.
652+
// (However they are still accessible via TranslationUnitDecl->decls())
653+
//
643654
// Changing the scope clears the parent cache, which is expensive to rebuild.
644655
std::vector<Decl *> getTraversalScope() const { return TraversalScope; }
645656
void setTraversalScope(const std::vector<Decl *> &);

clang/include/clang/AST/RecursiveASTVisitor.h

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -192,14 +192,12 @@ template <typename Derived> class RecursiveASTVisitor {
192192
/// Return whether this visitor should traverse post-order.
193193
bool shouldTraversePostOrder() const { return false; }
194194

195-
/// Recursively visits an entire AST, starting from the top-level Decls
196-
/// in the AST traversal scope (by default, the TranslationUnitDecl).
195+
/// Recursively visits an entire AST, starting from the TranslationUnitDecl.
197196
/// \returns false if visitation was terminated early.
198197
bool TraverseAST(ASTContext &AST) {
199-
for (Decl *D : AST.getTraversalScope())
200-
if (!getDerived().TraverseDecl(D))
201-
return false;
202-
return true;
198+
// Currently just an alias for TraverseDecl(TUDecl), but kept in case
199+
// we change the implementation again.
200+
return getDerived().TraverseDecl(AST.getTranslationUnitDecl());
203201
}
204202

205203
/// Recursively visit a statement or expression, by
@@ -1495,12 +1493,24 @@ DEF_TRAVERSE_DECL(StaticAssertDecl, {
14951493
TRY_TO(TraverseStmt(D->getMessage()));
14961494
})
14971495

1498-
DEF_TRAVERSE_DECL(
1499-
TranslationUnitDecl,
1500-
{// Code in an unnamed namespace shows up automatically in
1501-
// decls_begin()/decls_end(). Thus we don't need to recurse on
1502-
// D->getAnonymousNamespace().
1503-
})
1496+
DEF_TRAVERSE_DECL(TranslationUnitDecl, {
1497+
// Code in an unnamed namespace shows up automatically in
1498+
// decls_begin()/decls_end(). Thus we don't need to recurse on
1499+
// D->getAnonymousNamespace().
1500+
1501+
// If the traversal scope is set, then consider them to be the children of
1502+
// the TUDecl, rather than traversing (and loading?) all top-level decls.
1503+
auto Scope = D->getASTContext().getTraversalScope();
1504+
bool HasLimitedScope =
1505+
Scope.size() != 1 || !isa<TranslationUnitDecl>(Scope.front());
1506+
if (HasLimitedScope) {
1507+
ShouldVisitChildren = false; // we'll do that here instead
1508+
for (auto *Child : Scope) {
1509+
if (!canIgnoreChildDeclWhileTraversingDeclContext(Child))
1510+
TRY_TO(TraverseDecl(Child));
1511+
}
1512+
}
1513+
})
15041514

15051515
DEF_TRAVERSE_DECL(PragmaCommentDecl, {})
15061516

clang/unittests/AST/ASTContextParentMapTest.cpp

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,27 +81,31 @@ TEST(GetParents, ReturnsMultipleParentsInTemplateInstantiations) {
8181
}
8282

8383
TEST(GetParents, RespectsTraversalScope) {
84-
auto AST =
85-
tooling::buildASTFromCode("struct foo { int bar; };", "foo.cpp",
86-
std::make_shared<PCHContainerOperations>());
84+
auto AST = tooling::buildASTFromCode(
85+
"struct foo { int bar; }; struct baz{};", "foo.cpp",
86+
std::make_shared<PCHContainerOperations>());
8787
auto &Ctx = AST->getASTContext();
8888
auto &TU = *Ctx.getTranslationUnitDecl();
8989
auto &Foo = *TU.lookup(&Ctx.Idents.get("foo")).front();
9090
auto &Bar = *cast<DeclContext>(Foo).lookup(&Ctx.Idents.get("bar")).front();
91+
auto &Baz = *TU.lookup(&Ctx.Idents.get("baz")).front();
9192

9293
// Initially, scope is the whole TU.
9394
EXPECT_THAT(Ctx.getParents(Bar), ElementsAre(DynTypedNode::create(Foo)));
9495
EXPECT_THAT(Ctx.getParents(Foo), ElementsAre(DynTypedNode::create(TU)));
96+
EXPECT_THAT(Ctx.getParents(Baz), ElementsAre(DynTypedNode::create(TU)));
9597

9698
// Restrict the scope, now some parents are gone.
9799
Ctx.setTraversalScope({&Foo});
98100
EXPECT_THAT(Ctx.getParents(Bar), ElementsAre(DynTypedNode::create(Foo)));
99-
EXPECT_THAT(Ctx.getParents(Foo), ElementsAre());
101+
EXPECT_THAT(Ctx.getParents(Foo), ElementsAre(DynTypedNode::create(TU)));
102+
EXPECT_THAT(Ctx.getParents(Baz), ElementsAre());
100103

101104
// Reset the scope, we get back the original results.
102105
Ctx.setTraversalScope({&TU});
103106
EXPECT_THAT(Ctx.getParents(Bar), ElementsAre(DynTypedNode::create(Foo)));
104107
EXPECT_THAT(Ctx.getParents(Foo), ElementsAre(DynTypedNode::create(TU)));
108+
EXPECT_THAT(Ctx.getParents(Baz), ElementsAre(DynTypedNode::create(TU)));
105109
}
106110

107111
TEST(GetParents, ImplicitLambdaNodes) {

clang/unittests/Tooling/RecursiveASTVisitorTests/TraversalScope.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ class Visitor : public ExpectedLocationVisitor<Visitor, clang::TestVisitor> {
1616
public:
1717
Visitor(ASTContext *Context) { this->Context = Context; }
1818

19+
bool VisitTranslationUnitDecl(TranslationUnitDecl *D) {
20+
auto &SM = D->getParentASTContext().getSourceManager();
21+
Match("TU", SM.getLocForStartOfFile(SM.getMainFileID()));
22+
return true;
23+
}
24+
1925
bool VisitNamedDecl(NamedDecl *D) {
2026
if (!D->isImplicit())
2127
Match(D->getName(), D->getLocation());
@@ -41,6 +47,7 @@ struct foo {
4147
Ctx.setTraversalScope({&Bar});
4248

4349
Visitor V(&Ctx);
50+
V.ExpectMatch("TU", 1, 1);
4451
V.DisallowMatch("foo", 2, 8);
4552
V.ExpectMatch("bar", 3, 10);
4653
V.ExpectMatch("baz", 4, 12);

0 commit comments

Comments
 (0)