You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[mono] Class constructor for beforefieldinit class runs even if static fields are not accessed #88066
Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100] Unhandled exception rendering component: The type initializer for 'NestedStatic' threw an exception.System.TypeInitializationException: The type initializer for 'NestedStatic' threw an exception. ---> System.TypeInitializationException: The type initializer for 'BlzWasmTypeInitRepro.TwistedType' threw an exception. ---> System.ArgumentNullException: Value cannot be null. (Parameter 'source') at System.Linq.ThrowHelper.ThrowArgumentNullException(ExceptionArgument argument) at System.Linq.Enumerable.Prepend[TwistedType](IEnumerable`1 source, TwistedType element) at BlzWasmTypeInitRepro.TwistedType..cctor() in BlzWasmTypeInitRepro\Types.cs:line 5 --- End of inner exception stack trace --- at BlzWasmTypeInitRepro.TwistedType.NestedStatic..cctor() in BlzWasmTypeInitRepro\Types.cs:line 9 --- End of inner exception stack trace --- at BlzWasmTypeInitRepro.Pages.Index.BuildRenderTree(RenderTreeBuilder __builder) in BlzWasmTypeInitRepro\Pages\Index.razor:line 3 at Microsoft.AspNetCore.Components.ComponentBase.<.ctor>b__6_0(RenderTreeBuilder builder) at Microsoft.AspNetCore.Components.Rendering.ComponentState.RenderIntoBatch(RenderBatchBuilder batchBuilder, RenderFragment renderFragment, Exception& renderFragmentException)
Hmm... comparing the IL for the working and broken versions of FirstOne, it looks like the working version has beforefieldinit and the broken one doesn't.
Ok, this isn't one of the cases where ECMA allows us some leeway because of circular dependencies. Rather, this is a case where the cctor must run "at or before static field access" and mono is leaning on the "or before" (specifically I think we run it when the instance constructor for FirstOne is called)
It looks like mono is calling a beforefieldinit static constructor even if we never touch the static fields of that class
It looks like we're running the cctor for FirstOne when SecondOne tries to instantiate it. But constructor invocation for a beforefieldinit class is not sufficient. Only static field access (or explicit cctor invocation) should trigger it, I think.
There is a variant where instead of calling the instance constructor, we call a static method from FirstOne. In this case, too, CoreCLR doesn't call the cctor for FirstOne
Details
using System;
using System.Runtime.CompilerServices;
using System.Collections.Generic;
object o = SecondOne.ArrayOfFirstOne[0];
Console.WriteLine ($"Hello {o!=null}");
public static class Helper
{
[MethodImpl(MethodImplOptions.NoInlining)]
public static FirstOne[] PrependOne(FirstOne[] coll)
{
throw new Exception ("Called");
}
}
public sealed class FirstOne {
public static IEnumerable TheTroubleMaker = Helper.PrependOne (SecondOne.ArrayOfFirstOne);
public FirstOne() {}
public string World() => "World";
public static FirstOne SomeStaticMethod() { return null; }
}
public static class SecondOne {
public static FirstOne[] ArrayOfFirstOne = { FirstOne.SomeStaticMethod () };
}
@vargaz@BrzVlad I see some calls to mono_class_is_before_field_init in method-to-ir, so we're aware that sometimes beforefieldinit means we shouldn't run the cctor. But it looks like we do it anyway. In the interpreter I don't see anything that looks at beforefieldinit, so it's probably too eager, also.
I think #77513 is not quite the same. In that one, the bad static field is used in the body of a called method - it's just not on an executed path. In this one, the bad static field is not used at all in any JITted method.
Fixing 77513 seems harder
Activity
marek-safar commentedon Jul 24, 2023
/cc @lambdageek
lambdageek commentedon Jul 24, 2023
Repros in console app using mono as the runtime:
$ dotnet run --self-contained -p:UseMonoRuntime=true -r osx-arm64At first glance, I think this might be allowed by ECMA you cannot depend on the order of execution for static constructors...
lambdageek commentedon Jul 24, 2023
CoreCLR is also quite fragile here. change
#if trueto#if falsebelow to see a difference - the first one runs, the second one throwslambdageek commentedon Jul 24, 2023
Hmm... comparing the IL for the working and broken versions of
FirstOne, it looks like the working version hasbeforefieldinitand the broken one doesn't.lambdageek commentedon Jul 24, 2023
Ok, this isn't one of the cases where ECMA allows us some leeway because of circular dependencies. Rather, this is a case where the cctor must run "at or before static field access" and mono is leaning on the "or before" (specifically I think we run it when the instance constructor for
FirstOneis called)It looks like mono is calling a
beforefieldinitstatic constructor even if we never touch the static fields of that classIt looks like we're running the cctor for
FirstOnewhenSecondOnetries to instantiate it. But constructor invocation for abeforefieldinitclass is not sufficient. Only static field access (or explicit cctor invocation) should trigger it, I think.There is a variant where instead of calling the instance constructor, we call a static method from
FirstOne. In this case, too, CoreCLR doesn't call the cctor forFirstOneDetails
1 remaining item
lambdageek commentedon Jul 24, 2023
@vargaz @BrzVlad I see some calls to
mono_class_is_before_field_initin method-to-ir, so we're aware that sometimesbeforefieldinitmeans we shouldn't run the cctor. But it looks like we do it anyway. In the interpreter I don't see anything that looks atbeforefieldinit, so it's probably too eager, also.vargaz commentedon Jul 24, 2023
Probably a dup of:
#77513
lambdageek commentedon Jul 24, 2023
I think #77513 is not quite the same. In that one, the bad static field is used in the body of a called method - it's just not on an executed path. In this one, the bad static field is not used at all in any JITted method.
Fixing 77513 seems harder
LeVladIonescu commentedon Aug 14, 2023
Moving this to
Postponed to .Net9with high priority.