https://github.com/Maoyao233 updated https://github.com/llvm/llvm-project/pull/237067
>From 96323110e6cf29de6e983f0aa537b97d268ecef1 Mon Sep 17 00:00:00 2001 From: Maoyao Ye <[email protected]> Date: Fri, 30 Oct 2026 11:15:24 +0800 Subject: [PATCH 1/4] [clang][AST] Visit lambda bodies whose call operator is not a definition A lambda whose body is part of a variable template specialization can end up with a closure type merged across modules. When the closure type is merged, ASTReader's PendingBodies handling can drop the duplicate body of the call operator and leave the closure class with a call operator declaration that has no body, while the definition lives on another declaration of the same entity. RecursiveASTVisitor's implicit-code model reaches a lambda body through the closure class' call operator: TraverseLambdaExpr visits S->getLambdaClass(), and TraverseFunctionHelper only visits the body of a declaration that is itself a definition. The body is then missing from the traversal, so tools that rely on it -- e.g. clang-tidy's misc-static-initialization-cycle, which builds a parent map and reaches the body through LambdaExpr::getBody() -- hit llvm_unreachable("declaration of ParentD should be reached") with assertions enabled, or crash in release builds. Traverse the body through the lambda expression when the closure type's call operator is not a definition. LambdaExpr::getBody() resolves the body through the redeclaration chain, so the body is visited exactly once: when the call operator is a definition, TraverseDecl has already visited it and the new branch is not taken; shouldVisitLambdaBody() is still honored. Add a clang-tidy regression test and a release note. --- clang-tools-extra/docs/ReleaseNotes.md | 4 ++ .../static-initialization-cycle-modules.cppm | 45 +++++++++++++++++++ clang/include/clang/AST/RecursiveASTVisitor.h | 13 ++++++ 3 files changed, 62 insertions(+) create mode 100644 clang-tools-extra/test/clang-tidy/checkers/misc/static-initialization-cycle-modules.cppm diff --git a/clang-tools-extra/docs/ReleaseNotes.md b/clang-tools-extra/docs/ReleaseNotes.md index 679ca9ecbb4a39..24ee2465bb2570 100644 --- a/clang-tools-extra/docs/ReleaseNotes.md +++ b/clang-tools-extra/docs/ReleaseNotes.md @@ -283,6 +283,10 @@ infrastructure are described first, followed by tool-specific sections. nested expressions involving different macros or a mix of macro and non-macro operands. +- Fixed a crash in {doc}`misc-static-initialization-cycle + <clang-tidy/checks/misc/static-initialization-cycle>` when checking a lambda + body from a closure type that was merged across modules. + - Fixed false positive in {doc}`modernize-make-unique <clang-tidy/checks/modernize/make-unique>` in presence of private, protected or deleted destructor. diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc/static-initialization-cycle-modules.cppm b/clang-tools-extra/test/clang-tidy/checkers/misc/static-initialization-cycle-modules.cppm new file mode 100644 index 00000000000000..d77de075f27578 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/misc/static-initialization-cycle-modules.cppm @@ -0,0 +1,45 @@ +// The check walks the body of a lambda that is part of a variable template +// specialization instantiated in a module. Building the parent map must not +// skip that body: the lambda body is reached through the call operator of the +// closure type, and when the closure type is merged with one from another +// module, the call operator the closure class refers to can be a declaration +// without a body, with the definition living on another declaration of the +// same entity. +// +// RUN: rm -rf %t +// RUN: mkdir -p %t +// RUN: split-file %s %t +// RUN: %clang -std=c++20 -x c++-module -I%t -fmodule-output=%t/mod_a.pcm -c %t/mod_a.cppm -o %t/mod_a.o +// RUN: %clang -std=c++20 -x c++-module -I%t -fmodule-output=%t/mod_b.pcm -c %t/mod_b.cppm -o %t/mod_b.o +// RUN: clang-tidy -checks='-*,misc-static-initialization-cycle' %t/main.cppm -- \ +// RUN: -std=c++20 -x c++-module -fmodule-file=mod_a=%t/mod_a.pcm \ +// RUN: -fmodule-file=mod_b=%t/mod_b.pcm + +//--- shared.h +#pragma once +struct SharedType { + int a{}; +}; + +template <class T> +inline constexpr unsigned long self = sizeof(T); + +template <class T> +inline constexpr auto probe = [] { return self<T>; }(); + +//--- mod_a.cppm +module; +#include "shared.h" +export module mod_a; +export inline constexpr unsigned long kA = probe<SharedType>; + +//--- mod_b.cppm +module; +#include "shared.h" +export module mod_b; +export inline constexpr unsigned long kB = probe<SharedType>; + +//--- main.cppm +export module main; +import mod_a; +import mod_b; diff --git a/clang/include/clang/AST/RecursiveASTVisitor.h b/clang/include/clang/AST/RecursiveASTVisitor.h index 13dd5eda84fa65..fbd82b86d8a1cc 100644 --- a/clang/include/clang/AST/RecursiveASTVisitor.h +++ b/clang/include/clang/AST/RecursiveASTVisitor.h @@ -2897,6 +2897,19 @@ DEF_TRAVERSE_STMT(LambdaExpr, { if (getDerived().shouldVisitImplicitCode()) { // The implicit model is simple: everything else is in the lambda class. TRY_TO(TraverseDecl(S->getLambdaClass())); + + // The body is normally reached through the call operator of the closure + // class. If that declaration is not a definition, the traversal above did + // not visit the body, so visit it through the lambda expression instead. + // This happens when the closure type was merged with one from another + // module and the definition ended up on a different declaration of the + // same entity than the one the closure class refers to. For named functions + // and methods the declaration carrying the definition is traversed + // separately, so only closure types need this fallback. + if (CXXMethodDecl *CallOp = S->getCallOperator(); + CallOp && !CallOp->isThisDeclarationADefinition() && + getDerived().shouldVisitLambdaBody()) + TRY_TO_TRAVERSE_OR_ENQUEUE_STMT(S->getBody()); } else { // We need to poke around to find the bits that might be explicitly written. TypeLoc TL = S->getCallOperator()->getTypeSourceInfo()->getTypeLoc(); >From e0feb481e4f89b8ffdd37b7a4b99e9f1b5245ba0 Mon Sep 17 00:00:00 2001 From: Maoyao Ye <[email protected]> Date: Fri, 30 Oct 2026 22:43:39 +0800 Subject: [PATCH 2/4] [clang][AST] Address review comments - Add a RecursiveASTVisitor unit test (clang/unittests/Tooling/RecursiveASTVisitorTests/LambdaExprModules.cpp): builds two reduced-BMI PCMs and checks that the implicit traversal visits every lambda body. - Shorten the comment in RecursiveASTVisitor.h; the clang-tidy test comment now links to this PR instead of restating the analysis. - Build the module interfaces in the clang-tidy test with --precompile instead of producing unused object files. - Drop the release note for now; it can be added to clang's release notes if reviewers prefer. --- clang-tools-extra/docs/ReleaseNotes.md | 4 - .../static-initialization-cycle-modules.cppm | 15 +- clang/include/clang/AST/RecursiveASTVisitor.h | 10 +- clang/unittests/Tooling/CMakeLists.txt | 1 + .../LambdaExprModules.cpp | 156 ++++++++++++++++++ 5 files changed, 166 insertions(+), 20 deletions(-) create mode 100644 clang/unittests/Tooling/RecursiveASTVisitorTests/LambdaExprModules.cpp diff --git a/clang-tools-extra/docs/ReleaseNotes.md b/clang-tools-extra/docs/ReleaseNotes.md index 24ee2465bb2570..679ca9ecbb4a39 100644 --- a/clang-tools-extra/docs/ReleaseNotes.md +++ b/clang-tools-extra/docs/ReleaseNotes.md @@ -283,10 +283,6 @@ infrastructure are described first, followed by tool-specific sections. nested expressions involving different macros or a mix of macro and non-macro operands. -- Fixed a crash in {doc}`misc-static-initialization-cycle - <clang-tidy/checks/misc/static-initialization-cycle>` when checking a lambda - body from a closure type that was merged across modules. - - Fixed false positive in {doc}`modernize-make-unique <clang-tidy/checks/modernize/make-unique>` in presence of private, protected or deleted destructor. diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc/static-initialization-cycle-modules.cppm b/clang-tools-extra/test/clang-tidy/checkers/misc/static-initialization-cycle-modules.cppm index d77de075f27578..d71a22406c7eff 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/misc/static-initialization-cycle-modules.cppm +++ b/clang-tools-extra/test/clang-tidy/checkers/misc/static-initialization-cycle-modules.cppm @@ -1,16 +1,13 @@ -// The check walks the body of a lambda that is part of a variable template -// specialization instantiated in a module. Building the parent map must not -// skip that body: the lambda body is reached through the call operator of the -// closure type, and when the closure type is merged with one from another -// module, the call operator the closure class refers to can be a declaration -// without a body, with the definition living on another declaration of the -// same entity. +// Regression test for https://github.com/llvm/llvm-project/pull/237067: +// the parent map must contain the lambda body even when the closure type's +// call operator is a declaration without a body (merged from another module), +// otherwise this check hits llvm_unreachable. // // RUN: rm -rf %t // RUN: mkdir -p %t // RUN: split-file %s %t -// RUN: %clang -std=c++20 -x c++-module -I%t -fmodule-output=%t/mod_a.pcm -c %t/mod_a.cppm -o %t/mod_a.o -// RUN: %clang -std=c++20 -x c++-module -I%t -fmodule-output=%t/mod_b.pcm -c %t/mod_b.cppm -o %t/mod_b.o +// RUN: %clang -std=c++20 -I%t --precompile %t/mod_a.cppm -o %t/mod_a.pcm +// RUN: %clang -std=c++20 -I%t --precompile %t/mod_b.cppm -o %t/mod_b.pcm // RUN: clang-tidy -checks='-*,misc-static-initialization-cycle' %t/main.cppm -- \ // RUN: -std=c++20 -x c++-module -fmodule-file=mod_a=%t/mod_a.pcm \ // RUN: -fmodule-file=mod_b=%t/mod_b.pcm diff --git a/clang/include/clang/AST/RecursiveASTVisitor.h b/clang/include/clang/AST/RecursiveASTVisitor.h index fbd82b86d8a1cc..05bab54c5c85c6 100644 --- a/clang/include/clang/AST/RecursiveASTVisitor.h +++ b/clang/include/clang/AST/RecursiveASTVisitor.h @@ -2899,13 +2899,9 @@ DEF_TRAVERSE_STMT(LambdaExpr, { TRY_TO(TraverseDecl(S->getLambdaClass())); // The body is normally reached through the call operator of the closure - // class. If that declaration is not a definition, the traversal above did - // not visit the body, so visit it through the lambda expression instead. - // This happens when the closure type was merged with one from another - // module and the definition ended up on a different declaration of the - // same entity than the one the closure class refers to. For named functions - // and methods the declaration carrying the definition is traversed - // separately, so only closure types need this fallback. + // class. If that declaration is not a definition (which happens when the + // closure type was merged with one from another module), visit the body + // through the lambda expression. if (CXXMethodDecl *CallOp = S->getCallOperator(); CallOp && !CallOp->isThisDeclarationADefinition() && getDerived().shouldVisitLambdaBody()) diff --git a/clang/unittests/Tooling/CMakeLists.txt b/clang/unittests/Tooling/CMakeLists.txt index 4de300fa86385f..655b39606c290c 100644 --- a/clang/unittests/Tooling/CMakeLists.txt +++ b/clang/unittests/Tooling/CMakeLists.txt @@ -39,6 +39,7 @@ add_clang_unittest(ToolingTests RecursiveASTVisitorTests/IntegerLiteral.cpp RecursiveASTVisitorTests/LambdaDefaultCapture.cpp RecursiveASTVisitorTests/LambdaExpr.cpp + RecursiveASTVisitorTests/LambdaExprModules.cpp RecursiveASTVisitorTests/LambdaTemplateParams.cpp RecursiveASTVisitorTests/MemberPointerTypeLoc.cpp RecursiveASTVisitorTests/NestedNameSpecifiers.cpp diff --git a/clang/unittests/Tooling/RecursiveASTVisitorTests/LambdaExprModules.cpp b/clang/unittests/Tooling/RecursiveASTVisitorTests/LambdaExprModules.cpp new file mode 100644 index 00000000000000..5a021c23d8e3bd --- /dev/null +++ b/clang/unittests/Tooling/RecursiveASTVisitorTests/LambdaExprModules.cpp @@ -0,0 +1,156 @@ +//===- unittest/Tooling/RecursiveASTVisitorTests/LambdaExprModules.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/ASTContext.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/Driver/CreateInvocationFromArgs.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendActions.h" +#include "clang/Frontend/Utils.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/ADT/SmallPtrSet.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/raw_ostream.h" +#include "gtest/gtest.h" + +using namespace clang; + +namespace { + +// Counts lambdas visited in the implicit-code model (the model ParentMapContext +// uses) and whether their bodies were visited too. +class ImplicitLambdaBodyVisitor + : public RecursiveASTVisitor<ImplicitLambdaBodyVisitor> { +public: + bool shouldVisitImplicitCode() const { return true; } + + bool VisitLambdaExpr(LambdaExpr *LE) { + ++NumLambdas; + Bodies.insert(LE->getBody()); + return true; + } + + bool VisitStmt(Stmt *S) { + if (Bodies.contains(S)) + ++NumVisitedBodies; + return true; + } + + unsigned NumLambdas = 0; + unsigned NumVisitedBodies = 0; + +private: + llvm::SmallPtrSet<Stmt *, 8> Bodies; +}; + +class LambdaExprModulesTest : public ::testing::Test { +protected: + void SetUp() override { + ASSERT_FALSE( + llvm::sys::fs::createUniqueDirectory("lambda-expr-modules", TestDir)); + } + + void TearDown() override { llvm::sys::fs::remove_directories(TestDir); } + + void addFile(llvm::StringRef Path, llvm::StringRef Contents) { + ASSERT_FALSE(llvm::sys::path::is_absolute(Path)); + + llvm::SmallString<256> AbsPath(TestDir); + llvm::sys::path::append(AbsPath, Path); + + ASSERT_FALSE(llvm::sys::fs::create_directories( + llvm::sys::path::parent_path(AbsPath))); + + std::error_code EC; + llvm::raw_fd_ostream OS(AbsPath, EC); + ASSERT_FALSE(EC); + OS << Contents; + } + + std::string precompile(llvm::StringRef FileName) { + CreateInvocationOptions CIOpts; + CIOpts.VFS = llvm::vfs::createPhysicalFileSystem(); + DiagnosticOptions DiagOpts; + IntrusiveRefCntPtr<DiagnosticsEngine> Diags = + CompilerInstance::createDiagnostics(*CIOpts.VFS, DiagOpts); + CIOpts.Diags = Diags; + + std::string File = FileName.str(); + const char *Args[] = {"clang++", "-std=c++20", + "--precompile", "-working-directory", + TestDir.c_str(), File.c_str()}; + std::shared_ptr<CompilerInvocation> Invocation = + createInvocation(Args, CIOpts); + EXPECT_TRUE(Invocation); + Invocation->getFrontendOpts().DisableFree = false; + + CompilerInstance Instance(std::move(Invocation)); + Instance.setDiagnostics(Diags); + + std::string PCM = (llvm::Twine(TestDir) + "/" + File + ".pcm").str(); + Instance.getFrontendOpts().OutputFile = PCM; + + GenerateReducedModuleInterfaceAction Action; + EXPECT_TRUE(Instance.ExecuteAction(Action)); + EXPECT_FALSE(Diags->hasErrorOccurred()); + return PCM; + } + + llvm::SmallString<256> TestDir; +}; + +TEST_F(LambdaExprModulesTest, VisitsMergedLambdaBodies) { + addFile("shared.h", R"cpp( +#pragma once +template <class T> +inline constexpr unsigned long size_of = sizeof(T); + +template <class T> +inline constexpr auto probe = [] { return size_of<T>; }(); +)cpp"); + addFile("mod_a.cppm", R"cpp( +module; +#include "shared.h" +export module mod_a; +export inline constexpr unsigned long kA = probe<unsigned long>; +)cpp"); + addFile("mod_b.cppm", R"cpp( +module; +#include "shared.h" +export module mod_b; +export inline constexpr unsigned long kB = probe<unsigned long>; +)cpp"); + + std::string PModA = precompile("mod_a.cppm"); + std::string PModB = precompile("mod_b.cppm"); + std::string ArgA = "-fmodule-file=mod_a=" + PModA; + std::string ArgB = "-fmodule-file=mod_b=" + PModB; + + std::unique_ptr<ASTUnit> AST = tooling::buildASTFromCodeWithArgs( + R"cpp( +import mod_a; +import mod_b; +unsigned long use() { return kA + kB; } +)cpp", + /*Args=*/{"-std=c++20", ArgA, ArgB}); + ASSERT_TRUE(AST); + + ImplicitLambdaBodyVisitor Visitor; + Visitor.TraverseDecl(AST->getASTContext().getTranslationUnitDecl()); + + // The TU imports two modules that each instantiate the same variable + // template, so the closure type is merged and one call operator is left + // without a body. The implicit traversal must still visit every lambda body + // it encountered. + ASSERT_GT(Visitor.NumLambdas, 0u); + EXPECT_EQ(Visitor.NumLambdas, Visitor.NumVisitedBodies); +} + +} // namespace >From 5dc94cf896d83e40c6dd43f1bb3597c55024c317 Mon Sep 17 00:00:00 2001 From: Maoyao Ye <[email protected]> Date: Sat, 31 Oct 2026 00:12:04 +0800 Subject: [PATCH 3/4] remove comments in test --- .../checkers/misc/static-initialization-cycle-modules.cppm | 5 ----- 1 file changed, 5 deletions(-) diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc/static-initialization-cycle-modules.cppm b/clang-tools-extra/test/clang-tidy/checkers/misc/static-initialization-cycle-modules.cppm index d71a22406c7eff..e31ae27eceab1c 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/misc/static-initialization-cycle-modules.cppm +++ b/clang-tools-extra/test/clang-tidy/checkers/misc/static-initialization-cycle-modules.cppm @@ -1,8 +1,3 @@ -// Regression test for https://github.com/llvm/llvm-project/pull/237067: -// the parent map must contain the lambda body even when the closure type's -// call operator is a declaration without a body (merged from another module), -// otherwise this check hits llvm_unreachable. -// // RUN: rm -rf %t // RUN: mkdir -p %t // RUN: split-file %s %t >From 7d9403b60d74f4ad7b77805843feb7f719b614ce Mon Sep 17 00:00:00 2001 From: Maoyao Ye <[email protected]> Date: Sat, 31 Oct 2026 14:53:18 +0800 Subject: [PATCH 4/4] move the fix to ParentMap --- clang/include/clang/AST/RecursiveASTVisitor.h | 9 - clang/lib/AST/ParentMapContext.cpp | 16 ++ .../unittests/AST/ASTContextParentMapTest.cpp | 104 ++++++++++++ clang/unittests/Tooling/CMakeLists.txt | 1 - .../LambdaExprModules.cpp | 156 ------------------ 5 files changed, 120 insertions(+), 166 deletions(-) delete mode 100644 clang/unittests/Tooling/RecursiveASTVisitorTests/LambdaExprModules.cpp diff --git a/clang/include/clang/AST/RecursiveASTVisitor.h b/clang/include/clang/AST/RecursiveASTVisitor.h index 05bab54c5c85c6..13dd5eda84fa65 100644 --- a/clang/include/clang/AST/RecursiveASTVisitor.h +++ b/clang/include/clang/AST/RecursiveASTVisitor.h @@ -2897,15 +2897,6 @@ DEF_TRAVERSE_STMT(LambdaExpr, { if (getDerived().shouldVisitImplicitCode()) { // The implicit model is simple: everything else is in the lambda class. TRY_TO(TraverseDecl(S->getLambdaClass())); - - // The body is normally reached through the call operator of the closure - // class. If that declaration is not a definition (which happens when the - // closure type was merged with one from another module), visit the body - // through the lambda expression. - if (CXXMethodDecl *CallOp = S->getCallOperator(); - CallOp && !CallOp->isThisDeclarationADefinition() && - getDerived().shouldVisitLambdaBody()) - TRY_TO_TRAVERSE_OR_ENQUEUE_STMT(S->getBody()); } else { // We need to poke around to find the bits that might be explicitly written. TypeLoc TL = S->getCallOperator()->getTypeSourceInfo()->getTypeLoc(); diff --git a/clang/lib/AST/ParentMapContext.cpp b/clang/lib/AST/ParentMapContext.cpp index 7607cf27bfe60f..dce8bdbb1e674c 100644 --- a/clang/lib/AST/ParentMapContext.cpp +++ b/clang/lib/AST/ParentMapContext.cpp @@ -457,6 +457,22 @@ class ParentMapContext::ParentMap::ASTVisitor &Map.OtherParents); } + // Traverse the body of a lambda whose call operator is a declaration rather + // than a definition because the closure type was merged across modules, so + // that it is not missing from the parent map. + // See https://github.com/llvm/llvm-project/issues/236863. + bool TraverseCXXMethodDecl(CXXMethodDecl *MethodNode) { + if (!VisitorBase::TraverseCXXMethodDecl(MethodNode)) + return false; + if (!shouldVisitLambdaBody() || MethodNode->isThisDeclarationADefinition()) + return true; + const CXXRecordDecl *RD = MethodNode->getParent(); + if (!RD->isLambda() || + !declaresSameEntity(RD->getLambdaCallOperator(), MethodNode)) + return true; + return TraverseStmt(MethodNode->getBody()); + } + // Using generic TraverseNode for Stmt would prevent data-recursion. bool dataTraverseStmtPre(Stmt *StmtNode) { addParent(StmtNode, &Map.PointerParents); diff --git a/clang/unittests/AST/ASTContextParentMapTest.cpp b/clang/unittests/AST/ASTContextParentMapTest.cpp index 545eaf36191717..37cab9c43e592a 100644 --- a/clang/unittests/AST/ASTContextParentMapTest.cpp +++ b/clang/unittests/AST/ASTContextParentMapTest.cpp @@ -11,12 +11,17 @@ //===----------------------------------------------------------------------===// #include "clang/AST/ASTContext.h" +#include "clang/AST/RecursiveASTVisitor.h" #include "MatchVerifier.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/Tooling/Tooling.h" +#include "llvm/ADT/ScopeExit.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/Support/FileSystem.h" #include "gtest/gtest.h" #include "gmock/gmock.h" +#include <vector> using testing::ElementsAre; @@ -119,6 +124,105 @@ TEST(GetParents, ImplicitLambdaNodes) { Lang_CXX11)); } +namespace { + +/// Collects every LambdaExpr in the AST, including template instantiations. +class LambdaCollector : public RecursiveASTVisitor<LambdaCollector> { +public: + bool shouldVisitTemplateInstantiations() const { return true; } + + bool VisitLambdaExpr(LambdaExpr *LE) { + Lambdas.push_back(LE); + return true; + } + + std::vector<LambdaExpr *> Lambdas; +}; + +void collectDeclRefExprs(Stmt *S, std::vector<DeclRefExpr *> &Refs) { + if (!S) + return; + if (auto *DRE = dyn_cast<DeclRefExpr>(S)) + Refs.push_back(DRE); + for (Stmt *Child : S->children()) + collectDeclRefExprs(Child, Refs); +} + +} // namespace + +TEST(GetParents, MergedLambdaClosureBody) { + // Two module units instantiate the same variable template specialization + // containing a lambda. The closure types are merged and the call operator is + // left without a body; see issue #236863. + llvm::SmallString<256> ModuleCache; + ASSERT_FALSE(llvm::sys::fs::createUniqueDirectory( + "clang-parent-map-lambda-modules", ModuleCache)); + llvm::scope_exit CleanupModuleCache( + [&] { llvm::sys::fs::remove_directories(ModuleCache); }); + + auto AST = tooling::buildASTFromCodeWithArgs( + R"cpp( +#pragma clang module build A +module A {} +#pragma clang module contents +#pragma clang module begin A +struct SharedType { int a{}; }; +template <class T> inline constexpr unsigned long self = sizeof(T); +template <class T> inline constexpr auto probe = [] { return self<T>; }(); +inline constexpr unsigned long kA = probe<SharedType>; +#pragma clang module end +#pragma clang module endbuild + +#pragma clang module build B +module B {} +#pragma clang module contents +#pragma clang module begin B +struct SharedType { int a{}; }; +template <class T> inline constexpr unsigned long self = sizeof(T); +template <class T> inline constexpr auto probe = [] { return self<T>; }(); +inline constexpr unsigned long kB = probe<SharedType>; +#pragma clang module end +#pragma clang module endbuild + +#pragma clang module import A +#pragma clang module import B + +unsigned long use() { return kA + kB; } +)cpp", + {"-std=c++20", "-fmodules", + "-fmodules-cache-path=" + ModuleCache.str().str()}); + ASSERT_TRUE(AST); + + auto &Ctx = AST->getASTContext(); + // Build the parent map before anything below loads the call operator bodies: + // deserializing the bodies changes which traversal path reaches them. + (void)Ctx.getParents(*Ctx.getTranslationUnitDecl()); + + LambdaCollector Collector; + Collector.TraverseDecl(Ctx.getTranslationUnitDecl()); + + unsigned NumBodies = 0; + for (LambdaExpr *LE : Collector.Lambdas) { + if (!LE->getLambdaClass()->isFromASTFile()) + continue; + Stmt *Body = LE->getBody(); + ASSERT_NE(Body, nullptr); + ++NumBodies; + + // The body is reached through the call operator, as for any other lambda. + DynTypedNodeList BodyParents = Ctx.getParents(*Body); + ASSERT_FALSE(BodyParents.empty()); + EXPECT_NE(BodyParents[0].get<CXXMethodDecl>(), nullptr); + + // These nodes' missing parents caused the clang-tidy crash. + std::vector<DeclRefExpr *> Refs; + collectDeclRefExprs(Body, Refs); + for (DeclRefExpr *Ref : Refs) + EXPECT_FALSE(Ctx.getParents(*Ref).empty()); + } + EXPECT_GT(NumBodies, 0u); +} + TEST(GetParents, FriendTypeLoc) { auto AST = tooling::buildASTFromCode("struct A { friend struct Fr; };" "struct B { friend struct Fr; };" diff --git a/clang/unittests/Tooling/CMakeLists.txt b/clang/unittests/Tooling/CMakeLists.txt index 655b39606c290c..4de300fa86385f 100644 --- a/clang/unittests/Tooling/CMakeLists.txt +++ b/clang/unittests/Tooling/CMakeLists.txt @@ -39,7 +39,6 @@ add_clang_unittest(ToolingTests RecursiveASTVisitorTests/IntegerLiteral.cpp RecursiveASTVisitorTests/LambdaDefaultCapture.cpp RecursiveASTVisitorTests/LambdaExpr.cpp - RecursiveASTVisitorTests/LambdaExprModules.cpp RecursiveASTVisitorTests/LambdaTemplateParams.cpp RecursiveASTVisitorTests/MemberPointerTypeLoc.cpp RecursiveASTVisitorTests/NestedNameSpecifiers.cpp diff --git a/clang/unittests/Tooling/RecursiveASTVisitorTests/LambdaExprModules.cpp b/clang/unittests/Tooling/RecursiveASTVisitorTests/LambdaExprModules.cpp deleted file mode 100644 index 5a021c23d8e3bd..00000000000000 --- a/clang/unittests/Tooling/RecursiveASTVisitorTests/LambdaExprModules.cpp +++ /dev/null @@ -1,156 +0,0 @@ -//===- unittest/Tooling/RecursiveASTVisitorTests/LambdaExprModules.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/ASTContext.h" -#include "clang/AST/RecursiveASTVisitor.h" -#include "clang/Driver/CreateInvocationFromArgs.h" -#include "clang/Frontend/CompilerInstance.h" -#include "clang/Frontend/FrontendActions.h" -#include "clang/Frontend/Utils.h" -#include "clang/Tooling/Tooling.h" -#include "llvm/ADT/SmallPtrSet.h" -#include "llvm/ADT/SmallString.h" -#include "llvm/Support/FileSystem.h" -#include "llvm/Support/Path.h" -#include "llvm/Support/raw_ostream.h" -#include "gtest/gtest.h" - -using namespace clang; - -namespace { - -// Counts lambdas visited in the implicit-code model (the model ParentMapContext -// uses) and whether their bodies were visited too. -class ImplicitLambdaBodyVisitor - : public RecursiveASTVisitor<ImplicitLambdaBodyVisitor> { -public: - bool shouldVisitImplicitCode() const { return true; } - - bool VisitLambdaExpr(LambdaExpr *LE) { - ++NumLambdas; - Bodies.insert(LE->getBody()); - return true; - } - - bool VisitStmt(Stmt *S) { - if (Bodies.contains(S)) - ++NumVisitedBodies; - return true; - } - - unsigned NumLambdas = 0; - unsigned NumVisitedBodies = 0; - -private: - llvm::SmallPtrSet<Stmt *, 8> Bodies; -}; - -class LambdaExprModulesTest : public ::testing::Test { -protected: - void SetUp() override { - ASSERT_FALSE( - llvm::sys::fs::createUniqueDirectory("lambda-expr-modules", TestDir)); - } - - void TearDown() override { llvm::sys::fs::remove_directories(TestDir); } - - void addFile(llvm::StringRef Path, llvm::StringRef Contents) { - ASSERT_FALSE(llvm::sys::path::is_absolute(Path)); - - llvm::SmallString<256> AbsPath(TestDir); - llvm::sys::path::append(AbsPath, Path); - - ASSERT_FALSE(llvm::sys::fs::create_directories( - llvm::sys::path::parent_path(AbsPath))); - - std::error_code EC; - llvm::raw_fd_ostream OS(AbsPath, EC); - ASSERT_FALSE(EC); - OS << Contents; - } - - std::string precompile(llvm::StringRef FileName) { - CreateInvocationOptions CIOpts; - CIOpts.VFS = llvm::vfs::createPhysicalFileSystem(); - DiagnosticOptions DiagOpts; - IntrusiveRefCntPtr<DiagnosticsEngine> Diags = - CompilerInstance::createDiagnostics(*CIOpts.VFS, DiagOpts); - CIOpts.Diags = Diags; - - std::string File = FileName.str(); - const char *Args[] = {"clang++", "-std=c++20", - "--precompile", "-working-directory", - TestDir.c_str(), File.c_str()}; - std::shared_ptr<CompilerInvocation> Invocation = - createInvocation(Args, CIOpts); - EXPECT_TRUE(Invocation); - Invocation->getFrontendOpts().DisableFree = false; - - CompilerInstance Instance(std::move(Invocation)); - Instance.setDiagnostics(Diags); - - std::string PCM = (llvm::Twine(TestDir) + "/" + File + ".pcm").str(); - Instance.getFrontendOpts().OutputFile = PCM; - - GenerateReducedModuleInterfaceAction Action; - EXPECT_TRUE(Instance.ExecuteAction(Action)); - EXPECT_FALSE(Diags->hasErrorOccurred()); - return PCM; - } - - llvm::SmallString<256> TestDir; -}; - -TEST_F(LambdaExprModulesTest, VisitsMergedLambdaBodies) { - addFile("shared.h", R"cpp( -#pragma once -template <class T> -inline constexpr unsigned long size_of = sizeof(T); - -template <class T> -inline constexpr auto probe = [] { return size_of<T>; }(); -)cpp"); - addFile("mod_a.cppm", R"cpp( -module; -#include "shared.h" -export module mod_a; -export inline constexpr unsigned long kA = probe<unsigned long>; -)cpp"); - addFile("mod_b.cppm", R"cpp( -module; -#include "shared.h" -export module mod_b; -export inline constexpr unsigned long kB = probe<unsigned long>; -)cpp"); - - std::string PModA = precompile("mod_a.cppm"); - std::string PModB = precompile("mod_b.cppm"); - std::string ArgA = "-fmodule-file=mod_a=" + PModA; - std::string ArgB = "-fmodule-file=mod_b=" + PModB; - - std::unique_ptr<ASTUnit> AST = tooling::buildASTFromCodeWithArgs( - R"cpp( -import mod_a; -import mod_b; -unsigned long use() { return kA + kB; } -)cpp", - /*Args=*/{"-std=c++20", ArgA, ArgB}); - ASSERT_TRUE(AST); - - ImplicitLambdaBodyVisitor Visitor; - Visitor.TraverseDecl(AST->getASTContext().getTranslationUnitDecl()); - - // The TU imports two modules that each instantiate the same variable - // template, so the closure type is merged and one call operator is left - // without a body. The implicit traversal must still visit every lambda body - // it encountered. - ASSERT_GT(Visitor.NumLambdas, 0u); - EXPECT_EQ(Visitor.NumLambdas, Visitor.NumVisitedBodies); -} - -} // namespace _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
