https://github.com/ipopov updated https://github.com/llvm/llvm-project/pull/202248
>From 5805a98439860b7ddbae7558ebd4ac6f0ee00f16 Mon Sep 17 00:00:00 2001 From: ipopov <[email protected]> Date: Fri, 26 Jun 2026 15:28:02 -0700 Subject: [PATCH 1/3] Serialization: Skip anonymous declaration numbering for local tags in dependent contexts Local tag declarations (classes, structs, enums, and lambdas) defined within function template bodies or class templates do not require ODR merging at the local declaration level across different modules. ODR consistency is already guaranteed because the instantiator only walks the canonical template definition body, which instantiates its own copy of the local class. Merging them across different modules causes their member definitions (like methods or call operators) to be canonicalized to one module, while the instantiator walks the template body from another module. Since local variables within template bodies are not merged, this mismatch leads to assertion crashes during template instantiation in `LocalInstantiationScope::findInstantiationOf`. --- clang/lib/Serialization/ASTCommon.cpp | 3 + .../modules-lambda-dependent-crash.cppm | 73 +++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 clang/test/Modules/modules-lambda-dependent-crash.cppm diff --git a/clang/lib/Serialization/ASTCommon.cpp b/clang/lib/Serialization/ASTCommon.cpp index 49e6fe8004cec..e3df196242919 100644 --- a/clang/lib/Serialization/ASTCommon.cpp +++ b/clang/lib/Serialization/ASTCommon.cpp @@ -498,6 +498,9 @@ bool serialization::needsAnonymousDeclarationNumber(const NamedDecl *D) { if (D->getLexicalDeclContext()->isFunctionOrMethod()) { if (auto *VD = dyn_cast<VarDecl>(D)) return VD->isStaticLocal(); + if (const auto *TD = dyn_cast<TagDecl>(D)) + if (TD->getLexicalDeclContext()->isDependentContext()) + return false; // FIXME: What about CapturedDecls (and declarations nested within them)? return isa<TagDecl, BlockDecl>(D); } diff --git a/clang/test/Modules/modules-lambda-dependent-crash.cppm b/clang/test/Modules/modules-lambda-dependent-crash.cppm new file mode 100644 index 0000000000000..a0701b54c1989 --- /dev/null +++ b/clang/test/Modules/modules-lambda-dependent-crash.cppm @@ -0,0 +1,73 @@ +// RUN: rm -rf %t +// RUN: mkdir -p %t +// RUN: split-file %s %t +// RUN: cd %t +// +// RUN: %clang_cc1 -std=c++20 -I. m_template.cppm -emit-reduced-module-interface \ +// RUN: -o m_template.pcm +// +// RUN: %clang_cc1 -std=c++20 -I. m_wrapper.cppm \ +// RUN: -emit-reduced-module-interface \ +// RUN: -fmodule-file=m_template=m_template.pcm \ +// RUN: -o m_wrapper.pcm +// +// RUN: %clang_cc1 -std=c++20 -I. main.cpp \ +// RUN: -fmodule-file=m_template=m_template.pcm \ +// RUN: -fmodule-file=m_wrapper=m_wrapper.pcm \ +// RUN: -fsyntax-only -verify + +//--- template_class.h +#pragma once + +class TemplateClass { + public: + template <typename U> + auto f1(U u); +}; + +template <typename U> +auto TemplateClass::f1(U u) { + U z = u; + return [=](auto) { + (void)z; + }; +} + +template <typename U> +auto trigger_ast_deserialization(U u) { + return []() {}; +} + +//--- m_template.cppm +module; +#include "template_class.h" +export module m_template; +export using ::TemplateClass; +export using ::trigger_ast_deserialization; + +//--- m_wrapper.cppm +module; +#include "template_class.h" +export module m_wrapper; +import m_template; + +export inline void TriggerInstantiation() { + TemplateClass tc; + tc.f1(0); + trigger_ast_deserialization(0); +} + +//--- main.cpp +// expected-no-diagnostics +import m_template; +import m_wrapper; +#include "template_class.h" + +struct Token {}; + +int main() { + TriggerInstantiation(); + trigger_ast_deserialization(Token{}); + TemplateClass tc; + tc.f1(Token{}); +} >From 24c9297bc2db4cd0b8af7982c048b11c603d6e02 Mon Sep 17 00:00:00 2001 From: Ivo Popov <[email protected]> Date: Sat, 27 Jun 2026 18:47:23 -0400 Subject: [PATCH 2/3] Generalize the change, responding to review comments. --- clang/lib/Serialization/ASTCommon.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/clang/lib/Serialization/ASTCommon.cpp b/clang/lib/Serialization/ASTCommon.cpp index e3df196242919..9b454428457e2 100644 --- a/clang/lib/Serialization/ASTCommon.cpp +++ b/clang/lib/Serialization/ASTCommon.cpp @@ -493,14 +493,15 @@ bool serialization::needsAnonymousDeclarationNumber(const NamedDecl *D) { // At block scope, we number everything that we need to deduplicate, since we // can't just use name matching to keep things lined up. - // FIXME: This is only necessary for an inline function or a template or - // similar. + // FIXME: This is only necessary for an inline function or + // a template specialization or similar. if (D->getLexicalDeclContext()->isFunctionOrMethod()) { + // An uninstantiated template pattern is never emitted; only its + // instantiations are numbered, so its decls need no cross-module number. + if (D->getLexicalDeclContext()->isDependentContext()) + return false; if (auto *VD = dyn_cast<VarDecl>(D)) return VD->isStaticLocal(); - if (const auto *TD = dyn_cast<TagDecl>(D)) - if (TD->getLexicalDeclContext()->isDependentContext()) - return false; // FIXME: What about CapturedDecls (and declarations nested within them)? return isa<TagDecl, BlockDecl>(D); } >From d8329e0a424322d5158d44f07d202a7d1e91b1bb Mon Sep 17 00:00:00 2001 From: Ivo Popov <[email protected]> Date: Sat, 27 Jun 2026 18:47:23 -0400 Subject: [PATCH 3/3] Demonstrate that local classes also cause the crash; not only lambdas. --- .../modules-local-class-dependent-crash.cppm | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 clang/test/Modules/modules-local-class-dependent-crash.cppm diff --git a/clang/test/Modules/modules-local-class-dependent-crash.cppm b/clang/test/Modules/modules-local-class-dependent-crash.cppm new file mode 100644 index 0000000000000..8516175a2b063 --- /dev/null +++ b/clang/test/Modules/modules-local-class-dependent-crash.cppm @@ -0,0 +1,70 @@ +// RUN: rm -rf %t +// RUN: mkdir -p %t +// RUN: split-file %s %t +// RUN: cd %t +// +// RUN: %clang_cc1 -std=c++20 -I. m_template.cppm -emit-reduced-module-interface \ +// RUN: -o m_template.pcm +// +// RUN: %clang_cc1 -std=c++20 -I. m_wrapper.cppm \ +// RUN: -emit-reduced-module-interface \ +// RUN: -fmodule-file=m_template=m_template.pcm \ +// RUN: -o m_wrapper.pcm +// +// RUN: %clang_cc1 -std=c++20 -I. main.cpp \ +// RUN: -fmodule-file=m_template=m_template.pcm \ +// RUN: -fmodule-file=m_wrapper=m_wrapper.pcm \ +// RUN: -fsyntax-only -verify + +//--- local_class.h +#pragma once + +template <typename U> +auto make(U u) { + static int counter; + struct Box { + U value; + int id() const { return counter; } // references the enclosing static local + U unwrap() const { return value; } + }; + return Box{u}; +} + +template <typename U> +auto trigger_ast_deserialization(U u) { + return []() {}; +} + +//--- m_template.cppm +module; +#include "local_class.h" +export module m_template; +export using ::make; +export using ::trigger_ast_deserialization; + +//--- m_wrapper.cppm +module; +#include "local_class.h" +export module m_wrapper; +import m_template; + +export inline void TriggerInstantiation() { + make(0); + trigger_ast_deserialization(0); +} + +//--- main.cpp +// expected-no-diagnostics +import m_template; +import m_wrapper; +#include "local_class.h" + +struct Token {}; + +int main() { + TriggerInstantiation(); + trigger_ast_deserialization(Token{}); + auto b = make(Token{}); + (void)b.unwrap(); + (void)b.id(); +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
