Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 21 additions & 5 deletions graph/javascript/engine/call-edge-generation/partial-dispatch.dl
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,27 @@ swallowed_read(e, i) :- expr_kind(_, "ELEMENT_ACCESS", _, e),
expr_kind(_, "BINARY", _, key), expr_operator(_, op, key), default_op(op),
expr_child(_, e, "ACCESS_TARGET", _, r), expr_value(r, "obj", i).

// ── the variable a site calls through, and every value written into it ─────
// Its initializer and each assignment (`h = d`, `h ??= d`, `h ||= d`).
var_source(v, e) :- var_init(_, _, e, v).
var_source(v, e) :- expr_kind(_, "ASSIGNMENT", _, a), expr_child(_, a, "ASSIGNMENT_TARGET", _, tgt),
expr_binding(_, v, tgt), expr_child(_, a, "ASSIGNMENT_VALUE", _, e).
// A default BY REASSIGNMENT: `let h = t[k]; if (!h) h = d;`, `h ??= d`, or the other
// way round. The variable holds the open read and something else, so the value layer
// sees only the something else and asserts it; the read is swallowed all the same.
swallowed_var(v, i) :- var_source(v, e), open_read(e, i), var_source(v, o), o != e.
// A default written INTO the variable: `const h = t[k] || d`, or `h = t[k] || d`.
swallowed_var(v, i) :- var_source(v, e), expr_child(_, e, _, _, o), swallowed_read(o, i).

// ── partial_dispatch(CallExpr) ─────────────────────────────────────────────
// The site invokes such a read: written at the site, or one hop through the const
// it was assigned to, which is how both real shapes are written.
// The site invokes such a read: written at the site (`t[k]()`, or the default itself
// as the callee, `(t[k] || d)()`, whose parentheses the parser drops), or through the
// variable it was stored in.
partial_dispatch(ce) :- invocation_site(ce, _), expr_child(_, ce, "CALLEE", _, c), swallowed_read(c, _).
partial_dispatch(ce) :- invocation_site(ce, _), expr_child(_, ce, "CALLEE", _, c),
expr_binding(_, v, c), var_init(_, _, ie, v), expr_child(_, ie, _, _, o), swallowed_read(o, _).
expr_child(_, c, _, _, o), swallowed_read(o, _).
partial_dispatch(ce) :- invocation_site(ce, _), expr_child(_, ce, "CALLEE", _, c),
expr_binding(_, v, c), swallowed_var(v, _).
// `resolvers[<default>](...)`: the parser puts the object under RECEIVER and the key
// under COMPUTED_KEY on the call itself, with no ELEMENT_ACCESS node of its own.
partial_dispatch(ce) :- call_site(_, "COMPUTED_CALL", _, _, _, _, ce, _, _),
Expand All @@ -75,8 +90,9 @@ partial_dispatch(ce) :- call_site(_, "COMPUTED_CALL", _, _, _, _, ce, _, _),
partial_dispatch_target(ce, m) :- invocation_site(ce, _), expr_child(_, ce, "CALLEE", _, c),
swallowed_read(c, i), prop_value("obj", i, _, "func", m).
partial_dispatch_target(ce, m) :- invocation_site(ce, _), expr_child(_, ce, "CALLEE", _, c),
expr_binding(_, v, c), var_init(_, _, ie, v), expr_child(_, ie, _, _, o),
swallowed_read(o, i), prop_value("obj", i, _, "func", m).
expr_child(_, c, _, _, o), swallowed_read(o, i), prop_value("obj", i, _, "func", m).
partial_dispatch_target(ce, m) :- invocation_site(ce, _), expr_child(_, ce, "CALLEE", _, c),
expr_binding(_, v, c), swallowed_var(v, i), prop_value("obj", i, _, "func", m).
partial_dispatch_target(ce, m) :- call_site(_, "COMPUTED_CALL", _, _, _, _, ce, _, _),
expr_child(_, ce, "COMPUTED_KEY", _, key),
expr_kind(_, "BINARY", _, key), expr_operator(_, op, key), default_op(op),
Expand Down
2 changes: 2 additions & 0 deletions graph/javascript/souffle/decls_all.dl
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,8 @@
.decl swallowed_read(c0:symbol, c1:symbol)
.decl partial_dispatch(c0:symbol)
.decl partial_dispatch_target(c0:symbol, c1:symbol)
.decl var_source(c0:symbol, c1:symbol)
.decl swallowed_var(c0:symbol, c1:symbol)
// the same read with no default beside it, called: one of the literal's functions
.decl open_read(c0:symbol, c1:symbol)
.decl open_dispatch_target(c0:symbol, c1:symbol)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// A DISPATCH TABLE read with a computed key and defaulted, written without a const
// between the default and the call (#1727). Case 61 pins `const h = t[k] || d; h()`;
// these are the same default written inline, or by assigning the variable again. The
// value layer sees only the default, so each used to be a known_edge to it alone.
import { remote } from './table.js';

function fromA(a) { return a; }
function fromB(a) { return a; }
function deep(a) { return [a]; }

const table = { a: fromA, b: fromB };

// THE CONSTRUCT, inline: the default is the callee, `(t[k] || d)()`.
function inlineOr(k, a) { return (table[k] || deep)(a); }

// THE CONSTRUCT, inline with a conditional.
function inlineTernary(k, a) { return (k in table ? table[k] : deep)(a); }

// THE CONSTRUCT, by reassignment: `if (!h) h = d`.
function reassigned(k, a) {
let h = table[k];
if (!h) h = deep;
return h(a);
}

// THE CONSTRUCT, by a logical assignment.
function nullishAssigned(k, a) {
let h = table[k];
h ??= deep;
return h(a);
}

// THE CONSTRUCT, the other way round: the default first, the read assigned over it.
function readAssigned(k, a) {
let h = deep;
if (k) h = table[k];
return h(a);
}

// THE CONSTRUCT, the table imported from another module.
function importedInline(k, a) { return (remote[k] || deep)(a); }
function importedReassigned(k, a) {
let h = remote[k];
if (!h) h = deep;
return h(a);
}

// CONTROL 1: an inline default whose key is WRITTEN AS A LITERAL: fromA and deep only.
function inlineLiteralKey(a) { return (table['a'] || deep)(a); }

// CONTROL 2: a dotted read reassigned: fromB and deep only.
function reassignedDotted(a) {
let h = table.b;
if (!h) h = deep;
return h(a);
}

// CONTROL 3: a variable reassigned with no table read at all: fromA and deep only.
function reassignedPlain(k, a) {
let h = fromA;
if (k) h = deep;
return h(a);
}

// CONTROL 4: the default alone, inline. The one known target stays a known_edge.
function inlineDefaultOnly(k, a) { return (k || deep)(a); }

