Skip to content

C extractor: functions with #ifdef-split braces are dropped from the graph (no Function node) #961

Closed

Description

@DeusData

Summary

A C function whose body braces are split across #ifdef/#else branches is silently dropped from the code knowledge graph — no Function node is emitted for it, even though its callers remain and reference it as an unresolved name.

Root cause

The extractor parses raw source with tree-sitter without running the preprocessor, so both #ifdef branches are present at once. When a construct like this opens a brace in each branch but shares one close brace:

#ifdef _WIN32
    if (a() && b()) {      // open brace (branch 1)
        ...
#else
    if (c() && d()) {      // a different open brace (branch 2)
#endif
        ...shared body...
    }                      // single shared close brace

tree-sitter sees two if { opens and one } close → unbalanced braces → an ERROR node spanning the function body → no Function node is created. Recovery is localized (neighbouring functions extract fine), so the symptom is one function silently absent.

Reproduction (reproduce-first)

Found via a graph-vs-grep audit: cbm_path_within_root in src/mcp/mcp.c was absent from the graph while its 6 callers were present. Refactoring that specific function to a single unconditional control flow (#960) made it index. The general pattern remains unhandled.

Suggested RED reproduction for the bug-repro suite: a fixture C function with the #ifdef-splits-brace shape, asserting a Function node is produced for it. It should fail on the current extractor and pass once the extractor handles the pattern.

Impact

Low frequency but real: any function using this (uncommon) preprocessor pattern is invisible to search_graph/trace_path/callers, and dead-code / impact analysis will misjudge it. Also a latent correctness gap for graph-based audits.

Possible directions (extractor-side, harder than the per-function workaround)

  • Detect the ERROR-node-spanning-a-definition case and attempt a light preprocessor pass (pick one #ifdef branch) before re-parsing that region.
  • Or a targeted brace-rebalancing heuristic for #ifdef/#else/#endif regions inside a definition.

Not urgent; filing so the limitation is tracked rather than rediscovered.

Activity

  1. added this to the 0.9.2-rc milestone on Jul 8, 2026
  2. added
    parsing/qualityGraph extraction bugs, false positives, missing edges
    priority/normalStandard review queue; useful PR with ordinary maintainer urgency.
    on Jul 8, 2026
  3. modified the milestones: 0.9.2-rc, 0.9.1-rc on Jul 8, 2026
  4. DeusData commented on Jul 9, 2026

    @DeusData
    OwnerAuthor

    The coverage signal (#963, shipped in #968 / 97c6750) now detects this class automatically. A fresh full self-index flags 44 files as parse_partial; drilling into the two largest flagged regions:

    • src/mcp/mcp.c [4106-6406]: 49 functions in the source range, 48 recovered into the graph, exactly one missing — cbm_path_within_root (the known instance; its body contains an #ifdef _WIN32 split).
    • src/ui/http_server.c [574-1868]: 31 functions in range, one missing — handle_process_kill (new instance, same in-function #ifdef _WIN32 platform split).

    Smaller recurring classes from the same sweep (containing functions all recovered, only inner declarations lost): static CBM_TLS ... declarations across ~20 pipeline files, and the #ifdef __cplusplus } extern-C close braces in the vendored tree-sitter headers.

    Current confirmed loss list for this issue: cbm_path_within_root, handle_process_kill. The full flagged set is queryable any time via index_status or query_graph(graph="missed").

  5. DeusData commented on Jul 12, 2026

    @DeusData
    OwnerAuthor

    Fixed on main by 65a176e (merged in db330bf, PR #1050), shipping in v0.9.1.

    The general pattern is now handled, exactly along the lines the issue suggested: the preprocessed second pass (simplecpp, which already existed for macro-hidden CALLS) evaluates the conditionals so one branch survives and the tree parses clean — and since same-file token lines stay aligned, definitions recovered from it carry their original line numbers. Recovery is deliberately narrow: a def is adopted only when it intersects a raw-tree ERROR region, its name is visible on the corresponding original source line, and its qualified name wasn't already extracted — so clean files never gain phantom or duplicate defs.

    Both confirmed instances verified with the fixed binary: handle_process_kill (http_server.c, 580–647) and cbm_path_within_root (mcp.c, 5070–5085) now index with correct ranges — the second no longer only thanks to the #960 refactor. The reproduce-first fixture from this issue (#ifdef-splits-brace shape) is in the suite as the permanent guard, plus an inverse guard that clean #ifdef files gain nothing.

    The parse_partial coverage signal (#963) stays the detector for any residual instances of this class — a fresh self-index after this fix is the easiest way to confirm the flagged-region count drops.

  6. added a commit that references this issue on Jul 12, 2026
    65a176e
  7. added a commit that references this issue on Jul 15, 2026
    9d0b114
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingparsing/qualityGraph extraction bugs, false positives, missing edgespriority/normalStandard review queue; useful PR with ordinary maintainer urgency.

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions