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 4de4ac0aa5860..1b227e0521ddb 100644 --- a/src/coreclr/src/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/coreclr/src/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -220,6 +220,7 @@ + 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 cd9dfa1368fcd..f8ff5504380a7 100644 --- a/src/coreclr/src/System.Private.CoreLib/src/System/RuntimeHandles.cs +++ b/src/coreclr/src/System.Private.CoreLib/src/System/RuntimeHandles.cs @@ -208,12 +208,6 @@ internal static bool HasElementType(RuntimeType type) return outHandles; } - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern object CreateInstance(RuntimeType type, bool publicOnly, bool wrapExceptions, ref bool canBeCached, ref RuntimeMethodHandleInternal ctor, ref bool hasNoDefaultCtor); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern object Allocate(RuntimeType type); - internal static object CreateInstanceForAnotherGenericParameter(RuntimeType type, RuntimeType genericParameter) { object? instantiatedObject = null; @@ -258,6 +252,51 @@ private static extern void CreateInstanceForAnotherGenericParameter( int cTypeHandles, ObjectHandleOnStack instantiatedObject); + /// + /// Given a RuntimeType, returns information about how to activate it via calli + /// semantics. This method will ensure the type object is fully initialized within + /// the VM, but it will not call any static ctors on the type. + /// + internal static void GetActivationInfo( + RuntimeType rt, + out delegate* pfnAllocator, + out void* vAllocatorFirstArg, + out delegate* pfnCtor, + out bool ctorIsPublic) + { + Debug.Assert(rt != null); + + delegate* pfnAllocatorTemp = default; + void* vAllocatorFirstArgTemp = default; + delegate* pfnCtorTemp = default; + Interop.BOOL fCtorIsPublicTemp = default; + + GetActivationInfo( + ObjectHandleOnStack.Create(ref rt), + &pfnAllocatorTemp, &vAllocatorFirstArgTemp, + &pfnCtorTemp, &fCtorIsPublicTemp); + + pfnAllocator = pfnAllocatorTemp; + vAllocatorFirstArg = vAllocatorFirstArgTemp; + pfnCtor = pfnCtorTemp; + ctorIsPublic = fCtorIsPublicTemp != Interop.BOOL.FALSE; + } + + [DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)] + private static extern void GetActivationInfo( + ObjectHandleOnStack pRuntimeType, + delegate** ppfnAllocator, + void** pvAllocatorFirstArg, + delegate** ppfnCtor, + Interop.BOOL* pfCtorIsPublic); + +#if FEATURE_COMINTEROP + // Referenced by unmanaged layer (see GetActivationInfo). + // First parameter is ComClassFactory*. + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern object AllocateComObject(void* pClassFactory); +#endif + internal RuntimeType GetRuntimeType() { return m_type; diff --git a/src/coreclr/src/System.Private.CoreLib/src/System/RuntimeType.ActivatorCache.cs b/src/coreclr/src/System.Private.CoreLib/src/System/RuntimeType.ActivatorCache.cs new file mode 100644 index 0000000000000..abf4f4afa4410 --- /dev/null +++ b/src/coreclr/src/System.Private.CoreLib/src/System/RuntimeType.ActivatorCache.cs @@ -0,0 +1,130 @@ +// 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; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; + +namespace System +{ + internal sealed partial class RuntimeType + { + /// + /// A cache which allows optimizing , + /// , and related APIs. + /// + private sealed unsafe class ActivatorCache + { + // The managed calli to the newobj allocator, plus its first argument (MethodTable*). + // In the case of the COM allocator, first arg is ComClassFactory*, not MethodTable*. + private readonly delegate* _pfnAllocator; + private readonly void* _allocatorFirstArg; + + // The managed calli to the parameterless ctor, taking "this" (as object) as its first argument. + private readonly delegate* _pfnCtor; + private readonly bool _ctorIsPublic; + +#if DEBUG + private readonly RuntimeType _originalRuntimeType; +#endif + + internal ActivatorCache(RuntimeType rt) + { + Debug.Assert(rt != null); + +#if DEBUG + _originalRuntimeType = rt; +#endif + + // The check below is redundant since these same checks are performed at the + // unmanaged layer, but this call will throw slightly different exceptions + // than the unmanaged layer, and callers might be dependent on this. + + rt.CreateInstanceCheckThis(); + + try + { + RuntimeTypeHandle.GetActivationInfo(rt, + out _pfnAllocator!, out _allocatorFirstArg, + out _pfnCtor!, out _ctorIsPublic); + } + catch (Exception ex) + { + // Exception messages coming from the runtime won't include + // the type name. Let's include it here to improve the + // debugging experience for our callers. + + string friendlyMessage = SR.Format(SR.Activator_CannotCreateInstance, rt, ex.Message); + switch (ex) + { + case ArgumentException: throw new ArgumentException(friendlyMessage); + case PlatformNotSupportedException: throw new PlatformNotSupportedException(friendlyMessage); + case NotSupportedException: throw new NotSupportedException(friendlyMessage); + case MethodAccessException: throw new MethodAccessException(friendlyMessage); + case MissingMethodException: throw new MissingMethodException(friendlyMessage); + case MemberAccessException: throw new MemberAccessException(friendlyMessage); + } + + throw; // can't make a friendlier message, rethrow original exception + } + + // Activator.CreateInstance returns null given typeof(Nullable). + + if (_pfnAllocator == null) + { + Debug.Assert(Nullable.GetUnderlyingType(rt) != null, + "Null allocator should only be returned for Nullable."); + + static object? ReturnNull(void* _) => null; + _pfnAllocator = &ReturnNull; + } + + // If no ctor is provided, we have Nullable, a ctorless value type T, + // or a ctorless __ComObject. In any case, we should replace the + // ctor call with our no-op stub. The unmanaged GetActivationInfo layer + // would have thrown an exception if 'rt' were a normal reference type + // without a ctor. + + if (_pfnCtor == null) + { + static void CtorNoopStub(object? uninitializedObject) { } + _pfnCtor = &CtorNoopStub; // we use null singleton pattern if no ctor call is necessary + + Debug.Assert(_ctorIsPublic); // implicit parameterless ctor is always considered public + } + + // We don't need to worry about invoking cctors here. The runtime will figure it + // out for us when the instance ctor is called. For value types, because we're + // creating a boxed default(T), the static cctor is called when *any* instance + // method is invoked. + } + + internal bool CtorIsPublic => _ctorIsPublic; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal object? CreateUninitializedObject(RuntimeType rt) + { + // We don't use RuntimeType, but we force the caller to pass it so + // that we can keep it alive on their behalf. Once the object is + // constructed, we no longer need the reference to the type instance, + // as the object itself will keep the type alive. + +#if DEBUG + if (_originalRuntimeType != rt) + { + Debug.Fail("Caller passed the wrong RuntimeType to this routine." + + Environment.NewLineConst + "Expected: " + (_originalRuntimeType ?? (object)"") + + Environment.NewLineConst + "Actual: " + (rt ?? (object)"")); + } +#endif + + object? retVal = _pfnAllocator(_allocatorFirstArg); + GC.KeepAlive(rt); + return retVal; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void CallConstructor(object? uninitializedObject) => _pfnCtor(uninitializedObject); + } + } +} 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 1b968d25b370d..a3d0c9a840b9d 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 @@ -17,12 +17,6 @@ namespace System { - // this is a work around to get the concept of a calli. It's not as fast but it would be interesting to - // see how it compares to the current implementation. - // This delegate will disappear at some point in favor of calli - - internal delegate void CtorDelegate(object instance); - // Keep this in sync with FormatFlags defined in typestring.h internal enum TypeNameFormatFlags { @@ -3968,113 +3962,45 @@ 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 + /// Helper to invoke the default (parameterless) constructor. /// - private object? CreateInstanceDefaultCtorSlow(bool publicOnly, bool wrapExceptions, bool fillCache) + [DebuggerStepThrough] + [DebuggerHidden] + internal object? CreateInstanceDefaultCtor(bool publicOnly, bool skipCheckThis, bool fillCache, bool wrapExceptions) { - RuntimeMethodHandleInternal runtimeCtor = default; - bool canBeCached = false; - bool hasNoDefaultCtor = false; + // Get or create the cached factory. Creating the cache will fail if one + // of our invariant checks fails; e.g., no appropriate ctor found. + // + // n.b. In coreclr we ignore 'skipCheckThis' (assumed to be false) + // and 'fillCache' (assumed to be true). - object instance = RuntimeTypeHandle.CreateInstance(this, publicOnly, wrapExceptions, ref canBeCached, ref runtimeCtor, ref hasNoDefaultCtor); - if (hasNoDefaultCtor) + if (GenericCache is not ActivatorCache cache) { - throw new MissingMethodException(SR.Format(SR.Arg_NoDefCTor, this)); + cache = new ActivatorCache(this); + GenericCache = cache; } - if (canBeCached && fillCache) + if (!cache.CtorIsPublic && publicOnly) { - // cache the ctor - GenericCache = new ActivatorCache(runtimeCtor); + throw new MissingMethodException(SR.Format(SR.Arg_NoDefCTor, this)); } - return instance; - } + // Compat: allocation always takes place outside the try block so that OOMs + // bubble up to the caller; the ctor invocation is within the try block so + // that it can be wrapped in TIE if needed. - /// - /// Helper to invoke the default (parameterless) constructor. - /// - [DebuggerStepThrough] - [DebuggerHidden] - internal object? CreateInstanceDefaultCtor(bool publicOnly, bool skipCheckThis, bool fillCache, bool wrapExceptions) - { - // Call the cached - if (GenericCache is ActivatorCache cacheEntry) + object? obj = cache.CreateUninitializedObject(this); + try { - cacheEntry.EnsureInitialized(); - - if (publicOnly) - { - if (cacheEntry._ctor != null && - (cacheEntry._ctorAttributes & MethodAttributes.MemberAccessMask) != MethodAttributes.Public) - { - 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 - { - cacheEntry._ctor(instance); - } - catch (Exception e) when (wrapExceptions) - { - throw new TargetInvocationException(e); - } - } - - return instance; + cache.CallConstructor(obj); + } + catch (Exception e) when (wrapExceptions) + { + throw new TargetInvocationException(e); } - if (!skipCheckThis) - CreateInstanceCheckThis(); - - return CreateInstanceDefaultCtorSlow(publicOnly, wrapExceptions, fillCache); + return obj; } internal void InvalidateCachedNestedType() => Cache.InvalidateCachedNestedType(); diff --git a/src/coreclr/src/vm/corelib.h b/src/coreclr/src/vm/corelib.h index dbf514748420a..fd22fe782981f 100644 --- a/src/coreclr/src/vm/corelib.h +++ b/src/coreclr/src/vm/corelib.h @@ -371,6 +371,9 @@ DEFINE_CLASS(RT_TYPE_HANDLE, System, RuntimeTypeHandle) DEFINE_METHOD(RT_TYPE_HANDLE, GET_TYPE_HELPER, GetTypeHelper, SM_Type_ArrType_IntPtr_int_RetType) DEFINE_METHOD(RT_TYPE_HANDLE, PVOID_CTOR, .ctor, IM_RuntimeType_RetVoid) DEFINE_METHOD(RT_TYPE_HANDLE, GETVALUEINTERNAL, GetValueInternal, SM_RuntimeTypeHandle_RetIntPtr) +#ifdef FEATURE_COMINTEROP +DEFINE_METHOD(RT_TYPE_HANDLE, ALLOCATECOMOBJECT, AllocateComObject, SM_VoidPtr_RetObj) +#endif DEFINE_FIELD(RT_TYPE_HANDLE, M_TYPE, m_type) DEFINE_CLASS_U(Reflection, RtFieldInfo, NoClass) diff --git a/src/coreclr/src/vm/ecalllist.h b/src/coreclr/src/vm/ecalllist.h index e45decc9a7b56..00cb622a7046d 100644 --- a/src/coreclr/src/vm/ecalllist.h +++ b/src/coreclr/src/vm/ecalllist.h @@ -189,7 +189,6 @@ FCFuncStart(gSystem_RuntimeType) FCFuncEnd() FCFuncStart(gCOMTypeHandleFuncs) - FCFuncElement("CreateInstance", RuntimeTypeHandle::CreateInstance) QCFuncElement("CreateInstanceForAnotherGenericParameter", RuntimeTypeHandle::CreateInstanceForAnotherGenericParameter) QCFuncElement("GetGCHandle", RuntimeTypeHandle::GetGCHandle) QCFuncElement("FreeGCHandle", RuntimeTypeHandle::FreeGCHandle) @@ -239,7 +238,10 @@ FCFuncStart(gCOMTypeHandleFuncs) FCFuncElement("IsGenericTypeDefinition", RuntimeTypeHandle::IsGenericTypeDefinition) FCFuncElement("ContainsGenericVariables", RuntimeTypeHandle::ContainsGenericVariables) FCFuncElement("SatisfiesConstraints", RuntimeTypeHandle::SatisfiesConstraints) - FCFuncElement("Allocate", RuntimeTypeHandle::Allocate) //for A.CI + QCFuncElement("GetActivationInfo", RuntimeTypeHandle::GetActivationInfo) +#ifdef FEATURE_COMINTEROP + FCFuncElement("AllocateComObject", RuntimeTypeHandle::AllocateComObject) +#endif // FEATURE_COMINTEROP FCFuncElement("CompareCanonicalHandles", RuntimeTypeHandle::CompareCanonicalHandles) FCIntrinsic("GetValueInternal", RuntimeTypeHandle::GetValueInternal, CORINFO_INTRINSIC_RTH_GetValueInternal) FCFuncElement("IsEquivalentTo", RuntimeTypeHandle::IsEquivalentTo) diff --git a/src/coreclr/src/vm/metasig.h b/src/coreclr/src/vm/metasig.h index 0714e0c50e209..c9856714883b8 100644 --- a/src/coreclr/src/vm/metasig.h +++ b/src/coreclr/src/vm/metasig.h @@ -464,6 +464,7 @@ DEFINE_METASIG(IM(RefObject_RetBool, r(j), F)) DEFINE_METASIG_T(IM(Class_RetObj, C(CLASS), j)) DEFINE_METASIG(IM(Int_VoidPtr_RetVoid, i P(v), v)) DEFINE_METASIG(IM(VoidPtr_RetVoid, P(v), v)) +DEFINE_METASIG(SM(VoidPtr_RetObj, P(v), j)) DEFINE_METASIG_T(IM(Str_RetModule, s, C(MODULE))) DEFINE_METASIG_T(SM(Assembly_Str_RetAssembly, C(ASSEMBLY) s, C(ASSEMBLY))) diff --git a/src/coreclr/src/vm/methodtable.cpp b/src/coreclr/src/vm/methodtable.cpp index 845c655cef9df..d0d0e2f9d5cf9 100644 --- a/src/coreclr/src/vm/methodtable.cpp +++ b/src/coreclr/src/vm/methodtable.cpp @@ -9150,7 +9150,7 @@ BOOL MethodTable::HasExplicitOrImplicitPublicDefaultConstructor() } //========================================================================================== -MethodDesc *MethodTable::GetDefaultConstructor() +MethodDesc *MethodTable::GetDefaultConstructor(BOOL forceBoxedEntryPoint /* = FALSE */) { WRAPPER_NO_CONTRACT; _ASSERTE(HasDefaultConstructor()); @@ -9161,7 +9161,7 @@ MethodDesc *MethodTable::GetDefaultConstructor() // returns pCanonMD immediately. return MethodDesc::FindOrCreateAssociatedMethodDesc(pCanonMD, this, - FALSE /* no BoxedEntryPointStub */, + forceBoxedEntryPoint, Instantiation(), /* no method instantiation */ FALSE /* no allowInstParam */); } diff --git a/src/coreclr/src/vm/methodtable.h b/src/coreclr/src/vm/methodtable.h index 850a13d388935..4f05c6a1868c2 100644 --- a/src/coreclr/src/vm/methodtable.h +++ b/src/coreclr/src/vm/methodtable.h @@ -823,7 +823,7 @@ class MethodTable BOOL HasDefaultConstructor(); void SetHasDefaultConstructor(); WORD GetDefaultConstructorSlot(); - MethodDesc *GetDefaultConstructor(); + MethodDesc *GetDefaultConstructor(BOOL forceBoxedEntryPoint = FALSE); BOOL HasExplicitOrImplicitPublicDefaultConstructor(); diff --git a/src/coreclr/src/vm/reflectioninvocation.cpp b/src/coreclr/src/vm/reflectioninvocation.cpp index 22be44f4a2bcf..cef2ecb6d96c3 100644 --- a/src/coreclr/src/vm/reflectioninvocation.cpp +++ b/src/coreclr/src/vm/reflectioninvocation.cpp @@ -338,183 +338,6 @@ FCIMPL7(void, RuntimeFieldHandle::SetValue, ReflectFieldObject *pFieldUNSAFE, Ob } FCIMPLEND -//A.CI work -FCIMPL1(Object*, RuntimeTypeHandle::Allocate, ReflectClassBaseObject* pTypeUNSAFE) -{ - CONTRACTL { - FCALL_CHECK; - PRECONDITION(CheckPointer(pTypeUNSAFE)); - } - CONTRACTL_END - - REFLECTCLASSBASEREF refType = (REFLECTCLASSBASEREF)ObjectToOBJECTREF(pTypeUNSAFE); - TypeHandle type = refType->GetType(); - - // Handle the nullable special case - if (Nullable::IsNullableType(type)) { - return OBJECTREFToObject(Nullable::BoxedNullableNull(type)); - } - - OBJECTREF rv = NULL; - HELPER_METHOD_FRAME_BEGIN_RET_1(refType); - rv = AllocateObject(type.GetMethodTable()); - HELPER_METHOD_FRAME_END(); - return OBJECTREFToObject(rv); - -}//Allocate -FCIMPLEND - -FCIMPL6(Object*, RuntimeTypeHandle::CreateInstance, ReflectClassBaseObject* refThisUNSAFE, - CLR_BOOL publicOnly, - CLR_BOOL wrapExceptions, - CLR_BOOL* pbCanBeCached, - MethodDesc** pConstructor, - CLR_BOOL* pbHasNoDefaultCtor) { - CONTRACTL { - FCALL_CHECK; - PRECONDITION(CheckPointer(refThisUNSAFE)); - PRECONDITION(CheckPointer(pbCanBeCached)); - PRECONDITION(CheckPointer(pConstructor)); - PRECONDITION(CheckPointer(pbHasNoDefaultCtor)); - PRECONDITION(*pbCanBeCached == false); - PRECONDITION(*pConstructor == NULL); - PRECONDITION(*pbHasNoDefaultCtor == false); - } - CONTRACTL_END; - - if (refThisUNSAFE == NULL) - FCThrow(kNullReferenceException); - - MethodDesc* pMeth; - - OBJECTREF rv = NULL; - REFLECTCLASSBASEREF refThis = (REFLECTCLASSBASEREF)ObjectToOBJECTREF(refThisUNSAFE); - TypeHandle thisTH = refThis->GetType(); - - Assembly *pAssem = thisTH.GetAssembly(); - - HELPER_METHOD_FRAME_BEGIN_RET_2(rv, refThis); - - MethodTable* pVMT; - - // Get the type information associated with refThis - if (thisTH.IsNull() || thisTH.IsTypeDesc()) { - *pbHasNoDefaultCtor = true; - goto DoneCreateInstance; - } - - pVMT = thisTH.AsMethodTable(); - - pVMT->EnsureInstanceActive(); - -#ifdef FEATURE_COMINTEROP - // If this is __ComObject then create the underlying COM object. - if (IsComObjectClass(refThis->GetType())) { -#ifdef FEATURE_COMINTEROP_UNMANAGED_ACTIVATION - SyncBlock* pSyncBlock = refThis->GetSyncBlock(); - - void* pClassFactory = (void*)pSyncBlock->GetInteropInfo()->GetComClassFactory(); - if (!pClassFactory) - COMPlusThrow(kInvalidComObjectException, IDS_EE_NO_BACKING_CLASS_FACTORY); - - // create an instance of the Com Object - rv = ((ComClassFactory*)pClassFactory)->CreateInstance(NULL); - -#else // FEATURE_COMINTEROP_UNMANAGED_ACTIVATION - - COMPlusThrow(kInvalidComObjectException, IDS_EE_NO_BACKING_CLASS_FACTORY); - -#endif // FEATURE_COMINTEROP_UNMANAGED_ACTIVATION - } - else -#endif // FEATURE_COMINTEROP - { - // if this is an abstract class then we will fail this - if (pVMT->IsAbstract()) { - if (pVMT->IsInterface()) - COMPlusThrow(kMissingMethodException,W("Acc_CreateInterface")); - else - COMPlusThrow(kMissingMethodException,W("Acc_CreateAbst")); - } - else if (pVMT->ContainsGenericVariables()) { - COMPlusThrow(kArgumentException,W("Acc_CreateGeneric")); - } - - if (pVMT->IsByRefLike()) - COMPlusThrow(kNotSupportedException, W("NotSupported_ByRefLike")); - - if (pVMT->IsSharedByGenericInstantiations()) - COMPlusThrow(kNotSupportedException, W("NotSupported_Type")); - - if (!pVMT->HasDefaultConstructor()) - { - // We didn't find the parameterless constructor, - // if this is a Value class we can simply allocate one and return it - - if (!pVMT->IsValueType()) { - *pbHasNoDefaultCtor = true; - goto DoneCreateInstance; - } - - // Handle the nullable special case - if (Nullable::IsNullableType(thisTH)) { - rv = Nullable::BoxedNullableNull(thisTH); - } - else - rv = pVMT->Allocate(); - - *pbCanBeCached = true; - } - else // !pVMT->HasDefaultConstructor() - { - pMeth = pVMT->GetDefaultConstructor(); - - // Validate the method can be called by this caller - DWORD attr = pMeth->GetAttrs(); - - if (!IsMdPublic(attr) && publicOnly) { - *pbHasNoDefaultCtor = true; - goto DoneCreateInstance; - } - - // We've got the class, lets allocate it and call the constructor - OBJECTREF o; - - o = AllocateObject(pVMT); - GCPROTECT_BEGIN(o); - - MethodDescCallSite ctor(pMeth, &o); - - // Copy "this" pointer - ARG_SLOT arg; - if (pVMT->IsValueType()) - arg = PtrToArgSlot(o->UnBox()); - else - arg = ObjToArgSlot(o); - - // Call the method - TryCallMethod(&ctor, &arg, wrapExceptions); - - rv = o; - GCPROTECT_END(); - - // No need to set these if they cannot be cached. In particular, if the type is a value type with a custom - // parameterless constructor, don't allow caching and have subsequent calls come back here to allocate an object and - // call the constructor. - if (!pVMT->IsValueType()) - { - *pbCanBeCached = true; - *pConstructor = pMeth; - } - } - } -DoneCreateInstance: - ; - HELPER_METHOD_FRAME_END(); - return OBJECTREFToObject(rv); -} -FCIMPLEND - void QCALLTYPE RuntimeTypeHandle::CreateInstanceForAnotherGenericParameter( QCall::TypeHandle pTypeHandle, TypeHandle* pInstArray, @@ -2174,6 +1997,262 @@ lExit: ; } FCIMPLEND +/* + * Given a TypeHandle, validates whether it's legal to construct a real + * instance of that type. Throws an exception if the instantiation would + * be illegal; e.g., type is void or a pointer or an open generic. This + * doesn't guarantee that a ctor will succeed, only that the VM is able + * to support an instance of this type on the heap. + * ========== + * The 'fForGetUninitializedInstance' parameter controls the type of + * exception that is thrown if a check fails. + */ +void RuntimeTypeHandle::ValidateTypeAbleToBeInstantiated( + TypeHandle typeHandle, + bool fGetUninitializedObject) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + // Don't allow void + if (typeHandle.GetSignatureCorElementType() == ELEMENT_TYPE_VOID) + { + COMPlusThrow(kArgumentException, W("NotSupported_Type")); + } + + // Don't allow arrays, pointers, byrefs, or function pointers + if (typeHandle.IsTypeDesc() || typeHandle.IsArray()) + { + COMPlusThrow(fGetUninitializedObject ? kArgumentException : kMissingMethodException, W("NotSupported_Type")); + } + + MethodTable* pMT = typeHandle.AsMethodTable(); + PREFIX_ASSUME(pMT != NULL); + + // Don't allow creating instances of delegates + if (pMT->IsDelegate()) + { + COMPlusThrow(kArgumentException, W("NotSupported_Type")); + } + + // Don't allow string or string-like (variable length) types. + if (pMT->HasComponentSize()) + { + COMPlusThrow(fGetUninitializedObject ? kArgumentException : kMissingMethodException, W("Argument_NoUninitializedStrings")); + } + + // Don't allow abstract classes or interface types + if (pMT->IsAbstract()) + { + RuntimeExceptionKind exKind = fGetUninitializedObject ? kMemberAccessException : kMissingMethodException; + if (pMT->IsInterface()) + COMPlusThrow(exKind, W("Acc_CreateInterface")); + else + COMPlusThrow(exKind, W("Acc_CreateAbst")); + } + + // Don't allow generic variables (e.g., the 'T' from List) + // or open generic types (List<>). + if (typeHandle.ContainsGenericVariables()) + { + COMPlusThrow(kMemberAccessException, W("Acc_CreateGeneric")); + } + + // Don't allow generics instantiated over __Canon + if (pMT->IsSharedByGenericInstantiations()) + { + COMPlusThrow(kNotSupportedException, W("NotSupported_Type")); + } + + // Don't allow ref structs + if (pMT->IsByRefLike()) + { + COMPlusThrow(kNotSupportedException, W("NotSupported_ByRefLike")); + } +} + +/* + * Given a RuntimeType, queries info on how to instantiate the object. + * pRuntimeType - [required] the RuntimeType object + * ppfnAllocator - [required, null-init] fnptr to the allocator + * mgd sig: void* -> object + * pvAllocatorFirstArg - [required, null-init] first argument to the allocator + * (normally, but not always, the MethodTable*) + * ppfnCtor - [required, null-init] the instance's parameterless ctor, + * mgd sig object -> void, or null if no ctor is needed for this type + * pfCtorIsPublic - [required, null-init] whether the parameterless ctor is public + * ========== + * This method will not run the type's static cctor. + * This method will not allocate an instance of the target type. + */ +void QCALLTYPE RuntimeTypeHandle::GetActivationInfo( + QCall::ObjectHandleOnStack pRuntimeType, + PCODE* ppfnAllocator, + void** pvAllocatorFirstArg, + PCODE* ppfnCtor, + BOOL* pfCtorIsPublic +) +{ + CONTRACTL{ + QCALL_CHECK; + PRECONDITION(CheckPointer(ppfnAllocator)); + PRECONDITION(CheckPointer(pvAllocatorFirstArg)); + PRECONDITION(CheckPointer(ppfnCtor)); + PRECONDITION(CheckPointer(pfCtorIsPublic)); + PRECONDITION(*ppfnAllocator == NULL); + PRECONDITION(*pvAllocatorFirstArg == NULL); + PRECONDITION(*ppfnCtor == NULL); + PRECONDITION(*pfCtorIsPublic == FALSE); + } + CONTRACTL_END; + + TypeHandle typeHandle = NULL; + + BEGIN_QCALL; + + { + GCX_COOP(); + + // We need to take the RuntimeType itself rather than the RuntimeTypeHandle, + // as the COM CLSID is stored in the RuntimeType object's sync block, and we + // might need to pull it out later in this method. + typeHandle = ((REFLECTCLASSBASEREF)pRuntimeType.Get())->GetType(); + } + + ValidateTypeAbleToBeInstantiated(typeHandle, false /* fGetUninitializedObject */); + + MethodTable* pMT = typeHandle.AsMethodTable(); + PREFIX_ASSUME(pMT != NULL); + +#ifdef FEATURE_COMINTEROP + // COM allocation can involve the __ComObject base type (with attached CLSID) or a + // VM-implemented [ComImport] class. For CreateInstance, the flowchart is: + // - For __ComObject, + // .. on Windows, bypass normal newobj logic and use ComClassFactory::CreateInstance. + // .. on non-Windows, treat as a normal class, type has no special handling in VM. + // - For [ComImport] class, treat as a normal class. VM will replace default + // ctor with COM activation logic on supported platforms, else ctor itself will PNSE. + // IsComObjectClass is the correct way to check for __ComObject specifically + if (IsComObjectClass(typeHandle)) + { + void* pClassFactory = NULL; + +#ifdef FEATURE_COMINTEROP_UNMANAGED_ACTIVATION + { + // Need to enter cooperative mode to manipulate OBJECTREFs + GCX_COOP(); + SyncBlock* pSyncBlock = pRuntimeType.Get()->GetSyncBlock(); + pClassFactory = (void*)pSyncBlock->GetInteropInfo()->GetComClassFactory(); + } +#endif // FEATURE_COMINTEROP_UNMANAGED_ACTIVATION + + if (pClassFactory == NULL) + { + // no factory *or* unmanaged activation is not enabled in this runtime + COMPlusThrow(kInvalidComObjectException, IDS_EE_NO_BACKING_CLASS_FACTORY); + } + + // managed sig: ComClassFactory* -> object (via FCALL) + *ppfnAllocator = CoreLibBinder::GetMethod(METHOD__RT_TYPE_HANDLE__ALLOCATECOMOBJECT)->GetMultiCallableAddrOfCode(); + *pvAllocatorFirstArg = pClassFactory; + *ppfnCtor = NULL; // no ctor call needed; activation handled entirely by the allocator + *pfCtorIsPublic = TRUE; // no ctor call needed => assume 'public' equivalent + } + else +#endif // FEATURE_COMINTEROP + if (pMT->IsNullable()) + { + // CreateInstance returns null given Nullable + *ppfnAllocator = NULL; + *pvAllocatorFirstArg = NULL; + *ppfnCtor = NULL; + *pfCtorIsPublic = TRUE; // no ctor call needed => assume 'public' equivalent + } + else + { + // managed sig: MethodTable* -> object (via JIT helper) + *ppfnAllocator = CEEJitInfo::getHelperFtnStatic(CEEInfo::getNewHelperStatic(pMT)); + *pvAllocatorFirstArg = pMT; + + if (pMT->HasDefaultConstructor()) + { + // managed sig: object -> void + // for ctors on value types, lookup boxed entry point stub + MethodDesc* pMD = pMT->GetDefaultConstructor(pMT->IsValueType() /* forceBoxedEntryPoint */); + _ASSERTE(pMD != NULL); + + PCODE pCode = pMD->GetMultiCallableAddrOfCode(); + _ASSERTE(pCode != NULL); + + *ppfnCtor = pCode; + *pfCtorIsPublic = pMD->IsPublic(); + } + else if (pMT->IsValueType()) + { + *ppfnCtor = NULL; // no ctor call needed; we're creating a boxed default(T) + *pfCtorIsPublic = TRUE; // no ctor call needed => assume 'public' equivalent + } + else + { + // reference type with no parameterless ctor - we can't instantiate this + COMPlusThrow(kMissingMethodException, W("Arg_NoDefCTorWithoutTypeName")); + } + } + + pMT->EnsureInstanceActive(); + + END_QCALL; +} + +/* + * Given a ComClassFactory*, calls the COM allocator + * and returns a RCW. + */ +FCIMPL1(Object*, RuntimeTypeHandle::AllocateComObject, + void* pClassFactory) +{ + CONTRACTL{ + FCALL_CHECK; + PRECONDITION(CheckPointer(pClassFactory)); + } + CONTRACTL_END; + + OBJECTREF rv = NULL; + bool allocated = false; + + HELPER_METHOD_FRAME_BEGIN_RET_1(rv); + +#ifdef FEATURE_COMINTEROP +#ifdef FEATURE_COMINTEROP_UNMANAGED_ACTIVATION + { + if (pClassFactory != NULL) + { + rv = ((ComClassFactory*)pClassFactory)->CreateInstance(NULL); + allocated = true; + } + } +#endif // FEATURE_COMINTEROP_UNMANAGED_ACTIVATION +#endif // FEATURE_COMINTEROP + + if (!allocated) + { +#ifdef FEATURE_COMINTEROP + COMPlusThrow(kInvalidComObjectException, IDS_EE_NO_BACKING_CLASS_FACTORY); +#else // FEATURE_COMINTEROP + COMPlusThrow(kPlatformNotSupportedException, IDS_EE_NO_BACKING_CLASS_FACTORY); +#endif // FEATURE_COMINTEROP + } + + HELPER_METHOD_FRAME_END(); + return OBJECTREFToObject(rv); +} +FCIMPLEND + //************************************************************************************************* //************************************************************************************************* //************************************************************************************************* @@ -2191,36 +2270,9 @@ FCIMPL1(Object*, ReflectionSerialization::GetUninitializedObject, ReflectClassBa TypeHandle type = objType->GetType(); - // Don't allow void, arrays, pointers, byrefs or function pointers. - if (type.IsTypeDesc() || type.IsArray() || type.GetSignatureCorElementType() == ELEMENT_TYPE_VOID) - COMPlusThrow(kArgumentException, W("Argument_InvalidValue")); + RuntimeTypeHandle::ValidateTypeAbleToBeInstantiated(type, true /* fForGetUninitializedInstance */); - MethodTable *pMT = type.AsMethodTable(); - PREFIX_ASSUME(pMT != NULL); - - //We don't allow unitialized Strings or Utf8Strings. - if (pMT == g_pStringClass) { - 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")); - } + MethodTable* pMT = type.AsMethodTable(); // 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. @@ -2235,6 +2287,7 @@ FCIMPL1(Object*, ReflectionSerialization::GetUninitializedObject, ReflectClassBa if (Nullable::IsNullableType(pMT)) pMT = pMT->GetInstantiation()[0].GetMethodTable(); + // Allocation will invoke any precise static cctors as needed. retVal = pMT->Allocate(); HELPER_METHOD_FRAME_END(); @@ -2581,4 +2634,3 @@ FCIMPL2(FC_BOOL_RET, ReflectionEnum::InternalHasFlag, Object *pRefThis, Object* FC_RETURN_BOOL(cmp); } FCIMPLEND - diff --git a/src/coreclr/src/vm/runtimehandles.h b/src/coreclr/src/vm/runtimehandles.h index 4c5e9469da3cc..7675d1b3bcac9 100644 --- a/src/coreclr/src/vm/runtimehandles.h +++ b/src/coreclr/src/vm/runtimehandles.h @@ -122,13 +122,16 @@ class RuntimeTypeHandle { public: // Static method on RuntimeTypeHandle - static FCDECL1(Object*, Allocate, ReflectClassBaseObject *refType) ; //A.CI work - static FCDECL6(Object*, CreateInstance, ReflectClassBaseObject* refThisUNSAFE, - CLR_BOOL publicOnly, - CLR_BOOL wrapExceptions, - CLR_BOOL *pbCanBeCached, - MethodDesc** pConstructor, - CLR_BOOL *pbHasNoDefaultCtor); + + static + void QCALLTYPE GetActivationInfo( + QCall::ObjectHandleOnStack pRuntimeType, + PCODE* ppfnAllocator, + void** pvAllocatorFirstArg, + PCODE* ppfnCtor, + BOOL* pfCtorIsPublic); + + static FCDECL1(Object*, AllocateComObject, void* pClassFactory); static void QCALLTYPE MakeByRef(QCall::TypeHandle pTypeHandle, QCall::ObjectHandleOnStack retType); @@ -193,6 +196,7 @@ class RuntimeTypeHandle { static FCDECL2(FC_BOOL_RET, IsInstanceOfType, ReflectClassBaseObject *pType, Object *object); static FCDECL6(FC_BOOL_RET, SatisfiesConstraints, PTR_ReflectClassBaseObject pGenericParameter, TypeHandle *typeContextArgs, INT32 typeContextCount, TypeHandle *methodContextArgs, INT32 methodContextCount, PTR_ReflectClassBaseObject pGenericArgument); + static FCDECL1(FC_BOOL_RET, HasInstantiation, PTR_ReflectClassBaseObject pType); @@ -255,6 +259,10 @@ class RuntimeTypeHandle { static PVOID QCALLTYPE AllocateTypeAssociatedMemory(QCall::TypeHandle type, UINT32 size); + + // Helper methods not called by managed code + + static void ValidateTypeAbleToBeInstantiated(TypeHandle typeHandle, bool fGetUninitializedObject); }; class RuntimeMethodHandle { diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index 1f0f79373d450..fb707d1f4e7a2 100644 --- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx @@ -601,6 +601,9 @@ Must specify binding flags describing the invoke operation required (BindingFlags.InvokeMethod CreateInstance GetField SetField GetProperty SetProperty). + + No parameterless constructor defined. + No parameterless constructor defined for type '{0}'. @@ -3748,4 +3751,7 @@ CodeBase is not supported on assemblies loaded from a single-file bundle. + + Cannot dynamically create an instance of type '{0}'. Reason: {1} + diff --git a/src/tests/Interop/COM/Reflection/Reflection.cs b/src/tests/Interop/COM/Reflection/Reflection.cs index a8769d1d4800f..cccb9625937ca 100644 --- a/src/tests/Interop/COM/Reflection/Reflection.cs +++ b/src/tests/Interop/COM/Reflection/Reflection.cs @@ -89,25 +89,13 @@ static bool ActivateCOMType() return true; } - catch (TargetInvocationException e) + catch (PlatformNotSupportedException) when (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && e.InnerException is PlatformNotSupportedException) - { - return true; - } - - Console.WriteLine($"Caught unexpected {nameof(PlatformNotSupportedException)}: {e}"); - return false; + return true; } - catch(COMException e) + catch (COMException) when (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - return true; - } - - Console.WriteLine($"Caught unexpected {nameof(COMException)}: {e}"); - return false; + return true; } catch (Exception e) {