Skip to content
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

Add reflection introspection support for function pointers #71516

Closed
wants to merge 10 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@
<Compile Include="$(BclSourcesRoot)\System\Reflection\Emit\TypeBuilderInstantiation.cs" />
<Compile Include="$(BclSourcesRoot)\System\Reflection\Emit\XXXOnTypeBuilderInstantiation.cs" />
<Compile Include="$(BclSourcesRoot)\System\Reflection\FieldInfo.CoreCLR.cs" />
<Compile Include="$(BclSourcesRoot)\System\Reflection\FunctionPointerInfo.CoreCLR.cs" />
<Compile Include="$(BclSourcesRoot)\System\Reflection\LoaderAllocator.cs" />
<Compile Include="$(BclSourcesRoot)\System\Reflection\MdConstant.cs" />
<Compile Include="$(BclSourcesRoot)\System\Reflection\MdFieldInfo.cs" />
Expand All @@ -194,6 +195,7 @@
<Compile Include="$(BclSourcesRoot)\System\Reflection\RuntimeEventInfo.cs" />
<Compile Include="$(BclSourcesRoot)\System\Reflection\RuntimeExceptionHandlingClause.cs" />
<Compile Include="$(BclSourcesRoot)\System\Reflection\RuntimeFieldInfo.cs" />
<Compile Include="$(BclSourcesRoot)\System\Reflection\RuntimeFunctionPointerParameterInfo.CoreCLR.cs" />
<Compile Include="$(BclSourcesRoot)\System\Reflection\RuntimeLocalVariableInfo.cs" />
<Compile Include="$(BclSourcesRoot)\System\Reflection\RuntimeMethodBody.cs" />
<Compile Include="$(BclSourcesRoot)\System\Reflection\RuntimeMethodInfo.CoreCLR.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;

