On Fri, Jul 24, 2026 at 11:22:32AM -0400, Jason Merrill wrote:
> On 7/24/26 10:19 AM, Jakub Jelinek wrote:
> > Hi!
> > 
> > We silently accept during constant evaluation
> > https://eel.is/c++draft/ub:expr.static.cast.base.class
> > and
> > https://eel.is/c++draft/ub:expr.static.cast.downcast.wrong.derived.type
> > This has been undefined behavior all the way back to C++98.
> > 
> > The following patch attempts to diagnose this.
> > 
> > So far tested with
> > GXX_TESTSUITE_STDS=98,11,14,17,20,23,26,29 make check-g++ -j32 -k
> > make -j32 -k check-target-libstdc++-v3
> > 
> > Ok for trunk if it passes full bootstrap/regtest?
> > 
> > 2026-07-24  Jakub Jelinek  <[email protected]>
> > 
> >     * constexpr.cc (cxx_eval_constant_expression) <case CONVERT_EXPR>:
> >     Set *non_constant_p and optionally diagnose if a static_cast
> >     downcast results in undefined behavior.
> > 
> >     * g++.dg/cpp0x/constexpr-static-cast1.C: New test.
> >     * g++.dg/cpp0x/constexpr-static-cast2.C: New test.
> >     * g++.dg/cpp1y/constexpr-static-cast1.C: New test.
> >     * g++.dg/cpp1y/constexpr-static-cast2.C: New test.
> >     * g++.dg/cpp1y/constexpr-static-cast3.C: New test.
> > 
> > --- gcc/cp/constexpr.cc.jj  2026-07-23 21:31:45.145999227 +0200
> > +++ gcc/cp/constexpr.cc     2026-07-24 14:51:54.491184339 +0200
> > @@ -10259,6 +10259,56 @@ cxx_eval_constant_expression (const cons
> >           }
> >       }
> > +   /* [expr.static.cast]/10: A prvalue of type "pointer to cv1 B", where
> > +      B is a class type, can be converted to a prvalue of type
> > +      "pointer to cv2 D", where D is a complete class derived from B, ...
> > +      If the prvalue of type "pointer to cv1 B" points to a B that is
> > +      actually a base class subobject of an object of type D, the
> > +      resulting pointer points to the enclosing object of type D.
> > +      Otherwise, the behavior is undefined.
> > +      Similarly [expr.static.cast]/2 for references. */
> > +   if (INDIRECT_TYPE_P (type)
> > +       && INDIRECT_TYPE_P (TREE_TYPE (op))
> > +       && COMPLETE_TYPE_P (TREE_TYPE (type))
> > +       && !integer_zerop (op)
> > +       && is_properly_derived_from (TREE_TYPE (type),
> > +                                    TREE_TYPE (TREE_TYPE (op))))
> > +     {
> > +       tree sop = tree_strip_nop_conversions (op);
> > +       if (TREE_CODE (sop) == POINTER_PLUS_EXPR)
> > +         sop = cxx_fold_indirect_ref (ctx, loc, TREE_TYPE (type),
> > +                                      op, NULL, jump_target);
> 
> Why not do this unconditionally, like the cast from void* code above?
> 
> > +       else if (TREE_CODE (sop) == ADDR_EXPR)
> > +         sop = TREE_OPERAND (sop, 0);
> > +       else
> > +         sop = NULL_TREE;
> > +       if (sop == NULL_TREE)
> > +         {
> > +           if (!ctx->quiet)
> > +             error_at (loc, "cannot cast object to type %qT",
> > +                       TREE_TYPE (type));
> > +           *non_constant_p = true;
> > +           return t;
> > +         }
> > +       tree derived = cp_build_qualified_type (TREE_TYPE (type),
> > +                                               TYPE_UNQUALIFIED);
> > +       while (TREE_CODE (sop) == COMPONENT_REF
> > +              && DECL_FIELD_IS_BASE (TREE_OPERAND (sop, 1))
> > +              && !same_type_ignoring_top_level_qualifiers_p
> > +                                   (TREE_TYPE (sop), derived))
> > +         sop = TREE_OPERAND (sop, 0);
> > +       tree soptype = strip_array_types (TREE_TYPE (sop));
> > +       if (!same_type_ignoring_top_level_qualifiers_p (soptype, derived))
> 
> ...which should also avoid the need for this additional checking?

Because then the diagnostics can't print the interesting details,
what the dynamic type of the object is.
clang++ also prints it like that and I find it really useful for users:
constexpr-static-cast1.C:17:16: error: constexpr variable 'd' must be 
initialized by a constant expression
   17 | constexpr auto d = static_cast <const B *> (&a);        // { dg-error 
"cannot cast object of dynamic type 'const A' to type 'const B'" }
      |                ^   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
constexpr-static-cast1.C:17:20: note: cannot cast object of dynamic type 'const 
A' to type 'const B'
   17 | constexpr auto d = static_cast <const B *> (&a);        // { dg-error 
"cannot cast object of dynamic type 'const A' to type 'const B'" }
      |                    ^
etc.  In the patch above for sop == NULL_TREE I have a fallback
which also doesn't print that information, but that is there just
in case, I actually haven't managed to create a testcase where
that fallback has been used.  But without the cxx_fold_indirect_ref
call in there I got a regression on cxx1y/constexpr-base1.C testcase,
so I was afraid it could in theory trigger.

Or do you mean I should always unconditionally try
cxx_fold_indirect_ref, if that succeeds, nothing to report, we're done,
if it fails, and tree_strip_nop_conversions (op) is an ADDR_EXPR,
use a loop to skip over all DECL_FIELD_IS_BASE (because those shouldn't
be then matching) and just use it to find out what the dynamic
type is?

        Jakub

Reply via email to