From 0f0fd4c7bd71f7468f720724a13374023639ef5f Mon Sep 17 00:00:00 2001 From: Daveed Vandevoorde Date: Wed, 28 Oct 2026 16:35:26 +0000 Subject: [PATCH] Constant evaluation: find overriders below non-overriding classes [GH #421] --- src/interpret.c | 90 ++++++++++++------ ...gh-421-virtual-call-indirect-overrider.txt | 16 ++++ tests/expectations/edg_x86_64/imported.log | 1 - .../tests/changes/.gh-421.rto/default.1.1.txt | 1 + tests/tests/changes/gh-421.sft.cpp | 93 +++++++++++++++++++ .../default.1.1.txt | 30 +----- .../edg_x86_32.1.1.txt | 30 +----- .../edg_x86_32_cp.1.1.txt | 30 +----- .../edg_x86_64_cp.1.1.txt | 30 +----- .../cpp/cpp2a/.pr117317-1.rto/default.1.1.txt | 11 --- 10 files changed, 174 insertions(+), 158 deletions(-) create mode 100644 src/pending_changes/gh-421-virtual-call-indirect-overrider.txt create mode 100644 tests/tests/changes/.gh-421.rto/default.1.1.txt create mode 100644 tests/tests/changes/gh-421.sft.cpp diff --git a/src/interpret.c b/src/interpret.c index 979e782a0f..bea042deb8 100644 --- a/src/interpret.c +++ b/src/interpret.c @@ -22556,49 +22556,79 @@ if *p_this_arg is not initialized.) constructor has not completely run yet, or because the object has been partially destroyed). */ } else { - /* The final overrider is a member of the class of the subobject from which - it is found, and that subobject is the object for the call. */ - a_byte *callee_subobj = subobj; + /* Find the outermost enclosing subobject from which the call is + dispatched (outer_subobj, of class outer_class). */ + a_byte *outer_subobj = subobj; + a_type_ptr outer_class = NULL; + a_type_ptr callee_class = skip_typerefs(parent_class_of(*p_callee)); + a_base_class_ptr bcp; do { - a_base_class_ptr bcp = *(a_base_class_ptr*)subobj; - a_byte_count offset, retval_offset_step = 0; - a_routine_ptr overrider = NULL; - an_overriding_virtual_function_ptr - ovfp; + a_byte_count offset; + bcp = *(a_base_class_ptr*)outer_subobj; if (bcp == NULL) { /* A most derived subobject (i.e., a field or array element). We are done. */ break; } /* if */ - check_assertion(!bcp->is_pack_expansion); - ovfp = bcp->variant.overriding_virtual_functions; - for (; ovfp != NULL; ovfp = ovfp->next) { - if (ovfp->primary_function == *p_callee) { + get_mapped_byte_count(&persistent_map, bcp, offset); + outer_subobj -= offset; + outer_class = bcp->derived_class; + if (!subobject_is_initialized(outer_subobj, complete_obj)) { + /* The next-enclosing subobject is not constructed. Do not dispatch + from anything enclosing it. */ + break; + } /* if */ + } while (outer_subobj != complete_obj); + if (outer_class != NULL) { + /* The override information for the subobject of the callee's class + in outer_class gives the final overrider in outer_class (which may + be a member of an intermediate class). The base class entry for + that subobject is identified by its offset, since the callee's + class can be a base class of outer_class more than once. */ + an_overriding_virtual_function_ptr + ovfp; + for (bcp = base_classes_of(outer_class); bcp != NULL; bcp = bcp->next) { + if (!bcp->is_pack_expansion && + same_entities(skip_typerefs(bcp->type), callee_class) && + outer_subobj + compute_interpreter_base_offset( + bcp, (a_base_class_ptr*)NULL) == + subobj) { + break; + } /* if */ + } /* for */ + check_assertion(bcp != NULL); + do { + /* If the overrider found is a member of an intermediate class, it + is normally the final overrider, but a function that overrides + it without being recorded as overriding the original function + (e.g., because of an invalid covariant return type) is found + through the base class entry for that intermediate class. */ + ovfp = bcp->variant.overriding_virtual_functions; + while (ovfp != NULL && ovfp->primary_function != *p_callee) { + ovfp = ovfp->next; + } /* while */ + if (ovfp != NULL) { a_base_class_ptr ret_base = ovfp->return_adjustment_base_class; + *p_callee = ovfp->overriding_function; + bcp = ovfp->base_class; + if (bcp == NULL) { + /* The overrider is a member of outer_class. */ + this_val->address = outer_subobj; + } else { + this_val->address = + outer_subobj + + compute_interpreter_base_offset(bcp, + (a_base_class_ptr*)NULL); + } /* if */ if (ret_base != NULL) { /* A covariant return override. The return value will need adjustment (possibly to an indirect base class). */ - retval_offset_step = compute_interpreter_base_offset( + *p_retval_offset += compute_interpreter_base_offset( ret_base, (a_base_class_ptr*)NULL); } /* if */ - overrider = ovfp->overriding_function; - break; } /* if */ - } /* for */ - get_mapped_byte_count(&persistent_map, bcp, offset); - subobj -= offset; - if (overrider != NULL) { - *p_callee = overrider; - *p_retval_offset += retval_offset_step; - callee_subobj = subobj; - } /* if */ - if (!subobject_is_initialized(subobj, complete_obj)) { - /* The next-enclosing subobject is not constructed. Do not dispatch - from it. */ - break; - } /* if */ - } while (subobj != complete_obj); - this_val->address = callee_subobj; + } while (ovfp != NULL && bcp != NULL); + } /* if */ } /* if */ return result; } /* adjust_virtual_callee */ diff --git a/src/pending_changes/gh-421-virtual-call-indirect-overrider.txt b/src/pending_changes/gh-421-virtual-call-indirect-overrider.txt new file mode 100644 index 0000000000..df9ddcc775 --- /dev/null +++ b/src/pending_changes/gh-421-virtual-call-indirect-overrider.txt @@ -0,0 +1,16 @@ +[GH #421] +Constant evaluation of virtual calls to an overrider in a derived class + +During constant evaluation, a virtual function call did not find the final +overrider if the class of which it is a member is derived from the class of +the called function through an intermediate class that does not itself +override the function: The called function was not replaced. For example, +with --c++20: + + struct A { constexpr virtual int f() const { return 1; } }; + struct NA : A {}; + struct B : NA { constexpr int f() const override { return 2; } }; + constexpr int g() { B b; const A* p = &b; return p->f(); } + static_assert(g() == 2); // Previously failed. + +This is now fixed. diff --git a/tests/expectations/edg_x86_64/imported.log b/tests/expectations/edg_x86_64/imported.log index ada16fd747..cebb90a7b6 100644 --- a/tests/expectations/edg_x86_64/imported.log +++ b/tests/expectations/edg_x86_64/imported.log @@ -7144,7 +7144,6 @@ gnu/cpp/cpp2a/paren-init30.sft.cpp:fn:-DTEST_NUMBER=1 --c++20:FAIL: gnu/cpp/cpp2a/paren-init33.sft.cpp:rp:-DTEST_NUMBER=1 --c++20:FAIL(COMPILE): gnu/cpp/cpp2a/paren-init35.sft.cpp:fp:-DTEST_NUMBER=1 --c++20:FAIL: gnu/cpp/cpp2a/paren-init4.sft.cpp:rp:-DTEST_NUMBER=1 --c++20:FAIL(COMPILE): -gnu/cpp/cpp2a/pr117317-1.sft.cpp:fp:-DTEST_NUMBER=1 --c++20:FAIL: gnu/cpp/cpp2a/pr99833.sft.cpp:fp:-DTEST_NUMBER=1 --c++20:ABORT: gnu/cpp/cpp2a/spaceship-constexpr2/test.mft.cpp:fp:-DTEST_NUMBER=1 --c++20:FAIL: gnu/cpp/cpp2a/spaceship-narrowing1.sft.cpp:fp:-DTEST_NUMBER=1 --c++20:FAIL: diff --git a/tests/tests/changes/.gh-421.rto/default.1.1.txt b/tests/tests/changes/.gh-421.rto/default.1.1.txt new file mode 100644 index 0000000000..32fc82e916 --- /dev/null +++ b/tests/tests/changes/.gh-421.rto/default.1.1.txt @@ -0,0 +1 @@ +fe_only -DTEST_NUMBER=1 --c++20 Test_name.c diff --git a/tests/tests/changes/gh-421.sft.cpp b/tests/tests/changes/gh-421.sft.cpp new file mode 100644 index 0000000000..f71d11115a --- /dev/null +++ b/tests/tests/changes/gh-421.sft.cpp @@ -0,0 +1,93 @@ +//type:fp +//options:--c++20 +//remark:Constant evaluation: overrider below a class that does not override + +struct A { + constexpr virtual int f() const { return 1; } + int a = 0; +}; +struct NA : A {}; +struct B : NA { + constexpr int f() const override { return 2; } +}; +constexpr int g1() { B b; const A* p = &b; return p->f(); } +static_assert(g1() == 2); // Previously failed. +constexpr int g1n() { B b; const NA* p = &b; return p->f(); } +static_assert(g1n() == 2); // Previously failed. + +// Two non-overriding levels. +struct NNA : NA {}; +struct C : NNA { constexpr int f() const override { return 3; } }; +constexpr int g2() { C c; const A* p = &c; return p->f(); } +static_assert(g2() == 3); // Previously failed. + +// The overrider is in a middle class, above a non-overriding one. +struct M : NA { constexpr int f() const override { return 4 + a; } }; +struct NM : M {}; +struct E { int e = 7; }; +struct T : E, NM {}; +constexpr int g3() { T t; t.a = 10; const A* p = &t; return p->f(); } +static_assert(g3() == 14); // Previously failed. + +// Repeated base class: only one path overrides. +struct L : A { constexpr int f() const override { return 5 + a; } }; +struct R : A {}; +struct D : L, R {}; +constexpr int g4() { + D d; + static_cast(d).a = 1; + static_cast(d).a = 2; + const A* pl = static_cast(&d); + const A* pr = static_cast(&d); + return pl->f() * 10 + pr->f(); +} +static_assert(g4() == 61); +struct DD : D { + constexpr int f() const override { return 9; } +}; +constexpr int g5() { + DD d; + const A* pl = static_cast(&d); + const A* pr = static_cast(&d); + return pl->f() * 10 + pr->f(); +} +static_assert(g5() == 99); // Previously failed. + +// Covariant return through a non-overriding intermediate class. +struct X { int x = 'X'; }; +struct Y : X { int y = 'Y'; }; +struct Z0 { int z = 0; }; +struct Q : Z0, Y { int q = 'Q'; }; +struct CA { constexpr virtual const X* h() const { return &xx; } X xx; }; +struct CN : CA {}; +struct CB : CN { + constexpr const Q* h() const override { return &qq; } + Q qq; +}; +constexpr bool g6() { + CB b; + const CA* p = &b; + return p->h() == static_cast(&b.qq) && p->h()->x == 'X'; +} +static_assert(g6()); // Previously failed. + +// Virtual call during construction of an intermediate class. +struct K : NA { + int r; + constexpr K() : r(static_cast(this)->f()) {} + constexpr int f() const override { return 6; } +}; +struct NK : K {}; +struct KK : NK { constexpr int f() const override { return 8; } }; +constexpr int g7() { KK k; const A* p = &k; return k.r * 10 + p->f(); } +static_assert(g7() == 68); // Previously failed. +struct W : NA { + int r; + constexpr W() : r(static_cast(this)->f()) {} +}; +struct WW : W { constexpr int f() const override { return 8; } }; +constexpr int g8() { WW w; const A* p = &w; return w.r * 10 + p->f(); } +static_assert(g8() == 18); // Previously failed. + +constexpr B gb; +static_assert(static_cast(gb).f() == 2); diff --git a/tests/tests/imported/clang/cpp/SemaCXX/.constant-expression-cxx2a.rto/default.1.1.txt b/tests/tests/imported/clang/cpp/SemaCXX/.constant-expression-cxx2a.rto/default.1.1.txt index 055c6eb1dc..388b1ea329 100644 --- a/tests/tests/imported/clang/cpp/SemaCXX/.constant-expression-cxx2a.rto/default.1.1.txt +++ b/tests/tests/imported/clang/cpp/SemaCXX/.constant-expression-cxx2a.rto/default.1.1.txt @@ -13,24 +13,10 @@ fe_only -DTEST_NUMBER=1 --clang -tused -e 200 --no_wrap_diagnostics --clang_vers "SemaCXX/constant-expression-cxx2a.cpp", line 176: error: static assertion failed static_assert(d.ba == 'B'); ^ -"SemaCXX/constant-expression-cxx2a.cpp", line 176: note: the final comparison was 65 == 66 +"SemaCXX/constant-expression-cxx2a.cpp", line 176: note: the final comparison was 68 == 66 static_assert(d.ba == 'B'); ^ -"SemaCXX/constant-expression-cxx2a.cpp", line 181: error: static assertion failed - static_assert(a.f() == 'D'); - ^ -"SemaCXX/constant-expression-cxx2a.cpp", line 181: note: the final comparison was 65 == 68 - static_assert(a.f() == 'D'); - ^ - -"SemaCXX/constant-expression-cxx2a.cpp", line 182: error: static assertion failed - static_assert(b.f() == 'D'); - ^ -"SemaCXX/constant-expression-cxx2a.cpp", line 182: note: the final comparison was 66 == 68 - static_assert(b.f() == 'D'); - ^ - "SemaCXX/constant-expression-cxx2a.cpp", line 199: error: return type is not identical to nor covariant with return type "const Virtual::A *" of overridden virtual function "Virtual::Covariant1::f" constexpr virtual const D *f() const { return &this->d; } ^ @@ -41,20 +27,6 @@ fe_only -DTEST_NUMBER=1 --clang -tused -e 200 --no_wrap_diagnostics --clang_vers ^ detected during instantiation of class "Virtual::Covariant3 [with T=Virtual::C]" at line 203 -"SemaCXX/constant-expression-cxx2a.cpp", line 209: error: static assertion failed - static_assert(cb1->f()->f() == 'D'); - ^ -"SemaCXX/constant-expression-cxx2a.cpp", line 209: note: the final comparison was 65 == 68 - static_assert(cb1->f()->f() == 'D'); - ^ - -"SemaCXX/constant-expression-cxx2a.cpp", line 212: error: static assertion failed - static_assert(cb2->f()->f() == 'D'); - ^ -"SemaCXX/constant-expression-cxx2a.cpp", line 212: note: the final comparison was 66 == 68 - static_assert(cb2->f()->f() == 'D'); - ^ - "SemaCXX/constant-expression-cxx2a.cpp", line 232: error: initialization is not constant constexpr PureVirtualCall pure_virtual_call; ^ diff --git a/tests/tests/imported/clang/cpp/SemaCXX/.constant-expression-cxx2a.rto/edg_x86_32.1.1.txt b/tests/tests/imported/clang/cpp/SemaCXX/.constant-expression-cxx2a.rto/edg_x86_32.1.1.txt index 055c6eb1dc..388b1ea329 100644 --- a/tests/tests/imported/clang/cpp/SemaCXX/.constant-expression-cxx2a.rto/edg_x86_32.1.1.txt +++ b/tests/tests/imported/clang/cpp/SemaCXX/.constant-expression-cxx2a.rto/edg_x86_32.1.1.txt @@ -13,24 +13,10 @@ fe_only -DTEST_NUMBER=1 --clang -tused -e 200 --no_wrap_diagnostics --clang_vers "SemaCXX/constant-expression-cxx2a.cpp", line 176: error: static assertion failed static_assert(d.ba == 'B'); ^ -"SemaCXX/constant-expression-cxx2a.cpp", line 176: note: the final comparison was 65 == 66 +"SemaCXX/constant-expression-cxx2a.cpp", line 176: note: the final comparison was 68 == 66 static_assert(d.ba == 'B'); ^ -"SemaCXX/constant-expression-cxx2a.cpp", line 181: error: static assertion failed - static_assert(a.f() == 'D'); - ^ -"SemaCXX/constant-expression-cxx2a.cpp", line 181: note: the final comparison was 65 == 68 - static_assert(a.f() == 'D'); - ^ - -"SemaCXX/constant-expression-cxx2a.cpp", line 182: error: static assertion failed - static_assert(b.f() == 'D'); - ^ -"SemaCXX/constant-expression-cxx2a.cpp", line 182: note: the final comparison was 66 == 68 - static_assert(b.f() == 'D'); - ^ - "SemaCXX/constant-expression-cxx2a.cpp", line 199: error: return type is not identical to nor covariant with return type "const Virtual::A *" of overridden virtual function "Virtual::Covariant1::f" constexpr virtual const D *f() const { return &this->d; } ^ @@ -41,20 +27,6 @@ fe_only -DTEST_NUMBER=1 --clang -tused -e 200 --no_wrap_diagnostics --clang_vers ^ detected during instantiation of class "Virtual::Covariant3 [with T=Virtual::C]" at line 203 -"SemaCXX/constant-expression-cxx2a.cpp", line 209: error: static assertion failed - static_assert(cb1->f()->f() == 'D'); - ^ -"SemaCXX/constant-expression-cxx2a.cpp", line 209: note: the final comparison was 65 == 68 - static_assert(cb1->f()->f() == 'D'); - ^ - -"SemaCXX/constant-expression-cxx2a.cpp", line 212: error: static assertion failed - static_assert(cb2->f()->f() == 'D'); - ^ -"SemaCXX/constant-expression-cxx2a.cpp", line 212: note: the final comparison was 66 == 68 - static_assert(cb2->f()->f() == 'D'); - ^ - "SemaCXX/constant-expression-cxx2a.cpp", line 232: error: initialization is not constant constexpr PureVirtualCall pure_virtual_call; ^ diff --git a/tests/tests/imported/clang/cpp/SemaCXX/.constant-expression-cxx2a.rto/edg_x86_32_cp.1.1.txt b/tests/tests/imported/clang/cpp/SemaCXX/.constant-expression-cxx2a.rto/edg_x86_32_cp.1.1.txt index aaa0a07814..026f13ae7a 100644 --- a/tests/tests/imported/clang/cpp/SemaCXX/.constant-expression-cxx2a.rto/edg_x86_32_cp.1.1.txt +++ b/tests/tests/imported/clang/cpp/SemaCXX/.constant-expression-cxx2a.rto/edg_x86_32_cp.1.1.txt @@ -13,24 +13,10 @@ fe_only -DTEST_NUMBER=1 --clang -tused -e 200 --no_wrap_diagnostics --clang_vers "SemaCXX/constant-expression-cxx2a.cpp", line 176: error: static assertion failed static_assert(d.ba == 'B'); ^ -"SemaCXX/constant-expression-cxx2a.cpp", line 176: note: the final comparison was 65 == 66 +"SemaCXX/constant-expression-cxx2a.cpp", line 176: note: the final comparison was 68 == 66 static_assert(d.ba == 'B'); ^ -"SemaCXX/constant-expression-cxx2a.cpp", line 181: error: static assertion failed - static_assert(a.f() == 'D'); - ^ -"SemaCXX/constant-expression-cxx2a.cpp", line 181: note: the final comparison was 65 == 68 - static_assert(a.f() == 'D'); - ^ - -"SemaCXX/constant-expression-cxx2a.cpp", line 182: error: static assertion failed - static_assert(b.f() == 'D'); - ^ -"SemaCXX/constant-expression-cxx2a.cpp", line 182: note: the final comparison was 66 == 68 - static_assert(b.f() == 'D'); - ^ - "SemaCXX/constant-expression-cxx2a.cpp", line 186: error: expression must have a constant value static_assert(d_not_constexpr.f() == 'D'); ^ @@ -48,20 +34,6 @@ fe_only -DTEST_NUMBER=1 --clang -tused -e 200 --no_wrap_diagnostics --clang_vers ^ detected during instantiation of class "Virtual::Covariant3 [with T=Virtual::C]" at line 203 -"SemaCXX/constant-expression-cxx2a.cpp", line 209: error: static assertion failed - static_assert(cb1->f()->f() == 'D'); - ^ -"SemaCXX/constant-expression-cxx2a.cpp", line 209: note: the final comparison was 65 == 68 - static_assert(cb1->f()->f() == 'D'); - ^ - -"SemaCXX/constant-expression-cxx2a.cpp", line 212: error: static assertion failed - static_assert(cb2->f()->f() == 'D'); - ^ -"SemaCXX/constant-expression-cxx2a.cpp", line 212: note: the final comparison was 66 == 68 - static_assert(cb2->f()->f() == 'D'); - ^ - "SemaCXX/constant-expression-cxx2a.cpp", line 232: error: initialization is not constant constexpr PureVirtualCall pure_virtual_call; ^ diff --git a/tests/tests/imported/clang/cpp/SemaCXX/.constant-expression-cxx2a.rto/edg_x86_64_cp.1.1.txt b/tests/tests/imported/clang/cpp/SemaCXX/.constant-expression-cxx2a.rto/edg_x86_64_cp.1.1.txt index aaa0a07814..026f13ae7a 100644 --- a/tests/tests/imported/clang/cpp/SemaCXX/.constant-expression-cxx2a.rto/edg_x86_64_cp.1.1.txt +++ b/tests/tests/imported/clang/cpp/SemaCXX/.constant-expression-cxx2a.rto/edg_x86_64_cp.1.1.txt @@ -13,24 +13,10 @@ fe_only -DTEST_NUMBER=1 --clang -tused -e 200 --no_wrap_diagnostics --clang_vers "SemaCXX/constant-expression-cxx2a.cpp", line 176: error: static assertion failed static_assert(d.ba == 'B'); ^ -"SemaCXX/constant-expression-cxx2a.cpp", line 176: note: the final comparison was 65 == 66 +"SemaCXX/constant-expression-cxx2a.cpp", line 176: note: the final comparison was 68 == 66 static_assert(d.ba == 'B'); ^ -"SemaCXX/constant-expression-cxx2a.cpp", line 181: error: static assertion failed - static_assert(a.f() == 'D'); - ^ -"SemaCXX/constant-expression-cxx2a.cpp", line 181: note: the final comparison was 65 == 68 - static_assert(a.f() == 'D'); - ^ - -"SemaCXX/constant-expression-cxx2a.cpp", line 182: error: static assertion failed - static_assert(b.f() == 'D'); - ^ -"SemaCXX/constant-expression-cxx2a.cpp", line 182: note: the final comparison was 66 == 68 - static_assert(b.f() == 'D'); - ^ - "SemaCXX/constant-expression-cxx2a.cpp", line 186: error: expression must have a constant value static_assert(d_not_constexpr.f() == 'D'); ^ @@ -48,20 +34,6 @@ fe_only -DTEST_NUMBER=1 --clang -tused -e 200 --no_wrap_diagnostics --clang_vers ^ detected during instantiation of class "Virtual::Covariant3 [with T=Virtual::C]" at line 203 -"SemaCXX/constant-expression-cxx2a.cpp", line 209: error: static assertion failed - static_assert(cb1->f()->f() == 'D'); - ^ -"SemaCXX/constant-expression-cxx2a.cpp", line 209: note: the final comparison was 65 == 68 - static_assert(cb1->f()->f() == 'D'); - ^ - -"SemaCXX/constant-expression-cxx2a.cpp", line 212: error: static assertion failed - static_assert(cb2->f()->f() == 'D'); - ^ -"SemaCXX/constant-expression-cxx2a.cpp", line 212: note: the final comparison was 66 == 68 - static_assert(cb2->f()->f() == 'D'); - ^ - "SemaCXX/constant-expression-cxx2a.cpp", line 232: error: initialization is not constant constexpr PureVirtualCall pure_virtual_call; ^ diff --git a/tests/tests/imported/gnu/cpp/cpp2a/.pr117317-1.rto/default.1.1.txt b/tests/tests/imported/gnu/cpp/cpp2a/.pr117317-1.rto/default.1.1.txt index 466f5e9751..2ed4eaf390 100644 --- a/tests/tests/imported/gnu/cpp/cpp2a/.pr117317-1.rto/default.1.1.txt +++ b/tests/tests/imported/gnu/cpp/cpp2a/.pr117317-1.rto/default.1.1.txt @@ -1,12 +1 @@ fe_only -DTEST_NUMBER=1 --gnu_version=999999 -tused -e 200 --no_wrap --no_wrap_diagnostics --c++20 Test_name.c -"Test_name.c", line 21: error: expression must have a constant value - static_assert (e == f, ""); - ^ -"Test_name.c", line 7: note: constexpr function "C::foo" (declared at line 8) is not defined - constexpr bool operator== (const C &b) const { return foo (); } - ^ -"Test_name.c", line 21: note: called from: - static_assert (e == f, ""); - ^ - -1 error detected in the compilation of "Test_name.c".