https://github.com/ojhunt updated https://github.com/llvm/llvm-project/pull/139790
>From a933679801bde89b53584c4c65118658477db4da Mon Sep 17 00:00:00 2001 From: Oliver Hunt <oli...@apple.com> Date: Tue, 13 May 2025 13:35:55 -0700 Subject: [PATCH 1/5] [clang][PAC] Add __builtin_get_vtable_pointer With pointer authentication it becomes non-trivial to correctly load the vtable pointer of a polymorphic object. __builtin_get_vtable_pointer is a function that performs the load and performs the appropriate authentication operations if necessary. --- clang/docs/LanguageExtensions.rst | 33 ++ clang/docs/ReleaseNotes.rst | 2 + clang/include/clang/Basic/Builtins.td | 6 + .../clang/Basic/DiagnosticSemaKinds.td | 8 + clang/lib/CodeGen/CGBuiltin.cpp | 13 + clang/lib/Sema/SemaChecking.cpp | 35 ++ .../CodeGenCXX/builtin-get-vtable-pointer.cpp | 304 ++++++++++++++++++ .../SemaCXX/builtin-get-vtable-pointer.cpp | 123 +++++++ 8 files changed, 524 insertions(+) create mode 100644 clang/test/CodeGenCXX/builtin-get-vtable-pointer.cpp create mode 100644 clang/test/SemaCXX/builtin-get-vtable-pointer.cpp diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst index f56f2a640bb36..5561bfb944713 100644 --- a/clang/docs/LanguageExtensions.rst +++ b/clang/docs/LanguageExtensions.rst @@ -3062,6 +3062,39 @@ following way: Query for this feature with ``__has_builtin(__builtin_offsetof)``. +``__builtin_get_vtable_pointer`` +-------------------------------- + +``__builtin_get_vtable_pointer`` loads and authenticates the primary vtable +pointer from an instance of a polymorphic C++ class. + +**Syntax**: + +.. code-block:: c++ + + __builtin_get_vtable_pointer(PolymorphicClass*) + +**Example of Use**: + +.. code-block:: c++ + + struct PolymorphicClass { + virtual ~PolymorphicClass(); + }; + + PolymorphicClass anInstance; + const void* vtablePointer = __builtin_get_vtable_pointer(&anInstance); + +**Description**: + +The ``__builtin_get_vtable_pointer`` builtin loads the primary vtable +pointer from a polymorphic C++ type. If the target platform authenticates +vtable pointers, this builtin will perform the authentication and produce +the underlying raw pointer. The object being queried must be polymorphic, +and so must also be a complete type. + +Query for this feature with ``__has_builtin(__builtin_get_vtable_pointer)``. + ``__builtin_call_with_static_chain`` ------------------------------------ diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index bc13d02e2d20b..342771ca83608 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -291,6 +291,8 @@ Non-comprehensive list of changes in this release different than before. - Fixed a crash when a VLA with an invalid size expression was used within a ``sizeof`` or ``typeof`` expression. (#GH138444) +- Added `__builtin_get_vtable_pointer` to directly load the primary vtable pointer from a + polymorphic object. New Compiler Flags ------------------ diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td index 11b1e247237a7..52c0515d4e1b5 100644 --- a/clang/include/clang/Basic/Builtins.td +++ b/clang/include/clang/Basic/Builtins.td @@ -970,6 +970,12 @@ def IsWithinLifetime : LangBuiltin<"CXX_LANG"> { let Prototype = "bool(void*)"; } +def GetVtablePointer : LangBuiltin<"CXX_LANG"> { + let Spellings = ["__builtin_get_vtable_pointer"]; + let Attributes = [CustomTypeChecking, NoThrow, Const]; + let Prototype = "void*(void*)"; +} + // GCC exception builtins def EHReturn : Builtin { let Spellings = ["__builtin_eh_return"]; diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 3efe9593b8633..a54eb924ad5bf 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -12729,6 +12729,14 @@ def err_bit_cast_non_trivially_copyable : Error< def err_bit_cast_type_size_mismatch : Error< "size of '__builtin_bit_cast' source type %0 does not match destination type %1 (%2 vs %3 bytes)">; +def err_get_vtable_pointer_incorrect_type + : Error<"__builtin_get_vtable_pointer requires an argument of%select{| " + "polymorphic}0 class pointer type" + ", but %1 %select{was provided|has no virtual methods}0">; +def err_get_vtable_pointer_requires_complete_type + : Error<"__builtin_get_vtable_pointer requires an argument with a complete " + "type, but %0 is incomplete">; + // SYCL-specific diagnostics def warn_sycl_kernel_num_of_template_params : Warning< "'sycl_kernel' attribute only applies to a function template with at least" diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp index 45e0f69c46902..019c70726c267 100644 --- a/clang/lib/CodeGen/CGBuiltin.cpp +++ b/clang/lib/CodeGen/CGBuiltin.cpp @@ -17,6 +17,7 @@ #include "CGDebugInfo.h" #include "CGObjCRuntime.h" #include "CGOpenCLRuntime.h" +#include "CGPointerAuthInfo.h" #include "CGRecordLayout.h" #include "CGValue.h" #include "CodeGenFunction.h" @@ -5365,6 +5366,18 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID, return RValue::get(Result); } + case Builtin::BI__builtin_get_vtable_pointer: { + const Expr *Target = E->getArg(0); + QualType TargetType = Target->getType(); + const CXXRecordDecl *Decl = TargetType->getPointeeCXXRecordDecl(); + assert(Decl); + auto ThisAddress = EmitPointerWithAlignment(Target); + assert(ThisAddress.isValid()); + llvm::Value *VTablePointer = + GetVTablePtr(ThisAddress, Int8PtrTy, Decl, VTableAuthMode::MustTrap); + return RValue::get(VTablePointer); + } + case Builtin::BI__exception_code: case Builtin::BI_exception_code: return RValue::get(EmitSEHExceptionCode()); diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index 55121b90fa167..b3b33bc728da9 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -1825,6 +1825,37 @@ static ExprResult PointerAuthStringDiscriminator(Sema &S, CallExpr *Call) { return Call; } +static ExprResult GetVTablePointer(Sema &S, CallExpr *Call) { + if (S.checkArgCount(Call, 1)) + return ExprError(); + ExprResult ThisArg = S.DefaultFunctionArrayLvalueConversion(Call->getArg(0)); + if (ThisArg.isInvalid()) + return ExprError(); + Call->setArg(0, ThisArg.get()); + const Expr *Subject = Call->getArg(0); + QualType SubjectType = Subject->getType(); + const CXXRecordDecl *SubjectRecord = SubjectType->getPointeeCXXRecordDecl(); + if (!SubjectType->isPointerType() || !SubjectRecord) { + S.Diag(Subject->getBeginLoc(), diag::err_get_vtable_pointer_incorrect_type) + << 0 << SubjectType; + return ExprError(); + } + if (S.RequireCompleteType( + Subject->getBeginLoc(), SubjectType->getPointeeType(), + diag::err_get_vtable_pointer_requires_complete_type)) { + return ExprError(); + } + + if (!SubjectRecord->isPolymorphic()) { + S.Diag(Subject->getBeginLoc(), diag::err_get_vtable_pointer_incorrect_type) + << 1 << SubjectRecord; + return ExprError(); + } + QualType ReturnType = S.Context.getPointerType(S.Context.VoidTy.withConst()); + Call->setType(ReturnType); + return Call; +} + static ExprResult BuiltinLaunder(Sema &S, CallExpr *TheCall) { if (S.checkArgCount(TheCall, 1)) return ExprError(); @@ -2719,6 +2750,10 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID, return PointerAuthAuthAndResign(*this, TheCall); case Builtin::BI__builtin_ptrauth_string_discriminator: return PointerAuthStringDiscriminator(*this, TheCall); + + case Builtin::BI__builtin_get_vtable_pointer: + return GetVTablePointer(*this, TheCall); + // OpenCL v2.0, s6.13.16 - Pipe functions case Builtin::BIread_pipe: case Builtin::BIwrite_pipe: diff --git a/clang/test/CodeGenCXX/builtin-get-vtable-pointer.cpp b/clang/test/CodeGenCXX/builtin-get-vtable-pointer.cpp new file mode 100644 index 0000000000000..5577e01a09f6f --- /dev/null +++ b/clang/test/CodeGenCXX/builtin-get-vtable-pointer.cpp @@ -0,0 +1,304 @@ +// RUN: %clang_cc1 %s -x c++ -std=c++11 -triple x86_64-apple-darwin10 -emit-llvm -O1 -disable-llvm-passes -no-enable-noundef-analysis -o - | FileCheck --check-prefix=CHECK-NOAUTH %s +// RUN: %clang_cc1 %s -x c++ -std=c++11 -triple arm64-apple-ios -fptrauth-calls -fptrauth-vtable-pointer-type-discrimination -emit-llvm -O1 -disable-llvm-passes -no-enable-noundef-analysis -o - | FileCheck --check-prefix=CHECK-TYPEAUTH %s +// RUN: %clang_cc1 %s -x c++ -std=c++11 -triple arm64-apple-ios -fptrauth-calls -fptrauth-vtable-pointer-address-discrimination -emit-llvm -O1 -disable-llvm-passes -no-enable-noundef-analysis -o - | FileCheck --check-prefix=CHECK-ADDRESSAUTH %s +// RUN: %clang_cc1 %s -x c++ -std=c++11 -triple arm64-apple-ios -fptrauth-calls -fptrauth-vtable-pointer-type-discrimination -fptrauth-vtable-pointer-address-discrimination -emit-llvm -O1 -disable-llvm-passes -no-enable-noundef-analysis -o - | FileCheck --check-prefix=CHECK-BOTHAUTH %s +// FIXME: Assume load should not require -fstrict-vtable-pointers + +namespace test1 { +struct A { + A(); + virtual void bar(); +}; + +struct B : A { + B(); + virtual void foo(); +}; + +struct Z : A {}; +struct C : Z, B { + C(); + virtual void wibble(); +}; + +struct D : virtual A { +}; + +struct E : D, B { +}; + +const void *a(A *o) { + // CHECK-NOAUTH: define ptr @_ZN5test11aEPNS_1AE(ptr %o) #0 { + // CHECK-TYPEAUTH: define ptr @_ZN5test11aEPNS_1AE(ptr %o) #0 { + return __builtin_get_vtable_pointer(o); + // CHECK-NOAUTH: %vtable = load ptr, ptr %0, align 8 + // CHECK-TYPEAUTH: %0 = load ptr, ptr %o.addr, align 8 + // CHECK-TYPEAUTH: %vtable = load ptr, ptr %0, align 8 + // CHECK-TYPEAUTH: %1 = ptrtoint ptr %vtable to i64 + // CHECK-TYPEAUTH: %2 = call i64 @llvm.ptrauth.auth(i64 %1, i32 2, i64 48388) + // CHECK-TYPEAUTH: %3 = inttoptr i64 %2 to ptr + // CHECK-TYPEAUTH: %4 = load volatile i8, ptr %3, align 8 + // CHECK-ADDRESSAUTH: %2 = ptrtoint ptr %vtable to i64 + // CHECK-ADDRESSAUTH: %3 = call i64 @llvm.ptrauth.auth(i64 %2, i32 2, i64 %1) + // CHECK-ADDRESSAUTH: %4 = inttoptr i64 %3 to ptr + // CHECK-ADDRESSAUTH: %5 = load volatile i8, ptr %4, align 8 + // CHECK-BOTHAUTH: [[T1:%.*]] = ptrtoint ptr %0 to i64 + // CHECK-BOTHAUTH: [[T2:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[T1]], i64 48388) + // CHECK-BOTHAUTH: [[T3:%.*]] = ptrtoint ptr %vtable to i64 + // CHECK-BOTHAUTH: [[T4:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[T3]], i32 2, i64 [[T2]]) + // CHECK-BOTHAUTH: [[T5:%.*]] = inttoptr i64 [[T4]] to ptr + // CHECK-BOTHAUTH: [[T6:%.*]] = load volatile i8, ptr [[T5]], align 8 +} + +const void *b(B *o) { + // CHECK-TYPEAUTH: define ptr @_ZN5test11bEPNS_1BE(ptr %o) #0 { + // CHECK-NOAUTH: define ptr @_ZN5test11bEPNS_1BE(ptr %o) #0 { + return __builtin_get_vtable_pointer(o); + // CHECK-NOAUTH: %vtable = load ptr, ptr %0, align 8 + // CHECK-TYPEAUTH: %vtable = load ptr, ptr %0, align 8 + // CHECK-TYPEAUTH: %1 = ptrtoint ptr %vtable to i64 + // CHECK-TYPEAUTH: %2 = call i64 @llvm.ptrauth.auth(i64 %1, i32 2, i64 48388) + // CHECK-TYPEAUTH: %3 = inttoptr i64 %2 to ptr + // CHECK-TYPEAUTH: %4 = load volatile i8, ptr %3, align 8 + // CHECK-ADDRESSAUTH: %2 = ptrtoint ptr %vtable to i64 + // CHECK-ADDRESSAUTH: %3 = call i64 @llvm.ptrauth.auth(i64 %2, i32 2, i64 %1) + // CHECK-ADDRESSAUTH: %4 = inttoptr i64 %3 to ptr + // CHECK-ADDRESSAUTH: %5 = load volatile i8, ptr %4, align 8 + // CHECK-BOTHAUTH: [[T2:%.*]] = call i64 @llvm.ptrauth.blend(i64 %1, i64 48388) + // CHECK-BOTHAUTH: [[T3:%.*]] = ptrtoint ptr %vtable to i64 + // CHECK-BOTHAUTH: [[T4:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[T3]], i32 2, i64 [[T2]]) + // CHECK-BOTHAUTH: [[T5:%.*]] = inttoptr i64 [[T4]] to ptr + // CHECK-BOTHAUTH: [[T6:%.*]] = load volatile i8, ptr [[T5]], align 8 +} + +const void *b_as_A(B *o) { + // CHECK-NOAUTH: define ptr @_ZN5test16b_as_AEPNS_1BE(ptr %o) #0 { + return __builtin_get_vtable_pointer((A *)o); + // CHECK-NOAUTH: %vtable = load ptr, ptr %0, align 8 + // CHECK-TYPEAUTH: %vtable = load ptr, ptr %0, align 8 + // CHECK-TYPEAUTH: %1 = ptrtoint ptr %vtable to i64 + // CHECK-TYPEAUTH: %2 = call i64 @llvm.ptrauth.auth(i64 %1, i32 2, i64 48388) + // CHECK-TYPEAUTH: %3 = inttoptr i64 %2 to ptr + // CHECK-TYPEAUTH: %4 = load volatile i8, ptr %3, align 8 + // CHECK-ADDRESSAUTH: %2 = ptrtoint ptr %vtable to i64 + // CHECK-ADDRESSAUTH: %3 = call i64 @llvm.ptrauth.auth(i64 %2, i32 2, i64 %1) + // CHECK-ADDRESSAUTH: %4 = inttoptr i64 %3 to ptr + // CHECK-ADDRESSAUTH: %5 = load volatile i8, ptr %4, align 8 + // CHECK-BOTHAUTH: [[T2:%.*]] = call i64 @llvm.ptrauth.blend(i64 %1, i64 48388) + // CHECK-BOTHAUTH: [[T3:%.*]] = ptrtoint ptr %vtable to i64 + // CHECK-BOTHAUTH: [[T4:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[T3]], i32 2, i64 [[T2]]) + // CHECK-BOTHAUTH: [[T5:%.*]] = inttoptr i64 [[T4]] to ptr + // CHECK-BOTHAUTH: [[T6:%.*]] = load volatile i8, ptr [[T5]], align 8 +} + +const void *c(C *o) { + // CHECK-NOAUTH: define ptr @_ZN5test11cEPNS_1CE(ptr %o) #0 { + return __builtin_get_vtable_pointer(o); + // CHECK-NOAUTH: %vtable = load ptr, ptr %0, align 8 + // CHECK-TYPEAUTH: %vtable = load ptr, ptr %0, align 8 + // CHECK-TYPEAUTH: %1 = ptrtoint ptr %vtable to i64 + // CHECK-TYPEAUTH: %2 = call i64 @llvm.ptrauth.auth(i64 %1, i32 2, i64 48388) + // CHECK-TYPEAUTH: %3 = inttoptr i64 %2 to ptr + // CHECK-TYPEAUTH: %4 = load volatile i8, ptr %3, align 8 + // CHECK-ADDRESSAUTH: %2 = ptrtoint ptr %vtable to i64 + // CHECK-ADDRESSAUTH: %3 = call i64 @llvm.ptrauth.auth(i64 %2, i32 2, i64 %1) + // CHECK-ADDRESSAUTH: %4 = inttoptr i64 %3 to ptr + // CHECK-ADDRESSAUTH: %5 = load volatile i8, ptr %4, align 8 + // CHECK-BOTHAUTH: [[T2:%.*]] = call i64 @llvm.ptrauth.blend(i64 %1, i64 48388) + // CHECK-BOTHAUTH: [[T3:%.*]] = ptrtoint ptr %vtable to i64 + // CHECK-BOTHAUTH: [[T4:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[T3]], i32 2, i64 [[T2]]) + // CHECK-BOTHAUTH: [[T5:%.*]] = inttoptr i64 [[T4]] to ptr + // CHECK-BOTHAUTH: [[T6:%.*]] = load volatile i8, ptr [[T5]], align 8 +} + +const void *c_as_Z(C *o) { + // CHECK-NOAUTH: define ptr @_ZN5test16c_as_ZEPNS_1CE(ptr %o) #0 { + return __builtin_get_vtable_pointer((Z *)o); + // CHECK-NOAUTH: %0 = load ptr, ptr %o.addr, align 8 + // CHECK-NOAUTH: %vtable = load ptr, ptr %0, align 8 + // CHECK-TYPEAUTH: %vtable = load ptr, ptr %0, align 8 + // CHECK-TYPEAUTH: %1 = ptrtoint ptr %vtable to i64 + // CHECK-TYPEAUTH: %2 = call i64 @llvm.ptrauth.auth(i64 %1, i32 2, i64 48388) + // CHECK-TYPEAUTH: %3 = inttoptr i64 %2 to ptr + // CHECK-TYPEAUTH: %4 = load volatile i8, ptr %3, align 8 + // CHECK-ADDRESSAUTH: %2 = ptrtoint ptr %vtable to i64 + // CHECK-ADDRESSAUTH: %3 = call i64 @llvm.ptrauth.auth(i64 %2, i32 2, i64 %1) + // CHECK-ADDRESSAUTH: %4 = inttoptr i64 %3 to ptr + // CHECK-ADDRESSAUTH: %5 = load volatile i8, ptr %4, align 8 + // CHECK-BOTHAUTH: [[T2:%.*]] = call i64 @llvm.ptrauth.blend(i64 %1, i64 48388) + // CHECK-BOTHAUTH: [[T3:%.*]] = ptrtoint ptr %vtable to i64 + // CHECK-BOTHAUTH: [[T4:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[T3]], i32 2, i64 [[T2]]) + // CHECK-BOTHAUTH: [[T5:%.*]] = inttoptr i64 [[T4]] to ptr + // CHECK-BOTHAUTH: [[T6:%.*]] = load volatile i8, ptr [[T5]], align 8 +} + +const void *c_as_B(C *o) { + // CHECK-NOAUTH: define ptr @_ZN5test16c_as_BEPNS_1CE(ptr %o) #0 { + return __builtin_get_vtable_pointer((B *)o); + // CHECK-NOAUTH: %add.ptr = getelementptr inbounds i8, ptr %0, i64 8 + // CHECK-NOAUTH: br label %cast.end + // CHECK-NOAUTH: %cast.result = phi ptr [ %add.ptr, %cast.notnull ], [ null, %entry ] + // CHECK-NOAUTH: %vtable = load ptr, ptr %cast.result, align 8 + // CHECK-TYPEAUTH: %cast.result = phi ptr [ %add.ptr, %cast.notnull ], [ null, %entry ] + // CHECK-TYPEAUTH: %vtable = load ptr, ptr %cast.result, align 8 + // CHECK-TYPEAUTH: %2 = ptrtoint ptr %vtable to i64 + // CHECK-TYPEAUTH: %3 = call i64 @llvm.ptrauth.auth(i64 %2, i32 2, i64 48388) + // CHECK-TYPEAUTH: %4 = inttoptr i64 %3 to ptr + // CHECK-TYPEAUTH: %5 = load volatile i8, ptr %4, align 8 + // CHECK-ADDRESSAUTH: %2 = ptrtoint ptr %cast.result to i64 + // CHECK-ADDRESSAUTH: %3 = ptrtoint ptr %vtable to i64 + // CHECK-ADDRESSAUTH: %4 = call i64 @llvm.ptrauth.auth(i64 %3, i32 2, i64 %2) + // CHECK-ADDRESSAUTH: %5 = inttoptr i64 %4 to ptr + // CHECK-ADDRESSAUTH: %6 = load volatile i8, ptr %5, align 8 + // CHECK-BOTHAUTH: [[T2:%.*]] = call i64 @llvm.ptrauth.blend(i64 %2, i64 48388) + // CHECK-BOTHAUTH: [[T3:%.*]] = ptrtoint ptr %vtable to i64 + // CHECK-BOTHAUTH: [[T4:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[T3]], i32 2, i64 [[T2]]) + // CHECK-BOTHAUTH: [[T5:%.*]] = inttoptr i64 [[T4]] to ptr + // CHECK-BOTHAUTH: [[T6:%.*]] = load volatile i8, ptr [[T5]], align 8 +} + +const void *d(D *o) { + // CHECK-NOAUTH: define ptr @_ZN5test11dEPNS_1DE(ptr %o) #0 { + return __builtin_get_vtable_pointer(o); + // CHECK-NOAUTH: %vtable = load ptr, ptr %0, align 8 + // CHECK-TYPEAUTH: %vtable = load ptr, ptr %0, align 8 + // CHECK-TYPEAUTH: %1 = ptrtoint ptr %vtable to i64 + // CHECK-TYPEAUTH: %2 = call i64 @llvm.ptrauth.auth(i64 %1, i32 2, i64 48388) + // CHECK-TYPEAUTH: %3 = inttoptr i64 %2 to ptr + // CHECK-TYPEAUTH: %4 = load volatile i8, ptr %3, align 8 + // CHECK-ADDRESSAUTH: %1 = ptrtoint ptr %0 to i64 + // CHECK-ADDRESSAUTH: %2 = ptrtoint ptr %vtable to i64 + // CHECK-ADDRESSAUTH: %3 = call i64 @llvm.ptrauth.auth(i64 %2, i32 2, i64 %1) + // CHECK-ADDRESSAUTH: %4 = inttoptr i64 %3 to ptr + // CHECK-ADDRESSAUTH: %5 = load volatile i8, ptr %4, align 8 + // CHECK-BOTHAUTH: [[T1:%.*]] = ptrtoint ptr %0 to i64 + // CHECK-BOTHAUTH: [[T2:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[T1]], i64 48388) + // CHECK-BOTHAUTH: [[T3:%.*]] = ptrtoint ptr %vtable to i64 + // CHECK-BOTHAUTH: [[T4:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[T3]], i32 2, i64 [[T2]]) + // CHECK-BOTHAUTH: [[T5:%.*]] = inttoptr i64 [[T4]] to ptr + // CHECK-BOTHAUTH: [[T6:%.*]] = load volatile i8, ptr [[T5]], align 8 +} + +const void *d_as_A(D *o) { + // CHECK-NOAUTH: define ptr @_ZN5test16d_as_AEPNS_1DE(ptr %o) #0 { + return __builtin_get_vtable_pointer((A *)o); + // CHECK-NOAUTH: %vtable = load ptr, ptr %0, align 8 + // CHECK-NOAUTH: %vbase.offset.ptr = getelementptr i8, ptr %vtable, i64 -32 + // CHECK-NOAUTH: %vbase.offset = load i64, ptr %vbase.offset.ptr, align 8 + // CHECK-NOAUTH: %add.ptr = getelementptr inbounds i8, ptr %0, i64 %vbase.offset + // CHECK-NOAUTH: %cast.result = phi ptr [ %add.ptr, %cast.notnull ], [ null, %entry ] + // CHECK-NOAUTH: %vtable1 = load ptr, ptr %cast.result, align 8 + // CHECK-TYPEAUTH: %vtable1 = load ptr, ptr %cast.result, align 8 + // CHECK-TYPEAUTH: %5 = ptrtoint ptr %vtable1 to i64 + // CHECK-TYPEAUTH: %6 = call i64 @llvm.ptrauth.auth(i64 %5, i32 2, i64 48388) + // CHECK-TYPEAUTH: %7 = inttoptr i64 %6 to ptr + // CHECK-TYPEAUTH: %8 = load volatile i8, ptr %7, align 8 + // CHECK-ADDRESSAUTH: %6 = ptrtoint ptr %cast.result to i64 + // CHECK-ADDRESSAUTH: %7 = ptrtoint ptr %vtable1 to i64 + // CHECK-ADDRESSAUTH: %8 = call i64 @llvm.ptrauth.auth(i64 %7, i32 2, i64 %6) + // CHECK-ADDRESSAUTH: %9 = inttoptr i64 %8 to ptr + // CHECK-ADDRESSAUTH: %10 = load volatile i8, ptr %9, align 8 + // CHECK-BOTHAUTH: [[T1:%.*]] = ptrtoint ptr %cast.result to i64 + // CHECK-BOTHAUTH: [[T2:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[T1]], i64 48388) + // CHECK-BOTHAUTH: [[T3:%.*]] = ptrtoint ptr %vtable1 to i64 + // CHECK-BOTHAUTH: [[T4:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[T3]], i32 2, i64 [[T2]]) + // CHECK-BOTHAUTH: [[T5:%.*]] = inttoptr i64 [[T4]] to ptr + // CHECK-BOTHAUTH: [[T6:%.*]] = load volatile i8, ptr [[T5]], align 8 +} + +const void *e(E *o) { + // CHECK-NOAUTH: define ptr @_ZN5test11eEPNS_1EE(ptr %o) #0 { + return __builtin_get_vtable_pointer(o); + // CHECK-NOAUTH: %vtable = load ptr, ptr %0, align 8 + // CHECK-TYPEAUTH: %vtable = load ptr, ptr %0, align 8 + // CHECK-TYPEAUTH: %1 = ptrtoint ptr %vtable to i64 + // CHECK-TYPEAUTH: %2 = call i64 @llvm.ptrauth.auth(i64 %1, i32 2, i64 48388) + // CHECK-TYPEAUTH: %3 = inttoptr i64 %2 to ptr + // CHECK-TYPEAUTH: %4 = load volatile i8, ptr %3, align 8 + // CHECK-ADDRESSAUTH: [[T1:%.*]] = ptrtoint ptr %0 to i64 + // CHECK-ADDRESSAUTH: [[T2:%.*]] = ptrtoint ptr %vtable to i64 + // CHECK-ADDRESSAUTH: [[T3:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[T2]], i32 2, i64 [[T1]]) + // CHECK-ADDRESSAUTH: [[T4:%.*]] = inttoptr i64 [[T3]] to ptr + // CHECK-ADDRESSAUTH: [[T5:%.*]] = load volatile i8, ptr [[T4]], align 8 + // CHECK-BOTHAUTH: [[T1:%.*]] = ptrtoint ptr %0 to i64 + // CHECK-BOTHAUTH: [[T2:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[T1]], i64 48388) + // CHECK-BOTHAUTH: [[T3:%.*]] = ptrtoint ptr %vtable to i64 + // CHECK-BOTHAUTH: [[T4:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[T3]], i32 2, i64 [[T2]]) + // CHECK-BOTHAUTH: [[T5:%.*]] = inttoptr i64 [[T4]] to ptr + // CHECK-BOTHAUTH: [[T6:%.*]] = load volatile i8, ptr [[T5]], align 8 +} + +const void *e_as_B(E *o) { + // CHECK-NOAUTH: define ptr @_ZN5test16e_as_BEPNS_1EE(ptr %o) #0 { + return __builtin_get_vtable_pointer((B *)o); + // CHECK-NOAUTH: %add.ptr = getelementptr inbounds i8, ptr %0, i64 8 + // CHECK-NOAUTH: %cast.result = phi ptr [ %add.ptr, %cast.notnull ], [ null, %entry ] + // CHECK-NOAUTH: %vtable = load ptr, ptr %cast.result, align 8 + // CHECK-TYPEAUTH: %vtable = load ptr, ptr %cast.result, align 8 + // CHECK-TYPEAUTH: %2 = ptrtoint ptr %vtable to i64 + // CHECK-TYPEAUTH: %3 = call i64 @llvm.ptrauth.auth(i64 %2, i32 2, i64 48388) + // CHECK-TYPEAUTH: %4 = inttoptr i64 %3 to ptr + // CHECK-TYPEAUTH: %5 = load volatile i8, ptr %4, align 8 + // CHECK-ADDRESSAUTH: [[T1:%.*]] = ptrtoint ptr %cast.result to i64 + // CHECK-ADDRESSAUTH: [[T2:%.*]] = ptrtoint ptr %vtable to i64 + // CHECK-ADDRESSAUTH: [[T3:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[T2]], i32 2, i64 [[T1]]) + // CHECK-ADDRESSAUTH: [[T4:%.*]] = inttoptr i64 [[T3]] to ptr + // CHECK-ADDRESSAUTH: [[T5:%.*]] = load volatile i8, ptr [[T4]], align 8 + // CHECK-BOTHAUTH: [[T1:%.*]] = ptrtoint ptr %cast.result to i64 + // CHECK-BOTHAUTH: [[T2:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[T1]], i64 48388) + // CHECK-BOTHAUTH: [[T3:%.*]] = ptrtoint ptr %vtable to i64 + // CHECK-BOTHAUTH: [[T4:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[T3]], i32 2, i64 [[T2]]) + // CHECK-BOTHAUTH: [[T5:%.*]] = inttoptr i64 [[T4]] to ptr + // CHECK-BOTHAUTH: [[T6:%.*]] = load volatile i8, ptr [[T5]], align 8 +} + +const void *e_as_D(E *o) { + // CHECK-NOAUTH: define ptr @_ZN5test16e_as_DEPNS_1EE(ptr %o) #0 { + return __builtin_get_vtable_pointer((D *)o); + // CHECK-NOAUTH: %vtable = load ptr, ptr %0, align 8 + // CHECK-TYPEAUTH: %vtable = load ptr, ptr %0, align 8 + // CHECK-TYPEAUTH: %1 = ptrtoint ptr %vtable to i64 + // CHECK-TYPEAUTH: %2 = call i64 @llvm.ptrauth.auth(i64 %1, i32 2, i64 48388) + // CHECK-TYPEAUTH: %3 = inttoptr i64 %2 to ptr + // CHECK-TYPEAUTH: %4 = load volatile i8, ptr %3, align 8 + // CHECK-ADDRESSAUTH: [[T1:%.*]] = ptrtoint ptr %0 to i64 + // CHECK-ADDRESSAUTH: [[T2:%.*]] = ptrtoint ptr %vtable to i64 + // CHECK-ADDRESSAUTH: [[T3:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[T2]], i32 2, i64 [[T1]]) + // CHECK-ADDRESSAUTH: [[T4:%.*]] = inttoptr i64 [[T3]] to ptr + // CHECK-ADDRESSAUTH: [[T5:%.*]] = load volatile i8, ptr [[T4]], align 8 + // CHECK-BOTHAUTH: [[T1:%.*]] = ptrtoint ptr %0 to i64 + // CHECK-BOTHAUTH: [[T2:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[T1]], i64 48388) + // CHECK-BOTHAUTH: [[T3:%.*]] = ptrtoint ptr %vtable to i64 + // CHECK-BOTHAUTH: [[T4:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[T3]], i32 2, i64 [[T2]]) + // CHECK-BOTHAUTH: [[T5:%.*]] = inttoptr i64 [[T4]] to ptr + // CHECK-BOTHAUTH: [[T6:%.*]] = load volatile i8, ptr [[T5]], align 8 +} + +void test() { + A aInstance; + B bInstance; + C cInstance; + D dInstance; + E eInstance; + a(&aInstance); + a(&bInstance); + a((B *)&cInstance); + a(&dInstance); + a((D *)&eInstance); + a((B *)&eInstance); + b(&bInstance); + b(&cInstance); + b(&eInstance); + b_as_A(&bInstance); + c(&cInstance); + c_as_Z(&cInstance); + c_as_B(&cInstance); + d(&dInstance); + d(&eInstance); + d_as_A(&dInstance); + d_as_A(&eInstance); + e(&eInstance); + e_as_B(&eInstance); + e_as_D(&eInstance); +} +} // namespace test1 diff --git a/clang/test/SemaCXX/builtin-get-vtable-pointer.cpp b/clang/test/SemaCXX/builtin-get-vtable-pointer.cpp new file mode 100644 index 0000000000000..273f9c3b4c667 --- /dev/null +++ b/clang/test/SemaCXX/builtin-get-vtable-pointer.cpp @@ -0,0 +1,123 @@ +// RUN: %clang_cc1 -fsyntax-only -verify -std=c++2a %s + +namespace basic { +struct ForwardDeclaration; // expected-note{{forward declaration of 'basic::ForwardDeclaration'}} + // expected-note@-1{{forward declaration of 'basic::ForwardDeclaration'}} +struct NonPolymorphic {}; +struct Polymorphic { + virtual ~Polymorphic(); +}; + +template <typename T> +struct Foo { + virtual ~Foo(); +}; + +template <> +struct Foo<int> { +}; + +template <typename T> +struct Bar { + using SubType = typename T::SubType; + SubType *ty() const; +}; + +struct Thing1 { + using SubType = Thing1; +}; + +struct Thing2 { + using SubType = Thing2; + virtual ~Thing2(); +}; + +struct Thing3 { + using SubType = int; +}; + +struct Thing4 { + using SubType = Polymorphic; +}; + +struct Thing5 { + using SubType = NonPolymorphic; +}; + +struct Thing6 { + using SubType = ForwardDeclaration; +}; + +template <typename T> +const void *getThing(const Bar<T> *b = nullptr) { + return __builtin_get_vtable_pointer(b->ty()); // expected-error{{__builtin_get_vtable_pointer requires an argument of class pointer type, but 'SubType *' (aka 'int *') was provided}} + // expected-error@-1{{__builtin_get_vtable_pointer requires an argument of polymorphic class pointer type, but 'Thing1' has no virtual methods}} + // expected-error@-2{{__builtin_get_vtable_pointer requires an argument of polymorphic class pointer type, but 'NonPolymorphic' has no virtual methods}} + // expected-error@-3{{__builtin_get_vtable_pointer requires an argument with a complete type, but 'SubType' (aka 'basic::ForwardDeclaration') is incomplete}} +} +template <typename> +struct IncompleteTemplate; // expected-note{{template is declared here}} +template <typename> +struct MonomorphicTemplate { +}; +template <typename> +struct PolymorphicTemplate { + virtual ~PolymorphicTemplate(); +}; + +void test_function(int); // expected-note{{possible target for call}} + // expected-note@-1{{possible target for call}} +void test_function(double); // expected-note{{possible target for call}} + // expected-note@-1{{possible target for call}} + +void getVTablePointer() { + ForwardDeclaration *fd = nullptr; + NonPolymorphic np; + Polymorphic p; + NonPolymorphic np_array[1]; + Polymorphic p_array[1]; + __builtin_get_vtable_pointer(0); // expected-error{{__builtin_get_vtable_pointer requires an argument of class pointer type, but 'int' was provided}} + __builtin_get_vtable_pointer(nullptr); // expected-error{{__builtin_get_vtable_pointer requires an argument of class pointer type, but 'std::nullptr_t' was provided}} + __builtin_get_vtable_pointer(0.5); // expected-error{{__builtin_get_vtable_pointer requires an argument of class pointer type, but 'double' was provided}} + __builtin_get_vtable_pointer(fd); // expected-error{{__builtin_get_vtable_pointer requires an argument with a complete type, but 'ForwardDeclaration' is incomplete}} + __builtin_get_vtable_pointer(np); // expected-error{{__builtin_get_vtable_pointer requires an argument of class pointer type, but 'NonPolymorphic' was provided}} + __builtin_get_vtable_pointer(&np); // expected-error{{__builtin_get_vtable_pointer requires an argument of polymorphic class pointer type, but 'NonPolymorphic' has no virtual methods}} + __builtin_get_vtable_pointer(p); // expected-error{{__builtin_get_vtable_pointer requires an argument of class pointer type, but 'Polymorphic' was provided}} + __builtin_get_vtable_pointer(&p); // expected-warning{{ignoring return value of function declared with const attribute}} + __builtin_get_vtable_pointer(p_array); // expected-warning{{ignoring return value of function declared with const attribute}} + __builtin_get_vtable_pointer(&p_array); // expected-error{{__builtin_get_vtable_pointer requires an argument of class pointer type, but 'Polymorphic (*)[1]' was provided}} + __builtin_get_vtable_pointer(np_array); // expected-error{{__builtin_get_vtable_pointer requires an argument of polymorphic class pointer type, but 'NonPolymorphic' has no virtual methods}} + __builtin_get_vtable_pointer(&np_array); // expected-error{{__builtin_get_vtable_pointer requires an argument of class pointer type, but 'NonPolymorphic (*)[1]' was provided}} + __builtin_get_vtable_pointer(test_function); // expected-error{{reference to overloaded function could not be resolved; did you mean to call it?}} + // expected-error@-1{{reference to overloaded function could not be resolved; did you mean to call it?}} + Foo<double> Food; + Foo<int> Fooi; + __builtin_get_vtable_pointer(Food); // expected-error{{__builtin_get_vtable_pointer requires an argument of class pointer type, but 'Foo<double>' was provided}} + (void)__builtin_get_vtable_pointer(&Food); + __builtin_get_vtable_pointer(Fooi); // expected-error{{__builtin_get_vtable_pointer requires an argument of class pointer type, but 'Foo<int>' was provided}} + __builtin_get_vtable_pointer(&Fooi); // expected-error{{__builtin_get_vtable_pointer requires an argument of polymorphic class pointer type, but 'Foo<int>' has no virtual methods}} + + IncompleteTemplate<bool> *incomplete = nullptr; + (void)__builtin_get_vtable_pointer(incomplete); // expected-error{{implicit instantiation of undefined template 'basic::IncompleteTemplate<bool>'}} + PolymorphicTemplate<bool> *ptb = nullptr; + MonomorphicTemplate<bool> *mtb = nullptr; + PolymorphicTemplate<int> pti; + MonomorphicTemplate<int> mti; + PolymorphicTemplate<float> ptf; + MonomorphicTemplate<float> mtf; + (void)__builtin_get_vtable_pointer(ptb); + __builtin_get_vtable_pointer(mtb); // expected-error{{__builtin_get_vtable_pointer requires an argument of polymorphic class pointer type, but 'MonomorphicTemplate<bool>' has no virtual methods}} + __builtin_get_vtable_pointer(pti); // expected-error{{__builtin_get_vtable_pointer requires an argument of class pointer type, but 'PolymorphicTemplate<int>' was provided}} + __builtin_get_vtable_pointer(mti); // expected-error{{__builtin_get_vtable_pointer requires an argument of class pointer type, but 'MonomorphicTemplate<int>' was provided}} + (void)__builtin_get_vtable_pointer(&ptf); + __builtin_get_vtable_pointer(&mtf); // expected-error{{__builtin_get_vtable_pointer requires an argument of polymorphic class pointer type, but 'MonomorphicTemplate<float>' has no virtual methods}} + + getThing<Thing1>(); // expected-note{{in instantiation of function template specialization 'basic::getThing<basic::Thing1>' requested here}} + getThing<Thing2>(); + getThing<Thing3>(); // expected-note{{in instantiation of function template specialization 'basic::getThing<basic::Thing3>' requested here}} + getThing<Thing4>(); + getThing<Thing5>(); // expected-note{{in instantiation of function template specialization 'basic::getThing<basic::Thing5>' requested here}} + getThing<Thing6>(); // expected-note{{in instantiation of function template specialization 'basic::getThing<basic::Thing6>' requested here}} +} + +} // namespace basic >From 676775165937acaabcd222a2341610777f5dc519 Mon Sep 17 00:00:00 2001 From: Oliver Hunt <oli...@apple.com> Date: Tue, 3 Jun 2025 01:54:56 -0700 Subject: [PATCH 2/5] Update style, and increase test coverage --- clang/lib/Sema/SemaChecking.cpp | 27 ++++--- .../CodeGenCXX/builtin-get-vtable-pointer.cpp | 74 ++++++++++++++++++- 2 files changed, 85 insertions(+), 16 deletions(-) diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index 31595bd122422..dad4f9a1c45a7 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -1829,27 +1829,30 @@ static ExprResult PointerAuthStringDiscriminator(Sema &S, CallExpr *Call) { static ExprResult GetVTablePointer(Sema &S, CallExpr *Call) { if (S.checkArgCount(Call, 1)) return ExprError(); - ExprResult ThisArg = S.DefaultFunctionArrayLvalueConversion(Call->getArg(0)); - if (ThisArg.isInvalid()) + Expr *ThisArg = Call->getArg(0); + ExprResult ThisValue = S.DefaultFunctionArrayLvalueConversion(ThisArg); + if (ThisValue.isInvalid()) return ExprError(); - Call->setArg(0, ThisArg.get()); - const Expr *Subject = Call->getArg(0); - QualType SubjectType = Subject->getType(); - const CXXRecordDecl *SubjectRecord = SubjectType->getPointeeCXXRecordDecl(); - if (!SubjectType->isPointerType() || !SubjectRecord) { - S.Diag(Subject->getBeginLoc(), diag::err_get_vtable_pointer_incorrect_type) - << 0 << SubjectType; + Call->setArg(0, ThisValue.get()); + QualType ThisType = ThisArg->getType(); + if (ThisType->canDecayToPointerType() && ThisType->isArrayType()) + ThisType = S.Context.getDecayedType(ThisType); + + const CXXRecordDecl *SubjectRecord = ThisType->getPointeeCXXRecordDecl(); + if (!SubjectRecord) { + S.Diag(ThisArg->getBeginLoc(), diag::err_get_vtable_pointer_incorrect_type) + << /*isPolymorphic=*/0 << ThisType; return ExprError(); } if (S.RequireCompleteType( - Subject->getBeginLoc(), SubjectType->getPointeeType(), + ThisArg->getBeginLoc(), ThisType->getPointeeType(), diag::err_get_vtable_pointer_requires_complete_type)) { return ExprError(); } if (!SubjectRecord->isPolymorphic()) { - S.Diag(Subject->getBeginLoc(), diag::err_get_vtable_pointer_incorrect_type) - << 1 << SubjectRecord; + S.Diag(ThisArg->getBeginLoc(), diag::err_get_vtable_pointer_incorrect_type) + << /*isPolymorphic=*/ 1 << SubjectRecord; return ExprError(); } QualType ReturnType = S.Context.getPointerType(S.Context.VoidTy.withConst()); diff --git a/clang/test/CodeGenCXX/builtin-get-vtable-pointer.cpp b/clang/test/CodeGenCXX/builtin-get-vtable-pointer.cpp index 5577e01a09f6f..604fb6c5585ac 100644 --- a/clang/test/CodeGenCXX/builtin-get-vtable-pointer.cpp +++ b/clang/test/CodeGenCXX/builtin-get-vtable-pointer.cpp @@ -1,7 +1,7 @@ -// RUN: %clang_cc1 %s -x c++ -std=c++11 -triple x86_64-apple-darwin10 -emit-llvm -O1 -disable-llvm-passes -no-enable-noundef-analysis -o - | FileCheck --check-prefix=CHECK-NOAUTH %s -// RUN: %clang_cc1 %s -x c++ -std=c++11 -triple arm64-apple-ios -fptrauth-calls -fptrauth-vtable-pointer-type-discrimination -emit-llvm -O1 -disable-llvm-passes -no-enable-noundef-analysis -o - | FileCheck --check-prefix=CHECK-TYPEAUTH %s -// RUN: %clang_cc1 %s -x c++ -std=c++11 -triple arm64-apple-ios -fptrauth-calls -fptrauth-vtable-pointer-address-discrimination -emit-llvm -O1 -disable-llvm-passes -no-enable-noundef-analysis -o - | FileCheck --check-prefix=CHECK-ADDRESSAUTH %s -// RUN: %clang_cc1 %s -x c++ -std=c++11 -triple arm64-apple-ios -fptrauth-calls -fptrauth-vtable-pointer-type-discrimination -fptrauth-vtable-pointer-address-discrimination -emit-llvm -O1 -disable-llvm-passes -no-enable-noundef-analysis -o - | FileCheck --check-prefix=CHECK-BOTHAUTH %s +// RUN: %clang_cc1 %s -x c++ -std=c++23 -triple x86_64-apple-darwin10 -emit-llvm -O1 -disable-llvm-passes -no-enable-noundef-analysis -o - | FileCheck --check-prefix=CHECK-NOAUTH %s +// RUN: %clang_cc1 %s -x c++ -std=c++23 -triple arm64-apple-ios -fptrauth-calls -fptrauth-vtable-pointer-type-discrimination -emit-llvm -O1 -disable-llvm-passes -no-enable-noundef-analysis -o - | FileCheck --check-prefix=CHECK-TYPEAUTH %s +// RUN: %clang_cc1 %s -x c++ -std=c++23 -triple arm64-apple-ios -fptrauth-calls -fptrauth-vtable-pointer-address-discrimination -emit-llvm -O1 -disable-llvm-passes -no-enable-noundef-analysis -o - | FileCheck --check-prefix=CHECK-ADDRESSAUTH %s +// RUN: %clang_cc1 %s -x c++ -std=c++23 -triple arm64-apple-ios -fptrauth-calls -fptrauth-vtable-pointer-type-discrimination -fptrauth-vtable-pointer-address-discrimination -emit-llvm -O1 -disable-llvm-passes -no-enable-noundef-analysis -o - | FileCheck --check-prefix=CHECK-BOTHAUTH %s // FIXME: Assume load should not require -fstrict-vtable-pointers namespace test1 { @@ -27,7 +27,16 @@ struct D : virtual A { struct E : D, B { }; +template <class A, class B> struct same_type { + static const bool value = false; +}; + +template <class A> struct same_type<A, A> { + static const bool value = true; +}; + const void *a(A *o) { + static_assert(same_type<decltype(__builtin_get_vtable_pointer(o)), const void*>::value); // CHECK-NOAUTH: define ptr @_ZN5test11aEPNS_1AE(ptr %o) #0 { // CHECK-TYPEAUTH: define ptr @_ZN5test11aEPNS_1AE(ptr %o) #0 { return __builtin_get_vtable_pointer(o); @@ -51,6 +60,7 @@ const void *a(A *o) { } const void *b(B *o) { + static_assert(same_type<decltype(__builtin_get_vtable_pointer(o)), const void*>::value); // CHECK-TYPEAUTH: define ptr @_ZN5test11bEPNS_1BE(ptr %o) #0 { // CHECK-NOAUTH: define ptr @_ZN5test11bEPNS_1BE(ptr %o) #0 { return __builtin_get_vtable_pointer(o); @@ -72,6 +82,7 @@ const void *b(B *o) { } const void *b_as_A(B *o) { + static_assert(same_type<decltype(__builtin_get_vtable_pointer(o)), const void*>::value); // CHECK-NOAUTH: define ptr @_ZN5test16b_as_AEPNS_1BE(ptr %o) #0 { return __builtin_get_vtable_pointer((A *)o); // CHECK-NOAUTH: %vtable = load ptr, ptr %0, align 8 @@ -92,6 +103,7 @@ const void *b_as_A(B *o) { } const void *c(C *o) { + static_assert(same_type<decltype(__builtin_get_vtable_pointer(o)), const void*>::value); // CHECK-NOAUTH: define ptr @_ZN5test11cEPNS_1CE(ptr %o) #0 { return __builtin_get_vtable_pointer(o); // CHECK-NOAUTH: %vtable = load ptr, ptr %0, align 8 @@ -112,6 +124,7 @@ const void *c(C *o) { } const void *c_as_Z(C *o) { + static_assert(same_type<decltype(__builtin_get_vtable_pointer(o)), const void*>::value); // CHECK-NOAUTH: define ptr @_ZN5test16c_as_ZEPNS_1CE(ptr %o) #0 { return __builtin_get_vtable_pointer((Z *)o); // CHECK-NOAUTH: %0 = load ptr, ptr %o.addr, align 8 @@ -133,6 +146,7 @@ const void *c_as_Z(C *o) { } const void *c_as_B(C *o) { + static_assert(same_type<decltype(__builtin_get_vtable_pointer(o)), const void*>::value); // CHECK-NOAUTH: define ptr @_ZN5test16c_as_BEPNS_1CE(ptr %o) #0 { return __builtin_get_vtable_pointer((B *)o); // CHECK-NOAUTH: %add.ptr = getelementptr inbounds i8, ptr %0, i64 8 @@ -158,6 +172,7 @@ const void *c_as_B(C *o) { } const void *d(D *o) { + static_assert(same_type<decltype(__builtin_get_vtable_pointer(o)), const void*>::value); // CHECK-NOAUTH: define ptr @_ZN5test11dEPNS_1DE(ptr %o) #0 { return __builtin_get_vtable_pointer(o); // CHECK-NOAUTH: %vtable = load ptr, ptr %0, align 8 @@ -180,6 +195,7 @@ const void *d(D *o) { } const void *d_as_A(D *o) { + static_assert(same_type<decltype(__builtin_get_vtable_pointer(o)), const void*>::value); // CHECK-NOAUTH: define ptr @_ZN5test16d_as_AEPNS_1DE(ptr %o) #0 { return __builtin_get_vtable_pointer((A *)o); // CHECK-NOAUTH: %vtable = load ptr, ptr %0, align 8 @@ -207,6 +223,7 @@ const void *d_as_A(D *o) { } const void *e(E *o) { + static_assert(same_type<decltype(__builtin_get_vtable_pointer(o)), const void*>::value); // CHECK-NOAUTH: define ptr @_ZN5test11eEPNS_1EE(ptr %o) #0 { return __builtin_get_vtable_pointer(o); // CHECK-NOAUTH: %vtable = load ptr, ptr %0, align 8 @@ -229,6 +246,7 @@ const void *e(E *o) { } const void *e_as_B(E *o) { + static_assert(same_type<decltype(__builtin_get_vtable_pointer(o)), const void*>::value); // CHECK-NOAUTH: define ptr @_ZN5test16e_as_BEPNS_1EE(ptr %o) #0 { return __builtin_get_vtable_pointer((B *)o); // CHECK-NOAUTH: %add.ptr = getelementptr inbounds i8, ptr %0, i64 8 @@ -253,6 +271,7 @@ const void *e_as_B(E *o) { } const void *e_as_D(E *o) { + static_assert(same_type<decltype(__builtin_get_vtable_pointer(o)), const void*>::value); // CHECK-NOAUTH: define ptr @_ZN5test16e_as_DEPNS_1EE(ptr %o) #0 { return __builtin_get_vtable_pointer((D *)o); // CHECK-NOAUTH: %vtable = load ptr, ptr %0, align 8 @@ -274,12 +293,58 @@ const void *e_as_D(E *o) { // CHECK-BOTHAUTH: [[T6:%.*]] = load volatile i8, ptr [[T5]], align 8 } +extern "C" const void *aArrayParameter(A aArray[]) { + static_assert(same_type<decltype(__builtin_get_vtable_pointer(aArray)), const void*>::value); + // CHECK-NOAUTH: [[THIS_OBJ:%.*]] = load ptr, ptr %aArray.addr + // CHECK-NOAUTH: %vtable = load ptr, ptr [[THIS_OBJ]] + // CHECK-TYPEAUTH: [[THIS_OBJ:%.*]] = load ptr, ptr %aArray.addr + // CHECK-TYPEAUTH: %vtable = load ptr, ptr [[THIS_OBJ]] + // CHECK-TYPEAUTH: [[VTABLEI:%.*]] = ptrtoint ptr %vtable to i64 + // CHECK-TYPEAUTH: [[AUTHENTICATED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI]], i32 2, i64 48388) + // CHECK-ADDRESSAUTH: [[VTABLE_ADDR:%.*]] = load ptr, ptr %aArray.addr, align 8, !tbaa !2 + // CHECK-ADDRESSAUTH: %vtable = load ptr, ptr %0, align 8, !tbaa !7 + // CHECK-ADDRESSAUTH: [[VTABLE_ADDRI:%.*]] = ptrtoint ptr [[VTABLE_ADDR]] to i64 + // CHECK-ADDRESSAUTH: [[VTABLEI:%.*]] = ptrtoint ptr %vtable to i64 + // CHECK-ADDRESSAUTH: [[AUTHENTICATED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI]], i32 2, i64 [[VTABLE_ADDRI]]) + // CHECK-BOTHAUTH: [[VTABLE_ADDR:%.*]] = load ptr, ptr %aArray.addr, align 8, !tbaa !2 + // CHECK-BOTHAUTH: %vtable = load ptr, ptr [[VTABLE_ADDR]], align 8, !tbaa !7 + // CHECK-BOTHAUTH: [[VTABLE_ADDRI:%.*]] = ptrtoint ptr [[VTABLE_ADDR]] to i64 + // CHECK-BOTHAUTH: [[VTABLE_DISC:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[VTABLE_ADDRI]], i64 48388) + // CHECK-BOTHAUTH: [[VTABLE_PTR:%.*]] = ptrtoint ptr %vtable to i64 + // CHECK-BOTHAUTH: [[AUTHENTICATED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLE_PTR]], i32 2, i64 [[VTABLE_DISC]]) + return __builtin_get_vtable_pointer(aArray); +} + +extern "C" const void *aArrayLocal() { + A array[] = { A() }; + static_assert(same_type<decltype(__builtin_get_vtable_pointer(array)), const void*>::value); + // CHECK-NOAUTH: [[THIS_OBJ:%.*]] = getelementptr inbounds [1 x %"struct.test1::A"], ptr %array + // CHECK-NOAUTH: %vtable = load ptr, ptr %arraydecay + // CHECK-TYPEAUTH: %arraydecay = getelementptr inbounds [1 x %"struct.test1::A"] + // CHECK-TYPEAUTH: %vtable = load ptr, ptr %arraydecay + // CHECK-TYPEAUTH: [[VTABLEI:%.*]] = ptrtoint ptr %vtable to i64 + // CHECK-TYPEAUTH: [[AUTHENTICATED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI]], i32 2, i64 48388) + // CHECK-ADDRESSAUTH: %arraydecay = getelementptr inbounds [1 x %"struct.test1::A"], ptr %array, i64 0, i64 0 + // CHECK-ADDRESSAUTH: %vtable = load ptr, ptr %arraydecay, align 8, !tbaa !7 + // CHECK-ADDRESSAUTH: [[VTABLE_ADDRI:%.*]] = ptrtoint ptr %arraydecay to i64 + // CHECK-ADDRESSAUTH: [[VTABLEI:%.*]] = ptrtoint ptr %vtable to i64 + // CHECK-ADDRESSAUTH: [[AUTHENTICATED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI]], i32 2, i64 [[VTABLE_ADDRI]]) + // CHECK-BOTHAUTH: %arraydecay = getelementptr inbounds [1 x %"struct.test1::A"], ptr %array, i64 0, i64 0 + // CHECK-BOTHAUTH: %vtable = load ptr, ptr %arraydecay, align 8, !tbaa !7 + // CHECK-BOTHAUTH: [[VTABLE_ADDRI:%.*]] = ptrtoint ptr %arraydecay to i64 + // CHECK-BOTHAUTH: [[VTABLE_DISC:%.*]] = call i64 @llvm.ptrauth.blend(i64 %0, i64 48388) + // CHECK-BOTHAUTH: [[VTABLEI:%.*]] = ptrtoint ptr %vtable to i64 + // CHECK-BOTHAUTH: [[AUTHENTICATED:%.*]] = call i64 @llvm.ptrauth.auth(i64 [[VTABLEI]], i32 2, i64 [[VTABLE_DISC]]) + return __builtin_get_vtable_pointer(array); +} + void test() { A aInstance; B bInstance; C cInstance; D dInstance; E eInstance; + E eArray[] = { E() }; a(&aInstance); a(&bInstance); a((B *)&cInstance); @@ -300,5 +365,6 @@ void test() { e(&eInstance); e_as_B(&eInstance); e_as_D(&eInstance); + (void)__builtin_get_vtable_pointer(eArray); } } // namespace test1 >From ba361e7044db1e104583df163d144fba2eb9cef9 Mon Sep 17 00:00:00 2001 From: Oliver Hunt <oli...@apple.com> Date: Tue, 3 Jun 2025 02:02:03 -0700 Subject: [PATCH 3/5] sigh formatting --- clang/lib/Sema/SemaChecking.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index dad4f9a1c45a7..2b6842aaaebaa 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -1852,7 +1852,7 @@ static ExprResult GetVTablePointer(Sema &S, CallExpr *Call) { if (!SubjectRecord->isPolymorphic()) { S.Diag(ThisArg->getBeginLoc(), diag::err_get_vtable_pointer_incorrect_type) - << /*isPolymorphic=*/ 1 << SubjectRecord; + << /*isPolymorphic=*/1 << SubjectRecord; return ExprError(); } QualType ReturnType = S.Context.getPointerType(S.Context.VoidTy.withConst()); >From 8c0150cc44fd20cdc7e49d7bf889343b00533359 Mon Sep 17 00:00:00 2001 From: Oliver Hunt <oli...@apple.com> Date: Tue, 3 Jun 2025 18:36:00 -0700 Subject: [PATCH 4/5] Adding pointer authentication reference in docs --- clang/docs/LanguageExtensions.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst index fe4be422d6dc7..73544826809c3 100644 --- a/clang/docs/LanguageExtensions.rst +++ b/clang/docs/LanguageExtensions.rst @@ -3078,7 +3078,9 @@ Query for this feature with ``__has_builtin(__builtin_offsetof)``. -------------------------------- ``__builtin_get_vtable_pointer`` loads and authenticates the primary vtable -pointer from an instance of a polymorphic C++ class. +pointer from an instance of a polymorphic C++ class. This builtin is needed +for directly loading the vtable pointer when on platforms using +:doc:`PointerAuthentication`. **Syntax**: >From f9cc2880aae9938f4111d384842abe95f4a4a41f Mon Sep 17 00:00:00 2001 From: Oliver Hunt <oli...@apple.com> Date: Tue, 3 Jun 2025 22:00:41 -0700 Subject: [PATCH 5/5] Rename This.* to FirstArg.* --- clang/lib/Sema/SemaChecking.cpp | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index ca4b5cbab651b..9dd2c6cd3bf54 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -1832,30 +1832,30 @@ static ExprResult PointerAuthStringDiscriminator(Sema &S, CallExpr *Call) { static ExprResult GetVTablePointer(Sema &S, CallExpr *Call) { if (S.checkArgCount(Call, 1)) return ExprError(); - Expr *ThisArg = Call->getArg(0); - ExprResult ThisValue = S.DefaultFunctionArrayLvalueConversion(ThisArg); - if (ThisValue.isInvalid()) + Expr *FirstArg = Call->getArg(0); + ExprResult FirstValue = S.DefaultFunctionArrayLvalueConversion(FirstArg); + if (FirstValue.isInvalid()) return ExprError(); - Call->setArg(0, ThisValue.get()); - QualType ThisType = ThisArg->getType(); - if (ThisType->canDecayToPointerType() && ThisType->isArrayType()) - ThisType = S.Context.getDecayedType(ThisType); - - const CXXRecordDecl *SubjectRecord = ThisType->getPointeeCXXRecordDecl(); - if (!SubjectRecord) { - S.Diag(ThisArg->getBeginLoc(), diag::err_get_vtable_pointer_incorrect_type) - << /*isPolymorphic=*/0 << ThisType; + Call->setArg(0, FirstValue.get()); + QualType FirstArgType = FirstArg->getType(); + if (FirstArgType->canDecayToPointerType() && FirstArgType->isArrayType()) + FirstArgType = S.Context.getDecayedType(FirstArgType); + + const CXXRecordDecl *FirstArgRecord = FirstArgType->getPointeeCXXRecordDecl(); + if (!FirstArgRecord) { + S.Diag(FirstArg->getBeginLoc(), diag::err_get_vtable_pointer_incorrect_type) + << /*isPolymorphic=*/0 << FirstArgType; return ExprError(); } if (S.RequireCompleteType( - ThisArg->getBeginLoc(), ThisType->getPointeeType(), + FirstArg->getBeginLoc(), FirstArgType->getPointeeType(), diag::err_get_vtable_pointer_requires_complete_type)) { return ExprError(); } - if (!SubjectRecord->isPolymorphic()) { - S.Diag(ThisArg->getBeginLoc(), diag::err_get_vtable_pointer_incorrect_type) - << /*isPolymorphic=*/1 << SubjectRecord; + if (!FirstArgRecord->isPolymorphic()) { + S.Diag(FirstArg->getBeginLoc(), diag::err_get_vtable_pointer_incorrect_type) + << /*isPolymorphic=*/1 << FirstArgRecord; return ExprError(); } QualType ReturnType = S.Context.getPointerType(S.Context.VoidTy.withConst()); _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits