[mono] When creating an array type, don't do full initialization of the element type...
authorAleksey Kliger (λgeek) <alklig@microsoft.com>
Mon, 22 May 2023 15:21:21 +0000 (11:21 -0400)
committerGitHub <noreply@github.com>
Mon, 22 May 2023 15:21:21 +0000 (11:21 -0400)
commitbdf5df39c4749dd88cf45de008f9f3c2d5905fba
tree9da32def7f044638e6ccdae1035d3f1274917947
parented33e6c1ebfe9ae78a2a01a11a01c575f5dad747
[mono] When creating an array type, don't do full initialization of the 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
src/mono/mono/metadata/class-init.c
src/tests/Loader/classloader/regressions/GitHub_85821/GitHub_85821.csproj [new file with mode: 0644]
src/tests/Loader/classloader/regressions/GitHub_85821/repro.cs [new file with mode: 0644]