Ejemplo n.º 1
0
        /// <summary>
        /// Parses the instruction.
        /// </summary>
        /// <param name="mainProcessor">The main instruction processor.</param>
        /// <param name="asmCollection">The papyrus assembly collection.</param>
        /// <param name="instruction">The instruction.</param>
        /// <param name="targetMethod">The target method.</param>
        /// <param name="targetType">Type of the target.</param>
        /// <returns></returns>
        /// <exception cref="StackUnderflowException"></exception>
        public IEnumerable <PapyrusInstruction> Process(
            IClrInstructionProcessor mainProcessor,
            IReadOnlyCollection <PapyrusAssemblyDefinition> asmCollection,
            Instruction instruction,
            MethodDefinition targetMethod,
            TypeDefinition targetType)
        {
            bool isStructAccess;
            var  structGets = new List <PapyrusInstruction>();

            this.papyrusAssemblyCollection = asmCollection;
            var processInstruction = new List <PapyrusInstruction>();
            var stack = mainProcessor.EvaluationStack;

            currentInstruction = instruction;
            var methodRef = instruction.Operand as MethodReference;

            if (methodRef != null)
            {
                if (methodRef.FullName.ToLower().Contains("system.void") &&
                    methodRef.FullName.ToLower().Contains(".ctor"))
                {
                    return(new PapyrusInstruction[0]);
                }

                // What we need:
                // 1. Call Location (The name of the type that has this method)
                // 2. Method Name (The name of the method, duh)
                // 3. Method Parameters (The parameters that we need to pass to the method)
                // 4. Destination Variable (The variable needed to store the return value of the method)
                //      - Destination Variable must always exist, the difference between a function returning a object
                //        and a void, is that we "assign" the destination to a ::nonevar temp variable of type None (void)

                var name       = methodRef.FullName;
                var itemsToPop = instruction.OpCode.StackBehaviourPop == StackBehaviour.Varpop
                    ? methodRef.Parameters.Count
                    : Utility.GetStackPopCount(instruction.OpCode.StackBehaviourPop);

                if (stack.Count < itemsToPop)
                {
                    if (mainProcessor.PapyrusCompilerOptions == PapyrusCompilerOptions.Strict)
                    {
                        throw new StackUnderflowException(targetMethod, instruction);
                    }
                    return(processInstruction);
                }

                // Check if the current invoked Method is inside the same instance of this type
                // by checking if it is using "this.Method(params);" << "this."...
                var isThisCall     = false;
                var isStaticCall   = false;
                var callerLocation = "";
                var parameters     = new List <object>();

                if (methodRef.HasThis)
                {
                    callerLocation = "self";
                    isThisCall     = true;
                }

                for (var paramIndex = 0; paramIndex < itemsToPop; paramIndex++)
                {
                    var parameter = stack.Pop();
                    if (parameter.IsThis && stack.Count > methodRef.Parameters.Count ||
                        methodRef.CallingConvention == MethodCallingConvention.ThisCall)
                    {
                        isThisCall     = true;
                        callerLocation = "self"; // Location: 'self' is the same as 'this'
                    }
                    parameters.Insert(0, parameter);
                }

                var methodDefinition = mainProcessor.TryResolveMethodReference(methodRef);
                if (methodDefinition != null)
                {
                    isStaticCall = methodDefinition.IsStatic;
                }

                if (methodDefinition != null && methodDefinition.IsConstructor)
                {
                    return(processInstruction);
                }

                if (methodDefinition == null)
                {
                    isStaticCall = name.Contains("::");
                }

                if (isStaticCall)
                {
                    callerLocation = name.Split("::")[0];
                }

                if (callerLocation.Contains("."))
                {
                    callerLocation = callerLocation.Split('.').LastOrDefault();
                }

                if (methodRef.Name.ToLower().Contains("concat"))
                {
                    processInstruction.AddRange(mainProcessor.ProcessStringConcat(instruction, methodRef,
                                                                                  parameters));
                }
                else if (methodRef.Name.ToLower().Contains("op_equal") ||
                         methodRef.Name.ToLower().Contains("op_inequal"))
                {
                    // TODO: Add Equality comparison

                    mainProcessor.InvertedBranch = methodRef.Name.ToLower().Contains("op_inequal");

                    if (!InstructionHelper.IsStore(instruction.Next.OpCode.Code))
                    {
                        mainProcessor.SkipToOffset = instruction.Next.Offset;
                        return(processInstruction);
                    }
                    // EvaluationStack.Push(new EvaluationStackItem { IsMethodCall = true, Value = methodRef, TypeName = methodRef.ReturnType.FullName });

                    processInstruction.AddRange(mainProcessor.ProcessConditionalInstruction(instruction,
                                                                                            Code.Ceq));
                }
                else
                {
                    if (methodRef.Name.ToLower().StartsWith("get_") || methodRef.Name.ToLower().StartsWith("set_"))
                    {
                        processInstruction.AddRange(ProcessPropertyAccess(mainProcessor, instruction, methodRef, methodDefinition,
                                                                          parameters));

                        return(processInstruction);
                    }


                    if (isStaticCall)
                    {
                        var destinationVariable = mainProcessor.GetTargetVariable(instruction, methodRef,
                                                                                  out isStructAccess);
                        {
                            processInstruction.Add(CreatePapyrusCallInstruction(mainProcessor, PapyrusOpCodes.Callstatic, methodRef,
                                                                                callerLocation,
                                                                                destinationVariable, parameters, out structGets));
                            processInstruction.InsertRange(processInstruction.Count - 1, structGets);
                            return(processInstruction);
                        }
                    }
                    if (isThisCall)
                    {
                        var isDelegateInvoke = false;
                        if (stack.Count > 0)
                        {
                            var next   = stack.Peek().Value;
                            var varRef = next as PapyrusVariableReference;
                            if (varRef != null && varRef.IsDelegateReference)
                            {
                                isDelegateInvoke = true;
                            }
                        }

                        var destinationVariable = mainProcessor.GetTargetVariable(instruction, methodRef,
                                                                                  out isStructAccess);

                        if (isDelegateInvoke)
                        {
                            var targetDelegate           = stack.Pop().Value as PapyrusVariableReference;
                            var targetDelegateMethodName = targetDelegate.DelegateInvokeReference;

                            // In case we are invoking a delegate from inside a delegate then we need to find the
                            // reference to the previous delegate method and use that one instead.
                            var target = InstructionHelper.FindPreviousInstruction(instruction,
                                                                                   InstructionHelper.IsLoadMethodRef);
                            if (target != null && target.Operand is MethodReference)
                            {
                                methodRef = target.Operand as MethodReference;
                                targetDelegateMethodName = methodRef.Name;
                            }

                            processInstruction.Add(CreatePapyrusCallInstruction(mainProcessor, PapyrusOpCodes.Callmethod, methodRef,
                                                                                callerLocation, destinationVariable, parameters, out structGets,
                                                                                targetDelegateMethodName));
                            processInstruction.InsertRange(processInstruction.Count - 1, structGets);
                        }
                        else
                        {
                            if (isStructAccess)
                            {
                                var structRef =
                                    stack.Pop().Value as PapyrusStructFieldReference;
                                if (structRef != null)
                                {
                                    // (Call and Assign Temp then do StructSet using Temp)

                                    // Call and Assign return value to temp
                                    processInstruction.Add(CreatePapyrusCallInstruction(mainProcessor, PapyrusOpCodes.Callmethod,
                                                                                        methodRef,
                                                                                        callerLocation,
                                                                                        destinationVariable, parameters, out structGets));

                                    // StructSet
                                    processInstruction.Add(
                                        mainProcessor.CreatePapyrusInstruction(PapyrusOpCodes.StructSet,
                                                                               structRef.StructSource, structRef.StructVariable,
                                                                               mainProcessor.CreateVariableReferenceFromName(destinationVariable)));

                                    // Skip next instruction as it should be stfld and we already store the field here.
                                    mainProcessor.SkipNextInstruction = true;
                                }
                            }
                            else
                            {
                                processInstruction.Add(CreatePapyrusCallInstruction(mainProcessor, PapyrusOpCodes.Callmethod, methodRef,
                                                                                    callerLocation,
                                                                                    destinationVariable, parameters, out structGets));
                                processInstruction.InsertRange(processInstruction.Count - 1, structGets);
                            }
                        }
                        return(processInstruction);
                    }
                }
            }
            return(processInstruction);
        }