c++: Stray RESULT_DECLs in result of constexpr call [PR94034]
authorPatrick Palka <ppalka@redhat.com>
Wed, 8 Apr 2020 17:14:42 +0000 (13:14 -0400)
committerPatrick Palka <ppalka@redhat.com>
Tue, 14 Apr 2020 15:22:55 +0000 (11:22 -0400)
When evaluating the initializer of 'a' in the following example

  struct A {
    A() = default; A(const 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'.

Fixing this properly by immediately resolving 'this' and PLACEHOLDER_EXPRs to
the ultimate object under construction would in general mean that we would no
longer be able to cache constexpr calls for which RVO possibly applies, because
the result of the call may now depend on the ultimate object under construction.

So as a mostly correct stopgap solution that retains cachability of RVO'd
constexpr calls, this patch fixes this issue by rewriting all occurrences of the
RESULT_DECL in the result of a constexpr function call with the current object
under construction, after the call returns.  This means the 'this' pointer
during construction of the temporary will still point to the temporary object
instead of the ultimate object, but besides that this approach seems
functionally equivalent to the proper approach.

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): Use it.
* tree.c (build_aggr_init_expr): Set the location of the AGGR_INIT_EXPR
to that of its initializer.

gcc/testsuite/ChangeLog:

PR c++/94034
* g++.dg/cpp0x/constexpr-empty15.C: New test.
* g++.dg/cpp1y/constexpr-nsdmi6a.C: New test.
* g++.dg/cpp1y/constexpr-nsdmi6b.C: New test.
* g++.dg/cpp1y/constexpr-nsdmi7a.C: New test.
* g++.dg/cpp1y/constexpr-nsdmi7b.C: New test.

gcc/cp/ChangeLog
gcc/cp/constexpr.c
gcc/cp/tree.c
gcc/testsuite/ChangeLog
gcc/testsuite/g++.dg/cpp0x/constexpr-empty15.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp1y/constexpr-nsdmi6a.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp1y/constexpr-nsdmi6b.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp1y/constexpr-nsdmi7a.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp1y/constexpr-nsdmi7b.C [new file with mode: 0644]

index 7d742c15ba889b4afa76c57240efab6e245c2946..ce390931c2eae11ad08aa8e531a9deb4dc6b1e0f 100644 (file)
@@ -1,3 +1,13 @@
+2020-04-14  Patrick Palka  <ppalka@redhat.com>
+
+       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): Use it.
+       * tree.c (build_aggr_init_expr): Set the location of the AGGR_INIT_EXPR
+       to that of its initializer.
+
 2020-04-13  Marek Polacek  <polacek@redhat.com>
 
        PR c++/94588
index d8636ddb92f24a8cbe4aeb07c46cd048deb9f873..c8e7d083f40462dda6ed632b9eccb8516f2793f6 100644 (file)
@@ -2029,6 +2029,52 @@ 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;
+  /* The replacement for DECL.  */
+  tree replacement;
+  /* 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->replacement);
+      d->changed = true;
+      *walk_subtrees = 0;
+    }
+  else if (TYPE_P (*tp))
+    *walk_subtrees = 0;
+
+  return NULL_TREE;
+}
+
+/* Replace every occurrence of DECL, a RESULT_DECL, with (an unshared copy of)
+   REPLACEMENT within the reduced constant expression *TP.  Returns true iff a
+   replacement was performed.  */
+
+static bool
+replace_result_decl (tree *tp, tree decl, tree replacement)
+{
+  gcc_checking_assert (TREE_CODE (decl) == RESULT_DECL
+                      && (same_type_ignoring_top_level_qualifiers_p
+                          (TREE_TYPE (decl), TREE_TYPE (replacement))));
+  replace_result_decl_data data = { decl, replacement, 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.  */
@@ -2536,6 +2582,14 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree t,
                      break;
                    }
            }
+
+           /* Rewrite all occurrences of the function's RESULT_DECL with the
+              current object under construction.  */
+           if (!*non_constant_p && ctx->object
+               && AGGREGATE_TYPE_P (TREE_TYPE (res))
+               && !is_empty_class (TREE_TYPE (res)))
+             if (replace_result_decl (&result, res, ctx->object))
+               cacheable = false;
        }
       else
        /* Couldn't get a function copy to evaluate.  */
index 1d311b0fe61dc149772610e356fd81559ecdb438..8e4934c00093ec5f5627b52049394cbbca17d81f 100644 (file)
@@ -669,6 +669,9 @@ build_aggr_init_expr (tree type, tree init)
   else
     rval = init;
 
+  if (location_t loc = EXPR_LOCATION (init))
+    SET_EXPR_LOCATION (rval, loc);
+
   return rval;
 }
 
index 64ee01b943c7c974fe89349946075c610cf19222..3fec84eb261e972bca13fc42e898fc7c85531118 100644 (file)
@@ -1,3 +1,12 @@
+2020-04-14  Patrick Palka  <ppalka@redhat.com>
+
+       PR c++/94034
+       * g++.dg/cpp0x/constexpr-empty15.C: New test.
+       * g++.dg/cpp1y/constexpr-nsdmi6a.C: New test.
+       * g++.dg/cpp1y/constexpr-nsdmi6b.C: New test.
+       * g++.dg/cpp1y/constexpr-nsdmi7a.C: New test.
+       * g++.dg/cpp1y/constexpr-nsdmi7b.C: New test.
+
 2020-04-14  Jakub Jelinek  <jakub@redhat.com>
 
        PR tree-optimization/94573
diff --git a/gcc/testsuite/g++.dg/cpp0x/constexpr-empty15.C b/gcc/testsuite/g++.dg/cpp0x/constexpr-empty15.C
new file mode 100644 (file)
index 0000000..97863d4
--- /dev/null
@@ -0,0 +1,9 @@
+// { dg-do compile { target c++11 } }
+
+struct empty1 { };
+constexpr empty1 foo1() { return {}; }
+
+struct empty2 { };
+constexpr empty2 foo2(empty1) { return {}; }
+
+constexpr empty2 a = foo2(foo1());
diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-nsdmi6a.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-nsdmi6a.C
new file mode 100644 (file)
index 0000000..bb844b9
--- /dev/null
@@ -0,0 +1,26 @@
+// 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" }
+}
+
+constexpr A a = foo();
+constexpr A b = bar();
diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-nsdmi6b.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-nsdmi6b.C
new file mode 100644 (file)
index 0000000..f847fe9
--- /dev/null
@@ -0,0 +1,27 @@
+// PR c++/94034
+// { dg-do compile { target c++14 } }
+
+struct A {
+  A() = default; A(const 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" }
+}
+
+constexpr A a = foo();
+constexpr A b = bar();
diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-nsdmi7a.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-nsdmi7a.C
new file mode 100644 (file)
index 0000000..5a40cd0
--- /dev/null
@@ -0,0 +1,49 @@
+// 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" }
diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-nsdmi7b.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-nsdmi7b.C
new file mode 100644 (file)
index 0000000..86d8ab4
--- /dev/null
@@ -0,0 +1,48 @@
+// PR c++/94034
+// { dg-do compile { target c++14 } }
+
+struct A
+{
+  A() = default; A(const 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; // { dg-error "non-.constexpr." }
+}
+
+constexpr int
+baz()
+{
+  A b = foo();
+  b.p->n = 10;
+  A c = foo();
+  if (c.p->n != 3 || c.p->m != 2)
+    __builtin_abort();
+  foo();
+  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" }