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);
+           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))
+             {
+               if (!ctx->quiet)
+                 error_at (loc, "cannot cast object of dynamic type "
+                                "%qT to type %qT",
+                           soptype, TREE_TYPE (type));
+               *non_constant_p = true;
+               return t;
+             }
+         }
+
        if (TREE_CODE (op) == PTRMEM_CST && !TYPE_PTRMEM_P (type))
          {
            op = cplus_expand_constant (op);
--- gcc/testsuite/g++.dg/cpp0x/constexpr-static-cast1.C.jj      2026-07-24 
11:52:44.004697174 +0200
+++ gcc/testsuite/g++.dg/cpp0x/constexpr-static-cast1.C 2026-07-24 
13:16:36.486018476 +0200
@@ -0,0 +1,70 @@
+// { dg-do compile { target c++11 } }
+
+struct A { int a; };
+struct B : A { int b; };
+struct C : A { int c; };
+struct D { int d; };
+struct E { int e; };
+struct F : A, D, E { int f; };
+struct G {};
+struct H {};
+struct I {};
+struct J : G, H, I {};
+struct K { F k; J l[2]; };
+constexpr A a = {};
+constexpr B b = {};
+constexpr C c = {};
+constexpr auto d = static_cast <const B *> (&a);       // { dg-error "cannot 
cast object of dynamic type 'const A' to type 'const B'" }
+constexpr auto e = static_cast <const A *> (&a);
+constexpr auto f = static_cast <const A *> (&b);
+constexpr auto g = static_cast <const B *> (&b);
+constexpr auto h = static_cast <const B *> (f);
+constexpr auto i = static_cast <const A *> (nullptr);
+constexpr auto j = static_cast <const B *> (nullptr);
+constexpr auto k = static_cast <const B *> (i);
+constexpr auto l = static_cast <const A *> (j);
+constexpr auto m = static_cast <const A *> (&c);
+constexpr auto n = static_cast <const B *> (m);                // { dg-error 
"cannot cast object of dynamic type 'const C' to type 'const B'" }
+constexpr auto o = static_cast <const C *> (m);
+constexpr F p = {};
+constexpr auto q = static_cast <const A *> (&p);
+constexpr auto r = static_cast <const F *> (q);
+constexpr auto s = static_cast <const D *> (r);
+constexpr auto t = static_cast <const F *> (s);
+constexpr auto u = static_cast <const E *> (t);
+constexpr auto v = static_cast <const F *> (u);
+constexpr J y = {};
+constexpr auto z = static_cast <const G *> (&y);
+constexpr auto aa = static_cast <const J *> (z);
+constexpr auto ab = static_cast <const H *> (aa);
+constexpr auto ac = static_cast <const J *> (ab);
+constexpr auto ad = static_cast <const I *> (ac);
+constexpr auto ae = static_cast <const J *> (ad);
+constexpr H af = {};
+constexpr auto ag = static_cast <const J *> (&af);     // { dg-error "cannot 
cast object of dynamic type 'const H' to type 'const J'" }
+constexpr F ah[1] = {};
+constexpr auto ai = static_cast <const A *> (&ah[0]);
+constexpr auto aj = static_cast <const F *> (ai);
+constexpr auto ak = static_cast <const D *> (aj);
+constexpr auto al = static_cast <const F *> (ak);
+constexpr auto am = static_cast <const E *> (al);
+constexpr auto an = static_cast <const F *> (am);
+constexpr K ao = {};
+constexpr auto ap = static_cast <const A *> (&ao.k);
+constexpr auto aq = static_cast <const F *> (ap);
+constexpr auto ar = static_cast <const D *> (aq);
+constexpr auto as = static_cast <const F *> (ar);
+constexpr auto at = static_cast <const E *> (as);
+constexpr auto au = static_cast <const F *> (at);
+constexpr auto av = static_cast <const G *> (&ao.l[0]);
+constexpr auto aw = static_cast <const J *> (av);
+constexpr auto ax = static_cast <const H *> (aw);
+constexpr auto ay = static_cast <const J *> (ax);
+constexpr auto az = static_cast <const I *> (ay);
+constexpr auto ba = static_cast <const J *> (az);
+constexpr auto bb = static_cast <const G *> (&ao.l[1]);
+constexpr auto bc = static_cast <const J *> (bb);
+constexpr auto bd = static_cast <const H *> (bc);
+constexpr auto be = static_cast <const J *> (bd);
+constexpr auto bf = static_cast <const I *> (be);
+constexpr auto bg = static_cast <const J *> (bf);
--- gcc/testsuite/g++.dg/cpp0x/constexpr-static-cast2.C.jj      2026-07-24 
14:16:22.942498940 +0200
+++ gcc/testsuite/g++.dg/cpp0x/constexpr-static-cast2.C 2026-07-24 
14:21:30.260409277 +0200
@@ -0,0 +1,66 @@
+// { dg-do compile { target c++11 } }
+
+struct A { int a; };
+struct B : A { int b; };
+struct C : A { int c; };
+struct D { int d; };
+struct E { int e; };
+struct F : A, D, E { int f; };
+struct G {};
+struct H {};
+struct I {};
+struct J : G, H, I {};
+struct K { F k; J l[2]; };
+constexpr A a = {};
+constexpr B b = {};
+constexpr C c = {};
+constexpr auto &d = static_cast <const B &> (a);       // { dg-error "cannot 
cast object of dynamic type 'const A' to type 'const B'" }
+constexpr auto &e = static_cast <const A &> (a);
+constexpr auto &f = static_cast <const A &> (b);
+constexpr auto &g = static_cast <const B &> (b);
+constexpr auto &h = static_cast <const B &> (f);
+constexpr auto &m = static_cast <const A &> (c);
+constexpr auto &n = static_cast <const B &> (m);       // { dg-error "cannot 
cast object of dynamic type 'const C' to type 'const B'" }
+constexpr auto &o = static_cast <const C &> (m);
+constexpr F p = {};
+constexpr auto &q = static_cast <const A &> (p);
+constexpr auto &r = static_cast <const F &> (q);
+constexpr auto &s = static_cast <const D &> (r);
+constexpr auto &t = static_cast <const F &> (s);
+constexpr auto &u = static_cast <const E &> (t);
+constexpr auto &v = static_cast <const F &> (u);
+constexpr J y = {};
+constexpr auto &z = static_cast <const G &> (y);
+constexpr auto &aa = static_cast <const J &> (z);
+constexpr auto &ab = static_cast <const H &> (aa);
+constexpr auto &ac = static_cast <const J &> (ab);
+constexpr auto &ad = static_cast <const I &> (ac);
+constexpr auto &ae = static_cast <const J &> (ad);
+constexpr H af = {};
+constexpr auto &ag = static_cast <const J &> (af);     // { dg-error "cannot 
cast object of dynamic type 'const H' to type 'const J'" }
+constexpr F ah[1] = {};
+constexpr auto &ai = static_cast <const A &> (ah[0]);
+constexpr auto &aj = static_cast <const F &> (ai);
+constexpr auto &ak = static_cast <const D &> (aj);
+constexpr auto &al = static_cast <const F &> (ak);
+constexpr auto &am = static_cast <const E &> (al);
+constexpr auto &an = static_cast <const F &> (am);
+constexpr K ao = {};
+constexpr auto &ap = static_cast <const A &> (ao.k);
+constexpr auto &aq = static_cast <const F &> (ap);
+constexpr auto &ar = static_cast <const D &> (aq);
+constexpr auto &as = static_cast <const F &> (ar);
+constexpr auto &at = static_cast <const E &> (as);
+constexpr auto &au = static_cast <const F &> (at);
+constexpr auto &av = static_cast <const G &> (ao.l[0]);
+constexpr auto &aw = static_cast <const J &> (av);
+constexpr auto &ax = static_cast <const H &> (aw);
+constexpr auto &ay = static_cast <const J &> (ax);
+constexpr auto &az = static_cast <const I &> (ay);
+constexpr auto &ba = static_cast <const J &> (az);
+constexpr auto &bb = static_cast <const G &> (ao.l[1]);
+constexpr auto &bc = static_cast <const J &> (bb);
+constexpr auto &bd = static_cast <const H &> (bc);
+constexpr auto &be = static_cast <const J &> (bd);
+constexpr auto &bf = static_cast <const I &> (be);
+constexpr auto &bg = static_cast <const J &> (bf);
--- gcc/testsuite/g++.dg/cpp1y/constexpr-static-cast1.C.jj      2026-07-24 
11:52:43.971410063 +0200
+++ gcc/testsuite/g++.dg/cpp1y/constexpr-static-cast1.C 2026-07-24 
14:38:41.086696656 +0200
@@ -0,0 +1,91 @@
+// { dg-do compile { target c++14 } }
+
+struct A { int a; };
+struct B : A { int b; };
+struct C : A { int c; };
+struct D { int d = 42; };
+struct E { int e = 0; };
+struct F : E, D { };
+struct G : F { };
+struct H { G b; };
+
+constexpr bool
+foo (bool x)
+{
+  A a = {};
+  if (x)
+    {
+      auto c = static_cast <B *> (&a); // { dg-error "cannot cast object of 
dynamic type 'A' to type 'B'" }
+    }
+  return true;
+}
+
+constexpr bool
+bar ()
+{
+  A a = {};
+  B b = {};
+  auto c = static_cast <A *> (&a);
+  auto d = static_cast <A *> (&b);
+  auto e = static_cast <B *> (&b);
+  auto f = static_cast <B *> (d);
+  auto g = static_cast <A *> (nullptr);
+  auto h = static_cast <B *> (nullptr);
+  auto i = static_cast <B *> (g);
+  auto j = static_cast <A *> (h);
+  return true;
+}
+
+constexpr bool
+baz (bool x)
+{
+  C a = {};
+  auto b = static_cast <A *> (&a);
+  if (x)
+    {
+      auto c = static_cast <B *> (b);  // { dg-error "cannot cast object of 
dynamic type 'C' to type 'B'" }
+    }
+  return true;
+}
+
+#if __cpp_constexpr_dynamic_alloc >= 201907
+constexpr bool
+qux ()
+{
+  A *a = new B {};
+  auto b = static_cast <B *> (a);
+  auto c = static_cast <C *> (a);      // { dg-error "cannot cast object of 
dynamic type 'B' to type 'C'" "" { target c++20 } }
+  delete a;
+  return true;
+}
+
+constexpr bool
+corge ()
+{
+  A *a = new B[2] {};
+  auto b = static_cast <B *> (a);
+  auto c = static_cast <C *> (a);      // { dg-error "cannot cast object of 
dynamic type 'B' to type 'C'" "" { target c++20 } }
+  delete a;
+  return true;
+}
+#endif
+
+constexpr int
+fred ()
+{
+  H h;
+  auto a = static_cast <D *> (&h.b);
+  auto b = static_cast <G *> (a);
+  return b->d;
+}
+
+static_assert (foo (false), "");
+constexpr bool a = foo (true);
+static_assert (bar (), "");
+static_assert (baz (false), "");
+constexpr bool b = baz (true);
+#if __cpp_constexpr_dynamic_alloc >= 201907
+constexpr bool c = qux ();
+constexpr bool d = corge ();
+#endif
+static_assert(fred () == 42, "");
--- gcc/testsuite/g++.dg/cpp1y/constexpr-static-cast2.C.jj      2026-07-24 
14:54:38.963002065 +0200
+++ gcc/testsuite/g++.dg/cpp1y/constexpr-static-cast2.C 2026-07-24 
14:56:51.178246624 +0200
@@ -0,0 +1,87 @@
+// { dg-do compile { target c++14 } }
+
+struct A { int a; };
+struct B : A { int b; };
+struct C : A { int c; };
+struct D { int d = 42; };
+struct E { int e = 0; };
+struct F : E, D { };
+struct G : F { };
+struct H { G b; };
+
+constexpr bool
+foo (bool x)
+{
+  A a = {};
+  if (x)
+    {
+      auto &c = static_cast <B &> (a); // { dg-error "cannot cast object of 
dynamic type 'A' to type 'B'" }
+    }
+  return true;
+}
+
+constexpr bool
+bar ()
+{
+  A a = {};
+  B b = {};
+  auto &c = static_cast <A &> (a);
+  auto &d = static_cast <A &> (b);
+  auto &e = static_cast <B &> (b);
+  auto &f = static_cast <B &> (d);
+  return true;
+}
+
+constexpr bool
+baz (bool x)
+{
+  C a = {};
+  auto &b = static_cast <A &> (a);
+  if (x)
+    {
+      auto &c = static_cast <B &> (b); // { dg-error "cannot cast object of 
dynamic type 'C' to type 'B'" }
+    }
+  return true;
+}
+
+#if __cpp_constexpr_dynamic_alloc >= 201907
+constexpr bool
+qux ()
+{
+  A *a = new B {};
+  auto &b = static_cast <B &> (*a);
+  auto &c = static_cast <C &> (*a);    // { dg-error "cannot cast object of 
dynamic type 'B' to type 'C'" "" { target c++20 } }
+  delete a;
+  return true;
+}
+
+constexpr bool
+corge ()
+{
+  A *a = new B[2] {};
+  auto &b = static_cast <B &> (*a);
+  auto &c = static_cast <C &> (*a);    // { dg-error "cannot cast object of 
dynamic type 'B' to type 'C'" "" { target c++20 } }
+  delete a;
+  return true;
+}
+#endif
+
+constexpr int
+fred ()
+{
+  H h;
+  auto &a = static_cast <D &> (h.b);
+  auto &b = static_cast <G &> (a);
+  return b.d;
+}
+
+static_assert (foo (false), "");
+constexpr bool a = foo (true);
+static_assert (bar (), "");
+static_assert (baz (false), "");
+constexpr bool b = baz (true);
+#if __cpp_constexpr_dynamic_alloc >= 201907
+constexpr bool c = qux ();
+constexpr bool d = corge ();
+#endif
+static_assert(fred () == 42, "");
--- gcc/testsuite/g++.dg/cpp1y/constexpr-static-cast3.C.jj      2026-07-24 
15:30:54.027098649 +0200
+++ gcc/testsuite/g++.dg/cpp1y/constexpr-static-cast3.C 2026-07-24 
15:36:13.607849333 +0200
@@ -0,0 +1,31 @@
+// { dg-do compile { target c++14 } }
+
+template <typename T>
+struct A
+{
+  unsigned char a = 0;
+  constexpr T &
+  foo (const unsigned char &x)
+  {
+    a = x;
+    return *static_cast <T *> (this);
+  }
+};
+
+template <typename T>
+struct B
+{
+  unsigned char b = 0;
+  constexpr T &
+  bar (const unsigned char &x)
+  {
+    b = x;
+    return *static_cast <T *> (this);  // { dg-error "cannot cast object of 
dynamic type 'C<D>' to type 'D'" }
+  }
+};
+
+template <typename T>
+struct C : A <C <T>>, B <T> { };
+struct D : B <D> { };
+
+constexpr auto c = C <D> ().foo (100).bar (10);

        Jakub

Reply via email to