diff options
author | Nathaniel Shead <nathanieloshead@gmail.com> | 2023-10-12 19:53:55 +1100 |
---|---|---|
committer | Jason Merrill <jason@redhat.com> | 2023-10-19 23:25:46 -0400 |
commit | 1d260ab0e39ea63644e3af3ab2e0db946026b5a6 (patch) | |
tree | fab27019b7d8083cb6a2746e87e8238f3c928f69 /gcc/cp/constexpr.cc | |
parent | b69ee50081ca7dbd034cc244cf4515285ca7aa72 (diff) | |
download | gcc-1d260ab0e39ea63644e3af3ab2e0db946026b5a6.zip gcc-1d260ab0e39ea63644e3af3ab2e0db946026b5a6.tar.gz gcc-1d260ab0e39ea63644e3af3ab2e0db946026b5a6.tar.bz2 |
c++: indirect change of active union member in constexpr [PR101631,PR102286]
This patch adds checks for attempting to change the active member of a
union by methods other than a member access expression.
To be able to properly distinguish `*(&u.a) = ` from `u.a = `, this
patch redoes the solution for c++/59950 to avoid extranneous *&; it
seems that the only case that needed the workaround was when copying
empty classes.
This patch also ensures that constructors for a union field mark that
field as the active member before entering the call itself; this ensures
that modifications of the field within the constructor's body don't
cause false positives (as these will not appear to be member access
expressions). This means that we no longer need to start the lifetime of
empty union members after the constructor body completes.
As a drive-by fix, this patch also ensures that value-initialised unions
are considered to have activated their initial member for the purpose of
checking stores and accesses, which catches some additional mistakes
pre-C++20.
PR c++/101631
PR c++/102286
gcc/cp/ChangeLog:
* call.cc (build_over_call): Fold more indirect refs for trivial
assignment op.
* class.cc (type_has_non_deleted_trivial_default_ctor): Create.
* constexpr.cc (cxx_eval_call_expression): Start lifetime of
union member before entering constructor.
(cxx_eval_component_reference): Check against first member of
value-initialised union.
(cxx_eval_store_expression): Activate member for
value-initialised union. Check for accessing inactive union
member indirectly.
* cp-tree.h (type_has_non_deleted_trivial_default_ctor):
Forward declare.
gcc/testsuite/ChangeLog:
* g++.dg/cpp1y/constexpr-89336-3.C: Fix union initialisation.
* g++.dg/cpp1y/constexpr-union6.C: New test.
* g++.dg/cpp1y/constexpr-union7.C: New test.
* g++.dg/cpp2a/constexpr-union2.C: New test.
* g++.dg/cpp2a/constexpr-union3.C: New test.
* g++.dg/cpp2a/constexpr-union4.C: New test.
* g++.dg/cpp2a/constexpr-union5.C: New test.
* g++.dg/cpp2a/constexpr-union6.C: New test.
Signed-off-by: Nathaniel Shead <nathanieloshead@gmail.com>
Reviewed-by: Jason Merrill <jason@redhat.com>
Diffstat (limited to 'gcc/cp/constexpr.cc')
-rw-r--r-- | gcc/cp/constexpr.cc | 166 |
1 files changed, 119 insertions, 47 deletions
diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc index 1d59a2f..6e84c2d 100644 --- a/gcc/cp/constexpr.cc +++ b/gcc/cp/constexpr.cc @@ -3190,40 +3190,34 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree t, cxx_set_object_constness (ctx, new_obj, /*readonly_p=*/false, non_constant_p, overflow_p); + /* If this is a constructor, we are beginning the lifetime of the + object we are initializing. */ + if (new_obj + && DECL_CONSTRUCTOR_P (fun) + && TREE_CODE (new_obj) == COMPONENT_REF + && TREE_CODE (TREE_TYPE (TREE_OPERAND (new_obj, 0))) == UNION_TYPE) + { + tree activate = build2 (INIT_EXPR, TREE_TYPE (new_obj), + new_obj, + build_constructor (TREE_TYPE (new_obj), + NULL)); + cxx_eval_constant_expression (ctx, activate, + lval, non_constant_p, overflow_p); + ggc_free (activate); + } + tree jump_target = NULL_TREE; cxx_eval_constant_expression (&call_ctx, body, vc_discard, non_constant_p, overflow_p, &jump_target); if (DECL_CONSTRUCTOR_P (fun)) - { - /* This can be null for a subobject constructor call, in - which case what we care about is the initialization - side-effects rather than the value. We could get at the - value by evaluating *this, but we don't bother; there's - no need to put such a call in the hash table. */ - result = lval ? ctx->object : ctx->ctor; - - /* If we've just evaluated a subobject constructor call for an - empty union member, it might not have produced a side effect - that actually activated the union member. So produce such a - side effect now to ensure the union appears initialized. */ - if (!result && new_obj - && TREE_CODE (new_obj) == COMPONENT_REF - && TREE_CODE (TREE_TYPE - (TREE_OPERAND (new_obj, 0))) == UNION_TYPE - && is_really_empty_class (TREE_TYPE (new_obj), - /*ignore_vptr*/false)) - { - tree activate = build2 (MODIFY_EXPR, TREE_TYPE (new_obj), - new_obj, - build_constructor (TREE_TYPE (new_obj), - NULL)); - cxx_eval_constant_expression (ctx, activate, lval, - non_constant_p, overflow_p); - ggc_free (activate); - } - } + /* This can be null for a subobject constructor call, in + which case what we care about is the initialization + side-effects rather than the value. We could get at the + value by evaluating *this, but we don't bother; there's + no need to put such a call in the hash table. */ + result = lval ? ctx->object : ctx->ctor; else if (VOID_TYPE_P (TREE_TYPE (res))) result = void_node; else @@ -4523,6 +4517,7 @@ cxx_eval_component_reference (const constexpr_ctx *ctx, tree t, *non_constant_p = true; return t; } + bool pmf = TYPE_PTRMEMFUNC_P (TREE_TYPE (whole)); FOR_EACH_CONSTRUCTOR_ELT (CONSTRUCTOR_ELTS (whole), i, field, value) { @@ -4541,21 +4536,36 @@ cxx_eval_component_reference (const constexpr_ctx *ctx, tree t, break; } } - if (TREE_CODE (TREE_TYPE (whole)) == UNION_TYPE - && CONSTRUCTOR_NELTS (whole) > 0) + if (TREE_CODE (TREE_TYPE (whole)) == UNION_TYPE) { - /* DR 1188 says we don't have to deal with this. */ - if (!ctx->quiet) + if (CONSTRUCTOR_NELTS (whole) > 0) { - constructor_elt *cep = CONSTRUCTOR_ELT (whole, 0); - if (cep->value == NULL_TREE) - error ("accessing uninitialized member %qD", part); - else - error ("accessing %qD member instead of initialized %qD member in " - "constant expression", part, cep->index); + /* DR 1188 says we don't have to deal with this. */ + if (!ctx->quiet) + { + constructor_elt *cep = CONSTRUCTOR_ELT (whole, 0); + if (cep->value == NULL_TREE) + error ("accessing uninitialized member %qD", part); + else + error ("accessing %qD member instead of initialized %qD member " + "in constant expression", part, cep->index); + } + *non_constant_p = true; + return t; + } + else if (!CONSTRUCTOR_NO_CLEARING (whole)) + { + /* Value-initialized union, check if looking at the first member. */ + tree first = next_aggregate_field (TYPE_FIELDS (TREE_TYPE (whole))); + if (first != part) + { + if (!ctx->quiet) + error ("accessing %qD member instead of initialized %qD " + "member in constant expression", part, first); + *non_constant_p = true; + return t; + } } - *non_constant_p = true; - return t; } /* We only create a CONSTRUCTOR for a subobject when we modify it, so empty @@ -6172,6 +6182,14 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t, mutable_p) && const_object_being_modified == NULL_TREE) const_object_being_modified = probe; + + /* Track named member accesses for unions to validate modifications + that change active member. */ + if (!evaluated && TREE_CODE (probe) == COMPONENT_REF) + vec_safe_push (refs, probe); + else + vec_safe_push (refs, NULL_TREE); + vec_safe_push (refs, elt); vec_safe_push (refs, TREE_TYPE (probe)); probe = ob; @@ -6180,6 +6198,7 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t, case REALPART_EXPR: gcc_assert (probe == target); + vec_safe_push (refs, NULL_TREE); vec_safe_push (refs, probe); vec_safe_push (refs, TREE_TYPE (probe)); probe = TREE_OPERAND (probe, 0); @@ -6187,6 +6206,7 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t, case IMAGPART_EXPR: gcc_assert (probe == target); + vec_safe_push (refs, NULL_TREE); vec_safe_push (refs, probe); vec_safe_push (refs, TREE_TYPE (probe)); probe = TREE_OPERAND (probe, 0); @@ -6275,6 +6295,7 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t, enum tree_code code = TREE_CODE (type); tree reftype = refs->pop(); tree index = refs->pop(); + bool is_access_expr = refs->pop() != NULL_TREE; if (code == COMPLEX_TYPE) { @@ -6313,23 +6334,73 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t, break; } - type = reftype; + /* If a union is zero-initialized, its first non-static named data member + is zero-initialized (and therefore active). */ + if (code == UNION_TYPE + && !no_zero_init + && CONSTRUCTOR_NELTS (*valp) == 0) + if (tree first = next_aggregate_field (TYPE_FIELDS (type))) + CONSTRUCTOR_APPEND_ELT (CONSTRUCTOR_ELTS (*valp), first, NULL_TREE); - if (code == UNION_TYPE && CONSTRUCTOR_NELTS (*valp) - && CONSTRUCTOR_ELT (*valp, 0)->index != index) + /* Check for implicit change of active member for a union. */ + if (code == UNION_TYPE + && (CONSTRUCTOR_NELTS (*valp) == 0 + || CONSTRUCTOR_ELT (*valp, 0)->index != index) + /* An INIT_EXPR of the last member in an access chain is always OK, + but still check implicit change of members earlier on; see + cpp2a/constexpr-union6.C. */ + && !(TREE_CODE (t) == INIT_EXPR && refs->is_empty ())) { - if (cxx_dialect < cxx20) + bool has_active_member = CONSTRUCTOR_NELTS (*valp) != 0; + tree inner = strip_array_types (reftype); + + if (has_active_member && cxx_dialect < cxx20) { if (!ctx->quiet) error_at (cp_expr_loc_or_input_loc (t), "change of the active member of a union " - "from %qD to %qD", + "from %qD to %qD is not a constant expression " + "before C++20", CONSTRUCTOR_ELT (*valp, 0)->index, index); *non_constant_p = true; } - else if (TREE_CODE (t) == MODIFY_EXPR - && CONSTRUCTOR_NO_CLEARING (*valp)) + else if (!is_access_expr + || (TREE_CODE (t) == MODIFY_EXPR + && CLASS_TYPE_P (inner) + && !type_has_non_deleted_trivial_default_ctor (inner))) + { + /* Diagnose changing active union member after initialization + without a valid member access expression, as described in + [class.union.general] p5. */ + if (!ctx->quiet) + { + auto_diagnostic_group d; + if (has_active_member) + error_at (cp_expr_loc_or_input_loc (t), + "accessing %qD member instead of initialized " + "%qD member in constant expression", + index, CONSTRUCTOR_ELT (*valp, 0)->index); + else + error_at (cp_expr_loc_or_input_loc (t), + "accessing uninitialized member %qD", + index); + if (is_access_expr) + inform (DECL_SOURCE_LOCATION (index), + "%qD does not implicitly begin its lifetime " + "because %qT does not have a non-deleted " + "trivial default constructor, use " + "%<std::construct_at%> instead", + index, inner); + else + inform (DECL_SOURCE_LOCATION (index), + "initializing %qD requires a member access " + "expression as the left operand of the assignment", + index); + } + *non_constant_p = true; + } + else if (has_active_member && CONSTRUCTOR_NO_CLEARING (*valp)) { /* Diagnose changing the active union member while the union is in the process of being initialized. */ @@ -6355,6 +6426,7 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t, activated_union_member_p = true; valp = &cep->value; + type = reftype; } /* For initialization of an empty base, the original target will be |