Пример #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)
        {
            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);
        }
Пример #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 (!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);
        }