C++ PATCH for c++/88337 - Implement P1327R1: Allow dynamic_cast in constexpr
Jason Merrill
jason@redhat.com
Fri Nov 22 21:14:00 GMT 2019
On 11/8/19 4:24 PM, Marek Polacek wrote:
> After much weeping and gnashing of teeth, here's a patch to handle dynamic_cast
> in constexpr evaluation. While the change in the standard is trivial (see
> <http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1327r1.html>), the
> change in the compiler is less so.
>
> When build_dynamic_cast realizes that a dynamic_cast needs a run-time check, it
> generates a call to __dynamic_cast -- see dyncast.cc in libsupc++ for its
> definition. The gist of my approach is to evaluate such a call at compile time.
>
> This should be easy in theory: let the constexpr machinery find out the dynamic
> type and then handle a sidecast and upcast. That's ultimately what the patch
> is trying to do but there was a number of hindrances.
>
> 1) We can't use __dynamic_cast's type_info parameters, this type is not a
> literal class. But that means we have no idea what we're converting to!
get_tinfo_decl sets the TREE_TYPE of the DECL_NAME of the tinfo decl to
the relevant type, can't you use that?
> 2) [class.cdtor] says that when a dynamic_cast is used in a constructor or
> destructor and the operand of the dynamic_cast refers to the object under
> construction or destruction, this object is considered to be a most derived
> object.
This means that during the 'tor the vtable pointer refers to the
type_info for that class and the offset-to-top is 0. Can you use that?
> This was tricky, and the only thing that seemed to work was to add
> a new member to constexpr_global_ctx. I was happy to find out that I could
> use new_obj I'd added recently. Note that destruction is *not* handled at
> all and in fact I couldn't even construct a testcase where that would make
> a difference.
> 3) We can't rely on the hint __dynamic_cast gave us; the comment in
> cxx_eval_dynamic_cast_fn explains why the accessible_base_p checks were
> necessary.
>
> There are many various scanarios regarding inheritance so special care was
> devoted to test as much as possible, but testing the "dynamic_cast in
> a constructor" could be expanded.
>
> This patch doesn't handle polymorphic typeid yet. I think it will be easier
> to review to separate these two. Hopefully the typeid part will be much
> easier.
>
> Bootstrapped/regtested on x86_64-linux.
>
> 2019-11-08 Marek Polacek <polacek@redhat.com>
>
> PR c++/88337 - Implement P1327R1: Allow dynamic_cast in constexpr.
> * call.c (is_base_field_ref): No longer static.
> * constexpr.c (struct constexpr_global_ctx): Add ctor_object member
> and initialize it.
> (cxx_dynamic_cast_fn_p): New function.
> (cxx_eval_dynamic_cast_fn): Likewise.
> (cxx_eval_call_expression): Call cxx_eval_dynamic_cast_fn for a call
> to __dynamic_cast. Save the object a constexpr constructor is
> constructing.
> (cxx_eval_constant_expression) <case UNARY_PLUS_EXPR>: Save the target
> type of a call to __dynamic_cast.
> (potential_constant_expression_1): Don't give up on
> cxx_dynamic_cast_fn_p.
> * cp-tree.h (is_base_field_ref): Declare.
> * parser.c (cp_parser_postfix_expression): Set location of expression.
> * rtti.c (build_dynamic_cast_1): When creating a call to
> __dynamic_cast, use the location of the original expression.
>
> * g++.dg/cpp2a/constexpr-dynamic1.C: New test.
> * g++.dg/cpp2a/constexpr-dynamic10.C: New test.
> * g++.dg/cpp2a/constexpr-dynamic11.C: New test.
> * g++.dg/cpp2a/constexpr-dynamic12.C: New test.
> * g++.dg/cpp2a/constexpr-dynamic13.C: New test.
> * g++.dg/cpp2a/constexpr-dynamic14.C: New test.
> * g++.dg/cpp2a/constexpr-dynamic2.C: New test.
> * g++.dg/cpp2a/constexpr-dynamic3.C: New test.
> * g++.dg/cpp2a/constexpr-dynamic4.C: New test.
> * g++.dg/cpp2a/constexpr-dynamic5.C: New test.
> * g++.dg/cpp2a/constexpr-dynamic6.C: New test.
> * g++.dg/cpp2a/constexpr-dynamic7.C: New test.
> * g++.dg/cpp2a/constexpr-dynamic8.C: New test.
> * g++.dg/cpp2a/constexpr-dynamic9.C: New test.
>
> diff --git gcc/cp/call.c gcc/cp/call.c
> index 0034c1cee0d..5de2aca1358 100644
> --- gcc/cp/call.c
> +++ gcc/cp/call.c
> @@ -8193,7 +8193,7 @@ call_copy_ctor (tree a, tsubst_flags_t complain)
>
> /* Return true iff T refers to a base field. */
>
> -static bool
> +bool
> is_base_field_ref (tree t)
> {
> STRIP_NOPS (t);
> diff --git gcc/cp/constexpr.c gcc/cp/constexpr.c
> index 20fddc57825..ef7706347bc 100644
> --- gcc/cp/constexpr.c
> +++ gcc/cp/constexpr.c
> @@ -1025,8 +1025,11 @@ struct constexpr_global_ctx {
> /* Heap VAR_DECLs created during the evaluation of the outermost constant
> expression. */
> auto_vec<tree, 16> heap_vars;
> + /* For a constructor, this is the object we're constructing. */
> + tree ctor_object;
> /* Constructor. */
> - constexpr_global_ctx () : constexpr_ops_count (0) {}
> + constexpr_global_ctx () : constexpr_ops_count (0), ctor_object (NULL_TREE)
> + {}
> };
>
> /* The constexpr expansion context. CALL is the current function
> @@ -1663,6 +1666,244 @@ is_std_allocator_allocate (tree fndecl)
> return decl_in_std_namespace_p (decl);
> }
>
> +/* Return true if FNDECL is __dynamic_cast. */
> +
> +static inline bool
> +cxx_dynamic_cast_fn_p (tree fndecl)
> +{
> + return (cxx_dialect >= cxx2a
> + && id_equal (DECL_NAME (fndecl), "__dynamic_cast")
> + && CP_DECL_CONTEXT (fndecl) == global_namespace);
> +}
> +
> +/* Evaluate a call to __dynamic_cast (permitted by P1327R1).
> +
> + The declaration of __dynamic_cast is:
> +
> + void* __dynamic_cast (const void* __src_ptr,
> + const __class_type_info* __src_type,
> + const __class_type_info* __dst_type,
> + ptrdiff_t __src2dst);
> +
> + where src2dst has the following possible values
> +
> + >-1: src_type is a unique public non-virtual base of dst_type
> + dst_ptr + src2dst == src_ptr
> + -1: unspecified relationship
> + -2: src_type is not a public base of dst_type
> + -3: src_type is a multiple public non-virtual base of dst_type
> +
> + Since literal types can't have virtual bases, we only expect hint >=0
> + or -2. */
> +
> +static tree
> +cxx_eval_dynamic_cast_fn (const constexpr_ctx *ctx, tree call,
> + bool *non_constant_p, bool *overflow_p)
> +{
> + /* T will be something like
> + __dynamic_cast ((B*) b, &_ZTI1B, &_ZTI1D, 8)
> + dismantle it. */
> + gcc_assert (call_expr_nargs (call) == 4);
> + tsubst_flags_t complain = ctx->quiet ? tf_none : tf_warning_or_error;
> + tree obj = CALL_EXPR_ARG (call, 0);
> + HOST_WIDE_INT hint = int_cst_value (CALL_EXPR_ARG (call, 3));
> + location_t loc = cp_expr_loc_or_input_loc (call);
> +
> + /* Get the target type we've stashed. */
> + tree type;
> + if (tree *p = ctx->global->values.get (dynamic_cast_node))
> + type = *p;
> + else
> + {
> + *non_constant_p = true;
> + return call;
> + }
> + /* Don't need it anymore. */
> + ctx->global->values.remove (dynamic_cast_node);
> +
> + const bool reference_p = TYPE_REF_P (type);
> + /* TYPE can only be either T* or T&. Get what T points or refers to. */
> + type = TREE_TYPE (type);
> +
> + /* Evaluate the object so that we know its dynamic type. */
> + obj = cxx_eval_constant_expression (ctx, obj, /*lval*/false, non_constant_p,
> + overflow_p);
> + if (*non_constant_p)
> + return call;
> +
> + /* We expect OBJ to be in form of &d.D.2102 when HINT == 0,
> + but when HINT is > 0, it can also be something like
> + &d.D.2102 + 18446744073709551608, which includes the BINFO_OFFSET. */
> + if (TREE_CODE (obj) == POINTER_PLUS_EXPR)
> + obj = TREE_OPERAND (obj, 0);
> + /* If OBJ doesn't refer to a base field, we're done. */
> + if (!is_base_field_ref (obj))
> + return integer_zero_node;
> + STRIP_NOPS (obj);
> + /* Strip the &. */
> + if (TREE_CODE (obj) == ADDR_EXPR)
> + obj = TREE_OPERAND (obj, 0);
> +
> + /* Given dynamic_cast<T>(v),
> +
> + [expr.dynamic.cast] If C is the class type to which T points or refers,
> + the runtime check logically executes as follows:
> +
> + If, in the most derived object pointed (referred) to by v, v points
> + (refers) to a public base class subobject of a C object, and if only
> + one object of type C is derived from the subobject pointed (referred)
> + to by v the result points (refers) to that C object.
> +
> + In this case, HINT >= 0. This is a downcast. */
Please avoid using up/down to refer to inheritance relationships, people
disagree about what they mean. :)
> + if (hint >= 0)
> + {
> + /* We now have something like
> +
> + g.D.2181.D.2154.D.2102.D.2093
> + ^~~~~~
> + OBJ
> +
> + and we're looking for a component with type TYPE. */
> + tree objtype = TREE_TYPE (obj);
> + tree ctor_object = ctx->global->ctor_object;
> +
> + for (;;)
> + {
> + /* Unfortunately, we can't rely on HINT, we need to do some
> + verification here:
> +
> + 1) Consider
> + dynamic_cast<E*>((A*)(B*)(D*)&e);
> + and imagine that there's an accessible base A from E (so HINT
> + is >= 0), but it's a different A than where OBJ points to.
> + We need to check that the one we're accessing via E->D->B->A is
> + in fact accessible. If e.g. B on this path is private, we gotta
> + fail. So check that every base on the way can be reached from
> + the preceding class.
> +
> + 2) Further, consider
> +
> + struct A { virtual void a(); };
> + struct AA : A {};
> + struct B : A {};
> + struct Y : AA, private B {};
> +
> + dynamic_cast<Y*>((A*)(B*)&y);
> +
> + Here HINT is >=0, because A is a public unique base of Y,
> + but that's not the A accessed via Y->B->A. */
> + if (!accessible_base_p (TREE_TYPE (obj), objtype, false)
> + || !accessible_base_p (type, TREE_TYPE (obj), false))
> + {
> + if (reference_p)
> + {
> + if (!ctx->quiet)
> + {
> + error_at (loc, "reference %<dynamic_cast%> failed");
> + inform (loc, "static type %qT of its operand is a "
> + "non-public base class of dynamic type %qT",
> + objtype, type);
> + }
> + *non_constant_p = true;
> + }
> + return integer_zero_node;
> + }
> +
> + if (same_type_ignoring_top_level_qualifiers_p (TREE_TYPE (obj), type))
> + /* The result points to the TYPE object. */
> + return cp_build_addr_expr (obj, complain);
> + else if (TREE_CODE (obj) == COMPONENT_REF
> + && DECL_FIELD_IS_BASE (TREE_OPERAND (obj, 1)))
> + obj = TREE_OPERAND (obj, 0);
In the structural_type_p patch I tried to set
TREE_PRIVATE/TREE_PROTECTED in build_base_field_1, would that be useful
to check instead of accessible_base_p? Though I'm not sure I was
successful, I got a bug report about structural type vs. private bases
that I haven't looked at yet.
Jason
More information about the Gcc-patches
mailing list