Skip to content

Commit ba1247b

Browse files
committed
Fix box/unbox Nullable<T> types
- TypeDef_Instance ResolveToken now properly handles TypeSpec tokens for class and value types. - Add ResolveNullableType() to resolve T for Nullable<T> 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.
1 parent 406fa37 commit ba1247b

File tree

4 files changed

+217
-29
lines changed

4 files changed

+217
-29
lines changed

src/CLR/CorLib/corlib_native.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -644,6 +644,14 @@ struct Library_corlib_native_System_MulticastDelegate
644644
//--//
645645
};
646646

647+
struct Library_corlib_native_System_Nullable_1
648+
{
649+
static const int FIELD__hasValue = 1;
650+
static const int FIELD__value = 2;
651+
652+
//--//
653+
};
654+
647655
struct Library_corlib_native_System_Number
648656
{
649657
NANOCLR_NATIVE_DECLARE(FormatNative___STATIC__STRING__OBJECT__BOOLEAN__STRING__STRING__STRING__STRING__SZARRAY_I4);

src/CLR/Core/Interpreter.cpp

Lines changed: 88 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
//
66
#include "Core.h"
77

8+
typedef Library_corlib_native_System_Nullable_1 Sys_Nullable;
9+
810
////////////////////////////////////////////////////////////////////////////////////////////////////
911

1012
#if defined(NANOCLR_TRACE_EXCEPTIONS) && defined(VIRTUAL_DEVICE)
@@ -2802,13 +2804,95 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg)
28022804

28032805
UPDATESTACK(stack, evalPos);
28042806

2807+
// check if value is a nullable type
2808+
bool hasValue = false;
2809+
CLR_RT_HeapBlock *nullableValue = nullptr;
2810+
bool valueIsNullableType = false;
2811+
bool tokenIsNullableType = false;
2812+
CLR_RT_TypeDef_Instance destinationType;
2813+
2814+
valueIsNullableType =
2815+
CLR_RT_ExecutionEngine::IsInstanceOf(evalPos[0], g_CLR_RT_WellKnownTypes.Nullable);
2816+
tokenIsNullableType =
2817+
CLR_RT_ExecutionEngine::IsInstanceOf(typeInst, g_CLR_RT_WellKnownTypes.Nullable);
2818+
2819+
// resolve the T to box to / unbox from
2820+
if (tokenIsNullableType && destinationType.ResolveNullableType(arg, assm, &stack->m_call) == false)
2821+
{
2822+
NANOCLR_SET_AND_LEAVE(CLR_E_WRONG_TYPE);
2823+
}
2824+
2825+
if (valueIsNullableType)
2826+
{
2827+
// check HasValue property
2828+
nullableValue = evalPos[0].Dereference();
2829+
hasValue = nullableValue[Library_corlib_native_System_Nullable_1::FIELD__hasValue]
2830+
.NumericByRefConst()
2831+
.u1;
2832+
}
2833+
28052834
if (op == CEE_BOX)
28062835
{
2807-
NANOCLR_CHECK_HRESULT(evalPos[0].PerformBoxing(typeInst));
2836+
if (tokenIsNullableType)
2837+
{
2838+
if (!hasValue)
2839+
{
2840+
// box a null reference
2841+
evalPos[0].SetObjectReference(nullptr);
2842+
}
2843+
else
2844+
{
2845+
// reach the value to box
2846+
CLR_RT_HeapBlock &value =
2847+
nullableValue[Library_corlib_native_System_Nullable_1::FIELD__value];
2848+
2849+
// box the value
2850+
NANOCLR_CHECK_HRESULT(value.PerformBoxing(destinationType));
2851+
}
2852+
}
2853+
else
2854+
{
2855+
NANOCLR_CHECK_HRESULT(evalPos[0].PerformBoxing(typeInst));
2856+
}
28082857
}
28092858
else
28102859
{
2811-
NANOCLR_CHECK_HRESULT(evalPos[0].PerformUnboxing(typeInst));
2860+
if (tokenIsNullableType)
2861+
{
2862+
// create a Nullable<T>...
2863+
CLR_RT_HeapBlock nullableObject;
2864+
NANOCLR_CHECK_HRESULT(g_CLR_RT_ExecutionEngine.NewObjectFromIndex(
2865+
nullableObject,
2866+
g_CLR_RT_WellKnownTypes.Nullable));
2867+
2868+
if (evalPos[0].Dereference() == nullptr)
2869+
{
2870+
// assign a null reference (already carried out by NewObjectFromIndex)
2871+
// set HasValue to false
2872+
nullableObject.Dereference()[Sys_Nullable::FIELD__hasValue].SetBoolean(false);
2873+
}
2874+
else
2875+
{
2876+
// unbox the T value...
2877+
NANOCLR_CHECK_HRESULT(evalPos[0].PerformUnboxing(destinationType));
2878+
2879+
CLR_RT_HeapBlock unboxedValue = {};
2880+
unboxedValue.Assign(evalPos[0]);
2881+
2882+
// assign the copied unboxed value
2883+
nullableObject.Dereference()[Sys_Nullable::FIELD__value].Assign(unboxedValue);
2884+
2885+
// set HasValue to true
2886+
nullableObject.Dereference()[Sys_Nullable::FIELD__hasValue].SetBoolean(true);
2887+
}
2888+
2889+
// assign the Nullable<T> object to the evaluation stack
2890+
evalPos[0].SetObjectReference(nullableObject.Dereference());
2891+
}
2892+
else
2893+
{
2894+
NANOCLR_CHECK_HRESULT(evalPos[0].PerformUnboxing(typeInst));
2895+
}
28122896
}
28132897
break;
28142898
}
@@ -2825,6 +2909,8 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg)
28252909
// ldobj.) When applied to a reference type, the unbox.any instruction has the same effect as
28262910
// castclass typeTok.
28272911

