When evaluating the initializer of 'a' in the following example

  struct A { A *p = this; };
  constexpr A foo() { return {}; }
  constexpr A a = foo();

the PLACEHOLDER_EXPR for 'this' in the aggregate initializer returned by foo
gets resolved to the RESULT_DECL of foo.  But due to guaranteed RVO, the 'this'
should really be resolved to '&a'.

It seems to me that the right approach would be to immediately resolve the
PLACEHOLDER_EXPR to the correct target object during evaluation of 'foo()', so
that we could use 'this' to access objects adjacent to the current object in the
ultimate storage location.  (I think #c2 of PR c++/94537 is an example of such
usage of 'this', which currently doesn't work.  But as #c1 shows we don't seem
to handle this case correctly in non-constexpr initialization either.)

I haven't yet been able to make a solution using the above approach work --
making sure we use the ultimate object instead of the RESULT_DECL whenever we
access ctx->global->values is proving to be tricky and subtle.

As a simpler stopgap solution, this patch fixes this stray RESULT_DECL issue by
replacing all occurrences of the RESULT_DECL in the result of a constexpr
function call with the current object under construction.  This doesn't allow
one to access adjacent objects using the 'this' pointer, but besides that it
seems functionally equivalent to the above approach.

Is this stopgap solution adequate, or should I continue working on a more
correct approach?

This patch passes "make check-c++", and a full bootstrap/regtest is in progress.

gcc/cp/ChangeLog:

        PR c++/94034
        * constexpr.c (replace_result_decl_data): New struct.
        (replace_result_decl_data_r): New function.
        (replace_result_decl): New function.
        (cxx_eval_call_expression): Rename local variable 'res' to
        'result_decl' and move its declaration to an outer scope.  Use
        replace_result_decl.

gcc/testsuite/ChangeLog:

        PR c++/94034
        * g++.dg/cpp1y/constexpr-nsdmi7.C: New test.
        * g++.dg/cpp1y/constexpr-nsdmi8.C: New test.
---
 gcc/cp/constexpr.c                            | 68 +++++++++++++++++--
 gcc/testsuite/g++.dg/cpp1y/constexpr-nsdmi7.C | 21 ++++++
 gcc/testsuite/g++.dg/cpp1y/constexpr-nsdmi8.C | 45 ++++++++++++
 3 files changed, 127 insertions(+), 7 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp1y/constexpr-nsdmi7.C
 create mode 100644 gcc/testsuite/g++.dg/cpp1y/constexpr-nsdmi8.C

diff --git a/gcc/cp/constexpr.c b/gcc/cp/constexpr.c
index 96497ab85d7..c011f2c7722 100644
--- a/gcc/cp/constexpr.c
+++ b/gcc/cp/constexpr.c
@@ -2029,6 +2029,50 @@ cxx_eval_dynamic_cast_fn (const constexpr_ctx *ctx, tree 
call,
   return cp_build_addr_expr (obj, complain);
 }
 
+/* Data structure used by replace_result_decl and replace_result_decl_r.  */
+
+struct replace_result_decl_data
+{
+  /* The RESULT_DECL we want to replace.  */
+  tree decl;
+  /* What we're replacing every occurrence of DECL with.  */
+  tree to;
+  /* Whether we've performed any replacements.  */
+  bool changed;
+};
+
+/* Helper function for replace_result_decl, called through cp_walk_tree.  */
+
+static tree
+replace_result_decl_r (tree *tp, int *walk_subtrees, void *data)
+{
+  replace_result_decl_data *d = (replace_result_decl_data *) data;
+
+  if (*tp == d->decl)
+    {
+      *tp = unshare_expr (d->to);
+      d->changed = true;
+    }
+  else if (TYPE_P (*tp))
+    *walk_subtrees = 0;
+
+  return NULL_TREE;
+}
+
+/* Replace every occurrence of DECL, a RESULT_DECL, with (an unshared copy of)
+   TO within *TP.  Returns true iff a replacement was performed.  */
+
+static bool
+replace_result_decl (tree *tp, tree decl, tree to)
+{
+  gcc_assert (TREE_CODE (decl) == RESULT_DECL
+             && same_type_ignoring_top_level_qualifiers_p (TREE_TYPE (decl),
+                                                           TREE_TYPE (to)));
+  replace_result_decl_data data = { decl, to, false };
+  cp_walk_tree_without_duplicates (tp, replace_result_decl_r, &data);
+  return data.changed;
+}
+
 /* Subroutine of cxx_eval_constant_expression.
    Evaluate the call expression tree T in the context of OLD_CALL expression
    evaluation.  */
