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 1b227e0521ddbe..197b3a84a85191 100644 --- a/src/coreclr/src/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/coreclr/src/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -111,6 +111,7 @@ + @@ -220,7 +221,6 @@ - diff --git a/src/coreclr/src/System.Private.CoreLib/src/System/ActivationFactory.cs b/src/coreclr/src/System.Private.CoreLib/src/System/ActivationFactory.cs new file mode 100644 index 00000000000000..619e0ab406ca84 --- /dev/null +++ b/src/coreclr/src/System.Private.CoreLib/src/System/ActivationFactory.cs @@ -0,0 +1,219 @@ +// 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.Runtime.CompilerServices; + +namespace System +{ + /// + /// A factory which allows optimizing , + /// , and related APIs. + /// Requires a parameterless ctor (public or non-public). + /// + internal unsafe sealed class ActivationFactory + { + // 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. + // mgd sig: object -> void + private readonly delegate* _pfnCtor; + private readonly bool _ctorIsPublic; + + private readonly RuntimeType _originalRuntimeType; + + internal ActivationFactory(RuntimeType rt) + { + Debug.Assert(rt != null); + + _originalRuntimeType = rt; + + // 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) + { + TryThrowFriendlyException(_originalRuntimeType, ex); + 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; + + /// + /// Calls the default ctor over an existing zero-inited instance of the target type. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void CallConstructor(object? uninitializedObject) => _pfnCtor(uninitializedObject); + + internal Delegate CreateDelegate(RuntimeType delegateType) + { + Debug.Assert(delegateType is not null); + + // We only allow Func and Func (the latter only for reference types). + // This could probably be a Debug.Assert instead of a runtime check, but it's not *that* + // expensive in the grand scheme of things, so may as well do it. + + if (delegateType != typeof(Func)) + { + if (_originalRuntimeType.IsValueType || delegateType != typeof(Func<>).MakeGenericType(_originalRuntimeType)) + { + Debug.Fail($"Caller provided an unexpected RuntimeType: {delegateType}"); + Environment.FailFast("Potential type safety violation in ActivationFactory."); + } + } + + return Delegate.CreateDelegateUnsafe(delegateType, this, (IntPtr)(delegate*)&CreateInstance); + } + + /// + /// Constructs a new instance of the target type, including calling the default ctor if needed. + /// + private static object? CreateInstance(ActivationFactory @this) + { + object? newObj = @this.GetUninitializedObject(); + @this.CallConstructor(newObj); + return newObj; + } + + /// + /// Validates that this instance is a factory for the type desired by the caller. + /// + [Conditional("DEBUG")] + internal void DebugValidateExpectedType(RuntimeType rt) + { + if (_originalRuntimeType != rt) + { + Debug.Fail("Caller passed the wrong RuntimeType to this routine." + + Environment.NewLineConst + "Expected: " + (_originalRuntimeType ?? (object)"") + + Environment.NewLineConst + "Actual: " + (rt ?? (object)"")); + } + } + + /// + /// Allocates a new zero-inited instance of the target type, but does not run any ctors. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal object? GetUninitializedObject() + { + object? retVal = _pfnAllocator(_allocatorFirstArg); + GC.KeepAlive(this); // roots RuntimeType until allocation is completed + return retVal; + } + + [StackTraceHidden] + internal static void TryThrowFriendlyException(RuntimeType rt, 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); + } + } + } + + /// + /// An geared toward structs. + /// Requires no parameterless ctor or a public parameterless ctor. + /// + internal unsafe sealed class ActivationFactory : IActivationFactory where T : struct + { + private readonly delegate* _pfnCtor; // may be populated by CreateDelegate method + + public ActivationFactory() + { + delegate* pfnCtor; + bool ctorIsPublic; + + try + { + RuntimeTypeHandle.GetActivationInfoForStruct((RuntimeType)typeof(T), out pfnCtor, out ctorIsPublic); + } + catch (Exception ex) + { + ActivationFactory.TryThrowFriendlyException((RuntimeType)typeof(T), ex); + throw; // can't make a friendlier message, rethrow original exception + } + + if (pfnCtor != null && !ctorIsPublic) + { + throw new MissingMethodException(SR.Format(SR.Arg_NoDefCTor, (RuntimeType)typeof(T))); + } + + _pfnCtor = (delegate*)pfnCtor; + } + + private T CreateNoCtor() + { + Debug.Assert(_pfnCtor == null); + return default; + } + + private T CreateWithCtor() + { + Debug.Assert(_pfnCtor != null); + T newObj = default; + _pfnCtor(ref newObj); + return newObj; + } + + Delegate IActivationFactory.GetCreateInstanceDelegate() + => (_pfnCtor == null) ? (Func)CreateNoCtor : (Func)CreateWithCtor; + } + + internal interface IActivationFactory + { + Delegate GetCreateInstanceDelegate(); + } +} diff --git a/src/coreclr/src/System.Private.CoreLib/src/System/Delegate.CoreCLR.cs b/src/coreclr/src/System.Private.CoreLib/src/System/Delegate.CoreCLR.cs index ab338cd0bc9cdf..9f98477fca4d78 100644 --- a/src/coreclr/src/System.Private.CoreLib/src/System/Delegate.CoreCLR.cs +++ b/src/coreclr/src/System.Private.CoreLib/src/System/Delegate.CoreCLR.cs @@ -434,10 +434,29 @@ internal static Delegate CreateDelegateNoSecurityCheck(Type type, object? target [MethodImpl(MethodImplOptions.InternalCall)] internal static extern bool InternalEqualTypes(object a, object b); - // Used by the ctor. Do not call directly. + // Used by the ctor. // The name of this function will appear in managed stacktraces as delegate constructor. [MethodImpl(MethodImplOptions.InternalCall)] - private extern void DelegateConstruct(object target, IntPtr slot); + private extern void DelegateConstruct(object? target, IntPtr slot); + + /// + /// Creates a delegate with the specified first argument and function pointer. + /// No type safety checks take place. Caller must validate that all arguments are valid. + /// If no 'this' parameter is specified, caller must manually keep fnPtr alive until instantiation is complete. + /// + /// The type of the delegate to create. Must be a constructed delegate type. + /// The 'this' parameter to wrap. May be null. + /// The target function pointer. Must be compatible with the delegate type. + internal static Delegate CreateDelegateUnsafe(RuntimeType delegateType, object? target, IntPtr fnPtr) + { + Debug.Assert(delegateType is not null); + Debug.Assert(delegateType.IsDelegate()); + Debug.Assert(fnPtr != IntPtr.Zero); + + Delegate del = (Delegate)RuntimeHelpers.GetUninitializedObjectSkipChecks(delegateType); + del.DelegateConstruct(target, fnPtr); + return del; + } [MethodImpl(MethodImplOptions.InternalCall)] internal extern IntPtr GetMulticastInvoke(); 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 6f4d51917f4a51..a7e982e0575e4a 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 @@ -164,12 +164,25 @@ public static object GetUninitializedObject( } object? obj = null; - GetUninitializedObject(new QCallTypeHandle(ref rt), ObjectHandleOnStack.Create(ref obj)); + GetUninitializedObject(new QCallTypeHandle(ref rt), fSkipChecks: Interop.BOOL.FALSE, ObjectHandleOnStack.Create(ref obj)); + return obj!; + } + + /// + /// Creates a new zero-inited object on the heap, bypassing all verification checks. + /// Caller is responsible for ensuring not attempting to instantiate an abstract type, ref struct, etc. + /// + internal static object GetUninitializedObjectSkipChecks(RuntimeType type) + { + Debug.Assert(type is not null); + + object? obj = null; + GetUninitializedObject(new QCallTypeHandle(ref type), fSkipChecks: Interop.BOOL.TRUE, ObjectHandleOnStack.Create(ref obj)); return obj!; } [DllImport(RuntimeHelpers.QCall)] - private static extern void GetUninitializedObject(QCallTypeHandle type, ObjectHandleOnStack retObject); + private static extern void GetUninitializedObject(QCallTypeHandle type, Interop.BOOL fSkipChecks, ObjectHandleOnStack retObject); [MethodImpl(MethodImplOptions.InternalCall)] internal static extern object AllocateUninitializedClone(object obj); 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 fbca319a20c229..7b7ee272ebdb01 100644 --- a/src/coreclr/src/System.Private.CoreLib/src/System/RuntimeHandles.cs +++ b/src/coreclr/src/System.Private.CoreLib/src/System/RuntimeHandles.cs @@ -273,6 +273,7 @@ internal static void GetActivationInfo( GetActivationInfo( ObjectHandleOnStack.Create(ref rt), + fAvoidBoxing: Interop.BOOL.FALSE, &pfnAllocatorTemp, &vAllocatorFirstArgTemp, &pfnCtorTemp, &fCtorIsPublicTemp); @@ -282,9 +283,38 @@ internal static void GetActivationInfo( ctorIsPublic = fCtorIsPublicTemp != Interop.BOOL.FALSE; } + /// + /// Given a RuntimeType for a struct, 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 GetActivationInfoForStruct( + RuntimeType rt, + out delegate* pfnCtor, + out bool ctorIsPublic) + { + Debug.Assert(rt != null); + Debug.Assert(rt.IsValueType); + + delegate* pfnAllocatorTemp = default; + void* vAllocatorFirstArgTemp = default; + delegate* pfnCtorTemp = default; + Interop.BOOL fCtorIsPublicTemp = default; + + GetActivationInfo( + ObjectHandleOnStack.Create(ref rt), + fAvoidBoxing: Interop.BOOL.TRUE, + &pfnAllocatorTemp, &vAllocatorFirstArgTemp, + (delegate**)&pfnCtorTemp, &fCtorIsPublicTemp); + + pfnCtor = pfnCtorTemp; + ctorIsPublic = fCtorIsPublicTemp != Interop.BOOL.FALSE; + } + [DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)] private static extern void GetActivationInfo( ObjectHandleOnStack pRuntimeType, + Interop.BOOL fAvoidBoxing, delegate** ppfnAllocator, void** pvAllocatorFirstArg, delegate** ppfnCtor, 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 deleted file mode 100644 index abf4f4afa4410f..00000000000000 --- a/src/coreclr/src/System.Private.CoreLib/src/System/RuntimeType.ActivatorCache.cs +++ /dev/null @@ -1,130 +0,0 @@ -// 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 6e2c218e31bfa0..3b6275ffa5dd95 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 @@ -3858,7 +3858,7 @@ public override Type MakeArrayType(int rank) #region Legacy Internal - private void CreateInstanceCheckThis() + internal void CreateInstanceCheckThis() { if (ContainsGenericParameters) throw new ArgumentException(SR.Format(SR.Acc_CreateGenericEx, this)); @@ -3975,13 +3975,13 @@ private void CreateInstanceCheckThis() // n.b. In coreclr we ignore 'skipCheckThis' (assumed to be false) // and 'fillCache' (assumed to be true). - if (GenericCache is not ActivatorCache cache) + if (GenericCache is not ActivationFactory factory) { - cache = new ActivatorCache(this); - GenericCache = cache; + factory = new ActivationFactory(this); + GenericCache = factory; } - if (!cache.CtorIsPublic && publicOnly) + if (!factory.CtorIsPublic && publicOnly) { throw new MissingMethodException(SR.Format(SR.Arg_NoDefCTor, this)); } @@ -3990,10 +3990,11 @@ private void CreateInstanceCheckThis() // bubble up to the caller; the ctor invocation is within the try block so // that it can be wrapped in TIE if needed. - object? obj = cache.CreateUninitializedObject(this); + factory.DebugValidateExpectedType(this); + object? obj = factory.GetUninitializedObject(); try { - cache.CallConstructor(obj); + factory.CallConstructor(obj); } catch (Exception e) when (wrapExceptions) { diff --git a/src/coreclr/src/vm/reflectioninvocation.cpp b/src/coreclr/src/vm/reflectioninvocation.cpp index d1f48ed93e6ada..d93335c1350ea3 100644 --- a/src/coreclr/src/vm/reflectioninvocation.cpp +++ b/src/coreclr/src/vm/reflectioninvocation.cpp @@ -2038,12 +2038,15 @@ void RuntimeTypeHandle::ValidateTypeAbleToBeInstantiated( /* * Given a RuntimeType, queries info on how to instantiate the object. * pRuntimeType - [required] the RuntimeType object + * fAvoidBoxing - [required] true if ppfnCtor should have the mgd sig + * (ref T) -> void for value types, otherwise object -> void. * 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 + * mgd sig object -> void, or null if no ctor is needed for this type. + * for value types, can be mgd sig (ref T) -> void. * pfCtorIsPublic - [required, null-init] whether the parameterless ctor is public * ========== * This method will not run the type's static cctor. @@ -2051,6 +2054,7 @@ void RuntimeTypeHandle::ValidateTypeAbleToBeInstantiated( */ void QCALLTYPE RuntimeTypeHandle::GetActivationInfo( QCall::ObjectHandleOnStack pRuntimeType, + BOOL fAvoidBoxing, PCODE* ppfnAllocator, void** pvAllocatorFirstArg, PCODE* ppfnCtor, @@ -2140,9 +2144,9 @@ void QCALLTYPE RuntimeTypeHandle::GetActivationInfo( if (pMT->HasDefaultConstructor()) { - // managed sig: object -> void + // managed sig: object -> void (or ref T -> void) // for ctors on value types, lookup boxed entry point stub - MethodDesc* pMD = pMT->GetDefaultConstructor(pMT->IsValueType() /* forceBoxedEntryPoint */); + MethodDesc* pMD = pMT->GetDefaultConstructor(pMT->IsValueType() && !fAvoidBoxing /* forceBoxedEntryPoint */); _ASSERTE(pMD != NULL); PCODE pCode = pMD->GetMultiCallableAddrOfCode(); @@ -2219,7 +2223,7 @@ FCIMPLEND //************************************************************************************************* //************************************************************************************************* //************************************************************************************************* -void QCALLTYPE ReflectionSerialization::GetUninitializedObject(QCall::TypeHandle pType, QCall::ObjectHandleOnStack retObject) +void QCALLTYPE ReflectionSerialization::GetUninitializedObject(QCall::TypeHandle pType, BOOL fSkipChecks, QCall::ObjectHandleOnStack retObject) { QCALL_CONTRACT; @@ -2227,19 +2231,25 @@ void QCALLTYPE ReflectionSerialization::GetUninitializedObject(QCall::TypeHandle TypeHandle type = pType.AsTypeHandle(); - RuntimeTypeHandle::ValidateTypeAbleToBeInstantiated(type, true /* fForGetUninitializedInstance */); + if (!fSkipChecks) + { + RuntimeTypeHandle::ValidateTypeAbleToBeInstantiated(type, true /* fForGetUninitializedInstance */); + } MethodTable* pMT = type.AsMethodTable(); + if (!fSkipChecks) + { #ifdef FEATURE_COMINTEROP - // Also do not allow allocation of uninitialized RCWs (COM objects). - if (pMT->IsComObjectType()) - COMPlusThrow(kNotSupportedException, W("NotSupported_ManagedActivation")); + // Also do not allow allocation of uninitialized RCWs (COM objects). + if (pMT->IsComObjectType()) + COMPlusThrow(kNotSupportedException, W("NotSupported_ManagedActivation")); #endif // FEATURE_COMINTEROP - // If it is a nullable, return the underlying type instead. - if (pMT->IsNullable()) - pMT = pMT->GetInstantiation()[0].GetMethodTable(); + // If it is a nullable, return the underlying type instead. + if (pMT->IsNullable()) + pMT = pMT->GetInstantiation()[0].GetMethodTable(); + } { GCX_COOP(); diff --git a/src/coreclr/src/vm/reflectioninvocation.h b/src/coreclr/src/vm/reflectioninvocation.h index c00c17c883ac84..0c09b1edea7b5c 100644 --- a/src/coreclr/src/vm/reflectioninvocation.h +++ b/src/coreclr/src/vm/reflectioninvocation.h @@ -81,7 +81,7 @@ class ReflectionInvocation { class ReflectionSerialization { public: static - void QCALLTYPE GetUninitializedObject(QCall::TypeHandle pType, QCall::ObjectHandleOnStack retObject); + void QCALLTYPE GetUninitializedObject(QCall::TypeHandle pType, BOOL fSkipChecks, QCall::ObjectHandleOnStack retObject); }; class ReflectionEnum { diff --git a/src/coreclr/src/vm/runtimehandles.h b/src/coreclr/src/vm/runtimehandles.h index 7675d1b3bcac9f..3ed8da220a6505 100644 --- a/src/coreclr/src/vm/runtimehandles.h +++ b/src/coreclr/src/vm/runtimehandles.h @@ -126,6 +126,7 @@ class RuntimeTypeHandle { static void QCALLTYPE GetActivationInfo( QCall::ObjectHandleOnStack pRuntimeType, + BOOL fAvoidBoxing, PCODE* ppfnAllocator, void** pvAllocatorFirstArg, PCODE* ppfnCtor, 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 96ac22ef364295..4603f4367f5464 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Activator.RuntimeType.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Activator.RuntimeType.cs @@ -2,8 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics.CodeAnalysis; -using System.Reflection; using System.Globalization; +using System.Reflection; using System.Runtime.Loader; using System.Runtime.Remoting; using System.Threading; @@ -152,5 +152,50 @@ public static partial class Activator } private static T CreateDefaultInstance() where T : struct => default; + + public static Func CreateFactory([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] Type type, bool nonPublic) + { + if (type is null) + throw new ArgumentNullException(nameof(type)); + + if (type.UnderlyingSystemType is not RuntimeType rt) + throw new ArgumentException(SR.Arg_MustBeType, nameof(type)); + + // First create the factory instance. + + ActivationFactory factory = new ActivationFactory(rt); + if (!nonPublic && !factory.CtorIsPublic) + { + throw new MissingMethodException(SR.Format(SR.Arg_NoDefCTor, rt)); + } + + // Then create a delegate to factory.CreateInstance, closed over the "this" parameter. + + return (Func)factory.CreateDelegate((RuntimeType)typeof(Func)); + } + + [DynamicDependency("#ctor", typeof(ActivationFactory<>))] + public static Func CreateFactory<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T>() + { + if (typeof(T).IsValueType) + { + IActivationFactory factory = (IActivationFactory)RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(ActivationFactory<>), (RuntimeType)typeof(T)); + return (Func)factory.GetCreateInstanceDelegate(); + } + else + { + // First create the factory instance. + + ActivationFactory factory = new ActivationFactory((RuntimeType)typeof(T)); + if (!factory.CtorIsPublic) + { + throw new MissingMethodException(SR.Format(SR.Arg_NoDefCTor, typeof(T))); + } + + // Then create a delegate to factory.CreateInstance, closed over the "this" parameter. + + return (Func)factory.CreateDelegate((RuntimeType)typeof(Func)); + } + } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Activator.cs b/src/libraries/System.Private.CoreLib/src/System/Activator.cs index 8bdd99692ca3f4..8a46fe7fd109ce 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Activator.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Activator.cs @@ -40,6 +40,13 @@ public static partial class Activator public static object? CreateInstance([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type type) => CreateInstance(type, nonPublic: false); + [DebuggerHidden] + [DebuggerStepThrough] + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2067:UnrecognizedReflectionPattern", + Justification = "CreateFactory(Type, bool) is annotated as requiring private ctors, but this entry point only cares about public ctors.")] + public static Func CreateFactory([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type type) => + CreateFactory(type, nonPublic: false); + [RequiresUnreferencedCode("Type and its constructor could be removed")] public static ObjectHandle? CreateInstanceFrom(string assemblyFile, string typeName) => CreateInstanceFrom(assemblyFile, typeName, false, ConstructorDefault, null, null, null, null); diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index e7b8eac6abc532..5518561d45518b 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -66,6 +66,9 @@ 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() { throw null; } + public static System.Func CreateFactory([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] System.Type type) { throw null; } + public static System.Func CreateFactory([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] System.Type type, bool nonPublic) { 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; } diff --git a/src/libraries/System.Runtime/tests/System/ActivatorTests.Generic.cs b/src/libraries/System.Runtime/tests/System/ActivatorTests.Generic.cs index 2ba5c521ae87e4..8ffcddde15d305 100644 --- a/src/libraries/System.Runtime/tests/System/ActivatorTests.Generic.cs +++ b/src/libraries/System.Runtime/tests/System/ActivatorTests.Generic.cs @@ -8,6 +8,22 @@ namespace System.Tests { public partial class ActivatorTests { + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.SupportsComInterop))] + public void CreateInstanceT_ComObject_Success() + { + WbemContext instance = Activator.CreateInstance(); + Assert.NotNull(instance); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.SupportsComInterop))] + public void CreateFactoryT_ComObject_Success() + { + Func factory = Activator.CreateFactory(); + Assert.NotNull(factory); + WbemContext instance = factory(); + Assert.NotNull(instance); + } + [Fact] public void CreateInstanceT_Array_ThrowsMissingMethodException() => Assert.Throws(() => Activator.CreateInstance()); @@ -60,6 +76,82 @@ public void CreateInstanceT_StructWithoutDefaultConstructor_ThrowsMissingMethodE public void CreateInstanceT_StructWithDefaultConstructorThatThrows_ThrowsTargetInvocationException() => Assert.Throws(() => Activator.CreateInstance()); + [Fact] + public void CreateFactoryT_ReferenceTypeWithPublicCtor_Success() + { + Func factory = Activator.CreateFactory(); + PublicType instance = factory(); + Assert.NotNull(instance); + Assert.True(instance.CtorWasCalled); + } + + [Fact] + public void CreateFactoryT_ReferenceTypeWithPrivateCtor_ThrowsMissingMethodException() + => Assert.Throws(() => Activator.CreateFactory()); + + [Fact] + public void CreateFactoryT_ReferenceTypeWithPrivateCtorThatThrows_DoesNotWrapInTargetInvocationException() + { + Func factory = Activator.CreateFactory(); + Assert.Throws(() => factory()); // no TIE + } + + [Fact] + public void CreateFactoryT_OfNullableT_ReturnsNull() + { + Func factory = Activator.CreateFactory(); + Assert.NotNull(factory); + int? instance = factory(); + Assert.False(instance.HasValue); + } + + [Fact] + public void CreateFactoryT_OfValueTypeWithoutDefaultCtor_ReturnsDefaultT() + { + Func factory = Activator.CreateFactory(); + Assert.NotNull(factory); + ValueTypeWithParameterfulConstructor instance = factory(); + Assert.False(instance.ParameterfulCtorWasCalled); + } + + [Fact] + public void CreateFactoryT_OfValueTypeWithPublicDefaultCtor_CallsDefaultCtor() + { + Func factory = Activator.CreateFactory(); + Assert.NotNull(factory); + StructWithPublicDefaultConstructor instance = factory(); + Assert.True(instance.ConstructorInvoked); + Assert.True(IsAddressOnLocalStack(instance.AddressPassedToConstructor)); // using Func, value type ctor is called using ref to stack local, no boxing + } + + [Fact] + public void CreateFactoryT_OfValueTypeWithPrivateDefaultCtor_ThrowsMissingMethodException() + => Assert.Throws(() => Activator.CreateFactory()); + + [Fact] + public void CreateFactoryT_OfValueTypeWithPublicDefaultCtorThatThrows_DoesNotWrapInTargetInvocationException() + { + Func factory = Activator.CreateFactory(); + Assert.NotNull(factory); + Assert.Throws(() => factory()); // shouldn't wrap in TIE + } + + [Fact] + public void CreateFactoryT_Array_ThrowsMissingMethodException() => + Assert.Throws(() => Activator.CreateFactory()); + + [Fact] + public void CreateFactoryT_Interface_ThrowsMissingMethodException() => + Assert.Throws(() => Activator.CreateFactory()); + + [Fact] + public void CreateFactoryT_AbstractClass_ThrowsMissingMethodException() => + Assert.Throws(() => Activator.CreateFactory()); + + [Fact] + public void CreateFactoryT_String_ThrowsMissingMethodException() => + Assert.Throws(() => Activator.CreateFactory()); + private interface IInterface { } diff --git a/src/libraries/System.Runtime/tests/System/ActivatorTests.cs b/src/libraries/System.Runtime/tests/System/ActivatorTests.cs index 05e07447a96164..3a2a914dd79fb9 100644 --- a/src/libraries/System.Runtime/tests/System/ActivatorTests.cs +++ b/src/libraries/System.Runtime/tests/System/ActivatorTests.cs @@ -8,6 +8,7 @@ using System.IO; using System.Reflection; using System.Reflection.Emit; +using System.Runtime.InteropServices; using System.Runtime.Remoting; using Microsoft.DotNet.RemoteExecutor; using Xunit; @@ -16,6 +17,9 @@ namespace System.Tests { public partial class ActivatorTests { + // random GUID, shouldn't map to any known COM registration + private static readonly Guid UnknownComClsid = new Guid("db8d1a68-84f2-4c37-9581-33286f3c1b49"); + [Fact] public static void CreateInstance() { @@ -91,6 +95,44 @@ public void CreateInstance_NonPublicTypeWithPrivateDefaultConstructor_Success() Assert.Equal(-1, c2.Property); } + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.SupportsComInterop))] + public void CreateInstance_ComObject_Success() + { + object instance = Activator.CreateInstance(typeof(WbemContext)); + Assert.NotNull(instance); + Assert.True(instance.GetType().IsEquivalentTo(typeof(WbemContext))); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.SupportsComInterop))] + public void CreateInstance_ComObjectWithUnknownClsid_ThrowsCOMException() + { + // failure occurs at construction time, no TIE + Type unknownComType = Type.GetTypeFromCLSID(UnknownComClsid); + var ex = Assert.Throws(() => Activator.CreateInstance(unknownComType)); + Assert.Equal(unchecked((int)0x80040154), ex.HResult); // REGDB_E_CLASSNOTREG, to distinguish from other unexpected exceptions + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.SupportsComInterop))] + public void CreateFactory_ComObject_Success() + { + Func factory = Activator.CreateFactory(typeof(WbemContext)); + Assert.NotNull(factory); + object instance = factory(); + Assert.NotNull(instance); + Assert.True(instance.GetType().IsEquivalentTo(typeof(WbemContext))); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.SupportsComInterop))] + public void CreateFactory_ComObjectWithUnknownClsid_ThrowsCOMException() + { + // obtaining the factory should succeed; invoking the factory should fail + Type unknownComType = Type.GetTypeFromCLSID(UnknownComClsid); + Func factory = Activator.CreateFactory(unknownComType); + Assert.NotNull(factory); + var ex = Assert.Throws(() => factory()); + Assert.Equal(unchecked((int)0x80040154), ex.HResult); // REGDB_E_CLASSNOTREG, to distinguish from other unexpected exceptions + } + [Fact] public void CreateInstance_PublicOnlyTypeWithPrivateDefaultConstructor_ThrowsMissingMethodException() { @@ -277,6 +319,156 @@ public static void TestActivatorOnNonActivatableFinalizableTypes() Assert.False(TypeWithPrivateDefaultCtorAndFinalizer.WasCreated); } + public static IEnumerable CreateInstance_NegativeTestCases() + { + // TODO: Test actual function pointer types when typeof(delegate*<...>) support is available + + yield return new[] { typeof(string), typeof(MissingMethodException) }; // variable-length type + yield return new[] { typeof(int[]), typeof(MissingMethodException) }; // variable-length type + yield return new[] { typeof(int[,]), typeof(MissingMethodException) }; // variable-length type + yield return new[] { Array.CreateInstance(typeof(int), new[] { 1 }, new[] { 1 }).GetType(), typeof(MissingMethodException) }; // variable-length type (non-szarray) + yield return new[] { typeof(Array), typeof(MissingMethodException) }; // abstract type + yield return new[] { typeof(Enum), typeof(MissingMethodException) }; // abstract type + + yield return new[] { typeof(Stream), typeof(MissingMethodException) }; // abstract type + yield return new[] { typeof(Buffer), typeof(MissingMethodException) }; // static type (runtime sees it as abstract) + yield return new[] { typeof(IDisposable), typeof(MissingMethodException) }; // interface type + + yield return new[] { typeof(List<>), typeof(ArgumentException) }; // open generic type + yield return new[] { typeof(List<>).GetGenericArguments()[0], PlatformDetection.IsMonoRuntime ? typeof(MemberAccessException) : typeof(ArgumentException) }; // 'T' placeholder typedesc + + yield return new[] { typeof(Delegate), typeof(MissingMethodException) }; // abstract type + + yield return new[] { typeof(void), typeof(NotSupportedException) }; // explicit block in place + yield return new[] { typeof(int).MakePointerType(), typeof(MissingMethodException) }; // pointer typedesc + yield return new[] { typeof(int).MakeByRefType(), typeof(MissingMethodException) }; // byref typedesc + + yield return new[] { typeof(ReadOnlySpan), typeof(NotSupportedException) }; // byref type + yield return new[] { typeof(ArgIterator), typeof(NotSupportedException) }; // byref type + + yield return new[] { typeof(TypeWithoutDefaultCtor), typeof(MissingMethodException) }; // reference type with no parameterless ctor + yield return new[] { typeof(TypeWithVarargsCtor), typeof(MissingMethodException) }; // parameterless ctor exists but has incorrect calling convention + + Type canonType = typeof(object).Assembly.GetType("System.__Canon", throwOnError: false); + if (canonType != null) + { + yield return new[] { typeof(List<>).MakeGenericType(canonType), typeof(NotSupportedException) }; // shared by generic instantiations + } + + Type comObjType = typeof(object).Assembly.GetType("System.__ComObject", throwOnError: false); + if (comObjType != null) + { + yield return new[] { comObjType, typeof(InvalidComObjectException) }; // COM type with no associated CLSID + } + } + + [Theory] + [MemberData(nameof(CreateInstance_NegativeTestCases))] + public static void CreateInstance_InvalidType_ThrowsException(Type typeToInstantiate, Type expectedExceptionType) + { + // We don't expect a TIE wrapper since we won't get as far as calling the ctor + Assert.Throws(expectedExceptionType, () => Activator.CreateInstance(typeToInstantiate)); + Assert.Throws(expectedExceptionType, () => Activator.CreateInstance(typeToInstantiate, nonPublic: true)); + } + + [Theory] + [MemberData(nameof(CreateInstance_NegativeTestCases))] + public static void CreateFactory_InvalidType_ThrowsException(Type typeToInstantiate, Type expectedExceptionType) + { + // We don't expect a TIE wrapper since we won't get as far as calling the ctor + Assert.Throws(expectedExceptionType, () => Activator.CreateFactory(typeToInstantiate)); + Assert.Throws(expectedExceptionType, () => Activator.CreateFactory(typeToInstantiate, nonPublic: true)); + } + + [Fact] + public void CreateFactory_ReferenceTypeWithPublicCtor_Success() + { + Func factory = Activator.CreateFactory(typeof(PublicType)); + Assert.NotNull(factory); + object instance = factory(); + var castInstance = Assert.IsType(instance); + Assert.True(castInstance.CtorWasCalled); + } + + [Fact] + public void CreateFactory_ReferenceTypeWithPrivateCtor_ThrowsMissingMethodException() + { + Assert.Throws(() => Activator.CreateFactory(typeof(PrivateTypeWithDefaultCtor))); + Assert.Throws(() => Activator.CreateFactory(typeof(PrivateTypeWithDefaultCtor), nonPublic: false)); + } + + [Fact] + public void CreateFactory_ReferenceTypeWithPrivateCtor_AllowNonPublicCtors_Success() + { + Func factory = Activator.CreateFactory(typeof(PrivateTypeWithDefaultCtor), nonPublic: true); + Assert.NotNull(factory); + object instance = factory(); + var castInstance = Assert.IsType(instance); + Assert.True(castInstance.CtorWasCalled); + } + + [Fact] + public void CreateFactory_ReferenceTypeWithPrivateCtorThatThrows_DoesNotWrapInTargetInvocationException() + { + Func factory = Activator.CreateFactory(typeof(TypeWithDefaultCtorThatThrows)); + Assert.NotNull(factory); + Assert.Throws(() => factory()); // no TIE + } + + [Fact] + public void CreateFactory_OfNullableT_ReturnsNull() + { + Func factory = Activator.CreateFactory(typeof(int?)); + Assert.NotNull(factory); + Assert.Null(factory()); + } + + [Fact] + public void CreateFactory_OfValueTypeWithoutDefaultCtor_ReturnsBoxedDefaultT() + { + Func factory = Activator.CreateFactory(typeof(ValueTypeWithParameterfulConstructor)); + Assert.NotNull(factory); + object instance = factory(); + var castInstance = Assert.IsType(instance); + Assert.False(castInstance.ParameterfulCtorWasCalled); + } + + [Fact] + public void CreateFactory_OfValueTypeWithPublicDefaultCtor_CallsDefaultCtor() + { + Func factory = Activator.CreateFactory(typeof(StructWithPublicDefaultConstructor)); + Assert.NotNull(factory); + object instance = factory(); + var castInstance = Assert.IsType(instance); + Assert.True(castInstance.ConstructorInvoked); + Assert.False(IsAddressOnLocalStack(castInstance.AddressPassedToConstructor)); // using Func, value type ctor is called using unboxing stub + } + + [Fact] + public void CreateFactory_OfValueTypeWithPrivateDefaultCtor_ThrowsMissingMethodException() + { + Assert.Throws(() => Activator.CreateFactory(typeof(StructWithPrivateDefaultConstructor))); + Assert.Throws(() => Activator.CreateFactory(typeof(StructWithPrivateDefaultConstructor), nonPublic: false)); + } + + [Fact] + public void CreateFactory_OfValueTypeWithPrivateDefaultCtor_AllowNonPublicCtors_Success() + { + Func factory = Activator.CreateFactory(typeof(StructWithPrivateDefaultConstructor), nonPublic: true); + Assert.NotNull(factory); + object instance = factory(); + var castInstance = Assert.IsType(instance); + Assert.True(castInstance.ConstructorInvoked); + } + + [Fact] + public void CreateFactory_OfValueTypeWithPublicDefaultCtorThatThrows_DoesNotWrapInTargetInvocationException() + { + Func factory = Activator.CreateFactory(typeof(StructWithDefaultConstructorThatThrows)); + Assert.NotNull(factory); + Assert.Throws(() => factory()); // shouldn't wrap in TIE + } + private class PrivateType { public PrivateType() { } @@ -284,7 +476,8 @@ public PrivateType() { } class PrivateTypeWithDefaultCtor { - private PrivateTypeWithDefaultCtor() { } + public readonly bool CtorWasCalled; + private PrivateTypeWithDefaultCtor() { CtorWasCalled = true; } } class PrivateTypeWithoutDefaultCtor @@ -363,6 +556,12 @@ public struct ValueTypeWithDefaultConstructor { } + public struct ValueTypeWithParameterfulConstructor + { + public readonly bool ParameterfulCtorWasCalled; + public ValueTypeWithParameterfulConstructor(int i) { ParameterfulCtorWasCalled = true; } + } + public class TypeWithPrivateDefaultConstructor { public int Property { get; } @@ -401,6 +600,11 @@ public class TypeWithoutDefaultCtor private TypeWithoutDefaultCtor(int x) { } } + public class TypeWithVarargsCtor + { + public TypeWithVarargsCtor(__arglist) { } + } + public class TypeWithDefaultCtorThatThrows { public TypeWithDefaultCtorThatThrows() { throw new Exception(); } @@ -477,6 +681,14 @@ class Flag public static bool Equal(int i) { return cnt == i; } } + // This type definition is lifted from System.Management, just for testing purposes + [ClassInterface((short)0x0000)] + [Guid("674B6698-EE92-11D0-AD71-00C04FD8FDFF")] + [ComImport] + internal class WbemContext + { + } + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsInvokingStaticConstructorsSupported))] public static void TestingBindingFlags() { @@ -783,7 +995,8 @@ private static void CheckValidity(ObjectHandle instance, string expected) public class PublicType { - public PublicType() { } + public readonly bool CtorWasCalled; + public PublicType() { CtorWasCalled = true; } } [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] @@ -807,5 +1020,13 @@ public void CreateInstance_TypeBuilder_ThrowsNotSupportedException() Assert.Throws("type", () => Activator.CreateInstance(typeBuilder)); Assert.Throws(() => Activator.CreateInstance(typeBuilder, new object[0])); } + + // Returns true iff the target address is on the local thread's stack. + // Heuristic-based: probably true if target address is within 1MB of a local's address. + private unsafe static bool IsAddressOnLocalStack(IntPtr address) + { + byte dummy = 0; + return Math.Abs((byte*)address - &dummy) < 1024 * 1024; + } } } diff --git a/src/libraries/System.Runtime/tests/System/Runtime/CompilerServices/RuntimeHelpersTests.cs b/src/libraries/System.Runtime/tests/System/Runtime/CompilerServices/RuntimeHelpersTests.cs index 33327d00fed473..6ed2a2037b9eaf 100644 --- a/src/libraries/System.Runtime/tests/System/Runtime/CompilerServices/RuntimeHelpersTests.cs +++ b/src/libraries/System.Runtime/tests/System/Runtime/CompilerServices/RuntimeHelpersTests.cs @@ -6,6 +6,7 @@ using System.IO; using System.Reflection; using System.Runtime.InteropServices; +using System.Tests; using Xunit; namespace System.Runtime.CompilerServices.Tests @@ -300,6 +301,14 @@ public static void GetUninitializedObject_DoesNotRunConstructor() Assert.Equal(0, ((ObjectWithDefaultCtor)RuntimeHelpers.GetUninitializedObject(typeof(ObjectWithDefaultCtor))).Value); } + [Fact] + public static void GetUninitializedObject_ValueTypeWithExplicitDefaultCtor_DoesNotRunConstructor() + { + object o = RuntimeHelpers.GetUninitializedObject(typeof(StructWithPublicDefaultConstructor)); + var castObj = Assert.IsType(o); + Assert.False(castObj.ConstructorInvoked); + } + [Fact] public static void GetUninitializedObject_Struct() { diff --git a/src/libraries/System.Runtime/tests/TestStructs/System.TestStructs.il b/src/libraries/System.Runtime/tests/TestStructs/System.TestStructs.il index dabc5ac4a1b452..251ff74395d8df 100644 --- a/src/libraries/System.Runtime/tests/TestStructs/System.TestStructs.il +++ b/src/libraries/System.Runtime/tests/TestStructs/System.TestStructs.il @@ -25,10 +25,14 @@ .class public sequential sealed StructWithPublicDefaultConstructor extends [System.Runtime]System.ValueType { + .field public initonly native int AddressPassedToConstructor .field public initonly bool ConstructorInvoked .method public hidebysig specialname rtspecialname instance void .ctor () cil managed { + ldarg.0 + ldarg.0 + stfld native int System.Tests.StructWithPublicDefaultConstructor::AddressPassedToConstructor ldarg.0 ldc.i4.1 stfld bool System.Tests.StructWithPublicDefaultConstructor::ConstructorInvoked @@ -39,10 +43,13 @@ .class public sequential sealed StructWithPrivateDefaultConstructor extends [System.Runtime]System.ValueType { - .size 1 + .field public initonly bool ConstructorInvoked .method private hidebysig specialname rtspecialname instance void .ctor () cil managed { + ldarg.0 + ldc.i4.1 + stfld bool System.Tests.StructWithPrivateDefaultConstructor::ConstructorInvoked ret } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionEmitMemberAccessor.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionEmitMemberAccessor.cs index 6dc8490a398f0d..833e457db4c121 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionEmitMemberAccessor.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionEmitMemberAccessor.cs @@ -12,6 +12,10 @@ namespace System.Text.Json.Serialization { internal sealed class ReflectionEmitMemberAccessor : MemberAccessor { +#if NET6_0 // should really be NET6_0_OR_GREATER + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2067:UnrecognizedReflectionPattern", + Justification = "Temporary until IL linker understands new patterns.")] +#endif public override JsonClassInfo.ConstructorDelegate? CreateConstructor(Type type) { Debug.Assert(type != null); @@ -27,6 +31,9 @@ internal sealed class ReflectionEmitMemberAccessor : MemberAccessor return null; } +#if NET6_0 // should really be NET6_0_OR_GREATER + return new JsonClassInfo.ConstructorDelegate(Activator.CreateFactory(type)); +#else var dynamicMethod = new DynamicMethod( ConstructorInfo.ConstructorName, JsonClassInfo.ObjectType, @@ -53,6 +60,7 @@ internal sealed class ReflectionEmitMemberAccessor : MemberAccessor generator.Emit(OpCodes.Ret); return (JsonClassInfo.ConstructorDelegate)dynamicMethod.CreateDelegate(typeof(JsonClassInfo.ConstructorDelegate)); +#endif } public override JsonClassInfo.ParameterizedConstructorDelegate? CreateParameterizedConstructor(ConstructorInfo constructor) => diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionMemberAccessor.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionMemberAccessor.cs index f42d1374c75b7c..00a1ca71055cdf 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionMemberAccessor.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionMemberAccessor.cs @@ -24,7 +24,11 @@ internal sealed class ReflectionMemberAccessor : MemberAccessor return null; } +#if NET6_0 // should really be NET6_0_OR_GREATER + return new JsonClassInfo.ConstructorDelegate(Activator.CreateFactory(type)); +#else return () => Activator.CreateInstance(type, nonPublic: false); +#endif } public override JsonClassInfo.ParameterizedConstructorDelegate? CreateParameterizedConstructor(ConstructorInfo constructor)