Author: Chuanqi Xu
Date: 2026-08-25T03:25:08Z
New Revision: 5e2a95807a719ee98c902e5984c53974a3ce70bf

URL: 
https://github.com/llvm/llvm-project/commit/5e2a95807a719ee98c902e5984c53974a3ce70bf
DIFF: 
https://github.com/llvm/llvm-project/commit/5e2a95807a719ee98c902e5984c53974a3ce70bf.diff

LOG: [C++20] [Modules] Merge lambda where the merge definition data is merged 
before the primary one (#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.

Added: 
    clang/test/Modules/pr217858.cppm

Modified: 
    clang/lib/Serialization/ASTReaderDecl.cpp

Removed: 
    


################################################################################
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