Skip to content

[clang] Add builtin_get_vtable_pointer and virtual_member_address #135469

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions clang/docs/LanguageExtensions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3031,6 +3031,72 @@ 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_virtual_member_address``
------------------------------------

``__builtin_virtual_member_address`` loads the function pointer that would
be called by a virtual method.

**Syntax**:

.. code-block:: c++

__builtin_virtual_member_address(PolymorphicClass&, Member function pointer)

**Exampe of Use**

.. code-block:: c++

struct PolymorphicClass {
virtual ~PolymorphicClass();
virtual void SomeMethod();
};

PolymorphicClass anInstance;
const void* MethodAddress =
__builtin_virtual_member_address(anInstance, &PolymorphicClass::SomeMethod);

**Description**

This builtin returns the dynamic target for virtual dispatch of the requested virtual
method. If the target platform supports pointer authentication, it emits the code to
authenticates the vtable pointer and the virtual function pointer being loaded. The returned
value is an untyped pointer as it cannot reasonably be proved that any given use of the returned
function pointer is correct, so we want to discourage any attempt to do such.

``__builtin_call_with_static_chain``
------------------------------------

Expand Down
3 changes: 3 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,9 @@ Non-comprehensive list of changes in this release
- Support parsing the `cc` operand modifier and alias it to the `c` modifier (#GH127719).
- Added `__builtin_elementwise_exp10`.
- For AMDPGU targets, added `__builtin_v_cvt_off_f32_i4` that maps to the `v_cvt_off_f32_i4` instruction.
- Added `__builtin_get_vtable_pointer` to directly load the primary vtable pointer from a
polymorphic object and `__builtin_virtual_member_address` to load the real function pointer of a
virtual method.

New Compiler Flags
------------------
Expand Down
12 changes: 12 additions & 0 deletions clang/include/clang/Basic/Builtins.td
Original file line number Diff line number Diff line change
Expand Up @@ -970,6 +970,18 @@ 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*)";
}

def VirtualMemberAddress : Builtin {
let Spellings = ["__builtin_virtual_member_address"];
let Attributes = [CustomTypeChecking, NoThrow, Const];
let Prototype = "void*(void*,void*)";
}

// GCC exception builtins
def EHReturn : Builtin {
let Spellings = ["__builtin_eh_return"];
Expand Down
19 changes: 19 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -1013,6 +1013,17 @@ def err_ptrauth_indirect_goto_addrlabel_arithmetic : Error<
"%select{subtraction|addition}0 of address-of-label expressions is not "
"supported with ptrauth indirect gotos">;

def err_virtual_member_lhs_cxxrec
: Error<"first argument to __builtin_virtual_member_address must have C++ "
"class type">;
def err_virtual_member_addrof
: Error<
"second argument to __builtin_virtual_member_address must be the "
"address of a virtual C++ member function: for example '&Foo::func'">;
def err_virtual_member_inherit
: Error<"first argument to __builtin_virtual_member_address must have a "
"type deriving from class where second argument was defined">;

/// main()
// static main() is not an error in C, just in C++.
def warn_static_main : Warning<"'main' should not be declared static">,
Expand Down Expand Up @@ -12547,6 +12558,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"
Expand Down
35 changes: 35 additions & 0 deletions clang/lib/CodeGen/CGBuiltin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -5349,6 +5350,40 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
return RValue::get(Result);
}

case Builtin::BI__builtin_virtual_member_address: {
Address This = EmitLValue(E->getArg(0)).getAddress();
APValue ConstMemFun;
E->getArg(1)->isCXX11ConstantExpr(getContext(), &ConstMemFun, nullptr);
Copy link
Collaborator

Choose a reason for hiding this comment

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

So we don't care about the return value of this call? I guess b/c we verify in VirtualMemberAddress, so we are using this for a side effect?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, the isCXX11ConstantExpr gives us the target through ConstMemFun, but I'll see if there's a cleaner way to do this.

const CXXMethodDecl *CXXMethod =
cast<CXXMethodDecl>(ConstMemFun.getMemberPointerDecl());
const CGFunctionInfo &FInfo =
CGM.getTypes().arrangeCXXMethodDeclaration(CXXMethod);
llvm::FunctionType *Ty = CGM.getTypes().GetFunctionType(FInfo);
CGCallee VCallee = CGM.getCXXABI().getVirtualFunctionPointer(
*this, CXXMethod, This, Ty, E->getBeginLoc());
llvm::Value *Callee = VCallee.getFunctionPointer();
if (const CGPointerAuthInfo &Schema = VCallee.getPointerAuthInfo())
Callee = EmitPointerAuthAuth(Schema, Callee);
return RValue::get(Callee);
}

