Skip to main content

Roadmap and limitations

Built​

  • Babel AST to LadybugDB, with one node table per type, SON {key, idx} containment edges and props slug references.
  • A CS-MAST-S hash on every node (cs-mast parser), with the plain babel parser as a fallback.
  • Stored source, with code(id) going from a node id back to its text.
  • Persistence: build on disk, save() from memory, reopen and keep adding files.
  • DECLARES: binding-introducing node to Identifier.
  • Scopes: Scope nodes with CREATES_SCOPE, PARENT_SCOPE and IN_SCOPE, so every declared binding is attached to the lexical scope it lives in. See Scopes.
  • REFERS_TO, READS and WRITES: each use of a name resolved to its declaration, and each access classified with its exact occurrence. See References, reads and writes.
  • FLOWS_TO: a conservative, flow-insensitive, intra-procedural value-flow graph through expressions and variables. See Value flow.
  • CALLS, ARGUMENT_TO and RETURNS_TO: statically resolved calls, with argument-to-parameter and return-to-callsite flow, so value paths cross functions. See Calls, arguments and returns.

Planned​

What's still needed before taint queries work, in dependency order:

  1. Property and heap flow. READS_PROPERTY, WRITES_PROPERTY and points-to over allocation sites, covering member access, destructuring, for-of/for-in and object/array literals. All of these are deferred for now. See the design.
  2. Module recovery. IMPORTS/EXPORTS across files, and bundler runtime semantics (webpack module factories, chunk registration), so calls through modules and bundle loaders resolve.
  3. Control flow. A CFG, CONTROL_DEPENDS_ON for implicit flows, and optionally reaching definitions or SSA to refine the flow-insensitive binding summaries.
  4. Taint rules. Source, sink and sanitizer patterns. Taint is then FLOWS_TO|ARGUMENT_TO|RETURNS_TO reachability from a source to a sink.
  5. Integration into JS Recon, replacing its current taint engine.

Each new edge type is a RELS entry plus the emitting code in flatten() (or a later pass). open(), save() and load() pick it up from RELS. See Architecture.

Known limitations​

LimitationEffectPossible fix
Hash collisions for sinc types&&/||, template text and a.b/a[b] can't be told apart by hashBroaden cs-mast's scat coverage
cs-mast has no error recoveryMalformed input needs parser: "babel" and loses hashesError recovery upstream in cs-mast
Random idsIds change on every importUse hash or file + startOffset for identity
add() isn't transactionalA failed COPY partway through leaves a partial file in the graphWrap add() in a transaction (Storage)
Unbounded code() source cacheMemory grows with the number of distinct files queriedLRU (marked ponytail: in the code)
save() buffers the whole graphThe whole graph is held in JS memory during the copyStream per table, if graphs get that large
Babel 7 (cs-mast) tree walked with Babel 8 VISITOR_KEYSA field renamed between versions would turn a subtree into props JSONGuarded by the parser-parity test. Re-check when either Babel is bumped.
Node labels exist only once seenMatching an absent node type is a binder error (edge tables always exist)Pre-create all tables, at the cost of about 250 empty tables per graph
Simplified scope modelNo per-iteration loop scopes, no separate parameter scope, no Annex B function hoisting, no TypeScript namespace scopesAdd them when resolution needs them. See Scopes: What isn't modelled
LadybugDB inline-filter bug(n {p: v}) followed by an OPTIONAL MATCH that finds nothing returns n's properties as nullFilter with WHERE (Query cookbook)
Flow-insensitive bindingsEvery write of a variable reaches every read of it, whatever the order, and FLOWS_TO has cyclesReaching definitions or SSA (Value flow)
Per-file resolutionGlobals shared between script files, and host objects, stay unresolvedModel the global object and host APIs
Context-insensitive callsEvery callsite of a function shares its param and return summaries, so id(s1) and id(s2) mixCall strings or cloning (Calls)
All-or-nothing call resolutionOne unknown definition (a param, an import, a member value) leaves a call unresolvedProperty and module recovery, then higher-order flow
Load cost per statementImport time is dominated by Ladybug, not analysis: about 500 COPY/DDL statements for a 178 KB bundle (react-dom: parse 0.6 s, flatten with all semantics 0.24 s, load 5.4 s)Fewer edge-table pairs, or batching pairs per statement if Ladybug allows it
Large saved filesA saved graph is at least about 6.7 MB, and roughly 750 bytes per edge (react-dom: 141K edges, 135 MB)Look at Ladybug page allocation per table and pair, and compression