c++: Delegating constructor in constexpr init [PR94772]

In the first testcase below, the call to the target constructor foo{} from foo's
delegating constructor is encoded as the INIT_EXPR

  *(struct foo *) this = AGGR_INIT_EXPR <4, __ct_comp, D.2140, ...>;

During initialization of the variable 'bar', we prematurely set TREE_READONLY on
bar's CONSTRUCTOR in two places before the outer delegating constructor has
returned: first, at the end of cxx_eval_call_expression after evaluating the RHS
of the above INIT_EXPR, and second, at the end of cxx_eval_store_expression
after having finished evaluating the above INIT_EXPR.  This then prevents the
rest of the outer delegating constructor from mutating 'bar'.

This (hopefully minimally risky) patch makes cxx_eval_call_expression refrain
from setting TREE_READONLY when evaluating the target constructor of a
delegating constructor.  It also makes cxx_eval_store_expression refrain from
setting TREE_READONLY when the object being initialized is "*this', on the basis
that it should be the responsibility of the routine that set 'this' in the first
place to set the object's TREE_READONLY appropriately.

gcc/cp/ChangeLog:

	PR c++/94772
	* constexpr.c (cxx_eval_call_expression): Don't set new_obj if we're
	evaluating the target constructor of a delegating constructor.
	(cxx_eval_store_expression): Don't set TREE_READONLY if the LHS of the
	INIT_EXPR is '*this'.

gcc/testsuite/ChangeLog:

	PR c++/94772
	* g++.dg/cpp1y/constexpr-tracking-const23.C: New test.
	* g++.dg/cpp1y/constexpr-tracking-const24.C: New test.
	* g++.dg/cpp1y/constexpr-tracking-const25.C: New test.
This commit is contained in:
Patrick Palka 2020-04-27 17:06:35 -04:00
parent 067ebf8413
commit 64da1b761d
6 changed files with 155 additions and 1 deletions

View File

@ -1,3 +1,11 @@
2020-04-27 Patrick Palka <ppalka@redhat.com>
PR c++/94772
* constexpr.c (cxx_eval_call_expression): Don't set new_obj if we're
evaluating the target constructor of a delegating constructor.
(cxx_eval_store_expression): Don't set TREE_READONLY if the LHS of the
INIT_EXPR is '*this'.
2020-04-26 Marek Polacek <polacek@redhat.com>
PR c++/90320

View File

@ -2371,6 +2371,21 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree t,
STRIP_NOPS (new_obj);
if (TREE_CODE (new_obj) == ADDR_EXPR)
new_obj = TREE_OPERAND (new_obj, 0);
if (ctx->call && ctx->call->fundef
&& DECL_CONSTRUCTOR_P (ctx->call->fundef->decl))
{
tree cur_obj = TREE_VEC_ELT (ctx->call->bindings, 0);
STRIP_NOPS (cur_obj);
if (TREE_CODE (cur_obj) == ADDR_EXPR)
cur_obj = TREE_OPERAND (cur_obj, 0);
if (new_obj == cur_obj)
/* We're calling the target constructor of a delegating
constructor, or accessing a base subobject through a
NOP_EXPR as part of a call to a base constructor, so
there is no new (sub)object. */
new_obj = NULL_TREE;
}
}
tree result = NULL_TREE;
@ -4950,7 +4965,18 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t,
if (TREE_CODE (t) == INIT_EXPR
&& TREE_CODE (*valp) == CONSTRUCTOR
&& TYPE_READONLY (type))
TREE_READONLY (*valp) = true;
{
if (INDIRECT_REF_P (target)
&& (is_this_parameter
(tree_strip_nop_conversions (TREE_OPERAND (target, 0)))))
/* We've just initialized '*this' (perhaps via the target
constructor of a delegating constructor). Leave it up to the
caller that set 'this' to set TREE_READONLY appropriately. */
gcc_checking_assert (same_type_ignoring_top_level_qualifiers_p
(TREE_TYPE (target), type));
else
TREE_READONLY (*valp) = true;
}
/* Update TREE_CONSTANT and TREE_SIDE_EFFECTS on enclosing
CONSTRUCTORs, if any. */

View File

@ -1,3 +1,10 @@
2020-04-27 Patrick Palka <ppalka@redhat.com>
PR c++/94772
* g++.dg/cpp1y/constexpr-tracking-const23.C: New test.
* g++.dg/cpp1y/constexpr-tracking-const24.C: New test.
* g++.dg/cpp1y/constexpr-tracking-const25.C: New test.
2020-04-27 Szabolcs Nagy <szabolcs.nagy@arm.com>
PR target/94697

View File

@ -0,0 +1,21 @@
// PR c++/94772
// { dg-do compile { target c++14 } }
struct foo
{
int x{};
constexpr foo() noexcept = default;
constexpr foo(int a) : foo{}
{ x = -a; }
constexpr foo(int a, int b) : foo{a}
{ x += a + b; }
};
int main()
{
constexpr foo bar{1, 2};
static_assert(bar.x == 2, "");
}

View File

@ -0,0 +1,26 @@
// PR c++/94772
// { dg-do compile { target c++14 } }
struct base
{
base() = default;
constexpr base(int) : base{} { }
};
struct foo : base
{
int x{};
constexpr foo(int a) : base{a}
{ x = -a; }
constexpr foo(int a, int b) : foo{a}
{ x += a + b; }
};
int main()
{
constexpr foo bar{1, 2};
static_assert(bar.x == 2, "");
}

View File

@ -0,0 +1,66 @@
// PR c++/94772
// { dg-do compile { target c++14 } }
template<int>
struct base
{
int y{};
base() = default;
constexpr base(int a) : base{}
{ y = a; }
};
struct foo : base<1>, base<2>
{
int x{};
constexpr foo() : base<2>{}
{
++x; --x;
++base<1>::y;
++base<2>::y;
}
constexpr foo(int a) : base<2>{a}
{
x = -base<2>::y;
++base<1>::y;
++base<2>::y;
}
constexpr foo(int a, int b) : foo{a}
{
x += a + b;
++base<1>::y;
++base<2>::y;
}
constexpr foo(int a, int b, int c) : base<1>{a}
{
x += a + b + c;
++base<1>::y;
++base<2>::y;
}
};
#define SA(X) static_assert(X, #X)
int main()
{
constexpr foo bar1{1, 2};
SA( bar1.x == 2 );
SA( bar1.base<1>::y == 2 );
SA( bar1.base<2>::y == 3 );
constexpr foo bar2{1, 2, 3};
SA( bar2.x == 6 );
SA( bar2.base<1>::y == 2 );
SA( bar2.base<2>::y == 1 );
constexpr foo bar3{};
SA( bar3.x == 0 );
SA( bar3.base<1>::y == 1 );
SA( bar3.base<2>::y == 1 );
}