const key = process.argv[2];
inlineOr(key, 1);
inlineTernary(key, 1);
reassigned(key, 1);
nullishAssigned(key, 1);
readAssigned(key, 1);
importedInline(key, 1);
importedReassigned(key, 1);
inlineLiteralKey(1);
reassignedDotted(1);
reassignedPlain(key, 1);
inlineDefaultOnly(key, 1);
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
function fromC(a) { return a; }
function fromD(a) { return a; }
export const remote = { c: fromC, d: fromD };
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package_entry @axiomcode/code-graph . [] MAIN dist/reason.js NOT_STAGED -> -
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
main.js:14:34 FUNCTION_CALL (table[k] || deep) -> multi_inferred main.js:7:1 fromA
main.js:14:34 FUNCTION_CALL (table[k] || deep) -> multi_inferred main.js:8:1 fromB
main.js:14:34 FUNCTION_CALL (table[k] || deep) -> multi_inferred main.js:9:1 deep
main.js:17:39 FUNCTION_CALL (k in table ? table[k] : deep) -> multi_inferred main.js:7:1 fromA
main.js:17:39 FUNCTION_CALL (k in table ? table[k] : deep) -> multi_inferred main.js:8:1 fromB
main.js:17:39 FUNCTION_CALL (k in table ? table[k] : deep) -> multi_inferred main.js:9:1 deep
main.js:23:10 FUNCTION_CALL h -> multi_inferred main.js:7:1 fromA
main.js:23:10 FUNCTION_CALL h -> multi_inferred main.js:8:1 fromB
main.js:23:10 FUNCTION_CALL h -> multi_inferred main.js:9:1 deep
main.js:30:10 FUNCTION_CALL h -> multi_inferred main.js:7:1 fromA
main.js:30:10 FUNCTION_CALL h -> multi_inferred main.js:8:1 fromB
main.js:30:10 FUNCTION_CALL h -> multi_inferred main.js:9:1 deep
main.js:37:10 FUNCTION_CALL h -> multi_inferred main.js:7:1 fromA
main.js:37:10 FUNCTION_CALL h -> multi_inferred main.js:8:1 fromB
main.js:37:10 FUNCTION_CALL h -> multi_inferred main.js:9:1 deep
main.js:41:40 FUNCTION_CALL (remote[k] || deep) -> multi_inferred main.js:9:1 deep
main.js:41:40 FUNCTION_CALL (remote[k] || deep) -> multi_inferred table.js:1:1 fromC
main.js:41:40 FUNCTION_CALL (remote[k] || deep) -> multi_inferred table.js:2:1 fromD
main.js:45:10 FUNCTION_CALL h -> multi_inferred main.js:9:1 deep
main.js:45:10 FUNCTION_CALL h -> multi_inferred table.js:1:1 fromC
main.js:45:10 FUNCTION_CALL h -> multi_inferred table.js:2:1 fromD
main.js:49:39 FUNCTION_CALL (table['a'] || deep) -> multi_inferred main.js:7:1 fromA
main.js:49:39 FUNCTION_CALL (table['a'] || deep) -> multi_inferred main.js:9:1 deep
main.js:55:10 FUNCTION_CALL h -> multi_inferred main.js:8:1 fromB
main.js:55:10 FUNCTION_CALL h -> multi_inferred main.js:9:1 deep
main.js:62:10 FUNCTION_CALL h -> multi_inferred main.js:7:1 fromA
main.js:62:10 FUNCTION_CALL h -> multi_inferred main.js:9:1 deep
main.js:66:43 FUNCTION_CALL (k || deep) -> ambient_terminal -
main.js:66:43 FUNCTION_CALL (k || deep) -> multi_inferred main.js:9:1 deep
main.js:69:1 FUNCTION_CALL inlineOr -> known_edge main.js:14:1 inlineOr
main.js:70:1 FUNCTION_CALL inlineTernary -> known_edge main.js:17:1 inlineTernary
main.js:71:1 FUNCTION_CALL reassigned -> known_edge main.js:20:1 reassigned
main.js:72:1 FUNCTION_CALL nullishAssigned -> known_edge main.js:27:1 nullishAssigned
main.js:73:1 FUNCTION_CALL readAssigned -> known_edge main.js:34:1 readAssigned
main.js:74:1 FUNCTION_CALL importedInline -> known_edge main.js:41:1 importedInline
main.js:75:1 FUNCTION_CALL importedReassigned -> known_edge main.js:42:1 importedReassigned
main.js:76:1 FUNCTION_CALL inlineLiteralKey -> known_edge main.js:49:1 inlineLiteralKey
main.js:77:1 FUNCTION_CALL reassignedDotted -> known_edge main.js:52:1 reassignedDotted
main.js:78:1 FUNCTION_CALL reassignedPlain -> known_edge main.js:59:1 reassignedPlain
main.js:79:1 FUNCTION_CALL inlineDefaultOnly -> known_edge main.js:66:1 inlineDefaultOnly
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
main.js:37:10 FUNCTION_CALL h SOUND_SUPERSET main.js:7:1;main.js:8:1;main.js:9:1
main.js:55:10 FUNCTION_CALL h SOUND_SUPERSET main.js:8:1;main.js:9:1
main.js:62:10 FUNCTION_CALL h SOUND_SUPERSET main.js:7:1;main.js:9:1
main.js:69:1 FUNCTION_CALL inlineOr EXACT main.js:14:1
main.js:70:1 FUNCTION_CALL inlineTernary EXACT main.js:17:1
main.js:71:1 FUNCTION_CALL reassigned EXACT main.js:20:1
main.js:72:1 FUNCTION_CALL nullishAssigned EXACT main.js:27:1
main.js:73:1 FUNCTION_CALL readAssigned EXACT main.js:34:1
main.js:74:1 FUNCTION_CALL importedInline EXACT main.js:41:1
main.js:75:1 FUNCTION_CALL importedReassigned EXACT main.js:42:1
main.js:76:1 FUNCTION_CALL inlineLiteralKey EXACT main.js:49:1
main.js:77:1 FUNCTION_CALL reassignedDotted EXACT main.js:52:1
main.js:78:1 FUNCTION_CALL reassignedPlain EXACT main.js:59:1
main.js:79:1 FUNCTION_CALL inlineDefaultOnly EXACT main.js:66:1
# defects: 0
Loading