Skip to content

Commit 2170ea9

Browse files
authored
Fix: using default value for nullable enum parameter throws ArgumentException (#71388)
1 parent cc0ccbe commit 2170ea9

File tree

3 files changed

+108
-1
lines changed

3 files changed

+108
-1
lines changed

src/coreclr/nativeaot/System.Private.CoreLib/src/System/InvokeUtils.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -569,18 +569,28 @@ public static object DynamicInvokeParamHelperCore(ref ArgSetupState argSetupStat
569569

570570
Debug.Assert(argSetupState.parameters != null);
571571
object? incomingParam = argSetupState.parameters[index];
572+
bool nullable = type.ToEETypePtr().IsNullable;
572573

573574
// Handle default parameters
574575
if ((incomingParam == System.Reflection.Missing.Value) && paramType == DynamicInvokeParamType.In)
575576
{
576577
incomingParam = GetDefaultValue(argSetupState.targetMethodOrDelegate, type, index);
578+
if (incomingParam != null && nullable)
579+
{
580+
// In case if the parameter is nullable Enum type the ParameterInfo.DefaultValue returns a raw value which
581+
// needs to be parsed to the Enum type, for more info: https://github.com/dotnet/runtime/issues/12924
582+
EETypePtr nullableType = type.ToEETypePtr().NullableType;
583+
if (nullableType.IsEnum)
584+
{
585+
incomingParam = Enum.ToObject(Type.GetTypeFromEETypePtr(nullableType), incomingParam);
586+
}
587+
}
577588

578589
// The default value is captured into the parameters array
579590
argSetupState.parameters[index] = incomingParam;
580591
}
581592

582593
RuntimeTypeHandle widenAndCompareType = type;
583-
bool nullable = type.ToEETypePtr().IsNullable;
584594
if (nullable)
585595
{
586596
widenAndCompareType = new RuntimeTypeHandle(type.ToEETypePtr().NullableType);

src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBase.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,17 @@ BindingFlags invokeAttr
207207
}
208208
else
209209
{
210+
if (arg != null && sigType.IsNullableOfT)
211+
{
212+
// In case if the parameter is nullable Enum type the ParameterInfo.DefaultValue returns a raw value which
213+
// needs to be parsed to the Enum type, for more info: https://github.com/dotnet/runtime/issues/12924
214+
Type argumentType = sigType.GetGenericArguments()[0];
215+
if (argumentType.IsEnum)
216+
{
217+
arg = Enum.ToObject(argumentType, arg);
218+
}
219+
}
220+
210221
isValueType = sigType.CheckValue(ref arg, ref copyBackArg, binder, culture, invokeAttr);
211222
}
212223
}

src/libraries/System.Reflection/tests/MethodInfoTests.cs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -708,6 +708,61 @@ static MethodInfo GetMethod(string name) => typeof(EnumMethods).GetMethod(
708708
name, BindingFlags.Public | BindingFlags.Static)!;
709709
}
710710

