Skip to content

fix: scan #else branches of unknown #if/#ifdef blocks in the macro shield (#1720) - #1764

Merged
squid-protocol merged 4 commits into
squid-protocol:mainfrom
uuzzrm:fix/macro-shield-else-branch-1720
Aug 17, 2026
Merged

fix: scan #else branches of unknown #if/#ifdef blocks in the macro shield (#1720)#1764
squid-protocol merged 4 commits into
squid-protocol:mainfrom
uuzzrm:fix/macro-shield-else-branch-1720

Conversation

@uuzzrm

@uuzzrm uuzzrm commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #1720.

The macro shield in _build_brace_safe_stream (gitgalaxy/core/detector.py) assumed the first branch of every #if/#ifdef was the active one and blindly blanked the #else branch:

elif stripped.startswith(("#else", "#elif")):
    if not in_dead_branch and dead_nesting_depth == 0:
        in_dead_branch = True

That's wrong for the common case where the condition is a macro name the engine can't evaluate (#if PLATFORM_WINDOWS, #ifdef DEBUG): the #else branch is just as likely to hold the real implementation, and it was scrubbed before the regex engine ever saw it. Tree-sitter ground truth parses both branches.

The fix

Replaced the single in_dead_branch flag with a per-open-#if policy stack, where each entry is (static_truth, side):

  • #if 1 / #if true → first branch alive, #else dead
  • #if 0 / #if false → first branch dead, #else alive
  • anything else (macro names, defined(X), expressions) → both branches scanned

A new _classify_preproc_condition helper returns the static truth value (stripping C comments first). Nested blocks stay dead when any enclosing #if is statically dead (any() over the stack), and #endif pops restore prior state. #elif starts a fresh condition on the else side.

Evidence on the pinned corpus

  • godot/main.cppMain::test_setup (130-line body) and Main::test_cleanup were being truncated to stubs
  • godot/rendering_server_default.h — the redraw_request declared in an #else block is now extracted
  • godot/gdscript_vm.cppOPCODE/switch handlers behind unknown #ifs now show up
  • cpython/frameobject.c — same class of recovery on the C side

Tests

  • test_detector_c_macro_else_branch_is_scanned_issue_1720 — regression test for the exact cpp: func_recall_pct - Naive #else Macro Shielding #1720 shape
  • test_detector_c_macro_static_truth_prunes_branches — pins #if 0 / #if 1 pruning in both directions plus nested liveness
  • Both golden-master fixtures re-blessed via tests/tools/update_golden_master.py (the sanctioned path, showing the full diff); pytest -m golden_crucible passes in full-precision and zero-dependency modes
  • audit_check.py: ruff clean, dead-key OK, ast-accuracy OK
  • tree_sitter_accuracy_audit --lang cpp: no regressions

uuzzrm added 2 commits August 16, 2026 09:59
…ield (squid-protocol#1720)

The preprocessor shield in _build_brace_safe_stream assumed the first branch of every #if/#ifdef was the active one and blindly blanked the #else branch. Any real implementation living in #else was silently dropped from extraction, even though tree-sitter ground truth parses both branches.

Replace the in_dead_branch flag with a per-open-#if policy stack:
- #if 1 / #if true  -> first branch alive, #else dead
- #if 0 / #if false -> first branch dead, #else alive
- anything else (macro name, defined(X), expression) -> scan BOTH branches
Nested blocks honor the outer branch's liveness via any() over the stack.
…quid-protocol#1720)

Re-running the pinned language-crucible corpus in both dependency modes after the shield change shifts the c/cpp snapshots: implementations that were previously blanked out of #else branches are now extracted (godot main.cpp's Main::test_setup/test_cleanup, rendering_server_default.h's redraw_request, gdscript_vm.cpp's OPCODE/OPCODE_WHILE/OPCODE_SWITCH, cpython frameobject.c, and others). Both golden crucible modes pass against the updated fixtures.
@uuzzrm
uuzzrm requested a review from squid-protocol as a code owner August 16, 2026 17:00
@uuzzrm

uuzzrm commented Aug 16, 2026

Copy link
Copy Markdown
Contributor Author

Closes #1720

@squid-protocol

Copy link
Copy Markdown
Owner

Nice fix for #1720, well-evidenced and correctly reblessed via the sanctioned update_golden_master.py path. Found one real edge-case bug in the new branch-tracking logic before this merges.

Bug: no-space #if(/#elif( desyncs the branch stack

_build_brace_safe_stream requires a literal space to recognize a directive:

if stripped.startswith("#if "):
    branch_stack.append((self._classify_preproc_condition(stripped[3:].strip()), "first"))
...
elif stripped.startswith("#elif ") and branch_stack:

But #if(1) / #if(FOO) (valid C preprocessor syntax — no whitespace required between #if and the expression) doesn't match "#if ", so nothing gets pushed onto branch_stack for it. Its matching #endif, however, has no such space requirement and does pop the stack — popping the wrong (outer) entry instead of a no-op, and desyncing the stack from actual nesting depth for the rest of the file.

Concrete repro against this branch:

code = (
    "#if 0\n"
    "int deadOne() { return 1; }\n"
    "#if(1)\n"
    "int deadTwo() { return 2; }\n"
    "#endif\n"
    "int deadThree() { return 3; }\n"   # should stay dead -- still inside outer #if 0, before #else
    "#else\n"
    "int aliveOne() { return 4; }\n"
    "#endif\n"
)

Extracted functions: ['deadThree', 'aliveOne']deadThree is a false positive. The premature pop from the untracked #if(1)'s #endif clears the outer #if 0 entry early, so deadThree (which is genuinely still inside the dead first branch) gets scanned as if alive.

The old code (stripped.startswith("#if"), no space required) didn't have this failure mode since it only tracked symmetric depth, not per-line condition text — so this is a regression class introduced by this PR, not a pre-existing gap.

Suggested fix: swap the space-anchored checks for a word-boundary match, e.g. re.match(r"#if\b", stripped) / r"#elif\b" instead of .startswith("#if ") / .startswith("#elif "). That handles #if(, #if\t1, etc. without accidentally matching #ifdef/#ifndef (the \b correctly disambiguates, since if immediately followed by d has no word boundary).

This is narrow enough that it doesn't show up in the pinned crucible corpus (CI is green), but given this is core parsing logic, I'd rather see it folded into this PR than merged now with a fast-follow.

@squid-protocol squid-protocol left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! I added the \b word-boundary fix for the #if( spacing issue as requested. Approving!

…-branch-1720

# Conflicts:
#	tests/golden_master_audit.json
#	tests/golden_master_zero_dep_audit.json

@squid-protocol squid-protocol left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! I added the \b word-boundary fix for the #if( spacing issue as requested. Approving!

@squid-protocol
squid-protocol merged commit 75fbef3 into squid-protocol:main Aug 17, 2026
27 checks passed
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.

cpp: func_recall_pct - Naive #else Macro Shielding

2 participants