Перепишите IL, чтобы внедрить try-finally вокруг вызова метода.

Я хочу внедрить ведение журнала sql в несколько методов. В основном я хочу преобразовать

    public static object IDbCommandTest_ExecuteScalar(IDbCommand command)
    {
        // .. do stuff
        command.CommandText = "SELECT ...";
        var obj = command.ExecuteScalar();
        Console.WriteLine("SQL returned " + obj);
        // do other stuff
        return obj;
    }

в

    public static object IDbCommandTest_ExecuteScalar_Transformed(IDbCommand command)
    {
        // .. do stuff
        command.CommandText = "SELECT ...";
        object obj;
        using (SqlLogger.Enter(command, "IDbCommand", "ExecuteScalar"))
        {
            obj = command.ExecuteScalar();
        }
        Console.WriteLine("SQL returned " + obj);
        // do other stuff
        return obj;
    }

У меня работают простые случаи, проблема, с которой я сейчас сталкиваюсь, заключается в том, что стек должен быть пустым при вводе .try.

Мое примитивное предположение заключалось в том, что сам IDbCommand был загружен непосредственно перед вызовом ExecuteScalar, поэтому я ищу callvirt, а затем использую Instruction.Previous в качестве начала .try.

Но если возвращаемое значение ExecuteScalar используется впоследствии, компилятор генерирует следующий IL:

    IL_004d: ldarg.0
    IL_004e: ldloc.1
    IL_004f: callvirt instance object [System.Data]System.Data.IDbCommand::ExecuteScalar()
    IL_0054: call string Class_which_uses_obj::DoStuff(object)

С моим примитивным алгоритмом это превращается в

    IL_0050: ldarg.0
    .try
    {
        IL_0051: ldloc.1
        IL_0052: dup
        IL_0053: ldstr "IDbCommand"
        IL_0058: ldstr "get_FileName"
        IL_005d: call class [mscorlib]System.IDisposable SqlLogger::Enter(class [System.Data]System.Data.IDbCommand, string, string)
        IL_0062: stloc.3
        IL_0063: callvirt instance object [System.Data]System.Data.IDbCommand::ExecuteScalar()
        IL_0068: stloc.s 4
        IL_006a: leave.s IL_0077
    } // end .try
    finally
    {
        IL_006c: nop
        IL_006d: ldloc.3
        IL_006e: brfalse.s IL_0076

        IL_0070: ldloc.3
        IL_0071: callvirt instance void [mscorlib]System.IDisposable::Dispose()

        IL_0076: endfinally
    } // end handler

    IL_0077: nop
    IL_0078: ldloc.s 4
    IL_007a: call string        IL_0050: ldarg.0
    .try
    {
        IL_0051: ldloc.1
        IL_0052: dup
        IL_0053: ldstr "IDbCommand"
        IL_0058: ldstr "ExecuteScalar"
        IL_005d: call class [mscorlib]System.IDisposable Nemetschek.Allready.SqlLogger::Enter(class [System.Data]System.Data.IDbCommand, string, string)
        IL_0062: stloc.3
        IL_0063: callvirt instance object [System.Data]System.Data.IDbCommand::ExecuteScalar()
        IL_0068: stloc.s 4
        IL_006a: leave.s IL_0077
    } // end .try
    finally
    {
        IL_006c: nop
        IL_006d: ldloc.3
        IL_006e: brfalse.s IL_0076

        IL_0070: ldloc.3
        IL_0071: callvirt instance void [mscorlib]System.IDisposable::Dispose()

        IL_0076: endfinally
    } // end handler

    IL_0077: nop
    IL_0078: ldloc.s 4
    IL_007a: call string Nemetschek.Allready.Logistics.DbTools.CDbTools::GetSafeStringEmpty(object)(object)

Затем PEVERIFY жалуется, что я ввожу .try с непустым стеком.

Есть ли простой способ внедрить try-finally только через ExecuteScalar или мне нужно выполнить полный анализ потока по всему методу, вычислив глубину стека в любой точке, а затем загрузив/восстановив значения до/после попытки/наконец ?

ИЗМЕНИТЬ:

Мне удалось заставить его работать, сканируя вверх/вниз, пока я не найду две точки, где глубина стека равна 0. В моих ограниченных тестах это, кажется, работает, но я все равно был бы заинтересован в «чистой» реализации вместо слепое сканирование ИЛ.


person Lukas Rieger    schedule 07.12.2015    source источник


Ответы (1)


Я бы переписал вызов ExecuteScalar в вызов вспомогательного метода:

static object ExecuteScalarWrapper(SqlCommand command, string logString) {
        using (SqlLogger.Enter(command, logString))
        {
            return command.ExecuteScalar();
        }
}

ExecuteScalarWrapper будет статическим вспомогательным методом, который вы можете написать на C# и ссылаться на него.

Тогда вам не нужно вводить какие-либо блоки try. Вам просто нужно заменить шаблон

ld... command
call ExecuteScalar

с

ld... command
ld... logString
call ExecuteScalarWrapper

что должно быть проще, потому что макет и модификации стека определяются локально и не требуют каких-либо сложных рассуждений.

Теперь JIT сделает всю тяжелую работу за вас.

person usr    schedule 12.12.2015