runtime: Unused generic type parameter should not cause loader failure

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 dotnet/runtime#5479

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Reactions: 95
  • Comments: 50 (18 by maintainers)

Commits related to this issue

Most upvoted comments

To give an update on the demand to fix this one year after @davidwrighton’s last message, the number of 👍s has more than doubled from 24 to 60, making this issue by far the most upvoted open or closed bug of this repo, and three duplicate issues have been opened since that time.

Closing since there doesnt seem to be any particular reasons to be fix in the near term.

This codebase has buckets of unit tests, but also buckets of emergent behavior that are customer visible such as exception ordering, assembly load ordering etc, and lots of extremely obscure corner cases. Unfortunately, the code that this impacts (field layout) is some of the most sensitive code in the runtime to odd, subtle, and obscure failure modes which are difficult to test. (We have thousands of field layout tests, and each release we discover new ways our testing was inadequate. It is extremely frustrating.) Each release we look at this bug, and try to determine if its important enough to counterbalance the fairly likely destabilizing effect that a fix would imply, and the time available to do type system work, and we haven’t been able to justify the work.

However, we do look at the issue upvote count and as @karelz notes the upvote count is up to 24 which is fairly high for an issue in this repo which indicates there is real customer demand for us to fix this, so later this year we will look at scheduling this for the next release, and what the estimated cost will be. We may be able to look at this for .NET 7. We may not.

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?

Apparently the “full-fledged” CLR is the only runtime where it doesn’t work. Mono and experimental CoreRT have no issues with running this code.

I have encountered this as I attempted to “compress” a tree of nodes. In essence I have code like this:

/// <summary>
/// List of T expressed as a value type
/// </summary>
public struct ValueList<T>
{
    private T[] _arr;
    private int _count;
}

public struct MyNode
{
    public int NodeData;

    public ValueList<MyNode> Nodes;
}

I’m frustrated that this has yet to be fixed after literal years of it being a thing. Much longer and this issue alone will have been open for a decade…

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.

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.

Reading this discussion is pretty disheartening.

I ran into this bug working on a struct to provide easy traversal and stitching functionality for objects that have linked list style nextIndex fields embedded within them. I am attempting to represent parent-child-sibling-sibling-sibling… relationships between objects (structs in my current case) of the same type; which causes me to have a, for example, struct Linker<Entry> member inside of struct Entry (causing this bug to manifest). Linker<T> in this case carries a head index, a tail index, and a pointer to a function that extracts the ref next index from ref T (which is why it requires the generic parameter). Can I work around this bug? Certainly. Will the results be half as readable to whoever inherits this code? Unlikely.

It looks like this issue is specific to value types; I could convert my struct to a reference type and I’d imagine this problem goes away; but then what’s the point of packing my memory to avoid cache misses?

I understand that it may appear that this bug is not being triggered by many, however I’d argue that those who are triggering it are the type of users who are convincing their businesses to take chances on R&D efforts or unusual optimizations. The type of users I’d imagine you were targeting when you added Span<T>, new ref support, function pointers, etc. Users that build cool new stuff with your cool new stuff. I hope that you will increase the priority of fixing this bug specifically, and this class of bug in general. Please help us avoid unnecessary gotchas like this one. Please help us deliver our best with your tools.

@mangod9 Is it really a good approach to simply close issues regarding bugs, just because you don’t plan to fix them in the near time? Especially considering that even the Roslyn team had to workaround this bug several times. Personally I’m still waiting and hoping for this to be fixed, and having an open issue is a nice way to at least track it.

I understand that it can be discouraging to have eternally open bug issues… But simply closing them without any resolution just seems weird and the wrong way to handle things.

edit: One of my most frustrating situations in recent times is to look up a bug… Only to find an issue closed due to no activity, while the bug still remains to the present day. Depending on the project this happens fairly frequently. It saddens me to see .NET go the same way.

Can the exception message at least be changed to indicate the cause of the failure? Having to prune code bit-by-bit to find an obscure type-system limitation is a real time waster.

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

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.

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

