Skip to content

Commit ea57880

Browse files
[AST] Ensure getRawCommentsForAnyRedecl() does not miss any redecl with a comment (#108475)
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. Fixes #108145
1 parent 173841c commit ea57880

File tree

4 files changed

+119
-4
lines changed

4 files changed

+119
-4
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,8 @@ Bug Fixes to AST Handling
408408
^^^^^^^^^^^^^^^^^^^^^^^^^
409409

410410
- Fixed a crash that occurred when dividing by zero in complex integer division. (#GH55390).
411+
- Fixed a bug in ``ASTContext::getRawCommentForAnyRedecl()`` where the function could
412+
sometimes incorrectly return null even if a comment was present. (#GH108145)
411413

412414
Miscellaneous Bug Fixes
413415
^^^^^^^^^^^^^^^^^^^^^^^

clang/lib/AST/ASTContext.cpp

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

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

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

clang/unittests/AST/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ add_clang_unittest(ASTTests
3434
NamedDeclPrinterTest.cpp
3535
ProfilingTest.cpp
3636
RandstructTest.cpp
37+
RawCommentForDeclTest.cpp
3738
RecursiveASTVisitorTest.cpp
3839
SizelessTypesTest.cpp
3940
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)