Beispiel #1
0
            public static bool For(MethodReference invocationTarget, out PatchableMethod result)
            {
                foreach (var candidate in AllPatchableMethods)
                {
                    if (candidate.UnpatchedMethod == invocationTarget.Name)
                    {
                        result = candidate;
                        return(true);
                    }
                }

                result = default;
                return(false);
            }
Beispiel #2
0
 // Walk through all instructions that call SystemBase.HasComponent/GetComponent/SetComponent
 // and change to direct ComponentDataFromEntity access instead.
 // https://sharplab.io/#v2:EYLgxg9gTgpgtADwGwBYA0AXEBLANmgExAGoAfAAQCYBGAWAChyBmAAipYGEWBvBl/tqwDOGKAFcwGFgFEAdhmwYAnjwC+fAcxbZ5MKADMAhmBgsAkhwgBbAA4RZMeQBFDGQ2o38tY2UMP7TEXFJTms7B2dXQwAxKGs5BWUAHgAVAD4WAHcACz1TFJYQFiCJDDRzS1t7RwwXN08eBoFBFgKMbOwhAG0ExRUavoBdJoFeemaJgQBzGAwRye4WecnNAHYWB0zWgAoASgBuZeb1cZWT44aGrRKQgFklOvciizDqyLcPU/4r1krwmsesXi8j6qQyAHFZn83rUokCrL1kultsAIBBcNohAAlGCGAgAeVkuBUAF4WEZcEIYLsjgIcnlWoViqJSuUXlUIrD6l9GjzmuR1ptQhyAXC4giQUi0ntDjzzgIGgVIRhoZywdtEf1JUpdllcrBGUUbmUKq9OY8GotlgKNjAtikZUs5ZceQ1VaK3PDNUl7o8MlYHlEAIJgExCbG4glEpSy5o/NgoFi3AD6HEMNgwYigOime15KxYAceIbDEbxhOJLDJyvd7xi4u9vqi0opVIOy1R6JYBAgABlDFZgAR3GSWTBYytlk4+wOh4ZthrtSwBspdSS0ssxgXmth9Cxtj3+4PhzS+Sst9vJgB6K+tLOyVodIQAOlftMmADdDFBu1FKFWWBrM0ah9QM3GlFcdVld8Jhvcx5AgFh2k6GDmhvL8f2HD4ySLYNQxgcMcXLaMukgwYJ0veVJlUdtnVdHktHIRMUwAOXsNMMyzHM8wvSZcLcEsCLLKNK2rKFgLrL1tVAv1tlbakKImKcZ2PedF0SLUNLXDcz34XjtzglJ70fTpX2fGCMN/Nx/zElUJIwGTm22SDaMvb5dNg28zAQpCnxg9DvyskdCzAwxBMIyMKyUUjtXI5YaMUqinX5VgmJYacjznbZyGoSgkk1DJjAUexT2afTmk1ZclzJIVNRlZYiuwexnO1VyBBOVQgA==
 public void PatchComponentAccessInstructions(MethodDefinition[] clonedMethods)
 {
     foreach (var method in clonedMethods)
     {
         foreach (var instruction in method.Body.Instructions.ToArray())
         {
             if (instruction.IsInvocation(out var methodReference) && methodReference.DeclaringType.TypeReferenceEquals(typeof(SystemBase)))
             {
                 if (PatchableMethod.For(methodReference, out var patchableMethod))
                 {
                     PatchInstructionToComponentAccessMethod(method, instruction, patchableMethod);
                 }
             }
         }
     }
 }
