Skip to content

Commit e300e77

Browse files
committed
skip generated branches for method group
1 parent 21d3964 commit e300e77

File tree

4 files changed

+96
-10
lines changed

4 files changed

+96
-10
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 .NET 7 Method Group branch coverage issue [#1447](https://github.com/coverlet-coverage/coverlet/issues/1447)
1011
-Fix ExcludeFromCodeCoverage does not exclude method in a partial class [#1548](https://github.com/coverlet-coverage/coverlet/issues/1548)
1112
-Fix ExcludeFromCodeCoverage does not exclude F# task [#1547](https://github.com/coverlet-coverage/coverlet/issues/1547)
1213
-Fix issues where ExcludeFromCodeCoverage ignored [#1431](https://github.com/coverlet-coverage/coverlet/issues/1431)

src/coverlet.core/Symbols/CecilSymbolHelper.cs

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
using System.Collections.Generic;
77
using System.Linq;
88
using System.Runtime.CompilerServices;
9-
9+
using System.Text.RegularExpressions;
1010
using Coverlet.Core.Abstractions;
1111
using Coverlet.Core.Extensions;
1212

@@ -251,6 +251,46 @@ Lambda cached field pattern
251251
return false;
252252
}
253253

254+
private static bool SkipDelegateCacheField(Instruction instruction)
255+
{
256+
/*
257+
Delegate cached field pattern (e.g. method groups)
258+
259+
IL_0001: ldc.i4.1
260+
IL_0002: newarr [System.Runtime]System.Int32
261+
IL_0007: dup
262+
IL_0008: ldc.i4.0
263+
IL_0009: ldc.i4.1
264+
IL_000a: stelem.i4
265+
IL_000b: ldsfld class [System.Runtime]System.Func`2<int32, int32> Coverlet.Core.Samples.Tests.Issue_1447/'<>O'::'<0>__Map'
266+
IL_0010: dup
267+
IL_0011: brtrue.s IL_0026 -> CHECK IF CACHED FIELD IS NULL OR JUMP TO DELEGATE USAGE
268+
269+
IL_0013: pop
270+
IL_0014: ldnull
271+
IL_0015: ldftn int32 Coverlet.Core.Samples.Tests.Issue_1447::Map(int32)
272+
IL_001b: newobj instance void class [System.Runtime]System.Func`2<int32, int32>::.ctor(object, native int)
273+
IL_0020: dup
274+
IL_0021: stsfld class [System.Runtime]System.Func`2<int32, int32> Coverlet.Core.Samples.Tests.Issue_1447/'<>O'::'<0>__Map'
275+
276+
(USE DELEGATE FIELD)
277+
IL_0026: call class [System.Runtime]System.Collections.Generic.IEnumerable`1<!!1> [System.Linq]System.Linq.Enumerable::Select<int32, int32>(class [System.Runtime] System.Collections.Generic.IEnumerable`1<!!0>, class [System.Runtime] System.Func`2<!!0, !!1>)
278+
*/
279+
280+
Instruction current = instruction.Previous;
281+
for (int instructionBefore = 2; instructionBefore > 0 && current.Previous != null; current = current.Previous, instructionBefore--)
282+
{
283+
if (current.OpCode == OpCodes.Ldsfld && current.Operand is FieldDefinition fd &&
284+
// https://github.com/dotnet/roslyn/blob/main/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNames.cs#L513
285+
Regex.IsMatch(fd.Name, "^<\\d+>__") &&
286+
IsCompilerGenerated(fd))
287+
{
288+
return true;
289+
}
290+
}
291+
return false;
292+
}
293+
254294
private static bool SkipGeneratedBranchForExceptionRethrown(List<Instruction> instructions, Instruction instruction)
255295
{
256296
/*
@@ -984,7 +1024,7 @@ public IReadOnlyList<BranchPoint> GetBranchPoints(MethodDefinition methodDefinit
9841024
continue;
9851025
}
9861026

987-
if (SkipLambdaCachedField(instruction))
1027+
if (SkipLambdaCachedField(instruction) || SkipDelegateCacheField(instruction))
9881028
{
9891029
continue;
9901030
}

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

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ public void Lambda_Issue343()
2020
{
2121
CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run<Lambda_Issue343>(async instance =>
2222
{
23-
instance.InvokeAnonymous_Test();
24-
await (Task<bool>)instance.InvokeAnonymousAsync_Test();
25-
}, persistPrepareResultToFile: pathSerialize[0]);
23+
instance.InvokeAnonymous_Test();
24+
await (Task<bool>)instance.InvokeAnonymousAsync_Test();
25+
}, persistPrepareResultToFile: pathSerialize[0]);
2626
return 0;
2727
}, new string[] { path });
2828

@@ -53,8 +53,8 @@ public void AsyncAwait_Issue_730()
5353
{
5454
CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run<Issue_730>(async instance =>
5555
{
56-
await (Task)instance.Invoke();
57-
},
56+
await (Task)instance.Invoke();
57+
},
5858
persistPrepareResultToFile: pathSerialize[0]);
5959

6060
return 0;
@@ -81,9 +81,9 @@ public void Lambda_Issue760()
8181
{
8282
CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run<Issue_760>(async instance =>
8383
{
84-
await (Task)instance.If();
85-
await (Task)instance.Foreach();
86-
},
84+
await (Task)instance.If();
85+
await (Task)instance.Foreach();
86+
},
8787
persistPrepareResultToFile: pathSerialize[0]);
8888

8989
return 0;
@@ -134,5 +134,35 @@ public void Issue_1056()
134134
File.Delete(path);
135135
}
136136
}
137+
138+
[Fact]
139+
public void Issue_1447()
140+
{
141+
string path = Path.GetTempFileName();
142+
try
143+
{
144+
FunctionExecutor.Run(async (string[] pathSerialize) =>
145+
{
146+
CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run<Issue_1447>(instance =>
147+
{
148+
instance.Query1();
149+
instance.Query2();
150+
return Task.CompletedTask;
151+
},
152+
persistPrepareResultToFile: pathSerialize[0]);
153+
154+
return 0;
155+
}, new string[] { path });
156+
157+
TestInstrumentationHelper.GetCoverageResult(path).GenerateReport(show: true)
158+
.Document("Instrumentation.Lambda.cs")
159+
.AssertLinesCovered((138, 1), (143, 1))
160+
.ExpectedTotalNumberOfBranches(0);
161+
}
162+
finally
163+
{
164+
File.Delete(path);
165+
}
166+
}
137167
}
138168
}

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,4 +130,19 @@ private static object Do2(System.Func<object, object> func)
130130

131131
public void WriteLine(string str) { }
132132
}
133+
134+
public class Issue_1447
135+
{
136+
public System.Collections.Generic.IEnumerable<int> Query1()
137+
{
138+
return new[] { 1 }.Select(Map);
139+
}
140+
141+
public System.Collections.Generic.IEnumerable<int> Query2()
142+
{
143+
return new[] { 1 }.Select(Map);
144+
}
145+
146+
private static int Map(int row) => row + 1;
147+
}
133148
}

0 commit comments

Comments
 (0)