@karelz - OK so you wrote the above around two years ago and there’s still absolutely no movement it seems on this. This just strikes me as odd, the system is failing to run code that compiles cleanly, we have valid, legal C# yet get assembly load failures - surely this can be given more attention???

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

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) https://github.com/dotnet/roslyn/issues/35324.

(sorry, closed by accident - hotkeys 😦)

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.

Should this be reopened since there are still cases not covered by the fix, such as the one https://github.com/dotnet/runtime/pull/83995#issuecomment-1492840010?

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.

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; }
The backtrace from 1765d037c80f241bf6b391ed84c20cd8deb3dd32 runtime's debug build looks like this.
$ lldb -- ~/projects/runtime_base/.dotnet2/dotnet bin/Debug/net6.0/teststruct.dll
(lldb) break set -E C++
(lldb) r
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x00007fff72b24f28 libc++abi.dylib`__cxa_throw
    frame #1: 0x00000001020d94aa libcoreclr.dylib`ThrowTypeLoadException(pFullTypeName=u"M", pAssemblyName=u"teststruct, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", pMessageArg=0x0000000000000000, resIDWhy=2148734242) at excep.cpp:11815:5
    frame #2: 0x000000010204844a libcoreclr.dylib`ClassLoader::ThrowTypeLoadException(pKey=0x00007ffeefbf5660, resIDWhy=2148734242) at clsload.cpp:1080:5
    frame #3: 0x000000010204ffca libcoreclr.dylib`ClassLoader::LoadTypeHandleForTypeKey_Body(this=0x0000000100219e20, pTypeKey=0x00007ffeefbf5660, typeHnd=TypeHandle @ 0x00007ffeefbf53c0, targetLevel=CLASS_LOAD_APPROXPARENTS) at clsload.cpp:3780:13
    frame #4: 0x0000000102048dcb libcoreclr.dylib`ClassLoader::LoadTypeHandleForTypeKey(this=0x0000000100219e20, pTypeKey=0x00007ffeefbf5660, typeHnd=TypeHandle @ 0x00007ffeefbf5540, targetLevel=CLASS_LOAD_APPROXPARENTS, pInstContext=0x0000000000000000) at clsload.cpp:3607:19
    frame #5: 0x000000010204b5d5 libcoreclr.dylib`ClassLoader::LoadTypeDefThrowing(pModule=0x00000001191429d0, typeDef=33554436, fNotFoundAction=ThrowIfNotFound, fUninstantiated=FailIfUninstDefOrRef, tokenNotToLoad=0, level=CLASS_LOAD_APPROXPARENTS, pTargetInstantiation=0x0000000000000000) at clsload.cpp:2501:54
    frame #6: 0x000000010204cb5d libcoreclr.dylib`ClassLoader::LoadTypeDefOrRefThrowing(pModule=0x00000001191429d0, typeDefOrRef=33554436, fNotFoundAction=ThrowIfNotFound, fUninstantiated=FailIfUninstDefOrRef, tokenNotToLoad=0, level=CLASS_LOAD_APPROXPARENTS) at clsload.cpp:2673:23
    frame #7: 0x00000001021d6d68 libcoreclr.dylib`SigPointer::GetTypeHandleThrowing(this=0x00007ffeefbf69f8, pModule=0x00000001191429d0, pTypeContext=0x00007ffeefbf6c08, fLoadTypes=LoadTypes, level=CLASS_LOAD_APPROXPARENTS, dropGenericArgumentLevel=YES, pSubst=0x0000000000000000, pZapSigContext=0x0000000000000000) const at siginfo.cpp:1475:17
    frame #8: 0x00000001021d68ce libcoreclr.dylib`SigPointer::GetTypeHandleThrowing(this=0x00007ffeefbf6c38, pModule=0x00000001191429d0, pTypeContext=0x00007ffeefbf6c08, fLoadTypes=LoadTypes, level=CLASS_LOAD_APPROXPARENTS, dropGenericArgumentLevel=YES, pSubst=0x0000000000000000, pZapSigContext=0x0000000000000000) const at siginfo.cpp:1387:36
    frame #9: 0x00000001021d96ea libcoreclr.dylib`SigPointer::PeekElemTypeNormalized(this=0x00007ffeefbf6c38, pModule=0x00000001191429d0, pTypeContext=0x00007ffeefbf6c08, pthValueType=0x0000000000000000) const at siginfo.cpp:2381:29
    frame #10: 0x0000000102042542 libcoreclr.dylib`MetaSig::NextArgNormalized(this=0x00007ffeefbf6c00, pthValueType=0x0000000000000000) at siginfo.hpp:835:45
    frame #11: 0x000000010203e4eb libcoreclr.dylib`(anonymous namespace)::DetermineBlittabilityAndManagedSequential(pInternalImport=0x0000000100809c10, phEnumField=0x00007ffeefbf9700, pModule=0x00000001191429d0, nativeTypeFlags=IsAnsi, pTypeContext=0x00007ffeefbf9650, fDisqualifyFromManagedSequential=NO, pFieldInfoArrayOut=0x00007ffeefbf7198, pIsBlittableOut=YES, cInstanceFields=0x00007ffeefbf706c, cTotalFields=1, szNamespace="", szName="M") at classlayoutinfo.cpp:493:51
    frame #12: 0x000000010203db3e libcoreclr.dylib`EEClassLayoutInfo::CollectLayoutFieldMetadataThrowing(cl=33554436, packingSize=' ', nlType='\x02', fExplicitOffsets=NO, pParentMT=0x0000000118e31238, cTotalFields=1, phEnumField=0x00007ffeefbf9700, pModule=0x00000001191429d0, pTypeContext=0x00007ffeefbf9650, pEEClassLayoutInfoOut=0x00000001191e2f60, pInfoArrayOut=0x00007ffeefbf7198, pAllocator=0x0000000102a095b8, pamTracker=0x00007ffeefbf9b10) at classlayoutinfo.cpp:641:5
    frame #13: 0x000000010238949c libcoreclr.dylib`ClassLoader::CreateTypeHandleForTypeDefThrowing(pModule=0x00000001191429d0, cl=33554436, inst=Instantiation @ 0x00007ffeefbf9a20, pamTracker=0x00007ffeefbf9b10) at methodtablebuilder.cpp:12163:17
    frame #14: 0x000000010204e104 libcoreclr.dylib`ClassLoader::CreateTypeHandleForTypeKey(pKey=0x00007ffeefbfa370, pamTracker=0x00007ffeefbf9b10) at clsload.cpp:3197:19
    frame #15: 0x000000010204de05 libcoreclr.dylib`ClassLoader::DoIncrementalLoad(pTypeKey=0x00007ffeefbfa370, typeHnd=TypeHandle @ 0x00007ffeefbf9c10, currentLevel=CLASS_LOAD_BEGIN) at clsload.cpp:3125:27
    frame #16: 0x00000001020508a0 libcoreclr.dylib`ClassLoader::LoadTypeHandleForTypeKey_Body(this=0x0000000100219e20, pTypeKey=0x00007ffeefbfa370, typeHnd=TypeHandle @ 0x00007ffeefbfa0d0, targetLevel=CLASS_LOAD_EXACTPARENTS) at clsload.cpp:3888:23
    frame #17: 0x0000000102048dcb libcoreclr.dylib`ClassLoader::LoadTypeHandleForTypeKey(this=0x0000000100219e20, pTypeKey=0x00007ffeefbfa370, typeHnd=TypeHandle @ 0x00007ffeefbfa250, targetLevel=CLASS_LOADED, pInstContext=0x0000000000000000) at clsload.cpp:3607:19
    frame #18: 0x000000010204b5d5 libcoreclr.dylib`ClassLoader::LoadTypeDefThrowing(pModule=0x00000001191429d0, typeDef=33554436, fNotFoundAction=ThrowIfNotFound, fUninstantiated=FailIfUninstDefOrRef, tokenNotToLoad=0, level=CLASS_LOADED, pTargetInstantiation=0x0000000000000000) at clsload.cpp:2501:54
    frame #19: 0x000000010204cb5d libcoreclr.dylib`ClassLoader::LoadTypeDefOrRefThrowing(pModule=0x00000001191429d0, typeDefOrRef=33554436, fNotFoundAction=ThrowIfNotFound, fUninstantiated=FailIfUninstDefOrRef, tokenNotToLoad=0, level=CLASS_LOADED) at clsload.cpp:2673:23
    frame #20: 0x00000001021d6d68 libcoreclr.dylib`SigPointer::GetTypeHandleThrowing(this=0x00007ffeefbfb020, pModule=0x00000001191429d0, pTypeContext=0x00007ffeefbfaff8, fLoadTypes=LoadTypes, level=CLASS_LOADED, dropGenericArgumentLevel=NO, pSubst=0x0000000000000000, pZapSigContext=0x0000000000000000) const at siginfo.cpp:1475:17
    frame #21: 0x000000010213fad4 libcoreclr.dylib`CEEInfo::getArgType(this=0x00007ffeefbfc480, sig=0x00007ffeefbfc368, args=0x0000000100179690, vcTypeRet=0x00007ffeefbfb138) at jitinterface.cpp:9641:27
    frame #22: 0x0000000106992ee8 libclrjit.dylib`Compiler::lvaInitTypeRef(this=0x00000001100b8028) at lclvars.cpp:277:31
    frame #23: 0x0000000106866070 libclrjit.dylib`Compiler::compCompileHelper(this=0x00000001100b8028, classPtr=0x00000001191429d0, compHnd=0x00007ffeefbfc480, methodInfo=0x00007ffeefbfc2d0, methodCodePtr=0x00007ffeefbfbc70, methodCodeSize=0x00007ffeefbfc1e4, compileFlags=0x00007ffeefbfbca8) at compiler.cpp:6219:5
    frame #24: 0x0000000106864e9c libclrjit.dylib`Compiler::compCompile(this=0x00007ffeefbfb400, __JITpParam=0x00007ffeefbfb410)::$_11::operator()(Compiler::compCompile(CORINFO_MODULE_STRUCT_*, void**, unsigned int*, JitFlags*)::__JITParam*) const at compiler.cpp:5645:28
    frame #25: 0x00000001068648e2 libclrjit.dylib`Compiler::compCompile(this=0x00000001100b8028, classPtr=0x00000001191429d0, methodCodePtr=0x00007ffeefbfbc70, methodCodeSize=0x00007ffeefbfc1e4, compileFlags=0x00007ffeefbfbca8) at compiler.cpp:5664:5
    frame #26: 0x000000010686f1f0 libclrjit.dylib`jitNativeCode(this=0x00007ffeefbfb570, __JITpParam=0x00007ffeefbfb580)::$_14::operator()(jitNativeCode(CORINFO_METHOD_STRUCT_*, CORINFO_MODULE_STRUCT_*, ICorJitInfo*, CORINFO_METHOD_INFO*, void**, unsigned int*, JitFlags*, void*)::__JITParam*) const::'lambda'(jitNativeCode(CORINFO_METHOD_STRUCT_*, CORINFO_MODULE_STRUCT_*, ICorJitInfo*, CORINFO_METHOD_INFO*, void**, unsigned int*, JitFlags*, void*)::$_14::operator()(jitNativeCode(CORINFO_METHOD_STRUCT_*, CORINFO_MODULE_STRUCT_*, ICorJitInfo*, CORINFO_METHOD_INFO*, void**, unsigned int*, JitFlags*, void*)::__JITParam*) const::__JITParam*)::operator()(jitNativeCode(CORINFO_METHOD_STRUCT_*, CORINFO_MODULE_STRUCT_*, ICorJitInfo*, CORINFO_METHOD_INFO*, void**, unsigned int*, JitFlags*, void*)::$_14::operator()(jitNativeCode(CORINFO_METHOD_STRUCT_*, CORINFO_MODULE_STRUCT_*, ICorJitInfo*, CORINFO_METHOD_INFO*, void**, unsigned int*, JitFlags*, void*)::__JITParam*) const::__JITParam*) const at compiler.cpp:7016:45
    frame #27: 0x0000000106868517 libclrjit.dylib`jitNativeCode(this=0x00007ffeefbfb658, __JITpParam=0x00007ffeefbfb668)::$_14::operator()(jitNativeCode(CORINFO_METHOD_STRUCT_*, CORINFO_MODULE_STRUCT_*, ICorJitInfo*, CORINFO_METHOD_INFO*, void**, unsigned int*, JitFlags*, void*)::__JITParam*) const at compiler.cpp:7041:9
    frame #28: 0x00000001068681e9 libclrjit.dylib`jitNativeCode(methodHnd=0x0000000119145278, classPtr=0x00000001191429d0, compHnd=0x00007ffeefbfc480, methodInfo=0x00007ffeefbfc2d0, methodCodePtr=0x00007ffeefbfbc70, methodCodeSize=0x00007ffeefbfc1e4, compileFlags=0x00007ffeefbfbca8, inlineInfoPtr=0x0000000000000000) at compiler.cpp:7043:5
    frame #29: 0x000000010687983c libclrjit.dylib`CILJit::compileMethod(this=0x0000000106d05020, compHnd=0x00007ffeefbfc480, methodInfo=0x00007ffeefbfc2d0, flags=4294967295, entryAddress=0x00007ffeefbfc1e8, nativeSizeOfCode=0x00007ffeefbfc1e4) at ee_il_dll.cpp:273:14
    frame #30: 0x000000010214c38a libcoreclr.dylib`invokeCompileMethodHelper(jitMgr=0x0000000103604850, comp=0x00007ffeefbfc480, info=0x00007ffeefbfc2d0, jitFlags=CORJIT_FLAGS @ 0x00007ffeefbfbdc8, nativeEntry=0x00007ffeefbfc1e8, nativeSizeOfCode=0x00007ffeefbfc1e4) at jitinterface.cpp:12476:30
    frame #31: 0x000000010214c62f libcoreclr.dylib`invokeCompileMethod(jitMgr=0x0000000103604850, comp=0x00007ffeefbfc480, info=0x00007ffeefbfc2d0, jitFlags=CORJIT_FLAGS @ 0x00007ffeefbfbe50, nativeEntry=0x00007ffeefbfc1e8, nativeSizeOfCode=0x00007ffeefbfc1e4) at jitinterface.cpp:12541:24
    frame #32: 0x000000010214c98f libcoreclr.dylib`CallCompileMethodWithSEHWrapper(this=0x00007ffeefbfbee0, pParam=0x00007ffeefbfbef0)::$_4::operator()(CallCompileMethodWithSEHWrapper(EEJitManager*, CEEInfo*, CORINFO_METHOD_INFO*, CORJIT_FLAGS, unsigned char**, unsigned int*, NativeCodeVersion)::Param*) const at jitinterface.cpp:12595:23
    frame #33: 0x000000010214c84b libcoreclr.dylib`CallCompileMethodWithSEHWrapper(jitMgr=0x0000000103604850, comp=0x00007ffeefbfc480, info=0x00007ffeefbfc2d0, flags=CORJIT_FLAGS @ 0x00007ffeefbfc1c0, nativeEntry=0x00007ffeefbfc1e8, nativeSizeOfCode=0x00007ffeefbfc1e4, nativeCodeVersion=NativeCodeVersion @ 0x00007ffeefbfc1b0) at jitinterface.cpp:12625:5
    frame #34: 0x000000010214e150 libcoreclr.dylib`UnsafeJitFunction(config=0x00007ffeefbfd738, ILHeader=0x00007ffeefbfd020, flags=CORJIT_FLAGS @ 0x00007ffeefbfcf88, pSizeOfCode=0x00007ffeefbfd2d4) at jitinterface.cpp:13117:19
    frame #35: 0x00000001021be953 libcoreclr.dylib`MethodDesc::JitCompileCodeLocked(this=0x0000000119145278, pConfig=0x00007ffeefbfd738, pEntry=0x000000010360ddd0, pSizeOfCode=0x00007ffeefbfd2d4, pFlags=0x00007ffeefbfd2c0) at prestub.cpp:1023:17
    frame #36: 0x00000001021be368 libcoreclr.dylib`MethodDesc::JitCompileCodeLockedEventWrapper(this=0x0000000119145278, pConfig=0x00007ffeefbfd738, pEntry=0x000000010360ddd0) at prestub.cpp:892:17
    frame #37: 0x00000001021bd074 libcoreclr.dylib`MethodDesc::JitCompileCode(this=0x0000000119145278, pConfig=0x00007ffeefbfd738) at prestub.cpp:832:20
    frame #38: 0x00000001021bc496 libcoreclr.dylib`MethodDesc::PrepareILBasedCode(this=0x0000000119145278, pConfig=0x00007ffeefbfd738) at prestub.cpp:437:17
    frame #39: 0x00000001021bc0c6 libcoreclr.dylib`MethodDesc::PrepareCode(this=0x0000000119145278, pConfig=0x00007ffeefbfd738) at prestub.cpp:336:19
    frame #40: 0x000000010205eba3 libcoreclr.dylib`CodeVersionManager::PublishVersionableCodeIfNecessary(this=0x0000000104005428, pMethodDesc=0x0000000119145278, callerGCMode=Coop, doBackpatchRef=0x00007ffeefbfd8d3, doFullBackpatchRef=0x00007ffeefbfd8d2) at codeversion.cpp:1701:34
    frame #41: 0x00000001021c2bd2 libcoreclr.dylib`MethodDesc::DoPrestub(this=0x0000000119145278, pDispatchingMT=0x0000000000000000, callerGCMode=Coop) at prestub.cpp:2170:42
    frame #42: 0x00000001021c1f73 libcoreclr.dylib`::PreStubWorker(pTransitionBlock=0x00007ffeefbfdc48, pMD=0x0000000119145278) at prestub.cpp:1997:25
    frame #43: 0x00000001025c9f03 libcoreclr.dylib`ThePreStub at theprestubamd64.S:16
    frame #44: 0x00000001025c9149 libcoreclr.dylib`CallDescrWorkerInternal at calldescrworkeramd64.S:97
    frame #45: 0x000000010227e58f libcoreclr.dylib`CallDescrWorkerWithHandler(pCallDescrData=0x00007ffeefbfded8, fCriticalCall=NO) at callhelpers.cpp:71:5
    frame #46: 0x000000010227f2a7 libcoreclr.dylib`MethodDescCallSite::CallTargetWorker(this=0x00007ffeefbfe130, pArguments=0x00007ffeefbfe0c0, pReturnValue=0x0000000000000000, cbReturnValue=0) at callhelpers.cpp:548:9
    frame #47: 0x0000000101fb9d33 libcoreclr.dylib`MethodDescCallSite::Call(this=0x00007ffeefbfe130, pArguments=0x00007ffeefbfe0c0) at callhelpers.h:458:9
    frame #48: 0x0000000101fe9f03 libcoreclr.dylib`RunMainInternal(pParam=0x00007ffeefbfe360) at assembly.cpp:1464:21
    frame #49: 0x0000000101fe9c49 libcoreclr.dylib`RunMain(this=0x00007ffeefbfe288, pParam=0x00007ffeefbfe360)::$_1::operator()(Param*) const::'lambda'(Param*)::operator()(Param*) const at assembly.cpp:1536:9
    frame #50: 0x0000000101fe44dc libcoreclr.dylib`RunMain(this=0x00007ffeefbfe350, __EXparam=0x00007ffeefbfe360)::$_1::operator()(Param*) const at assembly.cpp:1538:5
    frame #51: 0x0000000101fe42b0 libcoreclr.dylib`RunMain(pFD=0x0000000119145278, numSkipArgs=1, piRetVal=0x00007ffeefbfe47c, stringArgs=0x00007ffeefbfe930) at assembly.cpp:1538:5
    frame #52: 0x0000000101fe47d3 libcoreclr.dylib`Assembly::ExecuteMainMethod(this=0x0000000100219a90, stringArgs=0x00007ffeefbfe930, waitForOtherThreads=YES) at assembly.cpp:1648:18
    frame #53: 0x0000000102080ff0 libcoreclr.dylib`CorHost2::ExecuteAssembly(this=0x00000001035000a0, dwAppDomainId=1, pwzAssemblyPath=u"/Users/am11/projects/teststruct/bin/Debug/net6.0/teststruct.dll", argc=0, argv=0x0000000000000000, pReturnValue=0x00007ffeefbfebe4) at corhost.cpp:384:39
    frame #54: 0x0000000101fa55ca libcoreclr.dylib`::coreclr_execute_assembly(hostHandle=0x00000001035000a0, domainId=1, argc=0, argv=0x0000000000000000, managedAssemblyPath="/Users/am11/projects/teststruct/bin/Debug/net6.0/teststruct.dll", exitCode=0x00007ffeefbfebe4) at unixinterface.cpp:446:24
    frame #55: 0x0000000101b55ca4 libhostpolicy.dylib`coreclr_t::execute_assembly(this=0x0000000100217880, argc=0, argv=0x0000000000000000, managed_assembly_path="/Users/am11/projects/teststruct/bin/Debug/net6.0/teststruct.dll", exit_code=0x00007ffeefbfebe4) at coreclr.cpp:89:12
    frame #56: 0x0000000101b85e42 libhostpolicy.dylib`run_app_for_context(context=0x0000000100401490, argc=0, argv=0x00007ffeefbff5e8) at hostpolicy.cpp:249:32
    frame #57: 0x0000000101b862c0 libhostpolicy.dylib`run_app(argc=0, argv=0x00007ffeefbff5e8) at hostpolicy.cpp:284:12
    frame #58: 0x0000000101b87102 libhostpolicy.dylib`::corehost_main(argc=2, argv=0x00007ffeefbff5d8) at hostpolicy.cpp:430:12
    frame #59: 0x000000010182e25e libhostfxr.dylib`execute_app(impl_dll_dir="/Users/am11/projects/runtime_base/.dotnet2/shared/Microsoft.NETCore.App/6.0.0-dev", init=0x00000001005001e0, argc=2, argv=0x00007ffeefbff5d8) at fx_muxer.cpp:146:20
    frame #60: 0x000000010182226a libhostfxr.dylib`(anonymous namespace)::read_config_and_execute(host_command="", host_info=0x00007ffeefbff2e0, app_candidate="/Users/am11/projects/teststruct/bin/Debug/net6.0/teststruct.dll", opts=size=0, new_argc=2, new_argv=0x00007ffeefbff5d8, mode=muxer, out_buffer=0x0000000000000000, buffer_size=0, required_buffer_size=0x0000000000000000) at fx_muxer.cpp:520:18
    frame #61: 0x000000010181ed46 libhostfxr.dylib`fx_muxer_t::handle_exec_host_command(host_command="", host_info=0x00007ffeefbff2e0, app_candidate="/Users/am11/projects/teststruct/bin/Debug/net6.0/teststruct.dll", opts=size=0, argc=2, argv=0x00007ffeefbff5d8, argoff=1, mode=muxer, result_buffer=0x0000000000000000, buffer_size=0, required_buffer_size=0x0000000000000000) at fx_muxer.cpp:1001:12
    frame #62: 0x000000010181dff2 libhostfxr.dylib`fx_muxer_t::execute(host_command="", argc=2, argv=0x00007ffeefbff5d8, host_info=0x00007ffeefbff2e0, result_buffer=0x0000000000000000, buffer_size=0, required_buffer_size=0x0000000000000000) at fx_muxer.cpp:566:18
    frame #63: 0x0000000101818105 libhostfxr.dylib`::hostfxr_main_startupinfo(argc=2, argv=0x00007ffeefbff5d8, host_path="/Users/am11/projects/runtime_base/.dotnet2/dotnet", dotnet_root="/Users/am11/projects/runtime_base/.dotnet2/", app_path="/Users/am11/projects/runtime_base/.dotnet2/dotnet.dll") at hostfxr.cpp:60:12
    frame #64: 0x00000001000156ea dotnet`exe_start(argc=2, argv=0x00007ffeefbff5d8) at corehost.cpp:236:18
    frame #65: 0x0000000100015b85 dotnet`main(argc=2, argv=0x00007ffeefbff5d8) at corehost.cpp:302:21
    frame #66: 0x00007fff758a63d5 libdyld.dylib`start + 1
    frame #67: 0x00007fff758a63d5 libdyld.dylib`start + 1

