[gcc r17-3321] c++: Adjust constexpr covariant call results [PR126324]

Jason Merrill via Gcc-cvs <[email protected]>
Newsgroups gmane.comp.gcc.cvs
Message-ID <[email protected]>
https://gcc.gnu.org/g:eb54aa2d961ec016d15bd39ea62579af4ad25710

commit r17-3321-geb54aa2d961ec016d15bd39ea62579af4ad25710
Author: Odysseas Georgoudis <[email protected]>
Date:   Fri Jul 24 23:18:32 2026 +0100

    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/constexpr-virtual-pr126324.C: New test.
    
    Signed-off-by: Odysseas Georgoudis <[email protected]>

Diff:
---
 gcc/cp/constexpr.cc                                | 44 +++++++++++++++---
 .../g++.dg/cpp2a/constexpr-virtual-pr126324.C      | 53 ++++++++++++++++++++++
 2 files changed, 91 insertions(+), 6 deletions(-)

diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc
index c9ebc0027b7d..30c46b7b6a0a 100644
--- a/gcc/cp/constexpr.cc
+++ b/gcc/cp/constexpr.cc
@@ -3908,13 +3908,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
+      && INDIRECT_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
@@ -4746,6 +4762,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
+      && INDIRECT_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/constexpr-virtual-pr126324.C b/gcc/testsuite/g++.dg/cpp2a/constexpr-virtual-pr126324.C
new file mode 100644
index 000000000000..39837da75aa4
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/constexpr-virtual-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 ());
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.