diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs index 733e3a664bcc5..69506c2feda11 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs @@ -416,6 +416,48 @@ private static unsafe void DispatchTailCalls( } } } + + /// + /// Create a boxed object of the specified type from the data located at the target reference. + /// + /// The target data + /// The type of box to create. + /// A boxed object containing the specified data. + /// The specified type handle is null. + /// The specified type cannot have a boxed instance of itself created. + /// The passed in type is a by-ref-like type. + 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(ref target); + } + } } // Helper class to assist with unsafe pinning of arbitrary objects. // It's used by VM code. @@ -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; @@ -630,6 +673,8 @@ public bool IsConstructedGenericType } } + public bool ContainsGenericVariables => (Flags & enum_flag_ContainsGenericVariables) != 0; + /// /// Gets a for the element type of the current type. /// diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs index c6c7b9a563b4d..001a9fcdfee6a 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs @@ -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); diff --git a/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/CompilerServices/Unsafe.cs b/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/CompilerServices/Unsafe.cs index 341cd83260471..89eedc1638a72 100644 --- a/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/CompilerServices/Unsafe.cs +++ b/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/CompilerServices/Unsafe.cs @@ -120,6 +120,16 @@ public static T ReadUnaligned(void* source) throw new PlatformNotSupportedException(); } + /// + /// Reads a value of type from the given location. + /// + [Intrinsic] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T ReadUnaligned(ref readonly byte source) + { + throw new PlatformNotSupportedException(); + } + /// /// Copies bytes from the source address to the destination address. /// diff --git a/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/RuntimeExports.cs b/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/RuntimeExports.cs index 378fabf56836f..6fa6c5460dbb8 100644 --- a/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/RuntimeExports.cs +++ b/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/RuntimeExports.cs @@ -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(ref data); + ref byte dataAdjustedForNullable = ref data; // Can box non-ByRefLike value types only (which also implies no finalizers). diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.NativeAot.cs index 262ddb9857b75..c790682ecfcbc 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.NativeAot.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.NativeAot.cs @@ -361,6 +361,44 @@ public static unsafe object GetUninitializedObject( return RuntimeImports.RhNewObject(mt); } + + /// + /// Create a boxed object of the specified type from the data located at the target reference. + /// + /// The target data + /// The type of box to create. + /// A boxed object containing the specified data. + /// The specified type handle is null. + /// The specified type cannot have a boxed instance of itself created. + /// The passed in type is a by-ref-like type. + 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(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): diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/TypedReference.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/TypedReference.cs index 3b209cd489c44..56e8ef05dd1cd 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/TypedReference.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/TypedReference.cs @@ -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(), ref value.Value); - } - else - { - return Unsafe.As(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(); } diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index ef4bc568fb56f..0371e3037e265 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -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 CreateSpan(System.RuntimeFieldHandle fldHandle) { throw null; } public static void EnsureSufficientExecutionStack() { } public static new bool Equals(object? o1, object? o2) { throw null; } diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Runtime/CompilerServices/RuntimeHelpersTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Runtime/CompilerServices/RuntimeHelpersTests.cs index 5148ca0c488d8..afb72bbe0949f 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Runtime/CompilerServices/RuntimeHelpersTests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Runtime/CompilerServices/RuntimeHelpersTests.cs @@ -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(ref value), typeof(int).TypeHandle); + Assert.Equal(value, Assert.IsType(result)); + } + + [Fact] + public static void BoxPointer() + { + Assert.Throws(() => + { + nint value = 3; + object result = RuntimeHelpers.Box(ref Unsafe.As(ref value), typeof(void*).TypeHandle); + }); + } + + [StructLayout(LayoutKind.Sequential)] + private ref struct ByRefLikeType + { + public int i; + } + + [Fact] + public static void BoxByRefLike() + { + Assert.Throws(() => + { + int value = 3; + object result = RuntimeHelpers.Box(ref Unsafe.As(ref value), typeof(ByRefLikeType).TypeHandle); + }); + } + + [Fact] + public static void BoxStruct() + { + Span 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(result)); + } + + [StructLayout(LayoutKind.Sequential)] + private struct GenericStruct + { + public T data; + } + + [Fact] + public static void BoxUnmanagedGenericStruct() + { + int value = 3; + object result = RuntimeHelpers.Box(ref Unsafe.As(ref value), typeof(GenericStruct).TypeHandle); + + Assert.Equal(value, Assert.IsType>(result).data); + } + + [Fact] + public static void BoxManagedGenericStruct() + { + object value = new(); + object result = RuntimeHelpers.Box(ref Unsafe.As(ref value), typeof(GenericStruct).TypeHandle); + + Assert.Same(value, Assert.IsType>(result).data); + } + + [Fact] + public static void BoxNullable() + { + float? value = 3.14f; + object result = RuntimeHelpers.Box(ref Unsafe.As(ref value), typeof(float?).TypeHandle); + Assert.Equal(value, Assert.IsType(result)); + } + + [Fact] + public static void BoxNullNullable() + { + float? value = null; + object? result = RuntimeHelpers.Box(ref Unsafe.As(ref value), typeof(float?).TypeHandle); + Assert.Null(result); + } + + [Fact] + public static void NullBox() + { + Assert.Throws(() => RuntimeHelpers.Box(ref Unsafe.NullRef(), typeof(byte).TypeHandle)); + } + + [Fact] + public static void BoxNullTypeHandle() + { + Assert.Throws(() => + { + 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(ref str), typeof(string).TypeHandle)); + } + + [Fact] + public static void BoxArrayType() + { + string[] arr = ["a", "b", "c"]; + Assert.Same(arr, RuntimeHelpers.Box(ref Unsafe.As(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(() => + { + 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(() => + { + byte value = 3; + RuntimeHelpers.Box(ref value, t.TypeHandle); + }); + } + + [Fact] + public static void BoxGenericTypeDefinition() + { + Assert.Throws(() => + { + byte value = 3; + RuntimeHelpers.Box(ref value, typeof(List<>).TypeHandle); + }); + } + + [Fact] + public static void BoxVoid() + { + Assert.Throws(() => + { + byte value = 3; + RuntimeHelpers.Box(ref value, typeof(void).TypeHandle); + }); + } } public struct Age diff --git a/src/mono/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.Mono.cs index ca9ecdbe9f829..1e2241de12b35 100644 --- a/src/mono/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.Mono.cs @@ -211,5 +211,52 @@ private static extern unsafe IntPtr GetSpanDataFrom( [MethodImplAttribute(MethodImplOptions.InternalCall)] private static extern bool SufficientExecutionStack(); + + [MethodImplAttribute(MethodImplOptions.InternalCall)] + private static extern void InternalBox(QCallTypeHandle type, ref byte target, ObjectHandleOnStack result); + + /// + /// Create a boxed object of the specified type from the data located at the target reference. + /// + /// The target data + /// The type of box to create. + /// A boxed object containing the specified data. + /// The specified type handle is null. + /// The specified type cannot have a boxed instance of itself created. + /// The passed in type is a by-ref-like type. + /// This returns an object that is equivalent to executing the IL box instruction with the provided target address and type. + public static object? Box(ref byte target, RuntimeTypeHandle type) + { + if (type.Value is 0) + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.type); + + // Compatibility with CoreCLR, throw on a null reference to the unboxed data. + if (Unsafe.IsNullRef(ref target)) + throw new NullReferenceException(); + + RuntimeType rtType = (RuntimeType)Type.GetTypeFromHandle(type)!; + + if (rtType.ContainsGenericParameters + || rtType.IsPointer + || rtType.IsFunctionPointer + || rtType.IsByRef + || rtType.IsGenericParameter + || rtType == typeof(void)) + { + throw new ArgumentException(SR.Arg_TypeNotSupported); + } + + if (!rtType.IsValueType) + { + return Unsafe.As(ref target); + } + + if (rtType.IsByRefLike) + throw new NotSupportedException(SR.NotSupported_ByRefLike); + + object? result = null; + InternalBox(new QCallTypeHandle(ref rtType), ref target, ObjectHandleOnStack.Create(ref result)); + return result; + } } } diff --git a/src/mono/mono/metadata/icall-def.h b/src/mono/mono/metadata/icall-def.h index 84bb3df63a8cd..766754d520080 100644 --- a/src/mono/mono/metadata/icall-def.h +++ b/src/mono/mono/metadata/icall-def.h @@ -435,6 +435,7 @@ HANDLES(RUNH_1, "GetObjectValue", ves_icall_System_Runtime_CompilerServices_Runt HANDLES(RUNH_6, "GetSpanDataFrom", ves_icall_System_Runtime_CompilerServices_RuntimeHelpers_GetSpanDataFrom, gpointer, 3, (MonoClassField_ptr, MonoType_ptr, gpointer)) HANDLES(RUNH_2, "GetUninitializedObjectInternal", ves_icall_System_Runtime_CompilerServices_RuntimeHelpers_GetUninitializedObjectInternal, MonoObject, 1, (MonoType_ptr)) HANDLES(RUNH_3, "InitializeArray", ves_icall_System_Runtime_CompilerServices_RuntimeHelpers_InitializeArray, void, 2, (MonoArray, MonoClassField_ptr)) +HANDLES(RUNH_8, "InternalBox", ves_icall_System_Runtime_CompilerServices_RuntimeHelpers_InternalBox, void, 3, (MonoQCallTypeHandle, char_ref, MonoObjectHandleOnStack)) HANDLES(RUNH_7, "InternalGetHashCode", ves_icall_System_Runtime_CompilerServices_RuntimeHelpers_InternalGetHashCode, int, 1, (MonoObject)) HANDLES(RUNH_3a, "PrepareMethod", ves_icall_System_Runtime_CompilerServices_RuntimeHelpers_PrepareMethod, void, 3, (MonoMethod_ptr, gpointer, int)) HANDLES(RUNH_4, "RunClassConstructor", ves_icall_System_Runtime_CompilerServices_RuntimeHelpers_RunClassConstructor, void, 1, (MonoType_ptr)) diff --git a/src/mono/mono/metadata/icall-table.h b/src/mono/mono/metadata/icall-table.h index 1336ed368e9d2..6af12c82e4f4c 100644 --- a/src/mono/mono/metadata/icall-table.h +++ b/src/mono/mono/metadata/icall-table.h @@ -71,6 +71,7 @@ typedef mono_unichar2 *mono_unichar2_ptr; typedef mono_unichar4 *mono_unichar4_ptr; typedef MonoSpanOfObjects *MonoSpanOfObjects_ref; +typedef char *char_ref; typedef char **char_ptr_ref; typedef gint32 *gint32_ref; typedef gint64 *gint64_ref; @@ -173,6 +174,7 @@ typedef MonoStringHandle MonoStringOutHandle; #define MONO_HANDLE_TYPE_WRAP_int_ref ICALL_HANDLES_WRAP_VALUETYPE_REF #define MONO_HANDLE_TYPE_WRAP_gint32_ref ICALL_HANDLES_WRAP_VALUETYPE_REF #define MONO_HANDLE_TYPE_WRAP_int_ptr_ref ICALL_HANDLES_WRAP_VALUETYPE_REF +#define MONO_HANDLE_TYPE_WRAP_char_ref ICALL_HANDLES_WRAP_VALUETYPE_REF #define MONO_HANDLE_TYPE_WRAP_char_ptr_ref ICALL_HANDLES_WRAP_VALUETYPE_REF #define MONO_HANDLE_TYPE_WRAP_guint8_ptr_ref ICALL_HANDLES_WRAP_VALUETYPE_REF #define MONO_HANDLE_TYPE_WRAP_MonoResolveTokenError_ref ICALL_HANDLES_WRAP_VALUETYPE_REF diff --git a/src/mono/mono/metadata/icall.c b/src/mono/mono/metadata/icall.c index 6a3e33bc341af..54cae6cef38d2 100644 --- a/src/mono/mono/metadata/icall.c +++ b/src/mono/mono/metadata/icall.c @@ -1210,6 +1210,26 @@ ves_icall_System_Runtime_CompilerServices_RuntimeHelpers_PrepareMethod (MonoMeth // FIXME: Implement } +void +ves_icall_System_Runtime_CompilerServices_RuntimeHelpers_InternalBox (MonoQCallTypeHandle type_handle, char* data, MonoObjectHandleOnStack obj, MonoError *error) +{ + MonoType *type = type_handle.type; + MonoClass *klass = mono_class_from_mono_type_internal (type); + + g_assert (m_class_is_valuetype (klass)); + + mono_class_init_checked (klass, error); + goto_if_nok (error, error_ret); + + MonoObject* raw_obj = mono_value_box_checked (klass, data, error); + goto_if_nok (error, error_ret); + + HANDLE_ON_STACK_SET(obj, raw_obj); + return; +error_ret: + HANDLE_ON_STACK_SET (obj, NULL); +} + MonoObjectHandle ves_icall_System_Object_MemberwiseClone (MonoObjectHandle this_obj, MonoError *error) {