Roadmap and limitations
Built
- Babel AST to LadybugDB, with one node table per type,
SON {key, idx}containment edges andpropsslug references. - A CS-MAST-S hash on every node (
cs-mastparser), with the plainbabelparser 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 toIdentifier.- Scopes:
Scopenodes withCREATES_SCOPE,PARENT_SCOPEandIN_SCOPE, so every declared binding is attached to the lexical scope it lives in. See Scopes. REFERS_TO,READSandWRITES: 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_TOandRETURNS_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:
- Property and heap flow.
READS_PROPERTY,WRITES_PROPERTYand 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. - Module recovery.
IMPORTS/EXPORTSacross files, and bundler runtime semantics (webpack module factories, chunk registration), so calls through modules and bundle loaders resolve. - Control flow. A CFG,
CONTROL_DEPENDS_ONfor implicit flows, and optionally reaching definitions or SSA to refine the flow-insensitive binding summaries. - Taint rules. Source, sink and sanitizer patterns. Taint is then
FLOWS_TO|ARGUMENT_TO|RETURNS_TOreachability from a source to a sink. - 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
| Limitation | Effect | Possible fix |
|---|---|---|
Hash collisions for sinc types | &&/||, template text and a.b/a[b] can't be told apart by hash | Broaden cs-mast's scat coverage |
| cs-mast has no error recovery | Malformed input needs parser: "babel" and loses hashes | Error recovery upstream in cs-mast |
| Random ids | Ids change on every import | Use hash or file + startOffset for identity |
add() isn't transactional | A failed COPY partway through leaves a partial file in the graph | Wrap add() in a transaction (Storage) |
Unbounded code() source cache | Memory grows with the number of distinct files queried | LRU (marked ponytail: in the code) |
save() buffers the whole graph | The whole graph is held in JS memory during the copy | Stream per table, if graphs get that large |
Babel 7 (cs-mast) tree walked with Babel 8 VISITOR_KEYS | A field renamed between versions would turn a subtree into props JSON | Guarded by the parser-parity test. Re-check when either Babel is bumped. |
| Node labels exist only once seen | Matching 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 model | No per-iteration loop scopes, no separate parameter scope, no Annex B function hoisting, no TypeScript namespace scopes | Add 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 null | Filter with WHERE (Query cookbook) |
| Flow-insensitive bindings | Every write of a variable reaches every read of it, whatever the order, and FLOWS_TO has cycles | Reaching definitions or SSA (Value flow) |
| Per-file resolution | Globals shared between script files, and host objects, stay unresolved | Model the global object and host APIs |
| Context-insensitive calls | Every callsite of a function shares its param and return summaries, so id(s1) and id(s2) mix | Call strings or cloning (Calls) |
| All-or-nothing call resolution | One unknown definition (a param, an import, a member value) leaves a call unresolved | Property and module recovery, then higher-order flow |
| Load cost per statement | Import 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 files | A 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 |