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

Reply via email to