runtime: `FormatterServices.GetUninitializedObject` replacement?

I’m author of FsPickler, a multi-format message serialization library intended as a replacement to BinaryFormatter. The focus of the library is serialization of closures, which means that I need to support things like subtyping and internal, compiler-generated classes that often have no accessible constructors or serialization attribute annotations.

I would be interested in porting FsPickler (and its father project, mbrace) to CoreCLR, however there are many obstacles, mostly related to deprecated APIs. The component I’m more concerned about is the FormatterServices.GetUninitializedObject method which is used for instantiating objects without relying on a parameterless constructor. This is crucial for us since many closure types (particularly F# lambdas) do not come with such constructors.

Do you have any plans for replacing this with equivalent functionality?

/cc https://github.com/dotnet/corefx/issues/6564#issuecomment-212620200 https://github.com/dotnet/coreclr/issues/2715

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Reactions: 1
  • Comments: 15 (14 by maintainers)

Most upvoted comments

FormatterServices.GetUninitializedObject() is absolutely necessary for Orleans-generated serializers. Without it we’ll be entirely broken.

FormatterServices.GetUninitializedObject has been added to .NET Core 1.2. Related discussion at https://github.com/dotnet/corefx/issues/8133.

Hi, While evaluating whether we could eventually build a port of Orleans that supports CoreCLR, I’ve done some tests with manually creating serializers for exceptions using the FormatterServices.GetUninitializedObject method (via reflection), and seems to unblock us for now. Compared to just having the binary formatter, we are of course constrained with what parts of the exceptions we can serialize, and also of non-Exception types we want to serialize too for inter grain communication. For now I’m only serializing a subset of the Exception fields, but that means custom properties from exceptions will not flow through in any kind of grain interaction (such as ParamName, or ErrorCode, or HttpStatus from different exception types). This is a limitation that I guess we can live with until we get proper support for serialization in .NET Core (as was announced post-RTM).

In a nutshell, I’m serializing the following private fields from System.Exception using reflection: _className, _innerException, _message, _remoteStackTraceString, _stackTraceString. This allows for some troubleshooting of remote logic, since this allows us to preserve the exception type and because Exception.ToString() uses only those fields to print the exception details (unless of course a derived exception decides to override ToString). I only tested that this works in .NET Core RC2 running on Windows, but I’m not familiar whether accessing these private fields through reflection could cause issues in other platforms, could you guys validate?

These are the relevant portions (without any extra optimizations, as this is not production code yet) of the steps I took to serialize exceptions in Orleans:

public class ExceptionSerializer
{
    private static readonly FieldInfo classNameField = typeof (Exception).GetTypeInfo().GetField("_className", (BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public));
    private static readonly Func<Exception, String> getClassNameField = (Func<Exception, String>)SerializationManager.GetGetter(classNameField);
    private static readonly Action<Exception, String> setClassNameField = (Action<Exception, String>)SerializationManager.GetReferenceSetter(classNameField);
    private static readonly FieldInfo innerExceptionField = typeof (Exception).GetTypeInfo().GetField("_innerException", (BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public));
    private static readonly Func<Exception, Exception> getInnerExceptionField = (Func<Exception, Exception>)SerializationManager.GetGetter(innerExceptionField);
    private static readonly Action<Exception, Exception> setInnerExceptionField = (Action<Exception, Exception>)SerializationManager.GetReferenceSetter(innerExceptionField);
    private static readonly FieldInfo messageField = typeof (Exception).GetTypeInfo().GetField("_message", (BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public));
    private static readonly Func<Exception, String> getMessageField = (Func<Exception, String>)SerializationManager.GetGetter(messageField);
    private static readonly Action<Exception, String> setMessageField = (Action<Exception, String>)SerializationManager.GetReferenceSetter(messageField);
    private static readonly FieldInfo remoteStackTraceStringField = typeof (Exception).GetTypeInfo().GetField("_remoteStackTraceString", (BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public));
    private static readonly Func<Exception, String> getRemoteStackTraceStringField = (Func<Exception, String>)SerializationManager.GetGetter(remoteStackTraceStringField);
    private static readonly Action<Exception, String> setRemoteStackTraceStringField = (Action<Exception, String>)SerializationManager.GetReferenceSetter(remoteStackTraceStringField);
    private static readonly FieldInfo stackTraceStringField = typeof (Exception).GetTypeInfo().GetField("_stackTraceString", (BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public));
    private static readonly Func<Exception, String> getStackTraceStringField = (Func<Exception, String>)SerializationManager.GetGetter(stackTraceStringField);
    private static readonly Action<Exception, String> setStackTraceStringField = (Action<Exception, String>)SerializationManager.GetReferenceSetter(stackTraceStringField);

    [CopierMethodAttribute]
    public static Object DeepCopier(Object original)
    {
        Type exceptionType = original.GetType();
        Exception input = (Exception)original;
        Exception result = (Exception)SerializationManager.GetUninitializedObjectWithFormatterServices(exceptionType);
        setClassNameField(result, getClassNameField(input));
        setInnerExceptionField(result, (Exception)SerializationManager.DeepCopyInner(getInnerExceptionField(input)));
        setMessageField(result, getMessageField(input));
        setRemoteStackTraceStringField(result, getRemoteStackTraceStringField(input));
        setStackTraceStringField(result, getStackTraceStringField(input));
        SerializationContext.Current.RecordObject(original, result);
        return result;
    }

    [SerializerMethodAttribute]
    public static void Serializer(Object untypedInput, BinaryTokenStreamWriter stream, Type expected)
    {
        Exception input = (Exception)untypedInput;
        SerializationManager.SerializeInner(getClassNameField(input), stream, typeof (String));
        SerializationManager.SerializeInner(getInnerExceptionField(input), stream, typeof (Exception));
        SerializationManager.SerializeInner(getMessageField(input), stream, typeof (String));
        SerializationManager.SerializeInner(getRemoteStackTraceStringField(input), stream, typeof (String));
        SerializationManager.SerializeInner(getStackTraceStringField(input), stream, typeof (String));
    }

    [DeserializerMethodAttribute]
    public static Object Deserializer(Type expected, BinaryTokenStreamReader stream)
    {
        Type exceptionType = expected;
        Exception result = (Exception)SerializationManager.GetUninitializedObjectWithFormatterServices(exceptionType);
        DeserializationContext.Current.RecordObject(result);
        setClassNameField(result, (String)SerializationManager.DeserializeInner(typeof (String), stream));
        setInnerExceptionField(result, (Exception)SerializationManager.DeserializeInner(typeof (Exception), stream));
        setMessageField(result, (String)SerializationManager.DeserializeInner(typeof (String), stream));
        setRemoteStackTraceStringField(result, (String)SerializationManager.DeserializeInner(typeof (String), stream));
        setStackTraceStringField(result, (String)SerializationManager.DeserializeInner(typeof (String), stream));
        return result;
    }
}

public static class SerializationManager
{
    // Workaround for CoreCLR where FormatterServices.GetUninitializedObject is not public (but might change in RTM so we could remove this then).
    private static readonly Func<Type, object> getUninitializedObjectDelegate =
        (Func<Type, object>)
            typeof(string)
                .GetTypeInfo()
                .Assembly
                .GetType("System.Runtime.Serialization.FormatterServices")
                .GetMethod("GetUninitializedObject", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static)
                .CreateDelegate(typeof(Func<Type, object>));
    public static object GetUninitializedObjectWithFormatterServices(Type type)
    {
        return getUninitializedObjectDelegate.Invoke(type);
    }

    public static Delegate GetGetter(FieldInfo field)
    {
        return GetGetDelegate(
            field,
            typeof(Func<,>).MakeGenericType(field.DeclaringType, field.FieldType),
            new[] { field.DeclaringType });
    }

    public static Delegate GetReferenceSetter(FieldInfo field)
    {
        var delegateType = typeof(Action<,>).MakeGenericType(field.DeclaringType, field.FieldType);
        return GetSetDelegate(field, delegateType, new[] { field.DeclaringType, field.FieldType });
    }

    private static Delegate GetSetDelegate(FieldInfo field, Type delegateType, Type[] parameterTypes)
    {
        var declaringType = field.DeclaringType;
        if (declaringType == null)
        {
            throw new InvalidOperationException("Field " + field.Name + " does not have a declaring type.");
        }

        // Create a method to hold the generated IL.
        var method = new DynamicMethod(field.Name + "Set", null, parameterTypes, field.FieldType.GetTypeInfo().Module, true);

        // Emit IL to return the value of the Transaction property.
        var emitter = method.GetILGenerator();
        emitter.Emit(OpCodes.Ldarg_0);
        emitter.Emit(OpCodes.Ldarg_1);
        emitter.Emit(OpCodes.Stfld, field);
        emitter.Emit(OpCodes.Ret);

        return method.CreateDelegate(delegateType);
    }

    // other members omitted
}

Just as FYI, this is in an exploratory branch I have here: https://github.com/jdom/orleans/blob/coreclr-multi/src/Orleans/Core/ExceptionSerializer.cs