Skip to content

Fixes for TypeRef of generic types #3182

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 46 additions & 22 deletions src/CLR/Core/Interpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2157,41 +2157,65 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg)
}
else // Non delegate
{

CLR_RT_MethodDef_Index calleeReal;

if ((calleeInst.target->flags & CLR_RECORD_METHODDEF::MD_Static) == 0)
bool doInstanceResolution = true;

if (calleeInst.target->flags & CLR_RECORD_METHODDEF::MD_Static)
{
doInstanceResolution = false;
}
else
{
// Instance method, pThis[ 0 ] is valid
CLR_RT_TypeDef_Instance declType;
NANOCLR_CHECK_HRESULT(calleeInst.GetDeclaringType(declType));

if (op == CEE_CALL && pThis[0].Dereference() == nullptr)
if (declType.target->dataType == DATATYPE_VALUETYPE)
{
// CALL on a null instance is allowed, and should not throw a NullReferenceException on
// the call although a NullReferenceException is likely to be thrown soon thereafter if
// the call tries to access any member variables.
// any method on a value‐type is called via byref, so skip the object‐dereference path
// altogether
doInstanceResolution = false;
}
else
}

if (doInstanceResolution)
{
if ((calleeInst.target->flags & CLR_RECORD_METHODDEF::MD_Static) == 0)
{
NANOCLR_CHECK_HRESULT(CLR_RT_TypeDescriptor::ExtractTypeIndexFromObject(pThis[0], cls));

// This test is for performance reasons. c# emits a callvirt on all instance methods to
// make sure that a NullReferenceException is thrown if 'this' is nullptr. However, if
// the instance method isn't virtual we don't need to do the more expensive virtual
// method lookup.
if (op == CEE_CALLVIRT &&
(calleeInst.target->flags &
(CLR_RECORD_METHODDEF::MD_Abstract | CLR_RECORD_METHODDEF::MD_Virtual)))
// Instance method, pThis[ 0 ] is valid

if (op == CEE_CALL && pThis[0].Dereference() == nullptr)
{
if (g_CLR_RT_EventCache.FindVirtualMethod(cls, calleeInst, calleeReal) == false)
// CALL on a null instance is allowed, and should not throw a NullReferenceException
// on the call although a NullReferenceException is likely to be thrown soon
// thereafter if the call tries to access any member variables.
}
else
{
NANOCLR_CHECK_HRESULT(
CLR_RT_TypeDescriptor::ExtractTypeIndexFromObject(pThis[0], cls));

// This test is for performance reasons. c# emits a callvirt on all instance
// methods to make sure that a NullReferenceException is thrown if 'this' is
// nullptr. However, if the instance method isn't virtual we don't need to do the
// more expensive virtual method lookup.
if (op == CEE_CALLVIRT &&
(calleeInst.target->flags &
(CLR_RECORD_METHODDEF::MD_Abstract | CLR_RECORD_METHODDEF::MD_Virtual)))
{
NANOCLR_SET_AND_LEAVE(CLR_E_WRONG_TYPE);
}
if (g_CLR_RT_EventCache.FindVirtualMethod(cls, calleeInst, calleeReal) == false)
{
NANOCLR_SET_AND_LEAVE(CLR_E_WRONG_TYPE);
}

calleeInst.InitializeFromIndex(calleeReal);
}
calleeInst.InitializeFromIndex(calleeReal);
}

#if defined(NANOCLR_APPDOMAINS)
fAppDomainTransition = pThis[0].IsTransparentProxy();
fAppDomainTransition = pThis[0].IsTransparentProxy();
#endif
}
}
}
}
Expand Down
213 changes: 189 additions & 24 deletions src/CLR/Core/TypeSystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,8 @@ HRESULT CLR_RT_SignatureParser::Advance(Element &res)
// sanity check
if (dt == DATATYPE_CLASS || dt == DATATYPE_VALUETYPE)
{
res.DataType = dt;

// parse type
goto parse_type;
}
Expand Down Expand Up @@ -1258,6 +1260,43 @@ bool CLR_RT_MethodDef_Instance::InitializeFromIndex(const CLR_RT_MethodDef_Index
return false;
}

bool CLR_RT_MethodDef_Instance::InitializeFromIndex(
const CLR_RT_MethodDef_Index &md,
const CLR_RT_TypeSpec_Index &typeSpec)
{
NATIVE_PROFILE_CLR_CORE();

// Look up the TypeSpec's cross‐reference to find the *open* generic definition and its assembly
CLR_INDEX tsAsmIdx = typeSpec.Assembly();
if (tsAsmIdx == 0 || tsAsmIdx > (int)g_CLR_RT_TypeSystem.c_MaxAssemblies)
{
return false;
}
CLR_RT_Assembly *tsAsm = g_CLR_RT_TypeSystem.m_assemblies[tsAsmIdx - 1];

int tsRow = (int)typeSpec.TypeSpec();
const auto &xref = tsAsm->crossReferenceTypeSpec[tsRow];

// xref.ownerType is the open‐generic TypeDef_Index of Span`1, List`1, etc.
CLR_RT_TypeDef_Index ownerType = xref.ownerType;

// Compute the MethodDef_Index bound to the owner’s assembly
CLR_RT_MethodDef_Index mdRebound;

// pack the new assembly index (ownerType.Assembly()) with the original row
mdRebound.data = (ownerType.Assembly() << 24) | md.Method();

if (!InitializeFromIndex(mdRebound))
{
return false;
}

// set the generic‐type context *before* we lose it
this->genericType = &typeSpec;

return true;
}

