On Thu, Feb 27, 2025 at 10:42:07AM -0500, Jason Merrill wrote:
> On 2/26/25 2:16 PM, Marek Polacek wrote:
> > Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?
> > 
> > -- >8 --
> > Yet another problem that started with r15-6052, compile time evaluation of
> > prvalues.
> > 
> > cp_fold_r/TARGET_EXPR sees:
> > 
> >    TARGET_EXPR <D.2701, <<< Unknown tree: expr_stmt
> >      D.2701.__p = TARGET_EXPR <D.2684, <<< Unknown tree: aggr_init_expr
> >        3
> >        f1
> >        D.2684 >>>> >>>>
> > 
> > so when we call maybe_constant_init, the object we're initializing is 
> > D.2701,
> > and the init is the expr_stmt.  We unwrap the 
> > EXPR_STMT/INIT_EXPR/TARGET_EXPR
> > in maybe_constant_init_1 and so end up evaluating the f1 call.  But f1 
> > returns
> > c2 whereas the type of D.2701 is ._anon_0 -- the closure.
> 
> Sounds like the problem is with the maybe_constant_init_1 unwrapping, it
> probably shouldn't strip INIT_EXPR if the type doesn't match that of 'decl'.

Yes, that's where the types change.  I thought that skipping the unpacking
here might cause other issues but it seems not.
 
> > So then we crash in replace_decl on:
> > 
> >       gcc_checking_assert (same_type_ignoring_top_level_qualifiers_p
> >                            (TREE_TYPE (decl), TREE_TYPE (replacement)));
> > 
> > due to the mismatched types.
> > 
> > cxx_eval_outermost_constant_expr is already ready for the types to be
> > different, in which case the result isn't constant.  But replace_decl
> > is called before that check.
> > 
> > I'm leaving the assert in replace_decl on purpose, maybe we'll find
> > another use for it.
> > 
> >     PR c++/118986
> > 
> > gcc/cp/ChangeLog:
> > 
> >     * constexpr.cc (cxx_eval_call_expression): Check that the types match
> >     before calling replace_decl.
> > 
> > gcc/testsuite/ChangeLog:
> > 
> >     * g++.dg/cpp2a/constexpr-prvalue1.C: New test.
> > ---
> >   gcc/cp/constexpr.cc                           |  4 +++-
> >   .../g++.dg/cpp2a/constexpr-prvalue1.C         | 23 +++++++++++++++++++
> >   2 files changed, 26 insertions(+), 1 deletion(-)
> >   create mode 100644 gcc/testsuite/g++.dg/cpp2a/constexpr-prvalue1.C
> > 
> > diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc
> > index 59dd0668af3..204cda2a222 100644
> > --- a/gcc/cp/constexpr.cc
> > +++ b/gcc/cp/constexpr.cc
> > @@ -3390,7 +3390,9 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, 
> > tree t,
> >            current object under construction.  */
> >         if (!*non_constant_p && ctx->object
> >             && CLASS_TYPE_P (TREE_TYPE (res))
> > -           && !is_empty_class (TREE_TYPE (res)))
> > +           && !is_empty_class (TREE_TYPE (res))
> > +           && same_type_ignoring_top_level_qualifiers_p
> > +               (TREE_TYPE (res), TREE_TYPE (ctx->object)))
> 
> If this happens, rather than just skip the replace_decl, I think we want to
> set *non_constant_p or I expect we'll end up with a wrong value somewhere.

Good point, I agree.

Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?

-- >8 --
Yet another problem that started with r15-6052, compile time evaluation of
prvalues.

cp_fold_r/TARGET_EXPR sees:

  TARGET_EXPR <D.2701, <<< Unknown tree: expr_stmt
    D.2701.__p = TARGET_EXPR <D.2684, <<< Unknown tree: aggr_init_expr
      3
      f1
      D.2684 >>>> >>>>

so when we call maybe_constant_init, the object we're initializing is D.2701,
and the init is the expr_stmt.  We unwrap the EXPR_STMT/INIT_EXPR/TARGET_EXPR
in maybe_constant_init_1 and so end up evaluating the f1 call.  But f1 returns
c2 whereas the type of D.2701 is ._anon_0 -- the closure.

