Skip to content

System.TypeLoadException thrown by the Runtime when using structs and generics #11179

Description

@improbablejan

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.

Activity

  1. poizan42 commented on Oct 2, 2018

    @poizan42
    Contributor

    Your declaration is infinitely recursive. If every instance of struct A should contain B<A> which contains C<A> 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.

  2. improbablejan commented on Oct 2, 2018

    @improbablejan
    Author

    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>
    {
    }
  3. MichalStrehovsky commented on Oct 2, 2018

    @MichalStrehovsky
    Member

    The second example is #6924, but it's possible the first one is related too.

  4. improbablejan commented on Oct 2, 2018

    @improbablejan
    Author

    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.

  5. improbablejan commented on Oct 16, 2018

    @improbablejan
    Author

    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:

    • Change the resolution of generic type parameters to be calculated lazily as needed. This sounds like a big change with knock-on effects, and so probably isn't feasible.
    • Separate size and alignment calculations, use lazy type parameter evaluation there.

    Would any of these options be feasible/acceptable? Do you think there are other, better options?

  6. The-Futurist commented on May 2, 2019

    @The-Futurist

    @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.

  7. svick commented on May 2, 2019

    @svick
    Member

    @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.

  8. gafter commented on May 2, 2019

    @gafter
    Member

    @Korporal I agree with you totally... this needs to get the appropriate priority. I believe it will. See also

  9. The-Futurist commented on May 3, 2019

    @The-Futurist

    @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.

  10. svick commented on May 3, 2019

    @svick
    Member

    @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.

  11. The-Futurist commented on May 3, 2019

    @The-Futurist

    @svick - That's OK, it easy to misconstrue someone's intentions here sometimes! NP.

  12. karelz commented on Jun 5, 2019

    @karelz
    Member

    @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

  13. jkotas commented on Jun 5, 2019

    @jkotas
    Member

    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.

  14. karelz commented on Jun 5, 2019

    @karelz
    Member

    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?

  15. jkotas commented on Jun 5, 2019

    @jkotas
    Member

    This is duplicate of #6924

  16. jkotas commented on Jun 5, 2019

    @jkotas
    Member

    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;
    }
    
  17. transferred this issue fromdotnet/coreclron Jan 31, 2020
  18. added this to the Future milestone on Jan 31, 2020
  19. ghost locked as resolved and limited conversation to collaborators on Dec 15, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions