Skip to content

Commit 81ed756

Browse files
authored
[clang] Fix constant evaluation of member pointer access into sibling class. (#150829)
HandleMemberPointerAccess considered whether the lvalue path in a member pointer access matched the bases of the containing class of the member, but neglected to check the same for the containing class of the member itself, thereby ignoring access attempts to members in direct sibling classes. Fixes #150705. Fixes #150709.
1 parent 06884d0 commit 81ed756

File tree

4 files changed

+84
-0
lines changed

4 files changed

+84
-0
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,26 @@ C++ Specific Potentially Breaking Changes
4545
regressions if your build system supports two-phase compilation model but haven't support
4646
reduced BMI or it is a compiler bug or a bug in users code.
4747

48+
- Clang now correctly diagnoses during constant expression evaluation undefined behavior due to member
49+
pointer access to a member which is not a direct or indirect member of the most-derived object
50+
of the accessed object but is instead located directly in a sibling class to one of the classes
51+
along the inheritance hierarchy of the most-derived object as ill-formed.
52+
Other scenarios in which the member is not member of the most derived object were already
53+
diagnosed previously. (#GH150709)
54+
55+
.. code-block:: c++
56+
57+
struct A {};
58+
struct B : A {};
59+
struct C : A { constexpr int foo() const { return 1; } };
60+
constexpr A a;
61+
constexpr B b;
62+
constexpr C c;
63+
constexpr auto mp = static_cast<int(A::*)() const>(&C::foo);
64+
static_assert((a.*mp)() == 1); // continues to be rejected
65+
static_assert((b.*mp)() == 1); // newly rejected
66+
static_assert((c.*mp)() == 1); // accepted
67+
4868
ABI Changes in This Version
4969
---------------------------
5070

clang/lib/AST/ExprConstant.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5035,6 +5035,9 @@ static const ValueDecl *HandleMemberPointerAccess(EvalInfo &Info,
50355035
// This is a member of some derived class. Truncate LV appropriately.
50365036
// The end of the derived-to-base path for the base object must match the
50375037
// derived-to-base path for the member pointer.
5038+
// C++23 [expr.mptr.oper]p4:
5039+
// If the result of E1 is an object [...] whose most derived object does
5040+
// not contain the member to which E2 refers, the behavior is undefined.
50385041
if (LV.Designator.MostDerivedPathLength + MemPtr.Path.size() >
50395042
LV.Designator.Entries.size()) {
50405043
Info.FFDiag(RHS);
@@ -5051,6 +5054,24 @@ static const ValueDecl *HandleMemberPointerAccess(EvalInfo &Info,
50515054
return nullptr;
50525055
}
50535056
}
5057+
// MemPtr.Path only contains the base classes of the class directly
5058+
// containing the member E2. It is still necessary to check that the class
5059+
// directly containing the member E2 lies on the derived-to-base path of E1
5060+
// to avoid incorrectly permitting member pointer access into a sibling
5061+
// class of the class containing the member E2. If this class would
5062+
// correspond to the most-derived class of E1, it either isn't contained in
5063+
// LV.Designator.Entries or the corresponding entry refers to an array
5064+
// element instead. Therefore get the most derived class directly in this
5065+
// case. Otherwise the previous entry should correpond to this class.
5066+
const CXXRecordDecl *LastLVDecl =
5067+
(PathLengthToMember > LV.Designator.MostDerivedPathLength)
5068+
? getAsBaseClass(LV.Designator.Entries[PathLengthToMember - 1])
5069+
: LV.Designator.MostDerivedType->getAsCXXRecordDecl();
5070+
const CXXRecordDecl *LastMPDecl = MemPtr.getContainingRecord();
5071+
if (LastLVDecl->getCanonicalDecl() != LastMPDecl->getCanonicalDecl()) {
5072+
Info.FFDiag(RHS);
5073+
return nullptr;
5074+
}
50545075

50555076
// Truncate the lvalue to the appropriate derived class.
50565077
if (!CastToDerivedClass(Info, RHS, LV, MemPtr.getContainingRecord(),

clang/test/SemaCXX/constant-expression-cxx11.cpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2615,3 +2615,33 @@ namespace DoubleCapture {
26152615
};
26162616
}
26172617
}
2618+
2619+
namespace GH150709 {
2620+
struct C { };
2621+
struct D : C {
2622+
constexpr int f() const { return 1; };
2623+
};
2624+
struct E : C { };
2625+
struct F : D { };
2626+
struct G : E { };
2627+
2628+
constexpr C c1, c2[2];
2629+
constexpr D d1, d2[2];
2630+
constexpr E e1, e2[2];
2631+
constexpr F f;
2632+
constexpr G g;
2633+
2634+
constexpr auto mp = static_cast<int (C::*)() const>(&D::f);
2635+
2636+
// sanity checks for fix of GH150709 (unchanged behavior)
2637+
static_assert((c1.*mp)() == 1, ""); // expected-error {{constant expression}}
2638+
static_assert((d1.*mp)() == 1, "");
2639+
static_assert((f.*mp)() == 1, "");
2640+
static_assert((c2[0].*mp)() == 1, ""); // expected-error {{constant expression}}
2641+
static_assert((d2[0].*mp)() == 1, "");
2642+
2643+
// incorrectly undiagnosed before fix of GH150709
2644+
static_assert((e1.*mp)() == 1, ""); // expected-error {{constant expression}}
2645+
static_assert((e2[0].*mp)() == 1, ""); // expected-error {{constant expression}}
2646+
static_assert((g.*mp)() == 1, ""); // expected-error {{constant expression}}
2647+
}

clang/test/SemaCXX/constant-expression-cxx2a.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1497,3 +1497,16 @@ namespace GH67317 {
14971497
// expected-note {{subobject of type 'const unsigned char' is not initialized}}
14981498
__builtin_bit_cast(unsigned char, *new char[3][1]);
14991499
};
1500+
1501+
namespace GH150705 {
1502+
struct A { };
1503+
struct B : A { };
1504+
struct C : A {
1505+
constexpr virtual int foo() const { return 0; }
1506+
};
1507+
constexpr auto p = &C::foo;
1508+
constexpr auto q = static_cast<int (A::*)() const>(p);
1509+
constexpr B b;
1510+
constexpr const A& a = b;
1511+
constexpr auto x = (a.*q)(); // expected-error {{constant expression}}
1512+
}

0 commit comments

Comments
 (0)