On 7/24/26 11:36 AM, Jakub Jelinek wrote:
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,
Yes.
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?
Yes, though I note that clang's diagnostic doesn't properly describe the
case where there's an base of the desired type somewhere in the complete
object but not derived from the subobject we started from, e.g.
struct A{};
struct B:A{}; struct C:A{};
struct D:B,C{} d;
constexpr A* ap = static_cast<C*>(&d);
constexpr auto dp = static_cast<B*>(ap);
Jason