Skip to content

Commit 2b14e80

Browse files
[AST] Ensure getRawCommentsForAnyRedecl() does not miss any redecl with a comment
The previous implementation had a bug where, if it was called on a Decl later in the redecl chain than `LastCheckedDecl`, it could incorrectly skip and overlook a Decl with a comment. The patch addresses this by only using `LastCheckedDecl` if the input Decl `D` is on the path from the first (canonical) Decl to `LastCheckedDecl`. An alternative that was considered was to start the iteration from the (canonical) Decl, however this ran into problems with the modelling of explicit template specializations in the AST where the canonical Decl can be unusual. With the current solution, if no Decls were checked yet, we prefer to check the input Decl over the canonical one.
1 parent 99c43e3 commit 2b14e80

File tree

3 files changed

+117
-4
lines changed

3 files changed

+117
-4
lines changed

clang/lib/AST/ASTContext.cpp

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -439,12 +439,26 @@ const RawComment *ASTContext::getRawCommentForAnyRedecl(
439439
}
440440

441441
// Any redeclarations of D that we haven't checked for comments yet?
442-
// We can't use DenseMap::iterator directly since it'd get invalid.
443-
auto LastCheckedRedecl = [this, CanonicalD]() -> const Decl * {
444-
return CommentlessRedeclChains.lookup(CanonicalD);
442+
const Decl *LastCheckedRedecl = [&]() {
443+
const Decl *LastChecked = CommentlessRedeclChains.lookup(CanonicalD);
444+
bool CanUseCommentlessCache = false;
445+
if (LastChecked) {
446+
for (auto *Redecl : CanonicalD->redecls()) {
447+
if (Redecl == D) {
448+
CanUseCommentlessCache = true;
449+
break;
450+
}
451+
if (Redecl == LastChecked)
452+
break;
453+
}
454+
}
455+
// FIXME: This could be improved so that even if CanUseCommentlessCache
456+
// is false, once we've traversed past CanonicalD we still skip ahead
457+
// LastChecked.
458+
return CanUseCommentlessCache ? LastChecked : nullptr;
445459
}();
446460

447-
for (const auto Redecl : D->redecls()) {
461+
for (const Decl *Redecl : D->redecls()) {
448462
assert(Redecl);
449463
// Skip all redeclarations that have been checked previously.
450464
if (LastCheckedRedecl) {

clang/unittests/AST/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ add_clang_unittest(ASTTests
3333
NamedDeclPrinterTest.cpp
3434
ProfilingTest.cpp
3535
RandstructTest.cpp
36+
RawCommentForDeclTest.cpp
3637
RecursiveASTVisitorTest.cpp
3738
SizelessTypesTest.cpp
3839
SourceLocationTest.cpp
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
//===- unittests/AST/RawCommentForDeclTestTest.cpp
2+
//-------------------------===//
3+
//
4+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
5+
// See https://llvm.org/LICENSE.txt for license information.
6+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7+
//
8+
//===----------------------------------------------------------------------===//
9+
10+
#include "clang/AST/ASTConsumer.h"
11+
#include "clang/AST/DeclGroup.h"
12+
#include "clang/Frontend/CompilerInstance.h"
13+
#include "clang/Frontend/FrontendAction.h"
14+
#include "clang/Tooling/Tooling.h"
15+
16+
#include "gmock/gmock-matchers.h"
17+
#include "gtest/gtest.h"
18+
19+
namespace clang {
20+
21+
struct FoundComment {
22+
std::string DeclName;
23+
bool IsDefinition;
24+
std::string Comment;
25+
26+
bool operator==(const FoundComment &RHS) const {
27+
return DeclName == RHS.DeclName && IsDefinition == RHS.IsDefinition &&
28+
Comment == RHS.Comment;
29+
}
30+
friend llvm::raw_ostream &operator<<(llvm::raw_ostream &Stream,
31+
const FoundComment &C) {
32+
return Stream << "{Name: " << C.DeclName << ", Def: " << C.IsDefinition
33+
<< ", Comment: " << C.Comment << "}";
34+
}
35+
};
36+
37+
class CollectCommentsAction : public ASTFrontendAction {
38+
public:
39+
CollectCommentsAction(std::vector<FoundComment> &Comments)
40+
: Comments(Comments) {}
41+
42+
std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
43+
llvm::StringRef) override {
44+
CI.getLangOpts().CommentOpts.ParseAllComments = true;
45+
return std::make_unique<Consumer>(*this);
46+
}
47+
48+
std::vector<FoundComment> &Comments;
49+
50+
private:
51+
class Consumer : public clang::ASTConsumer {
52+
private:
53+
CollectCommentsAction &Action;
54+
55+
public:
56+
Consumer(CollectCommentsAction &Action) : Action(Action) {}
57+
58+
bool HandleTopLevelDecl(DeclGroupRef DG) override {
59+
for (Decl *D : DG) {
60+
if (NamedDecl *ND = dyn_cast<NamedDecl>(D)) {
61+
auto &Ctx = D->getASTContext();
62+
const auto *RC = Ctx.getRawCommentForAnyRedecl(D);
63+
Action.Comments.push_back(FoundComment{
64+
ND->getNameAsString(), IsDefinition(D),
65+
RC ? RC->getRawText(Ctx.getSourceManager()).str() : ""});
66+
}
67+
}
68+
69+
return true;
70+
}
71+
72+
static bool IsDefinition(const Decl *D) {
73+
if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(D)) {
74+
return FD->isThisDeclarationADefinition();
75+
}
76+
if (const TagDecl *TD = dyn_cast<TagDecl>(D)) {
77+
return TD->isThisDeclarationADefinition();
78+
}
79+
return false;
80+
}
81+
};
82+
};
83+
84+
TEST(RawCommentForDecl, DefinitionComment) {
85+
std::vector<FoundComment> Comments;
86+
auto Action = std::make_unique<CollectCommentsAction>(Comments);
87+
ASSERT_TRUE(tooling::runToolOnCode(std::move(Action), R"cpp(
88+
void f();
89+
90+
// f is the best
91+
void f() {}
92+
)cpp"));
93+
EXPECT_THAT(Comments, testing::ElementsAre(
94+
FoundComment{"f", false, ""},
95+
FoundComment{"f", true, "// f is the best"}));
96+
}
97+
98+
} // namespace clang

0 commit comments

Comments
 (0)