diff --git a/src/coreclr/src/System.Private.CoreLib/src/System/Reflection/INVOCATION_FLAGS.cs b/src/coreclr/src/System.Private.CoreLib/src/System/Reflection/INVOCATION_FLAGS.cs index e7d9b97ac5c6c5..b6a55518914a22 100644 --- a/src/coreclr/src/System.Private.CoreLib/src/System/Reflection/INVOCATION_FLAGS.cs +++ b/src/coreclr/src/System.Private.CoreLib/src/System/Reflection/INVOCATION_FLAGS.cs @@ -14,7 +14,8 @@ internal enum INVOCATION_FLAGS : uint INVOCATION_FLAGS_INITIALIZED = 0x00000001, // it's used for both method and field to signify that no access is allowed INVOCATION_FLAGS_NO_INVOKE = 0x00000002, - /* unused 0x00000004 */ + // Set for static ctors, to ensure that the static ctor is run as a static ctor before it is explicitly executed via reflection + INVOCATION_FLAGS_RUN_CLASS_CONSTRUCTOR = 0x00000004, // Set for static ctors and ctors on abstract types, which // can be invoked only if the "this" object is provided (even if it's null). INVOCATION_FLAGS_NO_CTOR_INVOKE = 0x00000008, diff --git a/src/coreclr/src/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.cs b/src/coreclr/src/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.cs index 9d0086ca442536..e06251e6ad851b 100644 --- a/src/coreclr/src/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.cs +++ b/src/coreclr/src/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.cs @@ -5,6 +5,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; +using System.Runtime.CompilerServices; using System.Text; using RuntimeTypeCache = System.RuntimeType.RuntimeTypeCache; @@ -47,7 +48,12 @@ internal INVOCATION_FLAGS InvocationFlags // We don't need other flags if this method cannot be invoked invocationFlags |= INVOCATION_FLAGS.INVOCATION_FLAGS_NO_INVOKE; } - else if (IsStatic || declaringType != null && declaringType.IsAbstract) + else if (IsStatic) + { + invocationFlags |= INVOCATION_FLAGS.INVOCATION_FLAGS_RUN_CLASS_CONSTRUCTOR | + INVOCATION_FLAGS.INVOCATION_FLAGS_NO_CTOR_INVOKE; + } + else if (declaringType != null && declaringType.IsAbstract) { invocationFlags |= INVOCATION_FLAGS.INVOCATION_FLAGS_NO_CTOR_INVOKE; } @@ -280,6 +286,21 @@ internal void ThrowNoInvokeException() // check basic method consistency. This call will throw if there are problems in the target/method relationship CheckConsistency(obj); + if ((invocationFlags & INVOCATION_FLAGS.INVOCATION_FLAGS_RUN_CLASS_CONSTRUCTOR) != 0) + { + // Run the class constructor through the class constructor mechanism instead of the Invoke path. + // This avoids allowing mutation of readonly static fields, and initializes the type correctly. + + var declaringType = DeclaringType; + + if (declaringType != null) + RuntimeHelpers.RunClassConstructor(declaringType.TypeHandle); + else + RuntimeHelpers.RunModuleConstructor(Module.ModuleHandle); + + return null; + } + Signature sig = Signature; // get the signature diff --git a/src/libraries/System.Reflection/tests/ConstructorInfoTests.cs b/src/libraries/System.Reflection/tests/ConstructorInfoTests.cs index 38a5e460718551..a1042452810748 100644 --- a/src/libraries/System.Reflection/tests/ConstructorInfoTests.cs +++ b/src/libraries/System.Reflection/tests/ConstructorInfoTests.cs @@ -68,6 +68,27 @@ public void Invoke_StaticConstructor_NullObject_NullParameters() Assert.Null(obj); } + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/40351", TestRuntimes.Mono)] + public void Invoke_StaticConstructorMultipleTimes() + { + ConstructorInfo[] constructors = GetConstructors(typeof(ClassWithStaticConstructorThatIsCalledMultipleTimesViaReflection)); + Assert.Equal(1, constructors.Length); + // The first time the static cctor is called, it should run the cctor twice + // Once to initialize run the cctor as a cctor + // The second to run it as a method which is invoked. + Assert.Equal(0, ClassWithStaticConstructorThatIsCalledMultipleTimesViaReflection.VisibleStatics.s_cctorCallCount); + object obj = constructors[0].Invoke(null, new object[] { }); + Assert.Null(obj); + Assert.Equal(1, ClassWithStaticConstructorThatIsCalledMultipleTimesViaReflection.VisibleStatics.s_cctorCallCount); + + // Subsequent invocations of the static cctor should not run the cctor at all, as it has already executed + // and running multiple times opens up the possibility of modifying read only static data + obj = constructors[0].Invoke(null, new object[] { }); + Assert.Null(obj); + Assert.Equal(1, ClassWithStaticConstructorThatIsCalledMultipleTimesViaReflection.VisibleStatics.s_cctorCallCount); + } + [Fact] [ActiveIssue("https://github.com/mono/mono/issues/15024", TestRuntimes.Mono)] public void Invoke_StaticConstructor_ThrowsMemberAccessException() @@ -236,6 +257,20 @@ public static class ClassWithStaticConstructor static ClassWithStaticConstructor() { } } + // Use this class only from the Invoke_StaticConstructorMultipleTimes method + public static class ClassWithStaticConstructorThatIsCalledMultipleTimesViaReflection + { + public static class VisibleStatics + { + public static int s_cctorCallCount; + } + + static ClassWithStaticConstructorThatIsCalledMultipleTimesViaReflection() + { + VisibleStatics.s_cctorCallCount++; + } + } + public struct StructWith1Constructor { public int x;