The exception is thrown from https://github.com/dotnet/runtime/blob/69e114c1abf91241a0eeecf1ecceab4711b8aa62/src/coreclr/vm/clsload.cpp#L3777-L3781

I just bumped into this issue. My workaround was to create an array.

 public struct t1 {
       // uncommenting this will cause a type load exception
       // public static t2<t1> broken = new t2<t1>();
        public static t2<t1> t => workaround[0];
        public static t2<t1>[] workaround  = new t2<t1>[]{ new t2<t1>()};
    }
    public struct t2<T>
    {

    }
    

public class Program
{  
	public static void Main()
	{
		var i = new t1();
	}
}

This has been a known issue in the CLR typesystem since generics were added many years ago. The general problem is that the field type of fields is computed at type load time during initial creation of the type data structures, and that would have to change as part of fixing this issue. In general, it is believed that changes to logic in this system are somewhat high in risk, and without a clear justification for risky work, we can’t justify the work. There is a related similar issue with static fields, where some ECMA permitted static fields are not loadable by the runtime.

To the point that @Korporal was making that leaving this issue around will make it harder to fix in the future, unfortunately that isn’t really the case. The reasons for this being difficult in the CLR have been part of the type loader design since the late 90’s when the runtime was first implemented before generics were even designed in the first place, and so I don’t believe we are in significant risk of making it a more difficult fix.

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.