@@ -2381,6 +2425,7 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree 
t,
     }
   else
     {
+      tree result_decl = NULL_TREE;
       bool cacheable = true;
       if (result && result != error_mark_node)
        /* OK */;
@@ -2395,13 +2440,13 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, 
tree t,
        }
       else if (tree copy = get_fundef_copy (ctx, new_call.fundef))
        {
-         tree body, parms, res;
+         tree body, parms;
          releasing_vec ctors;
 
          /* Reuse or create a new unshared copy of this function's body.  */
          body = TREE_PURPOSE (copy);
          parms = TREE_VALUE (copy);
-         res = TREE_TYPE (copy);
+         result_decl = TREE_TYPE (copy);
 
          /* Associate the bindings with the remapped parms.  */
          tree bound = new_call.bindings;
@@ -2428,8 +2473,8 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree 
t,
              remapped = DECL_CHAIN (remapped);
            }
          /* Add the RESULT_DECL to the values map, too.  */
-         gcc_assert (!DECL_BY_REFERENCE (res));
-         ctx->global->values.put (res, NULL_TREE);
+         gcc_assert (!DECL_BY_REFERENCE (result_decl));
+         ctx->global->values.put (result_decl, NULL_TREE);
 
          /* Track the callee's evaluated SAVE_EXPRs and TARGET_EXPRs so that
             we can forget their values after the call.  */
@@ -2452,11 +2497,11 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, 
tree t,
               value by evaluating *this, but we don't bother; there's
               no need to put such a call in the hash table.  */
            result = lval ? ctx->object : ctx->ctor;
-         else if (VOID_TYPE_P (TREE_TYPE (res)))
+         else if (VOID_TYPE_P (TREE_TYPE (result_decl)))
            result = void_node;
          else
            {
-             result = *ctx->global->values.get (res);
+             result = *ctx->global->values.get (result_decl);
              if (result == NULL_TREE && !*non_constant_p)
                {
                  if (!ctx->quiet)
@@ -2495,7 +2540,7 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree 
t,
             bothering to do this when the map itself is only live for
             one constexpr evaluation?  If so, maybe also clear out
             other vars from call, maybe in BIND_EXPR handling?  */
-         ctx->global->values.remove (res);
+         ctx->global->values.remove (result_decl);
          for (tree parm = parms; parm; parm = TREE_CHAIN (parm))
            ctx->global->values.remove (parm);
 
@@ -2547,6 +2592,15 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree 
t,
        result = error_mark_node;
       else if (!result)
        result = void_node;
+
+      /* Adjust any references to the function's RESULT_DECL inside the result
+        to point to the current object under construction.  */
+      if (result_decl && ctx->object
+         && same_type_ignoring_top_level_qualifiers_p (TREE_TYPE (result_decl),
+                                                       TREE_TYPE 
(ctx->object)))
+       if (replace_result_decl (&result, result_decl, ctx->object))
+         cacheable = false;
+
       if (entry)
        entry->result = cacheable ? result : error_mark_node;
     }
diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-nsdmi7.C 
b/gcc/testsuite/g++.dg/cpp1y/constexpr-nsdmi7.C
new file mode 100644
index 00000000000..55aef277d82
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-nsdmi7.C
@@ -0,0 +1,21 @@
+// PR c++/94034
+// { dg-do compile { target c++14 } }
+
+struct A { A *ap = this; };
+
+constexpr A foo()
+{
+  return {};
+}
+
+constexpr A bar()
+{
+  return foo();
+}
+
+void
+baz()
+{
+  constexpr A a = foo(); // { dg-error ".A..& a... is not a constant 
expression" }
+  constexpr A b = bar(); // { dg-error ".A..& b... is not a constant 
expression" }
+}
diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-nsdmi8.C 
b/gcc/testsuite/g++.dg/cpp1y/constexpr-nsdmi8.C
new file mode 100644
index 00000000000..7b7a48e694d
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-nsdmi8.C
@@ -0,0 +1,45 @@
+// PR c++/94034
+// { dg-do compile { target c++14 } }
+
+struct A { A *p = this; int n = 2; int m = p->n++; };
+
+constexpr A
+foo()
+{
+  return {};
+}
+
+constexpr A
+bar()
+{
+  A a = foo();
+  a.p->n = 5;
+  return a;
+}
+
+static_assert(bar().n == 5, "");
+
+constexpr int
+baz()
+{
+  A b = foo();
+  b.p->n = 10;
+  A c = foo();
+  if (c.p->n != 3 || c.p->m != 2)
+    __builtin_abort();
+  bar();
+  return 0;
+}
+
+static_assert(baz() == 0, "");
+
+constexpr int
+quux()
+{
+  const A d = foo();
+  d.p->n++; // { dg-error "const object" }
+  return 0;
+}
+
+static_assert(quux() == 0, ""); // { dg-error "non-constant" }
+
-- 
2.26.0.106.g9fadedd637

Reply via email to