[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 https://github.com/llvm/llvm-project/issues/108145
This commit is contained in:
Nathan Ridge 2024-09-20 02:23:58 -04:00 committed by GitHub
parent 173841cc56
commit ea578804c8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 119 additions and 4 deletions

View File

@ -408,6 +408,8 @@ Bug Fixes to AST Handling
^^^^^^^^^^^^^^^^^^^^^^^^^
- Fixed a crash that occurred when dividing by zero in complex integer division. (#GH55390).
- Fixed a bug in ``ASTContext::getRawCommentForAnyRedecl()`` where the function could
sometimes incorrectly return null even if a comment was present. (#GH108145)
Miscellaneous Bug Fixes
^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -440,12 +440,26 @@ const RawComment *ASTContext::getRawCommentForAnyRedecl(
}
// Any redeclarations of D that we haven't checked for comments yet?
// We can't use DenseMap::iterator directly since it'd get invalid.
auto LastCheckedRedecl = [this, CanonicalD]() -> const Decl * {
return CommentlessRedeclChains.lookup(CanonicalD);
const Decl *LastCheckedRedecl = [&]() {
const Decl *LastChecked = CommentlessRedeclChains.lookup(CanonicalD);
bool CanUseCommentlessCache = false;
if (LastChecked) {
for (auto *Redecl : CanonicalD->redecls()) {
if (Redecl == D) {
CanUseCommentlessCache = true;
break;
}
if (Redecl == LastChecked)
break;
}
}
// FIXME: This could be improved so that even if CanUseCommentlessCache
// is false, once we've traversed past CanonicalD we still skip ahead
// LastChecked.
return CanUseCommentlessCache ? LastChecked : nullptr;
}();
for (const auto Redecl : D->redecls()) {
for (const Decl *Redecl : D->redecls()) {
assert(Redecl);
// Skip all redeclarations that have been checked previously.
if (LastCheckedRedecl) {

View File

@ -34,6 +34,7 @@ add_clang_unittest(ASTTests
NamedDeclPrinterTest.cpp
ProfilingTest.cpp
RandstructTest.cpp
RawCommentForDeclTest.cpp
RecursiveASTVisitorTest.cpp
SizelessTypesTest.cpp
SourceLocationTest.cpp

View File

@ -0,0 +1,98 @@
//===- unittests/AST/RawCommentForDeclTestTest.cpp
//-------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "clang/AST/ASTConsumer.h"
#include "clang/AST/DeclGroup.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/FrontendAction.h"
#include "clang/Tooling/Tooling.h"
#include "gmock/gmock-matchers.h"
#include "gtest/gtest.h"
namespace clang {
struct FoundComment {
std::string DeclName;
bool IsDefinition;
std::string Comment;
bool operator==(const FoundComment &RHS) const {
return DeclName == RHS.DeclName && IsDefinition == RHS.IsDefinition &&
Comment == RHS.Comment;
}
friend llvm::raw_ostream &operator<<(llvm::raw_ostream &Stream,
const FoundComment &C) {
return Stream << "{Name: " << C.DeclName << ", Def: " << C.IsDefinition
<< ", Comment: " << C.Comment << "}";
}
};
class CollectCommentsAction : public ASTFrontendAction {
public:
CollectCommentsAction(std::vector<FoundComment> &Comments)
: Comments(Comments) {}
std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
llvm::StringRef) override {
CI.getLangOpts().CommentOpts.ParseAllComments = true;
return std::make_unique<Consumer>(*this);
}
std::vector<FoundComment> &Comments;
private:
class Consumer : public clang::ASTConsumer {
private:
CollectCommentsAction &Action;
public:
Consumer(CollectCommentsAction &Action) : Action(Action) {}
bool HandleTopLevelDecl(DeclGroupRef DG) override {
for (Decl *D : DG) {
if (NamedDecl *ND = dyn_cast<NamedDecl>(D)) {
auto &Ctx = D->getASTContext();
const auto *RC = Ctx.getRawCommentForAnyRedecl(D);
Action.Comments.push_back(FoundComment{
ND->getNameAsString(), IsDefinition(D),
RC ? RC->getRawText(Ctx.getSourceManager()).str() : ""});
}
}
return true;
}
static bool IsDefinition(const Decl *D) {
if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(D)) {
return FD->isThisDeclarationADefinition();
}
if (const TagDecl *TD = dyn_cast<TagDecl>(D)) {
return TD->isThisDeclarationADefinition();
}
return false;
}
};
};
TEST(RawCommentForDecl, DefinitionComment) {
std::vector<FoundComment> Comments;
auto Action = std::make_unique<CollectCommentsAction>(Comments);
ASSERT_TRUE(tooling::runToolOnCode(std::move(Action), R"cpp(
void f();
// f is the best
void f() {}
)cpp"));
EXPECT_THAT(Comments, testing::ElementsAre(
FoundComment{"f", false, ""},
FoundComment{"f", true, "// f is the best"}));
}
} // namespace clang