https://gcc.gnu.org/g:b79deb90c2295eab4ca5aec7571cdba6afeedf29
commit r17-4186-gb79deb90c2295eab4ca5aec7571cdba6afeedf29 Author: Jakub Jelinek <[email protected]> Date: Sat Sep 12 09:50:12 2026 +0200 c++: Reject UB in static_cast downcasts during constant evaluation 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. One problem is that the fold-const.cc (fold_unary_loc) /* Convert (T1)(X p+ Y) into ((T1)X p+ Y), for pointer type, when the new cast (T1)X will fold away. We assume that this happens when X itself is a cast. */ and match.pd /* Associate (p +p off1) +p off2 as (p +p (off1 + off2)). */ #if GENERIC (simplify (pointer_plus (convert:s (pointer_plus:s @0 @1)) @3) (pointer_plus (convert:type @0) (plus @1 @3))) #endif optimizations break this because they can move the cast from its original location to the argument of POINTER_PLUS_EXPR where it doesn't point to an object of the downcasted type. For now this patch changes constant evaluation of POINTER_PLUS_EXPR with a cast in its first operand to evaluate it as if those two were swapped. 2026-09-12 Jakub Jelinek <[email protected]> * constexpr.cc (cxx_eval_binary_expression): For POINTER_PLUS_EXPR with pointer cast in orig_lhs operand evaluate it as if it was cast of POINTER_PLUS_EXPR with cast operand. (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. * g++.dg/cpp1y/constexpr-static-cast4.C: New test. Reviewed-by: Jason Merrill <[email protected]> Diff: --- gcc/cp/constexpr.cc | 79 +++++++++++++++++++ .../g++.dg/cpp0x/constexpr-static-cast1.C | 76 ++++++++++++++++++ .../g++.dg/cpp0x/constexpr-static-cast2.C | 72 +++++++++++++++++ .../g++.dg/cpp1y/constexpr-static-cast1.C | 91 ++++++++++++++++++++++ .../g++.dg/cpp1y/constexpr-static-cast2.C | 87 +++++++++++++++++++++ .../g++.dg/cpp1y/constexpr-static-cast3.C | 31 ++++++++ .../g++.dg/cpp1y/constexpr-static-cast4.C | 37 +++++++++ 7 files changed, 473 insertions(+) diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc index a54fed1fdb95..b934df86a8a0 100644 --- a/gcc/cp/constexpr.cc +++ b/gcc/cp/constexpr.cc @@ -5671,6 +5671,38 @@ cxx_eval_binary_expression (const constexpr_ctx *ctx, tree t, tree orig_lhs = TREE_OPERAND (t, 0); tree orig_rhs = TREE_OPERAND (t, 1); tree lhs, rhs; + + if (TREE_CODE (t) == POINTER_PLUS_EXPR + && CONVERT_EXPR_P (orig_lhs) + && (lhs = TREE_OPERAND (orig_lhs, 0)) + && INDIRECT_TYPE_P (TREE_TYPE (lhs)) + && (TREE_CODE (orig_lhs) != NOP_EXPR || !REINTERPRET_CAST_P (orig_lhs)) + && is_properly_derived_from (TREE_TYPE (TREE_TYPE (orig_lhs)), + TREE_TYPE (TREE_TYPE (lhs)))) + /* fold_unary_loc and match.pd can optimize + (type *) (ptr p+ off) into ((type *) ptr) p+ off, which can break + static_cast constexpr diagnostics, because ptr before the p+ + cast to type * can be invalid while the original valid. + Evaluate it as (type *) (ptr p+ off) instead. */ + { + tree cast = copy_node (orig_lhs); + tree pplus = copy_node (t); + TREE_OPERAND (cast, 0) = pplus; + TREE_OPERAND (pplus, 0) = lhs; + TREE_TYPE (pplus) = TREE_TYPE (lhs); + lhs = cxx_eval_constant_expression (ctx, cast, vc_prvalue, + non_constant_p, overflow_p, + jump_target); + if (lhs != cast) + { + ggc_free (cast); + ggc_free (pplus); + } + if (*non_constant_p) + return t; + return lhs; + } + lhs = cxx_eval_constant_expression (ctx, orig_lhs, vc_prvalue, non_constant_p, overflow_p, jump_target); @@ -10768,6 +10800,53 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t, } } + /* [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 (cxx_fold_indirect_ref (ctx, loc, TREE_TYPE (type), sop, + NULL, jump_target) == NULL_TREE) + { + tree dyntype = NULL_TREE; + if (!ctx->quiet && TREE_CODE (sop) == ADDR_EXPR) + { + sop = TREE_OPERAND (sop, 0); + while (TREE_CODE (sop) == COMPONENT_REF + && DECL_FIELD_IS_BASE (TREE_OPERAND (sop, 1))) + sop = TREE_OPERAND (sop, 0); + dyntype = strip_array_types (TREE_TYPE (sop)); + if (same_type_p (dyntype, TREE_TYPE (TREE_TYPE (op)))) + dyntype = NULL_TREE; + } + if (!ctx->quiet) + { + if (dyntype) + error_at (loc, "%qT operand (of dynamic type %qT) is " + "not a base class subobject of a %qT object", + TREE_TYPE (TREE_TYPE (op)), dyntype, + TREE_TYPE (type)); + else + error_at (loc, "%qT operand is not a base class " + "subobject of a %qT object", + TREE_TYPE (TREE_TYPE (op)), TREE_TYPE (type)); + } + *non_constant_p = true; + return t; + } + } + if (TREE_CODE (op) == PTRMEM_CST && !TYPE_PTRMEM_P (type)) { op = cplus_expand_constant (op); diff --git a/gcc/testsuite/g++.dg/cpp0x/constexpr-static-cast1.C b/gcc/testsuite/g++.dg/cpp0x/constexpr-static-cast1.C new file mode 100644 index 000000000000..e3b4227e2da6 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-static-cast1.C @@ -0,0 +1,76 @@ +// { 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]; }; +struct L : G {}; +struct M : G {}; +struct N : L, M {}; +constexpr A a = {}; +constexpr B b = {}; +constexpr C c = {}; +constexpr auto d = static_cast <const B *> (&a); // { dg-error "'const A' operand is not a base class subobject of a 'const B' object" } +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 "'const A' operand \\\(of dynamic type 'const C'\\\) is not a base class subobject of a 'const B' object" } +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 "'const H' operand is not a base class subobject of a 'const J' object" } +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); +constexpr N bh = {}; +constexpr const G *bi = static_cast <const M *> (&bh); +constexpr auto bj = static_cast <const L *> (bi); // { dg-error "'const G' operand is not a base class subobject of a 'const L' object" } diff --git a/gcc/testsuite/g++.dg/cpp0x/constexpr-static-cast2.C b/gcc/testsuite/g++.dg/cpp0x/constexpr-static-cast2.C new file mode 100644 index 000000000000..e55be74d0d87 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-static-cast2.C @@ -0,0 +1,72 @@ +// { 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]; }; +struct L : G {}; +struct M : G {}; +struct N : L, M {}; +constexpr A a = {}; +constexpr B b = {}; +constexpr C c = {}; +constexpr auto &d = static_cast <const B &> (a); // { dg-error "'const A' operand is not a base class subobject of a 'const B' object" } +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 "'const A' operand \\\(of dynamic type 'const C'\\\) is not a base class subobject of a 'const B' object" } +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 "'const H' operand is not a base class subobject of a 'const J' object" } +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); +constexpr N bh = {}; +constexpr const G &bi = static_cast <const M &> (bh); +constexpr auto &bj = static_cast <const L &> (bi); // { dg-error "'const G' operand is not a base class subobject of a 'const L' object" } diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-static-cast1.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-static-cast1.C new file mode 100644 index 000000000000..2a7531029863 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-static-cast1.C @@ -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 "'A' operand is not a base class subobject of a 'B' object" } + } + 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 "'A' operand \\\(of dynamic type 'C'\\\) is not a base class subobject of a 'B' object" } + } + 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 "operand \\\(of dynamic type 'B'\\\) is not a base class subobject of a 'C' object" "" { 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 "operand \\\(of dynamic type 'B'\\\) is not a base class subobject of a 'C' object" "" { 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, ""); diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-static-cast2.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-static-cast2.C new file mode 100644 index 000000000000..2f37661a6079 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-static-cast2.C @@ -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 "'A' operand is not a base class subobject of a 'B' object" } + } + 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 "'A' operand \\\(of dynamic type 'C'\\\) is not a base class subobject of a 'B' object" } + } + 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 "'A' operand \\\(of dynamic type 'B'\\\) is not a base class subobject of a 'C' object" "" { 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 "'A' operand \\\(of dynamic type 'B'\\\) is not a base class subobject of a 'C' object" "" { 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, ""); diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-static-cast3.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-static-cast3.C new file mode 100644 index 000000000000..baba35658ba2 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-static-cast3.C @@ -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 "'B<D>' operand \\\(of dynamic type 'C<D>'\\\) is not a base class subobject of a 'D' object" } + } +}; + +template <typename T> +struct C : A <C <T>>, B <T> { }; +struct D : B <D> { }; + +constexpr auto c = C <D> ().foo (100).bar (10); diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-static-cast4.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-static-cast4.C new file mode 100644 index 000000000000..7012f4c6be71 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-static-cast4.C @@ -0,0 +1,37 @@ +// { dg-do compile { target c++14 } } + +struct A {}; +struct B : A {}; +struct C { int c; }; +struct D { int d; }; +struct E : C, D { int e; }; + +constexpr bool +foo (bool x) +{ + struct A a = {}; + A *b = &a; + A *c = nullptr; + if (x) + { + c = static_cast <A *> (static_cast <B *> (static_cast <A *> (static_cast <B *> (b)))); // { dg-error "'A' operand is not a base class subobject of a 'B' object" } + } + return true; +} + +constexpr bool a = foo (true); + +constexpr bool +bar (bool x) +{ + struct D a = { 42 }; + D *b = &a; + D *c = nullptr; + if (x) + { + c = static_cast <D *> (static_cast <E *> (static_cast <D *> (static_cast <E *> (b)))); // { dg-error "'D' operand is not a base class subobject of a 'E' object" } + } + return true; +} + +constexpr bool b = bar (true);
