Skip to content

Conversation

@ojhunt
Copy link
Contributor

@ojhunt ojhunt commented May 13, 2025

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.

@ojhunt ojhunt requested a review from cor3ntin May 13, 2025 20:38
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:codegen IR generation bugs: mangling, exceptions, etc. labels May 13, 2025
@llvmbot
Copy link
Member

llvmbot commented May 13, 2025

@llvm/pr-subscribers-clang

@llvm/pr-subscribers-clang-codegen

Author: Oliver Hunt (ojhunt)

Changes

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.


Patch is 30.42 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/139790.diff

8 Files Affected:

  • (modified) clang/docs/LanguageExtensions.rst (+33)
  • (modified) clang/docs/ReleaseNotes.rst (+2)
  • (modified) clang/include/clang/Basic/Builtins.td (+6)
  • (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+8)
  • (modified) clang/lib/CodeGen/CGBuiltin.cpp (+13)
  • (modified) clang/lib/Sema/SemaChecking.cpp (+37)
  • (added) clang/test/CodeGenCXX/builtin-get-vtable-pointer.cpp (+304)
  • (added) clang/test/SemaCXX/builtin-get-vtable-pointer.cpp (+123)
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..39c4fc3fb08e4 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -1825,6 +1825,39 @@ 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 +2752,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-N...
[truncated]

@github-actions
Copy link

github-actions bot commented May 13, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

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.
@ojhunt ojhunt force-pushed the users/ojhunt/builtin-get-vtable-pointer branch from 871714b to a933679 Compare May 13, 2025 20:44
Copy link
Contributor

@cor3ntin cor3ntin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add tests for const types, and tests that check
__is_same_as(decltype(__builtin_get_vtable_pointer(foo)), const void*) ?

LGTM otherwise

if (ThisArg.isInvalid())
return ExprError();
Call->setArg(0, ThisArg.get());
const Expr *Subject = Call->getArg(0);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const Expr *Subject = Call->getArg(0);
const Expr *Subject = ThisArg.get();

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FirstArg would be a better name, or jusst Arg

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

goddammit forgot this change

const CXXRecordDecl *SubjectRecord = SubjectType->getPointeeCXXRecordDecl();
if (!SubjectType->isPointerType() || !SubjectRecord) {
S.Diag(Subject->getBeginLoc(), diag::err_get_vtable_pointer_incorrect_type)
<< 0 << SubjectType;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<< 0 << SubjectType;
<< /*Class* /0 << SubjectType;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cor3ntin for both of these (/Class/ and /Polymorpic/) should that be using =*/? and also I wonder if we can make clang format aware of the /*ParameterName=*/ idiom for parameters of bool and (maybe) integer types? (recognizing that in the context of operator << in diags this is a different problem)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't use = there (or not consistently). The important bit is to know what's happening.
You could look into %enum_select as a better solution

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or we could make every diag encode the type of the diag as a function type, and have things fail at compile time when possible. This would be super great as it would provide a great stress test for clangs template code :D :D :D


if (!SubjectRecord->isPolymorphic()) {
S.Diag(Subject->getBeginLoc(), diag::err_get_vtable_pointer_incorrect_type)
<< 1 << SubjectRecord;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<< 1 << SubjectRecord;
<< /* Polymorphic*/1 << SubjectRecord;

@cor3ntin cor3ntin requested a review from AaronBallman June 3, 2025 12:30
Copy link
Contributor

@cor3ntin cor3ntin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@ojhunt
Copy link
Contributor Author

ojhunt commented Jun 4, 2025

waiting on bots to go green

@ojhunt ojhunt merged commit 93314bd into main Jun 4, 2025
12 checks passed
@ojhunt ojhunt deleted the users/ojhunt/builtin-get-vtable-pointer branch June 4, 2025 07:21
@github-project-automation github-project-automation bot moved this from In Progress to Done in Pointer Authentication Tasks Jun 4, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clang:codegen IR generation bugs: mangling, exceptions, etc. clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

5 participants