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

Reply via email to