So then we crash in replace_decl on:

          gcc_checking_assert (same_type_ignoring_top_level_qualifiers_p
                               (TREE_TYPE (decl), TREE_TYPE (replacement)));

due to the mismatched types.

cxx_eval_outermost_constant_expr is already ready for the types to be
different, in which case the result isn't constant.  But replace_decl
is called before that check.

I'm leaving the assert in replace_decl on purpose, maybe we'll find
another use for it.

        PR c++/118986

gcc/cp/ChangeLog:

        * constexpr.cc (cxx_eval_call_expression): Check that the types match
        before calling replace_decl, if not, set *non_constant_p.
        (maybe_constant_init_1): Don't strip INIT_EXPR if it would change the
        type of the expression.

gcc/testsuite/ChangeLog:

        * g++.dg/cpp2a/constexpr-prvalue1.C: New test.
---
 gcc/cp/constexpr.cc                           | 29 +++++++++++++------
 .../g++.dg/cpp2a/constexpr-prvalue1.C         | 23 +++++++++++++++
 2 files changed, 43 insertions(+), 9 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/constexpr-prvalue1.C

diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc
index c68666cc5dd..5439b2ea8fe 100644
--- a/gcc/cp/constexpr.cc
+++ b/gcc/cp/constexpr.cc
@@ -3388,16 +3388,22 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, 
tree t,
 
            /* Rewrite all occurrences of the function's RESULT_DECL with the
               current object under construction.  */
-           if (!*non_constant_p && ctx->object
+           if (!*non_constant_p
+               && ctx->object
                && CLASS_TYPE_P (TREE_TYPE (res))
                && !is_empty_class (TREE_TYPE (res)))
-             if (replace_decl (&result, res, ctx->object))
-               {
-                 cacheable = false;
-                 result = cxx_eval_constant_expression (ctx, result, lval,
-                                                        non_constant_p,
-                                                        overflow_p);
-               }
+             {
+               if (!same_type_ignoring_top_level_qualifiers_p
+                   (TREE_TYPE (res), TREE_TYPE (ctx->object)))
+                 *non_constant_p = true;
+               else if (replace_decl (&result, res, ctx->object))
+                 {
+                   cacheable = false;
+                   result = cxx_eval_constant_expression (ctx, result, lval,
+                                                          non_constant_p,
+                                                          overflow_p);
+                 }
+             }
 
          /* Only cache a permitted result of a constant expression.  */
          if (cacheable && !reduced_constant_expression_p (result))
@@ -9605,7 +9611,12 @@ maybe_constant_init_1 (tree t, tree decl, bool 
allow_non_constant,
   if (TREE_CODE (t) == CONVERT_EXPR
       && VOID_TYPE_P (TREE_TYPE (t)))
     t = TREE_OPERAND (t, 0);
-  if (TREE_CODE (t) == INIT_EXPR)
+  /* Doing the following stripping could change the type of the expression,
+     which then would not match DECL's type, and cause mischief later.  */
+  if (TREE_CODE (t) == INIT_EXPR
+      && (!decl
+         || same_type_ignoring_top_level_qualifiers_p (TREE_TYPE (decl),
+                                                       TREE_TYPE (t))))
     t = TREE_OPERAND (t, 1);
   if (TREE_CODE (t) == TARGET_EXPR)
     t = TARGET_EXPR_INITIAL (t);
diff --git a/gcc/testsuite/g++.dg/cpp2a/constexpr-prvalue1.C 
b/gcc/testsuite/g++.dg/cpp2a/constexpr-prvalue1.C
new file mode 100644
index 00000000000..f4e704d9487
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/constexpr-prvalue1.C
@@ -0,0 +1,23 @@
+// PR c++/118986
+// { dg-do compile { target c++20 } }
+// { dg-options "-O" }
+
+struct c1 {
+    constexpr c1(int *ptr) {}
+};
+struct c2 {
+    c1 _M_t;
+    constexpr ~c2() {}
+};
+constexpr inline
+c2 f1 ()
+{
+  return c2(new int);
+}
+
+void
+f ()
+{
+  auto l = [p = f1()](){};
+  [p = f1()](){};
+}

base-commit: 9792126ac769f2962c0f305991818c64f9e51221
-- 
2.48.1

Reply via email to