예제 #1
0
        public bool TryEmitDeserialiseCode(ILGenerator ilg, ThrowBlockGatherer throwBlocks, ICollection <string> errors, string name, Type targetType, LocalBuilder value, LocalBuilder unpacker, LocalBuilder contextLocal, DasherContext context, UnexpectedFieldBehaviour unexpectedFieldBehaviour)
        {
            // read value as string
            var s = ilg.DeclareLocal(typeof(string));

            ilg.Emit(OpCodes.Ldloc, unpacker);
            ilg.Emit(OpCodes.Ldloca, s);
            ilg.Emit(OpCodes.Call, Methods.Unpacker_TryReadString);

            // If the unpacker method failed (returned false), throw

            throwBlocks.ThrowIfFalse(() =>
            {
                ilg.Emit(OpCodes.Ldstr, $"Expecting string value for Version property {name}");
                ilg.LoadType(targetType);
                ilg.Emit(OpCodes.Newobj, Methods.DeserialisationException_Ctor_String_Type);
                ilg.Emit(OpCodes.Throw);
            });

            ilg.Emit(OpCodes.Ldloc, s);
            ilg.Emit(OpCodes.Newobj, typeof(Version).GetConstructor(new[] { typeof(string) }));
            ilg.Emit(OpCodes.Stloc, value);

            return(true);
        }
예제 #2
0
        public bool TryEmitDeserialiseCode(ILGenerator ilg, ThrowBlockGatherer throwBlocks, ICollection <string> errors, string name, Type targetType, LocalBuilder value, LocalBuilder unpacker, LocalBuilder contextLocal, DasherContext context, UnexpectedFieldBehaviour unexpectedFieldBehaviour)
        {
            if (!_unpackerTryReadMethodByType.TryGetValue(value.LocalType, out MethodInfo unpackerMethod))
            {
                errors.Add($"Type {targetType} does not map to a native MsgPack type");
                return(false);
            }

            ilg.Emit(OpCodes.Ldloc, unpacker);
            ilg.Emit(OpCodes.Ldloca, value);
            ilg.Emit(OpCodes.Call, unpackerMethod);

            // If the unpacker method failed (returned false), throw
            throwBlocks.ThrowIfFalse(() =>
            {
                ilg.Emit(OpCodes.Ldstr, "Unexpected MsgPack format for \"{0}\". Expected {1}, got {2}.");
                ilg.Emit(OpCodes.Ldstr, name);
                ilg.Emit(OpCodes.Ldstr, value.LocalType.Name);
                ilg.PeekFormatString(unpacker);
                ilg.Emit(OpCodes.Call, Methods.String_Format_String_Object_Object_Object);
                ilg.LoadType(targetType);
                ilg.Emit(OpCodes.Newobj, Methods.DeserialisationException_Ctor_String_Type);
                ilg.Emit(OpCodes.Throw);
            });

            return(true);
        }
예제 #3
0
        public bool TryEmitDeserialiseCode(ILGenerator ilg, ThrowBlockGatherer throwBlocks, ICollection <string> errors, string name, Type targetType, LocalBuilder value, LocalBuilder unpacker, LocalBuilder contextLocal, DasherContext context, UnexpectedFieldBehaviour unexpectedFieldBehaviour)
        {
            if (unexpectedFieldBehaviour == UnexpectedFieldBehaviour.Ignore)
            {
                // When ignoring unexpected fields, it doesn't matter what we receive in the message
                // as we will always accept the value and store 'null' on the target.
                ilg.Emit(OpCodes.Ldloc, unpacker);
                ilg.Emit(OpCodes.Call, Methods.Unpacker_SkipValue);
            }
            else
            {
                var end = ilg.DefineLabel();

                // Check for null
                ilg.Emit(OpCodes.Ldloc, unpacker);
                ilg.Emit(OpCodes.Call, Methods.Unpacker_TryReadNull);
                ilg.Emit(OpCodes.Brtrue, end);

                var mapSize = ilg.DeclareLocal(typeof(int));

                // Try to read the map header
                ilg.Emit(OpCodes.Ldloc, unpacker);
                ilg.Emit(OpCodes.Ldloca, mapSize);
                ilg.Emit(OpCodes.Call, Methods.Unpacker_TryReadMapLength);

                // If the unpacker method failed (returned false), throw
                throwBlocks.ThrowIfFalse(() =>
                {
                    ilg.Emit(OpCodes.Ldstr, $"Unable to deserialise {nameof(Empty)} type for \"{{0}}\". Expected MsgPack format Null or Map, got {{1}}.");
                    ilg.Emit(OpCodes.Ldstr, name);
                    ilg.PeekFormatString(unpacker);
                    ilg.Emit(OpCodes.Call, Methods.String_Format_String_Object_Object);
                    ilg.LoadType(targetType);
                    ilg.Emit(OpCodes.Newobj, Methods.DeserialisationException_Ctor_String_Type);
                    ilg.Emit(OpCodes.Throw);
                });

                ilg.Emit(OpCodes.Ldloc, mapSize);
                throwBlocks.ThrowIfTrue(() =>
                {
                    ilg.Emit(OpCodes.Ldstr, $"Unable to deserialise {nameof(Empty)} type for \"{{0}}\". Expected map with 0 entries, got {{1}}.");
                    ilg.Emit(OpCodes.Ldstr, name);
                    ilg.Emit(OpCodes.Ldloc, mapSize);
                    ilg.Emit(OpCodes.Box, typeof(int));
                    ilg.Emit(OpCodes.Call, Methods.String_Format_String_Object_Object);
                    ilg.LoadType(targetType);
                    ilg.Emit(OpCodes.Newobj, Methods.DeserialisationException_Ctor_String_Type);
                    ilg.Emit(OpCodes.Throw);
                });

                ilg.MarkLabel(end);
            }

            ilg.Emit(OpCodes.Ldnull);
            ilg.Emit(OpCodes.Stloc, value);

            return(true);
        }
예제 #4
0
        public bool TryEmitDeserialiseCode(ILGenerator ilg, ThrowBlockGatherer throwBlocks, ICollection <string> errors, string name, Type targetType, LocalBuilder value, LocalBuilder unpacker, LocalBuilder contextLocal, DasherContext context, UnexpectedFieldBehaviour unexpectedFieldBehaviour)
        {
            // Read value as a string
            var s = ilg.DeclareLocal(typeof(string));

            ilg.Emit(OpCodes.Ldloc, unpacker);
            ilg.Emit(OpCodes.Ldloca, s);
            ilg.Emit(OpCodes.Call, Methods.Unpacker_TryReadString);

            throwBlocks.ThrowIfFalse(() =>
            {
                ilg.Emit(OpCodes.Ldstr, "Unable to read string value for enum property \"{0}\" of type \"{1}\"");
                ilg.Emit(OpCodes.Ldstr, name);
                ilg.LoadType(value.LocalType);
                ilg.Emit(OpCodes.Call, Methods.String_Format_String_Object_Object);
                ilg.LoadType(targetType);
                ilg.Emit(OpCodes.Newobj, Methods.DeserialisationException_Ctor_String_Type);
                ilg.Emit(OpCodes.Throw);
            });

            ilg.Emit(OpCodes.Ldloc, s);
            ilg.Emit(OpCodes.Ldc_I4_1);
            ilg.Emit(OpCodes.Ldloca, value);
            ilg.Emit(OpCodes.Call, Methods.Enum_TryParse_OpenGeneric.MakeGenericMethod(value.LocalType));

            throwBlocks.ThrowIfFalse(() =>
            {
                ilg.Emit(OpCodes.Ldstr, "Unable to parse value \"{0}\" as a member of enum type \"{1}\"");
                ilg.Emit(OpCodes.Ldloc, s);
                ilg.LoadType(value.LocalType);
                ilg.Emit(OpCodes.Call, Methods.String_Format_String_Object_Object);
                ilg.LoadType(targetType);
                ilg.Emit(OpCodes.Newobj, Methods.DeserialisationException_Ctor_String_Type);
                ilg.Emit(OpCodes.Throw);
            });

            return(true);
        }
예제 #5
0
        public bool TryEmitDeserialiseCode(ILGenerator ilg, ThrowBlockGatherer throwBlocks, ICollection <string> errors, string name, Type targetType, LocalBuilder value, LocalBuilder unpacker, LocalBuilder contextLocal, DasherContext context, UnexpectedFieldBehaviour unexpectedFieldBehaviour)
        {
            // Read value as a string
            var s = ilg.DeclareLocal(typeof(string));

            ilg.Emit(OpCodes.Ldloc, unpacker);
            ilg.Emit(OpCodes.Ldloca, s);
            ilg.Emit(OpCodes.Call, Methods.Unpacker_TryReadString);

            throwBlocks.ThrowIfFalse(() =>
            {
                ilg.Emit(OpCodes.Ldstr, "Unexpected MsgPack format for \"{0}\". Expected string, got {1}.");
                ilg.Emit(OpCodes.Ldstr, name);
                ilg.PeekFormatString(unpacker);
                ilg.Emit(OpCodes.Call, Methods.String_Format_String_Object_Object);
                ilg.LoadType(targetType);
                ilg.Emit(OpCodes.Newobj, Methods.DeserialisationException_Ctor_String_Type);
                ilg.Emit(OpCodes.Throw);
            });

            ilg.Emit(OpCodes.Ldloc, s);
            ilg.Emit(OpCodes.Call, Methods.String_GetLength);

            // If the string's length is not 1, throw
            ilg.Emit(OpCodes.Ldc_I4_1);
            throwBlocks.ThrowIfNotEqual(() =>
            {
                ilg.Emit(OpCodes.Ldstr, "Unexpected string length for char value \"{0}\". Expected 1, got {1}.");
                ilg.Emit(OpCodes.Ldstr, name);
                ilg.Emit(OpCodes.Ldloc, s);
                ilg.Emit(OpCodes.Call, Methods.String_GetLength);
                ilg.Emit(OpCodes.Box, typeof(int));
                ilg.Emit(OpCodes.Call, Methods.String_Format_String_Object_Object);
                ilg.LoadType(targetType);
                ilg.Emit(OpCodes.Newobj, Methods.DeserialisationException_Ctor_String_Type);
                ilg.Emit(OpCodes.Throw);
            });

            ilg.Emit(OpCodes.Ldloc, s);
            ilg.Emit(OpCodes.Ldc_I4_0);
            ilg.Emit(OpCodes.Call, Methods.String_Indexer);
            ilg.Emit(OpCodes.Stloc, value);

            return(true);
        }
예제 #6
0
        public bool TryEmitDeserialiseCode(ILGenerator ilg, ThrowBlockGatherer throwBlocks, ICollection <string> errors, string name, Type targetType, LocalBuilder value, LocalBuilder unpacker, LocalBuilder contextLocal, DasherContext context, UnexpectedFieldBehaviour unexpectedFieldBehaviour)
        {
            ilg.Emit(OpCodes.Ldloc, unpacker);
            ilg.Emit(OpCodes.Ldloca, value);
            ilg.Emit(OpCodes.Call, Methods.Unpacker_TryReadByteArraySegment);

            throwBlocks.ThrowIfFalse(() =>
            {
                // not binary and not null
                ilg.Emit(OpCodes.Ldstr, "Unexpected MsgPack format for \"{0}\". Expected {1}, got {2}.");
                ilg.Emit(OpCodes.Ldstr, name);
                ilg.Emit(OpCodes.Ldstr, value.LocalType.Name);
                ilg.PeekFormatString(unpacker);
                ilg.Emit(OpCodes.Call, Methods.String_Format_String_Object_Object_Object);
                ilg.LoadType(targetType);
                ilg.Emit(OpCodes.Newobj, Methods.DeserialisationException_Ctor_String_Type);
                ilg.Emit(OpCodes.Throw);
            });

            return(true);
        }
예제 #7
0
        public bool TryEmitDeserialiseCode(ILGenerator ilg, ThrowBlockGatherer throwBlocks, ICollection <string> errors, string name, Type targetType, LocalBuilder value, LocalBuilder unpacker, LocalBuilder contextLocal, DasherContext context, UnexpectedFieldBehaviour unexpectedFieldBehaviour)
        {
            // Read value as a long
            var ticks = ilg.DeclareLocal(typeof(long));

            ilg.Emit(OpCodes.Ldloc, unpacker);
            ilg.Emit(OpCodes.Ldloca, ticks);
            ilg.Emit(OpCodes.Call, Methods.Unpacker_TryReadInt64);

            // If the unpacker method failed (returned false), throw
            throwBlocks.ThrowIfFalse(() =>
            {
                ilg.Emit(OpCodes.Ldstr, $"Expecting Int64 value for TimeSpan property {name}");
                ilg.LoadType(targetType);
                ilg.Emit(OpCodes.Newobj, Methods.DeserialisationException_Ctor_String_Type);
                ilg.Emit(OpCodes.Throw);
            });

            ilg.Emit(OpCodes.Ldloca, value);
            ilg.Emit(OpCodes.Ldloc, ticks);
            ilg.Emit(OpCodes.Call, Methods.TimeSpan_Ctor_Int64);

            return(true);
        }
예제 #8
0
        public bool TryEmitDeserialiseCode(ILGenerator ilg, ThrowBlockGatherer throwBlocks, ICollection <string> errors, string name, Type targetType, LocalBuilder value, LocalBuilder unpacker, LocalBuilder contextLocal, DasherContext context, UnexpectedFieldBehaviour unexpectedFieldBehaviour)
        {
            // Read value as a string
            var bytes = ilg.DeclareLocal(typeof(byte[]));

            ilg.Emit(OpCodes.Ldloc, unpacker);
            ilg.Emit(OpCodes.Ldloca, bytes);
            ilg.Emit(OpCodes.Call, Methods.Unpacker_TryReadBinary);

            // If the unpacker method failed (returned false), throw
            throwBlocks.ThrowIfFalse(() =>
            {
                ilg.Emit(OpCodes.Ldstr, "Unable to deserialise GUID value");
                ilg.LoadType(targetType);
                ilg.Emit(OpCodes.Newobj, Methods.DeserialisationException_Ctor_String_Type);
                ilg.Emit(OpCodes.Throw);
            });

            ilg.Emit(OpCodes.Ldloc, bytes);
            ilg.Emit(OpCodes.Newobj, Methods.Guid_Constructor_ByteArray);
            ilg.Emit(OpCodes.Stloc, value);

            return(true);
        }
예제 #9
0
        public bool TryEmitDeserialiseCode(ILGenerator ilg, ThrowBlockGatherer throwBlocks, ICollection <string> errors, string name, Type targetType, LocalBuilder value, LocalBuilder unpacker, LocalBuilder contextLocal, DasherContext context, UnexpectedFieldBehaviour unexpectedFieldBehaviour)
        {
            var rodicType = value.LocalType;
            var dicType   = typeof(Dictionary <,>).MakeGenericType(rodicType.GenericTypeArguments);
            var keyType   = rodicType.GetGenericArguments()[0];
            var valueType = rodicType.GetGenericArguments()[1];

            // read map length
            var count = ilg.DeclareLocal(typeof(int));

            ilg.Emit(OpCodes.Ldloc, unpacker);
            ilg.Emit(OpCodes.Ldloca, count);
            ilg.Emit(OpCodes.Call, Methods.Unpacker_TryReadMapLength);

            // verify read correctly
            throwBlocks.ThrowIfFalse(() =>
            {
                ilg.Emit(OpCodes.Ldstr, "Expecting collection data to be encoded a map");
                ilg.LoadType(targetType);
                ilg.Emit(OpCodes.Newobj, Methods.DeserialisationException_Ctor_String_Type);
                ilg.Emit(OpCodes.Throw);
            });

            var keyValue   = ilg.DeclareLocal(keyType);
            var valueValue = ilg.DeclareLocal(valueType);

            // create a mutable dictionary to store key/values
            var dic = ilg.DeclareLocal(dicType);

            ilg.Emit(OpCodes.Newobj, dicType.GetConstructor(Type.EmptyTypes));
            ilg.Emit(OpCodes.Stloc, dic);

            // begin loop
            var loopStart = ilg.DefineLabel();
            var loopTest  = ilg.DefineLabel();
            var loopEnd   = ilg.DefineLabel();

            var i = ilg.DeclareLocal(typeof(int));

            ilg.Emit(OpCodes.Ldc_I4_0);
            ilg.Emit(OpCodes.Stloc, i);

            ilg.Emit(OpCodes.Br, loopTest);
            ilg.MarkLabel(loopStart);

            // loop body

            // read key
            if (!DeserialiserEmitter.TryEmitDeserialiseCode(ilg, throwBlocks, errors, name, targetType, keyValue, unpacker, context, contextLocal, unexpectedFieldBehaviour))
            {
                errors.Add($"Unable to deserialise IReadOnlyDictionary<> key type {keyType} from MsgPack data.");
                return(false);
            }

            // read value
            if (!DeserialiserEmitter.TryEmitDeserialiseCode(ilg, throwBlocks, errors, name, targetType, valueValue, unpacker, context, contextLocal, unexpectedFieldBehaviour))
            {
                errors.Add($"Unable to deserialise IReadOnlyDictionary<> value type {keyType} from MsgPack data.");
                return(false);
            }

            ilg.Emit(OpCodes.Ldloc, dic);
            ilg.Emit(OpCodes.Ldloc, keyValue);
            ilg.Emit(OpCodes.Ldloc, valueValue);
            ilg.Emit(OpCodes.Callvirt, dicType.GetMethod(nameof(Dictionary <int, int> .Add)));

            // loop counter increment
            ilg.Emit(OpCodes.Ldloc, i);
            ilg.Emit(OpCodes.Ldc_I4_1);
            ilg.Emit(OpCodes.Add);
            ilg.Emit(OpCodes.Stloc, i);

            // loop test
            ilg.MarkLabel(loopTest);
            ilg.Emit(OpCodes.Ldloc, i);
            ilg.Emit(OpCodes.Ldloc, count);
            ilg.Emit(OpCodes.Clt);
            ilg.Emit(OpCodes.Brtrue, loopStart);

            // after loop
            ilg.MarkLabel(loopEnd);

            ilg.Emit(OpCodes.Ldloc, dic);
            ilg.Emit(OpCodes.Stloc, value);

            return(true);
        }
예제 #10
0
        public bool TryEmitDeserialiseCode(ILGenerator ilg, ThrowBlockGatherer throwBlocks, ICollection <string> errors, string name, Type targetType, LocalBuilder value, LocalBuilder unpacker, LocalBuilder contextLocal, DasherContext context, UnexpectedFieldBehaviour unexpectedFieldBehaviour)
        {
            var tupleType = value.LocalType;
            var tupleSize = tupleType.GenericTypeArguments.Length;

            Debug.Assert(tupleSize > 1);

            // read array length
            var count = ilg.DeclareLocal(typeof(int));

            ilg.Emit(OpCodes.Ldloc, unpacker);
            ilg.Emit(OpCodes.Ldloca, count);
            ilg.Emit(OpCodes.Call, Methods.Unpacker_TryReadArrayLength);

            // verify read correctly
            throwBlocks.ThrowIfFalse(() =>
            {
                ilg.Emit(OpCodes.Ldstr, "Expecting tuple data to be encoded as array");
                ilg.LoadType(targetType);
                ilg.Emit(OpCodes.Newobj, Methods.DeserialisationException_Ctor_String_Type);
                ilg.Emit(OpCodes.Throw);
            });

            // Ensure the array has the expected number of items
            ilg.Emit(OpCodes.Ldloc, count);
            ilg.Emit(OpCodes.Ldc_I4, tupleSize);

            throwBlocks.ThrowIfNotEqual(() =>
            {
                ilg.Emit(OpCodes.Ldstr, $"Received array must have length {tupleSize} for type {tupleType.FullName}");
                ilg.LoadType(targetType);
                ilg.Emit(OpCodes.Newobj, Methods.DeserialisationException_Ctor_String_Type);
                ilg.Emit(OpCodes.Throw);
            });

            var success = true;

            var locals = new List <LocalBuilder>();
            var i      = 1;

            foreach (var type in tupleType.GenericTypeArguments)
            {
                var local = ilg.DeclareLocal(type);
                locals.Add(local);
                if (!DeserialiserEmitter.TryEmitDeserialiseCode(ilg, throwBlocks, errors, $"Item{i}", targetType, local, unpacker, context, contextLocal, unexpectedFieldBehaviour))
                {
                    errors.Add($"Unable to create deserialiser for tuple item type {type}");
                    success = false;
                }
                i++;
            }

            ilg.Emit(OpCodes.Ldloca, value);

            foreach (var local in locals)
            {
                ilg.Emit(OpCodes.Ldloc, local);
            }

            ilg.Emit(OpCodes.Call, tupleType.GetConstructor(tupleType.GenericTypeArguments));

            return(success);
        }
예제 #11
0
        public bool TryEmitDeserialiseCode(ILGenerator ilg, ThrowBlockGatherer throwBlocks, ICollection <string> errors, string name, Type targetType, LocalBuilder value, LocalBuilder unpacker, LocalBuilder contextLocal, DasherContext context, UnexpectedFieldBehaviour unexpectedFieldBehaviour)
        {
            // Ensure we have an array of two values
            var arrayLength = ilg.DeclareLocal(typeof(int));

            ilg.Emit(OpCodes.Ldloc, unpacker);
            ilg.Emit(OpCodes.Ldloca, arrayLength);
            ilg.Emit(OpCodes.Call, Methods.Unpacker_TryReadArrayLength);

            // If the unpacker method failed (returned false), throw
            throwBlocks.ThrowIfFalse(() =>
            {
                ilg.Emit(OpCodes.Ldstr, $"Expecting array header for DateTimeOffset property {name}");
                ilg.LoadType(targetType);
                ilg.Emit(OpCodes.Newobj, Methods.DeserialisationException_Ctor_String_Type);
                ilg.Emit(OpCodes.Throw);
            });

            ilg.Emit(OpCodes.Ldloc, arrayLength);
            ilg.Emit(OpCodes.Ldc_I4_2);

            throwBlocks.ThrowIfNotEqual(() =>
            {
                ilg.Emit(OpCodes.Ldstr, $"Expecting array to contain two items for DateTimeOffset property {name}");
                ilg.LoadType(targetType);
                ilg.Emit(OpCodes.Newobj, Methods.DeserialisationException_Ctor_String_Type);
                ilg.Emit(OpCodes.Throw);
            });

            // Read ticks
            var ticks = ilg.DeclareLocal(typeof(long));

            ilg.Emit(OpCodes.Ldloc, unpacker);
            ilg.Emit(OpCodes.Ldloca, ticks);
            ilg.Emit(OpCodes.Call, Methods.Unpacker_TryReadInt64);

            // If the unpacker method failed (returned false), throw
            throwBlocks.ThrowIfFalse(() =>
            {
                ilg.Emit(OpCodes.Ldstr, $"Expecting Int64 value for ticks component of DateTimeOffset property {name}");
                ilg.LoadType(targetType);
                ilg.Emit(OpCodes.Newobj, Methods.DeserialisationException_Ctor_String_Type);
                ilg.Emit(OpCodes.Throw);
            });

            // Read offset
            var minutes = ilg.DeclareLocal(typeof(short));

            ilg.Emit(OpCodes.Ldloc, unpacker);
            ilg.Emit(OpCodes.Ldloca, minutes);
            ilg.Emit(OpCodes.Call, Methods.Unpacker_TryReadInt16);

            // If the unpacker method failed (returned false), throw
            throwBlocks.ThrowIfFalse(() =>
            {
                ilg.Emit(OpCodes.Ldstr, $"Expecting Int16 value for offset component of DateTimeOffset property {name}");
                ilg.LoadType(targetType);
                ilg.Emit(OpCodes.Newobj, Methods.DeserialisationException_Ctor_String_Type);
                ilg.Emit(OpCodes.Throw);
            });

            // Compose the final DateTimeOffset
            ilg.Emit(OpCodes.Ldloca, value);
            ilg.Emit(OpCodes.Ldloc, ticks);
            ilg.Emit(OpCodes.Ldloc, minutes);
            ilg.Emit(OpCodes.Conv_I8);
            ilg.Emit(OpCodes.Ldc_I4, TicksPerMinute);
            ilg.Emit(OpCodes.Conv_I8);
            ilg.Emit(OpCodes.Mul);
            ilg.Emit(OpCodes.Conv_I8);
            ilg.Emit(OpCodes.Call, Methods.TimeSpan_FromTicks);
            ilg.Emit(OpCodes.Call, Methods.DateTimeOffset_Ctor_Long_TimeSpan);

            return(true);
        }
예제 #12
0
        public bool TryEmitDeserialiseCode(ILGenerator ilg, ThrowBlockGatherer throwBlocks, ICollection <string> errors, string name, Type targetType, LocalBuilder value, LocalBuilder unpacker, LocalBuilder contextLocal, DasherContext context, UnexpectedFieldBehaviour unexpectedFieldBehaviour)
        {
            var lblValidateArrayLength = ilg.DefineLabel();
            var lblExit = ilg.DefineLabel();

            // read the array length
            var count = ilg.DeclareLocal(typeof(int));

            ilg.Emit(OpCodes.Ldloc, unpacker);
            ilg.Emit(OpCodes.Ldloca, count);
            ilg.Emit(OpCodes.Call, Methods.Unpacker_TryReadArrayLength);
            ilg.Emit(OpCodes.Brtrue_S, lblValidateArrayLength);

            // test for empty map (empty type)
            ilg.Emit(OpCodes.Ldloc, unpacker);
            ilg.Emit(OpCodes.Call, Methods.Unpacker_TryPeekEmptyMap);

            throwBlocks.ThrowIfFalse(() =>
            {
                ilg.Emit(OpCodes.Ldstr, "Union values must be encoded as an array for property \"{0}\" of type \"{1}\"");
                ilg.Emit(OpCodes.Ldstr, name);
                ilg.LoadType(value.LocalType);
                ilg.Emit(OpCodes.Call, Methods.String_Format_String_Object_Object);
                ilg.LoadType(targetType);
                ilg.Emit(OpCodes.Newobj, Methods.DeserialisationException_Ctor_String_Type);
                ilg.Emit(OpCodes.Throw);
            });

            ilg.Emit(OpCodes.Ldnull);
            ilg.Emit(OpCodes.Stloc, value);
            ilg.Emit(OpCodes.Br, lblExit);

            // ensure we have two items in the array
            ilg.MarkLabel(lblValidateArrayLength);
            ilg.Emit(OpCodes.Ldloc, count);
            ilg.Emit(OpCodes.Ldc_I4_2);
            throwBlocks.ThrowIfNotEqual(() =>
            {
                ilg.Emit(OpCodes.Ldstr, "Union array should have 2 elements (not {0}) for property \"{1}\" of type \"{2}\"");
                ilg.Emit(OpCodes.Ldloc, count);
                ilg.Emit(OpCodes.Box, typeof(int));
                ilg.Emit(OpCodes.Ldstr, name);
                ilg.LoadType(value.LocalType);
                ilg.Emit(OpCodes.Call, Methods.String_Format_String_Object_Object_Object);
                ilg.LoadType(targetType);
                ilg.Emit(OpCodes.Newobj, Methods.DeserialisationException_Ctor_String_Type);
                ilg.Emit(OpCodes.Throw);
            });

            // read the serialised type name
            var typeName = ilg.DeclareLocal(typeof(string));

            ilg.Emit(OpCodes.Ldloc, unpacker);
            ilg.Emit(OpCodes.Ldloca, typeName);
            ilg.Emit(OpCodes.Call, Methods.Unpacker_TryReadString);

            throwBlocks.ThrowIfFalse(() =>
            {
                ilg.Emit(OpCodes.Ldstr, "Unable to read union type name for property \"{0}\" of type \"{1}\"");
                ilg.Emit(OpCodes.Ldstr, name);
                ilg.LoadType(value.LocalType);
                ilg.Emit(OpCodes.Call, Methods.String_Format_String_Object_Object);
                ilg.LoadType(targetType);
                ilg.Emit(OpCodes.Newobj, Methods.DeserialisationException_Ctor_String_Type);
                ilg.Emit(OpCodes.Throw);
            });

            var success = true;

            // loop through types within the union, looking for a matching type name
            var labelNextType = ilg.DefineLabel();

            foreach (var type in value.LocalType.GetGenericArguments())
            {
                var expectedTypeName = UnionEncoding.GetTypeName(type);

                ilg.Emit(OpCodes.Ldloc, typeName);
                ilg.Emit(OpCodes.Ldstr, expectedTypeName);
                ilg.Emit(OpCodes.Call, Methods.String_Equals_String_String);

                // continue if this type doesn't match the union's values
                ilg.Emit(OpCodes.Brfalse, labelNextType);

                // we have a match
                // read the value
                var readValue = ilg.DeclareLocal(type);
                if (!DeserialiserEmitter.TryEmitDeserialiseCode(ilg, throwBlocks, errors, name, targetType, readValue, unpacker, context, contextLocal, unexpectedFieldBehaviour))
                {
                    errors.Add($"Unable to deserialise union member type {type}");
                    success = false;
                }

                // create the union
                ilg.Emit(OpCodes.Ldloc, readValue);
                ilg.Emit(OpCodes.Call, value.LocalType.GetMethod(nameof(Union <int, int> .Create), new[] { type }));

                // store it in the result value
                ilg.Emit(OpCodes.Stloc, value);

                // exit the loop
                ilg.Emit(OpCodes.Br, lblExit);

                ilg.MarkLabel(labelNextType);
                labelNextType = ilg.DefineLabel();
            }

            // If we exhaust the loop, throws
            ilg.MarkLabel(labelNextType);
            throwBlocks.Throw(() =>
            {
                // TODO include received type name in error message and some more general info
                ilg.Emit(OpCodes.Ldstr, "No match on union type");
                ilg.Emit(OpCodes.Newobj, Methods.Exception_Ctor_String);
                ilg.Emit(OpCodes.Throw);
            });

            ilg.MarkLabel(lblExit);

            return(success);
        }
예제 #13
0
        public bool TryEmitDeserialiseCode(ILGenerator ilg, ThrowBlockGatherer throwBlocks, ICollection <string> errors, string name, Type targetType, LocalBuilder value, LocalBuilder unpacker, LocalBuilder contextLocal, DasherContext context, UnexpectedFieldBehaviour unexpectedFieldBehaviour)
        {
            var elementType = value.LocalType.GetGenericArguments().Single();

            // read list length
            var count = ilg.DeclareLocal(typeof(int));

            ilg.Emit(OpCodes.Ldloc, unpacker);
            ilg.Emit(OpCodes.Ldloca, count);
            ilg.Emit(OpCodes.Call, Methods.Unpacker_TryReadArrayLength);

            // verify read correctly
            throwBlocks.ThrowIfFalse(() =>
            {
                ilg.Emit(OpCodes.Ldstr, "Expecting collection data to be encoded as an array");
                ilg.LoadType(targetType);
                ilg.Emit(OpCodes.Newobj, Methods.DeserialisationException_Ctor_String_Type);
                ilg.Emit(OpCodes.Throw);
            });

            var array = ilg.DeclareLocal(elementType.MakeArrayType());

            var nonZeroLength = ilg.DefineLabel();
            var loopStart     = ilg.DefineLabel();
            var loopTest      = ilg.DefineLabel();
            var loopEnd       = ilg.DefineLabel();

            // if zero-length, return singleton empty array
            ilg.Emit(OpCodes.Ldloc, count);
            ilg.Emit(OpCodes.Brtrue, nonZeroLength);
            ilg.Emit(OpCodes.Call, Methods.EmptyArrayInstanceGetter(elementType));
            ilg.Emit(OpCodes.Stloc, array);

            ilg.Emit(OpCodes.Br, loopEnd);

            ilg.MarkLabel(nonZeroLength);

            // create an array to store values
            ilg.Emit(OpCodes.Ldloc, count);
            ilg.Emit(OpCodes.Newarr, elementType);

            ilg.Emit(OpCodes.Stloc, array);

            // begin loop
            var i = ilg.DeclareLocal(typeof(int));

            ilg.Emit(OpCodes.Ldc_I4_0);
            ilg.Emit(OpCodes.Stloc, i);

            ilg.Emit(OpCodes.Br, loopTest);
            ilg.MarkLabel(loopStart);

            // loop body
            var element = ilg.DeclareLocal(elementType);

            if (!DeserialiserEmitter.TryEmitDeserialiseCode(ilg, throwBlocks, errors, name, targetType, element, unpacker, context, contextLocal, unexpectedFieldBehaviour))
            {
                errors.Add($"Unable to deserialise IReadOnlyList<> element type {elementType} from MsgPack data.");
                return(false);
            }

            ilg.Emit(OpCodes.Ldloc, array);
            ilg.Emit(OpCodes.Ldloc, i);
            ilg.Emit(OpCodes.Ldloc, element);
            ilg.Emit(OpCodes.Stelem, elementType);

            // loop counter increment
            ilg.Emit(OpCodes.Ldloc, i);
            ilg.Emit(OpCodes.Ldc_I4_1);
            ilg.Emit(OpCodes.Add);
            ilg.Emit(OpCodes.Stloc, i);

            // loop test
            ilg.MarkLabel(loopTest);
            ilg.Emit(OpCodes.Ldloc, i);
            ilg.Emit(OpCodes.Ldloc, count);
            ilg.Emit(OpCodes.Clt);
            ilg.Emit(OpCodes.Brtrue, loopStart);

            // after loop
            ilg.MarkLabel(loopEnd);

            ilg.Emit(OpCodes.Ldloc, array);
            ilg.Emit(OpCodes.Stloc, value);

            return(true);
        }
예제 #14
0
        public bool TryEmitDeserialiseCode(ILGenerator ilg, ThrowBlockGatherer throwBlocks, ICollection <string> errors, string name, Type targetType, LocalBuilder value, LocalBuilder unpacker, LocalBuilder contextLocal, DasherContext context, UnexpectedFieldBehaviour unexpectedFieldBehaviour)
        {
            if (!TryValidateComplexType(targetType, errors))
            {
                return(false);
            }

            var constructors = targetType.GetConstructors(BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Instance);

            Debug.Assert(constructors.Length == 1, "Should have one constructor (validation should be performed before call)");
            var constructor = constructors[0];
            var parameters  = constructor.GetParameters();

            void ThrowException()
            {
                ilg.LoadType(targetType);
                ilg.Emit(OpCodes.Newobj, Methods.DeserialisationException_Ctor_String_Type);
                ilg.Emit(OpCodes.Throw);
            }

            #region Initialise locals for constructor args

            var valueLocals    = new LocalBuilder[parameters.Length];
            var valueSetLocals = new LocalBuilder[parameters.Length];

            for (var i = 0; i < parameters.Length; i++)
            {
                var parameter = parameters[i];

                valueLocals[i]    = ilg.DeclareLocal(parameter.ParameterType);
                valueSetLocals[i] = ilg.DeclareLocal(typeof(int));

                if (parameter.HasDefaultValue)
                {
                    // set default values on params
                    if (NullableValueProvider.IsNullableValueType(parameter.ParameterType))
                    {
                        ilg.Emit(OpCodes.Ldloca, valueLocals[i]);
                        if (parameter.DefaultValue == null)
                        {
                            ilg.Emit(OpCodes.Initobj, parameter.ParameterType);
                        }
                        else
                        {
                            ilg.LoadConstant(parameter.DefaultValue);
                            ilg.Emit(OpCodes.Call, parameter.ParameterType.GetConstructor(new[] { parameter.ParameterType.GetGenericArguments().Single() }));
                        }
                    }
                    else if (parameter.DefaultValue == null && parameter.ParameterType.GetTypeInfo().IsValueType)
                    {
                        // When DefaultValue is null for a parameter of value type, the programmer must have
                        // stated default(T) as the default value.
                        ilg.Emit(OpCodes.Ldloca, valueLocals[i]);
                        ilg.Emit(OpCodes.Initobj, valueLocals[i].LocalType);
                    }
                    else
                    {
                        ilg.LoadConstant(parameter.DefaultValue);
                        ilg.Emit(OpCodes.Stloc, valueLocals[i]);
                    }
                    // set 'valueSet' to true
                    // note we use the second LSb to indicate a default value
                    ilg.Emit(OpCodes.Ldc_I4_2);
                    ilg.Emit(OpCodes.Stloc, valueSetLocals[i]);
                }
                else
                {
                    // set 'valueSet' to false
                    ilg.Emit(OpCodes.Ldc_I4_0);
                    ilg.Emit(OpCodes.Stloc, valueSetLocals[i]);
                }
            }

            #endregion

            #region Read map length

            var mapSize = ilg.DeclareLocal(typeof(int));
            {
                // MsgPack messages may be single values, arrays, maps, or any arbitrary
                // combination of these types. Our convention is to require messages to
                // be encoded as maps where the key is the property name.
                //
                // MsgPack maps begin with a header indicating the number of pairs
                // within the map. We read this here.
                ilg.Emit(OpCodes.Ldloc, unpacker);
                ilg.Emit(OpCodes.Ldloca, mapSize);
                ilg.Emit(OpCodes.Call, Methods.Unpacker_TryReadMapLength);

                // If false was returned, then the next MsgPack value is not a map
                throwBlocks.ThrowIfFalse(() =>
                {
                    // Check if it's a null
                    ilg.Emit(OpCodes.Ldloc, unpacker);
                    ilg.Emit(OpCodes.Call, Methods.Unpacker_TryReadNull);
                    var lblNotNull = ilg.DefineLabel();
                    ilg.Emit(OpCodes.Brfalse_S, lblNotNull);
                    {
                        // value is null
                        ilg.Emit(OpCodes.Ldnull);
                        ilg.Emit(OpCodes.Ret);
                    }
                    ilg.MarkLabel(lblNotNull);
                    ilg.Emit(OpCodes.Ldloc, unpacker);
                    ilg.Emit(OpCodes.Call, Methods.Unpacker_HasStreamEnded_Get);
                    var lblNotEmpty = ilg.DefineLabel();
                    ilg.Emit(OpCodes.Brfalse_S, lblNotEmpty);
                    ilg.Emit(OpCodes.Ldstr, "Data stream empty");
                    ThrowException();
                    ilg.MarkLabel(lblNotEmpty);
                    ilg.Emit(OpCodes.Ldstr, "Message must be encoded as a MsgPack map, not \"{0}\".");
                    ilg.PeekFormatString(unpacker);
                    ilg.Emit(OpCodes.Call, Methods.String_Format_String_Object);
                    ThrowException();
                });
            }

            #endregion

            #region Loop through each key/value pair in the map

            {
                #region Initialise loop

                // Create a loop counter, initialised to zero
                var loopIndex = ilg.DeclareLocal(typeof(long));
                ilg.Emit(OpCodes.Ldc_I4_0);
                ilg.Emit(OpCodes.Conv_I8);
                ilg.Emit(OpCodes.Stloc, loopIndex);

                // Create labels to jump to within the loop
                var lblLoopTest  = ilg.DefineLabel(); // Comparing counter to map size
                var lblLoopExit  = ilg.DefineLabel(); // The first instruction after the loop
                var lblLoopStart = ilg.DefineLabel(); // The first instruction within the loop

                // Run the test first
                ilg.Emit(OpCodes.Br, lblLoopTest);

                // Mark the first instruction within the loop
                ilg.MarkLabel(lblLoopStart);

                #endregion

                #region Read field name

                // Although MsgPack allows map keys to be of any arbitrary type, our convention
                // is to require keys to be strings. We read the key here.
                var key = ilg.DeclareLocal(typeof(string));
                {
                    ilg.Emit(OpCodes.Ldloc, unpacker);
                    ilg.Emit(OpCodes.Ldloca, key);
                    ilg.Emit(OpCodes.Call, Methods.Unpacker_TryReadString);

                    // If false was returned, the data stream ended
                    throwBlocks.ThrowIfFalse(() =>
                    {
                        ilg.Emit(OpCodes.Ldstr, "Data stream ended.");
                        ThrowException();
                    });
                }

                #endregion

                #region Match name to expected field, then attempt to deserialise by expected type

                // Build a chain of if/elseif/elseif... blocks for each of the expected fields.
                // It could be slightly more efficient here to generate a O(log(N)) tree-based lookup,
                // but that would take quite some engineering. Let's see if there's a significant perf
                // hit here or not first.

                var lblEndIfChain = ilg.DefineLabel();

                // TODO how much perf diff if properties sorted lexicographically, so no searching?
                Label?nextLabel = null;
                for (var parameterIndex = 0; parameterIndex < parameters.Length; parameterIndex++)
                {
                    // Mark the beginning of the next block, as used if the previous block's condition failed
                    if (nextLabel != null)
                    {
                        ilg.MarkLabel(nextLabel.Value);
                    }
                    nextLabel = ilg.DefineLabel();

                    // Compare map's key with this parameter's name in a case insensitive way
                    ilg.Emit(OpCodes.Ldloc, key);
                    ilg.Emit(OpCodes.Ldstr, parameters[parameterIndex].Name);
                    ilg.Emit(OpCodes.Ldc_I4_5); // StringComparison.OrdinalIgnoreCase
                    ilg.Emit(OpCodes.Callvirt, Methods.String_Equals_String_StringComparison);

                    // If the key doesn't match this property, go to the next block
                    ilg.Emit(OpCodes.Brfalse, nextLabel.Value);

                    #region Test for duplicate fields

                    // Verify we haven't already seen a value for this parameter
                    {
                        // Mask out the LSb and see if it is set. If so, we've seen this property
                        // already in this message, which is invalid.
                        ilg.Emit(OpCodes.Ldloc, valueSetLocals[parameterIndex]);
                        ilg.Emit(OpCodes.Ldc_I4_1);
                        ilg.Emit(OpCodes.And);
                        throwBlocks.ThrowIfTrue(() =>
                        {
                            ilg.Emit(OpCodes.Ldstr, "Encountered duplicate field \"{0}\" for type \"{1}\".");
                            ilg.Emit(OpCodes.Ldloc, key);
                            ilg.Emit(OpCodes.Ldstr, targetType.Name);
                            ilg.Emit(OpCodes.Call, Methods.String_Format_String_Object_Object);
                            ThrowException();
                        });

                        // Record the fact that we've seen this property
                        ilg.Emit(OpCodes.Ldloc, valueSetLocals[parameterIndex]);
                        ilg.Emit(OpCodes.Ldc_I4_1);
                        ilg.Emit(OpCodes.Or);
                        ilg.Emit(OpCodes.Stloc, valueSetLocals[parameterIndex]);
                    }

                    #endregion

                    if (!DeserialiserEmitter.TryEmitDeserialiseCode(ilg, throwBlocks, errors, parameters[parameterIndex].Name, targetType, valueLocals[parameterIndex], unpacker, context, contextLocal, unexpectedFieldBehaviour))
                    {
                        throw new Exception($"Unable to deserialise values of type {valueLocals[parameterIndex].LocalType} from MsgPack data.");
                    }

                    ilg.Emit(OpCodes.Br, lblEndIfChain);
                }

                if (nextLabel != null)
                {
                    ilg.MarkLabel(nextLabel.Value);
                }

                #region Process unexpected field

                // If we got here then the property was not recognised. Either throw or ignore, depending upon configuration.
                if (unexpectedFieldBehaviour == UnexpectedFieldBehaviour.Throw)
                {
                    throwBlocks.Throw(() =>
                    {
                        ilg.Emit(OpCodes.Ldstr, "Encountered unexpected field \"{0}\" of MsgPack format \"{1}\" for CLR type \"{2}\".");
                        ilg.Emit(OpCodes.Ldloc, key);
                        ilg.PeekFormatString(unpacker);
                        ilg.Emit(OpCodes.Ldstr, targetType.Name);
                        ilg.Emit(OpCodes.Call, Methods.String_Format_String_Object_Object_Object);
                        ThrowException();
                    });
                }
                else
                {
                    // skip unexpected value
                    ilg.Emit(OpCodes.Ldloc, unpacker);
                    ilg.Emit(OpCodes.Call, Methods.Unpacker_SkipValue);
                }

                #endregion

                ilg.MarkLabel(lblEndIfChain);

                #endregion

                #region Evaluate loop and branch to start unless finished

                // Increment the loop index
                ilg.Emit(OpCodes.Ldloc, loopIndex);
                ilg.Emit(OpCodes.Ldc_I4_1);
                ilg.Emit(OpCodes.Conv_I8);
                ilg.Emit(OpCodes.Add);
                ilg.Emit(OpCodes.Stloc, loopIndex);

                // Loop condition
                ilg.MarkLabel(lblLoopTest);
                ilg.Emit(OpCodes.Ldloc, loopIndex);
                ilg.Emit(OpCodes.Ldloc, mapSize);
                ilg.Emit(OpCodes.Conv_I8, mapSize);
                // If the loop is done, jump to the first instruction after the loop
                ilg.Emit(OpCodes.Beq_S, lblLoopExit);

                // Jump back to the start of the loop
                ilg.Emit(OpCodes.Br, lblLoopStart);

                // Mark the end of the loop
                ilg.MarkLabel(lblLoopExit);

                #endregion
            }

            #endregion

            #region Verify all required values either specified or have a default value

            var paramName = ilg.DeclareLocal(typeof(string));
            for (var i = 0; i < valueSetLocals.Length; i++)
            {
                // Store the name of the parameter we are inspecting
                // for use in the exception later.
                ilg.Emit(OpCodes.Ldstr, parameters[i].Name);
                ilg.Emit(OpCodes.Stloc, paramName);

                // If any value is zero then neither a default nor specified value
                // exists for that parameter, and we cannot continue.
                ilg.Emit(OpCodes.Ldloc, valueSetLocals[i]);
                ilg.Emit(OpCodes.Ldc_I4_0);
                throwBlocks.ThrowIfEqual(() =>
                {
                    ilg.Emit(OpCodes.Ldstr, "Missing required field \"{0}\" for type \"{1}\".");
                    ilg.Emit(OpCodes.Ldloc, paramName);
                    ilg.Emit(OpCodes.Ldstr, targetType.Name);
                    ilg.Emit(OpCodes.Call, Methods.String_Format_String_Object_Object);
                    ThrowException();
                });
            }

            // If we got here, all value exist and we're good to continue.

            #endregion

            #region Construct final complex object via its constructor

            // Push all values onto the execution stack
            foreach (var valueLocal in valueLocals)
            {
                ilg.Emit(OpCodes.Ldloc, valueLocal);
            }

            // Call the target type's constructor
            ilg.Emit(OpCodes.Newobj, constructor);

            ilg.Emit(OpCodes.Stloc, value);

            #endregion

            return(true);
        }