From 022586f92105c674e2698f2418ad74623366e336 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Mon, 7 Jul 2025 15:57:04 +0100 Subject: [PATCH] Fix box/unbox Nullable types - TypeDef_Instance ResolveToken now properly handles TypeSpec tokens for class and value types. - Add ResolveNullableType() to resolve T for Nullable types. - Add Nullable`1 to well known types array. - Add System_Nullable_1 to mscorlib class declaration. - Rework box/unbox handler code to properly deal with nullable types. --- src/CLR/CorLib/corlib_native.h | 8 ++ src/CLR/Core/Interpreter.cpp | 96 +++++++++++++++++++- src/CLR/Core/TypeSystem.cpp | 146 ++++++++++++++++++++++++------ src/CLR/Include/nanoCLR_Runtime.h | 2 + 4 files changed, 223 insertions(+), 29 deletions(-) diff --git a/src/CLR/CorLib/corlib_native.h b/src/CLR/CorLib/corlib_native.h index a133cf247d..0024bd5eaf 100644 --- a/src/CLR/CorLib/corlib_native.h +++ b/src/CLR/CorLib/corlib_native.h @@ -644,6 +644,14 @@ struct Library_corlib_native_System_MulticastDelegate //--// }; +struct Library_corlib_native_System_Nullable_1 +{ + static const int FIELD__hasValue = 1; + static const int FIELD__value = 2; + + //--// +}; + struct Library_corlib_native_System_Number { NANOCLR_NATIVE_DECLARE(FormatNative___STATIC__STRING__OBJECT__BOOLEAN__STRING__STRING__STRING__STRING__SZARRAY_I4); diff --git a/src/CLR/Core/Interpreter.cpp b/src/CLR/Core/Interpreter.cpp index ba41d2ff27..d2bafe8ce5 100644 --- a/src/CLR/Core/Interpreter.cpp +++ b/src/CLR/Core/Interpreter.cpp @@ -5,6 +5,8 @@ // #include "Core.h" +typedef Library_corlib_native_System_Nullable_1 Sys_Nullable; + //////////////////////////////////////////////////////////////////////////////////////////////////// #if defined(NANOCLR_TRACE_EXCEPTIONS) && defined(VIRTUAL_DEVICE) @@ -2802,13 +2804,101 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) UPDATESTACK(stack, evalPos); + // check if value is a nullable type + bool hasValue = false; + CLR_RT_HeapBlock *nullableValue = nullptr; + bool valueIsNullableType = false; + bool tokenIsNullableType = false; + CLR_RT_TypeDef_Instance destinationType; + + valueIsNullableType = + CLR_RT_ExecutionEngine::IsInstanceOf(evalPos[0], g_CLR_RT_WellKnownTypes.Nullable); + tokenIsNullableType = + CLR_RT_ExecutionEngine::IsInstanceOf(typeInst, g_CLR_RT_WellKnownTypes.Nullable); + + // resolve the T to box to / unbox from + if (tokenIsNullableType && destinationType.ResolveNullableType(arg, assm, &stack->m_call) == false) + { + NANOCLR_SET_AND_LEAVE(CLR_E_WRONG_TYPE); + } + + if (valueIsNullableType) + { + // check HasValue property + nullableValue = evalPos[0].Dereference(); + hasValue = nullableValue[Library_corlib_native_System_Nullable_1::FIELD__hasValue] + .NumericByRefConst() + .u1; + } + if (op == CEE_BOX) { - NANOCLR_CHECK_HRESULT(evalPos[0].PerformBoxing(typeInst)); + if (tokenIsNullableType) + { + if (!hasValue) + { + // box a null reference + evalPos[0].SetObjectReference(nullptr); + } + else + { + // reach the value to box + CLR_RT_HeapBlock &value = + nullableValue[Library_corlib_native_System_Nullable_1::FIELD__value]; + + // box the value + NANOCLR_CHECK_HRESULT(value.PerformBoxing(destinationType)); + + // assign the boxed result back to the evaluation stack + evalPos[0].Assign(value); + } + } + else + { + NANOCLR_CHECK_HRESULT(evalPos[0].PerformBoxing(typeInst)); + } } else { - NANOCLR_CHECK_HRESULT(evalPos[0].PerformUnboxing(typeInst)); + if (tokenIsNullableType) + { + // create a Nullable... + CLR_RT_HeapBlock nullableObject; + CLR_RT_TypeSpec_Index destinationTypeSpec; + destinationTypeSpec.data = arg; + + NANOCLR_CHECK_HRESULT(g_CLR_RT_ExecutionEngine.NewGenericInstanceObject( + nullableObject, + typeInst, + destinationTypeSpec)); + + CLR_RT_ProtectFromGC gc(nullableObject); + + if (evalPos[0].Dereference() == nullptr) + { + // assign a null reference (already carried out by NewObjectFromIndex) + // set HasValue to false + nullableObject.Dereference()[Sys_Nullable::FIELD__hasValue].SetBoolean(false); + } + else + { + // unbox the T value... + NANOCLR_CHECK_HRESULT(evalPos[0].PerformUnboxing(destinationType)); + + // assign the copied unboxed value + nullableObject.Dereference()[Sys_Nullable::FIELD__value].Assign(evalPos[0]); + + // set HasValue to true + nullableObject.Dereference()[Sys_Nullable::FIELD__hasValue].SetBoolean(true); + } + + // assign the Nullable object to the evaluation stack + evalPos[0].SetObjectReference(nullableObject.Dereference()); + } + else + { + NANOCLR_CHECK_HRESULT(evalPos[0].PerformUnboxing(typeInst)); + } } break; } @@ -2825,6 +2915,8 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg) // ldobj.) When applied to a reference type, the unbox.any instruction has the same effect as // castclass typeTok. + // TODO: still not handling Nullable types here + CLR_RT_TypeDef_Instance typeInst{}; if (typeInst.ResolveToken(arg, assm, &stack->m_call) == false) { diff --git a/src/CLR/Core/TypeSystem.cpp b/src/CLR/Core/TypeSystem.cpp index d9fc7ea430..e273b180aa 100644 --- a/src/CLR/Core/TypeSystem.cpp +++ b/src/CLR/Core/TypeSystem.cpp @@ -976,34 +976,23 @@ bool CLR_RT_TypeDef_Instance::ResolveToken( parser.Initialize_TypeSpec(assm, ts); CLR_RT_SignatureParser::Element elem; - - // Skip any leading SZARRAY or BYREF - do + if (FAILED(parser.Advance(elem))) { - if (FAILED(parser.Advance(elem))) - { - return false; - } - } while (elem.DataType == DATATYPE_SZARRAY || elem.DataType == DATATYPE_BYREF); + return false; + } - // If this is a closed‐generic instantiation header, peel off the wrapper - if (elem.DataType == DATATYPE_GENERICINST) + if (elem.DataType == DATATYPE_CLASS || elem.DataType == DATATYPE_VALUETYPE) { - // consume the CLASS/VALUETYPE marker - if (FAILED(parser.Advance(elem))) - { - return false; - } - // consume the generic‐definition token itself - if (FAILED(parser.Advance(elem))) - { - return false; - } - // consume the count of generic arguments - if (FAILED(parser.Advance(elem))) - { - return false; - } + // If it's a class or value type, resolve the type + data = elem.Class.data; + assembly = g_CLR_RT_TypeSystem.m_assemblies[elem.Class.Assembly() - 1]; + target = assembly->GetTypeDef(elem.Class.Type()); + +#if defined(NANOCLR_INSTANCE_NAMES) + name = assembly->GetString(target->name); +#endif + + return true; } // walk forward until a VAR (type‐generic) or MVAR (method‐generic) is hit @@ -1045,8 +1034,6 @@ bool CLR_RT_TypeDef_Instance::ResolveToken( } else { - // elem.DataType == DATATYPE_MVAR - // Use the *caller's* bound genericType (Stack, etc.) if (caller == nullptr || caller->genericType == nullptr) { @@ -1060,6 +1047,7 @@ bool CLR_RT_TypeDef_Instance::ResolveToken( data = gp.classTypeDef.data; assembly = g_CLR_RT_TypeSystem.m_assemblies[gp.classTypeDef.Assembly() - 1]; target = assembly->GetTypeDef(gp.classTypeDef.Type()); + return true; } } @@ -1081,6 +1069,109 @@ bool CLR_RT_TypeDef_Instance::ResolveToken( return false; } +bool CLR_RT_TypeDef_Instance::ResolveNullableType( + CLR_UINT32 tk, + CLR_RT_Assembly *assm, + const CLR_RT_MethodDef_Instance *caller) +{ + NATIVE_PROFILE_CLR_CORE(); + + if (assm) + { + CLR_UINT32 index = CLR_DataFromTk(tk); + + if (CLR_TypeFromTk(tk) != TBL_TypeSpec) + { + // not a TypeSpec, so return false + ClearInstance(); + return false; + } + + // Grab the raw signature for the IL token (e.g. !T[], List`1, etc.) + const CLR_RECORD_TYPESPEC *ts = assm->GetTypeSpec(index); + CLR_RT_SignatureParser parser; + parser.Initialize_TypeSpec(assm, ts); + + CLR_RT_SignatureParser::Element elem; + if (FAILED(parser.Advance(elem))) + { + return false; + } + + if (elem.DataType != DATATYPE_VALUETYPE) + { + // If it's not a value type, we can't resolve it as a nullable type + ClearInstance(); + return false; + } + + // move to the next element in the signature + if (FAILED(parser.Advance(elem))) + { + return false; + } + + // If it's a type‐generic slot (!T), resolve against the caller's closed generic + if (elem.DataType == DATATYPE_VAR) + { + int pos = elem.GenericParamPosition; + + // Use the *caller's* bound genericType (Stack, etc.) + if (caller == nullptr || caller->genericType == nullptr) + { + return false; + } + + auto &tsi = *caller->genericType; + CLR_UINT32 closedTsRow = tsi.TypeSpec(); + + CLR_RT_TypeDef_Index realTypeDef; + NanoCLRDataType realDataType; + + // Only call this once to map (e.g. !T→Int32) + caller->assembly->FindGenericParamAtTypeSpec(closedTsRow, (CLR_UINT32)pos, realTypeDef, realDataType); + + // populate this instance + data = realTypeDef.data; + assembly = g_CLR_RT_TypeSystem.m_assemblies[realTypeDef.Assembly() - 1]; + target = assembly->GetTypeDef(realTypeDef.Type()); + + return true; + } + else if (elem.DataType == DATATYPE_MVAR) + { + // Use the *caller's* bound genericType (Stack, etc.) + if (caller == nullptr || caller->genericType == nullptr) + { + return false; + } + + CLR_RT_GenericParam_Index gpIdx; + caller->assembly->FindGenericParamAtMethodDef(*caller, elem.GenericParamPosition, gpIdx); + auto &gp = caller->assembly->crossReferenceGenericParam[gpIdx.GenericParam()]; + + data = gp.classTypeDef.data; + assembly = g_CLR_RT_TypeSystem.m_assemblies[gp.classTypeDef.Assembly() - 1]; + target = assembly->GetTypeDef(gp.classTypeDef.Type()); + + return true; + } + else + { + // If it's a class or value type, resolve the type + data = elem.Class.data; + assembly = g_CLR_RT_TypeSystem.m_assemblies[elem.Class.Assembly() - 1]; + target = assembly->GetTypeDef(elem.Class.Type()); + + return true; + } + } + + ClearInstance(); + + return false; +} + //--// bool CLR_RT_TypeDef_Instance::SwitchToParent() @@ -4293,6 +4384,7 @@ static const TypeIndexLookup c_TypeIndexLookup[] = { TIL("System", "Object", Object), TIL("System", "ValueType", ValueType), TIL("System", "Enum", Enum), + TIL("System", "Nullable`1", Nullable), TIL("System", "AppDomainUnloadedException", AppDomainUnloadedException), TIL("System", "ArgumentNullException", ArgumentNullException), diff --git a/src/CLR/Include/nanoCLR_Runtime.h b/src/CLR/Include/nanoCLR_Runtime.h index f59689d17c..170037ff63 100644 --- a/src/CLR/Include/nanoCLR_Runtime.h +++ b/src/CLR/Include/nanoCLR_Runtime.h @@ -1691,6 +1691,7 @@ struct CLR_RT_WellKnownTypes CLR_RT_TypeDef_Index Object; CLR_RT_TypeDef_Index ValueType; CLR_RT_TypeDef_Index Enum; + CLR_RT_TypeDef_Index Nullable; CLR_RT_TypeDef_Index AppDomainUnloadedException; CLR_RT_TypeDef_Index ArgumentNullException; @@ -2114,6 +2115,7 @@ struct CLR_RT_TypeDef_Instance : public CLR_RT_TypeDef_Index void ClearInstance(); bool ResolveToken(CLR_UINT32 tk, CLR_RT_Assembly *assm, const CLR_RT_MethodDef_Instance *caller = nullptr); + bool ResolveNullableType(CLR_UINT32 tk, CLR_RT_Assembly *assm, const CLR_RT_MethodDef_Instance *caller = nullptr); //--//