Eliminates phantom branches from async try-finally with await statements#1904
Merged
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
This PR updates coverlet’s branch-point detection to ignore compiler-generated conditional branches produced by async state machines for try/finally blocks that contain await in the finally, and adds coverage samples/tests intended to prevent regressions.
Changes:
- Add async try/finally branch-skipping logic to
CecilSymbolHelper.GetBranchPoints()for async state machineMoveNext(). - Add new async sample methods (Issue_1767) covering multiple try/finally + await patterns.
- Add new coverage tests asserting “no phantom branches” (and preserving real user branches) for the new samples.
Reviewed changes
Copilot reviewed 2 out of 3 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
src/coverlet.core/Symbols/CecilSymbolHelper.cs |
Adds SkipGeneratedBranchesForAsyncTryFinally and wires it into async state-machine branch filtering. |
test/coverlet.core.coverage.tests/Samples/Instrumentation.AsyncAwait.cs |
Appends new async try/finally sample methods used for instrumentation/coverage validation. |
test/coverlet.core.coverage.tests/Coverage/CoverageTests.AsyncAwait.cs |
Adds new tests aimed at detecting/removing phantom branches for the new async try/finally samples. |
sstjean
added a commit
to sstjean/epcubegraph
that referenced
this pull request
Apr 29, 2026
Coverlet 10.0.0 emits async state machine classes (d__N) with 0 hits for await using / try-finally patterns, causing false coverage drops. Known upstream issue: coverlet-coverage/coverlet#1337, #1767. Active fix in progress: coverlet-coverage/coverlet#1904. 8.0.1 correctly reports 100% coverage for the same code. Upgrade to 10.x once PR #1904 ships.
Collaborator
Author
|
Current coverage metrics for master and pull request.
|
f9584c3 to
26b7ce7
Compare
…reporting - Updated .gitignore to exclude dotnet-install.sh. - Improved documentation on branch coverage behavior for async methods in UnderstandingBranchCoverage.md. - Enhanced Instrumenter.cs to implement trampoline instrumentation for taken-branch paths, preventing false-positive coverage for async try-finally patterns. - Added new methods in CecilSymbolHelper.cs to skip generated branches for async try-finally constructs. - Introduced tests in CollectCoverageTests.cs and CoverageTests.AsyncAwait.cs to validate coverage behavior for async methods with try-finally blocks, ensuring no phantom branches are reported. - Updated TestEnvironment.cs to handle interactive stdin scenarios. - Refined assertions in CoverageTests.SelectionStatements.cs to accurately reflect branch coverage.
Refactor Instrumenter.cs to use ContainsKey/Add for .NET Standard 2.0 and TryAdd for other frameworks when updating branchRedirectMap, improving cross-framework compatibility. Also, clarify a comment and remove an obsolete issue reference in CoverageTests.AsyncAwait.cs without changing test logic.
…eview comments Switch trampolines to use (branchOffset, path) as the key, ensuring each branch path (including switch arms sharing targets) gets a unique trampoline and independent counter. Update ReplaceInstructionTarget to match new keying. Adjust async/await test to assert correct branch coverage and prevent phantom branches from multiple awaits in finally blocks.
Detect and skip compiler-generated branches from relational pattern `is >= A and <= B` in compound `&&` expressions to prevent misleading branch coverage. Add targeted samples and unit tests to verify correct handling of both compound and simple relational pattern scenarios. Update copyright headers.
Add regression test and update instrumenter logic to guarantee the injected Tracker class is marked with [CompilerGenerated] and [ExcludeFromCodeCoverage] attributes, addressing issue coverlet-coverage#1828 and improving compatibility with reflection-based and coverage tools.
Update test to match tracker type by exact namespace and name, using module filename and identifier. This ensures robustness when assemblies contain multiple tracker types from prior instrumentation runs.
…ge#1337) Enhance CecilSymbolHelper to detect both direct and local variable-based exception null checks in async/await state machines, preventing false-positive phantom branches. Add targeted test and sample method for Issue coverlet-coverage#1337. Update instructions and minor formatting.
Remove proximity search for await-related patterns when identifying compiler-generated null-checks in async state machines. Now rely solely on opcode, field name, field type, ldarg.0, and presence of finally handler for detection, streamlining the logic.
Async try/finally blocks compile to Catch handlers, not Finally, so the previous check for ExceptionHandlerType.Finally would always return false. Now, the method always returns true when other conditions are met, ensuring correct identification of compiler-generated null-checks in async methods.
This was referenced May 14, 2026
This was referenced May 16, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Issues #1313, #1337 relates to incorrect or incomplete coverage reporting for async methods containing try-finally blocks with await statements in the finally block. When the C# compiler generates IL for such constructs, it creates complex exception handling patterns within the async state machine's MoveNext() method that include:
These compiler-generated branches were being reported as user code branches, leading to:
• Phantom branches in coverage reports
• Incorrect branch coverage metrics
• Confusing coverage results where simple linear code shows uncovered branches
Issue #1828 is resolved and the attributes are copied during instrumentation.