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 <pola...@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