From ba64bae0f952cec298cd94dda3c36108f3d43b32 Mon Sep 17 00:00:00 2001
From: Odysseas Georgoudis <odygrd@hotmail.com>
Date: Fri, 24 Jul 2026 23:18:32 +0100
Subject: [PATCH v2] c++: Adjust constexpr covariant call results [PR126324]

Constant evaluation of a virtual call can evaluate the final overrider
directly.  For a zero-offset covariant override, the result then has the
overrider's declared type rather than the static type of the call.  Storing
that result can make cxx_eval_store_expression mistake the scalar type
mismatch for an empty-base initialization and ICE.

For a nonzero covariant adjustment, virtual lookup uses a result thunk.
cxx_eval_thunk_call applied the fixed offset even to a null result and left
the adjusted result with the overrider's return type, causing the same type
mismatch.

Adjust successful virtual-call results to the static call type after caching
the underlying function result.  Make result-thunk evaluation preserve null
pointers, evaluate the call only once, and give the result the thunk's static
return type.

gcc/cp/ChangeLog:

	PR c++/126324
	* constexpr.cc (cxx_eval_thunk_call): Preserve null pointer results and
	adjust the result to the thunk's return type.
	(cxx_eval_call_expression): Adjust covariant virtual-call results to
	the static call type.

gcc/testsuite/ChangeLog:

	PR c++/126324
	* g++.dg/cpp2a/pr126324.C: New test.

Signed-off-by: Odysseas Georgoudis <odygrd@hotmail.com>
---
 gcc/cp/constexpr.cc                   | 44 +++++++++++++++++++---
 gcc/testsuite/g++.dg/cpp2a/pr126324.C | 53 +++++++++++++++++++++++++++
 2 files changed, 91 insertions(+), 6 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/pr126324.C

diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc
index b4db9fd2bae..9f60b9c10a9 100644
--- a/gcc/cp/constexpr.cc
+++ b/gcc/cp/constexpr.cc
@@ -3893,13 +3893,29 @@ cxx_eval_thunk_call (const constexpr_ctx *ctx, tree t, tree thunk_fndecl,
       CALL_EXPR_ARG (new_call, 0) = this_arg;
     }
   else
-    /* Return-adjusting thunk.  */
-    new_call = build2 (POINTER_PLUS_EXPR, TREE_TYPE (new_call),
-		       new_call, offset);
+    {
+      /* Return-adjusting thunk.  As in expand_thunk, adjust a pointer only
+	 if it is non-null.  */
+      tree call = new_call;
+      if (TYPE_PTR_P (TREE_TYPE (call)))
+	call = save_expr (call);
+      new_call = build2 (POINTER_PLUS_EXPR, TREE_TYPE (call), call, offset);
+      if (TYPE_PTR_P (TREE_TYPE (call)))
+	new_call = build_if_nonnull (call, new_call, tf_none);
+    }
 
-  return cxx_eval_constant_expression (ctx, new_call, lval,
-				       non_constant_p, overflow_p,
-				       jump_target);
+  tree result = cxx_eval_constant_expression (ctx, new_call, lval,
+					       non_constant_p, overflow_p,
+					       jump_target);
+  if (!*non_constant_p
+      && !*overflow_p
+      && !*jump_target
+      && result != void_node
+      && scalarish_type_p (TREE_TYPE (t))
+      && !same_type_ignoring_top_level_qualifiers_p
+	   (TREE_TYPE (result), TREE_TYPE (t)))
+    result = adjust_temp_type (TREE_TYPE (t), result);
+  return result;
 }
 
 /* If OBJECT is of const class type, evaluate it to a CONSTRUCTOR and set
@@ -4731,6 +4747,22 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree t,
     clear_no_implicit_zero (result);
 
   pop_cx_call_context ();
+
+  /* A virtual call with a zero-offset covariant return needs no thunk, so
+     constant evaluation can evaluate the final overrider directly.  The
+     result then has the overrider's return type rather than the static type
+     of the call.  Adjust after caching so a direct call can reuse the result
+     with its original type.  */
+  if (!*non_constant_p
+      && !*overflow_p
+      && !*jump_target
+      && DECL_VIRTUAL_P (fun)
+      && result != void_node
+      && scalarish_type_p (TREE_TYPE (t))
+      && !same_type_ignoring_top_level_qualifiers_p
+	   (TREE_TYPE (result), TREE_TYPE (t)))
+    result = adjust_temp_type (TREE_TYPE (t), result);
+
   return result;
 }
 
diff --git a/gcc/testsuite/g++.dg/cpp2a/pr126324.C b/gcc/testsuite/g++.dg/cpp2a/pr126324.C
new file mode 100644
index 00000000000..39837da75aa
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/pr126324.C
@@ -0,0 +1,53 @@
+// PR c++/126324
+// { dg-do compile { target c++20 } }
+
+struct B
+{
+  virtual constexpr B *clone(bool null)
+  {
+    return null ? nullptr : this;
+  }
+};
+
+struct C
+{
+  virtual void dummy() { }
+};
+
+struct D : B
+{
+  constexpr D *clone(bool null) override
+  {
+    return null ? nullptr : this;
+  }
+};
+
+struct E : C, B
+{
+  int calls = 0;
+
+  constexpr E *clone(bool null) override
+  {
+    ++calls;
+    return null ? nullptr : this;
+  }
+};
+
+constexpr bool test()
+{
+  D d;
+  B *b = &d;
+  D *direct = d.clone (false);
+  B *nonnull = b->clone (false);
+  B *null = b->clone (true);
+
+  E e;
+  B *eb = &e;
+  B *enonnull = eb->clone (false);
+  B *enull = eb->clone (true);
+
+  return (direct == &d && nonnull == b && null == nullptr
+	  && enonnull == eb && enull == nullptr && e.calls == 2);
+}
+
+static_assert (test ());
-- 
2.43.5

