diff --git a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Resolvers/DynamicObjectResolver.cs b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Resolvers/DynamicObjectResolver.cs index 16195088f..37e71ecaa 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Resolvers/DynamicObjectResolver.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Resolvers/DynamicObjectResolver.cs @@ -796,7 +796,7 @@ private static void EmitSerializeValue(ILGenerator il, TypeInfo type, ObjectSeri argOptions.EmitLoad(); il.EmitCall(getSerialize(t)); } - else if (IsOptimizeTargetType(t)) + else if (ObjectSerializationInfo.IsOptimizeTargetType(t)) { if (!t.GetTypeInfo().IsValueType) { @@ -851,263 +851,547 @@ private static void EmitSerializeValue(ILGenerator il, TypeInfo type, ObjectSeri // T Deserialize([arg:1]ref MessagePackReader reader, [arg:2]MessagePackSerializerOptions options); private static void BuildDeserialize(Type type, ObjectSerializationInfo info, ILGenerator il, Func tryEmitLoadCustomFormatter, int firstArgIndex) { - var reader = new ArgumentField(il, firstArgIndex, @ref: true); + var argReader = new ArgumentField(il, firstArgIndex, @ref: true); var argOptions = new ArgumentField(il, firstArgIndex + 1); - // if(reader.TryReadNil()) { return null; } - Label falseLabel = il.DefineLabel(); - reader.EmitLdarg(); - il.EmitCall(MessagePackReaderTypeInfo.TryReadNil); - il.Emit(OpCodes.Brfalse_S, falseLabel); - if (type.GetTypeInfo().IsClass) + // if (reader.TryReadNil()) { throw / return; } + BuildDeserializeInternalTryReadNil(type, il, ref argReader); + + // T ____result; + var localResult = il.DeclareLocal(type); + + // where T : new() + var canOverwrite = info.ConstructorParameters.Length == 0; + if (canOverwrite) { - il.Emit(OpCodes.Ldnull); - il.Emit(OpCodes.Ret); + // ____result = new T(); + BuildDeserializeInternalCreateInstance(type, info, il, localResult); + } + + // options.Security.DepthStep(ref reader); + BuildDeserializeInternalDepthStep(il, ref argReader, ref argOptions); + + // var length = reader.Read(Map|Array)Header(); + var localLength = BuildDeserializeInternalReadHeaderLength(info, il, ref argReader); + + // var resolver = options.Resolver; + var localResolver = BuildDeserializeInternalResolver(info, il, ref argOptions); + + if (info.IsIntKey) + { + // switch (key) { ... } + BuildDeserializeInternalDeserializeEachPropertyIntKey(info, il, tryEmitLoadCustomFormatter, canOverwrite, ref argReader, ref argOptions, localResolver, localResult, localLength); } else { - il.Emit(OpCodes.Ldstr, "typecode is null, struct not supported"); - il.Emit(OpCodes.Newobj, messagePackSerializationExceptionMessageOnlyConstructor); - il.Emit(OpCodes.Throw); + // var span = reader.ReadStringSpan(); + BuildDeserializeInternalDeserializeEachPropertyStringKey(info, il, tryEmitLoadCustomFormatter, canOverwrite, ref argReader, argOptions, localResolver, localResult, localLength); } - il.MarkLabel(falseLabel); + // ____result.OnAfterDeserialize() + BuildDeserializeInternalOnAfterDeserialize(type, info, il, localResult); - // options.Security.DepthStep(ref reader); - argOptions.EmitLoad(); - il.EmitCall(getSecurityFromOptions); - reader.EmitLdarg(); - il.EmitCall(securityDepthStep); + // reader.Depth--; + BuildDeserializeInternalDepthUnStep(il, ref argReader); - // var length = ReadMapHeader(ref byteSequence); - LocalBuilder length = il.DeclareLocal(typeof(int)); // [loc:1] - reader.EmitLdarg(); + // return ____result; + il.Emit(OpCodes.Ldloc, localResult); + il.Emit(OpCodes.Ret); + } - if (info.IsIntKey) + private static void BuildDeserializeInternalDeserializeEachPropertyStringKey(ObjectSerializationInfo info, ILGenerator il, Func tryEmitLoadCustomFormatter, bool canOverwrite, ref ArgumentField argReader, ArgumentField argOptions, LocalBuilder localResolver, LocalBuilder localResult, LocalBuilder localLength) + { + // Prepare local variables or assignment fields/properties + var infoList = BuildDeserializeInternalDeserializationInfoArrayStringKey(info, il, canOverwrite); + + // Read Loop(for var i = 0; i < length; i++) + BuildDeserializeInternalDeserializeLoopStringKey(il, tryEmitLoadCustomFormatter, ref argReader, ref argOptions, infoList, localResolver, localResult, localLength, canOverwrite, info); + + if (canOverwrite) { - il.EmitCall(MessagePackReaderTypeInfo.ReadArrayHeader); + return; } - else + + // ____result = new T(...); + BuildDeserializeInternalCreateInstanceWithArguments(info, il, infoList, localResult); + + // ... if (__field__IsInitialized) { ____result.field = __field__; } ... + BuildDeserializeInternalAssignFieldFromLocalVariableStringKey(info, il, infoList, localResult); + } + + private static void BuildDeserializeInternalDeserializeEachPropertyIntKey(ObjectSerializationInfo info, ILGenerator il, Func tryEmitLoadCustomFormatter, bool canOverwrite, ref ArgumentField argReader, ref ArgumentField argOptions, LocalBuilder localResolver, LocalBuilder localResult, LocalBuilder localLength) + { + // Prepare local variables or assignment fields/properties + var infoList = BuildDeserializeInternalDeserializationInfoArrayIntKey(info, il, canOverwrite, out var gotoDefault, out var maxKey); + + // Read Loop(for var i = 0; i < length; i++) + BuildDeserializeInternalDeserializeLoopIntKey(il, tryEmitLoadCustomFormatter, ref argReader, ref argOptions, infoList, localResolver, localResult, localLength, canOverwrite, gotoDefault); + + if (canOverwrite) { - il.EmitCall(MessagePackReaderTypeInfo.ReadMapHeader); + return; } - il.EmitStloc(length); + // ____result = new T(...); + BuildDeserializeInternalCreateInstanceWithArguments(info, il, infoList, localResult); - // make local fields - Label? gotoDefault = null; - DeserializeInfo[] infoList; - if (info.IsIntKey) + // ... ____result.field = __field__; ... + BuildDeserializeInternalAssignFieldFromLocalVariableIntKey(info, il, infoList, localResult, localLength, maxKey); + } + + private static void BuildDeserializeInternalAssignFieldFromLocalVariableStringKey(ObjectSerializationInfo info, ILGenerator il, DeserializeInfo[] infoList, LocalBuilder localResult) + { + foreach (var item in infoList) { - var maxKey = info.Members.Select(x => x.IntKey).DefaultIfEmpty(-1).Max(); - var len = maxKey + 1; - var intKeyMap = info.Members.ToDictionary(x => x.IntKey); + if (item.MemberInfo == null || item.IsInitializedLocalVariable == null) + { + continue; + } - infoList = Enumerable.Range(0, len) - .Select(x => - { - ObjectSerializationInfo.EmittableMember member; - if (intKeyMap.TryGetValue(x, out member)) - { - return new DeserializeInfo - { - MemberInfo = member, - LocalField = il.DeclareLocal(member.Type), - SwitchLabel = il.DefineLabel(), - }; - } - else - { - // return null MemberInfo, should filter null - if (gotoDefault == null) - { - gotoDefault = il.DefineLabel(); - } + // if (__field__IsInitialized) { ____result.field = __field__; } + var skipLabel = il.DefineLabel(); + il.EmitLdloc(item.IsInitializedLocalVariable); + il.Emit(OpCodes.Brfalse_S, skipLabel); - return new DeserializeInfo - { - MemberInfo = null, - LocalField = null, - SwitchLabel = gotoDefault.Value, - }; - } - }) - .ToArray(); + if (info.IsClass) + { + il.EmitLdloc(localResult); + } + else + { + il.EmitLdloca(localResult); + } + + il.EmitLdloc(item.LocalVariable); + item.MemberInfo.EmitStoreValue(il); + + il.MarkLabel(skipLabel); } - else + } + + private static void BuildDeserializeInternalAssignFieldFromLocalVariableIntKey(ObjectSerializationInfo info, ILGenerator il, DeserializeInfo[] infoList, LocalBuilder localResult, LocalBuilder localLength, int maxKey) + { + if (maxKey == -1) { - infoList = info.Members - .Select(item => new DeserializeInfo - { - MemberInfo = item, - LocalField = il.DeclareLocal(item.Type), - //// SwitchLabel = il.DefineLabel() - }) - .ToArray(); + return; } - // IFormatterResolver resolver = options.Resolver; - LocalBuilder localResolver = il.DeclareLocal(typeof(IFormatterResolver)); - argOptions.EmitLoad(); - il.EmitCall(getResolverFromOptions); - il.EmitStloc(localResolver); + Label? memberAssignmentDoneLabel = null; + var intKeyMap = infoList.Where(x => x.MemberInfo != null && x.MemberInfo.IsWritable).ToDictionary(x => x.MemberInfo.IntKey); + for (var key = 0; key <= maxKey; key++) + { + if (!intKeyMap.TryGetValue(key, out var item)) + { + continue; + } - // Read Loop(for var i = 0; i < length; i++) - if (info.IsStringKey) + // if (length <= key) { goto MEMBER_ASSIGNMENT_DONE; } + il.EmitLdloc(localLength); + il.EmitLdc_I4(key); + if (memberAssignmentDoneLabel == null) + { + memberAssignmentDoneLabel = il.DefineLabel(); + } + + il.Emit(OpCodes.Ble, memberAssignmentDoneLabel.Value); + + // ____result.field = __field__; + if (info.IsClass) + { + il.EmitLdloc(localResult); + } + else + { + il.EmitLdloca(localResult); + } + + il.EmitLdloc(item.LocalVariable); + item.MemberInfo.EmitStoreValue(il); + } + + // MEMBER_ASSIGNMENT_DONE: + if (memberAssignmentDoneLabel != null) + { + il.MarkLabel(memberAssignmentDoneLabel.Value); + } + } + + private static void BuildDeserializeInternalCreateInstanceWithArguments(ObjectSerializationInfo info, ILGenerator il, DeserializeInfo[] infoList, LocalBuilder localResult) + { + foreach (var item in info.ConstructorParameters) { - var automata = new AutomataDictionary(); - for (int i = 0; i < info.Members.Length; i++) + var local = infoList.First(x => x.MemberInfo == item.MemberInfo); + il.EmitLdloc(local.LocalVariable); + + if (!item.ConstructorParameter.ParameterType.IsValueType && local.MemberInfo.IsValueType) { - automata.Add(info.Members[i].StringKey, i); + // When a constructor argument of type object is being provided by a serialized member value that is a value type + // then that value must be boxed in order for the generated code to be valid (see issue #987). This may occur because + // the only requirement when determining whether a member value may be used to populate a constructor argument in an + // IsAssignableFrom check and typeof(object) IsAssignableFrom typeof(int), for example. + il.Emit(OpCodes.Box, local.MemberInfo.Type); } + } - LocalBuilder buffer = il.DeclareLocal(typeof(ReadOnlySpan)); - LocalBuilder longKey = il.DeclareLocal(typeof(ulong)); + il.Emit(OpCodes.Newobj, info.BestmatchConstructor); + il.Emit(OpCodes.Stloc, localResult); + } - // for (int i = 0; i < len; i++) - il.EmitIncrementFor(length, forILocal => + private static DeserializeInfo[] BuildDeserializeInternalDeserializationInfoArrayStringKey(ObjectSerializationInfo info, ILGenerator il, bool canOverwrite) + { + var infoList = new DeserializeInfo[info.Members.Length]; + for (var i = 0; i < infoList.Length; i++) + { + var item = info.Members[i]; + if (canOverwrite && item.IsWritable) { - Label readNext = il.DefineLabel(); - Label loopEnd = il.DefineLabel(); + infoList[i] = new DeserializeInfo + { + MemberInfo = item, + }; + } + else + { + var isConstructorParameter = info.ConstructorParameters.Any(p => p.MemberInfo.Equals(item)); + infoList[i] = new DeserializeInfo + { + MemberInfo = item, + LocalVariable = il.DeclareLocal(item.Type), + IsInitializedLocalVariable = isConstructorParameter ? default : il.DeclareLocal(typeof(bool)), + }; + } + } + + return infoList; + } - reader.EmitLdarg(); - il.EmitCall(ReadStringSpan); - il.EmitStloc(buffer); + private static DeserializeInfo[] BuildDeserializeInternalDeserializationInfoArrayIntKey(ObjectSerializationInfo info, ILGenerator il, bool canOverwrite, out Label? gotoDefault, out int maxKey) + { + maxKey = info.Members.Select(x => x.IntKey).DefaultIfEmpty(-1).Max(); + var len = maxKey + 1; + var intKeyMap = info.Members.ToDictionary(x => x.IntKey); + gotoDefault = null; - // gen automata name lookup - automata.EmitMatch( - il, - buffer, - longKey, - x => + var infoList = new DeserializeInfo[len]; + for (var i = 0; i < infoList.Length; i++) + { + if (intKeyMap.TryGetValue(i, out var member)) + { + if (canOverwrite && member.IsWritable) + { + infoList[i] = new DeserializeInfo { - var i = x.Value; - if (infoList[i].MemberInfo != null) - { - EmitDeserializeValue(il, infoList[i], i, tryEmitLoadCustomFormatter, reader, argOptions, localResolver); - il.Emit(OpCodes.Br, loopEnd); - } - else - { - il.Emit(OpCodes.Br, readNext); - } - }, - () => + MemberInfo = member, + SwitchLabel = il.DefineLabel(), + }; + } + else + { + infoList[i] = new DeserializeInfo { - il.Emit(OpCodes.Br, readNext); - }); - - il.MarkLabel(readNext); - reader.EmitLdarg(); - il.EmitCall(MessagePackReaderTypeInfo.Skip); + MemberInfo = member, + LocalVariable = il.DeclareLocal(member.Type), + SwitchLabel = il.DefineLabel(), + }; + } + } + else + { + // return null MemberInfo, should filter null + if (gotoDefault == null) + { + gotoDefault = il.DefineLabel(); + } - il.MarkLabel(loopEnd); - }); + infoList[i] = new DeserializeInfo + { + SwitchLabel = gotoDefault.Value, + }; + } } - else + + return infoList; + } + + private static void BuildDeserializeInternalDeserializeLoopIntKey(ILGenerator il, Func tryEmitLoadCustomFormatter, ref ArgumentField argReader, ref ArgumentField argOptions, DeserializeInfo[] infoList, LocalBuilder localResolver, LocalBuilder localResult, LocalBuilder localLength, bool canOverwrite, Label? gotoDefault) + { + var key = il.DeclareLocal(typeof(int)); + var switchDefault = il.DefineLabel(); + var reader = argReader; + var options = argOptions; + + void ForBody(LocalBuilder forILocal) { - LocalBuilder key = il.DeclareLocal(typeof(int)); - Label switchDefault = il.DefineLabel(); + var loopEnd = il.DefineLabel(); - il.EmitIncrementFor(length, forILocal => - { - Label loopEnd = il.DefineLabel(); + il.EmitLdloc(forILocal); + il.EmitStloc(key); - il.EmitLdloc(forILocal); - il.EmitStloc(key); + // switch... local = Deserialize + il.EmitLdloc(key); - // switch... local = Deserialize - il.EmitLdloc(key); + il.Emit(OpCodes.Switch, infoList.Select(x => x.SwitchLabel).ToArray()); - il.Emit(OpCodes.Switch, infoList.Select(x => x.SwitchLabel).ToArray()); + il.MarkLabel(switchDefault); - il.MarkLabel(switchDefault); + // default, only read. reader.ReadNextBlock(); + reader.EmitLdarg(); + il.EmitCall(MessagePackReaderTypeInfo.Skip); + il.Emit(OpCodes.Br, loopEnd); - // default, only read. reader.ReadNextBlock(); - reader.EmitLdarg(); - il.EmitCall(MessagePackReaderTypeInfo.Skip); - il.Emit(OpCodes.Br, loopEnd); + if (gotoDefault != null) + { + il.MarkLabel(gotoDefault.Value); + il.Emit(OpCodes.Br, switchDefault); + } - if (gotoDefault != null) + var i = 0; + foreach (var item in infoList) + { + if (item.MemberInfo == null) { - il.MarkLabel(gotoDefault.Value); - il.Emit(OpCodes.Br, switchDefault); + continue; } - var i = 0; - foreach (DeserializeInfo item in infoList) + il.MarkLabel(item.SwitchLabel); + if (canOverwrite) { - if (item.MemberInfo != null) - { - il.MarkLabel(item.SwitchLabel); - EmitDeserializeValue(il, item, i++, tryEmitLoadCustomFormatter, reader, argOptions, localResolver); - il.Emit(OpCodes.Br, loopEnd); - } + BuildDeserializeInternalDeserializeValueAssignDirectly(il, item, i++, tryEmitLoadCustomFormatter, ref reader, ref options, localResolver, localResult); + } + else + { + BuildDeserializeInternalDeserializeValueAssignLocalVariable(il, item, i++, tryEmitLoadCustomFormatter, ref reader, ref options, localResolver, localResult); } - il.MarkLabel(loopEnd); - }); + il.Emit(OpCodes.Br, loopEnd); + } + + il.MarkLabel(loopEnd); } - // create result object - LocalBuilder structLocal = EmitNewObject(il, type, info, infoList); + il.EmitIncrementFor(localLength, ForBody); + } - // IMessagePackSerializationCallbackReceiver.OnAfterDeserialize() - if (type.GetTypeInfo().ImplementedInterfaces.Any(x => x == typeof(IMessagePackSerializationCallbackReceiver))) + private static void BuildDeserializeInternalDeserializeLoopStringKey(ILGenerator il, Func tryEmitLoadCustomFormatter, ref ArgumentField argReader, ref ArgumentField argOptions, DeserializeInfo[] infoList, LocalBuilder localResolver, LocalBuilder localResult, LocalBuilder localLength, bool canOverwrite, ObjectSerializationInfo info) + { + var automata = new AutomataDictionary(); + for (var i = 0; i < info.Members.Length; i++) { - // call directly - MethodInfo[] runtimeMethods = type.GetRuntimeMethods().Where(x => x.Name == "OnAfterDeserialize").ToArray(); - if (runtimeMethods.Length == 1) + automata.Add(info.Members[i].StringKey, i); + } + + var buffer = il.DeclareLocal(typeof(ReadOnlySpan)); + var longKey = il.DeclareLocal(typeof(ulong)); + var reader = argReader; + var options = argOptions; + + // for (int i = 0; i < len; i++) + void ForBody(LocalBuilder forILocal) + { + var readNext = il.DefineLabel(); + var loopEnd = il.DefineLabel(); + + reader.EmitLdarg(); + il.EmitCall(ReadStringSpan); + il.EmitStloc(buffer); + + // gen automata name lookup + void OnFoundAssignDirect(KeyValuePair x) { - if (info.IsClass) + var i = x.Value; + var item = infoList[i]; + if (item.MemberInfo != null) { - il.Emit(OpCodes.Dup); + BuildDeserializeInternalDeserializeValueAssignDirectly(il, item, i, tryEmitLoadCustomFormatter, ref reader, ref options, localResolver, localResult); + il.Emit(OpCodes.Br, loopEnd); } else { - il.EmitLdloca(structLocal); + il.Emit(OpCodes.Br, readNext); } - - il.Emit(OpCodes.Call, runtimeMethods[0]); // don't use EmitCall helper(must use 'Call') } - else + + void OnFoundAssignLocalVariable(KeyValuePair x) { - if (info.IsStruct) + var i = x.Value; + var item = infoList[i]; + if (item.MemberInfo != null) { - il.EmitLdloc(structLocal); - il.Emit(OpCodes.Box, type); + BuildDeserializeInternalDeserializeValueAssignLocalVariable(il, item, i, tryEmitLoadCustomFormatter, ref reader, ref options, localResolver, localResult); + il.Emit(OpCodes.Br, loopEnd); } else { - il.Emit(OpCodes.Dup); + il.Emit(OpCodes.Br, readNext); } + } + + void OnNotFound() + { + il.Emit(OpCodes.Br, readNext); + } - il.EmitCall(onAfterDeserialize); + if (canOverwrite) + { + automata.EmitMatch(il, buffer, longKey, OnFoundAssignDirect, OnNotFound); + } + else + { + automata.EmitMatch(il, buffer, longKey, OnFoundAssignLocalVariable, OnNotFound); } + + il.MarkLabel(readNext); + reader.EmitLdarg(); + il.EmitCall(MessagePackReaderTypeInfo.Skip); + + il.MarkLabel(loopEnd); } - // reader.Depth--; - reader.EmitLdarg(); + il.EmitIncrementFor(localLength, ForBody); + } + + private static void BuildDeserializeInternalTryReadNil(Type type, ILGenerator il, ref ArgumentField argReader) + { + // if(reader.TryReadNil()) { return null; } + var falseLabel = il.DefineLabel(); + argReader.EmitLdarg(); + il.EmitCall(MessagePackReaderTypeInfo.TryReadNil); + il.Emit(OpCodes.Brfalse_S, falseLabel); + if (type.GetTypeInfo().IsClass) + { + il.Emit(OpCodes.Ldnull); + il.Emit(OpCodes.Ret); + } + else + { + il.Emit(OpCodes.Ldstr, "typecode is null, struct not supported"); + il.Emit(OpCodes.Newobj, messagePackSerializationExceptionMessageOnlyConstructor); + il.Emit(OpCodes.Throw); + } + + il.MarkLabel(falseLabel); + } + + private static void BuildDeserializeInternalDepthUnStep(ILGenerator il, ref ArgumentField argReader) + { + argReader.EmitLdarg(); il.Emit(OpCodes.Dup); il.EmitCall(readerDepthGet); il.Emit(OpCodes.Ldc_I4_1); il.Emit(OpCodes.Sub_Ovf); il.EmitCall(readerDepthSet); + } - if (info.IsStruct) + private static void BuildDeserializeInternalOnAfterDeserialize(Type type, ObjectSerializationInfo info, ILGenerator il, LocalBuilder localResult) + { + if (type.GetTypeInfo().ImplementedInterfaces.All(x => x != typeof(IMessagePackSerializationCallbackReceiver))) { - il.Emit(OpCodes.Ldloc, structLocal); + return; } - il.Emit(OpCodes.Ret); + if (info.IsClass) + { + il.EmitLdloc(localResult); + } + + // call directly + var runtimeMethod = type.GetRuntimeMethods().SingleOrDefault(x => x.Name == "OnAfterDeserialize"); + if (runtimeMethod != null) + { + if (info.IsStruct) + { + il.EmitLdloca(localResult); + } + + il.Emit(OpCodes.Call, runtimeMethod); // don't use EmitCall helper(must use 'Call') + } + else + { + if (info.IsStruct) + { + il.EmitLdloc(localResult); + il.Emit(OpCodes.Box, type); + } + + il.EmitCall(onAfterDeserialize); + } } - private static void EmitDeserializeValue(ILGenerator il, DeserializeInfo info, int index, Func tryEmitLoadCustomFormatter, ArgumentField argReader, ArgumentField argOptions, LocalBuilder localResolver) + private static LocalBuilder BuildDeserializeInternalResolver(ObjectSerializationInfo info, ILGenerator il, ref ArgumentField argOptions) { - Label storeLabel = il.DefineLabel(); - ObjectSerializationInfo.EmittableMember member = info.MemberInfo; - Type t = member.Type; - Action emitter = tryEmitLoadCustomFormatter(index, member); + if (!info.ShouldUseFormatterResolver) + { + return default; + } + + // IFormatterResolver resolver = options.Resolver; + var localResolver = il.DeclareLocal(typeof(IFormatterResolver)); + argOptions.EmitLoad(); + il.EmitCall(getResolverFromOptions); + il.EmitStloc(localResolver); + return localResolver; + } + + private static LocalBuilder BuildDeserializeInternalReadHeaderLength(ObjectSerializationInfo info, ILGenerator il, ref ArgumentField argReader) + { + // var length = ReadMapHeader(ref byteSequence); + var length = il.DeclareLocal(typeof(int)); // [loc:1] + argReader.EmitLdarg(); + + il.EmitCall(info.IsIntKey ? MessagePackReaderTypeInfo.ReadArrayHeader : MessagePackReaderTypeInfo.ReadMapHeader); + + il.EmitStloc(length); + return length; + } + + private static void BuildDeserializeInternalDepthStep(ILGenerator il, ref ArgumentField argReader, ref ArgumentField argOptions) + { + argOptions.EmitLoad(); + il.EmitCall(getSecurityFromOptions); + argReader.EmitLdarg(); + il.EmitCall(securityDepthStep); + } + + // where T : new(); + private static void BuildDeserializeInternalCreateInstance(Type type, ObjectSerializationInfo info, ILGenerator il, LocalBuilder localResult) + { + // var result = new T(); + if (info.IsClass) + { + il.Emit(OpCodes.Newobj, info.BestmatchConstructor); + il.EmitStloc(localResult); + } + else + { + il.Emit(OpCodes.Ldloca, localResult); + il.Emit(OpCodes.Initobj, type); + } + } + + private static void BuildDeserializeInternalDeserializeValueAssignDirectly(ILGenerator il, DeserializeInfo info, int index, Func tryEmitLoadCustomFormatter, ref ArgumentField argReader, ref ArgumentField argOptions, LocalBuilder localResolver, LocalBuilder localResult) + { + var storeLabel = il.DefineLabel(); + var member = info.MemberInfo; + var t = member.Type; + var emitter = tryEmitLoadCustomFormatter(index, member); + + if (member.IsWritable) + { + if (localResult.LocalType.IsClass) + { + il.EmitLdloc(localResult); + } + else + { + il.EmitLdloca(localResult); + } + } + else if (info.IsInitializedLocalVariable != null) + { + il.EmitLdc_I4(1); + il.EmitStloc(info.IsInitializedLocalVariable); + } + if (emitter != null) { emitter(); @@ -1115,13 +1399,13 @@ private static void EmitDeserializeValue(ILGenerator il, DeserializeInfo info, i argOptions.EmitLoad(); il.EmitCall(getDeserialize(t)); } - else if (IsOptimizeTargetType(t)) + else if (ObjectSerializationInfo.IsOptimizeTargetType(t)) { if (!t.GetTypeInfo().IsValueType) { // As a nullable type (e.g. byte[] and string) we need to first call TryReadNil // if (reader.TryReadNil()) - Label readNonNilValueLabel = il.DefineLabel(); + var readNonNilValueLabel = il.DefineLabel(); argReader.EmitLdarg(); il.EmitCall(MessagePackReaderTypeInfo.TryReadNil); il.Emit(OpCodes.Brfalse_S, readNonNilValueLabel); @@ -1134,7 +1418,7 @@ private static void EmitDeserializeValue(ILGenerator il, DeserializeInfo info, i argReader.EmitLdarg(); if (t == typeof(byte[])) { - LocalBuilder local = il.DeclareLocal(typeof(ReadOnlySequence?)); + var local = il.DeclareLocal(typeof(ReadOnlySequence?)); il.EmitCall(MessagePackReaderTypeInfo.ReadBytes); il.EmitStloc(local); il.EmitLdloca(local); @@ -1155,94 +1439,77 @@ private static void EmitDeserializeValue(ILGenerator il, DeserializeInfo info, i } il.MarkLabel(storeLabel); - il.EmitStloc(info.LocalField); + if (member.IsWritable) + { + member.EmitStoreValue(il); + } + else + { + il.Emit(OpCodes.Pop); + } } - private static LocalBuilder EmitNewObject(ILGenerator il, Type type, ObjectSerializationInfo info, DeserializeInfo[] members) + private static void BuildDeserializeInternalDeserializeValueAssignLocalVariable(ILGenerator il, DeserializeInfo info, int index, Func tryEmitLoadCustomFormatter, ref ArgumentField argReader, ref ArgumentField argOptions, LocalBuilder localResolver, LocalBuilder localResult) { - if (info.IsClass) - { - EmitNewObjectConstructorArguments(il, info, members); - - il.Emit(OpCodes.Newobj, info.BestmatchConstructor); + var storeLabel = il.DefineLabel(); + var member = info.MemberInfo; + var t = member.Type; + var emitter = tryEmitLoadCustomFormatter(index, member); - foreach (DeserializeInfo item in members.Where(x => x.MemberInfo != null && x.MemberInfo.IsWritable)) - { - il.Emit(OpCodes.Dup); - il.EmitLdloc(item.LocalField); - item.MemberInfo.EmitStoreValue(il); - } + if (info.IsInitializedLocalVariable != null) + { + il.EmitLdc_I4(1); + il.EmitStloc(info.IsInitializedLocalVariable); + } - return null; + if (emitter != null) + { + emitter(); + argReader.EmitLdarg(); + argOptions.EmitLoad(); + il.EmitCall(getDeserialize(t)); } - else + else if (ObjectSerializationInfo.IsOptimizeTargetType(t)) { - LocalBuilder result = il.DeclareLocal(type); - if (info.BestmatchConstructor == null) - { - il.Emit(OpCodes.Ldloca, result); - il.Emit(OpCodes.Initobj, type); - } - else + if (!t.GetTypeInfo().IsValueType) { - EmitNewObjectConstructorArguments(il, info, members); + // As a nullable type (e.g. byte[] and string) we need to first call TryReadNil + // if (reader.TryReadNil()) + var readNonNilValueLabel = il.DefineLabel(); + argReader.EmitLdarg(); + il.EmitCall(MessagePackReaderTypeInfo.TryReadNil); + il.Emit(OpCodes.Brfalse_S, readNonNilValueLabel); + il.Emit(OpCodes.Ldnull); + il.Emit(OpCodes.Br, storeLabel); - il.Emit(OpCodes.Newobj, info.BestmatchConstructor); - il.Emit(OpCodes.Stloc, result); + il.MarkLabel(readNonNilValueLabel); } - foreach (DeserializeInfo item in members.Where(x => x.MemberInfo != null && x.MemberInfo.IsWritable)) + argReader.EmitLdarg(); + if (t == typeof(byte[])) { - il.EmitLdloca(result); - il.EmitLdloc(item.LocalField); - item.MemberInfo.EmitStoreValue(il); + var local = il.DeclareLocal(typeof(ReadOnlySequence?)); + il.EmitCall(MessagePackReaderTypeInfo.ReadBytes); + il.EmitStloc(local); + il.EmitLdloca(local); + il.EmitCall(ArrayFromNullableReadOnlySequence); } - - return result; // struct returns local result field - } - } - - private static void EmitNewObjectConstructorArguments(ILGenerator il, ObjectSerializationInfo info, DeserializeInfo[] members) - { - foreach (ObjectSerializationInfo.EmittableMemberAndConstructorParameter item in info.ConstructorParameters) - { - DeserializeInfo local = members.First(x => x.MemberInfo == item.MemberInfo); - il.EmitLdloc(local.LocalField); - - if (!item.ConstructorParameter.ParameterType.IsValueType && local.MemberInfo.IsValueType) + else { - // When a constructor argument of type object is being provided by a serialized member value that is a value type - // then that value must be boxed in order for the generated code to be valid (see issue #987). This may occur because - // the only requirement when determining whether a member value may be used to populate a constructor argument in an - // IsAssignableFrom check and typeof(object) IsAssignableFrom typeof(int), for example. - il.Emit(OpCodes.Box, local.MemberInfo.Type); + il.EmitCall(MessagePackReaderTypeInfo.TypeInfo.GetDeclaredMethods("Read" + t.Name).First(x => x.GetParameters().Length == 0)); } } - } + else + { + il.EmitLdloc(localResolver); + il.EmitCall(getFormatterWithVerify.MakeGenericMethod(t)); + argReader.EmitLdarg(); + argOptions.EmitLoad(); + il.EmitCall(getDeserialize(t)); + } - /// - /// Keep this list in sync with ShouldUseFormatterResolverHelper.PrimitiveTypes. - /// - private static bool IsOptimizeTargetType(Type type) - { - return type == typeof(Int16) - || type == typeof(Int32) - || type == typeof(Int64) - || type == typeof(UInt16) - || type == typeof(UInt32) - || type == typeof(UInt64) - || type == typeof(Single) - || type == typeof(Double) - || type == typeof(bool) - || type == typeof(byte) - || type == typeof(sbyte) - || type == typeof(char) - || type == typeof(byte[]) - - // Do not include types that resolvers are allowed to modify. - ////|| type == typeof(DateTime) // OldSpec has no support, so for that and perf reasons a .NET native DateTime resolver exists. - ////|| type == typeof(string) // https://github.com/Cysharp/MasterMemory provides custom formatter for string interning. - ; + il.MarkLabel(storeLabel); + il.EmitStloc(info.LocalVariable); } #pragma warning disable SA1311 // Static readonly fields should begin with upper-case letter @@ -1336,7 +1603,9 @@ private class DeserializeInfo { public ObjectSerializationInfo.EmittableMember MemberInfo { get; set; } - public LocalBuilder LocalField { get; set; } + public LocalBuilder LocalVariable { get; set; } + + public LocalBuilder IsInitializedLocalVariable { get; set; } public Label SwitchLabel { get; set; } } @@ -1402,6 +1671,8 @@ public bool IsStruct get { return !this.IsClass; } } + public bool ShouldUseFormatterResolver { get; private set; } + public ConstructorInfo BestmatchConstructor { get; set; } public EmittableMemberAndConstructorParameter[] ConstructorParameters { get; set; } @@ -1929,17 +2200,62 @@ public static ObjectSerializationInfo CreateOrNull(Type type, bool forceStringKe .ToArray(); } + var shouldUseFormatterResolver = false; + var membersArray = members.Where(m => m.IsExplicitContract || constructorParameters.Any(p => p.MemberInfo.Equals(m)) || m.IsWritable).ToArray(); + foreach (var member in membersArray) + { + if (IsOptimizeTargetType(member.Type)) + { + continue; + } + + var attr = member.GetMessagePackFormatterAttribute(); + if (!(attr is null)) + { + continue; + } + + shouldUseFormatterResolver = true; + break; + } + return new ObjectSerializationInfo { Type = type, IsClass = isClass, + ShouldUseFormatterResolver = shouldUseFormatterResolver, BestmatchConstructor = ctor, ConstructorParameters = constructorParameters.ToArray(), IsIntKey = isIntKey, - Members = members.Where(m => m.IsExplicitContract || constructorParameters.Any(p => p.MemberInfo.Equals(m)) || m.IsWritable).ToArray(), + Members = membersArray, }; } + /// + /// Keep this list in sync with ShouldUseFormatterResolverHelper.PrimitiveTypes. + /// + internal static bool IsOptimizeTargetType(Type type) + { + return type == typeof(Int16) + || type == typeof(Int32) + || type == typeof(Int64) + || type == typeof(UInt16) + || type == typeof(UInt32) + || type == typeof(UInt64) + || type == typeof(Single) + || type == typeof(Double) + || type == typeof(bool) + || type == typeof(byte) + || type == typeof(sbyte) + || type == typeof(char) + || type == typeof(byte[]) + + // Do not include types that resolvers are allowed to modify. + ////|| type == typeof(DateTime) // OldSpec has no support, so for that and perf reasons a .NET native DateTime resolver exists. + ////|| type == typeof(string) // https://github.com/Cysharp/MasterMemory provides custom formatter for string interning. + ; + } + private static IEnumerable GetAllFields(Type type) { if (type.BaseType is object) diff --git a/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/ContractlessStandardResolverTest.cs b/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/ContractlessStandardResolverTest.cs index 5fd8c31de..04d5b35ae 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/ContractlessStandardResolverTest.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/ContractlessStandardResolverTest.cs @@ -33,6 +33,16 @@ public class Person public object[] /*Address*/ Addresses { get; set; } } + public class DefaultValueStringKeyClassWithoutExplicitConstructor + { + public const int Prop1Constant = 11; + public const int Prop2Constant = 45; + + public int Prop1 { get; set; } = Prop1Constant; + + public int Prop2 { get; set; } = Prop2Constant; + } + public class V1 { public int ABCDEFG1 { get; set; } @@ -175,6 +185,18 @@ public void SimpleTest() ((string)d2["Street"]).Is("Ave."); } + [Fact] + public void DefaultValueStringKeyClassWithoutExplicitConstructorTest() + { + var dictionary = new Dictionary(); + + var result = MessagePackSerializer.Serialize(dictionary, Resolvers.ContractlessStandardResolver.Options); + + var instance = MessagePackSerializer.Deserialize(result, Resolvers.ContractlessStandardResolver.Options); + instance.Prop1.Is(DefaultValueStringKeyClassWithoutExplicitConstructor.Prop1Constant); + instance.Prop2.Is(DefaultValueStringKeyClassWithoutExplicitConstructor.Prop2Constant); + } + [Fact] public void Versioning() { diff --git a/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/DynamicObjectResolverTests.cs b/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/DynamicObjectResolverTests.cs index bb17e7ae8..9460d7f34 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/DynamicObjectResolverTests.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/DynamicObjectResolverTests.cs @@ -58,7 +58,7 @@ public void DeserializerSetsMissingPropertiesToDefaultValue() var instance = MessagePackSerializer.Deserialize(seq); Assert.Equal("Set", instance.Prop1); - Assert.Null(instance.Prop2); + Assert.Equal("Uninitialized", instance.Prop2); } [Fact] @@ -72,7 +72,7 @@ public void DeserializerSetsMissingPropertiesToDefaultValue_OrdinalKey() var instance = MessagePackSerializer.Deserialize(seq); Assert.Equal("Set", instance.Prop1); - Assert.Null(instance.Prop2); + Assert.Equal("Uninitialized", instance.Prop2); } /// @@ -155,6 +155,246 @@ private void PrivateMembersInBaseClass_Helper(MessagePackSerializerOptions optio Assert.Equal(obj.Name, obj2.Name); } + [Fact] + public void DefaultValueStringKeyClassWithoutExplicitConstructorTest() + { + var seq = new Sequence(); + var writer = new MessagePackWriter(seq); + writer.WriteMapHeader(0); + writer.Flush(); + + var instance = MessagePackSerializer.Deserialize(seq); + Assert.Equal(DefaultValueStringKeyClassWithoutExplicitConstructor.Prop1Constant, instance.Prop1); + Assert.Equal(DefaultValueStringKeyClassWithoutExplicitConstructor.Prop2Constant, instance.Prop2); + } + + [Fact] + public void DefaultValueStringKeyClassWithExplicitConstructorTest() + { + var seq = new Sequence(); + var writer = new MessagePackWriter(seq); + writer.WriteMapHeader(1); + writer.Write(nameof(DefaultValueStringKeyClassWithExplicitConstructor.Prop1)); + writer.Write(-1); + writer.Flush(); + + var instance = MessagePackSerializer.Deserialize(seq); + Assert.Equal(-1, instance.Prop1); + Assert.Equal(DefaultValueStringKeyClassWithExplicitConstructor.Prop2Constant, instance.Prop2); + } + + [Fact] + public void DefaultValueStringKeyClassWithExplicitConstructorSetPropertyTest() + { + var seq = new Sequence(); + var writer = new MessagePackWriter(seq); + writer.WriteMapHeader(2); + writer.Write(nameof(DefaultValueStringKeyClassWithExplicitConstructor.Prop1)); + writer.Write(-1); + writer.Write(nameof(DefaultValueStringKeyClassWithExplicitConstructor.Prop2)); + writer.Write(int.MaxValue); + writer.Flush(); + + var instance = MessagePackSerializer.Deserialize(seq); + Assert.Equal(-1, instance.Prop1); + Assert.Equal(int.MaxValue, instance.Prop2); + } + + [Fact] + public void DefaultValueStringKeyStructWithExplicitConstructorTest() + { + var seq = new Sequence(); + var writer = new MessagePackWriter(seq); + writer.WriteMapHeader(1); + writer.Write(nameof(DefaultValueStringKeyStructWithExplicitConstructor.Prop1)); + writer.Write(-1); + writer.Flush(); + + var instance = MessagePackSerializer.Deserialize(seq); + Assert.Equal(-1, instance.Prop1); + Assert.Equal(DefaultValueStringKeyStructWithExplicitConstructor.Prop2Constant, instance.Prop2); + } + + [Fact] + public void DefaultValueStringKeyStructWithExplicitConstructorSetPropertyTest() + { + var seq = new Sequence(); + var writer = new MessagePackWriter(seq); + writer.WriteMapHeader(2); + writer.Write(nameof(DefaultValueStringKeyStructWithExplicitConstructor.Prop1)); + writer.Write(-1); + writer.Write(nameof(DefaultValueStringKeyStructWithExplicitConstructor.Prop2)); + writer.Write(int.MinValue); + writer.Flush(); + + var instance = MessagePackSerializer.Deserialize(seq); + Assert.Equal(-1, instance.Prop1); + Assert.Equal(int.MinValue, instance.Prop2); + } + + [Fact] + public void DefaultValueIntKeyClassWithoutExplicitConstructorTest() + { + var seq = new Sequence(); + var writer = new MessagePackWriter(seq); + writer.WriteArrayHeader(0); + writer.Flush(); + + var instance = MessagePackSerializer.Deserialize(seq); + Assert.Equal(DefaultValueIntKeyClassWithoutExplicitConstructor.Prop1Constant, instance.Prop1); + Assert.Equal(DefaultValueIntKeyClassWithoutExplicitConstructor.Prop2Constant, instance.Prop2); + } + + [Fact] + public void DefaultValueIntKeyClassWithExplicitConstructorTest() + { + var seq = new Sequence(); + var writer = new MessagePackWriter(seq); + writer.WriteArrayHeader(1); + writer.Write(-1); + writer.Flush(); + + var instance = MessagePackSerializer.Deserialize(seq); + Assert.Equal(-1, instance.Prop1); + Assert.Equal(DefaultValueIntKeyClassWithExplicitConstructor.Prop2Constant, instance.Prop2); + } + + [Fact] + public void DefaultValueIntKeyClassWithExplicitConstructorSetPropertyTest() + { + var seq = new Sequence(); + var writer = new MessagePackWriter(seq); + writer.WriteArrayHeader(2); + writer.Write(-1); + writer.Write(42); + writer.Flush(); + + var instance = MessagePackSerializer.Deserialize(seq); + Assert.Equal(-1, instance.Prop1); + Assert.Equal(42, instance.Prop2); + } + + [Fact] + public void DefaultValueIntKeyStructWithExplicitConstructorTest() + { + var seq = new Sequence(); + var writer = new MessagePackWriter(seq); + writer.WriteArrayHeader(1); + writer.Write(-1); + writer.Flush(); + + var instance = MessagePackSerializer.Deserialize(seq); + Assert.Equal(-1, instance.Prop1); + Assert.Equal(DefaultValueIntKeyStructWithExplicitConstructor.Prop2Constant, instance.Prop2); + } + + [Fact] + public void DefaultValueIntKeyStructWithExplicitConstructorSetPropertyTest() + { + var seq = new Sequence(); + var writer = new MessagePackWriter(seq); + writer.WriteArrayHeader(2); + writer.Write(-1); + writer.Write(-98); + writer.Flush(); + + var instance = MessagePackSerializer.Deserialize(seq); + Assert.Equal(-1, instance.Prop1); + Assert.Equal(-98, instance.Prop2); + } + + [MessagePackObject(true)] + public class DefaultValueStringKeyClassWithoutExplicitConstructor + { + public const int Prop1Constant = 11; + public const int Prop2Constant = 45; + + public int Prop1 { get; set; } = Prop1Constant; + + public int Prop2 { get; set; } = Prop2Constant; + } + + [MessagePackObject(true)] + public class DefaultValueStringKeyClassWithExplicitConstructor + { + public const int Prop2Constant = 1419; + + public int Prop1 { get; set; } + + public int Prop2 { get; set; } + + public DefaultValueStringKeyClassWithExplicitConstructor(int prop1) + { + Prop1 = prop1; + Prop2 = Prop2Constant; + } + } + + [MessagePackObject(true)] + public struct DefaultValueStringKeyStructWithExplicitConstructor + { + public const int Prop2Constant = 198; + + public int Prop1 { get; set; } + + public int Prop2 { get; set; } + + public DefaultValueStringKeyStructWithExplicitConstructor(int prop1) + { + Prop1 = prop1; + Prop2 = Prop2Constant; + } + } + + [MessagePackObject] + public class DefaultValueIntKeyClassWithoutExplicitConstructor + { + public const int Prop1Constant = 33; + public const int Prop2Constant = -4; + + [Key(0)] + public int Prop1 { get; set; } = Prop1Constant; + + [Key(1)] + public int Prop2 { get; set; } = Prop2Constant; + } + + [MessagePackObject] + public class DefaultValueIntKeyClassWithExplicitConstructor + { + public const int Prop2Constant = -109; + + [Key(0)] + public int Prop1 { get; set; } + + [Key(1)] + public int Prop2 { get; set; } + + public DefaultValueIntKeyClassWithExplicitConstructor(int prop1) + { + Prop1 = prop1; + Prop2 = Prop2Constant; + } + } + + [MessagePackObject] + public struct DefaultValueIntKeyStructWithExplicitConstructor + { + public const int Prop2Constant = 31; + + [Key(0)] + public int Prop1 { get; set; } + + [Key(1)] + public int Prop2 { get; set; } + + public DefaultValueIntKeyStructWithExplicitConstructor(int prop1) + { + Prop1 = prop1; + Prop2 = Prop2Constant; + } + } + [DataContract] public class BaseClassWithVirtualProperty {