diff options
author | Marek Polacek <polacek@redhat.com> | 2022-04-26 15:52:00 -0400 |
---|---|---|
committer | Marek Polacek <polacek@redhat.com> | 2022-05-25 11:00:10 -0400 |
commit | 1b661f3f5e712c951e774b3b91fffe4dac734cc7 (patch) | |
tree | f16ce49da86f56df90e8b80135becc8bb1d88f14 /gcc/cp | |
parent | 75c4e4909ae2667f56487434f99c2915b4570794 (diff) | |
download | gcc-1b661f3f5e712c951e774b3b91fffe4dac734cc7.zip gcc-1b661f3f5e712c951e774b3b91fffe4dac734cc7.tar.gz gcc-1b661f3f5e712c951e774b3b91fffe4dac734cc7.tar.bz2 |
c++: ICE with temporary of class type in DMI [PR100252]
Consider
struct A {
int x;
int y = x;
};
struct B {
int x = 0;
int y = A{x}.y; // #1
};
where for #1 we end up with
{.x=(&<PLACEHOLDER_EXPR struct B>)->x, .y=(&<PLACEHOLDER_EXPR struct A>)->x}
that is, two PLACEHOLDER_EXPRs for different types on the same level in
a {}. This crashes because our CONSTRUCTOR_PLACEHOLDER_BOUNDARY mechanism to
avoid replacing unrelated PLACEHOLDER_EXPRs cannot deal with it.
Here's why we wound up with those PLACEHOLDER_EXPRs: When we're performing
cp_parser_late_parsing_nsdmi for "int y = A{x}.y;" we use finish_compound_literal
on type=A, compound_literal={((struct B *) this)->x}. When digesting this
initializer, we call get_nsdmi which creates a PLACEHOLDER_EXPR for A -- we don't
have any object to refer to yet. After digesting, we have
{.x=((struct B *) this)->x, .y=(&<PLACEHOLDER_EXPR struct A>)->x}
and since we've created a PLACEHOLDER_EXPR inside it, we marked the whole ctor
CONSTRUCTOR_PLACEHOLDER_BOUNDARY. f_c_l creates a TARGET_EXPR and returns
TARGET_EXPR <D.2384, {.x=((struct B *) this)->x, .y=(&<PLACEHOLDER_EXPR struct A>)->x}>
Then we get to
B b = {};
and call store_init_value, which digests the {}, which produces
{.x=NON_LVALUE_EXPR <0>, .y=(TARGET_EXPR <D.2395, {.x=(&<PLACEHOLDER_EXPR struct B>)->x, .y=(&<PLACEHOLDER_EXPR struct A>)->x}>).y}
lookup_placeholder in constexpr won't find an object to replace the
PLACEHOLDER_EXPR for B, because ctx->object will be D.2395 of type A, and we
cannot search outward from D.2395 to find 'b'.
The call to replace_placeholders in store_init_value will not do anything:
we've marked the inner { } CONSTRUCTOR_PLACEHOLDER_BOUNDARY, and it's only
a sub-expression, so replace_placeholders does nothing, so the <P_E struct B>
stays even though now is the perfect time to replace it because we have an
object for it: 'b'.
Later, in cp_gimplify_init_expr the *expr_p is
D.2395 = {.x=(&<PLACEHOLDER_EXPR struct B>)->x, .y=(&<PLACEHOLDER_EXPR struct A>)->x}
where D.2395 is of type A, but we crash because we hit <P_E struct B>, which
has a different type.
My idea was to replace <P_E struct A> with D.2384 after creating the
TARGET_EXPR because that means we have an object we can refer to.
Then clear CONSTRUCTOR_PLACEHOLDER_BOUNDARY because we no longer have
a PLACEHOLDER_EXPR in the {}. Then store_init_value will be able to
replace <P_E struct B> with 'b', and we should be good to go. We must
be careful not to break guaranteed copy elision, so this replacement
happens in digest_nsdmi_init where we can see the whole initializer,
and avoid replacing any placeholders in TARGET_EXPRs used in the context
of initialization/copy elision. This is achieved via the new function
called potential_prvalue_result_of.
While fixing this problem, I found PR105550, thus the FIXMEs in the
tests.
PR c++/100252
gcc/cp/ChangeLog:
* typeck2.cc (potential_prvalue_result_of): New.
(replace_placeholders_for_class_temp_r): New.
(digest_nsdmi_init): Call it.
gcc/testsuite/ChangeLog:
* g++.dg/cpp1y/nsdmi-aggr14.C: New test.
* g++.dg/cpp1y/nsdmi-aggr15.C: New test.
* g++.dg/cpp1y/nsdmi-aggr16.C: New test.
* g++.dg/cpp1y/nsdmi-aggr17.C: New test.
* g++.dg/cpp1y/nsdmi-aggr18.C: New test.
* g++.dg/cpp1y/nsdmi-aggr19.C: New test.
Diffstat (limited to 'gcc/cp')
-rw-r--r-- | gcc/cp/typeck2.cc | 91 |
1 files changed, 91 insertions, 0 deletions
diff --git a/gcc/cp/typeck2.cc b/gcc/cp/typeck2.cc index 1d92310..1a96be3 100644 --- a/gcc/cp/typeck2.cc +++ b/gcc/cp/typeck2.cc @@ -1371,6 +1371,71 @@ digest_init_flags (tree type, tree init, int flags, tsubst_flags_t complain) return digest_init_r (type, init, 0, flags, complain); } +/* Return true if SUBOB initializes the same object as FULL_EXPR. + For instance: + + A a = A{}; // initializer + A a = (A{}); // initializer + A a = (1, A{}); // initializer + A a = true ? A{} : A{}; // initializer + auto x = A{}.x; // temporary materialization + auto x = foo(A{}); // temporary materialization + + FULL_EXPR is the whole expression, SUBOB is its TARGET_EXPR subobject. */ + +static bool +potential_prvalue_result_of (tree subob, tree full_expr) +{ + if (subob == full_expr) + return true; + else if (TREE_CODE (full_expr) == TARGET_EXPR) + { + tree init = TARGET_EXPR_INITIAL (full_expr); + if (TREE_CODE (init) == COND_EXPR) + return (potential_prvalue_result_of (subob, TREE_OPERAND (init, 1)) + || potential_prvalue_result_of (subob, TREE_OPERAND (init, 2))); + else if (TREE_CODE (init) == COMPOUND_EXPR) + return potential_prvalue_result_of (subob, TREE_OPERAND (init, 1)); + /* ??? I don't know if this can be hit. */ + else if (TREE_CODE (init) == PAREN_EXPR) + { + gcc_checking_assert (false); + return potential_prvalue_result_of (subob, TREE_OPERAND (init, 0)); + } + } + return false; +} + +/* Callback to replace PLACEHOLDER_EXPRs in a TARGET_EXPR (which isn't used + in the context of guaranteed copy elision). */ + +static tree +replace_placeholders_for_class_temp_r (tree *tp, int *, void *data) +{ + tree t = *tp; + tree full_expr = *static_cast<tree *>(data); + + /* We're looking for a TARGET_EXPR nested in the whole expression. */ + if (TREE_CODE (t) == TARGET_EXPR + && !potential_prvalue_result_of (t, full_expr)) + { + tree init = TARGET_EXPR_INITIAL (t); + while (TREE_CODE (init) == COMPOUND_EXPR) + init = TREE_OPERAND (init, 1); + if (TREE_CODE (init) == CONSTRUCTOR + && CONSTRUCTOR_PLACEHOLDER_BOUNDARY (init)) + { + tree obj = TARGET_EXPR_SLOT (t); + replace_placeholders (init, obj); + /* We should have dealt with all PLACEHOLDER_EXPRs. */ + CONSTRUCTOR_PLACEHOLDER_BOUNDARY (init) = false; + gcc_checking_assert (!find_placeholders (init)); + } + } + + return NULL_TREE; +} + /* Process the initializer INIT for an NSDMI DECL (a FIELD_DECL). */ tree digest_nsdmi_init (tree decl, tree init, tsubst_flags_t complain) @@ -1390,6 +1455,32 @@ digest_nsdmi_init (tree decl, tree init, tsubst_flags_t complain) && CP_AGGREGATE_TYPE_P (type)) init = reshape_init (type, init, complain); init = digest_init_flags (type, init, flags, complain); + + /* We may have temporary materialization in a NSDMI, if the initializer + has something like A{} in it. Digesting the {} could have introduced + a PLACEHOLDER_EXPR referring to A. Now that we've got a TARGET_EXPR, + we have an object we can refer to. The reason we bother doing this + here is for code like + + struct A { + int x; + int y = x; + }; + + struct B { + int x = 0; + int y = A{x}.y; // #1 + }; + + where in #1 we don't want to end up with two PLACEHOLDER_EXPRs for + different types on the same level in a {} when lookup_placeholder + wouldn't find a named object for the PLACEHOLDER_EXPR for A. Note, + temporary materialization does not occur when initializing an object + from a prvalue of the same type, therefore we must not replace the + placeholder with a temporary object so that it can be elided. */ + cp_walk_tree (&init, replace_placeholders_for_class_temp_r, &init, + nullptr); + return init; } |