diff --git a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj index a440561b2b8ae0..ae9f811cb0cf1b 100644 --- a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -1,4 +1,4 @@ - + false @@ -179,6 +179,7 @@ + @@ -195,6 +196,7 @@ + @@ -245,6 +247,9 @@ + + Common\System\Collections\Generic\ArrayBuilder.cs + diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/FunctionPointerInfo.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/FunctionPointerInfo.CoreCLR.cs new file mode 100644 index 00000000000000..77942e052c4951 --- /dev/null +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/FunctionPointerInfo.CoreCLR.cs @@ -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 RuntimeType _type; + + internal FunctionPointerInfo(RuntimeType type) + { + _type = type; + Type[] arguments = RuntimeTypeHandle.GetArgumentTypesFromFunctionPointer(type); + Debug.Assert(arguments.Length >= 1); + + _returnInfo = new RuntimeFunctionPointerParameterInfo(this, arguments[0], 0); + int count = arguments.Length; + if (count == 1) + { + _parameterInfos = Array.Empty(); + } + else + { + RuntimeFunctionPointerParameterInfo[] parameterInfos = new RuntimeFunctionPointerParameterInfo[count - 1]; + for (int i = 0; i < count - 1; i++) + { + parameterInfos[i] = new RuntimeFunctionPointerParameterInfo(this, arguments[i + 1], i + 1); + } + _parameterInfos = parameterInfos; + } + } + + internal RuntimeType FunctionPointerType => _type; + internal unsafe MdSigCallingConvention CallingConvention => (MdSigCallingConvention)RuntimeTypeHandle.GetRawCallingConventionsFromFunctionPointer(_type); + internal Type[]? GetCustomModifiersFromFunctionPointer(int position, bool required) => + RuntimeTypeHandle.GetCustomModifiersFromFunctionPointer(_type, 0, required: false); + } +} diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/MdImport.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/MdImport.cs index cdc15dc8fb6e08..4bfdfee70accce 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/MdImport.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/MdImport.cs @@ -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 { diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeFunctionPointerParameterInfo.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeFunctionPointerParameterInfo.CoreCLR.cs new file mode 100644 index 00000000000000..f693b9864f9fd2 --- /dev/null +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeFunctionPointerParameterInfo.CoreCLR.cs @@ -0,0 +1,57 @@ +// 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 RuntimeFunctionPointerParameterInfo + { + private readonly FunctionPointerInfo _functionPointer; + private readonly int _position; + private Type[]? _optionalModifiers; + + public RuntimeFunctionPointerParameterInfo(FunctionPointerInfo functionPointer, Type parameterType, int position) + { + _functionPointer = functionPointer; + _parameterType = parameterType; + _position = position; // 0 = return value; 1 = first parameter + } + + public override Type[] GetOptionalCustomModifiers() + { + if (_optionalModifiers == null) + { + if (_position == 0) + { + // Return value should be normalized. This will call SetCustomModifiersForReturnType. + _functionPointer.ComputeCallingConventions(); + Debug.Assert(_optionalModifiers != null); + } + else + { + Type[]? mods = RuntimeTypeHandle.GetCustomModifiersFromFunctionPointer(_functionPointer.FunctionPointerType, _position, required: false); + if (mods == null) + { + _optionalModifiers = Type.EmptyTypes; + } + } + } + + Debug.Assert(_optionalModifiers != null); + return _optionalModifiers; + } + + internal void SetCustomModifiersForReturnType(Type[] modifiers) + { + Debug.Assert(_position == 0); + _optionalModifiers = modifiers; + } + + public override Type[] GetRequiredCustomModifiers() + { + Type[]? value = RuntimeTypeHandle.GetCustomModifiersFromFunctionPointer(_functionPointer.FunctionPointerType, _position, required: true); + return value ??= Type.EmptyTypes; + } + } +} diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs index 489c1b89205398..4eba03cb416f46 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs @@ -182,6 +182,12 @@ 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 HasElementType(RuntimeType type) { CorElementType corElemType = GetCorElementType(type); @@ -383,6 +389,15 @@ public ModuleHandle GetModuleHandle() [MethodImpl(MethodImplOptions.InternalCall)] internal static extern RuntimeMethodHandleInternal GetMethodAt(RuntimeType type, int slot); + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern Type[]? GetCustomModifiersFromFunctionPointer(RuntimeType functionPointerType, int position, bool required); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern byte GetRawCallingConventionsFromFunctionPointer(RuntimeType type); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern Type[] GetArgumentTypesFromFunctionPointer(RuntimeType type); + // This is managed wrapper for MethodTable::IntroducedMethodIterator internal struct IntroducedMethodEnumerator { @@ -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))] diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs index 1b57a97d18c014..566fcfa787fb0a 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs @@ -1489,7 +1489,7 @@ internal T[] GetMemberList(MemberListType listType, string? name, CacheType cach private static CerHashtable 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 @@ -1532,6 +1532,10 @@ private MemberInfoCache GetMemberCache(ref MemberInfoCache? m_cache) #region Internal Members + + /// + /// Generic cache for rare scenario specific data. It is used to cache either Enum names, Enum values, the Activator cache or a function pointer. + /// internal object? GenericCache { get => m_genericCache; @@ -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/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); @@ -1573,12 +1582,16 @@ internal bool DomainInitialized } } - internal string GetNameSpace() + internal string? GetNameSpace() { // @Optimization - Use ConstructName to populate m_namespace if (m_namespace == null) { Type type = m_runtimeType; + + if (type.IsFunctionPointer) + return null; + type = type.GetRootElementType(); while (type.IsNested) @@ -1757,6 +1770,12 @@ internal FieldInfo GetField(RuntimeFieldHandleInternal field) return m_fieldInfoCache!.AddField(field); } + internal FunctionPointerInfo? FunctionPointerInfo + { + get => GenericCache as FunctionPointerInfo; + set => GenericCache = value; + } + #endregion } #endregion @@ -3321,7 +3340,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; @@ -3333,7 +3353,7 @@ public override string? Namespace { get { - string ns = Cache.GetNameSpace(); + string? ns = Cache.GetNameSpace(); if (string.IsNullOrEmpty(ns)) { return null; @@ -3766,6 +3786,49 @@ internal static CorElementType GetUnderlyingType(RuntimeType type) #endregion + #region Function Pointer + public override bool IsFunctionPointer => RuntimeTypeHandle.IsFunctionPointer(this); + public override bool IsUnmanagedFunctionPointer + { + get + { + switch (GetFunctionPointerInfo().CallingConvention) + { + case MdSigCallingConvention.C: + case MdSigCallingConvention.StdCall: + case MdSigCallingConvention.ThisCall: + case MdSigCallingConvention.FastCall: + case MdSigCallingConvention.Unmanaged: + return true; + default: + return false; + } + } + } + + 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); + Cache.FunctionPointerInfo = fnPtr; + } + + return fnPtr; + } + #endregion + #endregion public override string ToString() => GetCachedName(TypeNameKind.ToString)!; diff --git a/src/coreclr/debug/daccess/dacdbiimpl.cpp b/src/coreclr/debug/daccess/dacdbiimpl.cpp index 6987ed2dbac6e3..e873638aa3699f 100644 --- a/src/coreclr/debug/daccess/dacdbiimpl.cpp +++ b/src/coreclr/debug/daccess/dacdbiimpl.cpp @@ -2370,9 +2370,11 @@ 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(0, // callConv numTypeArgs - 1, pInst, + 0, // numCustomMods + NULL, // customMods ClassLoader::DontLoadTypes); } // DacDbiInterfaceImpl::FindLoadedFnptrType diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj b/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj index 57906233af649c..913288ddc4e2cf 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj @@ -1,4 +1,4 @@ - + true @@ -159,6 +159,7 @@ + @@ -509,7 +510,7 @@ - + diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/RuntimeFunctionPointerParameterInfo.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/RuntimeFunctionPointerParameterInfo.NativeAot.cs new file mode 100644 index 00000000000000..d16d4f1c5fb871 --- /dev/null +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/RuntimeFunctionPointerParameterInfo.NativeAot.cs @@ -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(); + } +} diff --git a/src/coreclr/vm/clsload.cpp b/src/coreclr/vm/clsload.cpp index daebf64ea9f30e..ef6843deaed740 100644 --- a/src/coreclr/vm/clsload.cpp +++ b/src/coreclr/vm/clsload.cpp @@ -1618,6 +1618,8 @@ TypeHandle ClassLoader::LoadNativeValueTypeThrowing(TypeHandle baseType, TypeHandle ClassLoader::LoadFnptrTypeThrowing(BYTE callConv, DWORD ntypars, TypeHandle* inst, + DWORD numCustomMods, + FnPtrTypeDescCustomMod* customMods, LoadTypesFlag fLoadTypes/*=LoadTypes*/, ClassLoadLevel level/*=CLASS_LOADED*/) { @@ -1634,7 +1636,7 @@ TypeHandle ClassLoader::LoadFnptrTypeThrowing(BYTE callConv, } CONTRACT_END - TypeKey key(callConv, ntypars, inst); + TypeKey key(callConv, ntypars, inst, numCustomMods, customMods); RETURN(LoadConstructedTypeThrowing(&key, fLoadTypes, level)); } @@ -2953,13 +2955,36 @@ TypeHandle ClassLoader::CreateTypeHandleForTypeKey(TypeKey* pKey, AllocMemTracke else if (pKey->GetKind() == ELEMENT_TYPE_FNPTR) { Module *pLoaderModule = ComputeLoaderModule(pKey); + PREFIX_ASSUME(pLoaderModule != NULL); pLoaderModule->GetLoaderAllocator()->EnsureInstantiation(NULL, Instantiation(pKey->GetRetAndArgTypes(), pKey->GetNumArgs() + 1)); - PREFIX_ASSUME(pLoaderModule!=NULL); + PTR_LoaderHeap loaderHeap = pLoaderModule->GetAssembly()->GetLowFrequencyHeap(); 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))); + BYTE* mem = (BYTE*) pamTracker->Track(loaderHeap->AllocMem( + S_SIZE_T(sizeof(FnPtrTypeDesc)) + + S_SIZE_T(sizeof(TypeHandle)) * S_SIZE_T(numArgs))); + + DWORD numCustomMods = pKey->GetNumMods(); + FnPtrTypeDescCustomMod* dstMods = NULL; + if (numCustomMods) + { + FnPtrTypeDescCustomMod* srcMods = pKey->GetCustomMods(); + + dstMods = (FnPtrTypeDescCustomMod*) pamTracker->Track(loaderHeap->AllocMem( + S_SIZE_T(sizeof(FnPtrTypeDescCustomMod)) * S_SIZE_T(numCustomMods))); + + for (DWORD i = 0; i < numCustomMods; i++) + { + dstMods[i] = srcMods[i]; + } + } - typeHnd = TypeHandle(new(mem) FnPtrTypeDesc(pKey->GetCallConv(), numArgs, pKey->GetRetAndArgTypes())); + typeHnd = TypeHandle(new(mem) FnPtrTypeDesc( + pKey->GetCallConv(), + numArgs, + pKey->GetRetAndArgTypes(), + numCustomMods, + dstMods)); } else { diff --git a/src/coreclr/vm/clsload.hpp b/src/coreclr/vm/clsload.hpp index e446e239350aff..55496583b7cb83 100644 --- a/src/coreclr/vm/clsload.hpp +++ b/src/coreclr/vm/clsload.hpp @@ -37,6 +37,7 @@ class Thread; class EETypeHashTable; class DynamicResolver; class SigPointer; +struct FnPtrTypeDescCustomMod; // Hash table parameter for unresolved class hash #define UNRESOLVED_CLASS_HASH_BUCKETS 8 @@ -696,6 +697,8 @@ class ClassLoader static TypeHandle LoadFnptrTypeThrowing(BYTE callConv, DWORD numArgs, TypeHandle* retAndArgTypes, + DWORD numCustomMods, + FnPtrTypeDescCustomMod* customMods, LoadTypesFlag fLoadTypes = LoadTypes, ClassLoadLevel level = CLASS_LOADED); diff --git a/src/coreclr/vm/ecalllist.h b/src/coreclr/vm/ecalllist.h index d2c3822f9de832..b210f5404f6f3f 100644 --- a/src/coreclr/vm/ecalllist.h +++ b/src/coreclr/vm/ecalllist.h @@ -168,6 +168,10 @@ FCFuncStart(gCOMTypeHandleFuncs) FCFuncElement("IsGenericTypeDefinition", RuntimeTypeHandle::IsGenericTypeDefinition) FCFuncElement("ContainsGenericVariables", RuntimeTypeHandle::ContainsGenericVariables) FCFuncElement("SatisfiesConstraints", RuntimeTypeHandle::SatisfiesConstraints) + FCFuncElement("GetCustomModifiersFromFunctionPointer", RuntimeTypeHandle::GetCustomModifiersFromFunctionPointer) + FCFuncElement("GetRawCallingConventionsFromFunctionPointer", RuntimeTypeHandle::GetRawCallingConventionsFromFunctionPointer) + FCFuncElement("GetArgumentTypesFromFunctionPointer", RuntimeTypeHandle::GetArgumentTypesFromFunctionPointer) + #ifdef FEATURE_COMINTEROP FCFuncElement("AllocateComObject", RuntimeTypeHandle::AllocateComObject) #endif // FEATURE_COMINTEROP diff --git a/src/coreclr/vm/eedbginterfaceimpl.cpp b/src/coreclr/vm/eedbginterfaceimpl.cpp index 5d2edfb2c02929..fc23de3128135b 100644 --- a/src/coreclr/vm/eedbginterfaceimpl.cpp +++ b/src/coreclr/vm/eedbginterfaceimpl.cpp @@ -884,7 +884,12 @@ TypeHandle EEDbgInterfaceImpl::FindLoadedFnptrType(TypeHandle *inst, ENABLE_FORBID_GC_LOADER_USE_IN_THIS_SCOPE(); // : CALLCONV? - return ClassLoader::LoadFnptrTypeThrowing(0, ntypars, inst, + return ClassLoader::LoadFnptrTypeThrowing(0, // callConv + ntypars, + inst, + 0, // numCustomMods + NULL, // customMods + // should this be FailIfNotLoaded? - NO - although we may // want to debug unrestored VCs, we can't do it because the debug API // is not set up to handle them @@ -1009,7 +1014,12 @@ TypeHandle EEDbgInterfaceImpl::LoadFnptrType(TypeHandle *inst, CONTRACTL_END; /* @TODO : CALLCONV? */ - return ClassLoader::LoadFnptrTypeThrowing(0, ntypars, inst); + return ClassLoader::LoadFnptrTypeThrowing(0, // callConv + ntypars, + inst, + 0, // numCustomMods + NULL // customMods + ); } TypeHandle EEDbgInterfaceImpl::LoadElementType(CorElementType et) diff --git a/src/coreclr/vm/runtimehandles.cpp b/src/coreclr/vm/runtimehandles.cpp index c849a0a53d5eb3..1759f5b091903a 100644 --- a/src/coreclr/vm/runtimehandles.cpp +++ b/src/coreclr/vm/runtimehandles.cpp @@ -361,7 +361,6 @@ FCIMPL1(AssemblyBaseObject*, RuntimeTypeHandle::GetAssembly, ReflectClassBaseObj } FCIMPLEND - FCIMPL1(FC_BOOL_RET, RuntimeFieldHandle::AcquiresContextFromThis, FieldDesc* pField) { CONTRACTL { @@ -899,6 +898,162 @@ FCIMPL1(FC_BOOL_RET, RuntimeTypeHandle::IsByRefLike, ReflectClassBaseObject *pTy } FCIMPLEND +FCIMPL3(Object *, RuntimeTypeHandle::GetCustomModifiersFromFunctionPointer, ReflectClassBaseObject* pTypeUNSAFE, + INT32 position, CLR_BOOL fRequired) +{ + CONTRACTL { + FCALL_CHECK; + PRECONDITION(CheckPointer(pTypeUNSAFE)); + } + CONTRACTL_END; + + struct + { + PTRARRAYREF retVal; + } gc; + + gc.retVal = NULL; + + REFLECTCLASSBASEREF refType = (REFLECTCLASSBASEREF)ObjectToOBJECTREF(pTypeUNSAFE); + TypeHandle typeHandle = refType->GetType(); + + if (!typeHandle.IsFnPtrType()) + FCThrowRes(kArgumentException, W("Arg_InvalidHandle")); + + FnPtrTypeDesc* fnPtr = typeHandle.AsFnPtrType(); + + CorElementType cmodTypeExpected = fRequired ? ELEMENT_TYPE_CMOD_REQD : ELEMENT_TYPE_CMOD_OPT; + + // Discover the number of required and optional custom modifiers. + INT32 currentPos = 0; + INT32 cMods = 0; + DWORD numCustomMods = fnPtr->GetNumMods(); + FnPtrTypeDescCustomMod* mods = fnPtr->GetCustomModsPointer(); + + for (DWORD i = 0; i < numCustomMods; i++) + { + CorElementType cmodType = mods[i].elementType; + + if (currentPos == position) + { + if (cmodType == cmodTypeExpected) + { + cMods++; + } + } + + if (cmodType == ELEMENT_TYPE_END) + { + currentPos++; + if (currentPos > position) + { + break; + } + } + } + + if (cMods == 0) + return NULL; + + HELPER_METHOD_FRAME_BEGIN_RET_PROTECT(gc); + { + MethodTable *pMT = CoreLibBinder::GetClass(CLASS__TYPE); + TypeHandle arrayHandle = ClassLoader::LoadArrayTypeThrowing(TypeHandle(pMT), ELEMENT_TYPE_SZARRAY); + gc.retVal = (PTRARRAYREF) AllocateSzArray(arrayHandle, cMods); + + currentPos = 0; + for (DWORD i = 0; i < numCustomMods; i++) + { + CorElementType cmodType = mods[i].elementType; + + if (currentPos == position) + { + if (cmodType == cmodTypeExpected) + { + TypeHandle typeHandle = mods[i].typeHandle; + OBJECTREF refType = typeHandle.GetManagedClassObject(); + gc.retVal->SetAt(--cMods, refType); + } + } + + if (cmodType == ELEMENT_TYPE_END) + { + currentPos++; + if (currentPos > position) + { + break; + } + } + } + } + HELPER_METHOD_FRAME_END(); + + return OBJECTREFToObject(gc.retVal); +} +FCIMPLEND + +FCIMPL1(Object *, RuntimeTypeHandle::GetArgumentTypesFromFunctionPointer, ReflectClassBaseObject *pTypeUNSAFE) +{ + CONTRACTL { + FCALL_CHECK; + PRECONDITION(CheckPointer(pTypeUNSAFE)); + } + CONTRACTL_END; + + struct + { + PTRARRAYREF retVal; + } gc; + + gc.retVal = NULL; + + REFLECTCLASSBASEREF refType = (REFLECTCLASSBASEREF)ObjectToOBJECTREF(pTypeUNSAFE); + TypeHandle typeHandle = refType->GetType(); + + if (!typeHandle.IsFnPtrType()) + FCThrowRes(kArgumentException, W("Arg_InvalidHandle")); + + FnPtrTypeDesc* fnPtr = typeHandle.AsFnPtrType(); + + HELPER_METHOD_FRAME_BEGIN_RET_PROTECT(gc); + { + MethodTable *pMT = CoreLibBinder::GetClass(CLASS__TYPE); + TypeHandle arrayHandle = ClassLoader::LoadArrayTypeThrowing(TypeHandle(pMT), ELEMENT_TYPE_SZARRAY); + DWORD cArgs = fnPtr->GetNumArgs(); + gc.retVal = (PTRARRAYREF) AllocateSzArray(arrayHandle, cArgs + 1); + + for (DWORD position = 0; position <= cArgs; position++) + { + TypeHandle typeHandle = fnPtr->GetRetAndArgTypes()[position]; + OBJECTREF refType = typeHandle.GetManagedClassObject(); + gc.retVal->SetAt(position, refType); + } + } + HELPER_METHOD_FRAME_END(); + + return OBJECTREFToObject(gc.retVal); +} +FCIMPLEND + +FCIMPL1(FC_INT8_RET, RuntimeTypeHandle::GetRawCallingConventionsFromFunctionPointer, ReflectClassBaseObject *pTypeUNSAFE) +{ + CONTRACTL { + FCALL_CHECK; + PRECONDITION(CheckPointer(pTypeUNSAFE)); + } + CONTRACTL_END; + + REFLECTCLASSBASEREF refType = (REFLECTCLASSBASEREF)ObjectToOBJECTREF(pTypeUNSAFE); + TypeHandle typeHandle = refType->GetType(); + + if (!typeHandle.IsFnPtrType()) + FCThrowRes(kArgumentException, W("Arg_InvalidHandle")); + + FnPtrTypeDesc* fnPtr = typeHandle.AsFnPtrType(); + return FC_INT8_RET(fnPtr->GetCallConv() & IMAGE_CEE_CS_CALLCONV_MASK); +} +FCIMPLEND + extern "C" BOOL QCALLTYPE RuntimeTypeHandle_IsVisible(QCall::TypeHandle pTypeHandle) { CONTRACTL @@ -2024,6 +2179,7 @@ FCIMPL6(void, SignatureNative::GetSignature, pMethod, declType.GetClassOrArrayInstantiation(), pMethod->LoadMethodInstantiation(), &typeContext); else SigTypeContext::InitTypeContext(declType, &typeContext); + MetaSig msig(pCorSig, cCorSig, pModule, &typeContext, (callConv & IMAGE_CEE_CS_CALLCONV_MASK) == IMAGE_CEE_CS_CALLCONV_FIELD ? MetaSig::sigField : MetaSig::sigMember); diff --git a/src/coreclr/vm/runtimehandles.h b/src/coreclr/vm/runtimehandles.h index a4bddd1e3ca9f6..1a459384b66128 100644 --- a/src/coreclr/vm/runtimehandles.h +++ b/src/coreclr/vm/runtimehandles.h @@ -141,6 +141,10 @@ class RuntimeTypeHandle { static FCDECL1(FC_BOOL_RET, IsInterface, ReflectClassBaseObject* pType); static FCDECL1(FC_BOOL_RET, IsByRefLike, ReflectClassBaseObject* pType); + static FCDECL3(Object *, GetCustomModifiersFromFunctionPointer, ReflectClassBaseObject* pTypeUNSAFE, INT32 position, CLR_BOOL fRequired); + static FCDECL1(Object *, GetArgumentTypesFromFunctionPointer, ReflectClassBaseObject *pTypeUNSAFE); + static FCDECL1(FC_INT8_RET, GetRawCallingConventionsFromFunctionPointer, ReflectClassBaseObject *pTypeUNSAFE); + static FCDECL2(FC_BOOL_RET, CanCastTo, ReflectClassBaseObject *pType, ReflectClassBaseObject *pTarget); static FCDECL2(FC_BOOL_RET, IsInstanceOfType, ReflectClassBaseObject *pType, Object *object); @@ -382,6 +386,7 @@ class SignatureNative : public Object PCCOR_SIGNATURE pCorSig, DWORD cCorSig, FieldDesc *pFieldDesc, ReflectMethodObject *pMethodUNSAFE, ReflectClassBaseObject *pDeclaringType); + static FCDECL3(Object *, GetCustomModifiers, SignatureNative* pSig, INT32 parameter, CLR_BOOL fRequired); static FCDECL2(FC_BOOL_RET, CompareSig, SignatureNative* pLhs, SignatureNative* pRhs); diff --git a/src/coreclr/vm/sigformat.cpp b/src/coreclr/vm/sigformat.cpp index 268c255b531ab0..ebfc7aae27c6b5 100644 --- a/src/coreclr/vm/sigformat.cpp +++ b/src/coreclr/vm/sigformat.cpp @@ -545,12 +545,11 @@ void SigFormat::AddType(TypeHandle th) AddType(pRetAndArgTypes[i+1]); if (i != (cArgs - 1)) - AddString(", "); + AddString(", "); } if ((pTD->GetCallConv() & IMAGE_CEE_CS_CALLCONV_MASK) == IMAGE_CEE_CS_CALLCONV_VARARG) { AddString(", "); - AddString("..."); } AddString(")"); diff --git a/src/coreclr/vm/sigformat.h b/src/coreclr/vm/sigformat.h index 65a50db0a84486..7e043af08c5ee5 100644 --- a/src/coreclr/vm/sigformat.h +++ b/src/coreclr/vm/sigformat.h @@ -19,6 +19,7 @@ class SigFormat //@GENERICS: the owning type handle is required because pMeth may be shared between instantiations SigFormat(MethodDesc* pMeth, TypeHandle owner, BOOL fIgnoreMethodName = false); SigFormat(MetaSig &metaSig, LPCUTF8 memberName, LPCUTF8 className = NULL, LPCUTF8 ns = NULL); + SigFormat(FnPtrTypeDesc* pFnPtr); ~SigFormat(); diff --git a/src/coreclr/vm/siginfo.cpp b/src/coreclr/vm/siginfo.cpp index a8d18ec63a2936..39b68a4d8dec75 100644 --- a/src/coreclr/vm/siginfo.cpp +++ b/src/coreclr/vm/siginfo.cpp @@ -1488,9 +1488,9 @@ TypeHandle SigPointer::GetTypeHandleThrowing( { if (TypeFromToken(typeToken) == mdtTypeRef) { - loadedType = TypeHandle(CoreLibBinder::GetElementType(ELEMENT_TYPE_VOID)); - thRet = loadedType; - break; + loadedType = TypeHandle(CoreLibBinder::GetElementType(ELEMENT_TYPE_VOID)); + thRet = loadedType; + break; } } @@ -1542,8 +1542,8 @@ TypeHandle SigPointer::GetTypeHandleThrowing( pZapSigContext); if (elemType.IsNull()) { - thRet = elemType; - break; + thRet = elemType; + break; } uint32_t rank = 0; @@ -1554,7 +1554,7 @@ TypeHandle SigPointer::GetTypeHandleThrowing( _ASSERTE(0 < rank); } thRet = ClassLoader::LoadArrayTypeThrowing(elemType, typ, rank, fLoadTypes, level); - break; + break; } case ELEMENT_TYPE_PINNED: @@ -1566,7 +1566,7 @@ TypeHandle SigPointer::GetTypeHandleThrowing( dropGenericArgumentLevel, pSubst, pZapSigContext); - break; + break; case ELEMENT_TYPE_BYREF: case ELEMENT_TYPE_PTR: @@ -1584,9 +1584,9 @@ TypeHandle SigPointer::GetTypeHandleThrowing( } else { - thRet = ClassLoader::LoadPointerOrByrefTypeThrowing(typ, baseType, fLoadTypes, level); + thRet = ClassLoader::LoadPointerOrByrefTypeThrowing(typ, baseType, fLoadTypes, level); } - break; + break; } case ELEMENT_TYPE_FNPTR: @@ -1601,7 +1601,7 @@ TypeHandle SigPointer::GetTypeHandleThrowing( if ((uCallConv & IMAGE_CEE_CS_CALLCONV_GENERIC) > 0) THROW_BAD_FORMAT(BFA_FNPTR_CANNOT_BE_GENERIC, pOrigModule); - // Get arg count; + // Get the arg count. uint32_t cArgs = 0; IfFailThrowBF(psig.GetData(&cArgs), BFA_BAD_SIGNATURE, pOrigModule); @@ -1615,8 +1615,29 @@ TypeHandle SigPointer::GetTypeHandleThrowing( TypeHandle *retAndArgTypes = (TypeHandle*) _alloca(cAllocaSize); bool fReturnTypeOrParameterNotLoaded = false; + int cMods = 0; + BYTE data; + SigPointer psigModReread = psig; for (unsigned i = 0; i <= cArgs; i++) { + // Get the custom mod count. + IfFailThrowBF(psig.PeekByte(&data), BFA_BAD_SIGNATURE, pOrigModule); + CorElementType etyp = (CorElementType)data; + if (etyp == ELEMENT_TYPE_CMOD_OPT || etyp == ELEMENT_TYPE_CMOD_REQD) + { + mdToken tk; + SigPointer psigTemp = psig; + + do { + cMods++; + psigTemp.SkipBytes(1); + IfFailThrowBF(psigTemp.GetToken(&tk), BFA_BAD_SIGNATURE, pOrigModule); + IfFailThrowBF(psigTemp.PeekByte(&data), BFA_BAD_SIGNATURE, pOrigModule); + etyp = (CorElementType)data; + } while (etyp == ELEMENT_TYPE_CMOD_OPT || etyp == ELEMENT_TYPE_CMOD_REQD); + } + + // Lookup type handle. retAndArgTypes[i] = psig.GetTypeHandleThrowing(pOrigModule, pTypeContext, fLoadTypes, @@ -1624,6 +1645,7 @@ TypeHandle SigPointer::GetTypeHandleThrowing( dropGenericArgumentLevel, pSubst, pZapSigContext); + if (retAndArgTypes[i].IsNull()) { thRet = TypeHandle(); @@ -1639,13 +1661,73 @@ TypeHandle SigPointer::GetTypeHandleThrowing( break; } - // Now make the function pointer type - thRet = ClassLoader::LoadFnptrTypeThrowing((BYTE) uCallConv, cArgs, retAndArgTypes, fLoadTypes, level); + FnPtrTypeDescCustomMod *customMods = NULL; + int cModsAndSeparators = cMods; + if (cMods) + { + // We know the number of mods so allocate and re-parse. + uint32_t cAllocaSize; + cModsAndSeparators += cArgs; // Each arg has a separator except for the last. + if (!ClrSafeInt::multiply(cModsAndSeparators, sizeof(FnPtrTypeDescCustomMod), cAllocaSize)) + { + ThrowHR(COR_E_OVERFLOW); + } + + customMods = (FnPtrTypeDescCustomMod*) _alloca(cAllocaSize); + + int iCurrent = 0; + for (unsigned i = 0; i <= cArgs; i++) + { + IfFailThrowBF(psigModReread.PeekByte(&data), BFA_BAD_SIGNATURE, pOrigModule); + CorElementType etyp = (CorElementType)data; + if (etyp == ELEMENT_TYPE_CMOD_OPT || etyp == ELEMENT_TYPE_CMOD_REQD) + { + do + { + psigModReread.SkipBytes(1); + + mdToken token; + IfFailThrow(psigModReread.GetToken(&token)); + + TypeHandle typeHandle = ClassLoader::LoadTypeDefOrRefOrSpecThrowing( + static_cast(pOrigModule), + token, + pTypeContext, + ClassLoader::ThrowIfNotFound, + ClassLoader::FailIfUninstDefOrRef); + + FnPtrTypeDescCustomMod mod; + mod.elementType = etyp; + mod.typeHandle = typeHandle; + customMods[iCurrent++] = mod; + + IfFailThrowBF(psigModReread.PeekByte(&data), BFA_BAD_SIGNATURE, pOrigModule); + etyp = (CorElementType)data; + } while (etyp == ELEMENT_TYPE_CMOD_OPT || etyp == ELEMENT_TYPE_CMOD_REQD); + } + + IfFailThrowBF(psigModReread.SkipExactlyOne(), BFA_BAD_SIGNATURE, pOrigModule); + + if (i != cArgs) + { + // Create a mod separator for each parameter except for the last. + FnPtrTypeDescCustomMod mod; + mod.elementType = ELEMENT_TYPE_END; + mod.typeHandle = TypeHandle(); + customMods[iCurrent++] = mod; + } + } + + _ASSERT(iCurrent = cMods + cArgs); + } + + // Find an existing function pointer or make a new one + thRet = ClassLoader::LoadFnptrTypeThrowing((BYTE) uCallConv, cArgs, retAndArgTypes, cModsAndSeparators, customMods, fLoadTypes, level); #else - DacNotImpl(); + DacNotImpl(); thRet = TypeHandle(); #endif - break; + break; } case ELEMENT_TYPE_INTERNAL : diff --git a/src/coreclr/vm/typedesc.cpp b/src/coreclr/vm/typedesc.cpp index 41abdebda71ae8..f0f7f29e758ac9 100644 --- a/src/coreclr/vm/typedesc.cpp +++ b/src/coreclr/vm/typedesc.cpp @@ -518,6 +518,47 @@ OBJECTREF ParamTypeDesc::GetManagedClassObject() #endif // #ifndef DACCESS_COMPILE +#ifndef DACCESS_COMPILE + +OBJECTREF FnPtrTypeDesc::GetManagedClassObject() +{ + CONTRACTL { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + + INJECT_FAULT(COMPlusThrowOM()); + + PRECONDITION(GetInternalCorElementType() == ELEMENT_TYPE_FNPTR); + } + CONTRACTL_END; + + if (m_hExposedClassObject == NULL) { + REFLECTCLASSBASEREF refClass = NULL; + GCPROTECT_BEGIN(refClass); + refClass = (REFLECTCLASSBASEREF) AllocateObject(g_pRuntimeTypeClass); + + LoaderAllocator *pLoaderAllocator = GetLoaderAllocator(); + TypeHandle th = TypeHandle(this); + ((ReflectClassBaseObject*)OBJECTREFToObject(refClass))->SetType(th); + ((ReflectClassBaseObject*)OBJECTREFToObject(refClass))->SetKeepAlive(pLoaderAllocator->GetExposedObject()); + + // Let all threads fight over who wins using InterlockedCompareExchange. + // Only the winner can set m_hExposedClassObject from NULL. + LOADERHANDLE hExposedClassObject = pLoaderAllocator->AllocateHandle(refClass); + + if (InterlockedCompareExchangeT(&m_hExposedClassObject, hExposedClassObject, static_cast(NULL))) + { + pLoaderAllocator->FreeHandle(hExposedClassObject); + } + + GCPROTECT_END(); + } + return GetManagedClassObjectIfExists(); +} + +#endif // #ifndef DACCESS_COMPILE + BOOL TypeDesc::IsRestored() { STATIC_CONTRACT_NOTHROW; diff --git a/src/coreclr/vm/typedesc.h b/src/coreclr/vm/typedesc.h index 19833eede708bb..be465434b52efa 100644 --- a/src/coreclr/vm/typedesc.h +++ b/src/coreclr/vm/typedesc.h @@ -3,10 +3,7 @@ // // File: typedesc.h // - - // - // // ============================================================================ @@ -439,17 +436,34 @@ class TypeVarTypeDesc : public TypeDesc /*************************************************************************/ /* represents a function type. */ -typedef SPTR(class FnPtrTypeDesc) PTR_FnPtrTypeDesc; +struct FnPtrTypeDescCustomMod +{ + CorElementType elementType; + TypeHandle typeHandle; +}; + +typedef DPTR(class FnPtrTypeDesc) PTR_FnPtrTypeDesc; class FnPtrTypeDesc : public TypeDesc { public: #ifndef DACCESS_COMPILE - FnPtrTypeDesc(BYTE callConv, DWORD numArgs, TypeHandle * retAndArgTypes) - : TypeDesc(ELEMENT_TYPE_FNPTR), m_NumArgs(numArgs), m_CallConv(callConv) + FnPtrTypeDesc( + BYTE callConv, + DWORD numArgs, + TypeHandle * retAndArgTypes, + DWORD numCustomMods, + FnPtrTypeDescCustomMod * customMods) + : TypeDesc(ELEMENT_TYPE_FNPTR), + m_hExposedClassObject(0), + m_NumArgs(numArgs), + m_CallConv(callConv), + m_NumMods(numCustomMods), + m_pCustomMods(customMods) { LIMITED_METHOD_CONTRACT; + for (DWORD i = 0; i <= numArgs; i++) { m_RetAndArgTypes[i] = retAndArgTypes[i]; @@ -491,6 +505,22 @@ class FnPtrTypeDesc : public TypeDesc return PTR_TypeHandle(m_RetAndArgTypes); } + // Includes a separator between each parameter except for the last. + DWORD GetNumMods() + { + LIMITED_METHOD_CONTRACT; + SUPPORTS_DAC; + return m_NumMods; + } + + FnPtrTypeDescCustomMod* GetCustomModsPointer() + { + LIMITED_METHOD_CONTRACT; + SUPPORTS_DAC; + + return m_pCustomMods; + } + #ifndef DACCESS_COMPILE // Returns TRUE if all return and argument types are externally visible. BOOL IsExternallyVisible() const; @@ -507,13 +537,45 @@ class FnPtrTypeDesc : public TypeDesc void EnumMemoryRegions(CLRDataEnumMemoryFlags flags); #endif //DACCESS_COMPILE + OBJECTREF GetManagedClassObject(); + + OBJECTREF GetManagedClassObjectIfExists() + { + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + OBJECTREF objRet = NULL; + GET_LOADERHANDLE_VALUE_FAST(GetLoaderAllocator(), m_hExposedClassObject, &objRet); + return objRet; + } + OBJECTREF GetManagedClassObjectFast() + { + LIMITED_METHOD_CONTRACT; + + OBJECTREF objRet = NULL; + LoaderAllocator::GetHandleValueFast(m_hExposedClassObject, &objRet); + return objRet; + } + protected: + // Handle back to the internal reflection Type object + LOADERHANDLE m_hExposedClassObject; + // Number of arguments DWORD m_NumArgs; // Calling convention (actually just a single byte) DWORD m_CallConv; + // Custom modifiers + DWORD m_NumMods; // Includes a separator between each parameter except for the last. + FnPtrTypeDescCustomMod* m_pCustomMods; + // Return type first, then argument types TypeHandle m_RetAndArgTypes[1]; }; // class FnPtrTypeDesc diff --git a/src/coreclr/vm/typehandle.cpp b/src/coreclr/vm/typehandle.cpp index c0b194c86e9fac..e7c2402e297de9 100644 --- a/src/coreclr/vm/typehandle.cpp +++ b/src/coreclr/vm/typehandle.cpp @@ -1098,8 +1098,7 @@ OBJECTREF TypeHandle::GetManagedClassObject() const return ((TypeVarTypeDesc*)AsTypeDesc())->GetManagedClassObject(); case ELEMENT_TYPE_FNPTR: - // A function pointer is mapped into typeof(IntPtr). It results in a loss of information. - return CoreLibBinder::GetElementType(ELEMENT_TYPE_I)->GetManagedClassObject(); + return ((FnPtrTypeDesc*)AsTypeDesc())->GetManagedClassObject(); default: _ASSERTE(!"Bad Element Type"); @@ -1459,7 +1458,12 @@ TypeKey TypeHandle::GetTypeKey() const { CONSISTENCY_CHECK(etype == ELEMENT_TYPE_FNPTR); FnPtrTypeDesc* pFTD = (FnPtrTypeDesc*) pTD; - TypeKey tk(pFTD->GetCallConv(), pFTD->GetNumArgs(), pFTD->GetRetAndArgTypesPointer()); + TypeKey tk( + pFTD->GetCallConv(), + pFTD->GetNumArgs(), + pFTD->GetRetAndArgTypesPointer(), + pFTD->GetNumMods(), + pFTD->GetCustomModsPointer()); return tk; } } diff --git a/src/coreclr/vm/typehandle.h b/src/coreclr/vm/typehandle.h index 80a0e78865802e..73da23b733cae9 100644 --- a/src/coreclr/vm/typehandle.h +++ b/src/coreclr/vm/typehandle.h @@ -37,7 +37,7 @@ namespace Generics { class RecursionGraph; } struct CORINFO_CLASS_STRUCT_; typedef DPTR(class TypeVarTypeDesc) PTR_TypeVarTypeDesc; -typedef SPTR(class FnPtrTypeDesc) PTR_FnPtrTypeDesc; +typedef DPTR(class FnPtrTypeDesc) PTR_FnPtrTypeDesc; typedef DPTR(class ParamTypeDesc) PTR_ParamTypeDesc; typedef DPTR(class TypeDesc) PTR_TypeDesc; typedef DPTR(class TypeHandle) PTR_TypeHandle; @@ -460,6 +460,8 @@ class TypeHandle // PTR BOOL IsPointer() const; + BOOL IsUnmanagedFunctionPointer() const; + // True if this type *is* a formal generic type parameter or any component of it is a formal generic type parameter BOOL ContainsGenericVariables(BOOL methodOnly=FALSE) const; diff --git a/src/coreclr/vm/typehandle.inl b/src/coreclr/vm/typehandle.inl index ffe668a901f76c..58300c18f471bd 100644 --- a/src/coreclr/vm/typehandle.inl +++ b/src/coreclr/vm/typehandle.inl @@ -264,8 +264,7 @@ FORCEINLINE OBJECTREF TypeHandle::GetManagedClassObjectFast() const break; case ELEMENT_TYPE_FNPTR: - // A function pointer is mapped into typeof(IntPtr). It results in a loss of information. - o = CoreLibBinder::GetElementType(ELEMENT_TYPE_I)->GetManagedClassObjectIfExists(); + o = dac_cast(AsTypeDesc())->GetManagedClassObjectFast();//GetManagedClassObjectIfExists(); break; default: diff --git a/src/coreclr/vm/typehash.cpp b/src/coreclr/vm/typehash.cpp index dfc355c3fafbad..4008a1b10ae5fc 100644 --- a/src/coreclr/vm/typehash.cpp +++ b/src/coreclr/vm/typehash.cpp @@ -182,6 +182,8 @@ static DWORD HashFnPtrType(BYTE callConv, DWORD numArgs, TypeHandle *retAndArgTy dwHash = ((dwHash << 5) + dwHash) ^ retAndArgTypes[i].AsTAddr(); } + // Currently we are not hashing on the custom mods. + return (DWORD)dwHash; } @@ -223,7 +225,10 @@ static DWORD HashTypeHandle(TypeHandle t) else if (t.IsFnPtrType()) { FnPtrTypeDesc* pTD = t.AsFnPtrType(); - retVal = HashFnPtrType(pTD->GetCallConv(), pTD->GetNumArgs(), pTD->GetRetAndArgTypesPointer()); + retVal = HashFnPtrType( + pTD->GetCallConv(), + pTD->GetNumArgs(), + pTD->GetRetAndArgTypesPointer()); } else if (t.IsGenericVariable()) { @@ -314,11 +319,13 @@ EETypeHashEntry_t *EETypeHashTable::FindItem(TypeKey* pKey) BYTE callConv = pKey->GetCallConv(); DWORD numArgs = pKey->GetNumArgs(); TypeHandle *retAndArgTypes = pKey->GetRetAndArgTypes(); + DWORD numCustomMods = pKey->GetNumMods(); + FnPtrTypeDescCustomMod *customMods = pKey->GetCustomMods(); pSearch = BaseFindFirstEntryByHash(dwHash, &sContext); while (pSearch) { - if (CompareFnPtrType(pSearch->GetTypeHandle(), callConv, numArgs, retAndArgTypes)) + if (CompareFnPtrType(pSearch->GetTypeHandle(), callConv, numArgs, retAndArgTypes, numCustomMods, customMods)) { result = pSearch; break; @@ -436,7 +443,14 @@ BOOL EETypeHashTable::CompareInstantiatedType(TypeHandle t, Module *pModule, mdT return TRUE; } -BOOL EETypeHashTable::CompareFnPtrType(TypeHandle t, BYTE callConv, DWORD numArgs, TypeHandle *retAndArgTypes) +// See also TypeKey::Equals for similar comparison logic. +BOOL EETypeHashTable::CompareFnPtrType( + TypeHandle t, + BYTE callConv, + DWORD numArgs, + TypeHandle *retAndArgTypes, + DWORD numMods, + FnPtrTypeDescCustomMod *customMods) { CONTRACTL { @@ -446,6 +460,7 @@ BOOL EETypeHashTable::CompareFnPtrType(TypeHandle t, BYTE callConv, DWORD numArg MODE_ANY; PRECONDITION(CheckPointer(t)); PRECONDITION(CheckPointer(retAndArgTypes)); + PRECONDITION(CheckPointer(customMods, NULL_OK)); SUPPORTS_DAC; } CONTRACTL_END @@ -470,8 +485,23 @@ BOOL EETypeHashTable::CompareFnPtrType(TypeHandle t, BYTE callConv, DWORD numArg } } + if (numMods != pTD->GetNumMods()) + { + return FALSE; + } + + FnPtrTypeDescCustomMod *customModTypes2 = pTD->GetCustomModsPointer(); + for (DWORD i = 0; i < numMods; i++) + { + if ((customModTypes2[i].elementType != customMods[i].elementType) || + (customModTypes2[i].typeHandle != customMods[i].typeHandle)) + { + return FALSE; + } + } + return TRUE; - + #else DacNotImpl(); return FALSE; diff --git a/src/coreclr/vm/typehash.h b/src/coreclr/vm/typehash.h index b706ee64a44932..6ed10e0b918855 100644 --- a/src/coreclr/vm/typehash.h +++ b/src/coreclr/vm/typehash.h @@ -136,7 +136,7 @@ class EETypeHashTable : public DacEnumerableHashTablem_kind == ELEMENT_TYPE_FNPTR); - if (pKey1->u.asFnPtr.m_callConv != pKey2->u.asFnPtr.m_callConv || + + // See also EETypeHashTable::CompareFnPtrType for similar comparison logic. + + BYTE callConv = pKey1->u.asFnPtr.m_callConv; + if (callConv != pKey2->u.asFnPtr.m_callConv || pKey1->u.asFnPtr.m_numArgs != pKey2->u.asFnPtr.m_numArgs) return FALSE; @@ -253,6 +279,24 @@ class TypeKey if (pKey1->u.asFnPtr.m_pRetAndArgTypes[i] != pKey2->u.asFnPtr.m_pRetAndArgTypes[i]) return FALSE; } + + // Match the custom modifiers. + uint32_t numCustomMods = pKey1->u.asFnPtr.m_numMods; + if (numCustomMods != pKey2->u.asFnPtr.m_numMods) + { + return FALSE; + } + + for (DWORD i = 0; i < numCustomMods; i++) + { + FnPtrTypeDescCustomMod customModTypes1 = pKey1->u.asFnPtr.m_pCustomMods[i]; + FnPtrTypeDescCustomMod customModTypes2 = pKey2->u.asFnPtr.m_pCustomMods[i]; + + if ((customModTypes1.elementType != customModTypes2.elementType) || + (customModTypes1.typeHandle != customModTypes2.typeHandle)) + return FALSE; + } + return TRUE; } } diff --git a/src/coreclr/vm/typestring.cpp b/src/coreclr/vm/typestring.cpp index 0a05972721a1a2..29b5d7efad0312 100644 --- a/src/coreclr/vm/typestring.cpp +++ b/src/coreclr/vm/typestring.cpp @@ -223,6 +223,36 @@ HRESULT TypeNameBuilder::AddName(LPCWSTR szName, LPCWSTR szNamespace) return hr; } +HRESULT TypeNameBuilder::AddNameNoEscaping(LPCWSTR szName) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + if (!szName) + return Fail(); + + if (!CheckParseState(ParseStateSTART | ParseStateNAME)) + return Fail(); + + HRESULT hr = S_OK; + + m_parseState = ParseStateNAME; + + if (m_bNestedName) + Append(W('+')); + + m_bNestedName = TRUE; + + Append(szName); + + return hr; +} + HRESULT TypeNameBuilder::OpenGenericArguments() { WRAPPER_NO_CONTRACT; @@ -760,8 +790,47 @@ void TypeString::AppendType(TypeNameBuilder& tnb, TypeHandle ty, Instantiation t // ...or function pointer else if (ty.IsFnPtrType()) { - // Don't attempt to format this currently, it may trigger GC due to fixups. - tnb.AddName(W("(fnptr)")); + // Currently function pointers return NULL for FullName and AssemblyQualifiedName and "*()" for Name. + // We need a grammar update in order to support parsing (see https://docs.microsoft.com/dotnet/framework/reflection-and-codedom/specifying-fully-qualified-type-names) + if (format & FormatNamespace) + { + FnPtrTypeDesc* fnPtr = ty.AsFnPtrType(); + TypeHandle *retAndArgTypes = fnPtr->GetRetAndArgTypesPointer(); + + StackSString ss; + AppendType(ss, retAndArgTypes[0], format); + + SString ssOpening(SString::Literal, "("); + ss += ssOpening; + + SString ssComma(SString::Literal, ", "); + DWORD cArgs = fnPtr->GetNumArgs(); + for (DWORD i = 1; i <= cArgs; i++) + { + if (i != 1) + ss += ssComma; + + AppendType(ss, retAndArgTypes[i], format); + } + + if ((fnPtr->GetCallConv() & IMAGE_CEE_CS_CALLCONV_MASK) == IMAGE_CEE_CS_CALLCONV_VARARG) + { + if (cArgs) + ss += ssComma; + + SString ssEllipsis(SString::Literal, "..."); + ss += ssEllipsis; + } + + SString ssClosing(SString::Literal, ")"); + ss += ssClosing; + + tnb.AddNameNoEscaping(ss); + } + else + { + tnb.AddNameNoEscaping(W("*()")); + } } // ...otherwise it's just a plain type def or an instantiated type diff --git a/src/coreclr/vm/typestring.h b/src/coreclr/vm/typestring.h index df54ed76b5bec5..13ec1f4e42a3d3 100644 --- a/src/coreclr/vm/typestring.h +++ b/src/coreclr/vm/typestring.h @@ -43,6 +43,7 @@ class TypeNameBuilder HRESULT CloseGenericArgument(); HRESULT AddName(LPCWSTR szName); HRESULT AddName(LPCWSTR szName, LPCWSTR szNamespace); + HRESULT AddNameNoEscaping(LPCWSTR szName); HRESULT AddPointer(); HRESULT AddByRef(); HRESULT AddSzArray(); diff --git a/src/libraries/Common/tests/System/FunctionPointerTests.cs b/src/libraries/Common/tests/System/FunctionPointerTests.cs new file mode 100644 index 00000000000000..63fa6d1e7abf19 --- /dev/null +++ b/src/libraries/Common/tests/System/FunctionPointerTests.cs @@ -0,0 +1,501 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Linq; +using System.Reflection; +using System.Reflection.Tests; +using System.Runtime.CompilerServices; +using Xunit; + +namespace System.Tests.Types +{ + public partial class FunctionPointerTests + { + private const BindingFlags Bindings = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly; + + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/71095", TestRuntimes.Mono)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/71883", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] + public static unsafe void TestTypeMembers() + { + // Get an arbitrary function pointer + TypeInfo t = (TypeInfo)typeof(FunctionPointerHolder).Project().GetField(nameof(FunctionPointerHolder.ToString_1), Bindings).FieldType; + + // Function pointer relevant members: + Assert.Equal("System.Void()", t.ToString()); + Assert.Null(t.FullName); + Assert.Null(t.AssemblyQualifiedName); + Assert.Equal("*()", t.Name); + Assert.Null(t.Namespace); + Assert.True(t.IsFunctionPointer); + Assert.False(t.IsPointer); // A function pointer is not compatible with IsPointer semantics. + Assert.False(t.IsUnmanagedFunctionPointer); + + // Common for all function pointers: + Assert.NotNull(t.Assembly); + Assert.Equal(TypeAttributes.Public, t.Attributes); + Assert.Null(t.BaseType); + Assert.False(t.ContainsGenericParameters); + Assert.False(t.ContainsGenericParameters); + Assert.False(t.DeclaredConstructors.Any()); + Assert.False(t.DeclaredEvents.Any()); + Assert.False(t.DeclaredFields.Any()); + Assert.False(t.DeclaredMembers.Any()); + Assert.False(t.DeclaredMethods.Any()); + Assert.False(t.DeclaredNestedTypes.Any()); + Assert.False(t.DeclaredProperties.Any()); + Assert.Null(t.DeclaringType); + Assert.Equal(Guid.Empty, t.GUID); + Assert.Throws(() => t.GenericParameterAttributes); + Assert.Throws(() => t.GenericParameterPosition); + Assert.Equal(0, t.GenericTypeArguments.Length); + Assert.Equal(0, t.GenericTypeParameters.Length); + Assert.False(t.HasElementType); + Assert.False(t.IsAbstract); + Assert.True(t.IsAnsiClass); + Assert.False(t.IsArray); + Assert.False(t.IsAutoClass); + Assert.True(t.IsAutoLayout); + Assert.False(t.IsByRef); + Assert.False(t.IsByRefLike); + Assert.False(t.IsCOMObject); + Assert.True(t.IsClass); + Assert.False(t.IsAbstract); + Assert.False(t.IsConstructedGenericType); + Assert.False(t.IsContextful); + Assert.False(t.IsEnum); + Assert.False(t.IsExplicitLayout); + Assert.False(t.IsGenericMethodParameter); + Assert.False(t.IsGenericParameter); + Assert.False(t.IsGenericType); + Assert.False(t.IsGenericTypeDefinition); + Assert.False(t.IsGenericTypeParameter); + Assert.False(t.IsImport); + Assert.False(t.IsInterface); + Assert.False(t.IsLayoutSequential); + Assert.False(t.IsMarshalByRef); + Assert.False(t.IsNested); + Assert.False(t.IsNestedAssembly); + Assert.False(t.IsNestedFamANDAssem); + Assert.False(t.IsNestedFamORAssem); + Assert.False(t.IsNestedFamily); + Assert.False(t.IsNestedPrivate); + Assert.False(t.IsNestedPublic); + Assert.False(t.IsNotPublic); + Assert.False(t.IsPrimitive); + Assert.True(t.IsPublic); + Assert.False(t.IsSZArray); + Assert.False(t.IsSealed); + + try + { + // MetadataLoadContext throws here. + Assert.True(t.IsSecurityCritical); + Assert.False(t.IsSecuritySafeCritical); + Assert.False(t.IsSecurityTransparent); + } + catch (InvalidOperationException) { } + + Assert.False(t.IsSerializable); + Assert.False(t.IsSignatureType); + Assert.False(t.IsSpecialName); + Assert.False(t.IsTypeDefinition); + Assert.False(t.IsUnicodeClass); + Assert.False(t.IsValueType); + Assert.False(t.IsVariableBoundArray); + Assert.True(t.IsVisible); + Assert.Equal(MemberTypes.TypeInfo, t.MemberType); + Assert.True(t.MetadataToken != 0); + Assert.NotNull(t.Module); + Assert.Null(t.ReflectedType); + Assert.Null(t.TypeInitializer); + + // Select methods + Assert.Throws(() => t.GetArrayRank()); + Assert.Null(t.GetElementType()); + } + + private static void MyMethod(){} + + + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/71095", TestRuntimes.Mono)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/71883", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] + public static unsafe void TestNonFunctionPointerThrows() + { + Assert.Throws(() => typeof(int).GetFunctionPointerCallingConventions()); + Assert.Throws(() => typeof(int).GetFunctionPointerParameterInfos()); + Assert.Throws(() => typeof(int).GetFunctionPointerReturnParameter()); + } + + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/71095", TestRuntimes.Mono)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/71883", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] + public static unsafe void TestToString() + { + // Function pointer types are inline in metadata and can't be loaded independently so they do not support the + // MetadataLoadContext Type.Project() test extension so we use fields and project on the owning class. + Assert.Equal("System.Void()", GetType(1).ToString()); // delegate* + Assert.Equal("System.Void()", GetType(2).ToString()); // delegate*unmanaged + Assert.Equal("System.Int32()", GetType(3).ToString()); // delegate* + Assert.Equal("System.Int32()*", GetType(4).ToString()); // delegate** + Assert.Equal("System.Int32()[]", GetType(5).ToString()); // delegate*[] + Assert.Equal("System.Int32()", GetType(6). + GetElementType().ToString()); // delegate*[] + Assert.Equal("System.Int32()*[]", GetType(7).ToString()); // delegate**[] + Assert.Equal("System.Int32()()", GetType(8).ToString()); // delegate*> + Assert.Equal("System.Boolean(System.String(System.Int32))", + GetType(9).ToString()); // delegate*, bool> + + Type GetType(int i) => typeof(FunctionPointerHolder).Project().GetField("ToString_" + i, Bindings).FieldType; + } + + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/71095", TestRuntimes.Mono)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/71883", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] + public static unsafe void TestFunctionPointerReturn() + { + Type t = typeof(FunctionPointerHolder).Project(); + + MethodInfo m1 = t.GetMethod(nameof(FunctionPointerHolder.MethodReturnValue1), Bindings); + Type fcnPtr1 = m1.ReturnType; + Assert.True(fcnPtr1.IsFunctionPointer); + + MethodInfo m2 = t.GetMethod(nameof(FunctionPointerHolder.MethodReturnValue2), Bindings); + Type fcnPtr2 = m2.ReturnType; + Assert.True(fcnPtr2.IsFunctionPointer); + + Assert.True(fcnPtr1.Equals(fcnPtr2)); + } + + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/71095", TestRuntimes.Mono)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/71883", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] + public static unsafe void TestFunctionUnmanagedPointerReturn_DifferentReturnValue() + { + Type t = typeof(FunctionPointerHolder).Project(); + + MethodInfo m1 = t.GetMethod(nameof(FunctionPointerHolder.MethodUnmanagedReturnValue1), Bindings); + Type fcnPtr1 = m1.ReturnType; + Assert.True(fcnPtr1.IsFunctionPointer); + + MethodInfo m2 = t.GetMethod(nameof(FunctionPointerHolder.MethodUnmanagedReturnValue2), Bindings); + Type fcnPtr2 = m2.ReturnType; + Assert.True(fcnPtr2.IsFunctionPointer); + Assert.False(fcnPtr1.Equals(fcnPtr2)); + } + + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/71095", TestRuntimes.Mono)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/71883", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] + public static unsafe void TestFunctionUnmanagedPointerReturn_DifferentCallingConventions() + { + Type t = typeof(FunctionPointerHolder).Project(); + + MethodInfo m1 = t.GetMethod(nameof(FunctionPointerHolder.MethodUnmanagedReturnValue_DifferentCallingConventions1), Bindings); + Type fcnPtr1 = m1.ReturnType; + Assert.True(fcnPtr1.IsFunctionPointer); + Assert.True(fcnPtr1.IsUnmanagedFunctionPointer); + + MethodInfo m2 = t.GetMethod(nameof(FunctionPointerHolder.MethodUnmanagedReturnValue_DifferentCallingConventions2), Bindings); + Type fcnPtr2 = m2.ReturnType; + Assert.True(fcnPtr2.IsFunctionPointer); + Assert.True(fcnPtr2.IsUnmanagedFunctionPointer); + + // The modopts are considered part of the "type key" + Assert.NotSame(fcnPtr1, fcnPtr2); + Assert.False(fcnPtr1.Equals(fcnPtr2)); + + FunctionPointerParameterInfo retInfo = fcnPtr1.GetFunctionPointerReturnParameter(); + Assert.Equal(typeof(int).Project(), retInfo.ParameterType); + + Type[] modOpts = fcnPtr1.GetFunctionPointerReturnParameter().GetOptionalCustomModifiers(); + Assert.Equal(2, modOpts.Length); + } + + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/71095", TestRuntimes.Mono)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/71883", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] + public static unsafe void TestRequiredModifiers() + { + Type t = typeof(FunctionPointerHolder).Project(); + MethodInfo m = t.GetMethod(nameof(FunctionPointerHolder.RequiredModifiers), Bindings); + Type fcnPtr1 = m.ReturnType; + + FunctionPointerParameterInfo[] parameters = fcnPtr1.GetFunctionPointerParameterInfos(); + Assert.Equal(2, parameters.Length); + Assert.Equal(1, parameters[0].GetRequiredCustomModifiers().Length); + Assert.Equal(typeof(Runtime.InteropServices.InAttribute).Project(), parameters[0].GetRequiredCustomModifiers()[0]); + Assert.Equal(1, parameters[1].GetRequiredCustomModifiers().Length); + Assert.Equal(typeof(Runtime.InteropServices.OutAttribute).Project(), parameters[1].GetRequiredCustomModifiers()[0]); + } + + [Theory] + [InlineData(nameof(FunctionPointerHolder.MethodReturnValue1), + "MethodReturnValue1()", + "Int32", + "System.Int32()")] + [InlineData(nameof(FunctionPointerHolder.SeveralArguments), + "SeveralArguments()", + "Double", + "System.Double(System.String, System.Boolean*&, System.Tests.Types.FunctionPointerTests+FunctionPointerHolder+MyClass, System.Tests.Types.FunctionPointerTests+FunctionPointerHolder+MyStruct&)", + "String", "Boolean*&", "MyClass", "MyStruct&")] + [ActiveIssue("https://github.com/dotnet/runtime/issues/71095", TestRuntimes.Mono)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/71883", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] + public static unsafe void TestMethod( + string methodName, + string methodToStringPostfix, + string expectedFcnPtrReturnName, + string expectedFcnPtrFullName, + params string[] expectedArgNames) + { + Type t = typeof(FunctionPointerHolder).Project(); + MethodInfo m = t.GetMethod(methodName, Bindings); + Assert.Equal(expectedFcnPtrFullName + " " + methodToStringPostfix, m.ToString()); + + Type fnPtrType = m.ReturnType; + Assert.Null(fnPtrType.FullName); + Assert.Null(fnPtrType.AssemblyQualifiedName); + Assert.Equal("*()", fnPtrType.Name); + + VerifyArg(fnPtrType.GetFunctionPointerReturnParameter(), expectedFcnPtrReturnName); + + for (int i = 0; i < expectedArgNames.Length; i++) + { + VerifyArg(fnPtrType.GetFunctionPointerParameterInfos()[i], expectedArgNames[i]); + } + + static void VerifyArg(FunctionPointerParameterInfo paramInfo, string expected) + { + Assert.Equal(expected, paramInfo.ParameterType.Name); + Assert.Equal(paramInfo.ParameterType.ToString(), paramInfo.ToString()); + Assert.Null(paramInfo.GetType().DeclaringType); + } + } + + [Theory] + [InlineData(nameof(FunctionPointerHolder.Prop_Int), "System.Int32()")] + [InlineData(nameof(FunctionPointerHolder.Prop_MyClass), "System.Tests.Types.FunctionPointerTests+FunctionPointerHolder+MyClass()")] + [ActiveIssue("https://github.com/dotnet/runtime/issues/71095", TestRuntimes.Mono)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/71883", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] + public static unsafe void TestProperty(string name, string expectedToString) + { + Type t = typeof(FunctionPointerHolder).Project(); + PropertyInfo p = t.GetProperty(name, Bindings); + Assert.Equal(expectedToString + " " + name, p.ToString()); + + Type fnPtrType = p.PropertyType; + Assert.Equal(expectedToString, fnPtrType.ToString()); + VerifyFieldOrProperty(fnPtrType); + } + + [Theory] + [InlineData(nameof(FunctionPointerHolder.Field_Int), "System.Int32()")] + [InlineData(nameof(FunctionPointerHolder.Field_MyClass), "System.Tests.Types.FunctionPointerTests+FunctionPointerHolder+MyClass()")] + [ActiveIssue("https://github.com/dotnet/runtime/issues/71095", TestRuntimes.Mono)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/71883", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] + public static unsafe void TestField(string name, string expectedToString) + { + Type t = typeof(FunctionPointerHolder).Project(); + FieldInfo f = t.GetField(name, Bindings); + Assert.Equal(expectedToString + " " + name, f.ToString()); + + Type fnPtrType = f.FieldType; + Assert.Equal(expectedToString, fnPtrType.ToString()); + VerifyFieldOrProperty(fnPtrType); + } + + private static void VerifyFieldOrProperty(Type fnPtrType) + { + Assert.Null(fnPtrType.FullName); + Assert.Null(fnPtrType.AssemblyQualifiedName); + Assert.Equal("*()", fnPtrType.Name); + Assert.Null(fnPtrType.DeclaringType); + Assert.Null(fnPtrType.BaseType); + Assert.Equal(Type.EmptyTypes, fnPtrType.GetFunctionPointerCallingConventions()); + Assert.Equal(Type.EmptyTypes, fnPtrType.GetFunctionPointerReturnParameter().GetRequiredCustomModifiers()); + } + + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/71095", TestRuntimes.Mono)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/71883", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] + public static unsafe void TestManagedCallingConvention() + { + Type t = typeof(FunctionPointerHolder).Project(); + MethodInfo m = t.GetMethod(nameof(FunctionPointerHolder.MethodCallConv_Managed), Bindings); + Type fnPtrType = m.GetParameters()[0].ParameterType; + Type[] callConvs = fnPtrType.GetFunctionPointerCallingConventions(); + Assert.Equal(0, callConvs.Length); + Assert.True(fnPtrType.IsFunctionPointer); + Assert.False(fnPtrType.IsUnmanagedFunctionPointer); + + FunctionPointerParameterInfo returnParam = fnPtrType.GetFunctionPointerReturnParameter(); + Assert.Equal(0, returnParam.GetOptionalCustomModifiers().Length); + Assert.Equal(0, returnParam.GetRequiredCustomModifiers().Length); + } + + [Theory] + [InlineData(nameof(FunctionPointerHolder.MethodCallConv_Cdecl), typeof(CallConvCdecl))] + [InlineData(nameof(FunctionPointerHolder.MethodCallConv_Stdcall), typeof(CallConvStdcall))] + [InlineData(nameof(FunctionPointerHolder.MethodCallConv_Thiscall), typeof(CallConvThiscall))] + [InlineData(nameof(FunctionPointerHolder.MethodCallConv_Fastcall), typeof(CallConvFastcall))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/71095", TestRuntimes.Mono)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/71883", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] + public static unsafe void TestBaseCallingConventions(string methodName, Type callingConventionRuntime) + { + Type callingConvention = callingConventionRuntime.Project(); + Type t = typeof(FunctionPointerHolder).Project(); + MethodInfo m = t.GetMethod(methodName, Bindings); + Type fnPtrType = m.GetParameters()[0].ParameterType; + Type[] callConvs = fnPtrType.GetFunctionPointerCallingConventions(); + + Assert.Equal(1, callConvs.Length); + Assert.Equal(callingConvention, callConvs[0]); + Assert.True(fnPtrType.IsFunctionPointer); + Assert.True(fnPtrType.IsUnmanagedFunctionPointer); + + FunctionPointerParameterInfo returnParam = fnPtrType.GetFunctionPointerReturnParameter(); + Assert.Equal(1, returnParam.GetOptionalCustomModifiers().Length); + Assert.Equal(callingConvention, returnParam.GetOptionalCustomModifiers()[0]); + } + + [Theory] + [InlineData(nameof(FunctionPointerHolder.MethodCallConv_Cdecl_SuppressGCTransition), typeof(CallConvCdecl))] + [InlineData(nameof(FunctionPointerHolder.MethodCallConv_Stdcall_SuppressGCTransition), typeof(CallConvStdcall))] + [InlineData(nameof(FunctionPointerHolder.MethodCallConv_Thiscall_SuppressGCTransition), typeof(CallConvThiscall))] + [InlineData(nameof(FunctionPointerHolder.MethodCallConv_Fastcall_SuppressGCTransition), typeof(CallConvFastcall))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/71095", TestRuntimes.Mono)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/71883", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] + public static unsafe void TestOptionalCallingConventions(string methodName, Type callingConventionRuntime) + { + Type suppressGcTransitionType = typeof(CallConvSuppressGCTransition).Project(); + Type callingConvention = callingConventionRuntime.Project(); + Type t = typeof(FunctionPointerHolder).Project(); + MethodInfo m = t.GetMethod(methodName, Bindings); + Type fnPtrType = m.GetParameters()[0].ParameterType; + + FunctionPointerParameterInfo returnParam = fnPtrType.GetFunctionPointerReturnParameter(); + Type[] callConvs = fnPtrType.GetFunctionPointerCallingConventions(); + Assert.Equal(2, callConvs.Length); + Assert.Equal(suppressGcTransitionType, callConvs[0]); + Assert.Equal(callingConvention, callConvs[1]); + Assert.True(fnPtrType.IsFunctionPointer); + Assert.True(fnPtrType.IsUnmanagedFunctionPointer); + Assert.Equal(2, returnParam.GetOptionalCustomModifiers().Length); + Assert.Equal(suppressGcTransitionType, returnParam.GetOptionalCustomModifiers()[0]); + Assert.Equal(callingConvention, returnParam.GetOptionalCustomModifiers()[1]); + Assert.Equal(0, returnParam.GetRequiredCustomModifiers().Length); + } + + [Theory] + [InlineData(nameof(FunctionPointerHolder.Field_Int), nameof(FunctionPointerHolder.Field_DateOnly))] + [InlineData(nameof(FunctionPointerHolder.Field_DateOnly), nameof(FunctionPointerHolder.Field_Int))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/71095", TestRuntimes.Mono)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/71883", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] + public static unsafe void TestSigEqualityInDifferentModule_Field(string name, string otherName) + { + Type fph1 = typeof(FunctionPointerHolder).Project(); + Type fph2 = typeof(FunctionPointerHolderSeparateModule).Project(); + Assert.True(GetFuncPtr(fph1, name).IsEqualOrReferenceEquals(GetFuncPtr(fph2, name))); + + // Verify other combinations fail + Assert.False(GetFuncPtr(fph1, name).IsEqualOrReferenceEquals(GetFuncPtr(fph1, otherName))); + Assert.False(GetFuncPtr(fph1, name).IsEqualOrReferenceEquals(GetFuncPtr(fph1, otherName))); + Assert.False(GetFuncPtr(fph2, name).IsEqualOrReferenceEquals(GetFuncPtr(fph2, otherName))); + Assert.False(GetFuncPtr(fph2, name).IsEqualOrReferenceEquals(GetFuncPtr(fph2, otherName))); + + static Type GetFuncPtr(Type owner, string name) => owner.GetField(name, Bindings).FieldType; + } + + [Theory] + [InlineData(nameof(FunctionPointerHolder.Prop_Int), nameof(FunctionPointerHolder.Prop_DateOnly))] + [InlineData(nameof(FunctionPointerHolder.Prop_DateOnly), nameof(FunctionPointerHolder.Prop_Int))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/71095", TestRuntimes.Mono)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/71883", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] + public static unsafe void TestSigEqualityInDifferentModule_Property(string name, string otherName) + { + Type fph1 = typeof(FunctionPointerHolder).Project(); + Type fph2 = typeof(FunctionPointerHolderSeparateModule).Project(); + Assert.True(GetFuncPtr(fph1, name).IsEqualOrReferenceEquals(GetFuncPtr(fph2, name))); + + // Verify other combinations fail + Assert.False(GetFuncPtr(fph1, name).IsEqualOrReferenceEquals(GetFuncPtr(fph1, otherName))); + Assert.False(GetFuncPtr(fph1, name).IsEqualOrReferenceEquals(GetFuncPtr(fph1, otherName))); + Assert.False(GetFuncPtr(fph2, name).IsEqualOrReferenceEquals(GetFuncPtr(fph2, otherName))); + Assert.False(GetFuncPtr(fph2, name).IsEqualOrReferenceEquals(GetFuncPtr(fph2, otherName))); + + static Type GetFuncPtr(Type owner, string name) => owner.GetProperty(name, Bindings).PropertyType; + } + + [Theory] + [InlineData(nameof(FunctionPointerHolder.MethodReturnValue_Int), nameof(FunctionPointerHolder.MethodReturnValue_DateOnly))] + [InlineData(nameof(FunctionPointerHolder.MethodReturnValue_DateOnly), nameof(FunctionPointerHolder.MethodReturnValue_Int))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/71095", TestRuntimes.Mono)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/71883", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] + public static unsafe void TestSigEqualityInDifferentModule_MethodReturn(string name, string otherName) + { + Type fph1 = typeof(FunctionPointerHolder).Project(); + Type fph2 = typeof(FunctionPointerHolderSeparateModule).Project(); + Assert.True(GetFuncPtr(fph1, name).IsEqualOrReferenceEquals(GetFuncPtr(fph2, name))); + + // Verify other combinations fail + Assert.False(GetFuncPtr(fph1, name).IsEqualOrReferenceEquals(GetFuncPtr(fph1, otherName))); + Assert.False(GetFuncPtr(fph1, name).IsEqualOrReferenceEquals(GetFuncPtr(fph1, otherName))); + Assert.False(GetFuncPtr(fph2, name).IsEqualOrReferenceEquals(GetFuncPtr(fph2, otherName))); + Assert.False(GetFuncPtr(fph2, name).IsEqualOrReferenceEquals(GetFuncPtr(fph2, otherName))); + + static Type GetFuncPtr(Type owner, string name) => owner.GetMethod(name, Bindings).ReturnParameter.ParameterType; + } + + public unsafe class FunctionPointerHolder + { + public delegate* ToString_1; + public delegate*unmanaged ToString_2; + public delegate* ToString_3; + public delegate** ToString_4; + public delegate*[] ToString_5; + public delegate*[] ToString_6; + public delegate**[] ToString_7; + public delegate*> ToString_8; + public delegate*, bool> ToString_9; + + public delegate* managed Field_Int; + public delegate* managed Field_DateOnly; // Verify non-primitive + public delegate* managed Field_MyClass; + public delegate* managed Prop_Int { get; } + public delegate* managed Prop_DateOnly { get; } + public delegate* managed Prop_MyClass { get; } + public delegate* managed MethodReturnValue_Int() => default; + public delegate* managed MethodReturnValue_DateOnly() => default; + public delegate* unmanaged MethodUnmanagedReturnValue_Int() => default; + public delegate* unmanaged MethodUnmanagedReturnValue_DateOnly() => default; + + public delegate* managed MethodReturnValue1() => default; + public delegate* managed MethodReturnValue2() => default; + public delegate* unmanaged MethodUnmanagedReturnValue1() => default; + public delegate* unmanaged MethodUnmanagedReturnValue2() => default; + + public delegate* unmanaged[Cdecl, MemberFunction] MethodUnmanagedReturnValue_DifferentCallingConventions1() => default; + public delegate* unmanaged[Stdcall, MemberFunction] MethodUnmanagedReturnValue_DifferentCallingConventions2() => default; + + public delegate* unmanaged[Stdcall, MemberFunction] SeveralArguments() => default; + public delegate* RequiredModifiers() => default; + + // Methods to verify calling conventions and synthesized modopts. + // The non-SuppressGCTransition variants are encoded with the CallKind byte. + // The SuppressGCTransition variants are encoded as modopts (CallKind is "Unmananged"). + public void MethodCallConv_Managed(delegate* managed f) { } + public void MethodCallConv_Cdecl(delegate* unmanaged[Cdecl] f) { } + public void MethodCallConv_Cdecl_SuppressGCTransition(delegate* unmanaged[Cdecl, SuppressGCTransition] f) { } + public void MethodCallConv_Stdcall(delegate* unmanaged[Stdcall] f) { } + public void MethodCallConv_Stdcall_SuppressGCTransition(delegate* unmanaged[Stdcall, SuppressGCTransition] f) { } + public void MethodCallConv_Thiscall(delegate* unmanaged[Thiscall] f) { } + public void MethodCallConv_Thiscall_SuppressGCTransition(delegate* unmanaged[Thiscall, SuppressGCTransition] f) { } + public void MethodCallConv_Fastcall(delegate* unmanaged[Fastcall] f) { } + public void MethodCallConv_Fastcall_SuppressGCTransition(delegate* unmanaged[Fastcall, SuppressGCTransition] f) { } + + public class MyClass { } + public struct MyStruct { } + } + } +} diff --git a/src/libraries/Common/tests/System/TestFunctionPointerAssembly/FunctionPointerHolder.cs b/src/libraries/Common/tests/System/TestFunctionPointerAssembly/FunctionPointerHolder.cs new file mode 100644 index 00000000000000..0013f93e0f9abf --- /dev/null +++ b/src/libraries/Common/tests/System/TestFunctionPointerAssembly/FunctionPointerHolder.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Reflection; +using System.Threading; + +public unsafe class FunctionPointerHolderSeparateModule +{ + public delegate* managed Field_Int; + public delegate* managed Field_DateOnly; // Verify non-primitive which will have its own Rid + public delegate* managed Prop_Int { get; } + public delegate* managed Prop_DateOnly { get; } + public delegate* managed MethodReturnValue_Int() => default; + public delegate* managed MethodReturnValue_DateOnly() => default; + public delegate* unmanaged MethodUnmanagedReturnValue_Int() => default; + public delegate* unmanaged MethodUnmanagedReturnValue_DateOnly() => default; +} diff --git a/src/libraries/Common/tests/System/TestFunctionPointerAssembly/TestFunctionPointerAssembly.csproj b/src/libraries/Common/tests/System/TestFunctionPointerAssembly/TestFunctionPointerAssembly.csproj new file mode 100644 index 00000000000000..0a8720fa6bbdae --- /dev/null +++ b/src/libraries/Common/tests/System/TestFunctionPointerAssembly/TestFunctionPointerAssembly.csproj @@ -0,0 +1,11 @@ + + + true + 1.0.0.0 + $(NetCoreAppCurrent) + True + + + + + diff --git a/src/libraries/System.Diagnostics.StackTrace/tests/StackTraceTests.cs b/src/libraries/System.Diagnostics.StackTrace/tests/StackTraceTests.cs index e4f909176df160..556a07ed18de3f 100644 --- a/src/libraries/System.Diagnostics.StackTrace/tests/StackTraceTests.cs +++ b/src/libraries/System.Diagnostics.StackTrace/tests/StackTraceTests.cs @@ -316,9 +316,9 @@ public void ToString_NullFrame_ThrowsNullReferenceException() [ActiveIssue("https://github.com/dotnet/runtime/issues/11354", TestRuntimes.Mono)] public unsafe void ToString_FunctionPointerSignature() { - // This is sepate from ToString_Invoke_ReturnsExpected since unsafe cannot be used for iterators + // This is separate from ToString_Invoke_ReturnsExpected since unsafe cannot be used for iterators var stackTrace = FunctionPointerParameter(null); - Assert.Contains("System.Diagnostics.Tests.StackTraceTests.FunctionPointerParameter(IntPtr x)", stackTrace.ToString()); + Assert.Contains("System.Diagnostics.Tests.StackTraceTests.FunctionPointerParameter(*() x)", stackTrace.ToString()); } [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index 3fc2d2014809e2..089f7d149befed 100644 --- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx @@ -3988,4 +3988,7 @@ Cannot load assembly '{0}'. No metadata found for this assembly. + + Method may only be called on a Type for which Type.IsFunctionPointer is true. + diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 3404e7c0df2da6..3d6f31641db8ce 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -616,6 +616,8 @@ + + @@ -628,6 +630,7 @@ + @@ -660,6 +663,7 @@ + diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/FunctionPointerInfo.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/FunctionPointerInfo.cs new file mode 100644 index 00000000000000..d228bec1ba8b67 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/FunctionPointerInfo.cs @@ -0,0 +1,107 @@ +// 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; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace System.Reflection +{ + internal sealed partial class FunctionPointerInfo + { + private const string CallingConventionTypePrefix = "System.Runtime.CompilerServices.CallConv"; + private readonly RuntimeFunctionPointerParameterInfo _returnInfo; + private readonly RuntimeFunctionPointerParameterInfo[] _parameterInfos; + private Type[]? _callingConventions; + + public FunctionPointerParameterInfo ReturnParameter => _returnInfo; + public FunctionPointerParameterInfo[] ParameterInfos => CloneArray(_parameterInfos); + + public Type[] GetCallingConventions() + { + if (_callingConventions == null) + { + ComputeCallingConventions(); + Debug.Assert(_callingConventions != null); + } + + return CloneArray(_callingConventions); + } + + internal void ComputeCallingConventions() + { + if (_callingConventions == null) + { + ArrayBuilder ccBuilder = default; + ArrayBuilder allBuilder = default; + + Type[]? modifiers = GetCustomModifiersFromFunctionPointer(position: 0, required: false); + bool foundCallingConvention = false; + + if (modifiers != null) + { + for (int i = 0; i < modifiers.Length; i++) + { + Type type = modifiers[i]; + allBuilder.Add(type); + if (type.FullName!.StartsWith(CallingConventionTypePrefix, StringComparison.Ordinal)) + { + ccBuilder.Add(type); + + if (type == typeof(CallConvCdecl) || + type == typeof(CallConvFastcall) || + type == typeof(CallConvStdcall) || + type == typeof(CallConvThiscall)) + { + foundCallingConvention = true; + } + } + } + } + + // Normalize the calling conventions. + if (!foundCallingConvention) + { + Type? callConv = null; + + switch (CallingConvention) + { + case MdSigCallingConvention.C: + callConv = typeof(CallConvCdecl); + break; + case MdSigCallingConvention.StdCall: + callConv = typeof(CallConvStdcall); + break; + case MdSigCallingConvention.ThisCall: + callConv = typeof(CallConvThiscall); + break; + case MdSigCallingConvention.FastCall: + callConv = typeof(CallConvFastcall); + break; + } + + if (callConv != null) + { + allBuilder.Add(callConv); + ccBuilder.Add(callConv); + } + } + + _returnInfo.SetCustomModifiersForReturnType(allBuilder.Count == 0 ? Type.EmptyTypes : allBuilder.ToArray()); + _callingConventions = ccBuilder.Count == 0 ? Type.EmptyTypes : ccBuilder.ToArray(); + } + } + + private static T[] CloneArray(T[] original) + { + if (original.Length == 0) + { + return original; + } + + T[] copy = new T[original.Length]; + Array.Copy(sourceArray: original, sourceIndex: 0, destinationArray: copy, destinationIndex: 0, length: original.Length); + return copy; + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/FunctionPointerParameterInfo.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/FunctionPointerParameterInfo.cs new file mode 100644 index 00000000000000..b0870c0e61c62b --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/FunctionPointerParameterInfo.cs @@ -0,0 +1,13 @@ +// 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 +{ + public abstract class FunctionPointerParameterInfo + { + public abstract Type ParameterType { get; } + public abstract Type[] GetOptionalCustomModifiers(); + public abstract Type[] GetRequiredCustomModifiers(); + public override string? ToString() => ParameterType.ToString(); + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/MdSigCallingConvention.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/MdSigCallingConvention.cs new file mode 100644 index 00000000000000..eaa3aa2be4c9a0 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/MdSigCallingConvention.cs @@ -0,0 +1,31 @@ +// 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 +{ + /// + /// Represents the first byte of a method signature. + /// Calling conventions have been extended into modopts for Unmanaged. + /// + [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 + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeFunctionPointerParameterInfo.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeFunctionPointerParameterInfo.cs new file mode 100644 index 00000000000000..660b292ccd36a6 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeFunctionPointerParameterInfo.cs @@ -0,0 +1,13 @@ +// 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 sealed partial class RuntimeFunctionPointerParameterInfo : FunctionPointerParameterInfo + { + private readonly Type _parameterType; + + public override Type ParameterType => _parameterType; + public override string ToString() => _parameterType.FullName ?? string.Empty; + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/TypeDelegator.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/TypeDelegator.cs index ce3e75cde26061..4a8d08a9bd77e5 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/TypeDelegator.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/TypeDelegator.cs @@ -84,6 +84,10 @@ public TypeDelegator([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes. [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields)] public override FieldInfo[] GetFields(BindingFlags bindingAttr) => typeImpl.GetFields(bindingAttr); + public override Type[] GetFunctionPointerCallingConventions() => typeImpl.GetFunctionPointerCallingConventions(); + public override FunctionPointerParameterInfo[] GetFunctionPointerParameterInfos() => typeImpl.GetFunctionPointerParameterInfos(); + public override FunctionPointerParameterInfo GetFunctionPointerReturnParameter() => typeImpl.GetFunctionPointerReturnParameter(); + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] public override Type? GetInterface(string name, bool ignoreCase) => typeImpl.GetInterface(name, ignoreCase); @@ -146,6 +150,9 @@ public TypeDelegator([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes. public override bool IsCollectible => typeImpl.IsCollectible; + public override bool IsFunctionPointer => typeImpl.IsFunctionPointer; + public override bool IsUnmanagedFunctionPointer => typeImpl.IsUnmanagedFunctionPointer; + public override Type? GetElementType() => typeImpl.GetElementType(); protected override bool HasElementTypeImpl() => typeImpl.HasElementType; diff --git a/src/libraries/System.Private.CoreLib/src/System/Type.cs b/src/libraries/System.Private.CoreLib/src/System/Type.cs index 4907875d591c5e..09b8c69e22bc47 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Type.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Type.cs @@ -53,6 +53,9 @@ protected Type() { } public virtual bool IsByRefLike { [Intrinsic] get => throw new NotSupportedException(SR.NotSupported_SubclassOverride); } + public virtual bool IsFunctionPointer => false; + public virtual bool IsUnmanagedFunctionPointer => false; + public bool HasElementType => HasElementTypeImpl(); protected abstract bool HasElementTypeImpl(); public abstract Type? GetElementType(); @@ -201,6 +204,10 @@ public ConstructorInfo? TypeInitializer [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields)] public abstract FieldInfo[] GetFields(BindingFlags bindingAttr); + public virtual Type[] GetFunctionPointerCallingConventions() => throw new InvalidOperationException(SR.InvalidOperation_NotFunctionPointer); + public virtual FunctionPointerParameterInfo GetFunctionPointerReturnParameter() => throw new InvalidOperationException(SR.InvalidOperation_NotFunctionPointer); + public virtual FunctionPointerParameterInfo[] GetFunctionPointerParameterInfos() => throw new InvalidOperationException(SR.InvalidOperation_NotFunctionPointer); + [DynamicallyAccessedMembers( DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicMethods | diff --git a/src/libraries/System.Reflection.Metadata/tests/Metadata/Decoding/SignatureDecoderTests.cs b/src/libraries/System.Reflection.Metadata/tests/Metadata/Decoding/SignatureDecoderTests.cs index 7669fbcc2ef312..df0e0ba6ee1a53 100644 --- a/src/libraries/System.Reflection.Metadata/tests/Metadata/Decoding/SignatureDecoderTests.cs +++ b/src/libraries/System.Reflection.Metadata/tests/Metadata/Decoding/SignatureDecoderTests.cs @@ -241,6 +241,8 @@ public void ByReference(ref int i) { } public struct Nested { } public Nested Property { get { throw null; } } public event EventHandler Event { add { } remove { } } + public delegate* managed ManagedFunctionPointer; + public delegate* unmanaged[Stdcall] NativeFunctionPointer; } [Fact] @@ -329,6 +331,8 @@ private static Dictionary GetExpectedFieldSignatures() { "Array", "int32[0...,0...]" }, { "GenericTypeParameter", "!T" }, { "GenericInstantiation", $"[{CollectionsAssemblyName}]System.Collections.Generic.List`1" }, + { "ManagedFunctionPointer", "method bool *(int32)" }, + { "NativeFunctionPointer", "method bool *(int32)" }, }; } diff --git a/src/libraries/System.Reflection.MetadataLoadContext/System.Reflection.MetadataLoadContext.sln b/src/libraries/System.Reflection.MetadataLoadContext/System.Reflection.MetadataLoadContext.sln index 702ac482ae494d..9580f272000443 100644 --- a/src/libraries/System.Reflection.MetadataLoadContext/System.Reflection.MetadataLoadContext.sln +++ b/src/libraries/System.Reflection.MetadataLoadContext/System.Reflection.MetadataLoadContext.sln @@ -17,6 +17,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Reflection.MetadataL EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Reflection.MetadataLoadContext.Tests", "tests\System.Reflection.MetadataLoadContext.Tests.csproj", "{D28B6414-C82C-4BDE-B8BB-A4E3297A0651}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestFunctionPointerAssembly", "..\Common\tests\System\TestFunctionPointerAssembly\TestFunctionPointerAssembly.csproj", "{F85BDD51-AC29-4D8D-8257-C509BED9A448}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibraryImportGenerator", "..\System.Runtime.InteropServices\gen\LibraryImportGenerator\LibraryImportGenerator.csproj", "{4361CEFA-8238-4247-9CC5-D99DF794843C}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Interop.SourceGeneration", "..\System.Runtime.InteropServices\gen\Microsoft.Interop.SourceGeneration\Microsoft.Interop.SourceGeneration.csproj", "{7393C7CD-4C31-4B1C-96DC-1D46D240538A}" @@ -73,6 +75,10 @@ Global {D28B6414-C82C-4BDE-B8BB-A4E3297A0651}.Debug|Any CPU.Build.0 = Debug|Any CPU {D28B6414-C82C-4BDE-B8BB-A4E3297A0651}.Release|Any CPU.ActiveCfg = Release|Any CPU {D28B6414-C82C-4BDE-B8BB-A4E3297A0651}.Release|Any CPU.Build.0 = Release|Any CPU + {F85BDD51-AC29-4D8D-8257-C509BED9A448}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F85BDD51-AC29-4D8D-8257-C509BED9A448}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F85BDD51-AC29-4D8D-8257-C509BED9A448}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F85BDD51-AC29-4D8D-8257-C509BED9A448}.Release|Any CPU.Build.0 = Release|Any CPU {4361CEFA-8238-4247-9CC5-D99DF794843C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4361CEFA-8238-4247-9CC5-D99DF794843C}.Debug|Any CPU.Build.0 = Debug|Any CPU {4361CEFA-8238-4247-9CC5-D99DF794843C}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -92,6 +98,7 @@ Global GlobalSection(NestedProjects) = preSolution {6A69770F-4F95-411F-ACAE-2B902EB62161} = {F45DECCA-03D3-4087-AB01-F099C027DC33} {D28B6414-C82C-4BDE-B8BB-A4E3297A0651} = {F45DECCA-03D3-4087-AB01-F099C027DC33} + {F85BDD51-AC29-4D8D-8257-C509BED9A448} = {F45DECCA-03D3-4087-AB01-F099C027DC33} {22BDB23C-24DE-4C3C-9A18-A048C445EDC1} = {B3731232-B2FE-401B-A9F1-5DFB1A90D687} {E524DAF8-3F2C-4EC5-833D-E7D182055A66} = {B3731232-B2FE-401B-A9F1-5DFB1A90D687} {7AE8D7FD-6CEE-4F70-8675-0896AA6487BD} = {B3731232-B2FE-401B-A9F1-5DFB1A90D687} diff --git a/src/libraries/System.Reflection.MetadataLoadContext/src/Resources/Strings.resx b/src/libraries/System.Reflection.MetadataLoadContext/src/Resources/Strings.resx index d5a40f23b31697..69a5165b832309 100644 --- a/src/libraries/System.Reflection.MetadataLoadContext/src/Resources/Strings.resx +++ b/src/libraries/System.Reflection.MetadataLoadContext/src/Resources/Strings.resx @@ -1,4 +1,5 @@ - + + $(NoWarn);SYSLIB0005;SYSLIB0037 + + $(DefineConstants);FUNCTIONPOINTER_SUPPORT + - - + + @@ -72,9 +73,15 @@ + + + + + + diff --git a/src/libraries/System.Reflection.MetadataLoadContext/tests/src/SampleMetadata/SampleMetadata.cs b/src/libraries/System.Reflection.MetadataLoadContext/tests/src/SampleMetadata/SampleMetadata.cs index 52075da13b5ff3..dfe7b197b59b57 100644 --- a/src/libraries/System.Reflection.MetadataLoadContext/tests/src/SampleMetadata/SampleMetadata.cs +++ b/src/libraries/System.Reflection.MetadataLoadContext/tests/src/SampleMetadata/SampleMetadata.cs @@ -557,5 +557,5 @@ public class ClassWithDefaultMember1 where T : ClassWithDefaultMember1 public class PublicClass { internal class InternalNestedClass { } - } + } } diff --git a/src/libraries/System.Reflection.MetadataLoadContext/tests/src/TestUtils/TestUtils.JittedRuntimes.cs b/src/libraries/System.Reflection.MetadataLoadContext/tests/src/TestUtils/TestUtils.JittedRuntimes.cs index cdbd7d4f6ec1fc..76c5f3436c8a08 100644 --- a/src/libraries/System.Reflection.MetadataLoadContext/tests/src/TestUtils/TestUtils.JittedRuntimes.cs +++ b/src/libraries/System.Reflection.MetadataLoadContext/tests/src/TestUtils/TestUtils.JittedRuntimes.cs @@ -15,6 +15,11 @@ public static Type Project(this Type type) { if (type == null) return null; +#if FUNCTIONPOINTER_SUPPORT + // Function pointers don't support Type.GetType() so they can't be dynamically created. + if (type.IsFunctionPointer) + throw new NotSupportedException("Function pointers don't support Project()"); +#endif Assembly assembly = type.Assembly; string location = assembly.Location; @@ -75,5 +80,10 @@ public static string GetNameOfCoreAssembly() { return typeof(object).Assembly.GetName().Name; } + + /// + /// Do a type comparison; RO Types compare via .Equals, not ReferenceEquals + /// + public static bool IsEqualOrReferenceEquals(this Type type, Type other) => type.Equals(other); } } diff --git a/src/libraries/System.Reflection.MetadataLoadContext/tests/src/Tests/Method/MethodTests.cs b/src/libraries/System.Reflection.MetadataLoadContext/tests/src/Tests/Method/MethodTests.cs index 6f0f25ae95a637..1fab77de7cc4ea 100644 --- a/src/libraries/System.Reflection.MetadataLoadContext/tests/src/Tests/Method/MethodTests.cs +++ b/src/libraries/System.Reflection.MetadataLoadContext/tests/src/Tests/Method/MethodTests.cs @@ -248,7 +248,6 @@ public static unsafe void TestCustomModifiers1() { using (MetadataLoadContext lc = new MetadataLoadContext(new CoreMetadataAssemblyResolver(), "mscorlib")) { - Assembly a = lc.LoadFromByteArray(TestData.s_CustomModifiersImage); Type t = a.GetType("N", throwOnError: true); Type reqA = a.GetType("ReqA", throwOnError: true); diff --git a/src/libraries/System.Reflection/ref/System.Reflection.Forwards.cs b/src/libraries/System.Reflection/ref/System.Reflection.Forwards.cs index c539b15e1336fd..a6bb642c2b8f25 100644 --- a/src/libraries/System.Reflection/ref/System.Reflection.Forwards.cs +++ b/src/libraries/System.Reflection/ref/System.Reflection.Forwards.cs @@ -15,6 +15,7 @@ [assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Reflection.CustomAttributeTypedArgument))] [assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Reflection.EventInfo))] [assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Reflection.FieldInfo))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Reflection.FunctionPointerParameterInfo))] [assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Reflection.ICustomAttributeProvider))] [assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Reflection.IntrospectionExtensions))] [assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Reflection.InvalidFilterCriteriaException))] diff --git a/src/libraries/System.Reflection/tests/MethodInfoTests.cs b/src/libraries/System.Reflection/tests/MethodInfoTests.cs index b01cc2701e09fd..2874e98a2f2f9d 100644 --- a/src/libraries/System.Reflection/tests/MethodInfoTests.cs +++ b/src/libraries/System.Reflection/tests/MethodInfoTests.cs @@ -6,6 +6,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Xunit; +using Xunit.Sdk; namespace System.Reflection.Tests { @@ -205,7 +206,6 @@ public void EqualsTest(Type type1, string name1, Type type2, string name2, bool [InlineData("DummyMethod1", "DummyMethod1", true)] //Verify two different MethodInfo objects are not equal [InlineData("DummyMethod1", "DummyMethod2", false)] - public void Equality1(string str1, string str2, bool expected) { MethodInfo mi1 = GetMethod(typeof(MethodInfoTests), str1); @@ -852,6 +852,36 @@ private static void SecondCall(MethodInfo mi) Assert.Contains("TestAssembly", asm.ToString()); } + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/71883", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] + private static unsafe void TestFunctionPointers() + { + void* fn = FunctionPointerMethods.GetFunctionPointer(); + + // Sanity checks for direct invocation. + Assert.True(FunctionPointerMethods.GetFunctionPointer()(42)); + Assert.True(FunctionPointerMethods.CallFcnPtr_IntPtr((IntPtr)fn, 42)); + Assert.True(FunctionPointerMethods.CallFcnPtr_Void(fn, 42)); + Assert.False(FunctionPointerMethods.GetFunctionPointer()(41)); + Assert.False(FunctionPointerMethods.CallFcnPtr_IntPtr((IntPtr)fn, 41)); + Assert.False(FunctionPointerMethods.CallFcnPtr_Void(fn, 41)); + + MethodInfo m; + + m = GetMethod(typeof(FunctionPointerMethods), nameof(FunctionPointerMethods.CallFcnPtr_FP)); + // System.ArgumentException : Object of type 'System.IntPtr' cannot be converted to type 'System.Boolean(System.Int32)' + Assert.Throws(() => m.Invoke(null, new object[] { (IntPtr)fn, 42 })); + Assert.Throws(() => m.Invoke(null, new object[] { (IntPtr)fn, 41 })); + + m = GetMethod(typeof(FunctionPointerMethods), nameof(FunctionPointerMethods.CallFcnPtr_IntPtr)); + Assert.True((bool)m.Invoke(null, new object[] { (IntPtr)fn, 42 })); + Assert.False((bool)m.Invoke(null, new object[] { (IntPtr)fn, 41 })); + + m = GetMethod(typeof(FunctionPointerMethods), nameof(FunctionPointerMethods.CallFcnPtr_Void)); + Assert.True((bool)m.Invoke(null, new object[] { (IntPtr)fn, 42 })); + Assert.False((bool)m.Invoke(null, new object[] { (IntPtr)fn, 41 })); + } + //Methods for Reflection Metadata private void DummyMethod1(string str, int iValue, long lValue) { @@ -1240,6 +1270,31 @@ static YesNo NonNullableEnumDefaultYes(YesNo yesNo = YesNo.Yes) return yesNo; } } + + public static class FunctionPointerMethods + { + public static bool CallMe(int i) + { + return i == 42; + } + + public static unsafe bool CallFcnPtr_FP(delegate* fn, int value) + { + return fn(value); + } + + public static unsafe bool CallFcnPtr_IntPtr(IntPtr fn, int value) + { + return ((delegate*)fn)(value); + } + + public static unsafe bool CallFcnPtr_Void(void* fn, int value) + { + return ((delegate*)fn)(value); + } + + public static unsafe delegate* GetFunctionPointer() => &CallMe; + } #pragma warning restore 0414 } diff --git a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/ConvertToLibraryImportAnalyzerTests.cs b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/ConvertToLibraryImportAnalyzerTests.cs index da3ce81fd3a4cc..9f2966143321e2 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/ConvertToLibraryImportAnalyzerTests.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/ConvertToLibraryImportAnalyzerTests.cs @@ -35,7 +35,8 @@ public static IEnumerable NoMarshallingRequiredTypes() => new[] new object[] { typeof(int*) }, new object[] { typeof(bool*) }, new object[] { typeof(char*) }, - new object[] { typeof(delegate* ) }, + // See issue https://github.com/dotnet/runtime/issues/71891 + // new object[] { typeof(delegate* ) }, new object[] { typeof(IntPtr) }, new object[] { typeof(ConsoleKey) }, // enum }; diff --git a/src/libraries/System.Runtime/System.Runtime.sln b/src/libraries/System.Runtime/System.Runtime.sln index 1ae36ed216684e..9980e0c11b3d3f 100644 --- a/src/libraries/System.Runtime/System.Runtime.sln +++ b/src/libraries/System.Runtime/System.Runtime.sln @@ -39,6 +39,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Runtime.Tests", "tes EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestCollectibleAssembly", "tests\TestCollectibleAssembly\TestCollectibleAssembly.csproj", "{C230AC88-A377-4BEB-824F-AB174C14DC86}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestFunctionPointerAssembly", "..\Common\tests\System\TestFunctionPointerAssembly\TestFunctionPointerAssembly.csproj", "{B7975A39-2E87-4C6C-A7EC-1F5926676800}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestLoadAssembly", "tests\TestLoadAssembly\TestLoadAssembly.csproj", "{1BCCD2F5-A561-4641-8A0B-51F3EDCA35DC}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Reflection.TestModule", "tests\TestModule\System.Reflection.TestModule.ilproj", "{0F83B07B-2E3F-4708-BE6D-7A8DA8168803}" @@ -386,12 +388,24 @@ Global {4EE36055-AD7C-4779-B3F6-08687960DCC3}.Release|x64.Build.0 = Release|Any CPU {4EE36055-AD7C-4779-B3F6-08687960DCC3}.Release|x86.ActiveCfg = Release|Any CPU {4EE36055-AD7C-4779-B3F6-08687960DCC3}.Release|x86.Build.0 = Release|Any CPU - {C230AC88-A377-4BEB-824F-AB174C14DC86}.Checked|Any CPU.ActiveCfg = Debug|Any CPU - {C230AC88-A377-4BEB-824F-AB174C14DC86}.Checked|Any CPU.Build.0 = Debug|Any CPU - {C230AC88-A377-4BEB-824F-AB174C14DC86}.Checked|x64.ActiveCfg = Debug|Any CPU - {C230AC88-A377-4BEB-824F-AB174C14DC86}.Checked|x64.Build.0 = Debug|Any CPU - {C230AC88-A377-4BEB-824F-AB174C14DC86}.Checked|x86.ActiveCfg = Debug|Any CPU - {C230AC88-A377-4BEB-824F-AB174C14DC86}.Checked|x86.Build.0 = Debug|Any CPU + {B7975A39-2E87-4C6C-A7EC-1F5926676800}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B7975A39-2E87-4C6C-A7EC-1F5926676800}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B7975A39-2E87-4C6C-A7EC-1F5926676800}.Debug|x64.ActiveCfg = Debug|Any CPU + {B7975A39-2E87-4C6C-A7EC-1F5926676800}.Debug|x64.Build.0 = Debug|Any CPU + {B7975A39-2E87-4C6C-A7EC-1F5926676800}.Debug|x86.ActiveCfg = Debug|Any CPU + {B7975A39-2E87-4C6C-A7EC-1F5926676800}.Debug|x86.Build.0 = Debug|Any CPU + {B7975A39-2E87-4C6C-A7EC-1F5926676800}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B7975A39-2E87-4C6C-A7EC-1F5926676800}.Release|Any CPU.Build.0 = Release|Any CPU + {B7975A39-2E87-4C6C-A7EC-1F5926676800}.Release|x64.ActiveCfg = Release|Any CPU + {B7975A39-2E87-4C6C-A7EC-1F5926676800}.Release|x64.Build.0 = Release|Any CPU + {B7975A39-2E87-4C6C-A7EC-1F5926676800}.Release|x86.ActiveCfg = Release|Any CPU + {B7975A39-2E87-4C6C-A7EC-1F5926676800}.Release|x86.Build.0 = Release|Any CPU + {B7975A39-2E87-4C6C-A7EC-1F5926676800}.Checked|Any CPU.ActiveCfg = Debug|Any CPU + {B7975A39-2E87-4C6C-A7EC-1F5926676800}.Checked|Any CPU.Build.0 = Debug|Any CPU + {B7975A39-2E87-4C6C-A7EC-1F5926676800}.Checked|x64.ActiveCfg = Debug|Any CPU + {B7975A39-2E87-4C6C-A7EC-1F5926676800}.Checked|x64.Build.0 = Debug|Any CPU + {B7975A39-2E87-4C6C-A7EC-1F5926676800}.Checked|x86.ActiveCfg = Debug|Any CPU + {B7975A39-2E87-4C6C-A7EC-1F5926676800}.Checked|x86.Build.0 = Debug|Any CPU {C230AC88-A377-4BEB-824F-AB174C14DC86}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C230AC88-A377-4BEB-824F-AB174C14DC86}.Debug|Any CPU.Build.0 = Debug|Any CPU {C230AC88-A377-4BEB-824F-AB174C14DC86}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -404,6 +418,12 @@ Global {C230AC88-A377-4BEB-824F-AB174C14DC86}.Release|x64.Build.0 = Release|Any CPU {C230AC88-A377-4BEB-824F-AB174C14DC86}.Release|x86.ActiveCfg = Release|Any CPU {C230AC88-A377-4BEB-824F-AB174C14DC86}.Release|x86.Build.0 = Release|Any CPU + {C230AC88-A377-4BEB-824F-AB174C14DC86}.Checked|Any CPU.ActiveCfg = Debug|Any CPU + {C230AC88-A377-4BEB-824F-AB174C14DC86}.Checked|Any CPU.Build.0 = Debug|Any CPU + {C230AC88-A377-4BEB-824F-AB174C14DC86}.Checked|x64.ActiveCfg = Debug|Any CPU + {C230AC88-A377-4BEB-824F-AB174C14DC86}.Checked|x64.Build.0 = Debug|Any CPU + {C230AC88-A377-4BEB-824F-AB174C14DC86}.Checked|x86.ActiveCfg = Debug|Any CPU + {C230AC88-A377-4BEB-824F-AB174C14DC86}.Checked|x86.Build.0 = Debug|Any CPU {1BCCD2F5-A561-4641-8A0B-51F3EDCA35DC}.Checked|Any CPU.ActiveCfg = Debug|Any CPU {1BCCD2F5-A561-4641-8A0B-51F3EDCA35DC}.Checked|Any CPU.Build.0 = Debug|Any CPU {1BCCD2F5-A561-4641-8A0B-51F3EDCA35DC}.Checked|x64.ActiveCfg = Debug|Any CPU @@ -606,6 +626,7 @@ Global {A83A8520-F5E2-49B4-83BC-0F82A412951D} = {28140562-A65A-48E9-ABAB-53BA939084F0} {3B79DD71-8C2F-41BC-A1A7-86A490D6C726} = {FD72C125-C10D-457B-8AFC-6B4E5237AF6A} {4EE36055-AD7C-4779-B3F6-08687960DCC3} = {FD72C125-C10D-457B-8AFC-6B4E5237AF6A} + {B7975A39-2E87-4C6C-A7EC-1F5926676800} = {FD72C125-C10D-457B-8AFC-6B4E5237AF6A} {C230AC88-A377-4BEB-824F-AB174C14DC86} = {FD72C125-C10D-457B-8AFC-6B4E5237AF6A} {1BCCD2F5-A561-4641-8A0B-51F3EDCA35DC} = {FD72C125-C10D-457B-8AFC-6B4E5237AF6A} {0F83B07B-2E3F-4708-BE6D-7A8DA8168803} = {FD72C125-C10D-457B-8AFC-6B4E5237AF6A} diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index e4c2fa89f2f5bd..e3b5b2d49625c9 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -5789,6 +5789,7 @@ protected Type() { } public bool IsContextful { get { throw null; } } public virtual bool IsEnum { get { throw null; } } public bool IsExplicitLayout { get { throw null; } } + public virtual bool IsFunctionPointer { get { throw null; } } public virtual bool IsGenericMethodParameter { get { throw null; } } public virtual bool IsGenericParameter { get { throw null; } } public virtual bool IsGenericType { get { throw null; } } @@ -5819,6 +5820,7 @@ protected Type() { } public virtual bool IsSZArray { get { throw null; } } public virtual bool IsTypeDefinition { get { throw null; } } public bool IsUnicodeClass { get { throw null; } } + public virtual bool IsUnmanagedFunctionPointer { get { throw null; } } public bool IsValueType { get { throw null; } } public virtual bool IsVariableBoundArray { get { throw null; } } public bool IsVisible { get { throw null; } } @@ -5858,7 +5860,7 @@ protected Type() { } public virtual string? GetEnumName(object value) { throw null; } public virtual string[] GetEnumNames() { throw null; } public virtual System.Type GetEnumUnderlyingType() { throw null; } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("It might not be possible to create an array of the enum type at runtime. Use Enum.GetValues instead.")] + [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("It might not be possible to create an array of the enum type at runtime. Use Enum.GetValues instead.")] public virtual System.Array GetEnumValues() { throw null; } [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicEvents)] public System.Reflection.EventInfo? GetEvent(string name) { throw null; } @@ -5876,6 +5878,9 @@ protected Type() { } public System.Reflection.FieldInfo[] GetFields() { throw null; } [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicFields | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicFields)] public abstract System.Reflection.FieldInfo[] GetFields(System.Reflection.BindingFlags bindingAttr); + public virtual System.Type[] GetFunctionPointerCallingConventions() { throw null; } + public virtual System.Reflection.FunctionPointerParameterInfo[] GetFunctionPointerParameterInfos() { throw null; } + public virtual System.Reflection.FunctionPointerParameterInfo GetFunctionPointerReturnParameter() { throw null; } public virtual System.Type[] GetGenericArguments() { throw null; } public virtual System.Type[] GetGenericParameterConstraints() { throw null; } public virtual System.Type GetGenericTypeDefinition() { throw null; } @@ -6013,14 +6018,14 @@ protected Type() { } protected abstract bool IsPrimitiveImpl(); public virtual bool IsSubclassOf(System.Type c) { throw null; } protected virtual bool IsValueTypeImpl() { throw null; } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The code for an array of the specified type might not be available.")] + [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("The code for an array of the specified type might not be available.")] public virtual System.Type MakeArrayType() { throw null; } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The code for an array of the specified type might not be available.")] + [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("The code for an array of the specified type might not be available.")] public virtual System.Type MakeArrayType(int rank) { throw null; } public virtual System.Type MakeByRefType() { throw null; } public static System.Type MakeGenericMethodParameter(int position) { throw null; } public static System.Type MakeGenericSignatureType(System.Type genericTypeDefinition, params System.Type[] typeArguments) { throw null; } - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The native code for this instantiation might not be available at runtime.")] + [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("The native code for this instantiation might not be available at runtime.")] [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("If some of the generic arguments are annotated (either with DynamicallyAccessedMembersAttribute, or generic constraints), trimming can't validate that the requirements of those annotations are met.")] public virtual System.Type MakeGenericType(params System.Type[] typeArguments) { throw null; } public virtual System.Type MakePointerType() { throw null; } @@ -11269,6 +11274,13 @@ public void SetValue(object? obj, object? value) { } [System.CLSCompliantAttribute(false)] public virtual void SetValueDirect(System.TypedReference obj, object value) { } } + public abstract class FunctionPointerParameterInfo + { + public abstract Type ParameterType { get; } + public abstract Type[] GetOptionalCustomModifiers(); + public abstract Type[] GetRequiredCustomModifiers(); + public override string ToString() { throw null; } + } [System.FlagsAttribute] public enum GenericParameterAttributes { @@ -11892,10 +11904,12 @@ public TypeDelegator([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers public override bool IsByRefLike { get { throw null; } } public override bool IsCollectible { get { throw null; } } public override bool IsConstructedGenericType { get { throw null; } } + public override bool IsFunctionPointer { get { throw null; } } public override bool IsGenericMethodParameter { get { throw null; } } public override bool IsGenericTypeParameter { get { throw null; } } public override bool IsSZArray { get { throw null; } } public override bool IsTypeDefinition { get { throw null; } } + public override bool IsUnmanagedFunctionPointer { get { throw null; } } public override bool IsVariableBoundArray { get { throw null; } } public override int MetadataToken { get { throw null; } } public override System.Reflection.Module Module { get { throw null; } } @@ -11921,6 +11935,9 @@ public TypeDelegator([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers public override System.Reflection.FieldInfo? GetField(string name, System.Reflection.BindingFlags bindingAttr) { throw null; } [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicFields | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicFields)] public override System.Reflection.FieldInfo[] GetFields(System.Reflection.BindingFlags bindingAttr) { throw null; } + public override System.Type[] GetFunctionPointerCallingConventions() { throw null; } + public override System.Reflection.FunctionPointerParameterInfo[] GetFunctionPointerParameterInfos() { throw null; } + public override System.Reflection.FunctionPointerParameterInfo GetFunctionPointerReturnParameter() { throw null; } [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.Interfaces)] [return: System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.Interfaces)] public override System.Type? GetInterface(string name, bool ignoreCase) { throw null; } diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj b/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj index 036977e8d25f8c..384ea8f6cafd64 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj @@ -22,6 +22,7 @@ + @@ -142,6 +143,7 @@ + @@ -321,6 +323,7 @@ + diff --git a/src/libraries/System.Runtime/tests/System/Reflection/TypeDelegatorTests.cs b/src/libraries/System.Runtime/tests/System/Reflection/TypeDelegatorTests.cs index ca6dccd6a8c9bb..bdf0f5453b6c15 100644 --- a/src/libraries/System.Runtime/tests/System/Reflection/TypeDelegatorTests.cs +++ b/src/libraries/System.Runtime/tests/System/Reflection/TypeDelegatorTests.cs @@ -43,15 +43,31 @@ public void Properties() Assert.False(new TypeDelegator(typeof(IComparable)).IsValueType); Assert.False(new TypeDelegator(typeof(IComparable)).IsEnum); Assert.True(new TypeDelegator(typeof(IComparable)).IsInterface); + Assert.False(new TypeDelegator(typeof(IComparable)).IsFunctionPointer); Assert.True(new TypeDelegator(typeof(TypeDelegatorTests)).IsClass); Assert.False(new TypeDelegator(typeof(TypeDelegatorTests)).IsValueType); Assert.False(new TypeDelegator(typeof(TypeDelegatorTests)).IsInterface); + Assert.False(new TypeDelegator(typeof(IComparable)).IsFunctionPointer); Assert.False(new TypeDelegator(typeof(TypeCode)).IsClass); Assert.False(new TypeDelegator(typeof(TypeCode)).IsInterface); Assert.True(new TypeDelegator(typeof(TypeCode)).IsValueType); Assert.True(new TypeDelegator(typeof(TypeCode)).IsEnum); + Assert.False(new TypeDelegator(typeof(IComparable)).IsFunctionPointer); + } + + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/71095", TestRuntimes.Mono)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/71883", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] + public void FunctionPointers() + { + Assert.True(new TypeDelegator(typeof(delegate* unmanaged)).IsClass); + Assert.True(new TypeDelegator(typeof(delegate* unmanaged)).IsFunctionPointer); + Assert.True(new TypeDelegator(typeof(delegate* unmanaged)).IsUnmanagedFunctionPointer); + Assert.NotNull(new TypeDelegator(typeof(delegate* unmanaged)).GetFunctionPointerCallingConventions()); + Assert.NotNull(new TypeDelegator(typeof(delegate* unmanaged)).GetFunctionPointerParameterInfos()); + Assert.NotNull(new TypeDelegator(typeof(delegate* unmanaged)).GetFunctionPointerReturnParameter()); } public static IEnumerable SZArrayOrNotTypes() diff --git a/src/libraries/System.Runtime/tests/System/Type/FunctionPointerTestExtensions.cs b/src/libraries/System.Runtime/tests/System/Type/FunctionPointerTestExtensions.cs new file mode 100644 index 00000000000000..ac3cf2562ab1b1 --- /dev/null +++ b/src/libraries/System.Runtime/tests/System/Type/FunctionPointerTestExtensions.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Tests.Types +{ + internal static class FunctionPointerTestExtensions + { + /// + /// A noop to allow tests to be shared with MetadataLoadContext. + /// + public static Type Project(this Type type) => type; + + /// + /// Check for type equality; runtime Types compare via Equals and ReferenceEquals + /// + public static bool IsEqualOrReferenceEquals(this Type type, Type other) => type.Equals(other) && ReferenceEquals(type, other); + } +} diff --git a/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj index 4945fcc682af3e..9e24fc7b6400e6 100644 --- a/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -1,4 +1,4 @@ - + false true @@ -210,6 +210,7 @@ + @@ -217,6 +218,7 @@ + @@ -276,6 +278,9 @@ + + /Link>=CommonSystem\Collections\Generic\ArrayBuilder.cs + diff --git a/src/mono/System.Private.CoreLib/src/System/Reflection/FunctionPointerInfo.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Reflection/FunctionPointerInfo.Mono.cs new file mode 100644 index 00000000000000..b8f0f4c74df800 --- /dev/null +++ b/src/mono/System.Private.CoreLib/src/System/Reflection/FunctionPointerInfo.Mono.cs @@ -0,0 +1,18 @@ +// 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 FunctionPointerInfo + { + internal FunctionPointerInfo() + { + _returnInfo = default!; + _parameterInfos = default!; + } + + private Type[]? GetCustomModifiersFromFunctionPointer(int position, bool required) => throw new NotSupportedException(); + private unsafe MdSigCallingConvention CallingConvention => throw new NotSupportedException(); + private RuntimeType FunctionPointerType => throw new NotSupportedException(); + } +} diff --git a/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeFunctionPointerParameterInfo.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeFunctionPointerParameterInfo.Mono.cs new file mode 100644 index 00000000000000..b087fa299fb5d1 --- /dev/null +++ b/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeFunctionPointerParameterInfo.Mono.cs @@ -0,0 +1,19 @@ +// 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 + { + public RuntimeFunctionPointerParameterInfo() + { + _parameterType = default!; + } + + public override Type[] GetOptionalCustomModifiers() => throw new NotSupportedException(); + public override Type[] GetRequiredCustomModifiers() => throw new NotSupportedException(); + internal void SetCustomModifiersForReturnType(Type[] modifiers) => throw new NotSupportedException(); + } +} diff --git a/src/tests/reflection/ldtoken/modifiers.il b/src/tests/reflection/ldtoken/modifiers.il index 5b0ea00e458a0a..9a472c9b2efdb3 100644 --- a/src/tests/reflection/ldtoken/modifiers.il +++ b/src/tests/reflection/ldtoken/modifiers.il @@ -6,8 +6,8 @@ .method private hidebysig static int32 Main() cil managed { - .locals init (valuetype [System.Runtime]System.RuntimeTypeHandle) .entrypoint + .locals init (valuetype [System.Runtime]System.RuntimeTypeHandle) ldtoken string[] stloc.0 @@ -59,6 +59,40 @@ UIntArrayModifiedUIntArrayOK: ret MyStructPtrModifiedMyStructPtrOK: + ldtoken method int32 *() + stloc.0 + ldloca 0 + ldtoken method int32 modopt(MyModifier) *() + + call instance bool valuetype [System.Runtime]System.RuntimeTypeHandle::Equals(valuetype [System.Runtime]System.RuntimeTypeHandle) + // All modifiers are considered as part of identity. + brfalse IntReturnManagedFunctionPointerModifiedOK + ldc.i4.6 + ret +IntReturnManagedFunctionPointerModifiedOK: + + ldtoken method unmanaged cdecl int32 modopt(MyModifier) *() + stloc.0 + ldloca 0 + ldtoken method unmanaged cdecl int32 modopt(MyModifier) *() + + call instance bool valuetype [System.Runtime]System.RuntimeTypeHandle::Equals(valuetype [System.Runtime]System.RuntimeTypeHandle) + // CallKind and modifiers are the same + brtrue IntReturnUnmanagedFunctionPointerModifiedOK + ldc.i4.7 + ret +IntReturnUnmanagedFunctionPointerModifiedOK: + + ldtoken method unmanaged cdecl int32 modopt([System.Runtime]System.Runtime.CompilerServices.CallConvStdcall) *() + stloc.0 + ldloca 0 + ldtoken method unmanaged cdecl int32 modopt([System.Runtime]System.Runtime.CompilerServices.CallConvSuppressGCTransition) modopt([System.Runtime]System.Runtime.CompilerServices.CallConvStdcall) *() + call instance bool valuetype [System.Runtime]System.RuntimeTypeHandle::Equals(valuetype [System.Runtime]System.RuntimeTypeHandle) + brfalse IntReturnUnmanagedFunctionPointerWithCallConvModifiedOK + ldc.i4.8 + ret + +IntReturnUnmanagedFunctionPointerWithCallConvModifiedOK: ldc.i4 100 ret }