Skip to content

fix(dataflow): -j 1 and -j N disagree on the file set; default sequential path emits CFGs for <50% of callables #94

Description

@rahlk

Problem

The -j N vs -j 1 byte-identical determinism gate — the one src/dataflow/index.ts:92-95 names as the correctness guarantee for the worker path —

"correctness is guarded either way by the byte-identical N-vs-1 gate"

does not hold on a multi-tsconfig project, and the default (sequential) path is the losing side.

The two paths build their ts-morph Project from different file sets:

  • Sequential (extractSequential, src/dataflow/index.ts:156-170) reuses the main-thread project, which is the tsconfig-scoped program.
  • Workers (projectFor, src/dataflow/worker.ts:53-62) construct with skipAddingFilesFromTsConfig: true and then add everything discoverSourceFiles(root) finds — whole-repository discovery, which is what the symbol-table build also uses.

So the worker path's file set matches the symbol table and the sequential one does not. indexCallableDecls then fails to find a declaration node for every callable outside the tsconfig program, and extractSequential skips it (if (!fn) continue).

Consequence:-j defaults to sequential (cli.ts:50-51, index.ts:96), so by default the analyzer emits L3/L4 graphs for less than half the callables it put in the symbol table.

Reproduction

OWASP Juice Shop (3 tsconfigs: root, frontend/tsconfig.json, frontend/tsconfig.base.json), -a 4 --tsc-only --skip-tests --no-build, fresh cache per run:

-j 1 (default)-j 8
callables in symbol table2,4932,493
callables with a CFG1,093 (43.8%)2,344 (94.0%)
ddg edges11,14715,925
param_in4,8865,333
param_out1,0331,535
artifact12.4 MB14.3 MB

Not byte-identical, and not close.

Pre-existing — reproduced on 8032d08 (before #92/#93 landed) at 437 vs 946 with a CFG. The anonymous-callable work scaled the gap but did not create it.

Why CI never caught it

Every fixture in test/fixtures/ is a single-tsconfig project whose include covers all its sources, so the tsconfig-scoped set and the whole-repo set coincide and the paths agree. Verified: anon-app, dataflow-app and sample-app are all byte-identical between -j 1 and -j 8. The gate can only fail on a fixture where the two file sets differ, and no such fixture exists.

Scope boundary

Fixes the file-set divergence between the sequential and worker extraction paths, and adds a fixture that can actually fail the gate.

Does not decide which file set is correct — that is #58 (honor tsconfig boundaries vs. make whole-repository discovery explicit). This issue only requires the two paths to agree, whichever #58 settles on. Does not thread per-program tsconfigs into the workers (#77); nested-program under-resolution is orthogonal and stays as documented at core.ts:30-37.

Goals

  • Make extractSequential and projectFor derive their file set from one shared helper, so the two paths cannot drift
  • Add a multi-tsconfig fixture (root program excluding a subdirectory that still contains callables) to test/fixtures/
  • Add an -j 1 vs -j N byte-identity assertion over that fixture to the suite — the gate index.ts:92-95 already claims exists
  • Re-run the determinism gate on Juice Shop and record the numbers

Caveats and known risks

  • If fix: honor tsconfig source boundaries or make whole-repository discovery explicit #58 lands first and narrows discovery to tsconfig boundaries, the symbol table loses the ~1,400 out-of-program callables too, and this gap closes from the other direction — coordinate so the fix is not written twice.
  • Whichever file set wins changes L3/L4 output volume substantially in one direction or the other; that is a user-visible change and needs the same migration-note treatment as any other.
  • The -j 0 → 1 default means most users have only ever seen the under-populated output, so "fixing" this will look like a large unexplained growth in artifact size unless it is called out.

Definition of done

  • -j 1 and -j N outputs are byte-identical on a multi-tsconfig fixture, asserted in the suite and failing before the fix
  • Juice Shop at -a 4: -j 1 and -j 8 produce byte-identical analysis.json
  • The claim at src/dataflow/index.ts:92-95 is either true or rewritten to say what is actually guaranteed

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