<commit_before>﻿namespace LogSpectRewriter.Rewriting
{
    using System;
    using System.Collections.Generic;
    using System.Reflection;
    using System.Runtime.CompilerServices;
    using LogSpect;
    using LogSpect.Logging;
    using Mono.Cecil;
    using Mono.Cecil.Cil;
    using Mono.Cecil.Rocks;

    internal class MethodRewriter
    {
        private readonly ModuleDefinition module;

        private readonly TypeReference objectArray;
        private readonly TypeReference runtimeMethodHandle;
        private readonly TypeReference methodBase;
        private readonly TypeReference exception;
        private readonly MethodReference compilerGeneratedAttributeConstructor;

        private readonly TypeReference methodLogger;
        private readonly MethodReference methodLoggerLogLeave;
        private readonly MethodReference methodLoggerLogException;
        private readonly TypeReference methodLoggerFactory;
        private readonly TypeReference logSpectServiceLocator;

        public MethodRewriter(ModuleDefinition module)
        {
            this.module = module;

            this.objectArray = new ArrayType(this.module.TypeSystem.Object);
            this.runtimeMethodHandle = this.module.Import(typeof(RuntimeMethodHandle));
            this.methodBase = this.module.Import(typeof(MethodBase));
            this.exception = this.module.Import(typeof(Exception));

            TypeReference compilerGeneratedAttributeTypeReference = this.module.Import(typeof(CompilerGeneratedAttribute));
            this.compilerGeneratedAttributeConstructor = new MethodReference(".ctor", this.module.TypeSystem.Void, compilerGeneratedAttributeTypeReference) { HasThis = true };

            this.methodLogger = this.module.Import(typeof(IMethodLogger));

            this.methodLoggerLogLeave = new MethodReference("LogLeave", this.module.TypeSystem.Void, this.methodLogger) { HasThis = true };
            this.methodLoggerLogLeave.Parameters.Add(new ParameterDefinition(new ArrayType(this.module.TypeSystem.Object)));
            this.methodLoggerLogLeave.Parameters.Add(new ParameterDefinition(this.module.TypeSystem.Object));

            this.methodLoggerLogException = new MethodReference("LogException", this.module.TypeSystem.Void, this.methodLogger) { HasThis = true };
            this.methodLoggerLogException.Parameters.Add(new ParameterDefinition(this.exception));

            this.methodLoggerFactory = this.module.Import(typeof(IMethodLoggerFactory));
            this.logSpectServiceLocator = this.module.Import(typeof(LogSpectServiceLocator));
        }

        public void Rewrite(MethodDefinition method)
        {
            FieldDefinition methodLoggerField;
            this.CreateMethodLoggerField(method, out methodLoggerField);
            this.RewriteMethodWithLogging(methodLoggerField, method);
        }

        private void CreateMethodLoggerField(MethodDefinition method, out FieldDefinition methodLoggerField)
        {
            CustomAttribute compilerGeneratedAttribute = new CustomAttribute(this.compilerGeneratedAttributeConstructor);

            methodLoggerField = new FieldDefinition(
                string.Format("<>{0}_{1:x8}", method.Name, method.FullName.GetHashCode()),
                Mono.Cecil.FieldAttributes.Private | Mono.Cecil.FieldAttributes.Static,
                this.methodLogger);
            methodLoggerField.CustomAttributes.Add(compilerGeneratedAttribute);

            method.DeclaringType.Fields.Add(methodLoggerField);
        }

        private void RewriteMethodWithLogging(FieldDefinition methodLoggerField, MethodDefinition method)
        {
            VariableDefinition argsVariable;
            VariableDefinition returnValueVariable;
            VariableDefinition exceptionVariable;
            this.CreateVariables(method, out argsVariable, out returnValueVariable, out exceptionVariable);

            IEnumerable<Instruction> beforeTryInstructions = this.CreateBeforeTryInstructions(method, methodLoggerField, argsVariable);
            IList<Instruction> tryBodyInstructions = this.CreateTryBodyInstructions(method, methodLoggerField, returnValueVariable, argsVariable);
            IList<Instruction> catchBodyInstructions = this.CreateCatchBodyInstructions(methodLoggerField, exceptionVariable);
            IList<Instruction> afterTryInstructions = this.CreateAfterTryInstructions(returnValueVariable);

            method.Body.Instructions.Clear();
            method.Body.Instructions.AddRange(beforeTryInstructions);
            method.Body.Instructions.AddRange(tryBodyInstructions);
            method.Body.Instructions.Add(Instruction.Create(OpCodes.Leave, afterTryInstructions[0]));
            method.Body.Instructions.AddRange(catchBodyInstructions);
            method.Body.Instructions.AddRange(afterTryInstructions);

            // It's possible that there's no 'ret' instruction in the method because it always throws exception.
            // In this case there will be an unclosed exception handler and we need to fix it.
            foreach (ExceptionHandler exceptionHandler in method.Body.ExceptionHandlers)
            {
                if (exceptionHandler.HandlerEnd == null)
                {
                    exceptionHandler.HandlerEnd = catchBodyInstructions[0];
                }
            }

            method.Body.ExceptionHandlers.Add(
                new ExceptionHandler(ExceptionHandlerType.Catch)
                {
                    CatchType = this.exception,
                    TryStart = tryBodyInstructions[0],
                    TryEnd = catchBodyInstructions[0],
                    HandlerStart = catchBodyInstructions[0],
                    HandlerEnd = afterTryInstructions[0]
                });

            method.Body.OptimizeMacros();
            method.Body.InitLocals = true;
        }

        private void CreateVariables(
            MethodDefinition method,
            out VariableDefinition argsVariable,
            out VariableDefinition returnValueVariable,
            out VariableDefinition exceptionVariable)
        {
            argsVariable = new VariableDefinition("<>args", this.objectArray);
            method.Body.Variables.Add(argsVariable);

            returnValueVariable = null;
            if (method.MethodReturnType.ReturnType.MetadataType != MetadataType.Void)
            {
                returnValueVariable = new VariableDefinition("<>returnValue", method.ReturnType);
                method.Body.Variables.Add(returnValueVariable);
            }

            exceptionVariable = new VariableDefinition("<>exception", this.exception);
            method.Body.Variables.Add(exceptionVariable);
        }

        /// <summary>
        /// if (methodLogger == null)
        /// {
        ///     methodLogger = LogSpectServiceLocator.Factory.Create(methodof(Foo));
        /// }
        /// 
        /// object[] args = { p1, p2, p3, ..., pn };
        /// methodLogger.LogEnter(args);<commit_msg>Out parameters removed from MethodRewriter.<commit_after>﻿namespace LogSpectRewriter.Rewriting
{
    using System;
    using System.Collections.Generic;
    using System.Reflection;
    using System.Runtime.CompilerServices;
    using LogSpect;
    using LogSpect.Logging;
    using Mono.Cecil;
    using Mono.Cecil.Cil;
    using Mono.Cecil.Rocks;

    internal class MethodRewriter
    {
        private readonly ModuleDefinition module;

        private readonly TypeReference objectArray;
        private readonly TypeReference runtimeMethodHandle;
        private readonly TypeReference methodBase;
        private readonly TypeReference exception;
        private readonly MethodReference compilerGeneratedAttributeConstructor;

        private readonly TypeReference methodLogger;
        private readonly MethodReference methodLoggerLogLeave;
        private readonly MethodReference methodLoggerLogException;
        private readonly TypeReference methodLoggerFactory;
        private readonly TypeReference logSpectServiceLocator;

        public MethodRewriter(ModuleDefinition module)
        {
            this.module = module;

            this.objectArray = new ArrayType(this.module.TypeSystem.Object);
            this.runtimeMethodHandle = this.module.Import(typeof(RuntimeMethodHandle));
            this.methodBase = this.module.Import(typeof(MethodBase));
            this.exception = this.module.Import(typeof(Exception));

            TypeReference compilerGeneratedAttributeTypeReference = this.module.Import(typeof(CompilerGeneratedAttribute));
            this.compilerGeneratedAttributeConstructor = new MethodReference(".ctor", this.module.TypeSystem.Void, compilerGeneratedAttributeTypeReference) { HasThis = true };

            this.methodLogger = this.module.Import(typeof(IMethodLogger));

            this.methodLoggerLogLeave = new MethodReference("LogLeave", this.module.TypeSystem.Void, this.methodLogger) { HasThis = true };
            this.methodLoggerLogLeave.Parameters.Add(new ParameterDefinition(new ArrayType(this.module.TypeSystem.Object)));
            this.methodLoggerLogLeave.Parameters.Add(new ParameterDefinition(this.module.TypeSystem.Object));

            this.methodLoggerLogException = new MethodReference("LogException", this.module.TypeSystem.Void, this.methodLogger) { HasThis = true };
            this.methodLoggerLogException.Parameters.Add(new ParameterDefinition(this.exception));

            this.methodLoggerFactory = this.module.Import(typeof(IMethodLoggerFactory));
            this.logSpectServiceLocator = this.module.Import(typeof(LogSpectServiceLocator));
        }

        public void Rewrite(MethodDefinition method)
        {
            FieldDefinition methodLoggerField = new FieldDefinition(
                string.Format("<>{0}_{1:x8}", method.Name, method.FullName.GetHashCode()),
                Mono.Cecil.FieldAttributes.Private | Mono.Cecil.FieldAttributes.Static,
                this.methodLogger);
            methodLoggerField.CustomAttributes.Add(new CustomAttribute(this.compilerGeneratedAttributeConstructor));
            method.DeclaringType.Fields.Add(methodLoggerField);

            VariableDefinition argsVariable = new VariableDefinition("<>args", this.objectArray);
            method.Body.Variables.Add(argsVariable);

            VariableDefinition returnValueVariable = null;
            if (method.MethodReturnType.ReturnType.MetadataType != MetadataType.Void)
            {
                returnValueVariable = new VariableDefinition("<>returnValue", method.ReturnType);
                method.Body.Variables.Add(returnValueVariable);
            }

            VariableDefinition exceptionVariable = new VariableDefinition("<>exception", this.exception);
            method.Body.Variables.Add(exceptionVariable);

            this.RewriteBody(methodLoggerField, method, argsVariable, returnValueVariable, exceptionVariable);
        }

        private void RewriteBody(FieldDefinition methodLoggerField, MethodDefinition method, VariableDefinition argsVariable, VariableDefinition returnValueVariable, VariableDefinition exceptionVariable)
        {
            IEnumerable<Instruction> beforeTryInstructions = this.CreateBeforeTryInstructions(method, methodLoggerField, argsVariable);
            IList<Instruction> tryBodyInstructions = this.CreateTryBodyInstructions(method, methodLoggerField, returnValueVariable, argsVariable);
            IList<Instruction> catchBodyInstructions = this.CreateCatchBodyInstructions(methodLoggerField, exceptionVariable);
            IList<Instruction> afterTryInstructions = this.CreateAfterTryInstructions(returnValueVariable);

            method.Body.Instructions.Clear();
            method.Body.Instructions.AddRange(beforeTryInstructions);
            method.Body.Instructions.AddRange(tryBodyInstructions);
            method.Body.Instructions.Add(Instruction.Create(OpCodes.Leave, afterTryInstructions[0]));
            method.Body.Instructions.AddRange(catchBodyInstructions);
            method.Body.Instructions.AddRange(afterTryInstructions);

            // It's possible that there's no 'ret' instruction in the method because it always throws exception.
            // In this case there will be an unclosed exception handler and we need to fix it.
            foreach (ExceptionHandler exceptionHandler in method.Body.ExceptionHandlers)
            {
                if (exceptionHandler.HandlerEnd == null)
                {
                    exceptionHandler.HandlerEnd = catchBodyInstructions[0];
                }
            }

            method.Body.ExceptionHandlers.Add(
                new ExceptionHandler(ExceptionHandlerType.Catch)
                {
                    CatchType = this.exception,
                    TryStart = tryBodyInstructions[0],
                    TryEnd = catchBodyInstructions[0],
                    HandlerStart = catchBodyInstructions[0],
                    HandlerEnd = afterTryInstructions[0]
                });

            method.Body.OptimizeMacros();
            method.Body.InitLocals = true;
        }

        /// <summary>
        /// if (methodLogger == null)
        /// {
        ///     methodLogger = LogSpectServiceLocator.Factory.Create(methodof(Foo));
        /// }
        /// 
        /// object[] args = { p1, p2, p3, ..., pn };
        /// methodLogger.LogEnter(args);
{'fraction_non_alphanumeric': 0.07988960167089362, 'fraction_numerical': 0.0017902431746978965, 'mean_word_length': 2.807725078102812, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 21, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7314984', 'n_tokens_mistral': 3117, 'n_tokens_neox': 2996, 'n_words': 572}