https://github.com/ChuanqiXu9 created 
https://github.com/llvm/llvm-project/pull/218574

Close https://github.com/llvm/llvm-project/issues/217858

The crash triggers on the assertion:

```C++
assert(!DD.IsLambda && !MergeDD.IsLambda && "faked up lambda definition?");
```

where DD.IsLambda is false and MergeDD.IsLambda is true. This is kinda 
surprising. But this turns out to be real for the example.

```C++
export template <class Callback> auto make_closure(Callback &cb) {
  return [&cb](auto &arg) noexcept(noexcept(cb(arg))) { cb(arg); };
}
export template <class Callback, class Arg>
void for_each(Callback &&cb, Arg &arg)
noexcept(noexcept(make_closure(cb)(arg))) {}
export template <class It> struct iterator {
  It cur;
  void operator++() noexcept(noexcept(for_each([](auto &i) { ++i; },
cur))) {}
};
```

Here the chained use of lambda in noexcept inside the lambda makes the loading 
ordering pretty uncommon and odd. And it makes the first loading lambda 
definition doesn't get its definition data but the later loaded redeclaration 
get it.

As this falls into an assertion, the change itself is not expected to meet any 
regressions.

>From 9578297f12fbeb776734b491181001e384d29068 Mon Sep 17 00:00:00 2001
From: Chuanqi Xu <[email protected]>
Date: Tue, 25 Aug 2026 10:36:56 +0800
Subject: [PATCH] [C++20] [Modules] Merge lambda where the merge definition
 data is merged before the primary one

Close https://github.com/llvm/llvm-project/issues/217858

The crash triggers on the assertion:

```C++
assert(!DD.IsLambda && !MergeDD.IsLambda && "faked up lambda definition?");
```

where DD.IsLambda is false and MergeDD.IsLambda is true. This is kinda
surprising. But this turns out to be real for the example.

```C++
export template <class Callback> auto make_closure(Callback &cb) {
  return [&cb](auto &arg) noexcept(noexcept(cb(arg))) { cb(arg); };
}
export template <class Callback, class Arg>
void for_each(Callback &&cb, Arg &arg)
noexcept(noexcept(make_closure(cb)(arg))) {}
export template <class It> struct iterator {
  It cur;
  void operator++() noexcept(noexcept(for_each([](auto &i) { ++i; },
cur))) {}
};
```

Here the chained use of lambda in noexcept inside the lambda makes the
loading ordering pretty uncommon and odd. And it makes the first loading
lambda definition doesn't get its definition data but the later loaded
redeclaration get it.

As this falls into an assertion, the change itself is not expected to
meet any regressions.
---
 clang/lib/Serialization/ASTReaderDecl.cpp | 22 ++++++++-
 clang/test/Modules/pr217858.cppm          | 56 +++++++++++++++++++++++
 2 files changed, 77 insertions(+), 1 deletion(-)
 create mode 100644 clang/test/Modules/pr217858.cppm

diff --git a/clang/lib/Serialization/ASTReaderDecl.cpp 
b/clang/lib/Serialization/ASTReaderDecl.cpp
index e973b7ae71954..74e0106520011 100644
--- a/clang/lib/Serialization/ASTReaderDecl.cpp
+++ b/clang/lib/Serialization/ASTReaderDecl.cpp
@@ -2103,7 +2103,27 @@ void ASTDeclMerger::MergeDefinitionData(
       PFDI->second == ASTReader::PendingFakeDefinitionKind::Fake) {
     // We faked up this definition data because we found a class for which we'd
     // not yet loaded the definition. Replace it with the real thing now.
-    assert(!DD.IsLambda && !MergeDD.IsLambda && "faked up lambda definition?");
+    assert(!DD.IsLambda && "faked up lambda definition?");
+
+    // This is possible for some special loading ordering. See
+    // clang/test/Modules/pr217858.cppm for an example.
+    //
+    // LambdaDefinitionData is larger than DefinitionData, so it cannot replace
+    // the fake DefinitionData object in place.
+    if (MergeDD.IsLambda) {
+      auto *Def = DD.Definition;
+      MergeDD.Definition = Def;
+      // Unlike an instantiated class definition,
+      // whose update-record reader removes the fake entry after loading its
+      // lexical declarations, a lambda's definition is part of its declaration
+      // record and is fully loaded here.
+      Reader.PendingFakeDefinitionData.erase(PFDI);
+      for (auto *R = Reader.getMostRecentExistingDecl(Def); R;
+           R = R->getPreviousDecl())
+        cast<CXXRecordDecl>(R)->DefinitionData = &MergeDD;
+      return;
+    }
+
     PFDI->second = ASTReader::PendingFakeDefinitionKind::FakeLoaded;
 
     // Don't change which declaration is the definition; that is required
diff --git a/clang/test/Modules/pr217858.cppm b/clang/test/Modules/pr217858.cppm
new file mode 100644
index 0000000000000..040518bfec8ef
--- /dev/null
+++ b/clang/test/Modules/pr217858.cppm
@@ -0,0 +1,56 @@
+// RUN: rm -rf %t
+// RUN: mkdir -p %t
+// RUN: split-file %s %t
+//
+// RUN: %clang_cc1 -std=c++23 -triple %itanium_abi_triple \
+// RUN:   -emit-module-interface %t/lib.cppm -o %t/Lib.pcm
+// RUN: %clang_cc1 -std=c++23 -triple %itanium_abi_triple \
+// RUN:   -emit-module-interface %t/mod.cppm -o %t/Mod.pcm \
+// RUN:   -fmodule-file=Lib=%t/Lib.pcm
+// RUN: %clang_cc1 -std=c++23 -triple %itanium_abi_triple -emit-obj \
+// RUN:   %t/main.cpp -o %t/main.o -fmodule-file=Lib=%t/Lib.pcm \
+// RUN:   -fmodule-file=Mod=%t/Mod.pcm
+
+// Test again with reduced BMI.
+// RUN: %clang_cc1 -std=c++23 -triple %itanium_abi_triple \
+// RUN:   -emit-reduced-module-interface %t/lib.cppm -o %t/Lib.pcm
+// RUN: %clang_cc1 -std=c++23 -triple %itanium_abi_triple \
+// RUN:   -emit-reduced-module-interface %t/mod.cppm -o %t/Mod.pcm \
+// RUN:   -fmodule-file=Lib=%t/Lib.pcm
+// RUN: %clang_cc1 -std=c++23 -triple %itanium_abi_triple -emit-obj \
+// RUN:   %t/main.cpp -o %t/main.o -fmodule-file=Lib=%t/Lib.pcm \
+// RUN:   -fmodule-file=Mod=%t/Mod.pcm
+
+//--- lib.cppm
+export module Lib;
+
+export template <class Callback> auto make_closure(Callback &callback) {
+  return [&callback](auto &arg) noexcept(noexcept(callback(arg))) {
+    callback(arg);
+  };
+}
+
+export template <class Callback, class Arg>
+void for_each(Callback &&callback, Arg &arg)
+    noexcept(noexcept(make_closure(callback)(arg))) {}
+
+export template <class It> struct iterator {
+  It current;
+  void operator++()
+      noexcept(noexcept(for_each([](auto &it) { ++it; }, current))) {}
+};
+
+//--- mod.cppm
+export module Mod;
+import Lib;
+
+export inline int test() {
+  int data[4]{};
+  iterator<int *> it{data};
+  ++it;
+  return static_cast<int>(it.current - data);
+}
+
+//--- main.cpp
+import Mod;
+int main() { return test(); }

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to