void CLR_RT_MethodDef_Instance::ClearInstance()
{
NATIVE_PROFILE_CLR_CORE();
Expand Down Expand Up @@ -1344,16 +1383,26 @@ bool CLR_RT_MethodDef_Instance::ResolveToken(
const CLR_RT_TypeSpec_Index *definitiveTypeSpec = useCaller ? callerGeneric : methodOwnerTS;
genericType = (CLR_RT_TypeSpec_Index *)definitiveTypeSpec;

const CLR_RECORD_TYPESPEC *ts = assm->GetTypeSpec(definitiveTypeSpec->TypeSpec());
CLR_INDEX tsAsmIdx = genericType->Assembly();
CLR_RT_Assembly *genericTypeAssembly = g_CLR_RT_TypeSystem.m_assemblies[tsAsmIdx - 1];

const CLR_RECORD_TYPESPEC *ts = genericTypeAssembly->GetTypeSpec(definitiveTypeSpec->TypeSpec());
CLR_UINT32 assemblyIndex = 0xFFFF;
CLR_RT_MethodDef_Index methodIndex;

if (!assm->FindMethodDef(ts, assm->GetString(mr->name), assm, mr->signature, methodIndex))
if (!genericTypeAssembly->FindMethodDef(
ts,
genericTypeAssembly->GetString(mr->name),
genericTypeAssembly,
mr->signature,
methodIndex,
assemblyIndex))
{
return false;
}

Set(assm->assemblyIndex, methodIndex.Method());
assembly = assm;
Set(assemblyIndex - 1, methodIndex.Method());
assembly = g_CLR_RT_TypeSystem.m_assemblies[assemblyIndex - 1];
target = assembly->GetMethodDef(methodIndex.Method());
}
else
Expand Down Expand Up @@ -1484,6 +1533,32 @@ bool CLR_RT_MethodDef_Instance::ResolveToken(
return false;
}

bool CLR_RT_MethodDef_Instance::GetDeclaringType(CLR_RT_TypeDef_Instance &declType) const
{
NATIVE_PROFILE_CLR_CORE();

if (genericType && NANOCLR_INDEX_IS_VALID(*genericType))
{
// Look up the assembly that actually owns that TypeSpec
auto tsAsm = g_CLR_RT_TypeSystem.m_assemblies[genericType->Assembly() - 1];
auto tsRec = tsAsm->GetTypeSpec(genericType->TypeSpec());

// Parse the signature so we can peel off [GENERICINST CLASS <type> <args>].
CLR_RT_SignatureParser parser;
parser.Initialize_TypeSpec(tsAsm, tsRec);

CLR_RT_SignatureParser::Element elem;
parser.Advance(elem);

return declType.InitializeFromIndex(elem.Class);
}
else
{
// Normal (non‐generic or open‐generic)
return declType.InitializeFromMethod(*this);
}
}

//////////////////////////////

bool CLR_RT_GenericParam_Instance::InitializeFromIndex(const CLR_RT_GenericParam_Index &index)
Expand Down Expand Up @@ -3203,8 +3278,15 @@ HRESULT CLR_RT_Assembly::ResolveMethodRef()
#endif
}

if (typeSpecInstance.assembly
->FindMethodDef(typeSpecInstance.target, methodName, this, src->signature, dst->target))
CLR_UINT32 dummyAssemblyIndex = 0xffff;

if (typeSpecInstance.assembly->FindMethodDef(
typeSpecInstance.target,
methodName,
this,
src->signature,
dst->target,
dummyAssemblyIndex))
{
fGot = true;

Expand Down Expand Up @@ -3376,6 +3458,27 @@ HRESULT CLR_RT_Assembly::ResolveTypeSpec()
CLR_Debug::Printf(" [%04X]\r\n", i);
}
#endif

// parse the signature to find the defining type token
CLR_RT_SignatureParser parser;
parser.Initialize_TypeSpec(this, GetTypeSpec(i));

CLR_RT_SignatureParser::Element elem;

parser.Advance(elem);

if (elem.DataType == DATATYPE_VALUETYPE)
{
// now `elem.Class` is exactly the open Span`1 or List`1
// store the open‐generic’s token
dst->ownerType = elem.Class;
}
else
{
// if the first element is not a generic instantiation, then it must be a type token
// no open‐generic, so clear the token
dst->ownerType.Clear();
}
}

NANOCLR_NOCLEANUP();
Expand Down Expand Up @@ -4878,20 +4981,72 @@ bool CLR_RT_Assembly::FindMethodDef(
const char *methodName,
CLR_RT_Assembly *base,
CLR_SIG sig,
CLR_RT_MethodDef_Index &index)
CLR_RT_MethodDef_Index &index,
CLR_UINT32 &assmIndex)
{
NATIVE_PROFILE_CLR_CORE();

CLR_RT_TypeSpec_Index tsIndex;
base->FindTypeSpec(base->GetSignature(ts->signature), tsIndex);
if (!base->FindTypeSpec(base->GetSignature(ts->signature), tsIndex))
{
index.Clear();
return false;
}

CLR_RT_TypeSpec_Instance tsInstance;
tsInstance.InitializeFromIndex(tsIndex);
if (!tsInstance.InitializeFromIndex(tsIndex))
{
index.Clear();
return false;
}

// switch to the assembly that declared this TypeSpec
CLR_RT_Assembly *declAssm = tsInstance.assembly;
const CLR_RECORD_TYPEDEF *td =
(const CLR_RECORD_TYPEDEF *)declAssm->GetTable(TBL_TypeDef) + tsInstance.typeDefIndex;

if (declAssm->FindMethodDef(td, methodName, base, sig, index))
{
assmIndex = declAssm->assemblyIndex;
return true;
}

// parse the TypeSpec signature to get the *definition* token of the generic type:
CLR_RT_SignatureParser parser{};
parser.Initialize_TypeSpec(this, ts);
CLR_RT_SignatureParser::Element elem{};

// advance to get the actual CLASS/VALUETYPE token for the generic definition.
if (FAILED(parser.Advance(elem)))
{
return false;
}

auto *td = (const CLR_RECORD_TYPEDEF *)base->GetTable(TBL_TypeDef);
td += tsInstance.typeDefIndex;
CLR_UINT32 genericDefToken = elem.Class.data;

return CLR_RT_Assembly::FindMethodDef(td, methodName, base, sig, index);
CLR_RT_TypeDef_Index typeDef;
typeDef.Clear();
typeDef.data = genericDefToken;

CLR_RT_TypeDef_Instance typeDefInstance;
if (typeDefInstance.InitializeFromIndex(typeDef) == false)
{
return false;
}

while (NANOCLR_INDEX_IS_VALID(typeDefInstance))
{
if (typeDefInstance.assembly->FindMethodDef(typeDefInstance.target, methodName, this, sig, index))
{
assmIndex = typeDefInstance.assembly->assemblyIndex;

return true;
}

typeDefInstance.SwitchToParent();
}

return false;
}

bool CLR_RT_Assembly::FindTypeSpec(CLR_PMETADATA sig, CLR_RT_TypeSpec_Index &index)
Expand Down Expand Up @@ -6166,28 +6321,31 @@ HRESULT CLR_RT_TypeSystem::BuildMethodName(
NANOCLR_HEADER();

CLR_RT_MethodDef_Instance inst{};
CLR_RT_TypeDef_Instance instOwner{};

if (inst.InitializeFromIndex(md) == false)
if (genericType == nullptr)
{
NANOCLR_SET_AND_LEAVE(CLR_E_WRONG_TYPE);
}
if (inst.InitializeFromIndex(md) == false)
{
NANOCLR_SET_AND_LEAVE(CLR_E_WRONG_TYPE);
}

if (instOwner.InitializeFromMethod(inst) == false)
{
NANOCLR_SET_AND_LEAVE(CLR_E_WRONG_TYPE);
}
CLR_RT_TypeDef_Instance instOwner{};
if (instOwner.InitializeFromMethod(inst) == false)
{
NANOCLR_SET_AND_LEAVE(CLR_E_WRONG_TYPE);
}

if (genericType == nullptr)
{
NANOCLR_CHECK_HRESULT(BuildTypeName(instOwner, szBuffer, iBuffer));

CLR_SafeSprintf(szBuffer, iBuffer, "::%s", inst.assembly->GetString(inst.target->name));
}
else
{
CLR_INDEX tsAsmIdx = genericType->Assembly();
CLR_RT_Assembly *genericTypeAssembly = g_CLR_RT_TypeSystem.m_assemblies[tsAsmIdx - 1];

CLR_RT_SignatureParser parser;
parser.Initialize_TypeSpec(inst.assembly, inst.assembly->GetTypeSpec(genericType->TypeSpec()));
parser.Initialize_TypeSpec(genericTypeAssembly, genericTypeAssembly->GetTypeSpec(genericType->TypeSpec()));

CLR_RT_SignatureParser::Element element;

Expand Down Expand Up @@ -6215,7 +6373,14 @@ HRESULT CLR_RT_TypeSystem::BuildMethodName(
}
}

CLR_SafeSprintf(szBuffer, iBuffer, ">::%s", inst.assembly->GetString(inst.target->name));
if (inst.InitializeFromIndex(md, *genericType) == false)
{
// this is a MethodDef for a generic type, but the generic type is not bound to an assembly
// so we cannot build the name of the method.
NANOCLR_SET_AND_LEAVE(CLR_E_WRONG_TYPE);
}

CLR_SafeSprintf(szBuffer, iBuffer, ">::%s", genericTypeAssembly->GetString(inst.target->name));
}

NANOCLR_NOCLEANUP();
Expand Down
Loading
Loading