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 501e96f9a1437..fdbba863d0e53 100644 --- a/src/coreclr/src/System.Private.CoreLib/src/System/RuntimeHandles.cs +++ b/src/coreclr/src/System.Private.CoreLib/src/System/RuntimeHandles.cs @@ -214,7 +214,14 @@ internal static bool HasElementType(RuntimeType type) /// MethodTable* corresponding to that type. If the type is closed over /// some T and is true, then returns the values for the 'T'. /// - internal static delegate* GetNewobjHelperFnPtr(RuntimeType type, out MethodTable* pMT, bool unwrapNullable, bool allowCom) + internal static delegate* GetNewobjHelperFnPtr( + // This API doesn't call any constructors, but the type needs to be seen as constructed. + // A type is seen as constructed if a constructor is kept. + // This obviously won't cover a type with no constructor. Reference types with no + // constructor are an academic problem. Valuetypes with no constructors are a problem, + // but IL Linker currently treats them as always implicitly boxed. + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] RuntimeType type, + out MethodTable* pMT, bool unwrapNullable, bool allowCom) { Debug.Assert(type != null); 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 3e6c887e78d89..7c890cc2b3f25 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 @@ -3971,24 +3971,26 @@ public void EnsureInitialized(RuntimeType type) Debug.Assert(cache._pfnNewobj != null); Debug.Assert(cache._pfnCtor != null); + Debug.Assert(cache._pMT != null); if (!cache._ctorIsPublic && publicOnly) { throw new MissingMethodException(SR.Format(SR.Arg_NoDefCTor, this)); } + object? obj = cache._pfnNewobj(cache._pMT); // allocation outside the try block (allow OOM to bubble up) + GC.KeepAlive(this); // can't allow the type to be collected before the object is created + try { - object? obj = cache._pfnNewobj(cache._pMT); - GC.KeepAlive(this); // can't allow the type to be collected before the object is created - cache._pfnCtor(obj); - return obj; } catch (Exception e) when (wrapExceptions) { throw new TargetInvocationException(e); } + + return obj; } if (!skipCheckThis) diff --git a/src/coreclr/src/vm/reflectioninvocation.cpp b/src/coreclr/src/vm/reflectioninvocation.cpp index be484f8157eac..ebafe5ec7d4b0 100644 --- a/src/coreclr/src/vm/reflectioninvocation.cpp +++ b/src/coreclr/src/vm/reflectioninvocation.cpp @@ -568,7 +568,7 @@ FCIMPLEND * throws an exception. If TypeHandle is a value type, the NEWOBJ helper will create * a boxed zero-inited instance of the value type. */ - void QCALLTYPE RuntimeTypeHandle::GetNewobjHelperFnPtr( +void QCALLTYPE RuntimeTypeHandle::GetNewobjHelperFnPtr( QCall::TypeHandle pTypeHandle, PCODE* ppNewobjHelper, MethodTable** ppMT, @@ -597,6 +597,8 @@ FCIMPLEND MethodTable* pMT = typeHandle.AsMethodTable(); PREFIX_ASSUME(pMT != NULL); + pMT->EnsureInstanceActive(); + // Don't allow creating instances of void or delegates if (pMT == MscorlibBinder::GetElementType(ELEMENT_TYPE_VOID) || pMT->IsDelegate()) { @@ -646,13 +648,10 @@ FCIMPLEND pMT = pMT->GetInstantiation()[0].GetMethodTable(); } - // Ensure the type's cctor has run - Assembly* pAssem = pMT->GetAssembly(); - if (!pMT->IsClassInited()) + // Run the type's cctor if needed (if not marked beforefieldinit) + if (pMT->HasPreciseInitCctors()) { - pMT->CheckRestore(); - pMT->EnsureInstanceActive(); - pMT->CheckRunClassInitThrowing(); + pMT->CheckRunClassInitAsIfConstructingThrowing(); } // And we're done! diff --git a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs index f22afa8b6a05c..d9292d7351bc2 100644 --- a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs +++ b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs @@ -69,6 +69,7 @@ public static bool IsDrawingSupported } public static bool IsInContainer => GetIsInContainer(); + public static bool SupportsComInterop => IsWindows && IsNetCore; // matches definitions in clr.featuredefines.props public static bool SupportsSsl3 => GetSsl3Support(); public static bool SupportsSsl2 => IsWindows && !PlatformDetection.IsWindows10Version1607OrGreater; 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 5b3667da142ee..bdfd23323170e 100644 --- a/src/libraries/System.Runtime/tests/System/Runtime/CompilerServices/RuntimeHelpersTests.cs +++ b/src/libraries/System.Runtime/tests/System/Runtime/CompilerServices/RuntimeHelpersTests.cs @@ -196,8 +196,6 @@ public static IEnumerable GetUninitializedObject_NegativeTestCases() { // TODO: Test actual function pointer types when typeof(delegate*<...>) support is available - Type canonType = typeof(object).Assembly.GetType("System.__Canon", throwOnError: true); - yield return new[] { typeof(string), typeof(ArgumentException) }; // variable-length type yield return new[] { typeof(int[]), typeof(ArgumentException) }; // variable-length type yield return new[] { typeof(int[,]), typeof(ArgumentException) }; // variable-length type @@ -220,9 +218,14 @@ public static IEnumerable GetUninitializedObject_NegativeTestCases() yield return new[] { typeof(ReadOnlySpan), typeof(NotSupportedException) }; // byref type yield return new[] { typeof(ArgIterator), typeof(NotSupportedException) }; // byref type - yield return new[] { typeof(List<>).MakeGenericType(canonType), typeof(NotSupportedException) }; // shared by generic instantiations - if (PlatformDetection.IsWindows) + if (PlatformDetection.IsNetCore) + { + Type canonType = typeof(object).Assembly.GetType("System.__Canon", throwOnError: true); + yield return new[] { typeof(List<>).MakeGenericType(canonType), typeof(NotSupportedException) }; // shared by generic instantiations + } + + if (PlatformDetection.SupportsComInterop) { Type comObjType = typeof(object).Assembly.GetType("System.__ComObject", throwOnError: true); yield return new[] { comObjType, typeof(NotSupportedException) }; // COM type @@ -238,6 +241,46 @@ internal class WbemContext { } + internal class ClassWithBeforeFieldInitCctor + { + private static readonly int _theInt = GetInt(); + + private static int GetInt() + { + AppDomain.CurrentDomain.SetData("ClassWithBeforeFieldInitCctor_CctorRan", true); + return 0; + } + } + + internal class ClassWithNormalCctor + { + private static readonly int _theInt; + + static ClassWithNormalCctor() + { + AppDomain.CurrentDomain.SetData("ClassWithNormalCctor_CctorRan", true); + _theInt = 0; + } + } + + [Fact] + public static void GetUninitalizedObject_DoesNotRunBeforeFieldInitCctors() + { + object o = RuntimeHelpers.GetUninitializedObject(typeof(ClassWithBeforeFieldInitCctor)); + Assert.IsType(o); + + Assert.Null(AppDomain.CurrentDomain.GetData("ClassWithBeforeFieldInitCctor_CctorRan")); + } + + [Fact] + public static void GetUninitalizedObject_RunsNormalStaticCtors() + { + object o = RuntimeHelpers.GetUninitializedObject(typeof(ClassWithNormalCctor)); + Assert.IsType(o); + + Assert.Equal(true, AppDomain.CurrentDomain.GetData("ClassWithNormalCctor_CctorRan")); + } + [Theory] [MemberData(nameof(GetUninitializedObject_NegativeTestCases))] public static void GetUninitializedObject_InvalidArguments_ThrowsException(Type typeToInstantiate, Type expectedExceptionType)