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