diff --git a/eng/Versions.props b/eng/Versions.props index 0b398500e1356..d82c5a5d75d67 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -19,7 +19,7 @@ true false - 3.7.0-2.20258.1 + 3.7.0-ci.final dotnet $(ContainerName) diff --git a/src/coreclr/src/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/coreclr/src/System.Private.CoreLib/System.Private.CoreLib.csproj index b0456f3825d1e..973cff8e1e337 100644 --- a/src/coreclr/src/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/coreclr/src/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -60,7 +60,8 @@ <_FullFrameworkReferenceAssemblyPaths>$(MSBuildThisFileDirectory)/Documentation true $(OutputPath)$(MSBuildProjectName).xml - true + + false @@ -200,6 +201,7 @@ + @@ -212,6 +214,7 @@ + diff --git a/src/coreclr/src/System.Private.CoreLib/src/System/Reflection/ObjectFactory.CoreCLR.cs b/src/coreclr/src/System.Private.CoreLib/src/System/Reflection/ObjectFactory.CoreCLR.cs new file mode 100644 index 0000000000000..199e189f2b7ec --- /dev/null +++ b/src/coreclr/src/System.Private.CoreLib/src/System/Reflection/ObjectFactory.CoreCLR.cs @@ -0,0 +1,126 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Runtime.CompilerServices; +using Internal.Runtime.CompilerServices; + +namespace System.Reflection +{ + // Creates initialized instances of reference types or of value types. + // For reference types, calls the parameterless ctor. + // For value types, calls the parameterless ctor if it exists; otherwise + // return a boxed default(T). Must not be used with Nullable. + internal unsafe sealed class ObjectFactory : UninitializedObjectFactory + { + private readonly void* _pfnCtor; + private readonly bool _isNonPublicCtor; + + // Creates a factory from an existing parameterless ctor + internal ObjectFactory(RuntimeMethodHandleInternal hCtor) + : base(RuntimeMethodHandle.GetDeclaringType(hCtor)) + { + _pfnCtor = (void*)RuntimeMethodHandle.GetFunctionPointer(hCtor); + Debug.Assert(_pfnCtor != null); + + _isNonPublicCtor = (RuntimeMethodHandle.GetAttributes(hCtor) & MethodAttributes.MemberAccessMask) != MethodAttributes.Public; + } + + private ObjectFactory(RuntimeType type) + : base(type) + { + Debug.Assert(_pMT->IsValueType); + _isNonPublicCtor = false; // default(T) is always "public" + } + + // Creates a factory for "box(default(T))" around a value type + internal static ObjectFactory CreateFactoryForValueTypeDefaultOfT(RuntimeType type) + { + return new ObjectFactory(type); + } + + public bool IsNonPublicCtor => _isNonPublicCtor; + + public object CreateInstance() + { + object newObj = CreateUninitializedInstance(); + + if (!_pMT->IsValueType) + { + // Common case: we're creating a reference type + ((delegate*)_pfnCtor)(newObj); + } + else + { + // Less common case: we're creating a boxed value type + // If an explicit parameterless ctor exists, call it now. + if (_pfnCtor != null) + { + ((delegate*)_pfnCtor)(ref newObj.GetRawData()); + } + } + + return newObj; + } + } + + // Similar to ObjectFactory, but does not box value types 'T'. + internal unsafe sealed class ObjectFactory : UninitializedObjectFactory + { + private readonly void* _pfnCtor; + + internal ObjectFactory() + : base((RuntimeType)typeof(T)) + { + RuntimeType type = (RuntimeType)typeof(T); + + // It's ok if there's no default constructor on a value type. + // We'll return default(T). For reference types, the constructor + // must be present. In all cases, if a constructor is present, it + // must be public. + + RuntimeMethodHandleInternal hCtor = RuntimeMethodHandleInternal.EmptyHandle; + if (_pMT->HasDefaultConstructor) + { + hCtor = RuntimeTypeHandle.GetDefaultConstructor(type); + Debug.Assert(!hCtor.IsNullHandle()); + if ((RuntimeMethodHandle.GetAttributes(hCtor) & MethodAttributes.MemberAccessMask) != MethodAttributes.Public) + { + // parameterless ctor exists but is not public + throw new MissingMethodException(SR.Format(SR.Arg_NoDefCTor, type)); + } + + _pfnCtor = (void*)RuntimeMethodHandle.GetFunctionPointer(hCtor); + } + else + { + if (!_pMT->IsValueType) + { + // parameterless ctor missing on reference type + throw new MissingMethodException(SR.Format(SR.Arg_NoDefCTor, type)); + } + } + } + + public T CreateInstance() + { + if (typeof(T).IsValueType) + { + T value = default!; + if (_pfnCtor != null) + { + ((delegate*)_pfnCtor)(ref value); + } + return value; + } + else + { + object value = CreateUninitializedInstance(); + Debug.Assert(_pfnCtor != null); + ((delegate*)_pfnCtor)(value!); + return Unsafe.As(ref value); + } + } + } +} diff --git a/src/coreclr/src/System.Private.CoreLib/src/System/Reflection/UninitializedObjectFactory.CoreCLR.cs b/src/coreclr/src/System.Private.CoreLib/src/System/Reflection/UninitializedObjectFactory.CoreCLR.cs new file mode 100644 index 0000000000000..a6dc8e196f3c4 --- /dev/null +++ b/src/coreclr/src/System.Private.CoreLib/src/System/Reflection/UninitializedObjectFactory.CoreCLR.cs @@ -0,0 +1,48 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace System.Reflection +{ + // Creates zero-initialized instances of types. + // For reference types, equivalent of allocating memory without running ctor. + // For value types, equivalent of boxing default(T). + // Must not be used with Nullable. + internal unsafe class UninitializedObjectFactory + { + protected readonly MethodTable* _pMT; + private readonly delegate* _pfnAllocate; + private readonly RuntimeType _type; + + internal UninitializedObjectFactory(RuntimeType type) + { + Debug.Assert(type != null); + Debug.Assert(RuntimeHelpers.IsFastInstantiable(type)); + + _type = type; + _pMT = RuntimeTypeHandle.GetMethodTable(type); + _pfnAllocate = RuntimeHelpers.GetNewobjHelper(type); + + Debug.Assert(_pMT != null); + Debug.Assert(!_pMT->IsNullable); + Debug.Assert(_pfnAllocate != null); + } + + public object CreateUninitializedInstance() + { + // If a GC kicks in between the time we load the newobj + // helper address and the time we calli it, we don't want + // the Type object to be eligible for collection. To avoid + // this, we KeepAlive(this) - and the referenced Type - + // until we have an instance of the object. From that point + // onward, the object itself will keep the Type alive. + + object newObj = _pfnAllocate(_pMT); + GC.KeepAlive(this); + return newObj; + } + } +} diff --git a/src/coreclr/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs b/src/coreclr/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs index 49e8a4e08c8c0..70a8679a6abc2 100644 --- a/src/coreclr/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs +++ b/src/coreclr/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Reflection; using System.Runtime.InteropServices; using Internal.Runtime.CompilerServices; @@ -145,6 +146,41 @@ public static int OffsetToStringData [MethodImpl(MethodImplOptions.InternalCall)] internal static extern object AllocateUninitializedClone(object obj); + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern unsafe delegate* GetNewobjHelper(RuntimeType type); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern bool IsFastInstantiable(RuntimeType type); + + public static unsafe Func GetUninitializedObjectFactory(Type type) + { + if (type is null) + { + throw new ArgumentNullException(nameof(type)); + } + + if (!(type.UnderlyingSystemType is RuntimeType rt)) + { + throw new ArgumentException(SR.Arg_MustBeType, nameof(type)); + } + + type = null!; // just to make sure we don't use Type for the rest of the method + + if (rt.IsPointer || rt.IsByRef || rt.IsByRefLike || !RuntimeHelpers.IsFastInstantiable(rt)) + { + throw new ArgumentException( + paramName: nameof(type), + message: SR.NotSupported_Type); + } + + // Compat with GetUninitializedObject: if incoming type T is really + // Nullable, then this factory should return a boxed default(U) + // instead of a boxed default(Nullable). + + rt = (RuntimeType?)Nullable.GetUnderlyingType(rt) ?? rt; + return (Func)new UninitializedObjectFactory(rt).CreateUninitializedInstance; + } + /// true if given type is reference type or value type that contains references [Intrinsic] public static bool IsReferenceOrContainsReferences() @@ -332,10 +368,15 @@ internal unsafe struct MethodTable [FieldOffset(InterfaceMapOffset)] public MethodTable** InterfaceMap; - // WFLAGS_HIGH_ENUM + // WFLAGS_HIGH_ENUM & WFLAGS_LOW_ENUM + private const uint enum_flag_Category_Mask = 0x000F0000; + private const uint enum_flag_Category_Nullable = 0x00050000; + private const uint enum_flag_Category_ValueType = 0x00040000; + private const uint enum_flag_Category_ValueType_Mask = 0x000C0000; private const uint enum_flag_ContainsPointers = 0x01000000; private const uint enum_flag_HasComponentSize = 0x80000000; - private const uint enum_flag_HasTypeEquivalence = 0x00004000; + private const uint enum_flag_HasDefaultCtor = 0x00000200; + private const uint enum_flag_HasTypeEquivalence = 0x00004000; // TODO: shouldn't this be 0x02000000? // Types that require non-trivial interface cast have this bit set in the category private const uint enum_flag_NonTrivialInterfaceCast = 0x00080000 // enum_flag_Category_Array | 0x40000000 // enum_flag_ComObject @@ -391,6 +432,30 @@ public bool NonTrivialInterfaceCast } } + public bool HasDefaultConstructor + { + get + { + return (Flags & enum_flag_HasDefaultCtor) != 0; + } + } + + public bool IsNullable + { + get + { + return (Flags & enum_flag_Category_Mask) == enum_flag_Category_Nullable; + } + } + + public bool IsValueType + { + get + { + return (Flags & enum_flag_Category_ValueType_Mask) == enum_flag_Category_ValueType; + } + } + public bool HasTypeEquivalence { get diff --git a/src/coreclr/src/System.Private.CoreLib/src/System/RuntimeHandles.cs b/src/coreclr/src/System.Private.CoreLib/src/System/RuntimeHandles.cs index 5661ce8130ec5..35961de15a61a 100644 --- a/src/coreclr/src/System.Private.CoreLib/src/System/RuntimeHandles.cs +++ b/src/coreclr/src/System.Private.CoreLib/src/System/RuntimeHandles.cs @@ -249,6 +249,12 @@ public ModuleHandle GetModuleHandle() [MethodImpl(MethodImplOptions.InternalCall)] internal static extern RuntimeMethodHandleInternal GetMethodAt(RuntimeType type, int slot); + [MethodImpl(MethodImplOptions.InternalCall)] + internal static unsafe extern MethodTable* GetMethodTable(RuntimeType type); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern RuntimeMethodHandleInternal GetDefaultConstructor(RuntimeType type); + // This is managed wrapper for MethodTable::IntroducedMethodIterator internal struct IntroducedMethodEnumerator { diff --git a/src/coreclr/src/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs b/src/coreclr/src/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs index 93cfd86bc167b..a42625860ee5b 100644 --- a/src/coreclr/src/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs +++ b/src/coreclr/src/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs @@ -3890,46 +3890,6 @@ private void CreateInstanceCheckThis() return instance; } - // the cache entry - private sealed class ActivatorCache - { - // the delegate containing the call to the ctor - internal readonly RuntimeMethodHandleInternal _hCtorMethodHandle; - internal MethodAttributes _ctorAttributes; - internal CtorDelegate? _ctor; - - // Lazy initialization was performed - internal volatile bool _isFullyInitialized; - - private static ConstructorInfo? s_delegateCtorInfo; - - internal ActivatorCache(RuntimeMethodHandleInternal rmh) - { - _hCtorMethodHandle = rmh; - } - - private void Initialize() - { - if (!_hCtorMethodHandle.IsNullHandle()) - { - _ctorAttributes = RuntimeMethodHandle.GetAttributes(_hCtorMethodHandle); - - // The default ctor path is optimized for reference types only - ConstructorInfo delegateCtorInfo = s_delegateCtorInfo ??= typeof(CtorDelegate).GetConstructor(new Type[] { typeof(object), typeof(IntPtr) })!; - - // No synchronization needed here. In the worst case we create extra garbage - _ctor = (CtorDelegate)delegateCtorInfo.Invoke(new object?[] { null, RuntimeMethodHandle.GetFunctionPointer(_hCtorMethodHandle) }); - } - _isFullyInitialized = true; - } - - public void EnsureInitialized() - { - if (!_isFullyInitialized) - Initialize(); - } - } - /// /// The slow path of CreateInstanceDefaultCtor /// @@ -3947,8 +3907,17 @@ public void EnsureInitialized() if (canBeCached && fillCache) { - // cache the ctor - GenericCache = new ActivatorCache(runtimeCtor); + if (runtimeCtor.IsNullHandle()) + { + // value types with no parameterless ctor + Debug.Assert(this.IsValueType); + GenericCache = ObjectFactory.CreateFactoryForValueTypeDefaultOfT(this); + } + else + { + // reference types or value types with a parameterless ctor + GenericCache = new ObjectFactory(runtimeCtor); + } } return instance; @@ -3961,36 +3930,22 @@ public void EnsureInitialized() [DebuggerHidden] internal object? CreateInstanceDefaultCtor(bool publicOnly, bool skipCheckThis, bool fillCache, bool wrapExceptions) { - // Call the cached - if (GenericCache is ActivatorCache cacheEntry) + // Call the cached factory if it exists + if (GenericCache is ObjectFactory cachedFactory) { - cacheEntry.EnsureInitialized(); - - if (publicOnly) + if (cachedFactory.IsNonPublicCtor && publicOnly) { - if (cacheEntry._ctor != null && - (cacheEntry._ctorAttributes & MethodAttributes.MemberAccessMask) != MethodAttributes.Public) - { - throw new MissingMethodException(SR.Format(SR.Arg_NoDefCTor, this)); - } + throw new MissingMethodException(SR.Format(SR.Arg_NoDefCTor, this)); } - // Allocate empty object and call the default constructor if present. - object instance = RuntimeTypeHandle.Allocate(this); - Debug.Assert(cacheEntry._ctor != null || IsValueType); - if (cacheEntry._ctor != null) + try { - try - { - cacheEntry._ctor(instance); - } - catch (Exception e) when (wrapExceptions) - { - throw new TargetInvocationException(e); - } + return cachedFactory.CreateInstance(); + } + catch (Exception e) when (wrapExceptions) + { + throw new TargetInvocationException(e); } - - return instance; } if (!skipCheckThis) diff --git a/src/coreclr/src/vm/ecalllist.h b/src/coreclr/src/vm/ecalllist.h index 155ae2c516e7c..7950eae062bec 100644 --- a/src/coreclr/src/vm/ecalllist.h +++ b/src/coreclr/src/vm/ecalllist.h @@ -235,6 +235,8 @@ FCFuncStart(gCOMTypeHandleFuncs) FCFuncElement("GetToken", RuntimeTypeHandle::GetToken) FCFuncElement("_GetUtf8Name", RuntimeTypeHandle::GetUtf8Name) FCFuncElement("GetMethodAt", RuntimeTypeHandle::GetMethodAt) + FCFuncElement("GetMethodTable", RuntimeTypeHandle::GetMethodTable) + FCFuncElement("GetDefaultConstructor", RuntimeTypeHandle::GetDefaultConstructor) FCFuncElement("GetFields", RuntimeTypeHandle::GetFields) FCFuncElement("GetInterfaces", RuntimeTypeHandle::GetInterfaces) QCFuncElement("GetConstraints", RuntimeTypeHandle::GetConstraints) @@ -905,6 +907,8 @@ FCFuncStart(gRuntimeHelpers) FCFuncElement("EnsureSufficientExecutionStack", ReflectionInvocation::EnsureSufficientExecutionStack) FCFuncElement("TryEnsureSufficientExecutionStack", ReflectionInvocation::TryEnsureSufficientExecutionStack) FCFuncElement("GetUninitializedObjectInternal", ReflectionSerialization::GetUninitializedObject) + FCFuncElement("GetNewobjHelper", ReflectionSerialization::GetNewobjHelper) + FCFuncElement("IsFastInstantiable", ReflectionSerialization::IsFastInstantiable) QCFuncElement("AllocateTypeAssociatedMemoryInternal", RuntimeTypeHandle::AllocateTypeAssociatedMemory) FCFuncElement("AllocTailCallArgBuffer", TailCallHelp::AllocTailCallArgBuffer) FCFuncElement("FreeTailCallArgBuffer", TailCallHelp::FreeTailCallArgBuffer) diff --git a/src/coreclr/src/vm/mscorlib.h b/src/coreclr/src/vm/mscorlib.h index 7c0258449f0a5..600bf5e5b624e 100644 --- a/src/coreclr/src/vm/mscorlib.h +++ b/src/coreclr/src/vm/mscorlib.h @@ -726,6 +726,8 @@ DEFINE_METHOD(RUNTIME_HELPERS, GET_METHOD_TABLE, GetMethodTable, DEFINE_METHOD(RUNTIME_HELPERS, GET_RAW_DATA, GetRawData, NoSig) DEFINE_METHOD(RUNTIME_HELPERS, GET_RAW_ARRAY_DATA, GetRawArrayData, NoSig) DEFINE_METHOD(RUNTIME_HELPERS, GET_UNINITIALIZED_OBJECT, GetUninitializedObject, NoSig) +DEFINE_METHOD(RUNTIME_HELPERS, GET_NEWOBJ_HELPER, GetNewobjHelper, NoSig) +DEFINE_METHOD(RUNTIME_HELPERS, IS_FAST_INSTANTIABLE, IsFastInstantiable, NoSig) DEFINE_METHOD(RUNTIME_HELPERS, ENUM_EQUALS, EnumEquals, NoSig) DEFINE_METHOD(RUNTIME_HELPERS, ENUM_COMPARE_TO, EnumCompareTo, NoSig) DEFINE_METHOD(RUNTIME_HELPERS, ALLOC_TAILCALL_ARG_BUFFER, AllocTailCallArgBuffer, SM_Int_IntPtr_RetIntPtr) diff --git a/src/coreclr/src/vm/reflectioninvocation.cpp b/src/coreclr/src/vm/reflectioninvocation.cpp index b604b1a52d913..3e2c4541eb44e 100644 --- a/src/coreclr/src/vm/reflectioninvocation.cpp +++ b/src/coreclr/src/vm/reflectioninvocation.cpp @@ -2383,6 +2383,137 @@ FCIMPL1(Object*, ReflectionSerialization::GetUninitializedObject, ReflectClassBa } FCIMPLEND +FCIMPL1(void*, ReflectionSerialization::GetNewobjHelper, ReflectClassBaseObject* objTypeUNSAFE) { + FCALL_CONTRACT; + + void* retVal = NULL; + REFLECTCLASSBASEREF objType = (REFLECTCLASSBASEREF)objTypeUNSAFE; + + HELPER_METHOD_FRAME_BEGIN_RET_NOPOLL(); + + TypeHandle type = objType->GetType(); + + // Don't allow arrays, pointers, byrefs or function pointers. + if (type.IsTypeDesc() || type.IsArray()) + COMPlusThrow(kArgumentException, W("Argument_InvalidValue")); + + MethodTable* pMT = type.AsMethodTable(); + PREFIX_ASSUME(pMT != NULL); + + //We don't allow unitialized Strings or Utf8Strings. + if (pMT->HasComponentSize()) + { + COMPlusThrow(kArgumentException, W("Argument_NoUninitializedStrings")); + } + + // if this is an abstract class or an interface type then we will + // fail this + if (pMT->IsAbstract()) { + COMPlusThrow(kMemberAccessException, W("Acc_CreateAbst")); + } + + if (pMT->ContainsGenericVariables()) { + COMPlusThrow(kMemberAccessException, W("Acc_CreateGeneric")); + } + + if (pMT->IsByRefLike()) { + COMPlusThrow(kNotSupportedException, W("NotSupported_ByRefLike")); + } + + // Never allow allocation of generics actually instantiated over __Canon + if (pMT->IsSharedByGenericInstantiations()) { + COMPlusThrow(kNotSupportedException, W("NotSupported_Type")); + } + + // Never allow the allocation of an unitialized ContextBoundObject derived type, these must always be created with a paired + // transparent proxy or the jit will get confused. + +#ifdef FEATURE_COMINTEROP + // Also do not allow allocation of uninitialized RCWs (COM objects). + if (pMT->IsComObjectType()) + COMPlusThrow(kNotSupportedException, W("NotSupported_ManagedActivation")); +#endif // FEATURE_COMINTEROP + + Assembly* pAssem = pMT->GetAssembly(); + + if (!pMT->IsClassInited()) + { + pMT->CheckRestore(); + pMT->EnsureInstanceActive(); + pMT->CheckRunClassInitThrowing(); + } + + // TODO: Don't use a void* cast below. + + retVal = (void*)CEEJitInfo::getHelperFtnStatic(CEEInfo::getNewHelperStatic(pMT)); + + HELPER_METHOD_FRAME_END(); + return retVal; +} +FCIMPLEND + +FCIMPL1(FC_BOOL_RET, ReflectionSerialization::IsFastInstantiable, ReflectClassBaseObject* objTypeUNSAFE) { + FCALL_CONTRACT; + + BOOL retVal = FALSE; + REFLECTCLASSBASEREF objType = (REFLECTCLASSBASEREF)objTypeUNSAFE; + + HELPER_METHOD_FRAME_BEGIN_RET_NOPOLL(); + + TypeHandle type = objType->GetType(); + + // Don't allow arrays, pointers, byrefs or function pointers. + if (type.IsTypeDesc() || type.IsArray()) + goto Return; + + MethodTable* pMT = type.AsMethodTable(); + PREFIX_ASSUME(pMT != NULL); + + //We don't allow unitialized Strings or Utf8Strings. + if (pMT->HasComponentSize()) + { + goto Return; + } + + // if this is an abstract class or an interface type then we will + // fail this + if (pMT->IsAbstract()) { + goto Return; + } + + if (pMT->ContainsGenericVariables()) { + goto Return; + } + + if (pMT->IsByRefLike()) { + goto Return; + } + + // Never allow allocation of generics actually instantiated over __Canon + if (pMT->IsSharedByGenericInstantiations()) { + goto Return; + } + + // Never allow the allocation of an unitialized ContextBoundObject derived type, these must always be created with a paired + // transparent proxy or the jit will get confused. + +#ifdef FEATURE_COMINTEROP + // Also do not allow allocation of uninitialized RCWs (COM objects). + if (pMT->IsComObjectType()) + goto Return; +#endif // FEATURE_COMINTEROP + + retVal = TRUE; // all checks succeeded + +Return: + + do { /* dummy required for macro below to compile */ } while (0); + + HELPER_METHOD_FRAME_END(); + FC_RETURN_BOOL(retVal); +} +FCIMPLEND + //************************************************************************************************* //************************************************************************************************* //************************************************************************************************* diff --git a/src/coreclr/src/vm/reflectioninvocation.h b/src/coreclr/src/vm/reflectioninvocation.h index f4160204e8a0d..4052a685858cd 100644 --- a/src/coreclr/src/vm/reflectioninvocation.h +++ b/src/coreclr/src/vm/reflectioninvocation.h @@ -78,6 +78,8 @@ class ReflectionInvocation { class ReflectionSerialization { public: static FCDECL1(Object*, GetUninitializedObject, ReflectClassBaseObject* objTypeUNSAFE); + static FCDECL1(void*, GetNewobjHelper, ReflectClassBaseObject* objTypeUNSAFE); + static FCDECL1(FC_BOOL_RET, IsFastInstantiable, ReflectClassBaseObject* objTypeUNSAFE); }; class ReflectionEnum { diff --git a/src/coreclr/src/vm/runtimehandles.cpp b/src/coreclr/src/vm/runtimehandles.cpp index 82c793bf3f7af..317c8baaa0035 100644 --- a/src/coreclr/src/vm/runtimehandles.cpp +++ b/src/coreclr/src/vm/runtimehandles.cpp @@ -679,7 +679,44 @@ FCIMPL2(MethodDesc *, RuntimeTypeHandle::GetMethodAt, ReflectClassBaseObject *pT return pRetMethod; } +FCIMPLEND +FCIMPL1(MethodTable *, RuntimeTypeHandle::GetMethodTable, ReflectClassBaseObject *pTypeUNSAFE) { + CONTRACTL { + FCALL_CHECK; + } + CONTRACTL_END; + + REFLECTCLASSBASEREF refType = (REFLECTCLASSBASEREF)ObjectToOBJECTREF(pTypeUNSAFE); + + if (refType == NULL) + FCThrowRes(kArgumentNullException, W("Arg_InvalidHandle")); + + TypeHandle typeHandle = refType->GetType(); + return typeHandle.GetMethodTable(); +} +FCIMPLEND + +FCIMPL1(MethodDesc *, RuntimeTypeHandle::GetDefaultConstructor, ReflectClassBaseObject *pTypeUNSAFE) { + CONTRACTL { + FCALL_CHECK; + } + CONTRACTL_END; + + REFLECTCLASSBASEREF refType = (REFLECTCLASSBASEREF)ObjectToOBJECTREF(pTypeUNSAFE); + + if (refType == NULL) + FCThrowRes(kArgumentNullException, W("Arg_InvalidHandle")); + + TypeHandle typeHandle = refType->GetType(); + MethodDesc* pRetMethod = NULL; + + HELPER_METHOD_FRAME_BEGIN_RET_1(refType); + pRetMethod = typeHandle.GetMethodTable()->GetDefaultConstructor(); + HELPER_METHOD_FRAME_END(); + + return pRetMethod; +} FCIMPLEND FCIMPL3(FC_BOOL_RET, RuntimeTypeHandle::GetFields, ReflectClassBaseObject *pTypeUNSAFE, INT32 **result, INT32 *pCount) { diff --git a/src/coreclr/src/vm/runtimehandles.h b/src/coreclr/src/vm/runtimehandles.h index 7787e01675305..59493107a1e7a 100644 --- a/src/coreclr/src/vm/runtimehandles.h +++ b/src/coreclr/src/vm/runtimehandles.h @@ -243,6 +243,8 @@ class RuntimeTypeHandle { static FCDECL1(ReflectClassBaseObject*, GetElementType, ReflectClassBaseObject* pType); static FCDECL2(MethodDesc*, GetMethodAt, PTR_ReflectClassBaseObject pType, INT32 slot); + static FCDECL1(MethodTable*, GetMethodTable, PTR_ReflectClassBaseObject pType); + static FCDECL1(MethodDesc*, GetDefaultConstructor, PTR_ReflectClassBaseObject pType); static FCDECL1(INT32, GetNumVirtuals, ReflectClassBaseObject *pType); static diff --git a/src/libraries/System.Private.CoreLib/src/System/Activator.RuntimeType.cs b/src/libraries/System.Private.CoreLib/src/System/Activator.RuntimeType.cs index 76957ef99db0e..8f87f35104504 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Activator.RuntimeType.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Activator.RuntimeType.cs @@ -4,9 +4,11 @@ using System.Reflection; using System.Globalization; +using System.Runtime.CompilerServices; using System.Runtime.Loader; using System.Runtime.Remoting; using System.Threading; +using System.Diagnostics; namespace System { @@ -145,6 +147,76 @@ public static T CreateInstance() return (T)((RuntimeType)typeof(T)).CreateInstanceDefaultCtor(publicOnly: true, skipCheckThis: true, fillCache: true, wrapExceptions: true)!; } + public unsafe static Func CreateFactory() + { + // ObjectFactory ctor will perform correctness checks + ObjectFactory factory = new ObjectFactory(); + return factory.CreateInstance; + } + + public static unsafe Func CreateFactory(Type type, bool nonPublic) + { + if (type is null) + { + throw new ArgumentNullException(nameof(type)); + } + + if (!(type.UnderlyingSystemType is RuntimeType rt)) + { + throw new ArgumentException(SR.Arg_MustBeType, nameof(type)); + } + + type = null!; // just to make sure we don't use 'type' for the rest of the method + + if (rt.IsPointer || rt.IsByRef || rt.IsByRefLike || !RuntimeHelpers.IsFastInstantiable(rt)) + { + throw new ArgumentException( + paramName: nameof(type), + message: SR.NotSupported_Type); + } + + MethodTable* pMT = RuntimeTypeHandle.GetMethodTable(rt); + Debug.Assert(pMT != null); + + if (!pMT->HasDefaultConstructor) + { + // If no parameterless ctor exists, we can still fabricate an instance for value + // types, returning a boxed default(T). Unless the incoming type is a + // Nullable, at which point we'll return null instead of a "boxed null". + + if (pMT->IsValueType) + { + if (pMT->IsNullable) + { + return () => null; + } + else + { + ObjectFactory factory = ObjectFactory.CreateFactoryForValueTypeDefaultOfT(rt); + return factory.CreateInstance; + } + } + } + else + { + // If a parameterless ctor exists, perform visibility checks before linking to it. + + RuntimeMethodHandleInternal hCtor = RuntimeTypeHandle.GetDefaultConstructor(rt); + Debug.Assert(!hCtor.IsNullHandle()); + + if (nonPublic || (RuntimeMethodHandle.GetAttributes(hCtor) & MethodAttributes.MemberAccessMask) == MethodAttributes.Public) + { + ObjectFactory factory = new ObjectFactory(hCtor); + return factory.CreateInstance; + } + } + + // If we reached this point, no parameterless ctor was found, or the ctor + // was found but we can't link to it due to member access restrictions. + + throw new MissingMethodException(SR.Format(SR.Arg_NoDefCTor, rt)); + } + private static T CreateDefaultInstance() where T : struct => default; } } diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 0ff7018d9f5d1..fc5ee96edcbf3 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -67,6 +67,8 @@ public AccessViolationException(string? message, System.Exception? innerExceptio public delegate void Action(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9); public static partial class Activator { + public static System.Func CreateFactory(System.Type type, bool nonPublic) { throw null; } + public static System.Func CreateFactory() { throw null; } public static System.Runtime.Remoting.ObjectHandle? CreateInstance(string assemblyName, string typeName) { throw null; } public static System.Runtime.Remoting.ObjectHandle? CreateInstance(string assemblyName, string typeName, bool ignoreCase, System.Reflection.BindingFlags bindingAttr, System.Reflection.Binder? binder, object?[]? args, System.Globalization.CultureInfo? culture, object?[]? activationAttributes) { throw null; } public static System.Runtime.Remoting.ObjectHandle? CreateInstance(string assemblyName, string typeName, object?[]? activationAttributes) { throw null; } @@ -9103,6 +9105,7 @@ public static void ExecuteCodeWithGuaranteedCleanup(System.Runtime.CompilerServi public static object? GetObjectValue(object? obj) { throw null; } public static T[] GetSubArray(T[] array, System.Range range) { throw null; } public static object GetUninitializedObject(System.Type type) { throw null; } + public static System.Func GetUninitializedObjectFactory(System.Type type) { throw null; } public static void InitializeArray(System.Array array, System.RuntimeFieldHandle fldHandle) { } public static bool IsReferenceOrContainsReferences() { throw null; } public static void PrepareConstrainedRegions() { } diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj b/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj index 95118ff34a34c..0611285dba432 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj @@ -199,6 +199,7 @@ + diff --git a/src/libraries/System.Runtime/tests/System/Reflection/ObjectFactoryTests.cs b/src/libraries/System.Runtime/tests/System/Reflection/ObjectFactoryTests.cs new file mode 100644 index 0000000000000..430da045bce71 --- /dev/null +++ b/src/libraries/System.Runtime/tests/System/Reflection/ObjectFactoryTests.cs @@ -0,0 +1,169 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Collections.Tests; +using System.Runtime.CompilerServices; +using System.Tests; +using System.Tests.Types; +using Xunit; +using Xunit.Sdk; + +namespace System.Reflection.Tests +{ + public unsafe class ObjectFactoryTests + { + // Types that should fail when used in the ObjectFactory + public static IEnumerable NegativeTypes() + { + foreach (Type type in EnumerateTestCases()) + { + yield return new object[] { type }; + } + + IEnumerable EnumerateTestCases() + { + yield return null; + yield return typeof(int*); // pointers are disallowed + yield return typeof(int).MakeByRef(); // byrefs are disallowed + yield return typeof(Span); // ref structs are disallowed + yield return typeof(Enum); // uninstantiable type + yield return typeof(ValueType); // uninstantiable type + yield return typeof(Array); // uninstantiable type + yield return typeof(IDisposable); // interfaces disallowed + yield return typeof(string); // variable length types disallowed + yield return typeof(int[]); // variable length types disallowed + yield return typeof(MyAbstractClass); // abstract types disallowed + yield return typeof(List<>); // open generic types disallowed + yield return CanonType; // canon type disallowed + yield return typeof(List<>).MakeGenericType(CanonType); // shared instantiation disallowed + yield return typeof(ClassWithoutParameterlessCtor); // parameterless ctor not found + yield return typeof(StaticClass); // static types disallowed + yield return typeof(delegate*); // function pointers disallowed + yield return typeof(List<>).GetGenericArguments(0); // type parameters disallowed + } + } + + [Theory] + [MemberData(nameof(NegativeTypes))] + public void Activator_CreateFactory_OfInvalidType_Throws(Type type) + { + Exception throwException = Assert.ThrowsAny(() => Activator.CreateFactory(type, nonPublic: true)); + + switch (throwException) + { + case ArgumentException: + case MissingMethodException: + case NotSupportedException: + break; + + default: + throw new XunitException($"Unexpected exception {throwException.GetType()} thrown."); + } + } + + [Theory] + [MemberData(nameof(NegativeTypes))] + public void RuntimeHelpers_CreateUninitializedObjectFactory_OfInvalidType_Throws(Type type) + { + Exception throwException = Assert.ThrowsAny(() => RuntimeHelpers.CreateUninitializedObjectFactory(type)); + + switch (throwException) + { + case ArgumentException: + case MissingMethodException: + case NotSupportedException: + break; + + default: + throw new XunitException($"Unexpected exception {throwException.GetType()} thrown."); + } + } + + [Fact] + public void Activator_CreateFactory_ReferenceTypeWithParameterlessCtor() + { + ClassWithPublicParameterlessCtor c1 = Activator.CreateFactory(typeof(ClassWithPublicParameterlessCtor), nonPublic: false)() as ClassWithPublicParameterlessCtor; + Assert.NotNull(c1); + Assert.True(c1.WasConstructorCalled); + + ClassWithPublicParameterlessCtor c2 = Activator.CreateFactory(typeof(ClassWithPublicParameterlessCtor), nonPublic: true)() as ClassWithPublicParameterlessCtor; + Assert.NotNull(c2); + Assert.True(c2.WasConstructorCalled); + + // if ctor not visible, shouldn't even get as far as creating the factory + Assert.Throws(() => Activator.CreateFactory(typeof(ClassWithoutParameterlessCtor), nonPublic: false)); + + ClassWithoutParameterlessCtor c4 = Activator.CreateFactory(typeof(ClassWithoutParameterlessCtor), nonPublic: true)() as ClassWithoutParameterlessCtor; + Assert.NotNull(c4); + Assert.True(c4.WasConstructorCalled); + } + + [Fact] + public void RuntimeHelpers_CreateUninitializedObjectFactory_ReferenceTypeWithParameterlessCtor() + { + ClassWithPublicParameterlessCtor c1 = RuntimeHelpers.CreateUninitializedObjectFactory(typeof(ClassWithPublicParameterlessCtor))() as ClassWithPublicParameterlessCtor; + Assert.NotNull(c1); + Assert.False(c1.WasConstructorCalled); + + ClassWithNonPublicParameterlessCtor c2 = RuntimeHelpers.CreateUninitializedObjectFactory(typeof(ClassWithNonPublicParameterlessCtor))() as ClassWithNonPublicParameterlessCtor; + Assert.NotNull(c2); + Assert.False(c2.WasConstructorCalled); + + ClassWithoutParameterlessCtor c3 = RuntimeHelpers.CreateUninitializedObjectFactory(typeof(ClassWithoutParameterlessCtor))() as ClassWithoutParameterlessCtor; + Assert.NotNull(c3); + Assert.False(c3.WasConstructorCalled); + } + + // TODO Unit tests: + // When T is value type with custom ctor + // When T is value type without custom ctor + // When T is Nullable: Activator should return null, RuntimeHelpers should return boxed default(U) + // Activator.CreateFactory helper + + public static Type CanonType + { + get + { + Type type = typeof(object).Assembly.GetType("System.__Canon"); + Assert.NotNull(type); + return type; + } + } + + public abstract class MyAbstractClass { } + + public class ClassWithoutParameterlessCtor + { + public bool WasConstructorCalled; + + public ClassWithoutParameterlessCtor(int i) + { + WasConstructorCalled = true; + } + } + + public static class StaticClass { } + + public class ClassWithPublicParameterlessCtor + { + public bool WasConstructorCalled; + + public ClassWithPublicParameterlessCtor() + { + WasConstructorCalled = true; + } + } + + public class ClassWithNonPublicParameterlessCtor + { + public bool WasConstructorCalled; + + internal ClassWithNonPublicParameterlessCtor() + { + WasConstructorCalled = true; + } + } + } +}