Je souhaite injecter la journalisation SQL dans quelques méthodes. Fondamentalement, je veux transformerRéécrire IL pour injecter try-finally around appel de méthode
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;
}
dans
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;
}
Je suis les cas faciles à travailler, la question que je suis actuellement confronté est que la pile doit être vide lors de la saisie d'un .try
.
Mon hypothèse primitive était que le IDbCommand
lui-même a été chargé directement avant l'appel à ExecuteScalar
, donc je recherche pour callvirt puis utilisez Instruction.Previous
comme le début de la .try
.
Mais si la valeur de retour de ExecuteScalar
est utilisé par la suite, le compilateur génère l'IL suivante:
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)
Avec mon algorithme primitivy, cela est transformé en
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)
Alors PEVerify se plaint que je entrez un .try avec une pile non vide. Existe-t-il un moyen facile d'injecter le try-finally juste sur ExecuteScalar ou dois-je faire une analyse complète du flux sur toute la méthode, en calculant la profondeur de la pile à tout moment puis en chargeant/restaurant les valeurs avant/après l'essai/finalement?
EDIT:
Je suis en mesure de le faire fonctionner en balayant le haut/bas jusqu'à ce que je trouve deux points où la pile profondeur est 0. Dans mes tests limités, cela semble fonctionner, mais je serait toujours intéressé par une implémentation "propre" au lieu de scanner aveuglément l'IL.