[clang] [Clang][Sema] Fix crash in RemoveNestedImmediateInvocation with __builtin_dump_struct (PR #192880)

Vladislav Semykin via cfe-commits cfe-commits at lists.llvm.org
Mon Apr 20 00:20:28 PDT 2026


https://github.com/ViNN280801 updated https://github.com/llvm/llvm-project/pull/192880

>From 122f1193a0a0c5b8d25b088ecc5499521585ae81 Mon Sep 17 00:00:00 2001
From: ViNN280801 <vladislav.semykin at gmail.com>
Date: Mon, 20 Apr 2026 01:46:40 +0300
Subject: [PATCH 1/2] [Clang][Sema] Fix crash in
 RemoveNestedImmediateInvocation with __builtin_dump_struct

Signed-off-by: ViNN280801 <vladislav.semykin at gmail.com>
---
 clang/docs/ReleaseNotes.rst                   |  3 +
 clang/lib/Sema/SemaExpr.cpp                   | 66 +++++++++++++++++++
 .../builtin-dump-struct-immediate-clean.cpp   | 29 ++++++++
 .../SemaCXX/builtin-dump-struct-immediate.cpp | 63 ++++++++++++++++++
 4 files changed, 161 insertions(+)
 create mode 100644 clang/test/SemaCXX/builtin-dump-struct-immediate-clean.cpp
 create mode 100644 clang/test/SemaCXX/builtin-dump-struct-immediate.cpp

diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 638a813ca105b..f4d2d7c63c902 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -527,6 +527,9 @@ Miscellaneous Clang Crashes Fixed
 - Fixed an assertion failure when parsing an invalid out-of-line enum definition with template parameters. (#GH187909)
 - Fixed an assertion failure on invalid template template parameter during typo correction. (#GH183983)
 - Fixed an assertion failure in ``isAtEndOfMacroExpansion`` on macro expansions crossing the boundary of two fileIDs. (#GH115007), (#GH21755)
+- Fixed an assertion failure when ``__builtin_dump_struct`` is called with an
+  immediate-escalated callable (for example a function whose body contains
+  ``__builtin_is_within_lifetime``). (#GH192846)
 
 OpenACC Specific Changes
 ------------------------
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index cf235095d489d..204888e8503e5 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -18463,6 +18463,72 @@ static void RemoveNestedImmediateInvocation(
       // Lambdas have already been processed inside their eval contexts.
       return E;
     }
+
+    // Default TreeTransform::TransformOpaqueValueExpr requires either no
+    // SourceExpr or AlreadyTransformed(getType()). ComplexRemove inherits the
+    // base AlreadyTransformed implementation (true only for the null type), so
+    // any OpaqueValueExpr that carries a SourceExpr would assert.
+    //
+    // That arises for __builtin_dump_struct: the record pointer is bound in an
+    // OpaqueValueExpr and reused as the base of synthesized MemberExprs. When
+    // peeling nested immediate invocations we must recurse through that source
+    // as well; otherwise ConstantExpr wrappers inside the source would survive
+    // incorrectly.
+    //
+    // Alternatives considered:
+    // - Returning the original OVE without traversing SourceExpr silences the
+    //   assert but violates this pass's purpose for nested immediates under the
+    //   binding expression.
+    // - Overriding AlreadyTransformed to always return true skips traversal for
+    //   the same reason.
+    // - Restructuring __builtin_dump_struct's AST would touch many consumers.
+    // - In-place mutation of SourceExpr would preserve node identity, but
+    //   OpaqueValueExpr exposes no mutator for SourceExpr; widening the AST API
+    //   for this localized issue is disproportionate.
+    //
+    // Returning E without traversing SourceExpr could also rely on the
+    // enclosing PseudoObjectExpr to evaluate nested immediates correctly, but
+    // ComplexRemove is intended to strip nested immediate-invocation wrappers
+    // throughout the transformed subtree; recursing through SourceExpr matches
+    // that contract.
+    //
+    // Risk: code that keyed on the exact OpaqueValueExpr* for a dump-struct
+    // binding could theoretically desynchronize if we replace the node. No
+    // such registry is known; we only allocate a replacement when the
+    // transformed source is a different Expr*.
+    //
+    // Note: DenseMap may rehash on insert; do not hold iterators/pointers
+    // across TransformExpr(Src), since nested OVEs may insert into this map.
+    llvm::DenseMap<OpaqueValueExpr *, OpaqueValueExpr *> TransformedOpaqueValues;
+
+    ExprResult TransformOpaqueValueExpr(OpaqueValueExpr *E) {
+      if (auto It = TransformedOpaqueValues.find(E);
+          It != TransformedOpaqueValues.end())
+        return It->second;
+
+      // Provisional self-mapping breaks cycles if SourceExpr reaches this OVE.
+      TransformedOpaqueValues[E] = E;
+
+      Expr *Src = E->getSourceExpr();
+      if (!Src)
+        return E;
+
+      ExprResult NewSrc = getDerived().TransformExpr(Src);
+      if (NewSrc.isInvalid()) {
+        TransformedOpaqueValues.erase(E);
+        return ExprError();
+      }
+
+      if (NewSrc.get() == Src)
+        return E;
+
+      auto *Result = new (SemaRef.getASTContext())
+          OpaqueValueExpr(E->getLocation(), E->getType(), E->getValueKind(),
+                          E->getObjectKind(), NewSrc.get());
+      TransformedOpaqueValues[E] = Result;
+      return Result;
+    }
+
     bool AlwaysRebuild() { return false; }
     bool ReplacingOriginal() { return true; }
     bool AllowSkippingCXXConstructExpr() {
diff --git a/clang/test/SemaCXX/builtin-dump-struct-immediate-clean.cpp b/clang/test/SemaCXX/builtin-dump-struct-immediate-clean.cpp
new file mode 100644
index 0000000000000..bda7cb1a12dd1
--- /dev/null
+++ b/clang/test/SemaCXX/builtin-dump-struct-immediate-clean.cpp
@@ -0,0 +1,29 @@
+// RUN: %clang_cc1 -std=c++2c -fsyntax-only -Wno-unused -verify %s
+// RUN: %clang_cc1 -std=c++23 -fsyntax-only -Wno-unused -verify %s
+
+// expected-no-diagnostics
+
+// GH192846: minimal well-formed case - immediate-escalated printing function
+// with __builtin_dump_struct must not crash in ComplexRemove. See sibling
+// builtin-dump-struct-immediate.cpp for C++26 vs language-mode notes.
+
+#if !__has_builtin(__builtin_is_within_lifetime)
+#error "test requires __builtin_is_within_lifetime"
+#endif
+
+struct S {};
+
+template <typename... T>
+constexpr void F(S &out, const char *fmt, T... args) {
+  (void)__builtin_is_within_lifetime(&out);
+}
+
+template <class T>
+class C {
+  T value = {};
+};
+
+void bar() {
+  S s;
+  __builtin_dump_struct((C<int> *)nullptr, F, s);
+}
diff --git a/clang/test/SemaCXX/builtin-dump-struct-immediate.cpp b/clang/test/SemaCXX/builtin-dump-struct-immediate.cpp
new file mode 100644
index 0000000000000..f094e184178c1
--- /dev/null
+++ b/clang/test/SemaCXX/builtin-dump-struct-immediate.cpp
@@ -0,0 +1,63 @@
+// RUN: %clang_cc1 -std=c++23 -fsyntax-only -Wno-unused %s -DVALID_CASE
+// RUN: %clang_cc1 -std=c++2c -fsyntax-only -Wno-unused %s -DVALID_CASE
+// RUN: %clang_cc1 -std=c++23 -fsyntax-only -Wno-unused -verify %s
+// RUN: %clang_cc1 -std=c++2c -fsyntax-only -Wno-unused -verify %s
+
+// Source: GH192846 reported by @k-arrows with reproducer: https://godbolt.org/z/9aa43GE1a
+//
+// P2641R4 proposes std::is_within_lifetime(const T*) (consteval) for C++26 -
+// see https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2641r4.html
+// ("active union member" generalized to "within lifetime" for constexpr).
+// Clang: __builtin_is_within_lifetime (immediate builtin). The crash fixed here
+// is in Sema immediate-invocation handling, not in a particular -std mode, so
+// we run under both C++23 and C++2c.
+
+// Regression test for GH192846: ComplexRemove must traverse OpaqueValueExpr
+// sources (e.g. from __builtin_dump_struct) when stripping nested immediate
+// invocations; the default TreeTransform hook would assert.
+
+#if !__has_builtin(__builtin_is_within_lifetime)
+#error "test requires __builtin_is_within_lifetime"
+#endif
+
+#ifdef VALID_CASE
+
+// dump_struct may manifestly-constant-evaluate the printing callback; the
+// consteval builtin must only read objects usable in constant expressions.
+constexpr int foo = 0;
+
+template <typename T>
+struct C {
+  int n;
+};
+
+// The dump_struct machinery invokes this with a synthesized argument list; it
+// must not be a template (template arguments are not deduced from that call).
+void F(const char *fmt, ...) {
+  (void)__builtin_is_within_lifetime(&foo);
+}
+
+void bar() {
+  constexpr char s[] = "";
+  __builtin_dump_struct((C<int> *)nullptr, F, s);
+}
+
+#else
+
+template <typename T>
+struct C {
+  int n;
+};
+
+void F(const char *fmt, ...) {
+  // Error recovery can report both the bad identifier and an invalid use of the
+  // consteval builtin on the same expression.
+  (void)__builtin_is_within_lifetime(&foo); // expected-error {{use of undeclared identifier 'foo'}} expected-error {{cannot take address of consteval function '__builtin_is_within_lifetime' outside of an immediate invocation}}
+}
+
+void bar() {
+  constexpr char s[] = "";
+  __builtin_dump_struct((C<int> *)nullptr, F, s);
+}
+
+#endif

>From f1d96f2e0ee94f3fb0f81db08dcf5f511c5addc8 Mon Sep 17 00:00:00 2001
From: ViNN280801 <vladislav.semykin at gmail.com>
Date: Mon, 20 Apr 2026 10:20:15 +0300
Subject: [PATCH 2/2] [Clang][Sema] Reformatted the code properly

Signed-off-by: ViNN280801 <vladislav.semykin at gmail.com>
---
 clang/lib/Sema/SemaExpr.cpp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 204888e8503e5..0355d5af2dbee 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -18499,7 +18499,8 @@ static void RemoveNestedImmediateInvocation(
     //
     // Note: DenseMap may rehash on insert; do not hold iterators/pointers
     // across TransformExpr(Src), since nested OVEs may insert into this map.
-    llvm::DenseMap<OpaqueValueExpr *, OpaqueValueExpr *> TransformedOpaqueValues;
+    llvm::DenseMap<OpaqueValueExpr *, OpaqueValueExpr *>
+        TransformedOpaqueValues;
 
     ExprResult TransformOpaqueValueExpr(OpaqueValueExpr *E) {
       if (auto It = TransformedOpaqueValues.find(E);



More information about the cfe-commits mailing list