Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[AST] Ensure getRawCommentsForAnyRedecl() does not miss any redecl with a comment #108475

Merged
merged 1 commit into from
Sep 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
22 changes: 18 additions & 4 deletions clang/lib/AST/ASTContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
1 change: 1 addition & 0 deletions clang/unittests/AST/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ add_clang_unittest(ASTTests
NamedDeclPrinterTest.cpp
ProfilingTest.cpp
RandstructTest.cpp
RawCommentForDeclTest.cpp
RecursiveASTVisitorTest.cpp
SizelessTypesTest.cpp
SourceLocationTest.cpp
Expand Down
98 changes: 98 additions & 0 deletions clang/unittests/AST/RawCommentForDeclTest.cpp
Original file line number Diff line number Diff line change
@@ -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();
}
Comment on lines +73 to +78
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might want to handle *TemplateDecls too, but it is not required as we don't actually need them now.

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
Loading