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