Skip to content

L4 graph completeness: SDG port layer is disconnected from the statement ddg; call vertices sit off the CFG spine #115

Description

@rahlk

Found while validating python-sdk's new flows_to/slice verbs (python-sdk#270, PR python-sdk#297) end-to-end against 1.1.0. Audit corpus: the python-sdk cldk/ package at -a 4 — 758 callables, 29,562 ddg edges, 5,031 spanned body vertices.

1. The interprocedural port layer is an island

Observed ddg edge patterns across the entire corpus (src-class → dst-class):

pattern count
stmt → stmt 24,825
entry → stmt 4,737
anything touching a port (actual_in/actual_out/formal_in/formal_out) 0

Port wiring: 2,673 formal_in ports — 0 with outgoing ddg; 2,431 formal_out ports — 0 with incoming ddg. Meanwhile the app level emits 1,961 param_in + 1,788 param_out edges and per-call summary edges between those same ports.

So the SDG is two disconnected graphs: a statement-level ddg, and a port lattice (actual_in → formal_in, formal_out → actual_out, actual_in → actual_out) nothing flows into or out of. Consequences for any consumer:

  • flows_to(def_stmt, callee_formal) can never find a path — an end-to-end "this value reaches that parameter" witness is inexpressible; only port-to-port segments work.
  • Return-value flow (x = f(y) → uses of x) dies at actual_out.
  • Inside the callee, parameter flow is carried by @entry → stmt var edges, duplicating what formal_in → stmt should carry — so entering via param_in dead-ends at formal_in.

Missing edge classes (the fix checklist):

  • def stmt → actual_in:k (argument binding at the call site)
  • actual_out → use stmt (return-value binding)
  • formal_in:k → first-use stmts (or re-root the parameter's entry edges at the port)
  • return stmt → formal_out

Note: codeanalyzer-typescript already emits stmt → formal_out under the same schema (verified on its L4 sample), so the vocabulary supports this today — no schema change needed, emission only.

2. Every call vertex is off the CFG spine

1,781 of 5,031 spanned body vertices are touched by zero cfg edges — and all 1,781 are kind: call. A slice(edges=("cfg",)) or any cfg-reachability walk never visits a call site. If calls are intended as dataflow satellites of their statement, that design deserves a schema note; otherwise they should sit on the spine (stmt → call → next or a stmt —contains→ call link).

Minimal repro: any 2-function project at -a 4 (the python-sdk tests/resources/cpg/py-a4.json "pyfix" sample exhibits every one of these). Related: #112 (persist max_level in the Neo4j emit).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions