diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 5292280025..7c84e185a1 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -1467,12 +1467,17 @@ jobs: $fileCount = (Get-ChildItem $dumpPath -File | Measure-Object).Count $hasFiles = $fileCount -gt 0 } - echo "##vso[task.setvariable variable=hasFiles;isOutput=true]$hasFiles" + echo "##vso[task.setvariable variable=hasFiles]$hasFiles" + condition: succeededOrFailed() displayName: "Check for dump files" name: checkFiles - task: PublishPipelineArtifact@1 - condition: eq(variables['hasFiles'], 'true') + condition: >- + and( + succeededOrFailed(), + eq(variables['hasFiles'], 'true') + ) displayName: Publish vstest dump files inputs: targetPath: "$(Build.ArtifactStagingDirectory)/vstest_dumps" diff --git a/src/CLR/Core/Execution.cpp b/src/CLR/Core/Execution.cpp index 5c0210d7b7..975953fcfa 100644 --- a/src/CLR/Core/Execution.cpp +++ b/src/CLR/Core/Execution.cpp @@ -2074,6 +2074,12 @@ static HRESULT ResolveGenericTypeParameter( NANOCLR_SET_AND_LEAVE(CLR_E_FAIL); } + // Fail here so the caller's allowUnresolvedVarFallback path handles it. + if (paramElement.DataType == DATATYPE_VAR || paramElement.DataType == DATATYPE_MVAR) + { + NANOCLR_SET_AND_LEAVE(CLR_E_FAIL); + } + outClass = paramElement.Class; outDataType = paramElement.DataType; @@ -2133,10 +2139,23 @@ HRESULT CLR_RT_ExecutionEngine::InitializeReference( } else { - NANOCLR_CHECK_HRESULT( - ResolveGenericTypeParameter(*genericInstance, res.GenericParamPosition, realTypeDef, dt)); - - goto process_datatype; + HRESULT hrParam = + ResolveGenericTypeParameter(*genericInstance, res.GenericParamPosition, realTypeDef, dt); + if (FAILED(hrParam)) + { + if (allowUnresolvedVarFallback) + { + dt = DATATYPE_OBJECT; + } + else + { + NANOCLR_CHECK_HRESULT(hrParam); + } + } + else + { + goto process_datatype; + } } } else if (dt == DATATYPE_MVAR) @@ -2306,51 +2325,85 @@ HRESULT CLR_RT_ExecutionEngine::InitializeLocals( typeSpecSignature--; CLR_RT_TypeSpec_Index genericTSIndex = {}; + bool foundGenericTypeSpec = false; if (methodDefInstance.genericType && NANOCLR_INDEX_IS_VALID(*methodDefInstance.genericType) && methodDefInstance.genericType->data != CLR_EmptyToken) { // method is generic, it can only use class from method's class generic parameters genericInstance.InitializeFromIndex(*methodDefInstance.genericType); + foundGenericTypeSpec = true; } else { - if (!assembly->FindTypeSpec(typeSpecSignature, genericTSIndex)) + if (assembly->FindTypeSpec(typeSpecSignature, genericTSIndex)) { - NANOCLR_SET_AND_LEAVE(CLR_E_WRONG_TYPE); + // copy over to parameter + genericInstance.InitializeFromIndex(genericTSIndex); + foundGenericTypeSpec = true; } - - // copy over to parameter - genericInstance.InitializeFromIndex(genericTSIndex); } - CLR_RT_SignatureParser parser; - parser.Initialize_TypeSpec(genericInstance); - - CLR_RT_SignatureParser::Element element; - NANOCLR_CHECK_HRESULT(parser.Advance(element)); - - // if this is another generic instance, need to advance to get the type - if (dt == DATATYPE_GENERICINST) + if (foundGenericTypeSpec) { + CLR_RT_SignatureParser parser; + parser.Initialize_TypeSpec(genericInstance); + + CLR_RT_SignatureParser::Element element; NANOCLR_CHECK_HRESULT(parser.Advance(element)); + + // if this is another generic instance, need to advance to get the type + if (dt == DATATYPE_GENERICINST) + { + NANOCLR_CHECK_HRESULT(parser.Advance(element)); + } + + cls = element.Class; + dt = element.DataType; } + else + { + // Some generic method locals are open instantiations, such as IEnumerator. + // A reference-type local only needs a null object slot, so parse the local signature + // directly instead of requiring a closed TypeSpec row. + CLR_RT_SignatureParser ownerParser; + ownerParser.Initialize_LocalVar(assembly, typeSpecSignature); - cls = element.Class; - dt = element.DataType; + CLR_RT_SignatureParser::Element ownerElement; + NANOCLR_CHECK_HRESULT(ownerParser.Advance(ownerElement)); + + if (ownerElement.DataType != DATATYPE_GENERICINST) + { + NANOCLR_SET_AND_LEAVE(CLR_E_WRONG_TYPE); + } + + NANOCLR_CHECK_HRESULT(ownerParser.Advance(ownerElement)); + + cls = ownerElement.Class; + dt = ownerElement.DataType; + + if (dt == DATATYPE_VALUETYPE) + { + // Value-type generic locals need a closed generic instance for allocation. + NANOCLR_SET_AND_LEAVE(CLR_E_WRONG_TYPE); + } + } // done, now consume the remaining of the local var signature CLR_RT_SignatureParser varParser; varParser.Initialize_LocalVar(assembly, typeSpecSignature); CLR_RT_SignatureParser::Element varElement; - // consume GENERICINST - varParser.Advance(varElement); - // consume class|valuetype + // consume GENERICINST (first element) - this sets ParamCount to 1 for + // the class/valuetype that follows, plus all nested generic arguments. varParser.Advance(varElement); - // consume parameters - for (int paramIndex = 0; paramIndex < varElement.GenParamCount; paramIndex++) + // Drain all remaining elements (class/valuetype token, generic argument count, and + // all nested arguments recursively). Using Available() handles nested GENERICINSTs + // such as ICollection> where KVP itself has inner bytes + // (VALUETYPE + TypeRef + count + I4 + STRING) that a fixed GenParamCount loop + // would leave unread, corrupting `sig` for subsequent locals. + while (varParser.Available() > 0) { NANOCLR_CHECK_HRESULT(varParser.Advance(varElement)); } @@ -2369,8 +2422,22 @@ HRESULT CLR_RT_ExecutionEngine::InitializeLocals( // type-level generic parameter in a locals signature (e.g. 'T' inside a generic type) CLR_INT8 genericParamPosition = *sig++; + // A propagated runtime element type can also bind the first type generic parameter + // on helper objects whose closed generic context is inferred at runtime. + if (NANOCLR_INDEX_IS_VALID(methodDefInstance.arrayElementType) && genericParamPosition == 0) + { + CLR_RT_TypeDef_Instance td; + if (!td.InitializeFromIndex(methodDefInstance.arrayElementType)) + { + NANOCLR_SET_AND_LEAVE(CLR_E_WRONG_TYPE); + } + + cls = methodDefInstance.arrayElementType; + dt = (NanoCLRDataType)td.target->dataType; + } // Resolve type-level generic parameter (VAR) using the method's enclosing type context - if (methodDefInstance.genericType && NANOCLR_INDEX_IS_VALID(*methodDefInstance.genericType) && + else if ( + methodDefInstance.genericType && NANOCLR_INDEX_IS_VALID(*methodDefInstance.genericType) && methodDefInstance.genericType->data != CLR_EmptyToken) { NANOCLR_CHECK_HRESULT( @@ -2388,8 +2455,21 @@ HRESULT CLR_RT_ExecutionEngine::InitializeLocals( // Method-level generic parameter (e.g., '!!T' in a generic method like Array.Empty()) CLR_UINT8 genericParamPosition = *sig++; + // Some rebound generic methods receive their method-generic argument from the + // runtime element type rather than from a MethodSpec. + if (NANOCLR_INDEX_IS_VALID(methodDefInstance.arrayElementType) && genericParamPosition == 0) + { + CLR_RT_TypeDef_Instance td; + if (!td.InitializeFromIndex(methodDefInstance.arrayElementType)) + { + NANOCLR_SET_AND_LEAVE(CLR_E_WRONG_TYPE); + } + + cls = methodDefInstance.arrayElementType; + dt = (NanoCLRDataType)td.target->dataType; + } // For generic methods, use the MethodSpec's signature to get the concrete type - if (NANOCLR_INDEX_IS_VALID(methodDefInstance.methodSpec)) + else if (NANOCLR_INDEX_IS_VALID(methodDefInstance.methodSpec)) { CLR_RT_MethodSpec_Instance methodSpec; CLR_RT_SignatureParser::Element element; @@ -2742,6 +2822,12 @@ HRESULT CLR_RT_ExecutionEngine::NewGenericInstanceObject( fieldCursor = reinterpret_cast(giHeader) + totFields; + CLR_Debug::Printf( + "DBG GenericInst: NewGenericInstanceObject type='%s' totFields=%d giHeader=%08X\r\n", + instance.assembly->GetString(instance.target->name), + totFields, + (uintptr_t)giHeader); + while (--totFields > 0) { while (clsFields == 0) @@ -2765,7 +2851,15 @@ HRESULT CLR_RT_ExecutionEngine::NewGenericInstanceObject( target--; clsFields--; + CLR_Debug::Printf( + "DBG GenericInst: InitField field='%s' type='%s' cursor=%08X\r\n", + assm->GetString(target->name), + assm->GetString(target->type), + (uintptr_t)fieldCursor); + NANOCLR_CHECK_HRESULT(InitializeReference(*fieldCursor, target, assm, genericInstance)); + + CLR_Debug::Printf("DBG GenericInst: InitField done dt=%d\r\n", (int)fieldCursor->DataType()); } if (instance.HasFinalizer()) diff --git a/src/CLR/Core/Interpreter.cpp b/src/CLR/Core/Interpreter.cpp index cb72184344..da3b76c226 100644 --- a/src/CLR/Core/Interpreter.cpp +++ b/src/CLR/Core/Interpreter.cpp @@ -2279,6 +2279,20 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) if (calleeInst.ResolveToken(arg, assm, effectiveCallerGeneric, &stack->m_call) == false) { +#if !defined(BUILD_RTM) + CLR_Debug::Printf( + "CALL resolve failed: op=%u token=%08x type=%u index=%u caller=%s callerTs=%08x " + "callerMs=%08x effTs=%08x callerArr=%08x\r\n", + op, + arg, + CLR_TypeFromTk(arg), + CLR_DataFromTk(arg), + stack->m_call.assembly->GetString(stack->m_call.target->name), + stack->m_call.genericType ? stack->m_call.genericType->data : 0, + stack->m_call.methodSpec.data, + effectiveCallerGeneric ? effectiveCallerGeneric->data : 0, + stack->m_call.arrayElementType.data); +#endif NANOCLR_SET_AND_LEAVE(CLR_E_WRONG_TYPE); } @@ -2349,7 +2363,7 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) if (declType.target->dataType == DATATYPE_VALUETYPE) { - // any method on a value‐type is called via byref, so skip the object‐dereference path + // any method on a value-type is called via byref, so skip the object-dereference path // altogether doInstanceResolution = false; } @@ -2427,6 +2441,56 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) { NANOCLR_SET_AND_LEAVE(CLR_E_WRONG_TYPE); } + + // The TypeSpec from the MethodRef's owner might belong to an interface + // (e.g. ICollection>) while the dispatched method belongs + // to the concrete type (e.g. Dictionary). Using a mismatched + // TypeSpec corrupts generic-param resolution inside the callee. + // Detect this and try to recover the correct TypeSpec from the 'this' + // object, which stores its closed TypeSpec via HB_GenericInstance. + if (calleeInst.genericType && + NANOCLR_INDEX_IS_VALID(*calleeInst.genericType)) + { + CLR_RT_TypeSpec_Instance tsCheck{}; + CLR_RT_TypeDef_Instance declCheck{}; + if (tsCheck.InitializeFromIndex(*calleeInst.genericType) && + declCheck.InitializeFromMethod(calleeInst) && + tsCheck.genericTypeDef.data != declCheck.data) + { + // Interface TypeSpec doesn't match the callee's declaring type. + // Attempt to recover the correct closed TypeSpec from 'this'. + bool recovered = false; + CLR_RT_HeapBlock *thisHeap = pThis[0].Dereference(); + if (thisHeap != nullptr && thisHeap->IsAGenericInstance()) + { + const CLR_RT_TypeSpec_Index &objTS = + thisHeap->ObjectGenericType(); + if (NANOCLR_INDEX_IS_VALID(objTS)) + { + CLR_RT_TypeSpec_Instance tsObj{}; + if (tsObj.InitializeFromIndex(objTS) && + tsObj.genericTypeDef.data == declCheck.data) + { + // The object's TypeSpec matches the callee's declaring + // type — reinitialize with the correct closed TypeSpec. + if (calleeInst.InitializeFromIndex( + calleeReal, + objTS, + &stack->m_call)) + { + recovered = true; + } + } + } + } + + if (!recovered) + { + // Cannot recover — null is safer than a mismatched TypeSpec. + calleeInst.genericType = nullptr; + } + } + } } else { @@ -2485,7 +2549,14 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) if (op == CEE_CALLVIRT && pThis[0].DataType() == DATATYPE_BYREF) { CLR_RT_TypeDef_Instance constrainedType{}; - if (constrainedType.ResolveToken(constrainedTypeToken, assm, &stack->m_call)) + // Pass genericType as contextTypeSpec to enable MVAR→VAR→concrete + // two-level resolution (e.g. constrained.!!T inside Equals + // where TValue is a VAR in the enclosing Dictionary). + if (constrainedType.ResolveToken( + constrainedTypeToken, + assm, + &stack->m_call, + stack->m_call.genericType)) { bool isValueType = ((constrainedType.target->flags & CLR_RECORD_TYPEDEF::TD_Semantics_Mask) == @@ -2648,7 +2719,15 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) } else if (calleeInst.genericType && NANOCLR_INDEX_IS_VALID(*calleeInst.genericType)) { - effectiveGenericContext = calleeInst.genericType; + // Only use calleeInst's genericType when it is a CLOSED generic (all VAR/MVAR + // parameters already resolved to concrete types). An open TypeSpec (e.g. + // KeyValuePair) cannot resolve VAR indices and causes + // CLR_E_WRONG_TYPE in the callee. Fall through to Priority 3 instead. + CLR_RT_TypeSpec_Instance calleeTs{}; + if (calleeTs.InitializeFromIndex(*calleeInst.genericType) && calleeTs.IsClosedGenericType()) + { + effectiveGenericContext = calleeInst.genericType; + } } else if (effectiveCallerGeneric && NANOCLR_INDEX_IS_VALID(*effectiveCallerGeneric)) { @@ -2836,6 +2915,18 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) if (calleeInst.ResolveToken(arg, assm, stack->m_call.genericType, &stack->m_call) == false) { +#if !defined(BUILD_RTM) + CLR_Debug::Printf( + "NEWOBJ resolve failed: token=%08x type=%u index=%u caller=%s callerTs=%08x callerMs=%08x " + "callerArr=%08x\r\n", + arg, + CLR_TypeFromTk(arg), + CLR_DataFromTk(arg), + stack->m_call.assembly->GetString(stack->m_call.target->name), + stack->m_call.genericType ? stack->m_call.genericType->data : 0, + stack->m_call.methodSpec.data, + stack->m_call.arrayElementType.data); +#endif NANOCLR_SET_AND_LEAVE(CLR_E_WRONG_TYPE); } @@ -2845,6 +2936,21 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) if (!calleeInst.GetDeclaringType(cls)) { +#if !defined(BUILD_RTM) + CLR_Debug::Printf( + "NEWOBJ declaring type failed: token=%08x callee=%s calleeTs=%08x calleeMs=%08x caller=%s " + "callerTs=%08x callerMs=%08x callerArr=%08x\r\n", + arg, + calleeInst.assembly && calleeInst.target + ? calleeInst.assembly->GetString(calleeInst.target->name) + : "", + calleeInst.genericType ? calleeInst.genericType->data : 0, + calleeInst.methodSpec.data, + stack->m_call.assembly->GetString(stack->m_call.target->name), + stack->m_call.genericType ? stack->m_call.genericType->data : 0, + stack->m_call.methodSpec.data, + stack->m_call.arrayElementType.data); +#endif NANOCLR_SET_AND_LEAVE(CLR_E_WRONG_TYPE); } @@ -3058,6 +3164,17 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) CLR_RT_FieldDef_Instance fieldInst; if (fieldInst.ResolveToken(arg, assm, &stack->m_call) == false) { +#if defined(NANOCLR_TRACE_GENERICS) + if (s_CLR_RT_fTrace_GenericFields >= c_CLR_RT_Trace_Info) + { + CLR_Debug::Printf( + "GenericFields: LDFLD ResolveToken failed tk=%08X assm='%s' caller='%s'\r\n", + arg, + assm ? assm->name : "", + stack->m_call.assembly ? stack->m_call.assembly->GetString(stack->m_call.target->name) + : ""); + } +#endif NANOCLR_SET_AND_LEAVE(CLR_E_WRONG_TYPE); } @@ -3070,9 +3187,50 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) { case DATATYPE_CLASS: case DATATYPE_VALUETYPE: + { + CLR_RT_HeapBlock &field = obj[fieldInst.CrossReference().offset]; + if (field.DataSize() > 1) + { + // Embedded value type field spanning multiple heap blocks. + // Mirror the LDOBJ pattern: BYREF + LoadFromReference clones + // the full struct in a GC-safe way. Do NOT goto Execute_LoadAndPromote + // since LoadFromReference already places the clone in evalPos[0]. + UPDATESTACK(stack, evalPos); + CLR_RT_HeapBlock safeRef; + safeRef.SetReference(field); + CLR_RT_ProtectFromGC gc(safeRef); + NANOCLR_CHECK_HRESULT(evalPos[0].LoadFromReference(safeRef)); + break; + } + evalPos[0].Assign(field); + goto Execute_LoadAndPromote; + } case DATATYPE_GENERICINST: - evalPos[0].Assign(obj[fieldInst.CrossReference().offset]); + { +#if defined(NANOCLR_TRACE_GENERICS) + if (s_CLR_RT_fTrace_GenericFields >= c_CLR_RT_Trace_Verbose) + { + CLR_Debug::Printf( + "GenericFields: LDFLD GENERICINST field='%s' offset=%d dt=%d\r\n", + fieldInst.assembly ? fieldInst.assembly->GetString(fieldInst.target->name) + : "", + fieldInst.CrossReference().offset, + (int)obj[fieldInst.CrossReference().offset].DataType()); + } +#endif + CLR_RT_HeapBlock &field2 = obj[fieldInst.CrossReference().offset]; + if (field2.DataSize() > 1) + { + UPDATESTACK(stack, evalPos); + CLR_RT_HeapBlock safeRef2; + safeRef2.SetReference(field2); + CLR_RT_ProtectFromGC gc(safeRef2); + NANOCLR_CHECK_HRESULT(evalPos[0].LoadFromReference(safeRef2)); + break; + } + evalPos[0].Assign(field2); goto Execute_LoadAndPromote; + } case DATATYPE_DATETIME: case DATATYPE_TIMESPAN: evalPos[0].SetInteger((CLR_INT64)obj->NumericByRefConst().s8); @@ -3112,6 +3270,17 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) CLR_RT_FieldDef_Instance fieldInst; if (fieldInst.ResolveToken(arg, assm, &stack->m_call) == false) { +#if defined(NANOCLR_TRACE_GENERICS) + if (s_CLR_RT_fTrace_GenericFields >= c_CLR_RT_Trace_Info) + { + CLR_Debug::Printf( + "GenericFields: LDFLDA ResolveToken failed tk=%08X assm='%s' caller='%s'\r\n", + arg, + assm ? assm->name : "", + stack->m_call.assembly ? stack->m_call.assembly->GetString(stack->m_call.target->name) + : ""); + } +#endif NANOCLR_SET_AND_LEAVE(CLR_E_WRONG_TYPE); } @@ -3156,6 +3325,17 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) CLR_RT_FieldDef_Instance fieldInst; if (fieldInst.ResolveToken(arg, assm, &stack->m_call) == false) { +#if defined(NANOCLR_TRACE_GENERICS) + if (s_CLR_RT_fTrace_GenericFields >= c_CLR_RT_Trace_Info) + { + CLR_Debug::Printf( + "GenericFields: STFLD ResolveToken failed tk=%08X assm='%s' caller='%s'\r\n", + arg, + assm ? assm->name : "", + stack->m_call.assembly ? stack->m_call.assembly->GetString(stack->m_call.target->name) + : ""); + } +#endif NANOCLR_SET_AND_LEAVE(CLR_E_WRONG_TYPE); } @@ -3166,11 +3346,24 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) switch (dt) { - case DATATYPE_GENERICINST: case DATATYPE_CLASS: case DATATYPE_VALUETYPE: obj[fieldInst.CrossReference().offset].AssignAndPreserveType(evalPos[2]); break; + case DATATYPE_GENERICINST: +#if defined(NANOCLR_TRACE_GENERICS) + if (s_CLR_RT_fTrace_GenericFields >= c_CLR_RT_Trace_Verbose) + { + CLR_Debug::Printf( + "GenericFields: STFLD GENERICINST field='%s' offset=%d src_dt=%d\r\n", + fieldInst.assembly ? fieldInst.assembly->GetString(fieldInst.target->name) + : "", + fieldInst.CrossReference().offset, + (int)evalPos[2].DataType()); + } +#endif + obj[fieldInst.CrossReference().offset].AssignAndPreserveType(evalPos[2]); + break; case DATATYPE_DATETIME: // Special case. case DATATYPE_TIMESPAN: // Special case. obj->NumericByRef().s8 = evalPos[2].NumericByRefConst().s8; @@ -3225,6 +3418,18 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) field.genericType, &stack->m_genericTypeSpecStorage, &stack->m_call); + +#if defined(NANOCLR_TRACE_GENERICS) + if (s_CLR_RT_fTrace_GenericFields >= c_CLR_RT_Trace_Verbose) + { + CLR_Debug::Printf( + "GenericStatic: LDSFLD field='%s' ts=[%04X:%04X] ptr=%08X\r\n", + field.assembly ? field.assembly->GetString(field.target->name) : "", + field.genericType->Assembly(), + field.genericType->TypeSpec(), + (uintptr_t)ptr); + } +#endif } else { @@ -3255,6 +3460,16 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) NANOCLR_CHECK_HRESULT(HandleGenericCctorReschedule(tsInst, stack, &ip)); } } + +#if defined(NANOCLR_TRACE_GENERICS) + if (s_CLR_RT_fTrace_GenericFields >= c_CLR_RT_Trace_Verbose) + { + CLR_Debug::Printf( + "GenericStatic: LDSFLD resolved ptr=%08X dt=%d\r\n", + (uintptr_t)ptr, + (int)ptr->DataType()); + } +#endif } evalPos++; @@ -3420,7 +3635,10 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) } } - if (typeInst.ResolveToken(arg, assm, &stack->m_call) == false) + // Pass genericType as contextTypeSpec so that two-level MVAR→VAR→concrete + // resolution succeeds (e.g. box !!T inside Equals where TValue is a + // VAR in the enclosing generic type like Dictionary). + if (typeInst.ResolveToken(arg, assm, &stack->m_call, stack->m_call.genericType) == false) { // restore previous context before bailing stack->m_call.arrayElementType = previousArrayElemType; @@ -3593,7 +3811,9 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) } } - if (typeInst.ResolveToken(arg, assm, &stack->m_call) == false) + // Pass genericType as contextTypeSpec for the same MVAR→VAR→concrete + // two-level resolution needed by the BOX path. + if (typeInst.ResolveToken(arg, assm, &stack->m_call, stack->m_call.genericType) == false) { // restore previous context before bailing stack->m_call.arrayElementType = previousArrayElemType; @@ -3987,7 +4207,7 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) // Promote the value if it's a reference or boxed struct evalPos[3].Promote(); - // Compute the element‐size: 0 for refs (incl. genericinst), sizeInBytes for primitives + // Compute the element-size: 0 for refs (incl. genericinst), sizeInBytes for primitives size_t size = 0; if (elemDT <= DATATYPE_LAST_PRIMITIVE_TO_PRESERVE) { @@ -4148,7 +4368,7 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) // Type generic parameter (!T) if (stack->m_call.genericType == nullptr) { - // No closed‐generic context available: fall back to returning the + // No closed-generic context available: fall back to returning the // declaring TYPE itself as the reflection result (rarely correct, but at least // safe). CLR_RT_TypeDef_Index fallbackTypeDef = gpCR.classTypeDef; diff --git a/src/CLR/Core/TypeSystem.cpp b/src/CLR/Core/TypeSystem.cpp index ac43ca9fc9..d503ce5030 100644 --- a/src/CLR/Core/TypeSystem.cpp +++ b/src/CLR/Core/TypeSystem.cpp @@ -1,4 +1,4 @@ -// +// // Copyright (c) .NET Foundation and Contributors // Portions Copyright (c) Microsoft Corporation. All rights reserved. // See LICENSE file in the project root for full license information. @@ -33,11 +33,11 @@ int s_CLR_RT_fTrace_Exceptions = NANOCLR_TRACE_DEFAULT(c_CLR_RT_Trace_Info, c_CL #endif #if defined(NANOCLR_TRACE_INSTRUCTIONS) -int s_CLR_RT_fTrace_Instructions = NANOCLR_TRACE_DEFAULT(c_CLR_RT_Trace_Info, c_CLR_RT_Trace_None); +int s_CLR_RT_fTrace_Instructions = NANOCLR_TRACE_DEFAULT(c_CLR_RT_Trace_Verbose, c_CLR_RT_Trace_None); #endif #if defined(NANOCLR_GC_VERBOSE) -int s_CLR_RT_fTrace_Memory = NANOCLR_TRACE_DEFAULT(c_CLR_RT_Trace_Info, c_CLR_RT_Trace_None); +int s_CLR_RT_fTrace_Memory = NANOCLR_TRACE_DEFAULT(c_CLR_RT_Trace_Verbose, c_CLR_RT_Trace_None); #endif #if defined(NANOCLR_TRACE_MEMORY_STATS) @@ -56,6 +56,10 @@ int s_CLR_RT_fTrace_SimulateSpeed = NANOCLR_TRACE_DEFAULT(c_CLR_RT_Trace_Info, c int s_CLR_RT_fTrace_AssemblyOverhead = NANOCLR_TRACE_DEFAULT(c_CLR_RT_Trace_Info, c_CLR_RT_Trace_Info); #endif +#if defined(NANOCLR_TRACE_GENERICS) +int s_CLR_RT_fTrace_GenericFields = NANOCLR_TRACE_DEFAULT(c_CLR_RT_Trace_Verbose, c_CLR_RT_Trace_Info); +#endif + #if defined(VIRTUAL_DEVICE) int s_CLR_RT_fTrace_StopOnFAILED = NANOCLR_TRACE_DEFAULT(c_CLR_RT_Trace_None, c_CLR_RT_Trace_None); #endif @@ -213,6 +217,7 @@ void CLR_RT_SignatureParser::Initialize_TypeSpec(CLR_RT_Assembly *assm, CLR_PMET Flags = 0; ParamCount = 1; GenParamCount = 0; + m_pendingGenericInst = false; } void CLR_RT_SignatureParser::Initialize_TypeSpec(CLR_RT_TypeSpec_Instance tsInstance) @@ -226,6 +231,7 @@ void CLR_RT_SignatureParser::Initialize_TypeSpec(CLR_RT_TypeSpec_Instance tsInst Flags = 0; ParamCount = 1; GenParamCount = 0; + m_pendingGenericInst = false; } //--// @@ -251,6 +257,7 @@ void CLR_RT_SignatureParser::Initialize_Interfaces(CLR_RT_Assembly *assm, const Assembly = assm; GenParamCount = 0; + m_pendingGenericInst = false; } //--// @@ -271,6 +278,7 @@ void CLR_RT_SignatureParser::Initialize_FieldSignature(CLR_RT_Assembly *assm, CL Signature = fd; GenParamCount = 0; + m_pendingGenericInst = false; } void CLR_RT_SignatureParser::Initialize_FieldDef(CLR_RT_Assembly *assm, const CLR_RECORD_FIELDDEF *fd) @@ -291,6 +299,7 @@ void CLR_RT_SignatureParser::Initialize_FieldDef(CLR_RT_Assembly *assm, CLR_PMET Signature = fd; GenParamCount = 0; + m_pendingGenericInst = false; } //--// @@ -334,6 +343,7 @@ void CLR_RT_SignatureParser::Initialize_MethodSignature(CLR_RT_Assembly *assm, C Assembly = assm; Signature = md; + m_pendingGenericInst = false; } void CLR_RT_SignatureParser::Initialize_MethodSignature(CLR_RT_MethodSpec_Instance *ms) @@ -359,6 +369,7 @@ void CLR_RT_SignatureParser::Initialize_MethodSignature(CLR_RT_MethodSpec_Instan Assembly = ms->assembly; GenParamCount = ParamCount; + m_pendingGenericInst = false; } //--// @@ -388,6 +399,7 @@ bool CLR_RT_SignatureParser::Initialize_GenericParamTypeSignature( Flags = 0; GenParamCount = 0; + m_pendingGenericInst = false; // done here return true; @@ -412,6 +424,7 @@ void CLR_RT_SignatureParser::Initialize_MethodLocals(CLR_RT_Assembly *assm, cons ParamCount = md->localsCount; GenParamCount = 0; + m_pendingGenericInst = false; } void CLR_RT_SignatureParser::Initialize_LocalVar(CLR_RT_Assembly *assm, const CLR_PMETADATA sig) @@ -426,6 +439,7 @@ void CLR_RT_SignatureParser::Initialize_LocalVar(CLR_RT_Assembly *assm, const CL ParamCount = 1; GenParamCount = 0; + m_pendingGenericInst = false; } //--// @@ -440,6 +454,7 @@ void CLR_RT_SignatureParser::Initialize_Objects(CLR_RT_HeapBlock *lst, int count ParamCount = count; GenParamCount = 0; + m_pendingGenericInst = false; } //--// @@ -609,7 +624,15 @@ HRESULT CLR_RT_SignatureParser::Advance(Element &res) CLR_RT_TypeDef_Instance cls{}; cls.InitializeFromIndex(res.Class); - if (cls.target->genericParamCount > 0) + // Read arg_count when: + // a) this CLASS/VALUETYPE immediately follows a GENERICINST marker + // (covers nested types whose TypeDef has genericParamCount == 0 but + // whose GENERICINST TypeSpec still carries enclosing-type args), OR + // b) the TypeDef declares its own generic parameters. + // m_pendingGenericInst is checked FIRST so we never dereference cls.target + // when InitializeFromIndex returned false (e.g. TBL_TypeSpec sub-case where + // res.Class was not populated). + if (m_pendingGenericInst || (cls.target != nullptr && cls.target->genericParamCount > 0)) { // reset the generic instance flag res.IsGenericInst = false; @@ -621,6 +644,9 @@ HRESULT CLR_RT_SignatureParser::Advance(Element &res) ParamCount += res.GenParamCount; } + // consumed — clear for subsequent elements + m_pendingGenericInst = false; + NANOCLR_SET_AND_LEAVE(S_OK); } @@ -637,6 +663,10 @@ HRESULT CLR_RT_SignatureParser::Advance(Element &res) // set flag for GENERICINST res.IsGenericInst = true; + // Signal that the very next CLASS/VALUETYPE element must consume its + // arg-count byte unconditionally (even for nested types with 0 own params). + m_pendingGenericInst = true; + // update parser param counter ParamCount++; @@ -810,6 +840,14 @@ bool CLR_RT_TypeSpec_Instance::ResolveToken( int pos = element.GenericParamPosition; + if (caller != nullptr && NANOCLR_INDEX_IS_VALID(caller->arrayElementType) && pos == 0) + { + cachedElementType = caller->arrayElementType; + genericTypeDef.Clear(); + + return true; + } + // Use the *caller's* bound genericType (Stack, etc.) if (caller == nullptr || caller->genericType == nullptr) { @@ -843,7 +881,17 @@ bool CLR_RT_TypeSpec_Instance::ResolveToken( } else if (element.DataType == DATATYPE_MVAR) { - ASSERT(false); + if (caller != nullptr && NANOCLR_INDEX_IS_VALID(caller->arrayElementType) && + element.GenericParamPosition == 0) + { + cachedElementType = caller->arrayElementType; + genericTypeDef.Clear(); + } + else + { + ClearInstance(); + return false; + } } else { @@ -1315,34 +1363,52 @@ bool CLR_RT_TypeDef_Instance::ResolveToken( } else { - return false; + effectiveContext = nullptr; } - CLR_RT_TypeSpec_Instance callerTypeSpec; - if (!callerTypeSpec.InitializeFromIndex(*effectiveContext)) + if (effectiveContext != nullptr) { - return false; - } + CLR_RT_TypeSpec_Instance callerTypeSpec; + if (!callerTypeSpec.InitializeFromIndex(*effectiveContext)) + { + return false; + } - CLR_RT_SignatureParser::Element paramElement; + CLR_RT_SignatureParser::Element paramElement; - // Try to map using the generic context (e.g. !T→Int32) - if (callerTypeSpec.GetGenericParam(genericPosition, paramElement) && - paramElement.DataType != DATATYPE_VAR) - { - // Successfully resolved from generic context - if (NANOCLR_INDEX_IS_VALID(paramElement.Class)) + // Try to map using the generic context (e.g. !T→Int32) + if (callerTypeSpec.GetGenericParam(genericPosition, paramElement) && + paramElement.DataType != DATATYPE_VAR) { - data = paramElement.Class.data; - assembly = g_CLR_RT_TypeSystem.m_assemblies[paramElement.Class.Assembly() - 1]; - target = assembly->GetTypeDef(paramElement.Class.Type()); + // Successfully resolved from generic context + if (NANOCLR_INDEX_IS_VALID(paramElement.Class)) + { + data = paramElement.Class.data; + assembly = g_CLR_RT_TypeSystem.m_assemblies[paramElement.Class.Assembly() - 1]; + target = assembly->GetTypeDef(paramElement.Class.Type()); + } + else if (paramElement.DataType == DATATYPE_MVAR) + { + // need to defer to generic method argument + genericPosition = paramElement.GenericParamPosition; + + goto resolve_generic_argument; + } + else + { + return false; + } } - else if (paramElement.DataType == DATATYPE_MVAR) + else if ( + caller != nullptr && NANOCLR_INDEX_IS_VALID(caller->arrayElementType) && + genericPosition == 0) { - // need to defer to generic method argument - genericPosition = paramElement.GenericParamPosition; - - goto resolve_generic_argument; + // Fallback to arrayElementType: covers runtime-inferred generic bindings and + // the nested-VAR case where the context TypeSpec is still open. + data = caller->arrayElementType.data; + assembly = + g_CLR_RT_TypeSystem.m_assemblies[caller->arrayElementType.Assembly() - 1]; + target = assembly->GetTypeDef(caller->arrayElementType.Type()); } else { @@ -1353,8 +1419,6 @@ bool CLR_RT_TypeDef_Instance::ResolveToken( caller != nullptr && NANOCLR_INDEX_IS_VALID(caller->arrayElementType) && genericPosition == 0) { - // Fallback to arrayElementType: covers SZArrayHelper scenarios and - // the nested-VAR case where the context TypeSpec is still open. data = caller->arrayElementType.data; assembly = g_CLR_RT_TypeSystem.m_assemblies[caller->arrayElementType.Assembly() - 1]; target = assembly->GetTypeDef(caller->arrayElementType.Type()); @@ -1503,6 +1567,15 @@ bool CLR_RT_TypeDef_Instance::ResolveNullableType( { int pos = elem.GenericParamPosition; + if (caller != nullptr && NANOCLR_INDEX_IS_VALID(caller->arrayElementType) && pos == 0) + { + data = caller->arrayElementType.data; + assembly = g_CLR_RT_TypeSystem.m_assemblies[caller->arrayElementType.Assembly() - 1]; + target = assembly->GetTypeDef(caller->arrayElementType.Type()); + + return true; + } + // Use the *caller's* bound genericType (Stack, etc.) if (caller == nullptr || caller->genericType == nullptr) { @@ -1530,6 +1603,17 @@ bool CLR_RT_TypeDef_Instance::ResolveNullableType( } else if (elem.DataType == DATATYPE_MVAR) { + // Some rebound generic methods bind their first method generic parameter from the + // runtime element type. + if (caller != nullptr && NANOCLR_INDEX_IS_VALID(caller->arrayElementType) && elem.GenericParamPosition == 0) + { + data = caller->arrayElementType.data; + assembly = g_CLR_RT_TypeSystem.m_assemblies[caller->arrayElementType.Assembly() - 1]; + target = assembly->GetTypeDef(caller->arrayElementType.Type()); + + return true; + } + // Use the *caller's* bound genericType (Stack, etc.) if (caller == nullptr || caller->genericType == nullptr) { @@ -1675,43 +1759,61 @@ bool CLR_RT_FieldDef_Instance::ResolveToken( case TBL_TypeSpec: { - // the metadata own (possibly open) TypeSpec... - const CLR_RT_TypeSpec_Index *mdTS = &assm->crossReferenceFieldRef[index].genericType; + // Use the pre-resolved FieldDef from the cross-reference (set during ResolveFieldRef). + // FindFieldDef with assm (caller assembly) searches the wrong assembly field-def + // table when the owning type is in a different assembly, giving wrong results. + data = assm->crossReferenceFieldRef[index].target.data; + + if (!NANOCLR_INDEX_IS_VALID(*this)) + { +#if defined(NANOCLR_TRACE_GENERICS) + if (s_CLR_RT_fTrace_GenericFields >= c_CLR_RT_Trace_Info) + { + CLR_Debug::Printf( + "GenericFields: FieldDef ResolveToken TBL_TypeSpec invalid cross-ref idx=%u " + "assm='%s'\r\n", + index, + assm->name); + } +#endif + return false; + } - // decide whether to prefer the caller’s closed-generic + assembly = g_CLR_RT_TypeSystem.m_assemblies[Assembly() - 1]; + target = assembly->GetFieldDef(Field()); + + // Resolve the best TypeSpec context for type-printing and generic validation. + const CLR_RT_TypeSpec_Index *mdTS = &assm->crossReferenceFieldRef[index].genericType; const CLR_RT_TypeSpec_Index *effectiveTS = mdTS; + if (caller && caller->genericType && NANOCLR_INDEX_IS_VALID(*caller->genericType)) { CLR_RT_TypeSpec_Instance instCaller, instMd; if (instCaller.InitializeFromIndex(*caller->genericType) && instMd.InitializeFromIndex(*mdTS)) { - // check if generic definition token is the same... if (instCaller.genericTypeDef.data == instMd.genericTypeDef.data) { - // it is, therefore use the caller closed TypeSpec effectiveTS = caller->genericType; } } } - // now bind against effectiveTS genericType = effectiveTS; - CLR_RT_FieldDef_Index resolved; - - if (!assm->FindFieldDef(genericType, assm->GetString(fr->name), assm, fr->signature, resolved)) - { - return false; - } - - data = resolved.data; - assembly = assm; - target = assembly->GetFieldDef(Field()); - break; } default: // should not happen +#if defined(NANOCLR_TRACE_GENERICS) + if (s_CLR_RT_fTrace_GenericFields >= c_CLR_RT_Trace_Info) + { + CLR_Debug::Printf( + "GenericFields: FieldDef ResolveToken unexpected owner table=%u idx=%u assm='%s'\r\n", + (unsigned)fr->Owner(), + index, + assm->name); + } +#endif return false; } @@ -1735,6 +1837,16 @@ bool CLR_RT_FieldDef_Instance::ResolveToken( default: // Not a field token +#if defined(NANOCLR_TRACE_GENERICS) + if (s_CLR_RT_fTrace_GenericFields >= c_CLR_RT_Trace_Info) + { + CLR_Debug::Printf( + "GenericFields: FieldDef ResolveToken unrecognised token table=%u tk=%08X assm='%s'\r\n", + (unsigned)CLR_TypeFromTk(tk), + tk, + assm->name); + } +#endif return false; } } @@ -2102,16 +2214,21 @@ bool CLR_RT_MethodDef_Instance::ResolveToken( // Pick the "winner" between methodOwnerTS or callerGeneric: genericType = useCaller ? callerGeneric : methodOwnerTS; - // When the chosen TypeSpec is open (contains MVAR) and the caller has a MethodSpec, - // resolve the MVAR to find a matching closed TypeSpec. - if (!useCaller && caller != nullptr && NANOCLR_INDEX_IS_VALID(caller->methodSpec)) + // When the chosen TypeSpec is open (contains MVAR), resolve it to a matching closed TypeSpec. + // Normal generic calls use MethodSpec; some rebound calls instead supply the method generic + // through a runtime-inferred element type. + if (!useCaller && caller != nullptr && + (NANOCLR_INDEX_IS_VALID(caller->methodSpec) || + NANOCLR_INDEX_IS_VALID(caller->arrayElementType))) { CLR_RT_TypeSpec_Instance openTsInst{}; if (openTsInst.InitializeFromIndex(*methodOwnerTS) && NANOCLR_INDEX_IS_VALID(openTsInst.genericTypeDef) && !openTsInst.IsClosedGenericType()) { CLR_RT_MethodSpec_Instance msInst{}; - if (msInst.InitializeFromIndex(caller->methodSpec)) + bool hasMethodSpec = NANOCLR_INDEX_IS_VALID(caller->methodSpec) && + msInst.InitializeFromIndex(caller->methodSpec); + if (hasMethodSpec || NANOCLR_INDEX_IS_VALID(caller->arrayElementType)) { // Parse the open TypeSpec to get arg count and resolve each MVAR CLR_RT_SignatureParser openParser{}; @@ -2137,36 +2254,45 @@ bool CLR_RT_MethodDef_Instance::ResolveToken( if (argElem.DataType == DATATYPE_MVAR) { - CLR_RT_SignatureParser::Element msArgElem{}; - if (msInst.GetGenericArgument(argElem.GenericParamPosition, msArgElem)) + if (caller != nullptr && NANOCLR_INDEX_IS_VALID(caller->arrayElementType) && + argElem.GenericParamPosition == 0) + { + resolvedArgs[a] = caller->arrayElementType; + } + else { - if (msArgElem.DataType == DATATYPE_VAR && - caller->genericType != nullptr && - NANOCLR_INDEX_IS_VALID(*caller->genericType)) + CLR_RT_SignatureParser::Element msArgElem{}; + if (hasMethodSpec && + msInst.GetGenericArgument(argElem.GenericParamPosition, msArgElem)) { - CLR_RT_TypeSpec_Instance callerTsInst{}; - CLR_RT_SignatureParser::Element paramElem{}; - if (callerTsInst.InitializeFromIndex(*caller->genericType) && - callerTsInst.GetGenericParam( - msArgElem.GenericParamPosition, - paramElem)) + if (msArgElem.DataType == DATATYPE_VAR && + caller->genericType != nullptr && + NANOCLR_INDEX_IS_VALID(*caller->genericType)) { - resolvedArgs[a] = paramElem.Class; + CLR_RT_TypeSpec_Instance callerTsInst{}; + CLR_RT_SignatureParser::Element paramElem{}; + if (callerTsInst.InitializeFromIndex(*caller->genericType) && + callerTsInst.GetGenericParam( + msArgElem.GenericParamPosition, + paramElem)) + { + resolvedArgs[a] = paramElem.Class; + } + else + { + allResolved = false; + } } else { - allResolved = false; + resolvedArgs[a] = msArgElem.Class; } } else { - resolvedArgs[a] = msArgElem.Class; + allResolved = false; } } - else - { - allResolved = false; - } } else if (argElem.DataType == DATATYPE_VAR) { @@ -2284,26 +2410,16 @@ bool CLR_RT_MethodDef_Instance::ResolveToken( } } - CLR_RT_Assembly *methodAssembly = g_CLR_RT_TypeSystem.m_assemblies[methodOwnerTS->Assembly() - 1]; - - const CLR_RECORD_TYPESPEC *ts = methodAssembly->GetTypeSpec(methodOwnerTS->TypeSpec()); - CLR_UINT32 assemblyIndex = 0xFFFF; - CLR_RT_MethodDef_Index methodIndex; - - if (!methodAssembly->FindMethodDef( - ts, - methodAssembly->GetString(mr->name), - methodAssembly, - mr->signature, - methodIndex, - assemblyIndex)) + // Use the pre-resolved method target from startup instead of a fresh + // signature-based FindMethodDef call. + data = assm->crossReferenceMethodRef[index].target.data; + if (!data) { return false; } - Set(assemblyIndex, methodIndex.Method()); - assembly = g_CLR_RT_TypeSystem.m_assemblies[assemblyIndex - 1]; - target = assembly->GetMethodDef(methodIndex.Method()); + assembly = g_CLR_RT_TypeSystem.m_assemblies[Assembly() - 1]; + target = assembly->GetMethodDef(Method()); } else { @@ -2311,6 +2427,11 @@ bool CLR_RT_MethodDef_Instance::ResolveToken( // get data for MethodRef (from index) data = assm->crossReferenceMethodRef[index].target.data; + if (!data) + { + return false; + } + // get assembly for this type ref assembly = g_CLR_RT_TypeSystem.m_assemblies[Assembly() - 1]; // grab the MethodDef @@ -2370,8 +2491,15 @@ bool CLR_RT_MethodDef_Instance::ResolveToken( return false; } - // get generic type - genericType = &assembly->crossReferenceTypeSpec[ms->container].genericType; + // get generic type - guard against invalid container index (CLR_EmptyIndex == 65535) + if (ms->container != CLR_EmptyIndex && ms->container < (CLR_UINT16)assembly->tablesSize[TBL_TypeSpec]) + { + genericType = &assembly->crossReferenceTypeSpec[ms->container].genericType; + } + else + { + genericType = nullptr; + } #if defined(NANOCLR_INSTANCE_NAMES) name = assembly->GetString(target->name); @@ -2415,8 +2543,15 @@ bool CLR_RT_MethodDef_Instance::ResolveToken( } break; - // get generic type - genericType = &assembly->crossReferenceTypeSpec[ms->container].genericType; + // get generic type (dead code: all paths above return, but kept for clarity) + if (ms->container != CLR_EmptyIndex && ms->container < (CLR_UINT16)assembly->tablesSize[TBL_TypeSpec]) + { + genericType = &assembly->crossReferenceTypeSpec[ms->container].genericType; + } + else + { + genericType = nullptr; + } #if defined(NANOCLR_INSTANCE_NAMES) name = assembly->GetString(target->name); @@ -2920,7 +3055,7 @@ HRESULT CLR_RT_TypeDescriptor::InitializeFromSignatureToken( { // !T: ask the CLR to map that slot into the *actual* argument - // For SZArrayHelper scenarios, arrayElementType is authoritative for position 0 + // For runtime-inferred generic element scenarios, arrayElementType is authoritative for position 0 if (caller && NANOCLR_INDEX_IS_VALID(caller->arrayElementType) && elem.GenericParamPosition == 0) { this->InitializeFromTypeDef(caller->arrayElementType); @@ -2960,6 +3095,18 @@ HRESULT CLR_RT_TypeDescriptor::InitializeFromSignatureToken( { // !!U: method-generic parameter — resolve from the caller's MethodSpec if available, // which gives the concrete closed type (e.g. String for BuildListAndCount). + if (caller != nullptr && NANOCLR_INDEX_IS_VALID(caller->arrayElementType) && + elem.GenericParamPosition == 0) + { + this->InitializeFromTypeDef(caller->arrayElementType); + if (elem.Levels > 0) + { + m_reflex.levels = elem.Levels; + ConvertToArray(); + } + break; + } + if (caller != nullptr && NANOCLR_INDEX_IS_VALID(caller->methodSpec)) { CLR_RT_MethodSpec_Instance msInst; @@ -3017,7 +3164,9 @@ HRESULT CLR_RT_TypeDescriptor::InitializeFromSignatureToken( // full generic instantiation: parse it directly from the signature // Pass caller's generic type as context to resolve VAR parameters in the generic arguments const CLR_RT_TypeSpec_Index *contextTypeSpec = - (caller && NANOCLR_INDEX_IS_VALID(*caller->genericType)) ? caller->genericType : nullptr; + (caller && caller->genericType && NANOCLR_INDEX_IS_VALID(*caller->genericType)) + ? caller->genericType + : nullptr; this->InitializeFromSignatureParser(parser, contextTypeSpec); } else @@ -3025,6 +3174,12 @@ HRESULT CLR_RT_TypeDescriptor::InitializeFromSignatureToken( // e.g. SZARRAY, VALUETYPE, CLASS this->InitializeFromSignatureParser(parser); } + + if ((elem.DataType == DATATYPE_VAR || elem.DataType == DATATYPE_MVAR) && elem.Levels > 0) + { + m_reflex.levels = elem.Levels; + ConvertToArray(); + } break; } @@ -3165,8 +3320,10 @@ HRESULT CLR_RT_TypeDescriptor::InitializeFromObject(const CLR_RT_HeapBlock &ref) NANOCLR_SET_AND_LEAVE(InitializeFromObject(*obj)); } - reflex = &array->ReflectionDataConst(); - cls = &reflex->data.type; + // For a byref to an element of a primitive/value array, report the element type only. + // Carrying the array reflection data here would make the actual type look like T[] + // instead of T during ldelem.any validation. + cls = &array->ReflectionDataConst().data.type; } break; @@ -4361,6 +4518,46 @@ HRESULT CLR_RT_Assembly::ResolveMethodRef() dst->genericType.data = typeSpec.data; } + if (!fGot && NANOCLR_INDEX_IS_VALID(typeSpecInstance.genericTypeDef)) + { + CLR_RT_TypeDef_Instance typeDefInst{}; + if (typeDefInst.InitializeFromIndex(typeSpecInstance.genericTypeDef)) + { + while (NANOCLR_INDEX_IS_VALID(typeDefInst)) + { + if (typeDefInst.assembly + ->FindMethodDef(typeDefInst.target, methodName, this, src->signature, dst->target)) + { + fGot = true; + dst->genericType.data = typeSpec.data; + break; + } + + typeDefInst.SwitchToParent(); + } + } + } + + if (!fGot && NANOCLR_INDEX_IS_VALID(typeSpecInstance.genericTypeDef)) + { + CLR_RT_TypeDef_Instance typeDefInst{}; + if (typeDefInst.InitializeFromIndex(typeSpecInstance.genericTypeDef)) + { + while (NANOCLR_INDEX_IS_VALID(typeDefInst)) + { + if (typeDefInst.assembly + ->FindMethodDef(typeDefInst.target, methodName, this, CLR_SIG_INVALID, dst->target)) + { + fGot = true; + dst->genericType.data = typeSpec.data; + break; + } + + typeDefInst.SwitchToParent(); + } + } + } + if (fGot == false) { #if !defined(BUILD_RTM) @@ -5816,6 +6013,19 @@ CLR_RT_HeapBlock *CLR_RT_Assembly::GetStaticFieldByFieldDef( { CLR_RT_HeapBlock *hb = GetGenericStaticField(*genericType, fdIndex); +#if defined(NANOCLR_TRACE_GENERICS) + if (s_CLR_RT_fTrace_GenericFields >= c_CLR_RT_Trace_Verbose) + { + CLR_Debug::Printf( + "GenericStatic: GetStaticFieldByFieldDef ts=[%04X:%04X] fd=[%04X:%04X] hb=%08X\r\n", + genericType->Assembly(), + genericType->TypeSpec(), + fdIndex.Assembly(), + fdIndex.Field(), + (uintptr_t)hb); + } +#endif + if (hb != nullptr) { return hb; @@ -5850,6 +6060,12 @@ CLR_RT_HeapBlock *CLR_RT_Assembly::GetStaticFieldByFieldDef( } } } + + // Generic field not found in the per-TypeSpec store (even after on-demand allocation). + // Do NOT fall through to the assembly static field offset: generic static fields have no + // assembly-level storage slot (fdCross.offset == CLR_EmptyIndex), so the fallback would + // hit the assert below. Return nullptr and let the caller handle the miss. + return nullptr; } // fallback to assembly static fields (use offset stored on crossReferenceFieldDef) @@ -5857,6 +6073,15 @@ CLR_RT_HeapBlock *CLR_RT_Assembly::GetStaticFieldByFieldDef( _ASSERTE(fdCross.offset != CLR_EmptyIndex); +#if defined(NANOCLR_TRACE_GENERICS) + if (s_CLR_RT_fTrace_GenericFields >= c_CLR_RT_Trace_Info) + { + CLR_Debug::Printf( + "GenericStatic: GetStaticFieldByFieldDef fallback to assembly static offset=%u\r\n", + fdCross.offset); + } +#endif + #if defined(NANOCLR_APPDOMAINS) return GetStaticField(fdCross.offset); // existing method that uses current AppDomain's m_pStaticFields #else @@ -5996,6 +6221,18 @@ HRESULT CLR_RT_Assembly::AllocateGenericStaticFieldsOnDemand( // Initialize the storage using the field definition const CLR_RECORD_FIELDDEF *pFd = ownerAsm->GetFieldDef(fieldIndex); + +#if defined(NANOCLR_TRACE_GENERICS) + if (s_CLR_RT_fTrace_GenericFields >= c_CLR_RT_Trace_Verbose) + { + CLR_Debug::Printf( + "GenericStatic: AllocateGenericStaticFieldsOnDemand field[%u]='%s' ptr=%08X\r\n", + i, + ownerAsm->GetString(pFd->name), + (uintptr_t)&fields[i]); + } +#endif + g_CLR_RT_ExecutionEngine.InitializeReference(fields[i], pFd, ownerAsm); } @@ -6187,6 +6424,7 @@ bool CLR_RT_Assembly::FindTypeDef(const char *typeName, const char *nameSpace, C int tblSize = tablesSize[TBL_TypeDef]; bool isNestedType = false; std::string extractedNamespace; + std::string enclosedTypeName; // Check if typeName contains '/' const char *slashPos = strchr(typeName, '/'); @@ -6197,7 +6435,6 @@ bool CLR_RT_Assembly::FindTypeDef(const char *typeName, const char *nameSpace, C // Extract the enclosed type name from the '/' backwards to the '.' before const char *dotPos = strrchr(typeName, '.'); - std::string enclosedTypeName; if (dotPos != nullptr) { @@ -6228,10 +6465,24 @@ bool CLR_RT_Assembly::FindTypeDef(const char *typeName, const char *nameSpace, C { const char *szName = GetString(target->name); - // for nested types, there is no namespace encoded in the type - // looking at the type name only, does look a bit flaky but it will have to work for now if (!strcmp(szName, typeName)) { + // When the caller encoded the full path as "Namespace.OuterType/NestedType", + // we extracted enclosedTypeName (e.g. "List`1"). Verify the enclosing type's + // name matches so that two generic types with identically-named nested types + // (e.g. List`1/Enumerator vs Dictionary`2/Enumerator) are correctly + // distinguished. + if (!enclosedTypeName.empty() && target->EnclosingType() == TBL_TypeDef) + { + CLR_INDEX enclosingIdx = target->EnclosingTypeIndex(); + const CLR_RECORD_TYPEDEF *enclosingTD = GetTypeDef(enclosingIdx); + const char *enclosingName = GetString(enclosingTD->name); + if (strcmp(enclosingName, enclosedTypeName.c_str()) != 0) + { + continue; + } + } + index.Set(assemblyIndex, i); return true; } @@ -6267,6 +6518,17 @@ bool CLR_RT_Assembly::FindTypeDef(const char *typeName, CLR_INDEX scope, CLR_RT_ continue; } + // The caller passes a TypeDef index for the enclosing type. Make sure we only + // compare against nested types whose EnclosingType token also points to the + // TypeDef table. Ignoring the table kind allows accidental matches against + // nested types encoded with a TypeRef parent whose row number happens to equal + // the requested TypeDef index, which can resolve to the wrong nested generic + // type (for example List`1/Enumerator vs Dictionary`2/Enumerator). + if (target->EnclosingType() != TBL_TypeDef) + { + continue; + } + CLR_INDEX enclosingTypeIndex = target->EnclosingTypeIndex(); if (enclosingTypeIndex == scope) @@ -6893,6 +7155,32 @@ void CLR_RT_Assembly::Relocate() CLR_RT_GarbageCollector::RelocateGenericStaticField(&g_CLR_RT_TypeSystem.m_genericStaticFields[i]); } + // Sync all TypeSpec cross-reference caches that point into generic static field arrays. + // These caches (tsCross->genericStaticFields) are raw copies of record->m_fields stored in + // platform_malloc'd memory, so they are NOT updated by the GC relocation above. Refresh them + // from the now-updated m_genericStaticFields records. + for (CLR_UINT32 i = 0; i < g_CLR_RT_TypeSystem.m_genericStaticFieldsCount; i++) + { + const CLR_RT_GenericStaticFieldRecord &record = g_CLR_RT_TypeSystem.m_genericStaticFields[i]; + + // Walk every assembly, every TypeSpec cross-reference, and update the cache if it was + // pointing at this record's old field block. + NANOCLR_FOREACH_ASSEMBLY(g_CLR_RT_TypeSystem) + { + for (int tsIdx = 0; tsIdx < pASSM->tablesSize[TBL_TypeSpec]; tsIdx++) + { + CLR_RT_TypeSpec_CrossReference &tsCross = pASSM->crossReferenceTypeSpec[tsIdx]; + + if (tsCross.genericStaticFields != nullptr && tsCross.genericStaticFieldsCount == record.m_count && + tsCross.genericStaticFieldDefs == record.m_fieldDefs) + { + tsCross.genericStaticFields = record.m_fields; + } + } + } + NANOCLR_FOREACH_ASSEMBLY_END(); + } + CLR_RT_GarbageCollector::Heap_Relocate((void **)&header); CLR_RT_GarbageCollector::Heap_Relocate((void **)&name); CLR_RT_GarbageCollector::Heap_Relocate((void **)&file); @@ -7825,25 +8113,33 @@ bool CLR_RT_TypeSystem::MatchSignatureElement( if (resLeft.DataType == DATATYPE_GENERICINST && resRight.DataType == DATATYPE_GENERICINST) { - // processing generic instance signature - // need to advance to get generic type and param count + // Advance past GENERICINST to read the actual generic type definition (CLASS/VALUETYPE + // token) and the total generic argument count. if (FAILED(parserLeft.Advance(resLeft)) || FAILED(parserRight.Advance(resRight))) { return false; } - // need to check if type of generic parameters match, if there are more - if (resLeft.GenParamCount > 0 && resRight.GenParamCount > 0) + // The generic type definitions must be identical. Without this check two distinct + // nested types that happen to share the same argument count and argument types (e.g. + // List.Enumerator vs Stack.Enumerator) would be incorrectly treated as the + // same TypeSpec, causing FindTypeSpec to return the wrong entry. + if (resLeft.Class.data != resRight.Class.data) { - if (resLeft.GenParamCount != resRight.GenParamCount) - { - return false; - } + return false; + } - if (resLeft.DataType != resRight.DataType) - { - return false; - } + // Argument counts must match unconditionally (not only when both are > 0, which would + // silently skip the check when one side has zero arguments). + if (resLeft.GenParamCount != resRight.GenParamCount) + { + return false; + } + + // CLASS vs VALUETYPE must also agree. + if (resLeft.DataType != resRight.DataType) + { + return false; } } else @@ -8362,6 +8658,63 @@ HRESULT CLR_RT_TypeSystem::BuildMethodName( NANOCLR_NOCLEANUP(); } +// Overload that accepts an explicit parent context TypeSpec for resolving open VAR parameters +// in mdInst.genericType. When the method's declaring type has open generic params (e.g. +// List`1+Enumerator), parentCtx supplies the closed type (e.g. List) so that +// !0 is printed as "String" rather than "!0". +HRESULT CLR_RT_TypeSystem::BuildMethodName( + const CLR_RT_MethodDef_Instance &mdInst, + const CLR_RT_TypeSpec_Index *genericType, + const CLR_RT_TypeSpec_Index *parentCtx, + char *&szBuffer, + size_t &iBuffer) +{ + NATIVE_PROFILE_CLR_CORE(); + NANOCLR_HEADER(); + + CLR_RT_TypeDef_Instance instOwner{}; + bool useGeneric = (mdInst.genericType != nullptr && NANOCLR_INDEX_IS_VALID(*mdInst.genericType)); + + // If there is no useful parent context, fall back to the two-arg overload. + if (parentCtx == nullptr || !NANOCLR_INDEX_IS_VALID(*parentCtx)) + { + NANOCLR_CHECK_HRESULT(BuildMethodName(mdInst, genericType, szBuffer, iBuffer)); + NANOCLR_NOCLEANUP_NOLABEL(); + } + + if (!useGeneric) + { + // Non-generic method: print owning type name + method name the standard way. + if (!instOwner.InitializeFromMethod(mdInst)) + { + NANOCLR_SET_AND_LEAVE(CLR_E_WRONG_TYPE); + } + NANOCLR_CHECK_HRESULT(BuildTypeName(instOwner, szBuffer, iBuffer)); + CLR_SafeSprintf(szBuffer, iBuffer, "::%s", mdInst.assembly->GetString(mdInst.target->name)); + NANOCLR_NOCLEANUP_NOLABEL(); + } + + // Generic method: build the declaring type name using parentCtx to resolve open VARs. + // e.g. mdInst.genericType = List+Enumerator, parentCtx = List + // → BuildTypeName resolves VAR0 via parentCtx → "List+Enumerator" + if (FAILED(BuildTypeName(*mdInst.genericType, szBuffer, iBuffer, 0, parentCtx, &mdInst))) + { + // Fall back: render without parent context. + if (FAILED(BuildTypeName(*mdInst.genericType, szBuffer, iBuffer, 0, mdInst.genericType, &mdInst))) + { + if (!instOwner.InitializeFromMethod(mdInst)) + { + NANOCLR_SET_AND_LEAVE(CLR_E_WRONG_TYPE); + } + NANOCLR_CHECK_HRESULT(BuildTypeName(instOwner, szBuffer, iBuffer)); + } + } + + CLR_SafeSprintf(szBuffer, iBuffer, "::%s", mdInst.assembly->GetString(mdInst.target->name)); + + NANOCLR_NOCLEANUP(); +} + HRESULT CLR_RT_TypeSystem::BuildFieldName(const CLR_RT_FieldDef_Index &fd, char *&szBuffer, size_t &iBuffer) { NATIVE_PROFILE_CLR_CORE(); @@ -8629,6 +8982,16 @@ bool CLR_RT_TypeSystem::FindVirtualMethodDef( if (FindVirtualMethodDef(cls, calleeMD, rgBuffer, index)) return true; + + // For generic interfaces BuildTypeName() produces the backtick form + // (e.g. "ICollection`1.Remove") which never matches the stored explicit + // implementation name (e.g. "ICollection>.Remove"). + // Do a suffix-only pass: this accepts only methods whose name ends with + // "." (explicit interface implementations) while rejecting + // regular virtual methods whose name is exactly calleeName. This ensures + // ICollection>.Remove is chosen over Dictionary.Remove(TKey). + if (FindVirtualMethodDef(cls, calleeMD, calleeName, index, /*suffixMatchOnly=*/true)) + return true; } if (FindVirtualMethodDef(cls, calleeMD, calleeName, index)) @@ -8640,11 +9003,79 @@ bool CLR_RT_TypeSystem::FindVirtualMethodDef( return false; } +// Signature matching for virtual method dispatch. Identical to MatchSignatureDirect +// except that it accepts a DATATYPE_VAR on one side against DATATYPE_GENERICINST on +// the other. This covers interface explicit-implementation lookup where the interface +// method uses generic type parameter T (VAR N) while the concrete implementation uses +// a closed generic instance (e.g. KeyValuePair). The inner sub-elements +// of the GENERICINST are drained from the parser so Available() stays consistent. +static bool MatchSignatureForVirtualDispatch(CLR_RT_SignatureParser &parserLeft, CLR_RT_SignatureParser &parserRight) +{ + if (parserLeft.Type != parserRight.Type) + return false; + if (parserLeft.Flags != parserRight.Flags) + return false; + + while (true) + { + int iAvailLeft = parserLeft.Available(); + int iAvailRight = parserRight.Available(); + + if (iAvailLeft != iAvailRight) + return false; + if (!iAvailLeft) + return true; + + CLR_RT_SignatureParser::Element resLeft; + if (FAILED(parserLeft.Advance(resLeft))) + return false; + + CLR_RT_SignatureParser::Element resRight; + if (FAILED(parserRight.Advance(resRight))) + return false; + + // Relaxed VAR <-> GENERICINST matching for interface virtual dispatch: + // the interface method may use a type parameter (VAR N) while the concrete + // implementation uses the expanded generic type (GENERICINST ...). Drain the + // GENERICINST inner elements so the parser position stays consistent. + if (resLeft.DataType == DATATYPE_VAR && resRight.DataType == DATATYPE_GENERICINST) + { + CLR_RT_SignatureParser::Element inner{}; + if (FAILED(parserRight.Advance(inner))) + return false; // type element (CLASS/VALUETYPE + TypeRef) + for (int i = 0; i < inner.GenParamCount; i++) + { + if (FAILED(parserRight.Advance(inner))) + return false; + } + continue; + } + + if (resLeft.DataType == DATATYPE_GENERICINST && resRight.DataType == DATATYPE_VAR) + { + CLR_RT_SignatureParser::Element inner{}; + if (FAILED(parserLeft.Advance(inner))) + return false; + for (int i = 0; i < inner.GenParamCount; i++) + { + if (FAILED(parserLeft.Advance(inner))) + return false; + } + continue; + } + + // Fall back to the standard element matcher for everything else. + if (!CLR_RT_TypeSystem::MatchSignatureElement(resLeft, resRight, parserLeft, parserRight, false)) + return false; + } +} + bool CLR_RT_TypeSystem::FindVirtualMethodDef( const CLR_RT_TypeDef_Index &cls, const CLR_RT_MethodDef_Index &calleeMD, const char *calleeName, - CLR_RT_MethodDef_Index &index) + CLR_RT_MethodDef_Index &index, + bool suffixMatchOnly) { NATIVE_PROFILE_CLR_CORE(); CLR_RT_TypeDef_Instance clsInst{}; @@ -8678,14 +9109,34 @@ bool CLR_RT_TypeSystem::FindVirtualMethodDef( { const char *targetName = targetAssm->GetString(targetMDR->name); - if (!strcmp(targetName, calleeName)) + // Match by exact name OR by suffix "." (explicit interface implementation). + // Explicit implementations store the full qualified interface name, e.g. + // "System.Collections.Generic.IEnumerable>.GetEnumerator" + // while the runtime looks up the short name "GetEnumerator" or a backtick-form + // "System.Collections.Generic.IEnumerable`1.GetEnumerator". The suffix check + // handles both forms uniformly. + size_t targetLen = hal_strlen_s(targetName); + size_t calleeLen = hal_strlen_s(calleeName); + // When suffixMatchOnly=true only explicit interface implementations are + // considered. Exact short-name matches (e.g. "Remove" == "Remove") are + // skipped so that Dictionary.Remove(TKey) does not shadow the explicit + // impl ICollection>.Remove. + bool nameMatch = (!suffixMatchOnly && !strcmp(targetName, calleeName)); + if (!nameMatch && targetLen > calleeLen + 1) + { + // Check whether targetName ends with "." + calleeName + const char *suffix = targetName + targetLen - calleeLen; + nameMatch = (suffix[-1] == '.' && !strcmp(suffix, calleeName)); + } + + if (nameMatch) { CLR_RT_SignatureParser parserLeft{}; parserLeft.Initialize_MethodSignature(&calleeInst); CLR_RT_SignatureParser parserRight{}; parserRight.Initialize_MethodSignature(targetAssm, targetMDR); - if (CLR_RT_TypeSystem::MatchSignature(parserLeft, parserRight)) + if (MatchSignatureForVirtualDispatch(parserLeft, parserRight)) { index.Set(targetAssm->assemblyIndex, i + targetTDR->firstMethod); diff --git a/src/CLR/Diagnostics/Diagnostics_stub.cpp b/src/CLR/Diagnostics/Diagnostics_stub.cpp index 1cd6046570..032a78b2c7 100644 --- a/src/CLR/Diagnostics/Diagnostics_stub.cpp +++ b/src/CLR/Diagnostics/Diagnostics_stub.cpp @@ -146,8 +146,10 @@ __nfweak void CLR_RT_Assembly::DumpOpcodeDirect( CLR_RT_MethodDef_Instance &call, CLR_PMETADATA ip, CLR_PMETADATA ipStart, - int pid) + int pid, + const CLR_RT_TypeSpec_Index *parentCtx) { + (void)parentCtx; NATIVE_PROFILE_CLR_DIAGNOSTICS(); } @@ -201,6 +203,18 @@ __nfweak void CLR_RT_DUMP::METHOD(const CLR_RT_MethodDef_Instance &mdInst, const NATIVE_PROFILE_CLR_DIAGNOSTICS(); } +__nfweak void CLR_RT_DUMP::METHOD( + const CLR_RT_MethodDef_Instance &mdInst, + const CLR_RT_TypeSpec_Index *genericType, + const CLR_RT_TypeSpec_Index *parentCtx) +{ + (void)mdInst; + (void)genericType; + (void)parentCtx; + + NATIVE_PROFILE_CLR_DIAGNOSTICS(); +} + __nfweak void CLR_RT_DUMP::FIELD(const CLR_RT_FieldDef_Index &field) { (void)field; diff --git a/src/CLR/Diagnostics/Info.cpp b/src/CLR/Diagnostics/Info.cpp index 1a9869c7a0..9caa44efab 100644 --- a/src/CLR/Diagnostics/Info.cpp +++ b/src/CLR/Diagnostics/Info.cpp @@ -961,6 +961,27 @@ void CLR_RT_Assembly::DumpSignatureToken(const CLR_UINT8 *&p) //--// +// +// Returns true only when 'p' is a non-null pointer that lies within the process's +// user-mode address space. On 64-bit Windows/Linux user-mode addresses are always +// below 2^47 (0x0000_8000_0000_0000); anything at or above that range (e.g. the +// all-ones 0xFFFF_FFFF_FFFF_FFFF from a dangling calleeInst.m_typeSpecStorage) is a +// kernel / guard / garbage address and must not be dereferenced. +// +static inline bool IsUserModePtr(const void *p) +{ + if (p == nullptr) + return false; + +#if defined(_WIN64) || (defined(__GNUC__) && defined(__x86_64__)) + // On 64-bit: user-space tops out at canonical address boundary 0x00007FFF_FFFF_FFFF + return (reinterpret_cast(p) < 0x0000800000000000ULL); +#else + // On 32-bit: any non-null pointer is potentially valid; let the OS fault on a bad one. + return true; +#endif +} + void CLR_RT_Assembly::DumpOpcode(CLR_RT_StackFrame *stack, CLR_PMETADATA ip) { NATIVE_PROFILE_CLR_DIAGNOSTICS(); @@ -968,6 +989,7 @@ void CLR_RT_Assembly::DumpOpcode(CLR_RT_StackFrame *stack, CLR_PMETADATA ip) return; CLR_RT_MethodDef_Instance inst; + const CLR_RT_TypeSpec_Index *parentCtx = nullptr; if (s_CLR_RT_fTrace_Instructions >= c_CLR_RT_Trace_Verbose) { @@ -976,7 +998,7 @@ void CLR_RT_Assembly::DumpOpcode(CLR_RT_StackFrame *stack, CLR_PMETADATA ip) // When the current frame has an open generic type (contains MVAR) but no MethodSpec, // inherit the caller frame's MethodSpec so BuildTypeName can resolve the MVAR to its // concrete type (e.g. List + caller MethodSpec -> List). - if (!NANOCLR_INDEX_IS_VALID(inst.methodSpec) && inst.genericType != nullptr && + if (!NANOCLR_INDEX_IS_VALID(inst.methodSpec) && IsUserModePtr(inst.genericType) && NANOCLR_INDEX_IS_VALID(*inst.genericType)) { CLR_RT_StackFrame *caller = stack->Caller(); @@ -985,20 +1007,45 @@ void CLR_RT_Assembly::DumpOpcode(CLR_RT_StackFrame *stack, CLR_PMETADATA ip) inst.methodSpec = caller->m_call.methodSpec; } } + + // When the current frame's genericType has open VAR params (e.g. List+Enumerator), + // search caller frames for a closed generic context that can resolve the open params. + // Example: Enumerator.ctor called from List.GetEnumerator() resolves !0 = String. + if (IsUserModePtr(inst.genericType) && NANOCLR_INDEX_IS_VALID(*inst.genericType)) + { + CLR_RT_TypeSpec_Instance tsInst{}; + if (tsInst.InitializeFromIndex(*inst.genericType) && !tsInst.IsClosedGenericType()) + { + for (CLR_RT_StackFrame *f = stack->Caller(); f != nullptr; f = f->Caller()) + { + const CLR_RT_TypeSpec_Index *callerGT = f->m_call.genericType; + if (IsUserModePtr(callerGT) && NANOCLR_INDEX_IS_VALID(*callerGT)) + { + CLR_RT_TypeSpec_Instance callerTs{}; + if (callerTs.InitializeFromIndex(*callerGT) && callerTs.IsClosedGenericType()) + { + parentCtx = callerGT; + break; + } + } + } + } + } } else { - inst.Clear(); + inst.ClearInstance(); } - DumpOpcodeDirect(inst, ip, stack->m_IPstart, stack->m_owningThread->m_pid); + DumpOpcodeDirect(inst, ip, stack->m_IPstart, stack->m_owningThread->m_pid, parentCtx); } void CLR_RT_Assembly::DumpOpcodeDirect( CLR_RT_MethodDef_Instance &call, CLR_PMETADATA ip, CLR_PMETADATA ipStart, - int pid) + int pid, + const CLR_RT_TypeSpec_Index *parentCtx) { NATIVE_PROFILE_CLR_DIAGNOSTICS(); CLR_Debug::Printf(" [%04x:%04x", pid, (int)(ip - ipStart)); @@ -1006,7 +1053,7 @@ void CLR_RT_Assembly::DumpOpcodeDirect( if (NANOCLR_INDEX_IS_VALID(call)) { CLR_Debug::Printf(":"); - CLR_RT_DUMP::METHOD(call, call.genericType); + CLR_RT_DUMP::METHOD(call, call.genericType, parentCtx); } CLR_OPCODE op = CLR_ReadNextOpcodeCompressed(ip); @@ -1163,6 +1210,21 @@ void CLR_RT_DUMP::METHOD(const CLR_RT_MethodDef_Instance &mdInst, const CLR_RT_T CLR_Debug::Printf("%s", rgBuffer); } +void CLR_RT_DUMP::METHOD( + const CLR_RT_MethodDef_Instance &mdInst, + const CLR_RT_TypeSpec_Index *genericType, + const CLR_RT_TypeSpec_Index *parentCtx) +{ + NATIVE_PROFILE_CLR_DIAGNOSTICS(); + char rgBuffer[512]; + char *szBuffer = rgBuffer; + size_t iBuffer = MAXSTRLEN(rgBuffer); + + g_CLR_RT_TypeSystem.BuildMethodName(mdInst, genericType, parentCtx, szBuffer, iBuffer); + + CLR_Debug::Printf("%s", rgBuffer); +} + void CLR_RT_DUMP::FIELD(const CLR_RT_FieldDef_Index &field) { NATIVE_PROFILE_CLR_DIAGNOSTICS(); diff --git a/src/CLR/Include/nanoCLR_Checks.h b/src/CLR/Include/nanoCLR_Checks.h index 8048974bb1..4a7298f2e1 100644 --- a/src/CLR/Include/nanoCLR_Checks.h +++ b/src/CLR/Include/nanoCLR_Checks.h @@ -28,6 +28,7 @@ struct CLR_RT_DUMP static void TYPE (const CLR_RT_ReflectionDef_Index& reflex ) DECL_POSTFIX; static void METHOD (const CLR_RT_MethodDef_Index& method, const CLR_RT_TypeSpec_Index *genericType) DECL_POSTFIX; static void METHOD (const CLR_RT_MethodDef_Instance& mdInst, const CLR_RT_TypeSpec_Index *genericType) DECL_POSTFIX; + static void METHOD (const CLR_RT_MethodDef_Instance& mdInst, const CLR_RT_TypeSpec_Index *genericType, const CLR_RT_TypeSpec_Index *parentCtx) DECL_POSTFIX; static void FIELD (const CLR_RT_FieldDef_Index& field ) DECL_POSTFIX; static void OBJECT ( CLR_RT_HeapBlock* ptr , const char* text ) DECL_POSTFIX; static void METHODREF (const CLR_RT_MethodRef_Index& method ) DECL_POSTFIX; diff --git a/src/CLR/Include/nanoCLR_PlatformDef.h b/src/CLR/Include/nanoCLR_PlatformDef.h index 443f9bb5f3..df63020878 100644 --- a/src/CLR/Include/nanoCLR_PlatformDef.h +++ b/src/CLR/Include/nanoCLR_PlatformDef.h @@ -1,4 +1,4 @@ -// +// // Copyright (c) .NET Foundation and Contributors // Portions Copyright (c) Microsoft Corporation. All rights reserved. // See LICENSE file in the project root for full license information. @@ -109,6 +109,7 @@ #undef NANOCLR_TRACE_EXCEPTIONS #undef NANOCLR_TRACE_ERRORS #undef NANOCLR_TRACE_EARLYCOLLECTION +#undef NANOCLR_TRACE_GENERICS #undef NANOCLR_VALIDATE_HEAP #undef NANOCLR_FILL_MEMORY_WITH_DIRTY_PATTERN #endif diff --git a/src/CLR/Include/nanoCLR_Runtime.h b/src/CLR/Include/nanoCLR_Runtime.h index d9cf06d894..0079e5d149 100644 --- a/src/CLR/Include/nanoCLR_Runtime.h +++ b/src/CLR/Include/nanoCLR_Runtime.h @@ -575,6 +575,10 @@ extern int s_CLR_RT_fTrace_GC_Depth; extern int s_CLR_RT_fTrace_SimulateSpeed; extern int s_CLR_RT_fTrace_AssemblyOverhead; +#if defined(NANOCLR_TRACE_GENERICS) +extern int s_CLR_RT_fTrace_GenericFields; +#endif + #if defined(VIRTUAL_DEVICE) extern int s_CLR_RT_fTrace_ARM_Execution; @@ -1215,6 +1219,14 @@ struct CLR_RT_SignatureParser /// @brief Index into MetodDef table CLR_INDEX Method; + /// @brief When true the next CLASS/VALUETYPE element must read its arg-count byte unconditionally. + /// Set by DATATYPE_GENERICINST and cleared after the following class element is consumed. + /// This is required for nested generic types (e.g. List.Enumerator) whose TypeDef has + /// genericParamCount == 0 (no own generic params) even though the metadata processor writes + /// cumulative enclosing-type arg counts into the signature. + /// Always reset to false by every Initialize_* function. + bool m_pendingGenericInst; + //--// void Initialize_TypeSpec(CLR_RT_Assembly *assm, CLR_PMETADATA ts); @@ -1589,8 +1601,12 @@ struct CLR_RT_Assembly : public CLR_RT_HeapBlock_Node // EVENT HEAP - NO RELOCAT public: void DumpOpcode(CLR_RT_StackFrame *stack, CLR_PMETADATA ip) DECL_POSTFIX; - void DumpOpcodeDirect(CLR_RT_MethodDef_Instance &call, CLR_PMETADATA ip, CLR_PMETADATA ipStart, int pid) - DECL_POSTFIX; + void DumpOpcodeDirect( + CLR_RT_MethodDef_Instance &call, + CLR_PMETADATA ip, + CLR_PMETADATA ipStart, + int pid, + const CLR_RT_TypeSpec_Index *parentCtx = nullptr) DECL_POSTFIX; private: void DumpToken( @@ -2099,6 +2115,12 @@ struct CLR_RT_TypeSystem // EVENT HEAP - NO RELOCATION - const CLR_RT_TypeSpec_Index *genericType, char *&szBuffer, size_t &size); + HRESULT BuildMethodName( + const CLR_RT_MethodDef_Instance &mdInst, + const CLR_RT_TypeSpec_Index *genericType, + const CLR_RT_TypeSpec_Index *parentCtx, + char *&szBuffer, + size_t &size); HRESULT BuildFieldName(const CLR_RT_FieldDef_Index &fd, char *&szBuffer, size_t &size); HRESULT BuildMethodRefName(const CLR_RT_MethodRef_Index &method, char *&szBuffer, size_t &iBuffer); HRESULT BuildMethodRefName( @@ -2123,7 +2145,8 @@ struct CLR_RT_TypeSystem // EVENT HEAP - NO RELOCATION - const CLR_RT_TypeDef_Index &cls, const CLR_RT_MethodDef_Index &calleeMD, const char *calleeName, - CLR_RT_MethodDef_Index &index); + CLR_RT_MethodDef_Index &index, + bool suffixMatchOnly = false); static bool MatchSignature(CLR_RT_SignatureParser &parserLeft, CLR_RT_SignatureParser &parserRight); static bool MatchSignatureDirect( @@ -3143,7 +3166,13 @@ struct CLR_RT_GarbageCollector { if (field->m_fields) { + // Relocate the internal pointers within each HeapBlock in the array + // (must be done before updating m_fields, while it still points to the old location) CLR_RT_GarbageCollector::Heap_Relocate(field->m_fields, field->m_count); + + // Update m_fields pointer itself to wherever the block array moved after compaction. + // Without this, m_fields becomes a dangling pointer after any GC compaction. + CLR_RT_GarbageCollector::Heap_Relocate((void **)&field->m_fields); } if (field->m_fieldDefs) diff --git a/src/CLR/Include/nanoCLR_Runtime__HeapBlock.h b/src/CLR/Include/nanoCLR_Runtime__HeapBlock.h index 3d45ace35b..30bec49a1e 100644 --- a/src/CLR/Include/nanoCLR_Runtime__HeapBlock.h +++ b/src/CLR/Include/nanoCLR_Runtime__HeapBlock.h @@ -1,4 +1,4 @@ -// +// // Copyright (c) .NET Foundation and Contributors // Portions Copyright (c) Microsoft Corporation. All rights reserved. // See LICENSE file in the project root for full license information. diff --git a/targets/TI_SimpleLink/TI_CC1352R1_LAUNCHXL/common/Device_BlockStorage.c b/targets/TI_SimpleLink/TI_CC1352R1_LAUNCHXL/common/Device_BlockStorage.c index b73b30d075..a9dbc80914 100644 --- a/targets/TI_SimpleLink/TI_CC1352R1_LAUNCHXL/common/Device_BlockStorage.c +++ b/targets/TI_SimpleLink/TI_CC1352R1_LAUNCHXL/common/Device_BlockStorage.c @@ -10,8 +10,8 @@ const BlockRange BlockRange1[] = { // the last block is reserved for Customer Configuration Area and Bootloader Backdoor configuration // so we don't take it into account for the map - {BlockRange_BLOCKTYPE_CODE, 0, 26}, // 0x00000000 nanoCLR - {BlockRange_BLOCKTYPE_DEPLOYMENT, 27, 42}, // 0x00036000 deployment + {BlockRange_BLOCKTYPE_CODE, 0, 27}, // 0x00000000 nanoCLR + {BlockRange_BLOCKTYPE_DEPLOYMENT, 28, 42}, // 0x00038000 deployment }; const BlockRegionInfo BlockRegions[] = {{ diff --git a/targets/TI_SimpleLink/TI_CC1352R1_LAUNCHXL/nanoCLR/CC13x2_26x2_CLR.ld b/targets/TI_SimpleLink/TI_CC1352R1_LAUNCHXL/nanoCLR/CC13x2_26x2_CLR.ld index daa6672a5b..bb492da839 100644 --- a/targets/TI_SimpleLink/TI_CC1352R1_LAUNCHXL/nanoCLR/CC13x2_26x2_CLR.ld +++ b/targets/TI_SimpleLink/TI_CC1352R1_LAUNCHXL/nanoCLR/CC13x2_26x2_CLR.ld @@ -40,7 +40,7 @@ HEAPSIZE = 0x2500; /* Size of heap buffer used by HeapMem */ MEMORY { /* original flash LENGTH was 0x00057fa8 */ - FLASH (RX) : ORIGIN = 0x00000000, LENGTH = 0x00036000 + FLASH (RX) : ORIGIN = 0x00000000, LENGTH = 0x00038000 /* * Customer Configuration Area and Bootloader Backdoor configuration in * flash, 40 bytes