Skip to content

CALLS edges fall back to File/Module node when enclosing_func_qn is unset — trace_path returns empty (root cause for #694/#480/#686/#678/#523) #763

Description

@lg320531124

Summary

trace_path and call-graph queries return empty results across several unrelated reports, and I believe they share one root cause: when the LSP does not populate the enclosing-function qualified name for a call site, calls_find_source falls back to attaching the CALLS edge to the File/Module node instead of the enclosing function. Downstream traversal is by function node, so the edge is invisible to function-scoped queries.

Filing this as a consolidated root-cause issue rather than a sixth per-language symptom report.

Root cause (source-level)

src/pipeline/pass_calls.c:404-417calls_find_source():

/* Find source node for a call: enclosing function or file node. */
static const cbm_gbuf_node_t *calls_find_source(cbm_pipeline_ctx_t *ctx, const char *rel,
                                                const char *enclosing_qn) {
    const cbm_gbuf_node_t *src = NULL;
    if (enclosing_qn) {
        src = cbm_gbuf_find_by_qn(ctx->gbuf, enclosing_qn);   // 1. enclosing function
    }
    if (!src) {
        char *fqn = cbm_pipeline_fqn_compute(ctx->project_name, rel, "__file__");
        src = cbm_gbuf_find_by_qn(ctx->gbuf, fqn);            // 2. FALLBACK -> File node
        free(fqn);
    }
    return src;
}

resolve_single_call (line 424) calls this for the edge source. When call->enclosing_func_qn is unset or unresolvable, step 1 misses and step 2 attaches the CALLS edge to the file's __file__ node. The edge exists and is traversable via query_graph on the file node, but trace_path / search_graph scoped to a function see zero outbound CALLS — hence "returns empty despite traversable CALLS edges".

Why this hits specific languages hardest

LSP enclosing-function resolution is most likely to come back empty for:

Six reports, one mechanism.

What I checked before filing

Fix direction (asking before coding)

Two honest options, I'd like a steer on which you prefer before opening a PR:

  1. Stop falling back to File when enclosing_qn is set-but-unresolved — drop (or defer) the edge rather than misattribute. trace_path correctness improves for resolvable sites, but total CALLS edge count drops where the enclosing function genuinely can't be resolved.
  2. Keep the File fallback but tag it — add a source_kind: "file_fallback" (or similar) prop on edges emitted from the fallback path, so function-scoped traversal can skip/flag them while the file-level graph stays intact. Additive, lower risk.

I lean toward (2) for a first PR since it doesn't reduce edge coverage, but (1) is cleaner if you'd rather not carry misattributed edges at all. Happy to implement either with a reproduce-first test.

Reproduce

A test that indexes a C/C++ or cross-repo call site where enclosing_func_qn is empty, then asserts trace_path on the enclosing function returns a non-empty path — RED on current main, GREEN after the fix. (I'll write this in the PR.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingparsing/qualityGraph extraction bugs, false positives, missing edgespriority/highNeeds near-term maintainer attention; high-impact bug, regression, safety issue, or release blocker.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions