Skip to content

Commit 84876b0

Browse files
authored
Implementation of exclusion (#1277)
implementation of exclusion
1 parent 0632e7a commit 84876b0

File tree

4 files changed

+95
-1
lines changed

4 files changed

+95
-1
lines changed

Documentation/Changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77
## Unreleased
88

99
### Fixed
10+
-Fix wrong branch coverage with EnumeratorCancellation attribute [#1275](https://github.com/coverlet-coverage/coverlet/issues/1275)
1011
-Fix negative coverage exceeding int.MaxValue [#1266](https://github.com/coverlet-coverage/coverlet/issues/1266)
1112
-Fix summary output format for culture de-DE [#1263](https://github.com/coverlet-coverage/coverlet/issues/1263)
1213
-Fix branch coverage issue for finally block with await [#1233](https://github.com/coverlet-coverage/coverlet/issues/1233)

src/coverlet.core/Symbols/CecilSymbolHelper.cs

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -855,7 +855,6 @@ instructions[i].Operand is FieldDefinition field &&
855855
return false;
856856
}
857857

858-
859858
static bool DisposeCheck(List<Instruction> instructions, Instruction instruction, int currentIndex)
860859
{
861860
// Within the compiler-generated async iterator, there are at least a
@@ -891,6 +890,40 @@ static bool DisposeCheck(List<Instruction> instructions, Instruction instruction
891890
}
892891
}
893892

893+
private bool SkipGeneratedBranchesForEnumeratorCancellationAttribute(List<Instruction> instructions, Instruction instruction)
894+
{
895+
// For async-enumerable methods an additional cancellation token despite the default one can be passed.
896+
// The EnumeratorCancellation attribute marks the parameter whose value is received by GetAsyncEnumerator(CancellationToken).
897+
// Therefore the compiler generates the field x__combinedTokens and generates some additional branch points.
898+
//
899+
// IL_0118: ldarg.0
900+
// IL_0119: ldfld class [System.Runtime]System.Threading.CancellationTokenSource Issue1275.AwaitForeachReproduction/'<AsyncEnumerable>d__1'::'<>x__combinedTokens'
901+
// IL_011E: brfalse.s IL_0133
902+
//
903+
// We'll eliminate these wherever they appear. It's reasonable to just look for a "brfalse" or "brfalse.s" instruction, preceded
904+
// immediately by "ldfld" of the compiler-generated "<>x__combinedTokens" field.
905+
906+
int branchIndex = instructions.BinarySearch(instruction, new InstructionByOffsetComparer());
907+
908+
if (instruction.OpCode != OpCodes.Brfalse &&
909+
instruction.OpCode != OpCodes.Brfalse_S)
910+
{
911+
return false;
912+
}
913+
914+
if (branchIndex >= 2 &&
915+
instructions[branchIndex - 1].OpCode == OpCodes.Ldfld &&
916+
instructions[branchIndex - 1].Operand is FieldDefinition field &&
917+
field.FieldType.FullName.Equals("System.Threading.CancellationTokenSource") &&
918+
field.FullName.EndsWith("x__combinedTokens") &&
919+
(instructions[branchIndex - 2].OpCode == OpCodes.Ldarg ||
920+
instructions[branchIndex - 2].OpCode == OpCodes.Ldarg_0))
921+
{
922+
return true;
923+
}
924+
return false;
925+
}
926+
894927
// https://github.com/dotnet/roslyn/blob/master/docs/compilers/CSharp/Expression%20Breakpoints.md
895928
private static bool SkipExpressionBreakpointsBranches(Instruction instruction) => instruction.Previous is not null && instruction.Previous.OpCode == OpCodes.Ldc_I4 &&
896929
instruction.Previous.Operand is int operandValue && operandValue == 1 &&
@@ -973,6 +1006,11 @@ public IReadOnlyList<BranchPoint> GetBranchPoints(MethodDefinition methodDefinit
9731006
}
9741007
}
9751008

1009+
if (SkipGeneratedBranchesForEnumeratorCancellationAttribute(instructions, instruction))
1010+
{
1011+
continue;
1012+
}
1013+
9761014
if (SkipBranchGeneratedExceptionFilter(instruction, methodDefinition))
9771015
{
9781016
continue;

test/coverlet.core.tests/Coverage/CoverageTests.AsyncAwait.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.IO;
22
using System.Linq;
3+
using System.Threading;
34
using System.Threading.Tasks;
45

56
using Coverlet.Core.Samples.Tests;
@@ -182,5 +183,35 @@ public void AsyncAwait_Issue_1233()
182183
File.Delete(path);
183184
}
184185
}
186+
187+
[Fact]
188+
public void AsyncAwait_Issue_1275()
189+
{
190+
string path = Path.GetTempFileName();
191+
try
192+
{
193+
FunctionExecutor.Run(async (string[] pathSerialize) =>
194+
{
195+
CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run<Issue_1275>(instance =>
196+
{
197+
var cts = new CancellationTokenSource();
198+
((Task)instance.Execute(cts.Token)).ConfigureAwait(false).GetAwaiter().GetResult();
199+
return Task.CompletedTask;
200+
},
201+
persistPrepareResultToFile: pathSerialize[0]);
202+
203+
return 0;
204+
}, new string[] { path });
205+
206+
var document = TestInstrumentationHelper.GetCoverageResult(path).Document("Instrumentation.AsyncAwait.cs");
207+
document.AssertLinesCoveredFromTo(BuildConfiguration.Debug, 170, 176);
208+
document.AssertBranchesCovered(BuildConfiguration.Debug, (171, 0, 1), (171, 1, 1));
209+
Assert.DoesNotContain(document.Branches, x => x.Key.Line == 176);
210+
}
211+
finally
212+
{
213+
File.Delete(path);
214+
}
215+
}
185216
}
186217
}

test/coverlet.core.tests/Samples/Instrumentation.AsyncAwait.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,4 +151,28 @@ async public Task Test()
151151
}
152152
}
153153
}
154+
155+
public class Issue_1275
156+
{
157+
public async Task<int> Execute(System.Threading.CancellationToken token)
158+
{
159+
int sum = 0;
160+
161+
await foreach (int result in AsyncEnumerable(token))
162+
{
163+
sum += result;
164+
}
165+
166+
return sum;
167+
}
168+
169+
async System.Collections.Generic.IAsyncEnumerable<int> AsyncEnumerable([System.Runtime.CompilerServices.EnumeratorCancellation] System.Threading.CancellationToken cancellationToken)
170+
{
171+
for (int i = 0; i < 1; i++)
172+
{
173+
await Task.Delay(1, cancellationToken);
174+
yield return i;
175+
}
176+
}
177+
}
154178
}

0 commit comments

Comments
 (0)