c++: Stray RESULT_DECLs in result of constexpr call [PR94034]

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.
This commit is contained in:
Patrick Palka 2020-04-08 13:14:42 -04:00
parent 66b8801141
commit b256222910
9 changed files with 235 additions and 0 deletions

View 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

View 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. */

View 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;
}

View 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

View File

@ -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());

View File

@ -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();

View File

@ -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();

View File

@ -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" }

View File

@ -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" }