Beispiel #3
0
        // This method is responsible for transforming the IL that calls a component access method (GetComponent/SetComponent/etc)
        // into the IL that loads a previously created ComponentDataFromEntity and either calls a method (get_Item/set_Item/HasComponent)
        // or leaves it on the stack (in the case where the method being replaced is GetComponentDataFromEntity).
        //
        // For the source IL, there are three cases where the this instance can come from, depending on how roslyn emitted the code:
        //
        // 1. Either the original system was captured into a <>_this variable in our DisplayClass.  In this case the IL will look like:
        // ldarg0
        // ldfld <>__this
        // IL to load entity
        // call GetComponent<T>
        //
        // 2. Or we got emitted without a DisplayClass, and our method is on the actual system itself, and in that case the system is just ldarg0:
        // ldarg0
        // IL to load entity
        // call GetComponent<T>
        //
        // 3. OR we captured from multiple scopes, in which case <>this will live inside of another DisplayClass:
        // ldarg.0
        // ldfld valuetype '<>c__DisplayClass0_1'::'CS$<>8__locals1'
        // ldfld class '<>c__DisplayClass0_0'::'<>4__this'
        // ldarg.1
        // call GetComponent<T>

        // And the output IL that we want looks like this:
        // ldarg0
        // ldfld ComponentDataFromEntity
        // IL to load entity
        // call ComponentDataFromEntity.GetComponent<t>(entity e);
        //
        // So the changes we are going to do is remove that original ldfld if it existed, and add the ldfld for our ComponentDataFromEntity
        // and then patch the callsite target.  We also need to get nop any ldfld instructions that were used to load the nested DisplayClasses
        // in the case where we captured locals from multiple scopes.
        void PatchInstructionToComponentAccessMethod(MethodDefinition method, Instruction instruction, PatchableMethod patchableMethod)
        {
            method.Body.SimplifyMacros();
            var ilProcessor           = method.Body.GetILProcessor();
            var componentAccessMethod = (GenericInstanceMethod)instruction.Operand;
            var componentDataType     = componentAccessMethod.GenericArguments.First();

            bool        readOnlyAccess = true;
            Instruction instructionThatPushedROAccess = null;

            switch (patchableMethod.AccessRights)
            {
            case PatchableMethod.ComponentAccessRights.ReadOnly:
                readOnlyAccess = true;
                break;

            case PatchableMethod.ComponentAccessRights.ReadWrite:
                readOnlyAccess = false;
                break;

            // Get read-access from method's param (we later nop the instruction that loads)
            case PatchableMethod.ComponentAccessRights.GetFromFirstMethodParam:
                instructionThatPushedROAccess =
                    CecilHelpers.FindInstructionThatPushedArg(componentAccessMethod.ElementMethod.Resolve(), 1, instruction, true);
                if (instructionThatPushedROAccess.IsLoadConstantInt(out var intVal))
                {
                    readOnlyAccess = intVal != 0;
                }
                else
                {
                    if (instructionThatPushedROAccess.IsInvocation(out var _))
                    {
                        UserError.DC0048(method, patchableMethod.UnpatchedMethod, instruction).Throw();
                    }
                    else if (instructionThatPushedROAccess.IsLoadLocal(out _) ||
                             instructionThatPushedROAccess.IsLoadArg(out _) ||
                             instructionThatPushedROAccess.IsLoadFieldOrLoadFieldAddress())
                    {
                        UserError.DC0049(method, patchableMethod.UnpatchedMethod, instruction).Throw();
                    }
                    else
                    {
                        InternalCompilerError.DCICE008(method, patchableMethod.UnpatchedMethod, instruction).Throw();
                    }
                }
                break;
            }

            var componentDataFromEntityField = GetOrCreateComponentDataFromEntityField(componentDataType, readOnlyAccess);

            // Make sure our componentDataFromEntityField doesn't give write access to a lambda parameter of the same type
            // or there is a writable lambda parameter that gives access to this type (either could violate aliasing rules).
            foreach (var parameter in LambdaParameters)
            {
                if (parameter.ParameterType.GetElementType().TypeReferenceEquals(componentDataType))
                {
                    if (!readOnlyAccess)
                    {
                        UserError.DC0046(method, componentAccessMethod.Name, componentDataType.Name, instruction).Throw();
                    }
                    else if (!parameter.HasCompilerServicesIsReadOnlyAttribute())
                    {
                        UserError.DC0047(method, componentAccessMethod.Name, componentDataType.Name, instruction).Throw();
                    }
                }
            }

            // Find where we pushed the this argument and make it nop
            // Note: we don't want to do this when our method was inserted into our declaring type (in the case where we aren't capturing).
            var instructionThatPushedThis = CecilHelpers.FindInstructionThatPushedArg(method, 0, instruction, true);

            if (instructionThatPushedThis == null)
            {
                UserError.DC0045(method, componentAccessMethod.Name, instruction).Throw();
            }

            // Nop the ldfld for this
            if (instructionThatPushedThis.OpCode == OpCodes.Ldfld)
            {
                instructionThatPushedThis.MakeNOP();
            }

            // Nop any ldflds of nested DisplayClasses
            var previousInstruction = instructionThatPushedThis.Previous;

            while (previousInstruction != null && previousInstruction.OpCode == OpCodes.Ldfld &&
                   ((FieldReference)previousInstruction.Operand).IsNestedDisplayClassField())
            {
                previousInstruction.MakeNOP();
                previousInstruction = previousInstruction.Previous;
            }

            // Insert Ldflda of componentDataFromEntityField after that point
            var componentDataFromEntityFieldInstruction = CecilHelpers.MakeInstruction(
                patchableMethod.AccessFieldAsRef ? OpCodes.Ldflda : OpCodes.Ldfld, componentDataFromEntityField);

            ilProcessor.InsertAfter(instructionThatPushedThis, componentDataFromEntityFieldInstruction);

            // Replace method that we invoke from SystemBase method to ComponentDataFromEntity<T> method if we have one
            // (HasComponent, get_Item or set_Item).  Otherwise nop.
            if (patchableMethod.PatchedMethod != null)
            {
                var componentDataFromEntityTypeDef = componentDataFromEntityField.FieldType.Resolve();
                var itemAccessMethod = TypeDefinition.Module.ImportReference(
                    componentDataFromEntityTypeDef.Methods.Single(m => m.Name == patchableMethod.PatchedMethod));
                var closedGetItemMethod =
                    itemAccessMethod.MakeGenericHostMethod(componentDataFromEntityField.FieldType);
                instruction.Operand = TypeDefinition.Module.ImportReference(closedGetItemMethod);
            }
            else
            {
                instruction.MakeNOP();
            }

            // Handle special case where we have an instruction that pushed an argument for Read/Write access, nop that out
            instructionThatPushedROAccess?.MakeNOP();
            method.Body.OptimizeMacros();
        }
Beispiel #4
0
        void PatchInstructionToComponentAccessMethod(MethodDefinition method, Instruction instruction, PatchableMethod unpatchedMethod)
        {
            var ilProcessor                  = method.Body.GetILProcessor();
            var componentAccessMethod        = (GenericInstanceMethod)instruction.Operand;
            var componentDataType            = componentAccessMethod.GenericArguments.First();
            var componentDataFromEntityField = GetOrCreateComponentDataFromEntityField(componentDataType, unpatchedMethod.ReadOnly);

            // Make sure our componentDataFromEntityField doesn't give write access to a lambda parameter of the same type
            // or there is a writable lambda parameter that gives access to this type (either could violate aliasing rules).
            foreach (var parameter in LambdaParameters)
            {
                if (parameter.ParameterType.GetElementType().TypeReferenceEquals(componentDataType))
                {
                    if (!unpatchedMethod.ReadOnly)
                    {
                        UserError.DC0046(method, componentAccessMethod.Name, componentDataType.Name, instruction).Throw();
                    }
                    else if (!parameter.HasCompilerServicesIsReadOnlyAttribute())
                    {
                        UserError.DC0047(method, componentAccessMethod.Name, componentDataType.Name, instruction).Throw();
                    }
                }
            }

            // Find where we pushed the this argument and make it nop
            // Note: we don't want to do this when our method was inserted into our declaring type (in the case where we aren't capturing).
            var instructionThatPushedThis = CecilHelpers.FindInstructionThatPushedArg(method, 0, instruction, true);

            if (instructionThatPushedThis == null)
            {
                UserError.DC0045(method, componentAccessMethod.Name, instruction).Throw();
            }

            //this instruction is responsible for pushing the systembase 'this' object, that we called GetComponent<T>(Entity e) or its friends on.
            //there are two cases where this instance can come from, depending on how roslyn emitted the code. Either the original system
            //was captured into a <>_this variable in our displayclass.  in this case the IL will look like:
            //
            //ldarg0
            //ldfld <>__this
            //IL to load entity
            //call GetComponent<T>
            //
            //or we got emitted without a displayclass, and our method is on the
            //actual system itself, and in that case the system is just ldarg0:
            //
            //ldarg0
            //IL to load entity
            //call GetComponent<T>

            //the output IL that we want looks like this:
            //ldarg0
            //ldfld componentdatafromentity
            //IL to load entity
            //call componentdatafromentity.getcomponent<t>(entity e);
            //
            //so the changes we are going to do is remove that original ldfld if it existed, and add the ldfld for our componentdatafromentity
            //and then patch the callsite target.

            if (instructionThatPushedThis.OpCode == OpCodes.Ldfld)
            {
                instructionThatPushedThis.MakeNOP();
            }

            // Insert Ldflda of componentDataFromEntityField after that point
            var componentDataFromEntityFieldInstruction = CecilHelpers.MakeInstruction(OpCodes.Ldflda, componentDataFromEntityField);

            ilProcessor.InsertAfter(instructionThatPushedThis, componentDataFromEntityFieldInstruction);

            // Replace method that we invoke from SystemBase method to ComponentDataFromEntity<T> method (HasComponent, get_Item or set_Item)
            var componentDataFromEntityTypeDef = componentDataFromEntityField.FieldType.Resolve();
            var itemAccessMethod = TypeDefinition.Module.ImportReference(
                componentDataFromEntityTypeDef.Methods.Single(m => m.Name == unpatchedMethod.PatchedMethod));
            var closedGetItemMethod = itemAccessMethod.MakeGenericHostMethod(componentDataFromEntityField.FieldType);

            instruction.Operand = TypeDefinition.Module.ImportReference(closedGetItemMethod);
        }