From f62adf83902c7d23a3528a2677c4464968527542 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20L=C3=BCbe=C3=9F?= Date: Sat, 19 Sep 2026 23:47:01 +0200 Subject: [PATCH] Hardened access to boxed value --- ScriptCore/ScriptGlue.cs | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/ScriptCore/ScriptGlue.cs b/ScriptCore/ScriptGlue.cs index c35a9a3..8b03c88 100644 --- a/ScriptCore/ScriptGlue.cs +++ b/ScriptCore/ScriptGlue.cs @@ -685,6 +685,15 @@ internal static unsafe partial class ScriptGlue } } + /// + /// Layout stand-in to get a reference to the contents of a boxed value type via . + /// Never instantiated. + /// + private sealed class RawData + { + public byte Data; + } + public static void Serialization_SerializeField(IntPtr serializationContext, SerializationType type, string fieldName, object? valueObject, string? fullTypeName) { StringView fieldNameConverted; @@ -744,25 +753,16 @@ internal static unsafe partial class ScriptGlue default: if (valueObject is not null) { - // This is probably the dirtiest piece of C# Code, that I have ever seen. - // We first take a pointer the Object (which itself is a reference type -> object* is a double pointer!) - // We then dereference this double pointer, which gives us a pointer to the objects internals. - // These are Header (64bit), Method Table (64bit), and the objects content (at least another 64bit) - // I believe, that the actual reference points to the method table however and not the header (compare gcenv.object.h) - // Below we calculate a pointer to the objects contents by adding the size of a pointer. - -#pragma warning disable CS8500 // This takes the address of, gets the size of, or declares a pointer to a managed type - object?* objectRef = &valueObject; - // Skip Object Header (IntPtr) + Method Table (IntPtr) - byte* valueRef = (byte*)*(IntPtr*)objectRef + sizeof(IntPtr); - float* fRef = (float*)(void*)valueRef; - _engineFunctions.Serialization_SerializeField((void*)serializationContext, type, &fieldNameConverted, valueRef, &fullTypeNameConverted); + // valueObject is a boxed value type. An object reference points to the method table pointer, the boxed value starts right behind it. + // That's exactly where the first field of RawData lives, so reinterpreting the box gives us a reference to the first byte of the value. + // We have to pin it: During the native call this thread is in preemptive mode and a GC is free to move the box. + fixed (byte* valueRef = &Unsafe.As(valueObject).Data) + { + _engineFunctions.Serialization_SerializeField((void*)serializationContext, type, &fieldNameConverted, valueRef, &fullTypeNameConverted); + } } break; } - - //StringView.FreeNativeMemory(fieldNameConverted); - //StringView.FreeNativeMemory(fullTypeNameConverted); } public static void Serialization_SerializeValueType(IntPtr serializationContext, SerializationType type, string fieldName, in T value, string? fullTypeName) where T : unmanaged