Exemplo n.º 1
0
        /// <summary>
        ///     Attempts to construct an instance of <see cref="InjectionDefinition" /> by linking the injection method with the
        ///     injection target (the method to be injected).
        ///     The way how the method is injected is specified by the injection flags. If the injection method does not match the
        ///     criteria set by the injection flags, an exception will be thrown.
        /// </summary>
        /// <param name="injectTarget">The method that will be injected.</param>
        /// <param name="injectMethod">The method which to inject.</param>
        /// <param name="flags">Injection flags that specify what values to pass to the injection method and how to inject it.</param>
        /// <param name="localVarIDs">
        ///     An array of indicies of local variables to pass to the injection method. Used only if
        ///     <see cref="InjectFlags.PassLocals" /> is specified, otherwise ignored.
        /// </param>
        /// <param name="memberReferences">
        ///     An array of class fields from the type the target lies in to pass to the injection
        ///     method. Used only if <see cref="InjectFlags.PassFields" /> is specified, otherwise ignored.
        /// </param>
        public InjectionDefinition(MethodDefinition injectTarget,
                                   MethodDefinition injectMethod,
                                   InjectFlags flags,
                                   int[] localVarIDs = null,
                                   params FieldDefinition[] memberReferences)
        {
            Assert(
                injectMethod.IsStatic,
                $"{nameof(injectMethod)} must be static in order to be used as the injection.");
            Assert(injectMethod.IsPublic, $"{nameof(injectMethod)} must be public.");
            Assert(
                injectMethod.HasBody,
                $"{nameof(injectMethod)} must have a definition in order to be used as the injecton.");

            InjectValues hFlags = flags.ToValues();

            bool isVoid = injectTarget.ReturnType.FullName == "System.Void";

            Assert(
                !hFlags.PassLocals || localVarIDs != null,
                $"Supposed to pass local references, but {nameof(localVarIDs)} is empty");
            Assert(
                !hFlags.PassFields || memberReferences != null,
                $"Supposed to pass member fields, but {nameof(memberReferences)} is empty!");

            int prefixCount = Convert.ToInt32(hFlags.PassTag) + Convert.ToInt32(hFlags.PassInvokingInstance)
                              + Convert.ToInt32(hFlags.ModifyReturn && !isVoid);
            int localsCount    = hFlags.PassLocals ? localVarIDs.Length : 0;
            int memberRefCount = (hFlags.PassFields ? memberReferences.Length : 0);
            int paramCount     = (hFlags.PassParameters ? injectTarget.Parameters.Count : 0);
            int parameters     = injectMethod.Parameters.Count - prefixCount - localsCount - memberRefCount;

            // int parameters = injectMethod.Parameters.Count - prefixCount - localsCount - memberRefCount;
            // PassParameters => 0 < parameters <= injectTarget.Patameters.Count
            // !PassParameters => parameters = 0
            Assert(
                (hFlags.PassParameters && 0 < parameters && parameters <= injectTarget.Parameters.Count ||
                 !hFlags.PassParameters && parameters == 0),
                //injectMethod.Parameters.Count == prefixCount + localsCount + memberRefCount + paramCount,
                $@"The injection method has a wrong number of parameters! Check that the provided target method, local variables, member references and injection flags add up to the right number of parameters.
Needed parameters: Prefix: {
            prefixCount}, Locals: {localsCount}, Members: {memberRefCount}, Parameters: {
            (hFlags.PassParameters ? "between 1 and " + paramCount : "0")}, TOTAL: {
            (hFlags.PassParameters ? $"between {prefixCount + localsCount + memberRefCount + 1} and " : "")}{
            prefixCount + localsCount + memberRefCount + paramCount}.
Injection has {injectMethod.Parameters.Count
            } parameters.");

            TypeComparer comparer = new TypeComparer();

            if (hFlags.PassTag)
            {
                Assert(
                    injectMethod.Parameters[0].ParameterType.FullName == "System.Int32",
                    "Supposed to pass a tag, but the provided tag must be of type System.Int32 (int).");
            }

            if (hFlags.PassInvokingInstance)
            {
                Assert(
                    !injectTarget.IsStatic,
                    "Supposed to pass invoking instance, but the target method is static and thus is not bound to any instances.");
                Assert(
                    comparer.Equals(
                        injectMethod.Parameters[Convert.ToInt32(hFlags.PassTag)].ParameterType,
                        injectTarget.DeclaringType),
                    "Supposed to pass invoking instance, but the type of the instance does not match with the type declared in the injection method.");
            }

            if (hFlags.ModifyReturn)
            {
                Assert(
                    injectMethod.ReturnType.FullName == "System.Boolean",
                    "The injection method must return a boolean in order to alter the return value.");
                Assert(
                    isVoid ||
                    comparer.Equals(
                        injectMethod.Parameters[Convert.ToInt32(hFlags.PassTag) + Convert.ToInt32(hFlags.PassInvokingInstance)]
                        .ParameterType,
                        new ByReferenceType(injectTarget.ReturnType)),
                    "Supposed to modify the return value, but the provided return type does not match with the return type of the target method! Also make sure the type is passed by reference (out/ref).");
            }

            if (hFlags.PassLocals)
            {
                Assert(injectTarget.Body.HasVariables, "The target method does not have any locals.");
                Assert(
                    localVarIDs.Length != 0,
                    "Supposed to pass method locals, but the IDs of the locals were not specified.");
                Assert(
                    localVarIDs.All(i => 0 <= i && i < injectTarget.Body.Variables.Count),
                    "Supposed to receive local references, but the provided local variable index/indices do not exist in the target method!");
                Assert(
                    injectMethod.Parameters.Slice(prefixCount, localsCount)
                    .Select((p, i) => new { param = p, index = i })
                    .All(
                        t =>
                        t.param.ParameterType.IsByReference &&
                        comparer.Equals(
                            t.param.ParameterType,
                            new ByReferenceType(injectTarget.Body.Variables[t.index].VariableType))),
                    "Supposed to receive local references, but the types between injection method and target method mismatch. Also make sure they are passed by reference (ref/out).");
            }

            if (hFlags.PassFields)
            {
                Assert(!injectTarget.IsStatic, "Cannot pass member references if the injection method is static!");
                Assert(
                    memberReferences.Length != 0,
                    "Supposed to pass member references, but no members were specified.");
                Assert(
                    memberReferences.All(
                        m =>
                        m.DeclaringType.FullName == injectTarget.DeclaringType.FullName &&
                        m.DeclaringType.BaseType.FullName == injectTarget.DeclaringType.BaseType.FullName),
                    $"The provided member fields do not belong to {injectTarget.DeclaringType}");

                IEnumerable <TypeReference> paramRefs =
                    injectMethod.Parameters.Slice(prefixCount + localsCount, memberRefCount).Select(p => p.ParameterType);
                IEnumerable <TypeReference> typeReferences = paramRefs as TypeReference[] ?? paramRefs.ToArray();

                Assert(
                    typeReferences.All(p => p.IsByReference),
                    "Supposed to pass class members, but the provided parameters in the injection method are not of a reference type (ref)!");
                Assert(
                    typeReferences.SequenceEqual(
                        memberReferences.Select(f => (TypeReference) new ByReferenceType(f.FieldType)),
                        comparer),
                    "Supposed to pass class members, but the existing members are of a different type than the ones specified in the injection method.");
            }

            if (hFlags.PassParameters)
            {
                Assert(
                    injectMethod.HasGenericParameters == injectTarget.HasGenericParameters,
                    "The injection and target methods have mismatching specification of generic parameters!");

                Assert(
                    !injectMethod.HasGenericParameters ||
                    injectMethod.GenericParameters.Count <= injectTarget.GenericParameters.Count,
                    "The injection and target methods have a mismatching number of generic parameters! The injection method must have less or the same number of generic paramters as the target!");

                Assert(
                    !hFlags.PassParametersByRef ||
                    injectMethod.Parameters.Skip(prefixCount + localsCount + memberRefCount)
                    .All(p => p.ParameterType.IsByReference),
                    "Supposed to pass target method parameters by reference, but the provided parameters in the injection method are not of a reference type (ref).");

                Assert(
                    injectMethod.Parameters.Skip(prefixCount + localsCount + memberRefCount)
                    .Select(p => p.ParameterType)
                    .SequenceEqual(
                        injectTarget.Parameters.Take(parameters)
                        .Select(
                            p =>
                            (hFlags.PassParametersByRef
                                         ? new ByReferenceType(p.ParameterType) : p.ParameterType)),
                        comparer),
                    "Supposed to pass target method parameters by reference, but the types specified in injection and target methods do not match.");
            }

            InjectMethod     = injectMethod;
            InjectTarget     = injectTarget;
            Flags            = flags;
            MemberReferences = memberReferences;
            LocalVarIDs      = localVarIDs;
            _PrefixCount     = prefixCount;
            _MemeberRefCount = memberRefCount;
            _ParameterCount  = parameters;
        }