-
Notifications
You must be signed in to change notification settings - Fork 4.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Mono incorrectly throws a TypeLoadException for a recursive type definition #85821
Comments
Tagging subscribers to this area: Issue DetailsDescriptionGiven the following code: var test = new Node<int>();
Console.WriteLine(test);
public class Node<T>
{
public Node<T>[][] Children;
public T Data;
} Mono will throw a
CoreCLR does not throw a similar exception. Reproduction Steps
(Note that I'm using macOS x64, but the behavior is not specific to platform or architecture.)
Expected behaviorThe output is:
and no exception occurs. Actual behaviorMono will throw a
Regression?This has not worked in Mono. I also see the same behavior with Mono 6.12.0 from the mono/mono repository. Known WorkaroundsI don't have any known worarounds. ConfigurationI'm using .NET version 7.0.202 on macOS x64. Other informationThis bug was originally reported to Unity here: https://issuetracker.unity3d.com/issues/typeloadexception-is-thrown-when-using-recursive-types-in-monobehaviour-inherited-classes
|
/cc @lambdageek @vargaz |
We've fixed a number of cases similar to this one. In principle this ought to work: it's not a struct, the generic parameter doesn't get used in a parent class or an interface, etc. I'll take an initial look |
Repros with .NET8 previews. It's not because of the toplevel statements, AFAICT. Same problem if I make a normal class with a Main. It does seem to be related to I think probably the constructor for arrays is too aggressive in initializing the element type. if we know it's a reference type, we shouldn't need to expand it too much. But on the other hand, initializing Maybe it's something about array interface methods. I'll have to try a runtime with tracing enabled. |
we're probably hitting something added in 2005. If the code is overly-pessimistic, it's an easy fix. If it turns out the initialization is necessary, we are probably looking at a very hard bug (probably will need loader levels ala mono/mono#7856) runtime/src/mono/mono/metadata/class-init.c Lines 1186 to 1189 in 82640bd
I think we don't need the generic instance to be fully initialized at this point. so the first The second It's worth comparing how pointer types work. In a pointer |
I wanted to follow up on this. @lambdageek: Did the draft PR provide a viable solution? |
I haven't completely convinced myself that removing those 2 lines is correct. But it didn't crash on any existing tests. My plan is to try to find some time this week to step through the whole initialization in the debugger for |
@lambdageek: Did you have a chance to step through the initialization code in the debugger? |
@joshpeterson hey, sorry it took a while. yea I'm now pretty confident I understand what the difference was between |
oops. Edited the previous comment. I meant to say "I'm now pretty confident". The PR is good. |
Thank you! I'll watch that PR and sync it back to our fork of Mono when it lands. |
…he element type (#85828) Consider code like this: ```csharp public class Node<T> { public Node<T>[][] Children; } Console.WriteLine (typeof(Node<int>)); ``` In this case, when we're JITing ``ldtoken class Node`1<int32>`` we first have to parse the type ``Node`1<int32>`` and initialize it. When we're initializing it, we need to resolve the types of all the fields in the class in order to to figure out if its instance size. To resolve the field types we have to parse the types of all the fields, and we eventually end up parsing the type ``Node`1<!0>[][]`` which ends up here: https://github.com/dotnet/runtime/blob/558345d16cf76525d0c7fdbafbfd3a2457142b39/src/mono/mono/metadata/metadata.c#L4023-L4027 When we get to line 4027 the second time (recursively), `etype` is ``Node`1<!0>`` as a `MonoType*`. And we want to set `type->data.klass` to its corresponding `MonoClass*`. That ends up calling `mono_class_from_mono_type_internal`, which does this: https://github.com/dotnet/runtime/blob/558345d16cf76525d0c7fdbafbfd3a2457142b39/src/mono/mono/metadata/class.c#L2214-L2215 When we call `mono_class_create_array` we end up here: https://github.com/dotnet/runtime/blob/558345d16cf76525d0c7fdbafbfd3a2457142b39/src/mono/mono/metadata/class-init.c#L1186-L1187 with `eclass` equal to ``Node`1<!0>` which we try to initialize and we get a TLE because we're going to try to initialize the same generic type definition ``Node`1`` twice. Compare with this other class: ```csharp public unsafe class Node2<T> { public Node2<T>[] Children; } ``` In this case we only end up calling `mono_class_from_mono_type_internal` on ``Node2`1<int32>`` (not an array). And that branch does not do any initialization - it just returns `type->data.klass` (for ``Node2`1<!0>`` - which is seen as a gtd at this point) or `mono_class_create_generic_inst (type->data.generic_class)` (for ``Node2`1<int32>`` which is seen later when `ldtoken` inflates the gtd)) - without any extra initialization. --- It seems the reason we get into trouble is because `mono_class_create_array` does more work than other `MonoClass` creation functions - it tries to initialize the `MonoClass` a little bit (for example by setting `MonoClass:has_references`) which needs at least a little bit of the element class to be initialized. But note that the code has this: https://github.com/dotnet/runtime/blob/558345d16cf76525d0c7fdbafbfd3a2457142b39/src/mono/mono/metadata/class-init.c#L1186-L1189 I feel fairly certain we don't need the full class initialization if we're going to immediately initialize the field size information. Fixes #85821
Description
Given the following code:
Mono will throw a
TypeLoadException
exception, which is not expected:CoreCLR does not throw a similar exception.
Reproduction Steps
(Note that I'm using macOS x64, but the behavior is not specific to platform or architecture.)
Expected behavior
The output is:
and no exception occurs.
Actual behavior
Mono will throw a
TypeLoadException
exception, which is not expected:Regression?
This has not worked in Mono. I also see the same behavior with Mono 6.12.0 from the mono/mono repository.
Known Workarounds
I don't have any known worarounds.
Configuration
I'm using .NET version 7.0.202 on macOS x64.
Other information
This bug was originally reported to Unity here: https://issuetracker.unity3d.com/issues/typeloadexception-is-thrown-when-using-recursive-types-in-monobehaviour-inherited-classes
The text was updated successfully, but these errors were encountered: