Skip to content

Commit 1607a76

Browse files
committed
[clang] Add builtin_get_vtable_pointer and virtual_member_address
These are a pair of builtins to support particularly weird edge case operations while correctly handling the non-trivial implicit pointer authentication schemas applied to polymorphic members. Co-authored-by: Tim Northover
1 parent ca5b3a0 commit 1607a76

File tree

10 files changed

+829
-0
lines changed

10 files changed

+829
-0
lines changed

clang/docs/LanguageExtensions.rst

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3031,6 +3031,72 @@ following way:
30313031
30323032
Query for this feature with ``__has_builtin(__builtin_offsetof)``.
30333033
3034+
``__builtin_get_vtable_pointer``
3035+
--------------------------------
3036+
3037+
``__builtin_get_vtable_pointer`` loads and authenticates the primary vtable
3038+
pointer from an instance of a polymorphic C++ class.
3039+
3040+
**Syntax**:
3041+
3042+
.. code-block:: c++
3043+
3044+
__builtin_get_vtable_pointer(PolymorphicClass*)
3045+
3046+
**Example of Use**:
3047+
3048+
.. code-block:: c++
3049+
3050+
struct PolymorphicClass {
3051+
virtual ~PolymorphicClass();
3052+
};
3053+
3054+
PolymorphicClass anInstance;
3055+
const void* vtablePointer = __builtin_get_vtable_pointer(&anInstance);
3056+
3057+
**Description**:
3058+
3059+
The ``__builtin_get_vtable_pointer`` builtin loads the primary vtable
3060+
pointer from a polymorphic C++ type. If the target platform authenticates
3061+
vtable pointers, this builtin will perform the authentication and produce
3062+
the underlying raw pointer. The object being queried must be polymorphic,
3063+
and so must also be a complete type.
3064+
3065+
Query for this feature with ``__has_builtin(__builtin_get_vtable_pointer)``.
3066+
3067+
``__builtin_virtual_member_address``
3068+
------------------------------------
3069+
3070+
``__builtin_virtual_member_address`` loads the function pointer that would
3071+
be called by a virtual method.
3072+
3073+
**Syntax**:
3074+
3075+
.. code-block:: c++
3076+
3077+
__builtin_virtual_member_address(PolymorphicClass&, Member function pointer)
3078+
3079+
**Exampe of Use**
3080+
3081+
.. code-block:: c++
3082+
3083+
struct PolymorphicClass {
3084+
virtual ~PolymorphicClass();
3085+
virtual void SomeMethod();
3086+
};
3087+
3088+
PolymorphicClass anInstance;
3089+
const void* MethodAddress =
3090+
__builtin_virtual_member_address(anInstance, &PolymorphicClass::SomeMethod);
3091+
3092+
**Description**
3093+
3094+
This builtin returns the dynamic target for virtual dispatch of the requested virtual
3095+
method. If the target platform supports pointer authentication, it emits the code to
3096+
authenticates the vtable pointer and the virtual function pointer being loaded. The returned
3097+
value is an untyped pointer as it cannot reasonably be proved that any given use of the returned
3098+
function pointer is correct, so we want to discourage any attempt to do such.
3099+
30343100
``__builtin_call_with_static_chain``
30353101
------------------------------------
30363102

