In this PR, we are performing constexpr evaluation of a CONSTRUCTOR of type union U which looks like
{.a=foo (&<PLACEHOLDER_EXPR union U>)}. Since the function foo takes a reference to the CONSTRUCTOR we're building, it could potentially modify the CONSTRUCTOR from under us. In particular since U is a union, the evaluation of a's initializer could change the active member from a to another member -- something which cxx_eval_bare_aggregate doesn't expect to happen. Upon further investigation, it turns out this issue is not limited to constructors of UNION_TYPE and not limited to cxx_eval_bare_aggregate either. For example, within cxx_eval_store_expression we may be evaluating an assignment such as (this comes from the test pr94066-2.C): ((union U *) this)->a = TARGET_EXPR <D.2163, foo ((union U *) this)>; where evaluation of foo could change the active member of *this, which was set earlier in cxx_eval_store_expression to 'a'. And if U is a RECORD_TYPE, then evaluation of foo could add new fields to *this, thereby making stale the 'valp' pointer to the target constructor_elt through which we're later assigning. So in short, it seems that both cxx_eval_bare_aggregate and cxx_eval_store_expression do not anticipate that a constructor_elt's initializer could modify the underlying CONSTRUCTOR as a side-effect. To fix this problem, this patch makes cxx_eval_bare_aggregate and cxx_eval_store_expression recompute the pointer to the constructor_elt through which we're assigning, after the initializer has been evaluated. I am worried that the use of get_or_insert_ctor_field in cxx_eval_bare_aggregate might introduce quadratic behavior where there wasn't before. I wonder if there's a cheap heuristic we can use in cxx_eval_bare_aggregate to determine whether "self-modification" took place, and in which case we could avoid calling get_or_insert_ctor_field and do the fast thing we that we were doing before the patch. Tested on x86_64-pc-linux-gnu, but a full bootstrap is in progress. Does this look like the right approach? gcc/cp/ChangeLog: PR c++/94066 * constexpr.c (get_or_insert_ctor_field): Split out (while adding handling for VECTOR_TYPEs) from ... (cxx_eval_store_expression): ... here. Record the sequence of indexes into INDEXES that yields the suboject we're assigning to. After evaluating the initializer of the store expression, recompute valp using INDEXES. (cxx_eval_bare_aggregate): Use get_or_insert_ctor_field to recompute the pointer to the constructor_elt we're assigning through after evaluating each initializer. gcc/testsuite/ChangeLog: PR c++/94066 * g++.dg/cpp1y/pr94066.C: New test. * g++.dg/cpp1y/pr94066-2.C: New test. * g++.dg/cpp1y/pr94066-3.C: New test. * g++.dg/cpp1y/pr94066-4.C: New test. * g++.dg/cpp1y/pr94066-5.C: New test. --- gcc/cp/constexpr.c | 184 +++++++++++++++---------- gcc/testsuite/g++.dg/cpp1y/pr94066-2.C | 21 +++ gcc/testsuite/g++.dg/cpp1y/pr94066-3.C | 21 +++ gcc/testsuite/g++.dg/cpp1y/pr94066-4.C | 22 +++ gcc/testsuite/g++.dg/cpp1y/pr94066-5.C | 18 +++ gcc/testsuite/g++.dg/cpp1y/pr94066.C | 20 +++ 6 files changed, 210 insertions(+), 76 deletions(-) create mode 100644 gcc/testsuite/g++.dg/cpp1y/pr94066-2.C create mode 100644 gcc/testsuite/g++.dg/cpp1y/pr94066-3.C create mode 100644 gcc/testsuite/g++.dg/cpp1y/pr94066-4.C create mode 100644 gcc/testsuite/g++.dg/cpp1y/pr94066-5.C create mode 100644 gcc/testsuite/g++.dg/cpp1y/pr94066.C diff --git a/gcc/cp/constexpr.c b/gcc/cp/constexpr.c index 192face9a3a..9fcf9f33457 100644 --- a/gcc/cp/constexpr.c +++ b/gcc/cp/constexpr.c @@ -3144,6 +3144,88 @@ find_array_ctor_elt (tree ary, tree dindex, bool insert) return -1; } +/* Return a pointer to the constructor_elt of CTOR which matches INDEX. If no + matching constructor_elt exists, then add it to CTOR. Emit an appropriate + diagnostic if CTOR constructs a UNION_TYPE and adding INDEX would change the + active member of the union, unless QUIET is true. */ + +static constructor_elt * +get_or_insert_ctor_field (tree ctor, tree index, + bool quiet, bool *non_constant_p) +{ + 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; + + if (TREE_CODE (type) == UNION_TYPE && CONSTRUCTOR_NELTS (ctor) + && CONSTRUCTOR_ELT (ctor, 0)->index != index) + { + if (cxx_dialect < cxx2a) + { + if (!quiet) + error ("change of the active member of a union from %qD to %qD", + CONSTRUCTOR_ELT (ctor, 0)->index, + index); + *non_constant_p = true; + } + /* Changing active member. */ + vec_safe_truncate (CONSTRUCTOR_ELTS (ctor), 0); + } + + constructor_elt *cep = NULL; + for (idx = 0; + 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 an out-of-bounds subscript INDEX into the expression ARRAY. */ @@ -3784,14 +3866,12 @@ cxx_eval_bare_aggregate (const constexpr_ctx *ctx, tree t, } else { - if (new_ctx.ctor != ctx->ctor) - { - /* We appended this element above; update the value. */ - gcc_assert ((*p)->last().index == index); - (*p)->last().value = elt; - } - else - CONSTRUCTOR_APPEND_ELT (*p, index, elt); + iloc_sentinel ils (cp_expr_location (t)); + constructor_elt *cep + = get_or_insert_ctor_field (ctx->ctor, index, + ctx->quiet, non_constant_p); + cep->value = elt; + /* Adding or replacing an element might change the ctor's flags. */ TREE_CONSTANT (ctx->ctor) = constant_p; TREE_SIDE_EFFECTS (ctx->ctor) = side_effects_p; @@ -4566,7 +4646,7 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t, type = TREE_TYPE (object); bool no_zero_init = true; - releasing_vec ctors; + releasing_vec ctors, indexes; while (!refs->is_empty ()) { if (*valp == NULL_TREE) @@ -4607,77 +4687,21 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t, subobjects will also be zero-initialized. */ no_zero_init = CONSTRUCTOR_NO_CLEARING (*valp); - vec_safe_push (ctors, *valp); - enum tree_code code = TREE_CODE (type); type = refs->pop(); tree index = refs->pop(); - constructor_elt *cep = NULL; - if (code == ARRAY_TYPE) - { - HOST_WIDE_INT i - = 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), - "change of the active member of a union " - "from %qD to %qD", - CONSTRUCTOR_ELT (*valp, 0)->index, - index); - *non_constant_p = true; - } - /* Changing active member. */ - vec_safe_truncate (CONSTRUCTOR_ELTS (*valp), 0); - no_zero_init = true; - } + vec_safe_push (ctors, *valp); + vec_safe_push (indexes, index); - for (idx = 0; - vec_safe_iterate (CONSTRUCTOR_ELTS (*valp), 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; - } + if (code == UNION_TYPE && CONSTRUCTOR_NELTS (*valp) + && CONSTRUCTOR_ELT (*valp, 0)->index != index) + no_zero_init = true; - /* We fell off the end of the CONSTRUCTOR, so insert a new - entry at the end. */ - insert: - { - constructor_elt ce = { index, NULL_TREE }; + iloc_sentinel ils (cp_expr_location (t)); + constructor_elt *cep + = get_or_insert_ctor_field (*valp, index, ctx->quiet, non_constant_p); - vec_safe_insert (CONSTRUCTOR_ELTS (*valp), idx, ce); - cep = CONSTRUCTOR_ELT (*valp, idx); - } - found:; - } valp = &cep->value; } @@ -4758,9 +4782,17 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t, init = tinit; init = cxx_eval_constant_expression (&new_ctx, init, false, non_constant_p, overflow_p); - if (ctors->is_empty()) - /* The hash table might have moved since the get earlier. */ - valp = ctx->global->values.get (object); + /* The hash table might have moved since the get earlier. */ + valp = ctx->global->values.get (object); + /* And the underlying CONSTRUCTORs may have been modified by the + initializer. */ + for (unsigned i = 0; i < vec_safe_length (indexes); i++) + { + constructor_elt *cep + = get_or_insert_ctor_field (*valp, indexes[i], + /*quiet=*/true, non_constant_p); + valp = &cep->value; + } } /* Don't share a CONSTRUCTOR that might be changed later. */ diff --git a/gcc/testsuite/g++.dg/cpp1y/pr94066-2.C b/gcc/testsuite/g++.dg/cpp1y/pr94066-2.C new file mode 100644 index 00000000000..ec8210a9d73 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp1y/pr94066-2.C @@ -0,0 +1,21 @@ +// PR c++/94066 +// { dg-do compile { target c++14 } } + +struct A { long x; }; + +union U; +constexpr A foo(U *up); + +union U { + U() = default; + A a = foo(this); int y; +}; + +constexpr A foo(U *up) { + up->y = 11; // { dg-error ".U::a. to .U::y." "" { target c++17_down } } + return {42}; +} + +extern constexpr U u = {}; // { dg-error ".U::y. to .U::a." "" { target c++17_down } } + +static_assert(u.a.x == 42, ""); // { dg-error "non-constant" "" { target c++17_down } } diff --git a/gcc/testsuite/g++.dg/cpp1y/pr94066-3.C b/gcc/testsuite/g++.dg/cpp1y/pr94066-3.C new file mode 100644 index 00000000000..b68c975d569 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp1y/pr94066-3.C @@ -0,0 +1,21 @@ +// PR c++/94066 +// { 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, ""); diff --git a/gcc/testsuite/g++.dg/cpp1y/pr94066-4.C b/gcc/testsuite/g++.dg/cpp1y/pr94066-4.C new file mode 100644 index 00000000000..0362c939faf --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp1y/pr94066-4.C @@ -0,0 +1,22 @@ +// PR c++/94066 +// { 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, ""); diff --git a/gcc/testsuite/g++.dg/cpp1y/pr94066-5.C b/gcc/testsuite/g++.dg/cpp1y/pr94066-5.C new file mode 100644 index 00000000000..63069f3ea5f --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp1y/pr94066-5.C @@ -0,0 +1,18 @@ +// PR c++/94066 +// { dg-do compile { target c++14 } } + +union U; +constexpr int foo(U *up); + +union U { + int a = foo(this); int y; +}; + +constexpr int foo(U *up) { + up->y = 11; + return 42; +} + +extern constexpr U u = {}; // { dg-error ".U::y. to .U::a." "" { target c++17_down } } + +static_assert(u.a == 42, ""); // { dg-error "non-constant" "" { target c++17_down } } diff --git a/gcc/testsuite/g++.dg/cpp1y/pr94066.C b/gcc/testsuite/g++.dg/cpp1y/pr94066.C new file mode 100644 index 00000000000..11912199406 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp1y/pr94066.C @@ -0,0 +1,20 @@ +// PR c++/94066 +// { dg-do compile { target c++14 } } + +struct A { long x; }; + +union U; +constexpr A foo(U *up); + +union U { + A a = foo(this); int y; +}; + +constexpr A foo(U *up) { + up->y = 11; // { dg-error ".U::a. to .U::y." "" { target c++17_down } } + return {42}; +} + +extern constexpr U u = {}; // { dg-error ".U::y. to .U::a." "" { target c++17_down } } + +static_assert(u.a.x == 42, ""); // { dg-error "non-constant" "" { target c++17_down } } -- 2.26.0.rc1.11.g30e9940356