namespace System.Reflection
{
internal partial class FunctionPointerInfo
{
private readonly Signature _signature;
private readonly Type _type;

internal FunctionPointerInfo(Type type, Signature signature)
{
Debug.Assert(signature.m_csig > 0);

_type = type;
_signature = signature;
_returnInfo = new RuntimeFunctionPointerParameterInfo(signature.ReturnType.AsType(), -1, signature);

RuntimeType[] arguments = signature.Arguments;
int count = arguments.Length;
if (count == 0)
{
_parameterInfos = Array.Empty<RuntimeFunctionPointerParameterInfo>();
}
else
{
RuntimeFunctionPointerParameterInfo[] parameterInfos = new RuntimeFunctionPointerParameterInfo[count];
for (int i = 0; i < count; i++)
{
parameterInfos[i] = new RuntimeFunctionPointerParameterInfo(arguments[i].AsType(), i - 1, signature);
}
_parameterInfos = parameterInfos;
}
}

private unsafe MdSigCallingConvention CallingConvention => (MdSigCallingConvention)((byte*)_signature.m_sig)[0] & MdSigCallingConvention.CallConvMask;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,6 @@

namespace System.Reflection
{
[Flags]
internal enum MdSigCallingConvention : byte
{
CallConvMask = 0x0f, // Calling convention is bottom 4 bits

Default = 0x00,
C = 0x01,
StdCall = 0x02,
ThisCall = 0x03,
FastCall = 0x04,
Vararg = 0x05,
Field = 0x06,
LocalSig = 0x07,
Property = 0x08,
Unmanaged = 0x09,
GenericInst = 0x0a, // generic method instantiation

Generic = 0x10, // Generic method sig with explicit number of type arguments (precedes ordinary parameter count)
HasThis = 0x20, // Top bit indicates a 'this' parameter
ExplicitThis = 0x40, // This parameter is explicitly in the signature
}

[Flags]
internal enum PInvokeAttributes
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;

namespace System.Reflection
{
internal partial class RuntimeFunctionPointerParameterInfo
{
private readonly int _position;
private readonly Signature _signature;
private List<Type>? _optionalModifiers;

public RuntimeFunctionPointerParameterInfo(Type parameterType, int position, Signature signature)
{
_parameterType = parameterType;
_position = position;
_signature = signature;
}

public override Type[] GetOptionalCustomModifiers()
{
return _optionalModifiers == null ?
_signature.GetCustomModifiers(_position + 1, required: false) :
_optionalModifiers.ToArray();
}

// Expose the List in order to add calling conventions later.
internal List<Type> GetOptionalCustomModifiersList()
{
_optionalModifiers ??= new List<Type>(_signature.GetCustomModifiers(_position + 1, required: false));
return _optionalModifiers;
}

public override Type[] GetRequiredCustomModifiers() => _signature.GetCustomModifiers(_position + 1, required: true);
}
}
47 changes: 25 additions & 22 deletions src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,18 @@ internal static bool IsSZArray(RuntimeType type)
return corElemType == CorElementType.ELEMENT_TYPE_SZARRAY;
}

internal static bool IsFunctionPointer(RuntimeType type)
{
CorElementType corElemType = GetCorElementType(type);
return corElemType == CorElementType.ELEMENT_TYPE_FNPTR;
}

internal static bool IsUnmanagedFunctionPointer(RuntimeType type)
{
// Fast native path that does not need to create FunctionPointerInfo and parse the Signature.
return _IsUnmanagedFunctionPointer(type);
}

internal static bool HasElementType(RuntimeType type)
{
CorElementType corElemType = GetCorElementType(type);
Expand Down Expand Up @@ -522,6 +534,9 @@ internal static bool IsVisible(RuntimeType type)
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern bool IsValueType(RuntimeType type);

[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern bool _IsUnmanagedFunctionPointer(RuntimeType type);

[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "RuntimeTypeHandle_ConstructName")]
private static partial void ConstructName(QCallTypeHandle handle, TypeNameFormatFlags formatFlags, StringHandleOnStack retString);

Expand Down Expand Up @@ -1600,28 +1615,6 @@ internal static MetadataImport GetMetadataImport(RuntimeModule module)

internal sealed unsafe class Signature
{
#region Definitions
internal enum MdSigCallingConvention : byte
{
Generics = 0x10,
HasThis = 0x20,
ExplicitThis = 0x40,
CallConvMask = 0x0F,
Default = 0x00,
C = 0x01,
StdCall = 0x02,
ThisCall = 0x03,
FastCall = 0x04,
Vararg = 0x05,
Field = 0x06,
LocalSig = 0x07,
Property = 0x08,
Unmanaged = 0x09,
GenericInst = 0x0A,
Max = 0x0B,
}
#endregion

#region FCalls
[MemberNotNull(nameof(m_arguments))]
[MemberNotNull(nameof(m_returnTypeORfieldType))]
Expand Down Expand Up @@ -1673,6 +1666,16 @@ public Signature(IRuntimeFieldInfo fieldHandle, RuntimeType declaringType)
GC.KeepAlive(fieldHandle);
}

[MethodImpl(MethodImplOptions.InternalCall)]
private extern void GetSignatureFromFunctionPointer(Type functionPointerType);

public Signature(RuntimeType functionPointerType)
{
m_arguments = default!;
m_returnTypeORfieldType = default!;
GetSignatureFromFunctionPointer(functionPointerType);
}

public Signature(void* pCorSig, int cCorSig, RuntimeType declaringType)
{
GetSignature(pCorSig, cCorSig, default, null, declaringType);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1489,7 +1489,7 @@ internal T[] GetMemberList(MemberListType listType, string? name, CacheType cach
private static CerHashtable<RuntimeMethodInfo, RuntimeMethodInfo> s_methodInstantiations;
private static object? s_methodInstantiationsLock;
private string? m_defaultMemberName;
private object? m_genericCache; // Generic cache for rare scenario specific data. It is used to cache Enum names and values.
private object? m_genericCache;
private object[]? _emptyArray; // Object array cache for Attribute.GetCustomAttributes() pathological no-result case.
#endregion

Expand Down Expand Up @@ -1532,6 +1532,10 @@ private MemberInfoCache<T> GetMemberCache<T>(ref MemberInfoCache<T>? m_cache)

#region Internal Members


/// <summary>
/// Generic cache for rare scenario specific data. It is used to cache either Enum names, Enum values, the Activator cache or a function pointer.
/// </summary>
internal object? GenericCache
{
get => m_genericCache;
Expand Down Expand Up @@ -1561,6 +1565,11 @@ internal bool DomainInitialized
if (!m_runtimeType.GetRootElementType().IsGenericTypeDefinition && m_runtimeType.ContainsGenericParameters)
return null;

// Exclude function pointer; it requires a grammar update (see https://docs.microsoft.com/en-us/dotnet/framework/reflection-and-codedom/specifying-fully-qualified-type-names)
// and parsing support for Type.GetType(...) and related GetType() methods.
if (m_runtimeType.IsFunctionPointer)
return null;

// No assembly.
return ConstructName(ref m_fullname, TypeNameFormatFlags.FormatNamespace | TypeNameFormatFlags.FormatFullInst);

Expand All @@ -1579,6 +1588,11 @@ internal string GetNameSpace()
if (m_namespace == null)
{
Type type = m_runtimeType;

// Since Function pointers don't have a TypeDef metadata record just use the namespace for System.Type.
if (type.IsFunctionPointer)
return typeof(Type).Namespace!;

steveharter marked this conversation as resolved.
Show resolved Hide resolved
type = type.GetRootElementType();

while (type.IsNested)
Expand Down Expand Up @@ -1757,6 +1771,12 @@ internal FieldInfo GetField(RuntimeFieldHandleInternal field)
return m_fieldInfoCache!.AddField(field);
}

internal FunctionPointerInfo? FunctionPointerInfo
{
get => GenericCache as FunctionPointerInfo;
set => GenericCache = value;
}

#endregion
}
#endregion
Expand Down Expand Up @@ -3321,7 +3341,8 @@ public override string? AssemblyQualifiedName
{
string? fullname = FullName;

// FullName is null if this type contains generic parameters but is not a generic type definition.
// FullName is null if this type contains generic parameters but is not a generic type definition
// or if it is a function pointer.
if (fullname == null)
return null;

Expand Down Expand Up @@ -3766,6 +3787,32 @@ internal static CorElementType GetUnderlyingType(RuntimeType type)

#endregion

#region Function Pointer
public override bool IsFunctionPointer => RuntimeTypeHandle.IsFunctionPointer(this);
public override bool IsUnmanagedFunctionPointer => RuntimeTypeHandle.IsUnmanagedFunctionPointer(this);
public override FunctionPointerParameterInfo[] GetFunctionPointerParameterInfos() => GetFunctionPointerInfo().ParameterInfos;
public override FunctionPointerParameterInfo GetFunctionPointerReturnParameter() => GetFunctionPointerInfo().ReturnParameter;
public override Type[] GetFunctionPointerCallingConventions() => GetFunctionPointerInfo().GetCallingConventions();

internal FunctionPointerInfo GetFunctionPointerInfo()
{
FunctionPointerInfo? fnPtr = Cache.FunctionPointerInfo;

if (fnPtr == null)
{
if (!IsFunctionPointer)
{
throw new InvalidOperationException(SR.InvalidOperation_NotFunctionPointer);
}

fnPtr = new FunctionPointerInfo(this, new Signature(this));
Cache.FunctionPointerInfo = fnPtr;
}

return fnPtr;
}
#endregion

#endregion

public override string ToString() => GetCachedName(TypeNameKind.ToString)!;
Expand Down
5 changes: 4 additions & 1 deletion src/coreclr/debug/daccess/dacdbiimpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2370,7 +2370,10 @@ TypeHandle DacDbiInterfaceImpl::FindLoadedFnptrType(DWORD numTypeArgs, TypeHandl
// @dbgtodo : Do we need to worry about calling convention here?
// LoadFnptrTypeThrowing expects the count of arguments, not
// including return value, so we subtract 1 from numTypeArgs.
return ClassLoader::LoadFnptrTypeThrowing(0,
return ClassLoader::LoadFnptrTypeThrowing(NULL, /*module*/
NULL, /*sig*/
0, /*sigLen*/
0, /*callConv*/
numTypeArgs - 1,
pInst,
ClassLoader::DontLoadTypes);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
</PropertyGroup>
Expand Down Expand Up @@ -159,6 +159,7 @@
<Compile Include="System\Reflection\MethodBase.NativeAot.cs" />
<Compile Include="System\Reflection\Metadata\AssemblyExtensions.cs" />
<Compile Include="System\Reflection\Metadata\MetadataUpdater.cs" />
<Compile Include="System\Reflection\RuntimeFunctionPointerParameterInfo.NativeAot.cs" />
<Compile Include="System\Runtime\InteropServices\CriticalHandle.NativeAot.cs" />
<Compile Include="System\Activator.NativeAot.cs" />
<Compile Include="System\AppContext.NativeAot.cs" />
Expand Down Expand Up @@ -509,7 +510,7 @@
<Compile Include="System\Reflection\Runtime\TypeInfos\NativeFormat\NativeFormatRuntimeTypeInfo.CoreGetDeclared.cs" />
<Compile Include="System\Reflection\Runtime\TypeInfos\RuntimeArrayTypeInfo.cs" />
<Compile Include="System\Reflection\Runtime\TypeInfos\RuntimeByRefTypeInfo.cs" />
<Compile Include="System\Reflection\Runtime\TypeInfos\RuntimeClsIdTypeInfo.cs" Condition="'$(FeatureComInterop)' == 'true'"/>
<Compile Include="System\Reflection\Runtime\TypeInfos\RuntimeClsIdTypeInfo.cs" Condition="'$(FeatureComInterop)' == 'true'" />
<Compile Include="System\Reflection\Runtime\TypeInfos\RuntimeClsIdTypeInfo.UnificationKey.cs" Condition="'$(FeatureComInterop)' == 'true'" />
<Compile Include="System\Reflection\Runtime\TypeInfos\RuntimeConstructedGenericTypeInfo.cs" />
<Compile Include="System\Reflection\Runtime\TypeInfos\RuntimeConstructedGenericTypeInfo.UnificationKey.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace System.Reflection
{
internal partial class RuntimeFunctionPointerParameterInfo
{
public override Type[] GetOptionalCustomModifiers() => throw new NotSupportedException();
public override Type[] GetRequiredCustomModifiers() => throw new NotSupportedException();
}
}
29 changes: 23 additions & 6 deletions src/coreclr/vm/clsload.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1615,7 +1615,10 @@ TypeHandle ClassLoader::LoadNativeValueTypeThrowing(TypeHandle baseType,
}

/* static */
TypeHandle ClassLoader::LoadFnptrTypeThrowing(BYTE callConv,
TypeHandle ClassLoader::LoadFnptrTypeThrowing(Module *pModule,
PCOR_SIGNATURE sig,
uint32_t sigLen,
BYTE callConv,
DWORD ntypars,
TypeHandle* inst,
LoadTypesFlag fLoadTypes/*=LoadTypes*/,
Expand All @@ -1634,7 +1637,7 @@ TypeHandle ClassLoader::LoadFnptrTypeThrowing(BYTE callConv,
}
CONTRACT_END

TypeKey key(callConv, ntypars, inst);
TypeKey key(pModule, sig, sigLen, callConv, ntypars, inst);
RETURN(LoadConstructedTypeThrowing(&key, fLoadTypes, level));
}

Expand Down Expand Up @@ -2955,11 +2958,25 @@ TypeHandle ClassLoader::CreateTypeHandleForTypeKey(TypeKey* pKey, AllocMemTracke
Module *pLoaderModule = ComputeLoaderModule(pKey);
Copy link
Member

Choose a reason for hiding this comment

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

ComputeLoaderModule needs to be modified to take the custommods into account to handle the case where they are defined in unloaded assembly.

Also, we need to call EnsureInstantiation or something like that to capture the dependencies.

Copy link
Member Author

Choose a reason for hiding this comment

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

I think 'll hold off on this until we decide if we want to go with a restricted approach such as option (2) or (3) above since it wouldn't be needed assuming all calling conventions are in the runtime (e.g. System.Private.CoreLib) we don't have to worry about it being unloaded.

pLoaderModule->GetLoaderAllocator()->EnsureInstantiation(NULL, Instantiation(pKey->GetRetAndArgTypes(), pKey->GetNumArgs() + 1));

PREFIX_ASSUME(pLoaderModule!=NULL);
DWORD numArgs = pKey->GetNumArgs();
BYTE* mem = (BYTE*) pamTracker->Track(pLoaderModule->GetAssembly()->GetLowFrequencyHeap()->AllocMem(S_SIZE_T(sizeof(FnPtrTypeDesc)) + S_SIZE_T(sizeof(TypeHandle)) * S_SIZE_T(numArgs)));
PREFIX_ASSUME(pLoaderModule != NULL);
steveharter marked this conversation as resolved.
Show resolved Hide resolved
PTR_LoaderHeap loaderHeap = pLoaderModule->GetAssembly()->GetLowFrequencyHeap();

typeHnd = TypeHandle(new(mem) FnPtrTypeDesc(pKey->GetCallConv(), numArgs, pKey->GetRetAndArgTypes()));
int32_t sigLen = pKey->GetSignatureLen();
PCOR_SIGNATURE pCopyOfSig = (PCOR_SIGNATURE)pamTracker->Track(loaderHeap->AllocMem(S_SIZE_T(sigLen)));
memcpy(pCopyOfSig, pKey->GetSignature(), sigLen);

DWORD numArgs = pKey->GetNumArgs();
BYTE* mem = (BYTE*) pamTracker->Track(loaderHeap->AllocMem(
S_SIZE_T(sizeof(FnPtrTypeDesc)) +
S_SIZE_T(sizeof(TypeHandle)) * S_SIZE_T(numArgs)));

typeHnd = TypeHandle(new(mem) FnPtrTypeDesc(
pKey->GetModule(),
pCopyOfSig,
sigLen,
pKey->GetCallConv(),
numArgs,
pKey->GetRetAndArgTypes()));
}
else
{
Expand Down
Loading