Skip to content

Unused generic type parameter should not cause loader failure #6924

Description

@gafter

Consider the following code:

static class Program
{
    static void Main()
    {
        var m = new M();
    }
}

struct N<T> { }
struct M { public N<M> E; }

This code will compile with C# 6.0 and is legal according to the CLI spec. The type definition is recursive but the layout of the struct is not because the field involved here is an empty struct. The CLR is unable to handle this though and fails at runtime with a TypeLoadException:

'M:Program.Main' failed: Could not load type 'M' from assembly 'ConsoleApplication1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
System.TypeLoadException: Could not load type 'M' from assembly 'ConsoleApplication1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
at Program.Main()

See also #5479

Activity

  1. removed their assignment
    on Oct 31, 2017
  2. MartinJohns commented on Apr 16, 2018

    @MartinJohns

    Stumbled over exactly this issue the last days. 🙁 Really thought it would work, and it would have been a nice complement to the new System.Memory<T> type.

  3. verelpode commented on Aug 9, 2018

    @verelpode

    because the field involved here is an empty struct.

    People might reply, "True but nobody needs an empty struct in practice", therefore here is an example that throws TypeLoadException with a non-empty struct. i.e. a more severe example of the failure.

    class Program
    {
    	static void Main(string[] args)
    	{
    		Test();
    	}
    	static SNode Test()
    	{
    		return new SNode();
    	}
    	struct ArrayElementReference<T>
    	{
    		public T[] Array;
    		public int Index;
    	}
    	struct SNode
    	{
    		ArrayElementReference<SNode> x;
    		int y;
    	}
    } 
  4. improbable-nickkrempel commented on Oct 2, 2018

    @improbable-nickkrempel

    Suggest changing the title from "Unused generic type parameter ..." to "Self-referencing generic type parameter ...", as it's not related to whether the type parameter is used or unused - see @verelpode's example above, and also https://github.com/dotnet/coreclr/issues/20220, which now seems to be a duplicate of this.

  5. The-Futurist commented on May 3, 2019

    @The-Futurist

    This issue becomes less academic and much more significant when one begins to leverage recently added support for generic pointers, the unmanaged generic constraint and ref.

    Here is a concrete and realistic example (which comes from actual code I've developed) dotnet/roslyn#35324.

  6. The-Futurist commented on Jun 1, 2019

    @The-Futurist

    Still no progress on this bug. I can only assume that a few users are raising this, because a truly fundamental failure to process completely legal generated code strikes me as something that would get a little more attention.

  7. GSPP commented on Jun 1, 2019

    @GSPP

    @Korporal the 3.0 release is getting closer and the team is becoming more selective in what issues to take on. That might be the reason. (I'm not a team member.)

  8. karelz commented on Jun 5, 2019

    @karelz
    Member

    Also, this issue was reported by 3 people so far. So while your realistic example may be bad experience (I didn't check it yet), the reality is not too many people hit it yet, which means lower priority. Hardly something as must-have for 3.0 (just my opinion).

  9. The-Futurist commented on Jun 5, 2019

    @The-Futurist

    It's been around for three and a half year though!

  10. gafter commented on Jun 6, 2019

    @gafter
    MemberAuthor

    the reality is not too many people hit it yet

    We've had to work around this issue multiple times in Roslyn. Did you need separate reports each time we hit this to realize that it is a persistent problem?

  11. The-Futurist commented on Jun 19, 2019

    @The-Futurist

    @karelz I understand this seems to be relativly small impact but it is very fundamental in nature, a very foundational capability. In my case I'm working on a C# rewrite of a custom memory allocator that sits on top of an application's unmanaged memory space. Recent advances to C# in areas like type constraints and the ref keyword have made this possible. However this bug is a solid roadblock because we have a generic struct type SmartPointer<T> which supports lists and trees that cannot be implemented because code compiles but fails at runtime.

    The struct type SmartPointer<T> when being used in a list, appears as a member field in a struct type T that can "point" to another instance of a T, this is where the recursive reference comes up. For reasons I can't go into here, we cannot use a conventional unsafe pointer.

    (Just to be clear these struct instances are not in managed memory but allocated from unmanaged area of a process's addess space using the custom allocator).

    Is there any possibility the bug could be given to a competent intern to see if they can do anything with it? I dont care about it being in 3.0, but currently its going nowhere and this could continue for years more if Microsoft continue to treat it as low importance.

    Leaving it unfixed for year after year could also make it harder to fix because the CLR may get refactored or restructured in ways that make the fix more difficult. This feature really is a basic expectation.

    Id love to look at it myself even but will not pretend I have the internals knowledge or experience to actually deliver.

  12. grandseiken commented on Jun 25, 2019

    @grandseiken

    FWIW, this is an issue we hit repeatedly, particularly in the context of generated C# code, where it is often not easy or feasible to work around (changing output of the code generator wholesale to avoid this problem would be API breaking or have unacceptable knock-on effects, detecting and avoiding on a case-by-case basis would lead to inconsistent or incompatible APIs in some cases).

  13. The-Futurist commented on Oct 21, 2019

    @The-Futurist

    @karelz
    I see there's no movement on this, is anyone in a position to say that this will or won't be addressed and if so when approx it might be fixed?

  14. karelz commented on Oct 22, 2019

    @karelz
    Member

    No idea - maybe @davidwrighton or @jkotas can comment?

    I still believe it is not serious enough to prioritize it. If you have a fix, I think we would be open to a contribution (if it is not overly complex, it is high-quality and not breaking).
    General rule is Future = maybe one day.

  15. 37 remaining items

  16. DaZombieKiller commented on Aug 9, 2022

    @DaZombieKiller
    Contributor

    @pengweiqhca In your example you're using a class, and the ref is pointing to one of its fields. So in this scenario, the ref is not pointing to unmanaged/native memory. As a result it's not safe to convert it to an unmanaged pointer because the GC can relocate it between the AsRef and AsPointer calls.

  17. pengweiqhca commented on Aug 9, 2022

    @pengweiqhca

    @DaZombieKiller Sorry, I forgot to modify it, but it has been fixed now. In my actual code I use struct.

  18. tokizr commented on Feb 8, 2023

    @tokizr

    Whether this issue will be resolved or not, the user experience is currently really bad. We just ran into this issue because a colleague wanted to keep a LUT inside a value type using ImmutableArray<T> (company coding rules say no mutable static members so T[] would be forbidden).

    All I get is a TypeLoadException with no hint as to what is going wrong. When it first showed up it was in the middle of a massive file that was affected by several different source generators and it was very hard to know what it was causing the problem.

    I even tried using dotnet-trace in hopes of getting a better idea, but it just crashed and produced a truncated trace.

    I eventually built runtime to debug it myself and found the exception thrown in clsload.cpp:3451

    if (PendingTypeLoadHolder::CheckForDeadLockOnCurrentThread(pLoadingEntry))
    {
        // Attempting recursive load
        ClassLoader::ThrowTypeLoadException(pTypeKey, IDS_CLASSLOAD_GENERAL);
    }

    So even here there is not indication as to what is actually going wrong.

    If the runtime will not support this then Roslyn should not allow the code to compile in the first place. Internally we should be able to add this case to our analyzers so people are not caught off guard in the future, but we shouldn't be having to to do that ourselves.

  19. sunkin351 commented on Feb 13, 2023

    @sunkin351

    I decided, seeing as it's been forever and likely gonna take forever more, that I'd put careful thought into an analyzer of my own design to help deal with this issue.

    https://github.com/sunkin351/GenericStructCyclesAnalyzer

    Using this would deal with any user experience issues revolving around this. The analyzer itself is incredibly simple, single file and catches every situation I could think of.

    I would like to propose adding it to the existing suite of .NET Analyzers, but I'm not sure it would stand up to their guidelines. Maybe some of you could check that for me.

  20. jarnmo commented on Mar 21, 2023

    @jarnmo

    I run into this again when using a nested struct of a generic class, not immediately realizing I was dealing with this issue. I guess this happens because a nested type of a generic type is essentially a generic type as well.

    So, the following code throws a TypeLoadException:

    var tmp = new Other();
    
    public class Container<T> {
        public struct Nested { }
    }
    
    public struct Other {
        public Container<Other>.Nested Nested;
    }
    
  21. added a commit that references this issue on Mar 27, 2023
  22. ghost added
    in-prThere is an active PR which will close this issue when it is merged
    on Mar 28, 2023
  23. added a commit that references this issue on Apr 4, 2023
    bc887b3
  24. ghost removed
    in-prThere is an active PR which will close this issue when it is merged
    on Apr 4, 2023
  25. modified the milestones: Future, 8.0.0 on Apr 4, 2023
  26. DaZombieKiller commented on Apr 5, 2023

    @DaZombieKiller
    Contributor

    Should this be reopened since there are still cases not covered by the fix, such as the one mentioned here?

    struct N<T> { }
    struct M { public N<Nullable<M>> E; }
  27. MichalStrehovsky commented on Apr 5, 2023

    @MichalStrehovsky
    Member

    Should this be reopened since there are still cases not covered by the fix, such as the one #83995 (comment)?

    That should probably be a new issue so that it has a new thumbs up counter with people who're blocked by this. We can wait until someone runs into it.

    The counter is used to prioritize the bug against any other type loader work - i.e. there's a finite number of people who can fix such bug and working on fixing the bug means other things will not be worked on. It's zero sum.

  28. ghost locked as resolved and limited conversation to collaborators on May 5, 2023
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

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions