Skip to content

Commit e4903e7

Browse files
Constant evaluation of virtual calls to an overrider in a base class [GH #397]
A virtual call whose final overrider is a member of an intermediate base class is now evaluated with the subobject of that base class (instead of the complete object) as the object expression. The adjustment of the result of a call to an overrider with a covariant return type now uses the offset of the base class of the return type (possibly an indirect one), instead of the offset of the base class through which the overrider was found.
1 parent f51894d commit e4903e7

12 files changed

Lines changed: 145 additions & 318 deletions

File tree

‎src/interpret.c‎

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22554,10 +22554,13 @@ 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-
a_routine_ptr callee = *p_callee;
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;
2255822560
do {
2255922561
a_base_class_ptr bcp = *(a_base_class_ptr*)subobj;
22560-
a_byte_count offset;
22562+
a_byte_count offset, retval_offset_step = 0;
22563+
a_routine_ptr overrider = NULL;
2256122564
an_overriding_virtual_function_ptr
2256222565
ovfp;
2256322566
if (bcp == NULL) {
@@ -22568,29 +22571,32 @@ if *p_this_arg is not initialized.)
2256822571
check_assertion(!bcp->is_pack_expansion);
2256922572
ovfp = bcp->variant.overriding_virtual_functions;
2257022573
for (; ovfp != NULL; ovfp = ovfp->next) {
22571-
if (ovfp->primary_function == callee) {
22574+
if (ovfp->primary_function == *p_callee) {
2257222575
a_base_class_ptr ret_base = ovfp->return_adjustment_base_class;
22573-
a_byte_count retval_offset_step;
2257422576
if (ret_base != NULL) {
2257522577
/* A covariant return override. The return value will need
22576-
adjustment. */
22577-
get_mapped_byte_count(&persistent_map, bcp, retval_offset_step);
22578-
*p_retval_offset += retval_offset_step;
22578+
adjustment (possibly to an indirect base class). */
22579+
retval_offset_step = compute_interpreter_base_offset(
22580+
ret_base, (a_base_class_ptr*)NULL);
2257922581
} /* if */
22580-
callee = ovfp->overriding_function;
22582+
overrider = ovfp->overriding_function;
2258122583
break;
2258222584
} /* if */
2258322585
} /* for */
2258422586
get_mapped_byte_count(&persistent_map, bcp, offset);
2258522587
subobj -= offset;
22588+
if (overrider != NULL) {
22589+
*p_callee = overrider;
22590+
*p_retval_offset += retval_offset_step;
22591+
callee_subobj = subobj;
22592+
} /* if */
2258622593
if (!subobject_is_initialized(subobj, complete_obj)) {
2258722594
/* The next-enclosing subobject is not constructed. Do not dispatch
2258822595
from it. */
2258922596
break;
2259022597
} /* if */
2259122598
} while (subobj != complete_obj);
22592-
*p_callee = callee;
22593-
this_val->address = subobj;
22599+
this_val->address = callee_subobj;
2259422600
} /* if */
2259522601
return result;
2259622602
} /* adjust_virtual_callee */
@@ -22793,10 +22799,12 @@ was charged up front to the actual cost of the call.
2279322799
a_byte_count *arg_size = (a_byte_count*)cw->arg_sizes;
2279422800

2279522801
if (result) {
22796-
if (cw->retval_offset != 0) {
22802+
if (cw->retval_offset != 0 &&
22803+
((a_constexpr_address*)item->result_storage)->address != NULL) {
2279722804
/* A virtual call dispatching to an overriding function with a covariant
22798-
return type. The return value is an address that must be updated to
22799-
match the static type of the expression. */
22805+
return type. The return value is an address (other than a null
22806+
pointer) that must be updated to match the static type of the
22807+
expression. */
2280022808
((a_constexpr_address*)item->result_storage)->address +=
2280122809
cw->retval_offset;
2280222810
} /* if */
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
[GH #397]
2+
Constant evaluation of virtual calls to an overrider in a base class
3+
4+
During constant evaluation, a virtual function call whose final overrider is
5+
a member of an intermediate base class (rather than of the most derived
6+
class) was evaluated with the complete object as the object expression,
7+
instead of the base class subobject of which the overrider is a member. For
8+
example, with --c++20:
9+
10+
struct A { constexpr virtual int f() const { return 1; } int a = 1; };
11+
struct B : A { constexpr int f() const override { return a + 2; } };
12+
struct E { int e = 0; };
13+
struct F : E, B {};
14+
constexpr int g() { F f; A& r = f; return r.f(); }
15+
static_assert(g() == 3); // Previously failed.
16+
17+
This is now fixed. In addition, the adjustment of the result of a call to
18+
an overrider with a covariant return type (e.g., from a pointer to a derived
19+
class to a pointer to its base class) was computed incorrectly, and was
20+
applied even to a null pointer result (which could cause the front end to
21+
crash); this is also fixed.

‎tests/expectations/edg_x86_64/imported.log‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7070,7 +7070,6 @@ gnu/cpp/cpp2a/consteval-memfn2.sft.cpp:fp:-DTEST_NUMBER=1 --c++20:FAIL:
70707070
gnu/cpp/cpp2a/consteval-prop17.sft.cpp:fn:-DTEST_NUMBER=1 --c++20:FAIL:
70717071
gnu/cpp/cpp2a/consteval-prop3.sft.cpp:fn:-DTEST_NUMBER=1 --c++20:FAIL:
70727072
gnu/cpp/cpp2a/consteval-prop6.sft.cpp:fn:-DTEST_NUMBER=1 --c++20:FAIL:
7073-
gnu/cpp/cpp2a/consteval-virtual5.sft.cpp:fp:-DTEST_NUMBER=1 --c++20:FAIL:
70747073
gnu/cpp/cpp2a/consteval12.sft.cpp:fp:-DTEST_NUMBER=1 --c++20:FAIL:
70757074
gnu/cpp/cpp2a/consteval13.sft.cpp:fp:-DTEST_NUMBER=1 --c++20:FAIL:
70767075
gnu/cpp/cpp2a/consteval25.sft.cpp:fp:-DTEST_NUMBER=1 --c++20:FAIL:
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
fe_only -DTEST_NUMBER=1 --g++ --c++20 Test_name.c
2+
"Test_name.c", line 21: error: expression must have a constant value
3+
constexpr int v = g(); // Error: null pointer argument (previously crashed).
4+
^
5+
"Test_name.c", line 19: note: attempt to dereference a null pointer
6+
return __builtin_memcmp(rp.p(), (const X*)&s.z, sizeof(X));
7+
^
8+
9+
"Test_name.c", line 21: warning: variable "v" was declared but never referenced
10+
constexpr int v = g(); // Error: null pointer argument (previously crashed).
11+
^
12+
13+
1 error detected in the compilation of "Test_name.c".
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
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//type:fn
2+
//options:--g++ --c++20
3+
//remark:[GH #397] Null pointer result of a covariant virtual call
4+
5+
struct E { int e = 0; };
6+
struct X { int x = 7; };
7+
struct Y { int y = 0; };
8+
struct Z : Y, X {};
9+
struct P { constexpr virtual const X* p() const { return nullptr; } };
10+
struct S : E, P {
11+
Z z;
12+
constexpr const Z* p() const override { return nullptr; }
13+
};
14+
constexpr bool f() { S s; const P& rp = s; return rp.p() == nullptr; }
15+
static_assert(f());
16+
constexpr int g() {
17+
S s;
18+
const P& rp = s;
19+
return __builtin_memcmp(rp.p(), (const X*)&s.z, sizeof(X));
20+
}
21+
constexpr int v = g(); // Error: null pointer argument (previously crashed).

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

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
//type:fp
2+
//options:--c++20
3+
//remark:[GH #397] Constant evaluation of a virtual call whose final overrider
4+
//remark:is a member of an intermediate base class
5+
6+
struct A { constexpr virtual int f() const { return 1; } int a = 1; };
7+
struct B : A { constexpr int f() const override { return a + 2; } };
8+
struct E { int e = 0; };
9+
struct F : E, B {};
10+
constexpr int g() { F f; A& r = f; return r.f(); }
11+
static_assert(g() == 3); // Previously failed.
12+
13+
struct G : B {};
14+
constexpr int h() { G g; A& r = g; return r.f(); }
15+
static_assert(h() == 3); // Previously failed.
16+
17+
struct H : E, G { int x = 5; };
18+
constexpr int i() { H h; A& ra = h; B& rb = h; return ra.f() * 10 + rb.f(); }
19+
static_assert(i() == 33); // Previously failed.
20+
21+
struct C : A {};
22+
constexpr int j() { C c; A& r = c; return r.f(); }
23+
static_assert(j() == 1);
24+
25+
struct X { int x = 7; };
26+
struct Y { int y = 0; };
27+
struct Z : Y, X {};
28+
struct P { constexpr virtual const X* p() const { return nullptr; } };
29+
struct Q : P { Z z; constexpr const Z* p() const override { return &z; } };
30+
struct R : E, Q {};
31+
constexpr int k() { R r; const P& rp = r; return rp.p()->x; }
32+
static_assert(k() == 7);
33+
struct S : E, P {
34+
Z z;
35+
bool null;
36+
constexpr S(bool b) : null(b) {}
37+
constexpr const Z* p() const override { return null ? nullptr : &z; }
38+
};
39+
constexpr bool l(bool b) {
40+
S s(b);
41+
const P& rp = s;
42+
return b ? rp.p() == nullptr : rp.p() == (const X*)&s.z;
43+
}
44+
static_assert(l(false)); // Previously failed.
45+
static_assert(l(true));
46+
struct W : Y, Z {};
47+
struct T : Q { W w; constexpr const W* p() const override { return &w; } };
48+
struct U : E, T {};
49+
constexpr bool m() {
50+
U u;
51+
const P& rp = u;
52+
const Q& rq = u;
53+
return rp.p() == (const X*)&u.w && rq.p() == (const Z*)&u.w &&
54+
rp.p()->x == 7;
55+
}
56+
static_assert(m()); // Previously failed.
57+
58+
// A covariant return type whose class is an indirect base class.
59+
struct V : Y, Z {};
60+
struct VV : E, V {};
61+
struct I : P { VV vv; constexpr const VV* p() const override { return &vv; } };
62+
struct J : E, I {};
63+
constexpr bool n() {
64+
J j;
65+
const P& rp = j;
66+
return rp.p() == (const X*)&j.vv && rp.p()->x == 7;
67+
}
68+
static_assert(n());

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

Lines changed: 0 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -41,94 +41,20 @@ fe_only -DTEST_NUMBER=1 --clang -tused -e 200 --no_wrap_diagnostics --clang_vers
4141
^
4242
detected during instantiation of class "Virtual::Covariant3<T> [with T=Virtual::C]" at line 203
4343

44-
"SemaCXX/constant-expression-cxx2a.cpp", line 207: warning: integer conversion resulted in a change of sign
45-
static_assert(cb1->f()->a == 'A');
46-
^
47-
48-
"SemaCXX/constant-expression-cxx2a.cpp", line 207: error: static assertion failed
49-
static_assert(cb1->f()->a == 'A');
50-
^
51-
"SemaCXX/constant-expression-cxx2a.cpp", line 207: note: the final comparison was 0 == 65
52-
static_assert(cb1->f()->a == 'A');
53-
^
54-
55-
"SemaCXX/constant-expression-cxx2a.cpp", line 208: error: static assertion failed
56-
static_assert(cb1->f() == (B*)&cb.d);
57-
^
58-
5944
"SemaCXX/constant-expression-cxx2a.cpp", line 209: error: static assertion failed
6045
static_assert(cb1->f()->f() == 'D');
6146
^
6247
"SemaCXX/constant-expression-cxx2a.cpp", line 209: note: the final comparison was 65 == 68
6348
static_assert(cb1->f()->f() == 'D');
6449
^
6550

66-
"SemaCXX/constant-expression-cxx2a.cpp", line 210: warning: integer conversion resulted in a change of sign
67-
static_assert(cb2->f()->b == 'B');
68-
^
69-
70-
"SemaCXX/constant-expression-cxx2a.cpp", line 210: error: static assertion failed
71-
static_assert(cb2->f()->b == 'B');
72-
^
73-
"SemaCXX/constant-expression-cxx2a.cpp", line 210: note: the final comparison was VALUE == 66
74-
static_assert(cb2->f()->b == 'B');
75-
^
76-
77-
"SemaCXX/constant-expression-cxx2a.cpp", line 211: error: static assertion failed
78-
static_assert(cb2->f() == &cb.d);
79-
^
80-
8151
"SemaCXX/constant-expression-cxx2a.cpp", line 212: error: static assertion failed
8252
static_assert(cb2->f()->f() == 'D');
8353
^
8454
"SemaCXX/constant-expression-cxx2a.cpp", line 212: note: the final comparison was 66 == 68
8555
static_assert(cb2->f()->f() == 'D');
8656
^
8757

88-
"SemaCXX/constant-expression-cxx2a.cpp", line 216: warning: integer conversion resulted in a change of sign
89-
static_assert(cc1->f()->a == 'A');
90-
^
91-
92-
"SemaCXX/constant-expression-cxx2a.cpp", line 216: error: static assertion failed
93-
static_assert(cc1->f()->a == 'A');
94-
^
95-
"SemaCXX/constant-expression-cxx2a.cpp", line 216: note: the final comparison was 0 == 65
96-
static_assert(cc1->f()->a == 'A');
97-
^
98-
99-
"SemaCXX/constant-expression-cxx2a.cpp", line 217: error: static assertion failed
100-
static_assert(cc1->f() == (C*)&cc.d);
101-
^
102-
103-
"SemaCXX/constant-expression-cxx2a.cpp", line 218: error: static assertion failed
104-
static_assert(cc1->f()->f() == 'D');
105-
^
106-
"SemaCXX/constant-expression-cxx2a.cpp", line 218: note: the final comparison was 65 == 68
107-
static_assert(cc1->f()->f() == 'D');
108-
^
109-
110-
"SemaCXX/constant-expression-cxx2a.cpp", line 219: warning: integer conversion resulted in a change of sign
111-
static_assert(cc2->f()->c == 'C');
112-
^
113-
114-
"SemaCXX/constant-expression-cxx2a.cpp", line 219: error: static assertion failed
115-
static_assert(cc2->f()->c == 'C');
116-
^
117-
"SemaCXX/constant-expression-cxx2a.cpp", line 219: note: the final comparison was 2131689472 == 67
118-
static_assert(cc2->f()->c == 'C');
119-
^
120-
121-
"SemaCXX/constant-expression-cxx2a.cpp", line 220: error: static assertion failed
122-
static_assert(cc2->f() == &cc.d);
123-
^
124-
125-
"SemaCXX/constant-expression-cxx2a.cpp", line 221: error: static assertion failed
126-
static_assert(cc2->f()->f() == 'D');
127-
^
128-
"SemaCXX/constant-expression-cxx2a.cpp", line 221: note: the final comparison was 67 == 68
129-
static_assert(cc2->f()->f() == 'D');
130-
^
131-
13258
"SemaCXX/constant-expression-cxx2a.cpp", line 232: error: initialization is not constant
13359
constexpr PureVirtualCall pure_virtual_call;
13460
^

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

Lines changed: 0 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -41,94 +41,20 @@ fe_only -DTEST_NUMBER=1 --clang -tused -e 200 --no_wrap_diagnostics --clang_vers
4141
^
4242
detected during instantiation of class "Virtual::Covariant3<T> [with T=Virtual::C]" at line 203
4343

44-
"SemaCXX/constant-expression-cxx2a.cpp", line 207: warning: integer conversion resulted in a change of sign
45-
static_assert(cb1->f()->a == 'A');
46-
^
47-
48-
"SemaCXX/constant-expression-cxx2a.cpp", line 207: error: static assertion failed
49-
static_assert(cb1->f()->a == 'A');
50-
^
51-
"SemaCXX/constant-expression-cxx2a.cpp", line 207: note: the final comparison was 0 == 65
52-
static_assert(cb1->f()->a == 'A');
53-
^
54-
55-
"SemaCXX/constant-expression-cxx2a.cpp", line 208: error: static assertion failed
56-
static_assert(cb1->f() == (B*)&cb.d);
57-
^
58-
5944
"SemaCXX/constant-expression-cxx2a.cpp", line 209: error: static assertion failed
6045
static_assert(cb1->f()->f() == 'D');
6146
^
6247
"SemaCXX/constant-expression-cxx2a.cpp", line 209: note: the final comparison was 65 == 68
6348
static_assert(cb1->f()->f() == 'D');
6449
^
6550

66-
"SemaCXX/constant-expression-cxx2a.cpp", line 210: warning: integer conversion resulted in a change of sign
67-
static_assert(cb2->f()->b == 'B');
68-
^
69-
70-
"SemaCXX/constant-expression-cxx2a.cpp", line 210: error: static assertion failed
71-
static_assert(cb2->f()->b == 'B');
72-
^
73-
"SemaCXX/constant-expression-cxx2a.cpp", line 210: note: the final comparison was VALUE == 66
74-
static_assert(cb2->f()->b == 'B');
75-
^
76-
77-
"SemaCXX/constant-expression-cxx2a.cpp", line 211: error: static assertion failed
78-
static_assert(cb2->f() == &cb.d);
79-
^
80-
8151
"SemaCXX/constant-expression-cxx2a.cpp", line 212: error: static assertion failed
8252
static_assert(cb2->f()->f() == 'D');
8353
^
8454
"SemaCXX/constant-expression-cxx2a.cpp", line 212: note: the final comparison was 66 == 68
8555
static_assert(cb2->f()->f() == 'D');
8656
^
8757

88-
"SemaCXX/constant-expression-cxx2a.cpp", line 216: warning: integer conversion resulted in a change of sign
89-
static_assert(cc1->f()->a == 'A');
90-
^
91-
92-
"SemaCXX/constant-expression-cxx2a.cpp", line 216: error: static assertion failed
93-
static_assert(cc1->f()->a == 'A');
94-
^
95-
"SemaCXX/constant-expression-cxx2a.cpp", line 216: note: the final comparison was 0 == 65
96-
static_assert(cc1->f()->a == 'A');
97-
^
98-
99-
"SemaCXX/constant-expression-cxx2a.cpp", line 217: error: static assertion failed
100-
static_assert(cc1->f() == (C*)&cc.d);
101-
^
102-
103-
"SemaCXX/constant-expression-cxx2a.cpp", line 218: error: static assertion failed
104-
static_assert(cc1->f()->f() == 'D');
105-
^
106-
"SemaCXX/constant-expression-cxx2a.cpp", line 218: note: the final comparison was 65 == 68
107-
static_assert(cc1->f()->f() == 'D');
108-
^
109-
110-
"SemaCXX/constant-expression-cxx2a.cpp", line 219: warning: integer conversion resulted in a change of sign
111-
static_assert(cc2->f()->c == 'C');
112-
^
113-
114-
"SemaCXX/constant-expression-cxx2a.cpp", line 219: error: static assertion failed
115-
static_assert(cc2->f()->c == 'C');
116-
^
117-
"SemaCXX/constant-expression-cxx2a.cpp", line 219: note: the final comparison was 0 == 67
118-
static_assert(cc2->f()->c == 'C');
119-
^
120-
121-
"SemaCXX/constant-expression-cxx2a.cpp", line 220: error: static assertion failed
122-
static_assert(cc2->f() == &cc.d);
123-
^
124-
125-
"SemaCXX/constant-expression-cxx2a.cpp", line 221: error: static assertion failed
126-
static_assert(cc2->f()->f() == 'D');
127-
^
128-
"SemaCXX/constant-expression-cxx2a.cpp", line 221: note: the final comparison was 67 == 68
129-
static_assert(cc2->f()->f() == 'D');
130-
^
131-
13258
"SemaCXX/constant-expression-cxx2a.cpp", line 232: error: initialization is not constant
13359
constexpr PureVirtualCall pure_virtual_call;
13460
^

0 commit comments

Comments
 (0)