@pengweiqhca That code contains a GC hole and isn’t safe to use. Unsafe.AsPointer will cause the GC to lose track of the reference, leading to memory corruption. This is how you can avoid the GC hole:

public ref StructList<Node> Children => ref Unsafe.As<StructList, StructList<Node>>(ref _children);

The general rule is that you should almost never convert a managed ref to an unmanaged pointer without fixed, unless the ref is referring to unmanaged or already-pinned memory.

I resolved.

var node = (Node*)Marshal.AllocHGlobal(sizeof(Node)).ToPointer();

public unsafe struct Node
{
    private StructList _children;

    public ref StructList<Node> Children => ref Unsafe.AsRef<StructList<Node>>(Unsafe.AsPointer(ref _children));

    [StructLayout(LayoutKind.Sequential)]
    private struct StructList
    {
        private IntPtr _array;
        private int _size;
        private int _length;
    }
}

[StructLayout(LayoutKind.Sequential)]
public struct StructList<T>
{
    private IntPtr _array;
    private int _size;
    private int _length;
}

Just encountered this issue trying to specifically avoid recursion by boxing the reference 😅

    public struct Box<T> where T : struct {
        private object _boxedValue;

        public T Value { get => (T)_boxedValue; set => _boxedValue = value; }
    }

    public struct BoundingVolume
    {
        // other stuff...

        public Box<BoundingVolume> Father { get; set; }
    }

(Btw, I know there are other ways to do this… Just wanted to point out that I too was a bit puzzled when I encountered the issue)

Just encountered this with ReadOnlyMemory<Self>. Very odd that Self[] is allowed but (ReadOnly)Memory<Self> breaks.

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

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

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;
	}
}