Commit 37244b21 by Patrick Palka

c++: Fix constexpr evaluation of self-modifying CONSTRUCTORs [PR94219]

This PR reveals that cxx_eval_bare_aggregate and cxx_eval_store_expression do
not anticipate that a constructor element's initializer could mutate the
underlying CONSTRUCTOR.  Evaluation of the initializer could add new elements to
the underlying CONSTRUCTOR, thereby potentially invalidating any pointers to
or assumptions about the CONSTRUCTOR's elements, and so these routines should be
prepared for that.

To fix this problem, this patch makes cxx_eval_bare_aggregate and
cxx_eval_store_expression recompute the constructor_elt pointers through which
we're assigning, after it evaluates the initializer.  Care is taken to to not
slow down the common case where the initializer does not modify the underlying
CONSTRUCTOR.

gcc/cp/ChangeLog:

	PR c++/94219
	PR c++/94205
	* constexpr.c (get_or_insert_ctor_field): Split out (while adding
	support for VECTOR_TYPEs, and optimizations for the common case)
	from ...
	(cxx_eval_store_expression): ... here.  Rename local variable
	'changed_active_union_member_p' to 'activated_union_member_p'.  Record
	the sequence of indexes into 'indexes' that yields the subobject we're
	assigning to.  Record the integer offsets of the constructor indexes
	we're assigning through into 'index_pos_hints'.  After evaluating the
	initializer of the store expression, recompute 'valp' using 'indexes'
	and using 'index_pos_hints' as hints.
	(cxx_eval_bare_aggregate): Tweak comments.  Use get_or_insert_ctor_field
	to recompute the constructor_elt pointer we're assigning through after
	evaluating each initializer.

gcc/testsuite/ChangeLog:

	PR c++/94219
	PR c++/94205
	* g++.dg/cpp1y/constexpr-nsdmi3.C: New test.
	* g++.dg/cpp1y/constexpr-nsdmi4.C: New test.
	* g++.dg/cpp1y/constexpr-nsdmi5.C: New test.
	* g++.dg/cpp1z/lambda-this5.C: New test.
parent 21e28527
2020-04-04 Patrick Palka <ppalka@redhat.com>
PR c++/94219
PR c++/94205
* constexpr.c (get_or_insert_ctor_field): Split out (while adding
support for VECTOR_TYPEs, and optimizations for the common case)
from ...
(cxx_eval_store_expression): ... here. Rename local variable
'changed_active_union_member_p' to 'activated_union_member_p'. Record
the sequence of indexes into 'indexes' that yields the subobject we're
assigning to. Record the integer offsets of the constructor indexes
we're assigning through into 'index_pos_hints'. After evaluating the
initializer of the store expression, recompute 'valp' using 'indexes'
and using 'index_pos_hints' as hints.
(cxx_eval_bare_aggregate): Tweak comments. Use get_or_insert_ctor_field
to recompute the constructor_elt pointer we're assigning through after
evaluating each initializer.
2020-04-04 Jason Merrill <jason@redhat.com> 2020-04-04 Jason Merrill <jason@redhat.com>
PR c++/67825 PR c++/67825
......
...@@ -3151,6 +3151,95 @@ find_array_ctor_elt (tree ary, tree dindex, bool insert) ...@@ -3151,6 +3151,95 @@ find_array_ctor_elt (tree ary, tree dindex, bool insert)
return -1; return -1;
} }
/* Return a pointer to the constructor_elt of CTOR which matches INDEX. If no
matching constructor_elt exists, then add one to CTOR.
As an optimization, if POS_HINT is non-negative then it is used as a guess
for the (integer) index of the matching constructor_elt within CTOR. */
static constructor_elt *
get_or_insert_ctor_field (tree ctor, tree index, int pos_hint)
{
/* Check the hint first. */
if (pos_hint >= 0 && (unsigned)pos_hint < CONSTRUCTOR_NELTS (ctor)
&& CONSTRUCTOR_ELT (ctor, pos_hint)->index == index)
return CONSTRUCTOR_ELT (ctor, pos_hint);
tree type = TREE_TYPE (ctor);
if (TREE_CODE (type) == VECTOR_TYPE && index == NULL_TREE)
{
CONSTRUCTOR_APPEND_ELT (CONSTRUCTOR_ELTS (ctor), index, NULL_TREE);
return &CONSTRUCTOR_ELTS (ctor)->last();
}
else if (TREE_CODE (type) == ARRAY_TYPE || TREE_CODE (type) == VECTOR_TYPE)
{
HOST_WIDE_INT i = find_array_ctor_elt (ctor, index, /*insert*/true);
gcc_assert (i >= 0);
constructor_elt *cep = CONSTRUCTOR_ELT (ctor, i);
gcc_assert (cep->index == NULL_TREE
|| TREE_CODE (cep->index) != RANGE_EXPR);
return cep;
}
else
{
gcc_assert (TREE_CODE (index) == FIELD_DECL);
/* We must keep the CONSTRUCTOR's ELTS in FIELD order.
Usually we meet initializers in that order, but it is
possible for base types to be placed not in program
order. */
tree fields = TYPE_FIELDS (DECL_CONTEXT (index));
unsigned HOST_WIDE_INT idx = 0;
constructor_elt *cep = NULL;
/* Check if we're changing the active member of a union. */
if (TREE_CODE (type) == UNION_TYPE && CONSTRUCTOR_NELTS (ctor)
&& CONSTRUCTOR_ELT (ctor, 0)->index != index)
vec_safe_truncate (CONSTRUCTOR_ELTS (ctor), 0);
/* If the bit offset of INDEX is larger than that of the last
constructor_elt, then we can just immediately append a new
constructor_elt to the end of CTOR. */
else if (CONSTRUCTOR_NELTS (ctor)
&& tree_int_cst_compare (bit_position (index),
bit_position (CONSTRUCTOR_ELTS (ctor)
->last().index)) > 0)
{
idx = CONSTRUCTOR_NELTS (ctor);
goto insert;
}
/* Otherwise, we need to iterate over CTOR to find or insert INDEX
appropriately. */
for (; vec_safe_iterate (CONSTRUCTOR_ELTS (ctor), idx, &cep);
idx++, fields = DECL_CHAIN (fields))
{
if (index == cep->index)
goto found;
/* The field we're initializing must be on the field
list. Look to see if it is present before the
field the current ELT initializes. */
for (; fields != cep->index; fields = DECL_CHAIN (fields))
if (index == fields)
goto insert;
}
/* We fell off the end of the CONSTRUCTOR, so insert a new
entry at the end. */
insert:
{
constructor_elt ce = { index, NULL_TREE };
vec_safe_insert (CONSTRUCTOR_ELTS (ctor), idx, ce);
cep = CONSTRUCTOR_ELT (ctor, idx);
}
found:;
return cep;
}
}
/* Under the control of CTX, issue a detailed diagnostic for /* Under the control of CTX, issue a detailed diagnostic for
an out-of-bounds subscript INDEX into the expression ARRAY. */ an out-of-bounds subscript INDEX into the expression ARRAY. */
...@@ -3760,14 +3849,18 @@ cxx_eval_bare_aggregate (const constexpr_ctx *ctx, tree t, ...@@ -3760,14 +3849,18 @@ cxx_eval_bare_aggregate (const constexpr_ctx *ctx, tree t,
{ {
tree orig_value = value; tree orig_value = value;
init_subob_ctx (ctx, new_ctx, index, value); init_subob_ctx (ctx, new_ctx, index, value);
int pos_hint = -1;
if (new_ctx.ctor != ctx->ctor) if (new_ctx.ctor != ctx->ctor)
/* If we built a new CONSTRUCTOR, attach it now so that other {
initializers can refer to it. */ /* If we built a new CONSTRUCTOR, attach it now so that other
CONSTRUCTOR_APPEND_ELT (*p, index, new_ctx.ctor); initializers can refer to it. */
CONSTRUCTOR_APPEND_ELT (*p, index, new_ctx.ctor);
pos_hint = vec_safe_length (*p) - 1;
}
else if (TREE_CODE (type) == UNION_TYPE) else if (TREE_CODE (type) == UNION_TYPE)
/* Otherwise if we're constructing a union, set the active union member /* Otherwise if we're constructing a non-aggregate union member, set
anyway so that we can later detect if the initializer attempts to the active union member now so that we can later detect and diagnose
activate another member. */ if its initializer attempts to activate another member. */
CONSTRUCTOR_APPEND_ELT (*p, index, NULL_TREE); CONSTRUCTOR_APPEND_ELT (*p, index, NULL_TREE);
tree elt = cxx_eval_constant_expression (&new_ctx, value, tree elt = cxx_eval_constant_expression (&new_ctx, value,
lval, lval,
...@@ -3804,18 +3897,18 @@ cxx_eval_bare_aggregate (const constexpr_ctx *ctx, tree t, ...@@ -3804,18 +3897,18 @@ cxx_eval_bare_aggregate (const constexpr_ctx *ctx, tree t,
{ {
if (TREE_CODE (type) == UNION_TYPE if (TREE_CODE (type) == UNION_TYPE
&& (*p)->last().index != index) && (*p)->last().index != index)
/* The initializer may have erroneously changed the active union /* The initializer erroneously changed the active union member that
member that we're initializing. */ we're initializing. */
gcc_assert (*non_constant_p); gcc_assert (*non_constant_p);
else if (new_ctx.ctor != ctx->ctor else
|| TREE_CODE (type) == UNION_TYPE)
{ {
/* We appended this element above; update the value. */ /* The initializer might have mutated the underlying CONSTRUCTOR,
gcc_assert ((*p)->last().index == index); so recompute the location of the target constructer_elt. */
(*p)->last().value = elt; constructor_elt *cep
= get_or_insert_ctor_field (ctx->ctor, index, pos_hint);
cep->value = elt;
} }
else
CONSTRUCTOR_APPEND_ELT (*p, index, elt);
/* Adding or replacing an element might change the ctor's flags. */ /* Adding or replacing an element might change the ctor's flags. */
TREE_CONSTANT (ctx->ctor) = constant_p; TREE_CONSTANT (ctx->ctor) = constant_p;
TREE_SIDE_EFFECTS (ctx->ctor) = side_effects_p; TREE_SIDE_EFFECTS (ctx->ctor) = side_effects_p;
...@@ -4590,8 +4683,9 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t, ...@@ -4590,8 +4683,9 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t,
type = TREE_TYPE (object); type = TREE_TYPE (object);
bool no_zero_init = true; bool no_zero_init = true;
releasing_vec ctors; releasing_vec ctors, indexes;
bool changed_active_union_member_p = false; auto_vec<int> index_pos_hints;
bool activated_union_member_p = false;
while (!refs->is_empty ()) while (!refs->is_empty ())
{ {
if (*valp == NULL_TREE) if (*valp == NULL_TREE)
...@@ -4632,94 +4726,49 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t, ...@@ -4632,94 +4726,49 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t,
subobjects will also be zero-initialized. */ subobjects will also be zero-initialized. */
no_zero_init = CONSTRUCTOR_NO_CLEARING (*valp); no_zero_init = CONSTRUCTOR_NO_CLEARING (*valp);
vec_safe_push (ctors, *valp);
enum tree_code code = TREE_CODE (type); enum tree_code code = TREE_CODE (type);
type = refs->pop(); type = refs->pop();
tree index = refs->pop(); tree index = refs->pop();
constructor_elt *cep = NULL; if (code == UNION_TYPE && CONSTRUCTOR_NELTS (*valp)
if (code == ARRAY_TYPE) && CONSTRUCTOR_ELT (*valp, 0)->index != index)
{ {
HOST_WIDE_INT i if (cxx_dialect < cxx2a)
= find_array_ctor_elt (*valp, index, /*insert*/true);
gcc_assert (i >= 0);
cep = CONSTRUCTOR_ELT (*valp, i);
gcc_assert (cep->index == NULL_TREE
|| TREE_CODE (cep->index) != RANGE_EXPR);
}
else
{
gcc_assert (TREE_CODE (index) == FIELD_DECL);
/* We must keep the CONSTRUCTOR's ELTS in FIELD order.
Usually we meet initializers in that order, but it is
possible for base types to be placed not in program
order. */
tree fields = TYPE_FIELDS (DECL_CONTEXT (index));
unsigned HOST_WIDE_INT idx;
if (code == UNION_TYPE && CONSTRUCTOR_NELTS (*valp)
&& CONSTRUCTOR_ELT (*valp, 0)->index != index)
{ {
if (cxx_dialect < cxx2a) if (!ctx->quiet)
{ error_at (cp_expr_loc_or_input_loc (t),
if (!ctx->quiet) "change of the active member of a union "
error_at (cp_expr_loc_or_input_loc (t), "from %qD to %qD",
"change of the active member of a union " CONSTRUCTOR_ELT (*valp, 0)->index,
"from %qD to %qD", index);
CONSTRUCTOR_ELT (*valp, 0)->index, *non_constant_p = true;
index);
*non_constant_p = true;
}
else if (TREE_CODE (t) == MODIFY_EXPR
&& CONSTRUCTOR_NO_CLEARING (*valp))
{
/* Diagnose changing the active union member while the union
is in the process of being initialized. */
if (!ctx->quiet)
error_at (cp_expr_loc_or_input_loc (t),
"change of the active member of a union "
"from %qD to %qD during initialization",
CONSTRUCTOR_ELT (*valp, 0)->index,
index);
*non_constant_p = true;
}
/* Changing active member. */
vec_safe_truncate (CONSTRUCTOR_ELTS (*valp), 0);
no_zero_init = true;
} }
else if (TREE_CODE (t) == MODIFY_EXPR
for (idx = 0; && CONSTRUCTOR_NO_CLEARING (*valp))
vec_safe_iterate (CONSTRUCTOR_ELTS (*valp), idx, &cep);
idx++, fields = DECL_CHAIN (fields))
{ {
if (index == cep->index) /* Diagnose changing the active union member while the union
goto found; is in the process of being initialized. */
if (!ctx->quiet)
/* The field we're initializing must be on the field error_at (cp_expr_loc_or_input_loc (t),
list. Look to see if it is present before the "change of the active member of a union "
field the current ELT initializes. */ "from %qD to %qD during initialization",
for (; fields != cep->index; fields = DECL_CHAIN (fields)) CONSTRUCTOR_ELT (*valp, 0)->index,
if (index == fields) index);
goto insert; *non_constant_p = true;
} }
no_zero_init = true;
}
/* We fell off the end of the CONSTRUCTOR, so insert a new vec_safe_push (ctors, *valp);
entry at the end. */ vec_safe_push (indexes, index);
insert:
{
constructor_elt ce = { index, NULL_TREE };
vec_safe_insert (CONSTRUCTOR_ELTS (*valp), idx, ce); constructor_elt *cep
cep = CONSTRUCTOR_ELT (*valp, idx); = get_or_insert_ctor_field (*valp, index, /*pos_hint=*/-1);
index_pos_hints.safe_push (cep - CONSTRUCTOR_ELTS (*valp)->begin());
if (code == UNION_TYPE)
activated_union_member_p = true;
if (code == UNION_TYPE)
/* Record that we've changed an active union member. */
changed_active_union_member_p = true;
}
found:;
}
valp = &cep->value; valp = &cep->value;
} }
...@@ -4800,9 +4849,16 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t, ...@@ -4800,9 +4849,16 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t,
init = tinit; init = tinit;
init = cxx_eval_constant_expression (&new_ctx, init, false, init = cxx_eval_constant_expression (&new_ctx, init, false,
non_constant_p, overflow_p); non_constant_p, overflow_p);
if (ctors->is_empty()) /* The hash table might have moved since the get earlier, and the
/* The hash table might have moved since the get earlier. */ initializer might have mutated the underlying CONSTRUCTORs, so we must
valp = ctx->global->values.get (object); recompute VALP. */
valp = ctx->global->values.get (object);
for (unsigned i = 0; i < vec_safe_length (indexes); i++)
{
constructor_elt *cep
= get_or_insert_ctor_field (*valp, indexes[i], index_pos_hints[i]);
valp = &cep->value;
}
} }
/* Don't share a CONSTRUCTOR that might be changed later. */ /* Don't share a CONSTRUCTOR that might be changed later. */
...@@ -4847,7 +4903,7 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t, ...@@ -4847,7 +4903,7 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t,
unsigned i; unsigned i;
bool c = TREE_CONSTANT (init); bool c = TREE_CONSTANT (init);
bool s = TREE_SIDE_EFFECTS (init); bool s = TREE_SIDE_EFFECTS (init);
if (!c || s || changed_active_union_member_p) if (!c || s || activated_union_member_p)
FOR_EACH_VEC_ELT (*ctors, i, elt) FOR_EACH_VEC_ELT (*ctors, i, elt)
{ {
if (!c) if (!c)
......
2020-04-04 Patrick Palka <ppalka@redhat.com>
PR c++/94219
PR c++/94205
* g++.dg/cpp1y/constexpr-nsdmi3.C: New test.
* g++.dg/cpp1y/constexpr-nsdmi4.C: New test.
* g++.dg/cpp1y/constexpr-nsdmi5.C: New test.
* g++.dg/cpp1z/lambda-this5.C: New test.
2020-04-04 Jan Hubicka <hubicka@ucw.cz> 2020-04-04 Jan Hubicka <hubicka@ucw.cz>
PR ipa/93940 PR ipa/93940
......
// PR c++/94205
// { dg-do compile { target c++14 } }
struct S
{
struct A
{
S *p;
constexpr A(S* p): p(p) {}
constexpr operator int() { p->a = 5; return 6; }
};
int a = A(this);
};
constexpr S s = {};
#define SA(X) static_assert((X),#X)
SA(s.a == 6);
// PR c++/94219
// { dg-do compile { target c++14 } }
struct A { long x; };
struct U;
constexpr A foo(U *up);
struct U {
A a = foo(this); int y;
};
constexpr A foo(U *up) {
up->y = 11;
return {42};
}
extern constexpr U u = {};
static_assert(u.y == 11, "");
static_assert(u.a.x == 42, "");
// PR c++/94219
// { dg-do compile { target c++14 } }
struct A { long x; };
struct U;
constexpr A foo(U *up);
struct U {
U() = default;
int y; A a = foo(this);
};
constexpr A foo(U *up) {
up->y = 11;
return {42};
}
extern constexpr U u = {};
static_assert(u.y == 11, "");
static_assert(u.a.x == 42, "");
// PR c++/94205
// { dg-do compile { target c++17 } }
struct S
{
int a = [this] { this->a = 5; return 6; } ();
};
constexpr S s = {};
static_assert(s.a == 6);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment