The following snippet of code, when compiled and run as a standalone program, will compile just fine but throw a runtime exception:
struct A
{
B<A> b;
static void Main(string[] args)
{
A a = new A();
}
}
struct B<T>
{
C<T> c;
}
class C<T>
{
T t;
}
Exception thrown:
Unhandled Exception: System.TypeLoadException: Could not load type 'A' from assembly 'dotnet, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
I have tested this with the newest public release of .NET Core I could find - 2.2.100-preview2-009404.
The same issue also occurs when building a standard .NET Framework project; I have tested targeting versions 4.5.2 and 4.7.2. Running the executable produced for .NET with mono (Mono JIT compiler version 5.14.0 (Visual Studio built mono)) works just fine - the program prints nothing and exits with code 0.
The same problem occurs if similar code is compiled into a DLL that's later loaded as a library. However, this is the minimum repro case I could come up with. Should I separately file a bug for .NET Framework? This page suggests I should do so, but points to a "retired" web page.
Your declaration is infinitely recursive. If every instance of struct A should contain B\ which contains C\ which contains A, then the struct would have to be infinitely big, that is obviously not possible (in this case it is otherwise empty, but empty structs in .net takes 1 byte anyways).
You could question whether Roslyn should detect this and fail with CS0523, but note that it can only do so if all the types are declared in the same assembly.
Sorry, I didn't notice C being a class.
C is a class, which breaks the infinite recursion. A more minimal example (apologies) which highlights the problem better and makes it more obvious that there is no infinite recursion going on is this:
struct A
{
B<A> b;
static void Main(string[] args)
{
A a = new A();
}
}
struct B<T>
{
}
The second example is dotnet/runtime#6924, but it's possible the first one is related too.
Thanks @MichalStrehovsky
That does look like the same issue. I feel like the title in https://github.com/dotnet/coreclr/issues/7957 is a bit too narrow. https://github.com/dotnet/coreclr/issues/7957#issuecomment-411838562 describes a similar example to my original. The issue seems to be a cyclic recursion in type parameters of structs causing issues even if there is no cyclic reference in the struct data layout itself.
I poked around the codebase, and believe the issue is the following:
When A gets type loaded, we need to calculate the size and alignment requirements of the struct, which involves calculating the alignment requirements and size of non-static fields. Because B<A> is a value type, this forces type loading B<A>, and is in general unavoidable. In order to type load a generic type, the classloader first type loads all the type arguments, and then uses them for type substitution to instantiate the generic. This forces A to be type loaded again while it's still being loaded, detects the cycle, and throws.
My understanding of the codebase is very pedestrian, but I thought of two possible fixes:
Would any of these options be feasible/acceptable? Do you think there are other, better options?
@gafter
Why does nobody seem particularly interested in addressing this very fundamental bug - the current inability of the C# compiler to correctly process legal source code? I would have thought that these kinds of bugs would be given a high priority since this is quite legal source code.
@Korporal It seems to me that this is a runtime issue, which means that the C# compiler is behaving correctly and that there is no point in appealing to members of the C# compiler team.
@Korporal I agree with you totally... this needs to get the appropriate priority. I believe it will. See also
@Korporal It seems to me that this is a runtime issue, which means that the C# compiler is behaving correctly and that there is no point in appealing to members of the C# compiler team.
@svick - Thank you for that explanation, but please understand I have no idea who is on which team, nor do I know if some prominent individual has or does not have influence over certain decisions. As you'll see Neil has participated in discussions in this forum; confining remarks to "team members" only is also not a rule that I've been asked to follow.
Finally I see no reason for you to presumptuously speak on behalf of others whom I've politely addressed in a post, it is for them to respond or not respond as they see fit, I'm sure Neil is quite capable of this.
Finally I'm sure it likely is a runtime issue as you point out but again I do not know the underlying cause nor do I have the deeper insights that others like your good self may have and so for all I know this _could_ be due to the runtime falling victim to incorrect compiler output - I simply do not know at the time I posted this.
Thank you.
@Korporal I apologize. I did not mean to tell you how you have to behave, I was just trying to explain what I thought was an effective way to behave. Turns out I was wrong.
@svick - That's OK, it easy to misconstrue someone's intentions here sometimes! NP.
@davidwrighton @RussKeldorph there seems to be bunch of duplicates - see https://github.com/dotnet/coreclr/issues/20220#issuecomment-488859255
It seems to hit quite a few customers now. Is it correctly triaged to Future instead of 3.0?
cc @jkotas
Is it correctly triaged to Future instead of 3.0?
Yes. This implementation limitation of CoreCLR Typeloader goes pretty deep. It is not easy to fix.
Thanks @jkotas! Would it make sense to clean up the duplicates and make one central issue with clarification of the limitations as you posted above?
This is duplicate of dotnet/runtime#6924
For the record, the repro from the top can be simplified to:
struct A
{
B<A> b;
static void Main(string[] args)
{
A a = new A();
}
}
struct B<T>
{
object c;
}
Most helpful comment
@gafter
Why does nobody seem particularly interested in addressing this very fundamental bug - the current inability of the C# compiler to correctly process legal source code? I would have thought that these kinds of bugs would be given a high priority since this is quite legal source code.