Skip to content

Commit 0372f08

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 6c38e1f commit 0372f08

10 files changed

Lines changed: 174 additions & 158 deletions

File tree

‎src/interpret.c‎

Lines changed: 60 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -22556,49 +22556,79 @@ if *p_this_arg is not initialized.)
2255622556
constructor has not completely run yet, or because the object has
2255722557
been partially destroyed). */
2255822558
} else {
22559-
/* The final overrider is a member of the class of the subobject from which
22560-
it is found, and that subobject is the object for the call. */
22561-
a_byte *callee_subobj = subobj;
22559+
/* Find the outermost enclosing subobject from which the call is
22560+
dispatched (outer_subobj, of class outer_class). */
22561+
a_byte *outer_subobj = subobj;
22562+
a_type_ptr outer_class = NULL;
22563+
a_type_ptr callee_class = skip_typerefs(parent_class_of(*p_callee));
22564+
a_base_class_ptr bcp;
2256222565
do {
22563-
a_base_class_ptr bcp = *(a_base_class_ptr*)subobj;
22564-
a_byte_count offset, retval_offset_step = 0;
22565-
a_routine_ptr overrider = NULL;
22566-
an_overriding_virtual_function_ptr
22567-
ovfp;
22566+
a_byte_count offset;
22567+
bcp = *(a_base_class_ptr*)outer_subobj;
2256822568
if (bcp == NULL) {
2256922569
/* A most derived subobject (i.e., a field or array element). We are
2257022570
done. */
2257122571
break;
2257222572
} /* if */
22573-
check_assertion(!bcp->is_pack_expansion);
22574-
ovfp = bcp->variant.overriding_virtual_functions;
22575-
for (; ovfp != NULL; ovfp = ovfp->next) {
22576-
if (ovfp->primary_function == *p_callee) {
22573+
get_mapped_byte_count(&persistent_map, bcp, offset);
22574+
outer_subobj -= offset;
22575+
outer_class = bcp->derived_class;
22576+
if (!subobject_is_initialized(outer_subobj, complete_obj)) {
22577+
/* The next-enclosing subobject is not constructed. Do not dispatch
22578+
from anything enclosing it. */
22579+
break;
22580+
} /* if */
22581+
} while (outer_subobj != complete_obj);
22582+
if (outer_class != NULL) {
22583+
/* The override information for the subobject of the callee's class
22584+
in outer_class gives the final overrider in outer_class (which may
22585+
be a member of an intermediate class). The base class entry for
22586+
that subobject is identified by its offset, since the callee's
22587+
class can be a base class of outer_class more than once. */
22588+
an_overriding_virtual_function_ptr
22589+
ovfp;
22590+
for (bcp = base_classes_of(outer_class); bcp != NULL; bcp = bcp->next) {
22591+
if (!bcp->is_pack_expansion &&
22592+
same_entities(skip_typerefs(bcp->type), callee_class) &&
22593+
outer_subobj + compute_interpreter_base_offset(
22594+
bcp, (a_base_class_ptr*)NULL) ==
22595+
subobj) {
22596+
break;
22597+
} /* if */
22598+
} /* for */
22599+
check_assertion(bcp != NULL);
22600+
do {
22601+
/* If the overrider found is a member of an intermediate class, it
22602+
is normally the final overrider, but a function that overrides
22603+
it without being recorded as overriding the original function
22604+
(e.g., because of an invalid covariant return type) is found
22605+
through the base class entry for that intermediate class. */
22606+
ovfp = bcp->variant.overriding_virtual_functions;
22607+
while (ovfp != NULL && ovfp->primary_function != *p_callee) {
22608+
ovfp = ovfp->next;
22609+
} /* while */
22610+
if (ovfp != NULL) {
2257722611
a_base_class_ptr ret_base = ovfp->return_adjustment_base_class;
22612+
*p_callee = ovfp->overriding_function;
22613+
bcp = ovfp->base_class;
22614+
if (bcp == NULL) {
22615+
/* The overrider is a member of outer_class. */
22616+
this_val->address = outer_subobj;
22617+
} else {
22618+
this_val->address =
22619+
outer_subobj +
22620+
compute_interpreter_base_offset(bcp,
22621+
(a_base_class_ptr*)NULL);
22622+
} /* if */
2257822623
if (ret_base != NULL) {
2257922624
/* A covariant return override. The return value will need
2258022625
adjustment (possibly to an indirect base class). */
22581-
retval_offset_step = compute_interpreter_base_offset(
22626+
*p_retval_offset += compute_interpreter_base_offset(
2258222627
ret_base, (a_base_class_ptr*)NULL);
2258322628
} /* if */
22584-
overrider = ovfp->overriding_function;
22585-
break;
2258622629
} /* if */
22587-
} /* for */
22588-
get_mapped_byte_count(&persistent_map, bcp, offset);
22589-
subobj -= offset;
22590-
if (overrider != NULL) {
22591-
*p_callee = overrider;
22592-
*p_retval_offset += retval_offset_step;
22593-
callee_subobj = subobj;
22594-
} /* if */
22595-
if (!subobject_is_initialized(subobj, complete_obj)) {
22596-
/* The next-enclosing subobject is not constructed. Do not dispatch
22597-
from it. */
22598-
break;
22599-
} /* if */
22600-
} while (subobj != complete_obj);
22601-
this_val->address = callee_subobj;
22630+
} while (ovfp != NULL && bcp != NULL);
22631+
} /* if */
2260222632
} /* if */
2260322633
return result;
2260422634
} /* 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
@@ -7144,7 +7144,6 @@ gnu/cpp/cpp2a/paren-init30.sft.cpp:fn:-DTEST_NUMBER=1 --c++20:FAIL:
71447144
gnu/cpp/cpp2a/paren-init33.sft.cpp:rp:-DTEST_NUMBER=1 --c++20:FAIL(COMPILE):
71457145
gnu/cpp/cpp2a/paren-init35.sft.cpp:fp:-DTEST_NUMBER=1 --c++20:FAIL:
71467146
gnu/cpp/cpp2a/paren-init4.sft.cpp:rp:-DTEST_NUMBER=1 --c++20:FAIL(COMPILE):
7147-
gnu/cpp/cpp2a/pr117317-1.sft.cpp:fp:-DTEST_NUMBER=1 --c++20:FAIL:
71487147
gnu/cpp/cpp2a/pr99833.sft.cpp:fp:-DTEST_NUMBER=1 --c++20:ABORT:
71497148
gnu/cpp/cpp2a/spaceship-constexpr2/test.mft.cpp:fp:-DTEST_NUMBER=1 --c++20:FAIL:
71507149
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: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
//type:fp
2+
//options:--c++20
3+
//remark:Constant evaluation: overrider below a class that does not override
4+
5+
struct A {
6+
constexpr virtual int f() const { return 1; }
7+
int a = 0;
8+
};
9+
struct NA : A {};
10+
struct B : NA {
11+
constexpr int f() const override { return 2; }
12+
};
13+
constexpr int g1() { B b; const A* p = &b; return p->f(); }
14+
static_assert(g1() == 2); // Previously failed.
15+
constexpr int g1n() { B b; const NA* p = &b; return p->f(); }
16+
static_assert(g1n() == 2); // Previously failed.
17+
18+
// Two non-overriding levels.
19+
struct NNA : NA {};
20+
struct C : NNA { constexpr int f() const override { return 3; } };
21+
constexpr int g2() { C c; const A* p = &c; return p->f(); }
22+
static_assert(g2() == 3); // Previously failed.
23+
24+
// The overrider is in a middle class, above a non-overriding one.
25+
struct M : NA { constexpr int f() const override { return 4 + a; } };
26+
struct NM : M {};
27+
struct E { int e = 7; };
28+
struct T : E, NM {};
29+
constexpr int g3() { T t; t.a = 10; const A* p = &t; return p->f(); }
30+
static_assert(g3() == 14); // Previously failed.
31+
32+
// Repeated base class: only one path overrides.
33+
struct L : A { constexpr int f() const override { return 5 + a; } };
34+
struct R : A {};
35+
struct D : L, R {};
36+
constexpr int g4() {
37+
D d;
38+
static_cast<L&>(d).a = 1;
39+
static_cast<R&>(d).a = 2;
40+
const A* pl = static_cast<const L*>(&d);
41+
const A* pr = static_cast<const R*>(&d);
42+
return pl->f() * 10 + pr->f();
43+
}
44+
static_assert(g4() == 61);
45+
struct DD : D {
46+
constexpr int f() const override { return 9; }
47+
};
48+
constexpr int g5() {
49+
DD d;
50+
const A* pl = static_cast<const L*>(&d);
51+
const A* pr = static_cast<const R*>(&d);
52+
return pl->f() * 10 + pr->f();
53+
}
54+
static_assert(g5() == 99); // Previously failed.
55+
56+
// Covariant return through a non-overriding intermediate class.
57+
struct X { int x = 'X'; };
58+
struct Y : X { int y = 'Y'; };
59+
struct Z0 { int z = 0; };
60+
struct Q : Z0, Y { int q = 'Q'; };
61+
struct CA { constexpr virtual const X* h() const { return &xx; } X xx; };
62+
struct CN : CA {};
63+
struct CB : CN {
64+
constexpr const Q* h() const override { return &qq; }
65+
Q qq;
66+
};
67+
constexpr bool g6() {
68+
CB b;
69+
const CA* p = &b;
70+
return p->h() == static_cast<const X*>(&b.qq) && p->h()->x == 'X';
71+
}
72+
static_assert(g6()); // Previously failed.
73+
74+
// Virtual call during construction of an intermediate class.
75+
struct K : NA {
76+
int r;
77+
constexpr K() : r(static_cast<const A*>(this)->f()) {}
78+
constexpr int f() const override { return 6; }
79+
};
80+
struct NK : K {};
81+
struct KK : NK { constexpr int f() const override { return 8; } };
82+
constexpr int g7() { KK k; const A* p = &k; return k.r * 10 + p->f(); }
83+
static_assert(g7() == 68); // Previously failed.
84+
struct W : NA {
85+
int r;
86+
constexpr W() : r(static_cast<const A*>(this)->f()) {}
87+
};
88+
struct WW : W { constexpr int f() const override { return 8; } };
89+
constexpr int g8() { WW w; const A* p = &w; return w.r * 10 + p->f(); }
90+
static_assert(g8() == 18); // Previously failed.
91+
92+
constexpr B gb;
93+
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)