clang/docs/ReleaseNotes.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,9 @@ Non-comprehensive list of changes in this release
191191
- Support parsing the `cc` operand modifier and alias it to the `c` modifier (#GH127719).
192192
- Added `__builtin_elementwise_exp10`.
193193
- For AMDPGU targets, added `__builtin_v_cvt_off_f32_i4` that maps to the `v_cvt_off_f32_i4` instruction.
194+
- Added `__builtin_get_vtable_pointer` to directly load the primary vtable pointer from a
195+
polymorphic object and `__builtin_virtual_member_address` to load the real function pointer of a
196+
virtual method.
194197

195198
New Compiler Flags
196199
------------------

clang/include/clang/Basic/Builtins.td

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -970,6 +970,18 @@ def IsWithinLifetime : LangBuiltin<"CXX_LANG"> {
970970
let Prototype = "bool(void*)";
971971
}
972972

973+
def GetVtablePointer : LangBuiltin<"CXX_LANG"> {
974+
let Spellings = ["__builtin_get_vtable_pointer"];
975+
let Attributes = [CustomTypeChecking, NoThrow, Const];
976+
let Prototype = "void*(void*)";
977+
}
978+
979+
def VirtualMemberAddress : Builtin {
980+
let Spellings = ["__builtin_virtual_member_address"];
981+
let Attributes = [CustomTypeChecking, NoThrow, Const];
982+
let Prototype = "void*(void*,void*)";
983+
}
984+
973985
// GCC exception builtins
974986
def EHReturn : Builtin {
975987
let Spellings = ["__builtin_eh_return"];

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1013,6 +1013,17 @@ def err_ptrauth_indirect_goto_addrlabel_arithmetic : Error<
10131013
"%select{subtraction|addition}0 of address-of-label expressions is not "
10141014
"supported with ptrauth indirect gotos">;
10151015

1016+
def err_virtual_member_lhs_cxxrec
1017+
: Error<"first argument to __builtin_virtual_member_address must have C++ "
1018+
"class type">;
1019+
def err_virtual_member_addrof
1020+
: Error<
1021+
"second argument to __builtin_virtual_member_address must be the "
1022+
"address of a virtual C++ member function: for example '&Foo::func'">;
1023+
def err_virtual_member_inherit
1024+
: Error<"first argument to __builtin_virtual_member_address must have a "
1025+
"type deriving from class where second argument was defined">;
1026+
10161027
/// main()
10171028
// static main() is not an error in C, just in C++.
10181029
def warn_static_main : Warning<"'main' should not be declared static">,
@@ -12547,6 +12558,14 @@ def err_bit_cast_non_trivially_copyable : Error<
1254712558
def err_bit_cast_type_size_mismatch : Error<
1254812559
"size of '__builtin_bit_cast' source type %0 does not match destination type %1 (%2 vs %3 bytes)">;
1254912560

12561+
def err_get_vtable_pointer_incorrect_type
12562+
: Error<"__builtin_get_vtable_pointer requires an argument of%select{| "
12563+
"polymorphic}0 class pointer type"
12564+
", but %1 %select{was provided|has no virtual methods}0">;
12565+
def err_get_vtable_pointer_requires_complete_type
12566+
: Error<"__builtin_get_vtable_pointer requires an argument with a complete "
12567+
"type, but %0 is incomplete">;
12568+
1255012569
// SYCL-specific diagnostics
1255112570
def warn_sycl_kernel_num_of_template_params : Warning<
1255212571
"'sycl_kernel' attribute only applies to a function template with at least"

clang/lib/CodeGen/CGBuiltin.cpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "CGDebugInfo.h"
1818
#include "CGObjCRuntime.h"
1919
#include "CGOpenCLRuntime.h"
20+
#include "CGPointerAuthInfo.h"
2021
#include "CGRecordLayout.h"
2122
#include "CGValue.h"
2223
#include "CodeGenFunction.h"
@@ -5349,6 +5350,40 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
53495350
return RValue::get(Result);
53505351
}
53515352

5353+
case Builtin::BI__builtin_virtual_member_address: {
5354+
Address This = EmitLValue(E->getArg(0)).getAddress();
5355+
APValue ConstMemFun;
5356+
E->getArg(1)->isCXX11ConstantExpr(getContext(), &ConstMemFun, nullptr);
5357+
const CXXMethodDecl *CXXMethod =
5358+
cast<CXXMethodDecl>(ConstMemFun.getMemberPointerDecl());
5359+
const CGFunctionInfo &FInfo =
5360+
CGM.getTypes().arrangeCXXMethodDeclaration(CXXMethod);
5361+
llvm::FunctionType *Ty = CGM.getTypes().GetFunctionType(FInfo);
5362+
CGCallee VCallee = CGM.getCXXABI().getVirtualFunctionPointer(
5363+
*this, CXXMethod, This, Ty, E->getBeginLoc());
5364+
llvm::Value *Callee = VCallee.getFunctionPointer();
5365+
if (const CGPointerAuthInfo &Schema = VCallee.getPointerAuthInfo())
5366+
Callee = EmitPointerAuthAuth(Schema, Callee);
5367+
return RValue::get(Callee);
5368+
}
5369+
5370+
case Builtin::BI__builtin_get_vtable_pointer: {
5371+
const Expr *Target = E->getArg(0);
5372+
QualType TargetType = Target->getType();
5373+
QualType RecordType = TargetType;
5374+
if (RecordType->isPointerOrReferenceType())
5375+
RecordType = RecordType->getPointeeType();
5376+
const CXXRecordDecl *Decl = RecordType->getAsCXXRecordDecl();
5377+
assert(Decl);
5378+
auto ThisAddress = TargetType->isPointerType()
5379+
? EmitPointerWithAlignment(Target)
5380+
: EmitLValue(Target).getAddress();
5381+
assert(ThisAddress.isValid());
5382+
llvm::Value *VTablePointer =
5383+
GetVTablePtr(ThisAddress, Int8PtrTy, Decl, VTableAuthMode::MustTrap);
5384+
return RValue::get(VTablePointer);
5385+
}
5386+
53525387
case Builtin::BI__exception_code:
53535388
case Builtin::BI_exception_code:
53545389
return RValue::get(EmitSEHExceptionCode());

clang/lib/Sema/SemaChecking.cpp

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1782,6 +1782,92 @@ static ExprResult PointerAuthStringDiscriminator(Sema &S, CallExpr *Call) {
17821782
return Call;
17831783
}
17841784

1785+
static ExprResult VirtualMemberAddress(Sema &S, CallExpr *Call) {
1786+
if (S.checkArgCount(Call, 2))
1787+
return ExprError();
1788+
1789+
for (int i = 0; i < 2; ++i) {
1790+
ExprResult ArgRValue =
1791+
S.DefaultFunctionArrayLvalueConversion(Call->getArg(1));
1792+
if (ArgRValue.isInvalid())
1793+
return ExprError();
1794+
Call->setArg(1, ArgRValue.get());
1795+
}
1796+
1797+
if (Call->getArg(0)->isTypeDependent() || Call->getArg(1)->isValueDependent())
1798+
return Call;
1799+
1800+
const Expr *ThisArg = Call->getArg(0);
1801+
QualType ThisTy = ThisArg->getType();
1802+
if (ThisTy->isPointerOrReferenceType())
1803+
ThisTy = ThisTy->getPointeeType();
1804+
if (!ThisTy->getAsCXXRecordDecl()) {
1805+
S.Diag(ThisArg->getExprLoc(), diag::err_virtual_member_lhs_cxxrec);
1806+
return ExprError();
1807+
}
1808+
1809+
const Expr *MemFunArg = Call->getArg(1);
1810+
APValue Result;
1811+
if (!MemFunArg->isCXX11ConstantExpr(S.getASTContext(), &Result, nullptr)) {
1812+
S.Diag(MemFunArg->getExprLoc(), diag::err_virtual_member_addrof);
1813+
return ExprError();
1814+
}
1815+
1816+
if (!Result.isMemberPointer() ||
1817+
!isa<CXXMethodDecl>(Result.getMemberPointerDecl())) {
1818+
S.Diag(MemFunArg->getExprLoc(), diag::err_virtual_member_addrof);
1819+
return ExprError();
1820+
}
1821+
1822+
const CXXMethodDecl *CXXMethod =
1823+
cast<CXXMethodDecl>(Result.getMemberPointerDecl());
1824+
if (!CXXMethod->isVirtual()) {
1825+
S.Diag(MemFunArg->getExprLoc(), diag::err_virtual_member_addrof);
1826+
return ExprError();
1827+
}
1828+
1829+
if (ThisTy->getAsCXXRecordDecl() != CXXMethod->getParent() &&
1830+
!S.IsDerivedFrom(Call->getBeginLoc(), ThisTy,
1831+
CXXMethod->getFunctionObjectParameterType())) {
1832+
S.Diag(ThisArg->getExprLoc(), diag::err_virtual_member_inherit);
1833+
return ExprError();
1834+
}
1835+
return Call;
1836+
}
1837+
1838+
static ExprResult GetVTablePointer(Sema &S, CallExpr *Call) {
1839+
if (S.checkArgCount(Call, 1))
1840+
return ExprError();
1841+
ExprResult ThisArg = S.DefaultFunctionArrayLvalueConversion(Call->getArg(0));
1842+
if (ThisArg.isInvalid())
1843+
return ExprError();
1844+
Call->setArg(0, ThisArg.get());
1845+
const Expr *Subject = Call->getArg(0);
1846+
QualType SubjectType = Subject->getType();
1847+
if (SubjectType->isPointerOrReferenceType())
1848+
SubjectType = SubjectType->getPointeeType();
1849+
const CXXRecordDecl *SubjectRecord = SubjectType->getAsCXXRecordDecl();
1850+
if (!SubjectRecord) {
1851+
S.Diag(Subject->getBeginLoc(), diag::err_get_vtable_pointer_incorrect_type)
1852+
<< 0 << Subject->getType();
1853+
return ExprError();
1854+
}
1855+
if (S.RequireCompleteType(
1856+
Subject->getBeginLoc(), SubjectType,
1857+
diag::err_get_vtable_pointer_requires_complete_type)) {
1858+
return ExprError();
1859+
}
1860+
1861+
if (!SubjectRecord->isPolymorphic()) {
1862+
S.Diag(Subject->getBeginLoc(), diag::err_get_vtable_pointer_incorrect_type)
1863+
<< 1 << SubjectType;
1864+
return ExprError();
1865+
}
1866+
QualType ReturnType = S.Context.getPointerType(S.Context.VoidTy.withConst());
1867+
Call->setType(ReturnType);
1868+
return Call;
1869+
}
1870+
17851871
static ExprResult BuiltinLaunder(Sema &S, CallExpr *TheCall) {
17861872
if (S.checkArgCount(TheCall, 1))
17871873
return ExprError();
@@ -2625,6 +2711,12 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID,
26252711
return PointerAuthAuthAndResign(*this, TheCall);
26262712
case Builtin::BI__builtin_ptrauth_string_discriminator:
26272713
return PointerAuthStringDiscriminator(*this, TheCall);
2714+
2715+
case Builtin::BI__builtin_get_vtable_pointer:
2716+
return GetVTablePointer(*this, TheCall);
2717+
case Builtin::BI__builtin_virtual_member_address:
2718+
return VirtualMemberAddress(*this, TheCall);
2719+
26282720
// OpenCL v2.0, s6.13.16 - Pipe functions
26292721
case Builtin::BIread_pipe:
26302722
case Builtin::BIwrite_pipe:

0 commit comments

Comments
 (0)