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