Author: keinflue Date: 2025-08-05T14:44:15-07:00 New Revision: 81ed75679dcfb0b60764db1c8e0a91065a26a742
URL: https://github.com/llvm/llvm-project/commit/81ed75679dcfb0b60764db1c8e0a91065a26a742 DIFF: https://github.com/llvm/llvm-project/commit/81ed75679dcfb0b60764db1c8e0a91065a26a742.diff LOG: [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. Added: Modified: clang/docs/ReleaseNotes.rst clang/lib/AST/ExprConstant.cpp clang/test/SemaCXX/constant-expression-cxx11.cpp clang/test/SemaCXX/constant-expression-cxx2a.cpp Removed: ################################################################################ diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 9231f2cae9bfa..17e3df467593d 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -45,6 +45,26 @@ C++ Specific Potentially Breaking Changes regressions if your build system supports two-phase compilation model but haven't support reduced BMI or it is a compiler bug or a bug in users code. +- Clang now correctly diagnoses during constant expression evaluation undefined behavior due to member + pointer access to a member which is not a direct or indirect member of the most-derived object + of the accessed object but is instead located directly in a sibling class to one of the classes + along the inheritance hierarchy of the most-derived object as ill-formed. + Other scenarios in which the member is not member of the most derived object were already + diagnosed previously. (#GH150709) + + .. code-block:: c++ + + struct A {}; + struct B : A {}; + struct C : A { constexpr int foo() const { return 1; } }; + constexpr A a; + constexpr B b; + constexpr C c; + constexpr auto mp = static_cast<int(A::*)() const>(&C::foo); + static_assert((a.*mp)() == 1); // continues to be rejected + static_assert((b.*mp)() == 1); // newly rejected + static_assert((c.*mp)() == 1); // accepted + ABI Changes in This Version --------------------------- diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 3a47fa87b5f77..34af9ccd2e96e 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -5035,6 +5035,9 @@ static const ValueDecl *HandleMemberPointerAccess(EvalInfo &Info, // This is a member of some derived class. Truncate LV appropriately. // The end of the derived-to-base path for the base object must match the // derived-to-base path for the member pointer. + // C++23 [expr.mptr.oper]p4: + // If the result of E1 is an object [...] whose most derived object does + // not contain the member to which E2 refers, the behavior is undefined. if (LV.Designator.MostDerivedPathLength + MemPtr.Path.size() > LV.Designator.Entries.size()) { Info.FFDiag(RHS); @@ -5051,6 +5054,24 @@ static const ValueDecl *HandleMemberPointerAccess(EvalInfo &Info, return nullptr; } } + // MemPtr.Path only contains the base classes of the class directly + // containing the member E2. It is still necessary to check that the class + // directly containing the member E2 lies on the derived-to-base path of E1 + // to avoid incorrectly permitting member pointer access into a sibling + // class of the class containing the member E2. If this class would + // correspond to the most-derived class of E1, it either isn't contained in + // LV.Designator.Entries or the corresponding entry refers to an array + // element instead. Therefore get the most derived class directly in this + // case. Otherwise the previous entry should correpond to this class. + const CXXRecordDecl *LastLVDecl = + (PathLengthToMember > LV.Designator.MostDerivedPathLength) + ? getAsBaseClass(LV.Designator.Entries[PathLengthToMember - 1]) + : LV.Designator.MostDerivedType->getAsCXXRecordDecl(); + const CXXRecordDecl *LastMPDecl = MemPtr.getContainingRecord(); + if (LastLVDecl->getCanonicalDecl() != LastMPDecl->getCanonicalDecl()) { + Info.FFDiag(RHS); + return nullptr; + } // Truncate the lvalue to the appropriate derived class. if (!CastToDerivedClass(Info, RHS, LV, MemPtr.getContainingRecord(), diff --git a/clang/test/SemaCXX/constant-expression-cxx11.cpp b/clang/test/SemaCXX/constant-expression-cxx11.cpp index 5ecb8c607f59a..2423a77e6e7d2 100644 --- a/clang/test/SemaCXX/constant-expression-cxx11.cpp +++ b/clang/test/SemaCXX/constant-expression-cxx11.cpp @@ -2615,3 +2615,33 @@ namespace DoubleCapture { }; } } + +namespace GH150709 { + struct C { }; + struct D : C { + constexpr int f() const { return 1; }; + }; + struct E : C { }; + struct F : D { }; + struct G : E { }; + + constexpr C c1, c2[2]; + constexpr D d1, d2[2]; + constexpr E e1, e2[2]; + constexpr F f; + constexpr G g; + + constexpr auto mp = static_cast<int (C::*)() const>(&D::f); + + // sanity checks for fix of GH150709 (unchanged behavior) + static_assert((c1.*mp)() == 1, ""); // expected-error {{constant expression}} + static_assert((d1.*mp)() == 1, ""); + static_assert((f.*mp)() == 1, ""); + static_assert((c2[0].*mp)() == 1, ""); // expected-error {{constant expression}} + static_assert((d2[0].*mp)() == 1, ""); + + // incorrectly undiagnosed before fix of GH150709 + static_assert((e1.*mp)() == 1, ""); // expected-error {{constant expression}} + static_assert((e2[0].*mp)() == 1, ""); // expected-error {{constant expression}} + static_assert((g.*mp)() == 1, ""); // expected-error {{constant expression}} +} diff --git a/clang/test/SemaCXX/constant-expression-cxx2a.cpp b/clang/test/SemaCXX/constant-expression-cxx2a.cpp index ffb7e633c2919..b22a82c57ef06 100644 --- a/clang/test/SemaCXX/constant-expression-cxx2a.cpp +++ b/clang/test/SemaCXX/constant-expression-cxx2a.cpp @@ -1497,3 +1497,16 @@ namespace GH67317 { // expected-note {{subobject of type 'const unsigned char' is not initialized}} __builtin_bit_cast(unsigned char, *new char[3][1]); }; + +namespace GH150705 { + struct A { }; + struct B : A { }; + struct C : A { + constexpr virtual int foo() const { return 0; } + }; + constexpr auto p = &C::foo; + constexpr auto q = static_cast<int (A::*)() const>(p); + constexpr B b; + constexpr const A& a = b; + constexpr auto x = (a.*q)(); // expected-error {{constant expression}} +} _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits