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); }
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); }
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); }
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); }
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); }
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); }
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); }
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); }
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); }
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); }
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); }
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); }
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); }
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); }