Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,48 @@ private static unsafe void DispatchTailCalls(
}
}
}

/// <summary>
/// Create a boxed object of the specified type from the data located at the target reference.
/// </summary>
/// <param name="target">The target data</param>
/// <param name="type">The type of box to create.</param>
/// <returns>A boxed object containing the specified data.</returns>
/// <exception cref="ArgumentNullException">The specified type handle is <c>null</c>.</exception>
/// <exception cref="ArgumentException">The specified type cannot have a boxed instance of itself created.</exception>
/// <exception cref="NotSupportedException">The passed in type is a by-ref-like type.</exception>
public static unsafe object? Box(ref byte target, RuntimeTypeHandle type)
{
if (type.IsNullHandle())
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.type);

TypeHandle handle = type.GetNativeTypeHandle();

if (handle.IsTypeDesc)
throw new ArgumentException(SR.Arg_TypeNotSupported);

MethodTable* pMT = handle.AsMethodTable();

if (pMT->ContainsGenericVariables)
throw new ArgumentException(SR.Arg_TypeNotSupported);

if (pMT->IsValueType)
{
if (pMT->IsByRefLike)
throw new NotSupportedException(SR.NotSupported_ByRefLike);

if (MethodTable.AreSameType(pMT, (MethodTable*)RuntimeTypeHandle.ToIntPtr(typeof(void).TypeHandle)))
throw new ArgumentException(SR.Arg_TypeNotSupported);

object? result = Box(pMT, ref target);
GC.KeepAlive(type);
return result;
}
else
{
return Unsafe.As<byte, object?>(ref target);
}
}
}
// Helper class to assist with unsafe pinning of arbitrary objects.
// It's used by VM code.
Expand Down Expand Up @@ -523,6 +565,7 @@ internal unsafe struct MethodTable

// WFLAGS_HIGH_ENUM
private const uint enum_flag_ContainsPointers = 0x01000000;
private const uint enum_flag_ContainsGenericVariables = 0x20000000;
private const uint enum_flag_HasComponentSize = 0x80000000;
private const uint enum_flag_HasTypeEquivalence = 0x02000000;
private const uint enum_flag_Category_Mask = 0x000F0000;
Expand Down Expand Up @@ -630,6 +673,8 @@ public bool IsConstructedGenericType
}
}

public bool ContainsGenericVariables => (Flags & enum_flag_ContainsGenericVariables) != 0;

/// <summary>
/// Gets a <see cref="TypeHandle"/> for the element type of the current type.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,11 @@ internal bool IsNullHandle()
return m_type == null;
}

internal TypeHandle GetNativeTypeHandle()
{
return m_type.GetNativeTypeHandle();
}

internal static bool IsTypeDefinition(RuntimeType type)
{
CorElementType corElemType = GetCorElementType(type);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,16 @@ public static T ReadUnaligned<T>(void* source)
throw new PlatformNotSupportedException();
}

/// <summary>
/// Reads a value of type <typeparamref name="T"/> from the given location.
/// </summary>
[Intrinsic]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T ReadUnaligned<T>(ref readonly byte source)
{
throw new PlatformNotSupportedException();
}

/// <summary>
/// Copies bytes from the source address to the destination address.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ public static unsafe object RhNewArray(MethodTable* pEEType, int length)
[RuntimeExport("RhBox")]
public static unsafe object RhBox(MethodTable* pEEType, ref byte data)
{
// A null can be passed for boxing of a null ref.
_ = Unsafe.ReadUnaligned<byte>(ref data);

ref byte dataAdjustedForNullable = ref data;

// Can box non-ByRefLike value types only (which also implies no finalizers).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,44 @@ public static unsafe object GetUninitializedObject(

return RuntimeImports.RhNewObject(mt);
}

/// <summary>
/// Create a boxed object of the specified type from the data located at the target reference.
/// </summary>
/// <param name="target">The target data</param>
/// <param name="type">The type of box to create.</param>
/// <returns>A boxed object containing the specified data.</returns>
/// <exception cref="ArgumentNullException">The specified type handle is <c>null</c>.</exception>
/// <exception cref="ArgumentException">The specified type cannot have a boxed instance of itself created.</exception>
/// <exception cref="NotSupportedException">The passed in type is a by-ref-like type.</exception>
public static unsafe object? Box(ref byte target, RuntimeTypeHandle type)
{
if (type.IsNull)
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.type);

MethodTable* mt = type.ToMethodTable();

if (mt->ElementType == EETypeElementType.Void || mt->IsGenericTypeDefinition || mt->IsByRef || mt->IsPointer || mt->IsFunctionPointer)
throw new ArgumentException(SR.Arg_TypeNotSupported);

if (mt->NumVtableSlots == 0)
{
// This is a type without a vtable or GCDesc. We must not allow creating an instance of it
throw ReflectionCoreExecution.ExecutionEnvironment.CreateMissingMetadataException(Type.GetTypeFromHandle(type));
}
// Paranoid check: not-meant-for-GC-heap types should be reliably identifiable by empty vtable.
Debug.Assert(!mt->ContainsGCPointers || RuntimeImports.RhGetGCDescSize(mt) != 0);

if (!mt->IsValueType)
{
return Unsafe.As<byte, object>(ref target);
}

if (mt->IsByRefLike)
throw new NotSupportedException(SR.NotSupported_ByRefLike);

return RuntimeImports.RhBox(mt, ref target);
}
}

// CLR arrays are laid out in memory as follows (multidimensional array bounds are optional):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,23 +50,18 @@ internal static RuntimeTypeHandle RawTargetTypeToken(TypedReference value)

public static unsafe object ToObject(TypedReference value)
{
RuntimeTypeHandle typeHandle = value._typeHandle;
if (typeHandle.IsNull)
RuntimeTypeHandle handle = RawTargetTypeToken(value);

if (handle.IsNull)
ThrowHelper.ThrowArgumentException_ArgumentNull_TypedRefType();

MethodTable* eeType = typeHandle.ToMethodTable();
if (eeType->IsValueType)
{
return RuntimeImports.RhBox(eeType, ref value.Value);
}
else if (eeType->IsPointer || eeType->IsFunctionPointer)
MethodTable* mt = handle.ToMethodTable();
if (mt->IsPointer || mt->IsFunctionPointer)
{
return RuntimeImports.RhBox(MethodTable.Of<UIntPtr>(), ref value.Value);
}
else
{
return Unsafe.As<byte, object>(ref value.Value);
handle = typeof(UIntPtr).TypeHandle;
}

return RuntimeHelpers.Box(ref value.Value, handle);
}

public static void SetTypedReference(TypedReference target, object? value) { throw new NotSupportedException(); }
Expand Down
1 change: 1 addition & 0 deletions src/libraries/System.Runtime/ref/System.Runtime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13149,6 +13149,7 @@ public static partial class RuntimeHelpers
[System.ObsoleteAttribute("OffsetToStringData has been deprecated. Use string.GetPinnableReference() instead.")]
public static int OffsetToStringData { get { throw null; } }
public static System.IntPtr AllocateTypeAssociatedMemory(System.Type type, int size) { throw null; }
public static object? Box(ref byte target, System.RuntimeTypeHandle type) { throw null; }
public static System.ReadOnlySpan<T> CreateSpan<T>(System.RuntimeFieldHandle fldHandle) { throw null; }
public static void EnsureSufficientExecutionStack() { }
public static new bool Equals(object? o1, object? o2) { throw null; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,173 @@ public static void FixedAddressValueTypeTest()

Assert.Equal(fixedPtr1, fixedPtr2);
}

[Fact]
public static void BoxPrimitive()
{
int value = 4;
object result = RuntimeHelpers.Box(ref Unsafe.As<int, byte>(ref value), typeof(int).TypeHandle);
Assert.Equal(value, Assert.IsType<int>(result));
}

[Fact]
public static void BoxPointer()
{
Assert.Throws<ArgumentException>(() =>
{
nint value = 3;
object result = RuntimeHelpers.Box(ref Unsafe.As<nint, byte>(ref value), typeof(void*).TypeHandle);
});
}

[StructLayout(LayoutKind.Sequential)]
private ref struct ByRefLikeType
{
public int i;
}

[Fact]
public static void BoxByRefLike()
{
Assert.Throws<NotSupportedException>(() =>
{
int value = 3;
object result = RuntimeHelpers.Box(ref Unsafe.As<int, byte>(ref value), typeof(ByRefLikeType).TypeHandle);
});
}