2912+
// TODO: still not handling Nullable<T> types here
2913+
28282914
CLR_RT_TypeDef_Instance typeInst{};
28292915
if (typeInst.ResolveToken(arg, assm, &stack->m_call) == false)
28302916
{

src/CLR/Core/TypeSystem.cpp

Lines changed: 119 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -976,34 +976,23 @@ bool CLR_RT_TypeDef_Instance::ResolveToken(
976976
parser.Initialize_TypeSpec(assm, ts);
977977

978978
CLR_RT_SignatureParser::Element elem;
979-
980-
// Skip any leading SZARRAY or BYREF
981-
do
979+
if (FAILED(parser.Advance(elem)))
982980
{
983-
if (FAILED(parser.Advance(elem)))
984-
{
985-
return false;
986-
}
987-
} while (elem.DataType == DATATYPE_SZARRAY || elem.DataType == DATATYPE_BYREF);
981+
return false;
982+
}
988983

989-
// If this is a closed‐generic instantiation header, peel off the wrapper
990-
if (elem.DataType == DATATYPE_GENERICINST)
984+
if (elem.DataType == DATATYPE_CLASS || elem.DataType == DATATYPE_VALUETYPE)
991985
{
992-
// consume the CLASS/VALUETYPE marker
993-
if (FAILED(parser.Advance(elem)))
994-
{
995-
return false;
996-
}
997-
// consume the generic‐definition token itself
998-
if (FAILED(parser.Advance(elem)))
999-
{
1000-
return false;
1001-
}
1002-
// consume the count of generic arguments
1003-
if (FAILED(parser.Advance(elem)))
1004-
{
1005-
return false;
1006-
}
986+
// If it's a class or value type, resolve the type
987+
data = elem.Class.data;
988+
assembly = g_CLR_RT_TypeSystem.m_assemblies[elem.Class.Assembly() - 1];
989+
target = assembly->GetTypeDef(elem.Class.Type());
990+
991+
#if defined(NANOCLR_INSTANCE_NAMES)
992+
name = assembly->GetString(target->name);
993+
#endif
994+
995+
return true;
1007996
}
1008997

1009998
// walk forward until a VAR (type‐generic) or MVAR (method‐generic) is hit
@@ -1045,8 +1034,6 @@ bool CLR_RT_TypeDef_Instance::ResolveToken(
10451034
}
10461035
else
10471036
{
1048-
// elem.DataType == DATATYPE_MVAR
1049-
10501037
// Use the *caller's* bound genericType (Stack<Int32>, etc.)
10511038
if (caller == nullptr || caller->genericType == nullptr)
10521039
{
@@ -1060,6 +1047,7 @@ bool CLR_RT_TypeDef_Instance::ResolveToken(
10601047
data = gp.classTypeDef.data;
10611048
assembly = g_CLR_RT_TypeSystem.m_assemblies[gp.classTypeDef.Assembly() - 1];
10621049
target = assembly->GetTypeDef(gp.classTypeDef.Type());
1050+
10631051
return true;
10641052
}
10651053
}
@@ -1081,6 +1069,109 @@ bool CLR_RT_TypeDef_Instance::ResolveToken(
10811069
return false;
10821070
}
10831071

1072+
bool CLR_RT_TypeDef_Instance::ResolveNullableType(
1073+
CLR_UINT32 tk,
1074+
CLR_RT_Assembly *assm,
1075+
const CLR_RT_MethodDef_Instance *caller)
1076+
{
1077+
NATIVE_PROFILE_CLR_CORE();
1078+
1079+
if (assm)
1080+
{
1081+
CLR_UINT32 index = CLR_DataFromTk(tk);
1082+
1083+
if (CLR_TypeFromTk(tk) != TBL_TypeSpec)
1084+
{
1085+
// not a TypeSpec, so return false
1086+
ClearInstance();
1087+
return false;
1088+
}
1089+
1090+
// Grab the raw signature for the IL token (e.g. !T[], List`1<T>, etc.)
1091+
const CLR_RECORD_TYPESPEC *ts = assm->GetTypeSpec(index);
1092+
CLR_RT_SignatureParser parser;
1093+
parser.Initialize_TypeSpec(assm, ts);
1094+
1095+
CLR_RT_SignatureParser::Element elem;
1096+
if (FAILED(parser.Advance(elem)))
1097+
{
1098+
return false;
1099+
}
1100+
1101+
if (elem.DataType != DATATYPE_VALUETYPE)
1102+
{
1103+
// If it's not a value type, we can't resolve it as a nullable type
1104+
ClearInstance();
1105+
return false;
1106+
}
1107+
1108+
// move to the next element in the signature
1109+
if (FAILED(parser.Advance(elem)))
1110+
{
1111+
return false;
1112+
}
1113+
1114+
// If it's a type‐generic slot (!T), resolve against the caller's closed generic
1115+
if (elem.DataType == DATATYPE_VAR)
1116+
{
1117+
int pos = elem.GenericParamPosition;
1118+
1119+
// Use the *caller's* bound genericType (Stack<Int32>, etc.)
1120+
if (caller == nullptr || caller->genericType == nullptr)
1121+
{
1122+
return false;
1123+
}
1124+
1125+
auto &tsi = *caller->genericType;
1126+
CLR_UINT32 closedTsRow = tsi.TypeSpec();
1127+
1128+
CLR_RT_TypeDef_Index realTypeDef;
1129+
NanoCLRDataType realDataType;
1130+
1131+
// Only call this once to map (e.g. !T→Int32)
1132+
caller->assembly->FindGenericParamAtTypeSpec(closedTsRow, (CLR_UINT32)pos, realTypeDef, realDataType);
1133+
1134+
// populate this instance
1135+
data = realTypeDef.data;
1136+
assembly = g_CLR_RT_TypeSystem.m_assemblies[realTypeDef.Assembly() - 1];
1137+
target = assembly->GetTypeDef(realTypeDef.Type());
1138+
1139+
return true;
1140+
}
1141+
else if (elem.DataType == DATATYPE_MVAR)
1142+
{
1143+
// Use the *caller's* bound genericType (Stack<Int32>, etc.)
1144+
if (caller == nullptr || caller->genericType == nullptr)
1145+
{
1146+
return false;
1147+
}
1148+
1149+
CLR_RT_GenericParam_Index gpIdx;
1150+
caller->assembly->FindGenericParamAtMethodDef(*caller, elem.GenericParamPosition, gpIdx);
1151+
auto &gp = caller->assembly->crossReferenceGenericParam[gpIdx.GenericParam()];
1152+
1153+
data = gp.classTypeDef.data;
1154+
assembly = g_CLR_RT_TypeSystem.m_assemblies[gp.classTypeDef.Assembly() - 1];
1155+
target = assembly->GetTypeDef(gp.classTypeDef.Type());
1156+
1157+
return true;
1158+
}
1159+
else
1160+
{
1161+
// If it's a class or value type, resolve the type
1162+
data = elem.Class.data;
1163+
assembly = g_CLR_RT_TypeSystem.m_assemblies[elem.Class.Assembly() - 1];
1164+
target = assembly->GetTypeDef(elem.Class.Type());
1165+
1166+
return true;
1167+
}
1168+
}
1169+
1170+
ClearInstance();
1171+
1172+
return false;
1173+
}
1174+
10841175
//--//
10851176

10861177
bool CLR_RT_TypeDef_Instance::SwitchToParent()
@@ -4293,6 +4384,7 @@ static const TypeIndexLookup c_TypeIndexLookup[] = {
42934384
TIL("System", "Object", Object),
42944385
TIL("System", "ValueType", ValueType),
42954386
TIL("System", "Enum", Enum),
4387+
TIL("System", "Nullable`1", Nullable),
42964388

42974389
TIL("System", "AppDomainUnloadedException", AppDomainUnloadedException),
42984390
TIL("System", "ArgumentNullException", ArgumentNullException),

src/CLR/Include/nanoCLR_Runtime.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1691,6 +1691,7 @@ struct CLR_RT_WellKnownTypes
16911691
CLR_RT_TypeDef_Index Object;
16921692
CLR_RT_TypeDef_Index ValueType;
16931693
CLR_RT_TypeDef_Index Enum;
1694+
CLR_RT_TypeDef_Index Nullable;
16941695

16951696
CLR_RT_TypeDef_Index AppDomainUnloadedException;
16961697
CLR_RT_TypeDef_Index ArgumentNullException;
@@ -2114,6 +2115,7 @@ struct CLR_RT_TypeDef_Instance : public CLR_RT_TypeDef_Index
21142115
void ClearInstance();
21152116

21162117
bool ResolveToken(CLR_UINT32 tk, CLR_RT_Assembly *assm, const CLR_RT_MethodDef_Instance *caller = nullptr);
2118+
bool ResolveNullableType(CLR_UINT32 tk, CLR_RT_Assembly *assm, const CLR_RT_MethodDef_Instance *caller = nullptr);
21172119

21182120
//--//
21192121

0 commit comments

Comments
 (0)