711+
[Fact]
712+
public static void InvokeNullableEnumParameterDefaultNo()
713+
{
714+
MethodInfo method = typeof(EnumMethods).GetMethod("NullableEnumDefaultNo", BindingFlags.Static | BindingFlags.NonPublic);
715+
716+
Assert.Null(method.Invoke(null, new object?[] { default(object) }));
717+
Assert.Equal(YesNo.No, method.Invoke(null, new object?[] { YesNo.No }));
718+
Assert.Equal(YesNo.Yes, method.Invoke(null, new object?[] { YesNo.Yes }));
719+
Assert.Equal(YesNo.No, method.Invoke(null, new object?[] { Type.Missing }));
720+
}
721+
722+
[Fact]
723+
public static void InvokeNullableEnumParameterDefaultYes()
724+
{
725+
MethodInfo method = typeof(EnumMethods).GetMethod("NullableEnumDefaultYes", BindingFlags.Static | BindingFlags.NonPublic);
726+
727+
Assert.Null(method.Invoke(null, new object?[] { default(object) }));
728+
Assert.Equal(YesNo.No, method.Invoke(null, new object?[] { YesNo.No }));
729+
Assert.Equal(YesNo.Yes, method.Invoke(null, new object?[] { YesNo.Yes }));
730+
Assert.Equal(YesNo.Yes, method.Invoke(null, new object?[] { Type.Missing }));
731+
}
732+
733+
[Fact]
734+
public static void InvokeNonNullableEnumParameterDefaultYes()
735+
{
736+
MethodInfo method = typeof(EnumMethods).GetMethod("NonNullableEnumDefaultYes", BindingFlags.Static | BindingFlags.NonPublic);
737+
738+
Assert.Equal(YesNo.No, method.Invoke(null, new object[] { default(object) }));
739+
Assert.Equal(YesNo.No, method.Invoke(null, new object[] { YesNo.No }));
740+
Assert.Equal(YesNo.Yes, method.Invoke(null, new object[] { YesNo.Yes }));
741+
Assert.Equal(YesNo.Yes, method.Invoke(null, new object[] { Type.Missing }));
742+
}
743+
744+
[Fact]
745+
public static void InvokeNullableEnumParameterDefaultNull()
746+
{
747+
MethodInfo method = typeof(EnumMethods).GetMethod("NullableEnumDefaultNull", BindingFlags.Static | BindingFlags.NonPublic);
748+
749+
Assert.Null(method.Invoke(null, new object?[] { default(object) }));
750+
Assert.Equal(YesNo.No, method.Invoke(null, new object?[] { YesNo.No }));
751+
Assert.Equal(YesNo.Yes, method.Invoke(null, new object?[] { YesNo.Yes }));
752+
Assert.Null(method.Invoke(null, new object?[] { Type.Missing }));
753+
}
754+
755+
[Fact]
756+
public static void InvokeNullableEnumParameterNoDefault()
757+
{
758+
MethodInfo method = typeof(EnumMethods).GetMethod("NullableEnumNoDefault", BindingFlags.Static | BindingFlags.NonPublic);
759+
760+
Assert.Null(method.Invoke(null, new object?[] { default(object) }));
761+
Assert.Equal(YesNo.No, method.Invoke(null, new object?[] { YesNo.No }));
762+
Assert.Equal(YesNo.Yes, method.Invoke(null, new object?[] { YesNo.Yes }));
763+
Assert.Throws<ArgumentException>(() => method.Invoke(null, new object?[] { Type.Missing }));
764+
}
765+
711766
[Fact]
712767
public void ValueTypeMembers_WithOverrides()
713768
{
@@ -1140,6 +1195,12 @@ public struct ValueTypeWithoutOverrides
11401195
public int GetId() => Id;
11411196
}
11421197

1198+
public enum YesNo
1199+
{
1200+
No = 0,
1201+
Yes = 1,
1202+
}
1203+
11431204
public static class EnumMethods
11441205
{
11451206
public static bool PassColorsInt(ColorsInt color)
@@ -1153,6 +1214,31 @@ public static bool PassColorsShort(ColorsShort color)
11531214
Assert.Equal(ColorsShort.Red, color);
11541215
return true;
11551216
}
1217+
1218+
static YesNo NonNullableEnumDefaultYes(YesNo yesNo = YesNo.Yes)
1219+
{
1220+
return yesNo;
1221+
}
1222+
1223+
static YesNo? NullableEnumDefaultNo(YesNo? yesNo = YesNo.No)
1224+
{
1225+
return yesNo;
1226+
}
1227+
1228+
static YesNo? NullableEnumDefaultYes(YesNo? yesNo = YesNo.Yes)
1229+
{
1230+
return yesNo;
1231+
}
1232+
1233+
static YesNo? NullableEnumDefaultNull(YesNo? yesNo = null)
1234+
{
1235+
return yesNo;
1236+
}
1237+
1238+
static YesNo? NullableEnumNoDefault(YesNo? yesNo)
1239+
{
1240+
return yesNo;
1241+
}
11561242
}
11571243
#pragma warning restore 0414
11581244
}

0 commit comments

Comments
 (0)