Improve branch detection for lambda functions and async/await statements (#702)

Improve branch detection for lambda functions and async/await statements
This commit is contained in:
matteoerigozzi
2020-01-28 10:48:07 +01:00
committed by Marco Rossignoli
parent c314586a64
commit 3d9be6f90a
2 changed files with 24 additions and 13 deletions
+19 -6
View File
@@ -19,14 +19,26 @@ namespace Coverlet.Core.Symbols
{
private const int StepOverLineCode = 0xFEEFEE;
private static bool IsMoveNextInsideAsyncStateMachine(MethodDefinition methodDefinition)
// In case of nested compiler generated classes, only the root one presents the CompilerGenerated attribute.
// So let's search up to the outermost declaring type to find the attribute
private static bool IsCompilerGenerated(MethodDefinition methodDefinition)
{
if (!methodDefinition.FullName.EndsWith("::MoveNext()"))
TypeDefinition declaringType = methodDefinition.DeclaringType;
while (declaringType != null)
{
return false;
if (declaringType.CustomAttributes.Any(ca => ca.AttributeType.FullName == typeof(CompilerGeneratedAttribute).FullName))
{
return true;
}
declaringType = declaringType.DeclaringType;
}
if (methodDefinition.DeclaringType.CustomAttributes.Count(ca => ca.AttributeType.FullName == typeof(CompilerGeneratedAttribute).FullName) > 0)
return false;
}
private static bool IsMoveNextInsideAsyncStateMachine(MethodDefinition methodDefinition)
{
if (methodDefinition.FullName.EndsWith("::MoveNext()") && IsCompilerGenerated(methodDefinition))
{
foreach (InterfaceImplementation implementedInterface in methodDefinition.DeclaringType.Interfaces)
{
@@ -72,7 +84,8 @@ namespace Coverlet.Core.Symbols
methodDefinition.Body.Instructions[0].OpCode == OpCodes.Ldarg) &&
methodDefinition.Body.Instructions[1].OpCode == OpCodes.Ldfld &&
methodDefinition.Body.Instructions[1].Operand is FieldDefinition fd && fd.Name == "<>1__state" &&
((methodDefinition.Body.Instructions[1].Operand is FieldDefinition fd && fd.Name == "<>1__state") ||
(methodDefinition.Body.Instructions[1].Operand is FieldReference fr && fr.Name == "<>1__state")) &&
(methodDefinition.Body.Instructions[2].OpCode == OpCodes.Stloc &&
methodDefinition.Body.Instructions[2].Operand is VariableDefinition vd && vd.Index == 0) ||
@@ -104,7 +117,7 @@ namespace Coverlet.Core.Symbols
/*
If method is a generated MoveNext we'll skip first branches (could be a switch or a series of branches)
that check state machine value to jump to correct state(for instance after a true async call)
that check state machine value to jump to correct state (for instance after a true async call)
Check if it's a Cond_Branch on state machine current value int num = <>1__state;
We are on branch OpCode so we need to go back by max 2 operation to reach ldloc.0 the load of "num"
Max 2 because we handle following patterns
@@ -21,6 +21,7 @@ namespace Coverlet.Core.Tests
{
instance.InvokeAnonymous_Test();
((Task<bool>)instance.InvokeAnonymousAsync_Test()).ConfigureAwait(false).GetAwaiter().GetResult();
return Task.CompletedTask;
}, persistPrepareResultToFile: pathSerialize);
return 0;
@@ -34,15 +35,12 @@ namespace Coverlet.Core.Tests
// Expected branches
(22, 0, 0),
(22, 1, 1),
(50, 2, 0),
(50, 3, 1),
// Unexpected branches
(50, 0, 0),
(50, 1, 1),
// Unexpected branches - generated by compiler to cache delegate instance
(20, 0, 1),
(20, 1, 1),
(49, 0, 1),
(49, 1, 0),
(54, 4, 0),
(54, 5, 1),
(48, 0, 1),
(48, 1, 1)
);