case Builtin::BI__builtin_get_vtable_pointer: {
const Expr *Target = E->getArg(0);
QualType TargetType = Target->getType();
QualType RecordType = TargetType;
if (RecordType->isPointerOrReferenceType())
RecordType = RecordType->getPointeeType();
const CXXRecordDecl *Decl = RecordType->getAsCXXRecordDecl();
assert(Decl);
Copy link
Collaborator

Choose a reason for hiding this comment

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

I note you are asserting pointers here but not in BI__builtin_virtual_member_address case, not obvious to me why.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Different authors over time, I'll unify on not asserting as null deref is the downstream outcome.

auto ThisAddress = TargetType->isPointerType()
? EmitPointerWithAlignment(Target)
: EmitLValue(Target).getAddress();
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());
Expand Down
92 changes: 92 additions & 0 deletions clang/lib/Sema/SemaChecking.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1782,6 +1782,92 @@ static ExprResult PointerAuthStringDiscriminator(Sema &S, CallExpr *Call) {
return Call;
}

static ExprResult VirtualMemberAddress(Sema &S, CallExpr *Call) {
if (S.checkArgCount(Call, 2))
return ExprError();

for (int i = 0; i < 2; ++i) {
ExprResult ArgRValue =
S.DefaultFunctionArrayLvalueConversion(Call->getArg(1));
if (ArgRValue.isInvalid())
return ExprError();
Call->setArg(1, ArgRValue.get());
}

if (Call->getArg(0)->isTypeDependent() || Call->getArg(1)->isValueDependent())
return Call;

const Expr *ThisArg = Call->getArg(0);
QualType ThisTy = ThisArg->getType();
if (ThisTy->isPointerOrReferenceType())
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll update this to make it warn on a reference parameter

ThisTy = ThisTy->getPointeeType();
if (!ThisTy->getAsCXXRecordDecl()) {
S.Diag(ThisArg->getExprLoc(), diag::err_virtual_member_lhs_cxxrec);
return ExprError();
}

const Expr *MemFunArg = Call->getArg(1);
APValue Result;
if (!MemFunArg->isCXX11ConstantExpr(S.getASTContext(), &Result, nullptr)) {
S.Diag(MemFunArg->getExprLoc(), diag::err_virtual_member_addrof);
return ExprError();
}

if (!Result.isMemberPointer() ||
!isa<CXXMethodDecl>(Result.getMemberPointerDecl())) {
S.Diag(MemFunArg->getExprLoc(), diag::err_virtual_member_addrof);
return ExprError();
}

const CXXMethodDecl *CXXMethod =
cast<CXXMethodDecl>(Result.getMemberPointerDecl());
if (!CXXMethod->isVirtual()) {
S.Diag(MemFunArg->getExprLoc(), diag::err_virtual_member_addrof);
return ExprError();
}

if (ThisTy->getAsCXXRecordDecl() != CXXMethod->getParent() &&
!S.IsDerivedFrom(Call->getBeginLoc(), ThisTy,
CXXMethod->getFunctionObjectParameterType())) {
S.Diag(ThisArg->getExprLoc(), diag::err_virtual_member_inherit);
return ExprError();
}
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();
if (SubjectType->isPointerOrReferenceType())
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will be reverting this to require a pointer arg

SubjectType = SubjectType->getPointeeType();
const CXXRecordDecl *SubjectRecord = SubjectType->getAsCXXRecordDecl();
if (!SubjectRecord) {
S.Diag(Subject->getBeginLoc(), diag::err_get_vtable_pointer_incorrect_type)
<< 0 << Subject->getType();
return ExprError();
}
if (S.RequireCompleteType(
Subject->getBeginLoc(), SubjectType,
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 << SubjectType;
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();
Expand Down Expand Up @@ -2625,6 +2711,12 @@ 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);
case Builtin::BI__builtin_virtual_member_address:
return VirtualMemberAddress(*this, TheCall);

// OpenCL v2.0, s6.13.16 - Pipe functions
case Builtin::BIread_pipe:
case Builtin::BIwrite_pipe:
Expand Down
Loading