Skip to content

fix: stop reporting reachable C# code as unreachable - #735

Open
SirCrest wants to merge 1 commit into
peteromallet:mainfrom
SirCrest:fix/csharp-unreachable-false-positives
Open

fix: stop reporting reachable C# code as unreachable#735
SirCrest wants to merge 1 commit into
peteromallet:mainfrom
SirCrest:fix/csharp-unreachable-false-positives

Conversation

@SirCrest

@SirCrest SirCrest commented Sep 8, 2026

Copy link
Copy Markdown

Problem

detect_unreachable_code reports reachable C# as dead code. On a 211k-LOC
C# codebase it produced 7 unreachable_code findings and all 7 were false:

Unreachable code after yield_statement   Sussudio/Services/Runtime/FfmpegRuntimeLocator.cs:114
Unreachable code after yield_statement   Sussudio/Services/Runtime/FfmpegRuntimeLocator.cs:123
Unreachable code after yield_statement   tools/Common/PresentMon/PresentMonProbe.cs:278
Unreachable code after yield_statement   tools/Common/PresentMon/PresentMonProbe.cs:279
Unreachable code after return_statement  Sussudio/Services/Runtime/FfmpegRuntimeLocator.cs:202
Unreachable code after return_statement  Sussudio/Services/Runtime/NativeFfmpegCapabilityProbe.cs:335
Unreachable code after return_statement  Sussudio/Services/Audio/WasapiAudioCapture.cs:907

Three distinct causes:

  1. yield_statement was in _TERMINATOR_TYPES, with a comment conceding it
    is "not strictly terminating, but often last in generators". Consecutive
    yield return statements in an iterator are all reachable — each resumes when
    the caller pulls the next element — so any iterator with more than one yield
    in a block got flagged.

  2. C# local functions are hoisted. return result; followed by
    void Check(int code, string op) { ... } is idiomatic C#, but the declaration
    after the return was reported as unreachable.

  3. Nodes inside a tree-sitter ERROR region were reported. Where the parser
    loses sync the tree no longer describes real control flow; the
    WasapiAudioCapture.cs:907 hit above is an ERROR node whose text is just }.

Fix

  • Drop yield_statement from _TERMINATOR_TYPES. It is not a terminator in any
    supported language (Python yields parse as expression_statement, JS as
    yield_expression), so this only removes the C# false positives.
  • Add _HOISTED_DECLARATION_TYPES = {"local_function_statement"} and skip those
    when scanning past a terminator.
  • Skip ERROR nodes when reporting.

After the fix those same four files report [].

Tests

Four regression tests added to TestUnreachableCode:

  • test_csharp_local_function_after_return_is_reachable
  • test_csharp_consecutive_yields_are_reachable
  • test_error_nodes_are_not_reported_as_unreachable
  • test_csharp_statement_after_return_is_still_flagged — pins that genuine dead
    code after a return is still reported, so this does not weaken the detector.

python -m pytest desloppify/tests/lang/ -q → 603 passed. The two failures in
test_treesitter_imports_direct.py and the 34 in the full suite are present on
an unmodified checkout of main and are unrelated to this change.

🤖 Generated with Claude Code

detect_unreachable_code produced three classes of false positive on C#:

1. `yield_statement` was listed as a control-flow terminator, with a
   comment conceding it is "not strictly terminating". Consecutive
   `yield return` statements in an iterator are all reachable - each
   resumes when the caller pulls the next element - so any iterator with
   more than one yield in a block was flagged.

2. C# local functions are hoisted, so `return result; void Check(...) {}`
   is idiomatic rather than dead code, but the declaration following the
   return was reported.

3. Nodes inside a tree-sitter ERROR region were reported. Where the
   parser lost sync the tree no longer describes real control flow, so a
   "statement" after a terminator there is a parse artifact.

On a 211k-LOC C# codebase these accounted for every unreachable_code
finding: 7 reported, 7 false.

Real dead code after a return is still reported; a regression test pins
that alongside the three fixes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant