Skip to content

Commit 413c2f3

Browse files
Constant evaluation: find overriders below non-overriding classes [GH #421]
A virtual call is now dispatched by finding the outermost enclosing subobject from which it can be dispatched, and looking up the final overrider in the override information of that class's base class entry for the subobject of the callee's class (identified by its offset). Previously, only the entries for the direct base classes along the way were consulted, so an overrider declared in a class derived from the callee's class through a class that does not itself override the function was not found.
1 parent e4903e7 commit 413c2f3

10 files changed

Lines changed: 175 additions & 158 deletions

File tree

‎src/interpret.c‎

Lines changed: 60 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -22554,49 +22554,79 @@ if *p_this_arg is not initialized.)
2255422554
constructor has not completely run yet, or because the object has
2255522555
been partially destroyed). */
2255622556
} else {
22557-
/* The final overrider is a member of the class of the subobject from which
22558-
it is found, and that subobject is the object for the call. */
22559-
a_byte *callee_subobj = subobj;
22557+
/* Find the outermost enclosing subobject from which the call is
22558+
dispatched (outer_subobj, of class outer_class). */
22559+
a_byte *outer_subobj = subobj;
22560+
a_type_ptr outer_class = NULL;
22561+
a_type_ptr callee_class = skip_typerefs(parent_class_of(*p_callee));
22562+
a_base_class_ptr bcp;
2256022563
do {
22561-
a_base_class_ptr bcp = *(a_base_class_ptr*)subobj;
22562-
a_byte_count offset, retval_offset_step = 0;
22563-
a_routine_ptr overrider = NULL;
22564-
an_overriding_virtual_function_ptr
22565-
ovfp;
22564+
a_byte_count offset;
22565+
bcp = *(a_base_class_ptr*)outer_subobj;
2256622566
if (bcp == NULL) {
2256722567
/* A most derived subobject (i.e., a field or array element). We are
2256822568
done. */
2256922569
break;
2257022570
} /* if */
22571-
check_assertion(!bcp->is_pack_expansion);
22572-
ovfp = bcp->variant.overriding_virtual_functions;
22573-
for (; ovfp != NULL; ovfp = ovfp->next) {
22574-
if (ovfp->primary_function == *p_callee) {
22571+
get_mapped_byte_count(&persistent_map, bcp, offset);
22572+
outer_subobj -= offset;
22573+
outer_class = bcp->derived_class;
22574+
if (!subobject_is_initialized(outer_subobj, complete_obj)) {
22575+
/* The next-enclosing subobject is not constructed. Do not dispatch
22576+
from anything enclosing it. */
22577+
break;
22578+
} /* if */
22579+
} while (outer_subobj != complete_obj);
22580+
if (outer_class != NULL) {
22581+
/* The override information for the subobject of the callee's class
22582+
in outer_class gives the final overrider in outer_class (which may
22583+
be a member of an intermediate class). The base class entry for
22584+
that subobject is identified by its offset, since the callee's
22585+
class can be a base class of outer_class more than once. */
22586+
an_overriding_virtual_function_ptr
22587+
ovfp;
22588+
for (bcp = base_classes_of(outer_class); bcp != NULL; bcp = bcp->next) {
22589+
if (!bcp->is_pack_expansion &&
22590+
same_entities(skip_typerefs(bcp->type), callee_class) &&
22591+
outer_subobj + compute_interpreter_base_offset(
22592+
bcp, (a_base_class_ptr*)NULL) ==
22593+
subobj) {
22594+
break;
22595+
} /* if */
22596+
} /* for */
22597+
check_assertion(bcp != NULL);
22598+
do {
22599+
/* If the overrider found is a member of an intermediate class, it
22600+
is normally the final overrider, but a function that overrides
22601+
it without being recorded as overriding the original function
22602+
(e.g., because of an invalid covariant return type) is found
22603+
through the base class entry for that intermediate class. */
22604+
ovfp = bcp->variant.overriding_virtual_functions;
22605+
while (ovfp != NULL && ovfp->primary_function != *p_callee) {
22606+
ovfp = ovfp->next;
22607+
} /* while */
22608+
if (ovfp != NULL) {
2257522609
a_base_class_ptr ret_base = ovfp->return_adjustment_base_class;
22610+
*p_callee = ovfp->overriding_function;
22611+
bcp = ovfp->base_class;
22612+
if (bcp == NULL) {
22613+
/* The overrider is a member of outer_class. */
22614+
this_val->address = outer_subobj;
22615+
} else {
22616+
this_val->address =
22617+
outer_subobj +
22618+
compute_interpreter_base_offset(bcp,
22619+
(a_base_class_ptr*)NULL);
22620+
} /* if */
2257622621
if (ret_base != NULL) {
2257722622
/* A covariant return override. The return value will need
2257822623
adjustment (possibly to an indirect base class). */
22579-
retval_offset_step = compute_interpreter_base_offset(
22624+
*p_retval_offset += compute_interpreter_base_offset(
2258022625
ret_base, (a_base_class_ptr*)NULL);
2258122626
} /* if */
22582-
overrider = ovfp->overriding_function;
22583-
break;
2258422627
} /* if */
22585-
} /* for */
22586-
get_mapped_byte_count(&persistent_map, bcp, offset);
22587-
subobj -= offset;
22588-
if (overrider != NULL) {
22589-
*p_callee = overrider;
22590-
*p_retval_offset += retval_offset_step;
22591-
callee_subobj = subobj;
22592-
} /* if */
22593-
if (!subobject_is_initialized(subobj, complete_obj)) {
22594-
/* The next-enclosing subobject is not constructed. Do not dispatch
22595-
from it. */
22596-
break;
22597-
} /* if */
22598-
} while (subobj != complete_obj);
22599-
this_val->address = callee_subobj;
22628+
} while (ovfp != NULL && bcp != NULL);
22629+
} /* if */
2260022630
} /* if */
2260122631
return result;
2260222632
} /* adjust_virtual_callee */
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[GH #421]
2+
Constant evaluation of virtual calls to an overrider in a derived class
3+
4+
During constant evaluation, a virtual function call did not find the final
5+
overrider if the class of which it is a member is derived from the class of
6+
the called function through an intermediate class that does not itself
7+
override the function: The called function was not replaced. For example,
8+
with --c++20:
9+
10+
struct A { constexpr virtual int f() const { return 1; } };
11+
struct NA : A {};
12+
struct B : NA { constexpr int f() const override { return 2; } };
13+
constexpr int g() { B b; const A* p = &b; return p->f(); }
14+
static_assert(g() == 2); // Previously failed.
15+
16+
This is now fixed.

‎tests/expectations/edg_x86_64/imported.log‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7147,7 +7147,6 @@ gnu/cpp/cpp2a/paren-init30.sft.cpp:fn:-DTEST_NUMBER=1 --c++20:FAIL:
71477147
gnu/cpp/cpp2a/paren-init33.sft.cpp:rp:-DTEST_NUMBER=1 --c++20:FAIL(COMPILE):
71487148
gnu/cpp/cpp2a/paren-init35.sft.cpp:fp:-DTEST_NUMBER=1 --c++20:FAIL:
71497149
gnu/cpp/cpp2a/paren-init4.sft.cpp:rp:-DTEST_NUMBER=1 --c++20:FAIL(COMPILE):
7150-
gnu/cpp/cpp2a/pr117317-1.sft.cpp:fp:-DTEST_NUMBER=1 --c++20:FAIL:
71517150
gnu/cpp/cpp2a/pr99833.sft.cpp:fp:-DTEST_NUMBER=1 --c++20:ABORT:
71527151
gnu/cpp/cpp2a/spaceship-constexpr2/test.mft.cpp:fp:-DTEST_NUMBER=1 --c++20:FAIL:
71537152
gnu/cpp/cpp2a/spaceship-narrowing1.sft.cpp:fp:-DTEST_NUMBER=1 --c++20:FAIL:
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
fe_only -DTEST_NUMBER=1 --c++20 Test_name.c

‎tests/tests/changes/gh-421.sft.cpp‎

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
//type:fp
2+
//options:--c++20
3+
//remark:[GH #421] Constant evaluation of virtual calls whose final overrider
4+
//remark:is declared below a class that does not override the function
5+
6+
struct A {
7+
constexpr virtual int f() const { return 1; }
8+
int a = 0;
9+
};
10+
struct NA : A {};
11+
struct B : NA {
12+
constexpr int f() const override { return 2; }
13+
};
14+
constexpr int g1() { B b; const A* p = &b; return p->f(); }
15+
static_assert(g1() == 2); // Previously failed.
16+
constexpr int g1n() { B b; const NA* p = &b; return p->f(); }
17+
static_assert(g1n() == 2); // Previously failed.
18+
19+
// Two non-overriding levels.
20+
struct NNA : NA {};
21+
struct C : NNA { constexpr int f() const override { return 3; } };
22+
constexpr int g2() { C c; const A* p = &c; return p->f(); }
23+
static_assert(g2() == 3); // Previously failed.
24+
25+
// The overrider is in a middle class, above a non-overriding one.
26+
struct M : NA { constexpr int f() const override { return 4 + a; } };
27+
struct NM : M {};
28+
struct E { int e = 7; };
29+
struct T : E, NM {};
30+
constexpr int g3() { T t; t.a = 10; const A* p = &t; return p->f(); }
31+
static_assert(g3() == 14); // Previously failed.
32+
33+
// Repeated base class: only one path overrides.
34+
struct L : A { constexpr int f() const override { return 5 + a; } };
35+
struct R : A {};
36+
struct D : L, R {};
37+
constexpr int g4() {
38+
D d;
39+
static_cast<L&>(d).a = 1;
40+
static_cast<R&>(d).a = 2;
41+
const A* pl = static_cast<const L*>(&d);
42+
const A* pr = static_cast<const R*>(&d);
43+
return pl->f() * 10 + pr->f();
44+
}
45+
static_assert(g4() == 61);
46+
struct DD : D {
47+
constexpr int f() const override { return 9; }
48+
};
49+
constexpr int g5() {
50+
DD d;
51+
const A* pl = static_cast<const L*>(&d);
52+
const A* pr = static_cast<const R*>(&d);
53+
return pl->f() * 10 + pr->f();
54+
}
55+
static_assert(g5() == 99); // Previously failed.
56+
57+
// Covariant return through a non-overriding intermediate class.
58+
struct X { int x = 'X'; };
59+
struct Y : X { int y = 'Y'; };
60+
struct Z0 { int z = 0; };
61+
struct Q : Z0, Y { int q = 'Q'; };
62+
struct CA { constexpr virtual const X* h() const { return &xx; } X xx; };
63+
struct CN : CA {};
64+
struct CB : CN {
65+
constexpr const Q* h() const override { return &qq; }
66+
Q qq;
67+
};
68+
constexpr bool g6() {
69+
CB b;
70+
const CA* p = &b;
71+
return p->h() == static_cast<const X*>(&b.qq) && p->h()->x == 'X';
72+
}
73+
static_assert(g6()); // Previously failed.
74+
75+
// Virtual call during construction of an intermediate class.
76+
struct K : NA {
77+
int r;
78+
constexpr K() : r(static_cast<const A*>(this)->f()) {}
79+
constexpr int f() const override { return 6; }
80+
};
81+
struct NK : K {};
82+
struct KK : NK { constexpr int f() const override { return 8; } };
83+
constexpr int g7() { KK k; const A* p = &k; return k.r * 10 + p->f(); }
84+
static_assert(g7() == 68); // Previously failed.
85+
struct W : NA {
86+
int r;
87+
constexpr W() : r(static_cast<const A*>(this)->f()) {}
88+
};
89+
struct WW : W { constexpr int f() const override { return 8; } };
90+
constexpr int g8() { WW w; const A* p = &w; return w.r * 10 + p->f(); }
91+
static_assert(g8() == 18); // Previously failed.
92+
93+
constexpr B gb;
94+
static_assert(static_cast<const A&>(gb).f() == 2);

‎tests/tests/imported/clang/cpp/SemaCXX/.constant-expression-cxx2a.rto/default.1.1.txt‎

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -13,24 +13,10 @@ fe_only -DTEST_NUMBER=1 --clang -tused -e 200 --no_wrap_diagnostics --clang_vers
1313
"SemaCXX/constant-expression-cxx2a.cpp", line 176: error: static assertion failed
1414
static_assert(d.ba == 'B');
1515
^
16-
"SemaCXX/constant-expression-cxx2a.cpp", line 176: note: the final comparison was 65 == 66
16+
"SemaCXX/constant-expression-cxx2a.cpp", line 176: note: the final comparison was 68 == 66
1717
static_assert(d.ba == 'B');
1818
^
1919

20-
"SemaCXX/constant-expression-cxx2a.cpp", line 181: error: static assertion failed
21-
static_assert(a.f() == 'D');
22-
^
23-
"SemaCXX/constant-expression-cxx2a.cpp", line 181: note: the final comparison was 65 == 68
24-
static_assert(a.f() == 'D');
25-
^
26-
27-
"SemaCXX/constant-expression-cxx2a.cpp", line 182: error: static assertion failed
28-
static_assert(b.f() == 'D');
29-
^
30-
"SemaCXX/constant-expression-cxx2a.cpp", line 182: note: the final comparison was 66 == 68
31-
static_assert(b.f() == 'D');
32-
^
33-
3420
"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"
3521
constexpr virtual const D *f() const { return &this->d; }
3622
^
@@ -41,20 +27,6 @@ fe_only -DTEST_NUMBER=1 --clang -tused -e 200 --no_wrap_diagnostics --clang_vers
4127
^
4228
detected during instantiation of class "Virtual::Covariant3<T> [with T=Virtual::C]" at line 203
4329

44-
"SemaCXX/constant-expression-cxx2a.cpp", line 209: error: static assertion failed
45-
static_assert(cb1->f()->f() == 'D');
46-
^
47-
"SemaCXX/constant-expression-cxx2a.cpp", line 209: note: the final comparison was 65 == 68
48-
static_assert(cb1->f()->f() == 'D');
49-
^
50-
51-
"SemaCXX/constant-expression-cxx2a.cpp", line 212: error: static assertion failed
52-
static_assert(cb2->f()->f() == 'D');
53-
^
54-
"SemaCXX/constant-expression-cxx2a.cpp", line 212: note: the final comparison was 66 == 68
55-
static_assert(cb2->f()->f() == 'D');
56-
^
57-
5830
"SemaCXX/constant-expression-cxx2a.cpp", line 232: error: initialization is not constant
5931
constexpr PureVirtualCall pure_virtual_call;
6032
^

‎tests/tests/imported/clang/cpp/SemaCXX/.constant-expression-cxx2a.rto/edg_x86_32.1.1.txt‎

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -13,24 +13,10 @@ fe_only -DTEST_NUMBER=1 --clang -tused -e 200 --no_wrap_diagnostics --clang_vers
1313
"SemaCXX/constant-expression-cxx2a.cpp", line 176: error: static assertion failed
1414
static_assert(d.ba == 'B');
1515
^
16-
"SemaCXX/constant-expression-cxx2a.cpp", line 176: note: the final comparison was 65 == 66
16+
"SemaCXX/constant-expression-cxx2a.cpp", line 176: note: the final comparison was 68 == 66
1717
static_assert(d.ba == 'B');
1818
^
1919

20-
"SemaCXX/constant-expression-cxx2a.cpp", line 181: error: static assertion failed
21-
static_assert(a.f() == 'D');
22-
^
23-
"SemaCXX/constant-expression-cxx2a.cpp", line 181: note: the final comparison was 65 == 68
24-
static_assert(a.f() == 'D');
25-
^
26-
27-
"SemaCXX/constant-expression-cxx2a.cpp", line 182: error: static assertion failed
28-
static_assert(b.f() == 'D');
29-
^
30-
"SemaCXX/constant-expression-cxx2a.cpp", line 182: note: the final comparison was 66 == 68
31-
static_assert(b.f() == 'D');
32-
^
33-
3420
"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"
3521
constexpr virtual const D *f() const { return &this->d; }
3622
^
@@ -41,20 +27,6 @@ fe_only -DTEST_NUMBER=1 --clang -tused -e 200 --no_wrap_diagnostics --clang_vers
4127
^
4228
detected during instantiation of class "Virtual::Covariant3<T> [with T=Virtual::C]" at line 203
4329

44-
"SemaCXX/constant-expression-cxx2a.cpp", line 209: error: static assertion failed
45-
static_assert(cb1->f()->f() == 'D');
46-
^
47-
"SemaCXX/constant-expression-cxx2a.cpp", line 209: note: the final comparison was 65 == 68
48-
static_assert(cb1->f()->f() == 'D');
49-
^
50-
51-
"SemaCXX/constant-expression-cxx2a.cpp", line 212: error: static assertion failed
52-
static_assert(cb2->f()->f() == 'D');
53-
^
54-
"SemaCXX/constant-expression-cxx2a.cpp", line 212: note: the final comparison was 66 == 68
55-
static_assert(cb2->f()->f() == 'D');
56-
^
57-
5830
"SemaCXX/constant-expression-cxx2a.cpp", line 232: error: initialization is not constant
5931
constexpr PureVirtualCall pure_virtual_call;
6032
^

‎tests/tests/imported/clang/cpp/SemaCXX/.constant-expression-cxx2a.rto/edg_x86_32_cp.1.1.txt‎

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -13,24 +13,10 @@ fe_only -DTEST_NUMBER=1 --clang -tused -e 200 --no_wrap_diagnostics --clang_vers
1313
"SemaCXX/constant-expression-cxx2a.cpp", line 176: error: static assertion failed
1414
static_assert(d.ba == 'B');
1515
^
16-
"SemaCXX/constant-expression-cxx2a.cpp", line 176: note: the final comparison was 65 == 66
16+
"SemaCXX/constant-expression-cxx2a.cpp", line 176: note: the final comparison was 68 == 66
1717
static_assert(d.ba == 'B');
1818
^
1919

20-
"SemaCXX/constant-expression-cxx2a.cpp", line 181: error: static assertion failed
21-
static_assert(a.f() == 'D');
22-
^
23-
"SemaCXX/constant-expression-cxx2a.cpp", line 181: note: the final comparison was 65 == 68
24-
static_assert(a.f() == 'D');
25-
^
26-
27-
"SemaCXX/constant-expression-cxx2a.cpp", line 182: error: static assertion failed
28-
static_assert(b.f() == 'D');
29-
^
30-
"SemaCXX/constant-expression-cxx2a.cpp", line 182: note: the final comparison was 66 == 68
31-
static_assert(b.f() == 'D');
32-
^
33-
3420
"SemaCXX/constant-expression-cxx2a.cpp", line 186: error: expression must have a constant value
3521
static_assert(d_not_constexpr.f() == 'D');
3622
^
@@ -48,20 +34,6 @@ fe_only -DTEST_NUMBER=1 --clang -tused -e 200 --no_wrap_diagnostics --clang_vers
4834
^
4935
detected during instantiation of class "Virtual::Covariant3<T> [with T=Virtual::C]" at line 203
5036

51-
"SemaCXX/constant-expression-cxx2a.cpp", line 209: error: static assertion failed
52-
static_assert(cb1->f()->f() == 'D');
53-
^
54-
"SemaCXX/constant-expression-cxx2a.cpp", line 209: note: the final comparison was 65 == 68
55-
static_assert(cb1->f()->f() == 'D');
56-
^
57-
58-
"SemaCXX/constant-expression-cxx2a.cpp", line 212: error: static assertion failed
59-
static_assert(cb2->f()->f() == 'D');
60-
^
61-
"SemaCXX/constant-expression-cxx2a.cpp", line 212: note: the final comparison was 66 == 68
62-
static_assert(cb2->f()->f() == 'D');
63-
^
64-
6537
"SemaCXX/constant-expression-cxx2a.cpp", line 232: error: initialization is not constant
6638
constexpr PureVirtualCall pure_virtual_call;
6739
^

0 commit comments

Comments
 (0)