[Fact]
public static void BoxStruct()
{
Span<int> buffer = [0, 42, int.MaxValue];
StructWithoutReferences expected = new()
{
a = buffer[0],
b = buffer[1],
c = buffer[2]
};
object result = RuntimeHelpers.Box(ref MemoryMarshal.AsBytes(buffer)[0], typeof(StructWithoutReferences).TypeHandle);

Assert.Equal(expected, Assert.IsType<StructWithoutReferences>(result));
}

[StructLayout(LayoutKind.Sequential)]
private struct GenericStruct<T>
{
public T data;
}

[Fact]
public static void BoxUnmanagedGenericStruct()
{
int value = 3;
object result = RuntimeHelpers.Box(ref Unsafe.As<int, byte>(ref value), typeof(GenericStruct<int>).TypeHandle);

Assert.Equal(value, Assert.IsType<GenericStruct<int>>(result).data);
}

[Fact]
public static void BoxManagedGenericStruct()
{
object value = new();
object result = RuntimeHelpers.Box(ref Unsafe.As<object, byte>(ref value), typeof(GenericStruct<object>).TypeHandle);

Assert.Same(value, Assert.IsType<GenericStruct<object>>(result).data);
}

[Fact]
public static void BoxNullable()
{
float? value = 3.14f;
object result = RuntimeHelpers.Box(ref Unsafe.As<float?, byte>(ref value), typeof(float?).TypeHandle);
Assert.Equal(value, Assert.IsType<float>(result));
}

[Fact]
public static void BoxNullNullable()
{
float? value = null;
object? result = RuntimeHelpers.Box(ref Unsafe.As<float?, byte>(ref value), typeof(float?).TypeHandle);
Assert.Null(result);
}

[Fact]
public static void NullBox()
{
Assert.Throws<NullReferenceException>(() => RuntimeHelpers.Box(ref Unsafe.NullRef<byte>(), typeof(byte).TypeHandle));
}

[Fact]
public static void BoxNullTypeHandle()
{
Assert.Throws<ArgumentNullException>(() =>
{
byte value = 3;
RuntimeHelpers.Box(ref value, default(RuntimeTypeHandle));
});
}

[Fact]
public static void BoxReferenceType()
{
string str = "ABC";
Assert.Same(str, RuntimeHelpers.Box(ref Unsafe.As<string, byte>(ref str), typeof(string).TypeHandle));
}

[Fact]
public static void BoxArrayType()
{
string[] arr = ["a", "b", "c"];
Assert.Same(arr, RuntimeHelpers.Box(ref Unsafe.As<string[], byte>(ref arr), typeof(string[]).TypeHandle));
}

// We can't even get a RuntimeTypeHandle for a generic parameter type on NativeAOT,
// so we don't even get to the method we're testing.
// So, let's not even waste time running this test on NativeAOT
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotNativeAot))]
public static void BoxGenericParameterType()
{
Type t = typeof(List<>).GetGenericArguments()[0];
Assert.Throws<ArgumentException>(() =>
{
byte value = 3;
RuntimeHelpers.Box(ref value, t.TypeHandle);
});
}

// We can't even get a RuntimeTypeHandle for a partially instantiated generic type on NativeAOT,
// so we don't even get to the method we're testing.
// So, let's not even waste time running this test on NativeAOT
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotNativeAot))]
public static void BoxPartiallyOpenGeneric()
{
Type t = typeof(Dictionary<,>).MakeGenericType(typeof(object), typeof(Dictionary<,>).GetGenericArguments()[1]);
Assert.Throws<ArgumentException>(() =>
{
byte value = 3;
RuntimeHelpers.Box(ref value, t.TypeHandle);
});
}

[Fact]
public static void BoxGenericTypeDefinition()
{
Assert.Throws<ArgumentException>(() =>
{
byte value = 3;
RuntimeHelpers.Box(ref value, typeof(List<>).TypeHandle);
});
}

[Fact]
public static void BoxVoid()
{
Assert.Throws<ArgumentException>(() =>
{
byte value = 3;
RuntimeHelpers.Box(ref value, typeof(void).TypeHandle);
});
}
}

public struct Age
Expand Down
Loading