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
23 changes: 23 additions & 0 deletions graph/javascript/engine/call-edge-generation/calls.dl
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,29 @@ call_chain_edge(ce, caller, "-", m, "client", "multi_inferred", kind) :-
wrapper_call_target(ce, m), !call_over_cap(ce), call_from(ce, caller), invocation_site(ce, kind).
variable_reassigned(v) :- expr_kind(_, "ASSIGNMENT", _, a), expr_child(_, a, "ASSIGNMENT_TARGET", _, tgt),
expr_binding(_, v, tgt).
// ── a call of what a PROJECT wrapper returned runs what that site was handed ──
// `export default promisable(eachItem, 3)`, `const add = curry2(function add(a, b) {…})`:
// the wrapper returns a closure that calls its parameter, and every export of the module
// is that closure. The closure's call of the parameter is read context-insensitively, so
// it held every function any of the wrapper's sites passed (past the fan cap: nothing),
// and a test calling the export reached the closure and stopped there. The site is
// already a value of its own, ("wrap", site), beside the closure (value-flow.dl), and a
// call through it takes its RESULT from that site's argument alone; the same site says
// what the call RUNS: the function it was handed at the forwarded position, what the
// wrapper writes into that parameter, or what a wrapped argument itself runs
// (`memoize(once(f))`). One of a set, beside the edge to the closure the call keeps, and
// under the same fan cap: a callee that may hold more wrapped values than --dispatch-cap
// (a parameter every wrapped export is passed to) runs one of them, and naming all of
// them says nothing.
wrap_runs(s, g) :- wrap_site_arg(s, _, arg), expr_value(arg, "func", g).
wrap_runs(s, g) :- wrap_site_arg(s, _, arg), expr_value(arg, "wrap", s2), wrap_runs(s2, g).
wrap_runs(s, g) :- wrap_site(s, h, pos), wrapper_param_written(h, pos, o), expr_value(o, "func", g).
wrapped_call_target(ce, g) :- callee_value(ce, "wrap", s), wrap_runs(s, g), method_prov(g, "client"),
!call_site(_, "FUNCTION_CALL_BIND", _, _, _, _, ce, _, _).
wrapped_target_count(ce, n) :- wrapped_call_target(ce, _), n = count : { wrapped_call_target(ce, _) }.
wrapped_over_cap(ce) :- wrapped_target_count(ce, n), dispatch_cap(c), n > to_number(c).
call_chain_edge(ce, caller, "-", g, "client", "multi_inferred", kind) :-
wrapped_call_target(ce, g), !call_over_cap(ce), !wrapped_over_cap(ce), call_from(ce, caller), invocation_site(ce, kind).
// `(c ? a : b)()`, `(0, cb)()`, `make()()`: the callee is computed by an expression.
// `new Function(s)()` / `Function(s)()` runs code built at run time.
unresolved_value_callee(ce, "dynamic_code") :- value_callee_unresolved(ce, c), callee_builds_code(c).
Expand Down
36 changes: 35 additions & 1 deletion graph/javascript/engine/resolution/fan-cap.dl
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,48 @@ direct_export_func(mod, n, m) :- export_decl(_, n, _, _, _, _, _, se, mod, _), s
// shares its name with hundreds of unrelated sites and must not be capped by them,
// while a module-level `each` or `merge` called under its own name from everywhere
// is exactly the generic utility the cap exists for.
//
// The PROGRAM's arguments are decided by the program's sites alone. A suite calls the
// API it tests over and over, and counted with the program those calls made exactly the
// functions under test "generic utilities": their parameters went untracked, so a
// callback the program hands a constructor and stores on the instance (`this.decode =
// options.decode`) was lost, and the program's own call through that field resolved only
// to the fallback beside it. So there are two verdicts:
// method_is_hot every site counts (as before): decides whether a TEST's
// arguments flow in
// method_is_hot_in_program test files' sites do not count: decides whether the
// PROGRAM's arguments flow in
// Nothing that flowed before stops flowing (a test handing the program a plugin it calls
// back still reaches it). A function hot only because of its tests now receives what the
// program passes it and still nothing from the tests: their callbacks would be the widest
// values it holds, and with them the call through the stored field would be over the
// dispatch cap and answer nothing.
param_flow_capped_at(n) :- dispatch_cap(s), n = to_number(s).
direct_call_count(m, n) :- direct_call_site(_, m), n = count : { direct_call_site(_, m) }.
method_is_hot(m, n) :- direct_call_count(m, n), param_flow_capped_at(c), n > c.
name_call_count(cn, n) :- call_site(_, _, cn, _, _, _, _, _, _), cn != "", n = count : { call_site(_, _, cn, _, _, _, _, _, _) }.
program_direct_site(ce, m) :- direct_call_site(ce, m), !site_in_test_module(ce).
program_direct_count(m, n) :- program_direct_site(_, m), n = count : { program_direct_site(_, m) }.
method_is_hot_in_program(m, n) :- program_direct_count(m, n), param_flow_capped_at(c), n > c.
// The NAME count is the overcount for module-level functions. A test's member call that
// only shares the name (`lib.add(1, 2)` on an export that is a different function, a
// curried closure) stays in it, as before; a test's bare call or `new` of the name
// (`build(...)`, `new lib.Codec(...)`) denotes the function itself and leaves it.
test_names_itself(ce) :- site_in_test_module(ce), call_site(_, ck, _, _, _, _, ce, _, _), call_kind_is_callee_form(ck).
test_names_itself(ce) :- site_in_test_module(ce), call_site(_, "CONSTRUCTOR_CALL", _, _, _, _, ce, _, _).
program_name_site(m, ce) :- module_level_function(m, cn), call_site(_, _, cn, _, _, _, ce, _, _), !test_names_itself(ce).
program_name_count(m, n) :- program_name_site(m, _), n = count : { program_name_site(m, _) }.
// ── test_module / site_in_test_module ──────────────
// By path, as runners find their files: a `test`/`tests`/`__tests__`/`spec(s)` directory,
// or a `.test.` / `.spec.` file.
test_module(mod) :- module_file("client", fp, mod),
match("(.*/)?(tests?|__tests__|specs?)/.*|.*[.](test|spec)[.][cm]?[jt]sx?", fp).
site_in_test_module(ce) :- expr_owner(_, _, mod, ce), test_module(mod).
module_level_function(m, n) :- method_decl(_, n, "FUNCTION_DECLARATION", _, _, "", mod, m), method_scopes(_, _, _, init, m), module_init(_, init, mod).
module_level_function(m, n) :- var_decl(_, n, _, _, _, mod, v), function_binding_value(v, m), method_decl(_, _, _, _, _, "", mod, m),
method_scopes(_, _, _, init, m), module_init(_, init, mod).
method_is_hot(m, n) :- module_level_function(m, cn), name_call_count(cn, n), param_flow_capped_at(c), n > c.
method_is_hot_in_program(m, n) :- program_name_count(m, n), param_flow_capped_at(c), n > c.

// ── param_fan_capped(ParamHash, SiteCount) — countable ──────────────────────
param_fan_capped(p, n) :- method_is_hot(m, n), param_decl(_, _, _, m, p).
param_fan_capped(p, n) :- method_is_hot_in_program(m, n), param_decl(_, _, _, m, p).
19 changes: 15 additions & 4 deletions graph/javascript/engine/resolution/value-flow.dl
Original file line number Diff line number Diff line change
Expand Up @@ -264,8 +264,15 @@ param_value(p, "inst", t) :- param_type_name(_, _, tr, p), type_ref_type(tr, t).
// values on every export, a 20-minute solve — because a generic library is exactly
// the shape whose parameters unify everything. What a library does with a callback it
// is handed is a MODEL (resolution/frameworks.dl), stated per library, not inferred.
param_value(p, k, i) :- call_arg(ce, pos, arg), expr_resolves_to_method(ce, m), !method_is_hot(m, _),
method_decl("client", _, _, _, _, _, _, m),
// The program's sites pass their arguments unless the PROGRAM calls the function from
// more places than the cap; a test file's sites only while every site together stays
// under it (fan-cap.dl).
param_value(p, k, i) :- call_arg(ce, pos, arg), expr_resolves_to_method(ce, m), !site_in_test_module(ce),
!method_is_hot_in_program(m, _), method_decl("client", _, _, _, _, _, _, m),
param_decl(_, _, ppos, m, p), to_number(ppos) = pos, !param_is_rest(_, p),
expr_value(arg, k, i).
param_value(p, k, i) :- call_arg(ce, pos, arg), expr_resolves_to_method(ce, m), site_in_test_module(ce),
!method_is_hot(m, _), method_decl("client", _, _, _, _, _, _, m),
param_decl(_, _, ppos, m, p), to_number(ppos) = pos, !param_is_rest(_, p),
expr_value(arg, k, i).
// THE ONE LIBRARY EXCEPTION: a UMD wrapper (#710). `(function (root, factory) {
Expand All @@ -283,8 +290,12 @@ param_value(p, "func", f) :- call_site(_, "IIFE_CALL", _, _, _, _, ce, _, _), ca
expr_value(arg, "func", f).
// A rest parameter IS an array; the arguments at and past its position are its elements.
param_value(p, "arr", p) :- param_is_rest(_, p).
elem_value(p, k, i) :- call_arg(ce, pos, arg), expr_resolves_to_method(ce, m), !method_is_hot(m, _),
method_decl("client", _, _, _, _, _, _, m),
elem_value(p, k, i) :- call_arg(ce, pos, arg), expr_resolves_to_method(ce, m), !site_in_test_module(ce),
!method_is_hot_in_program(m, _), method_decl("client", _, _, _, _, _, _, m),
param_decl(_, _, ppos, m, p), param_is_rest(_, p), to_number(ppos) <= pos,
expr_value(arg, k, i).
elem_value(p, k, i) :- call_arg(ce, pos, arg), expr_resolves_to_method(ce, m), site_in_test_module(ce),
!method_is_hot(m, _), method_decl("client", _, _, _, _, _, _, m),
param_decl(_, _, ppos, m, p), param_is_rest(_, p), to_number(ppos) <= pos,
expr_value(arg, k, i).
// A default value: `cb = () => {}`. The parser gives the parameter the default's
Expand Down
12 changes: 12 additions & 0 deletions graph/javascript/souffle/decls_all.dl
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@
.decl wrapper_holder_call(c0:symbol, c1:symbol)
.decl wrapper_runs(c0:symbol, c1:symbol)
.decl wrapper_call_target(c0:symbol, c1:symbol)
.decl wrap_runs(c0:symbol, c1:symbol)
.decl wrapped_call_target(c0:symbol, c1:symbol)
.decl wrapped_target_count(c0:symbol, c1:number)
.decl wrapped_over_cap(c0:symbol)
.decl live_export_variable(c0:symbol, c1:symbol)
.decl this_type_open(c0:symbol)

Expand Down Expand Up @@ -462,6 +466,14 @@
.decl param_fan_capped(c0:symbol, c1:number)
.decl import_binding_is_default_like(c0:symbol)
.decl name_call_count(c0:symbol, c1:number)
.decl test_module(c0:symbol)
.decl site_in_test_module(c0:symbol)
.decl program_direct_site(c0:symbol, c1:symbol)
.decl program_direct_count(c0:symbol, c1:number)
.decl method_is_hot_in_program(c0:symbol, c1:number)
.decl test_names_itself(c0:symbol)
.decl program_name_site(c0:symbol, c1:symbol)
.decl program_name_count(c0:symbol, c1:number)
.decl module_level_function(c0:symbol, c1:symbol)
.decl ambient_collection(c0:symbol)
.decl coll_method_call(c0:symbol, c1:symbol, c2:symbol)
Expand Down
19 changes: 19 additions & 0 deletions graph/test/javascript/expected/64-memoize-wrapper-result.edges
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
app.js:11:3 FUNCTION_CALL getAlpha -> known_edge memoize.js:5:10 <arrow>
app.js:11:3 FUNCTION_CALL getAlpha -> multi_inferred app.js:7:26 <arrow>
app.js:11:3 METHOD_CALL getAlpha().getHooks -> known_edge alpha.js:2:3 getHooks
app.js:12:3 FUNCTION_CALL getBeta -> known_edge memoize.js:5:10 <arrow>
app.js:12:3 FUNCTION_CALL getBeta -> multi_inferred app.js:8:25 <arrow>
app.js:12:3 METHOD_CALL getBeta().getHooks -> known_edge beta.js:2:3 getHooks
app.js:16:20 FUNCTION_CALL once -> callback_registered app.js:16:25 <arrow>
app.js:16:20 FUNCTION_CALL once -> known_edge once.js:2:1 once
Expand All @@ -15,12 +17,16 @@ app.js:19:19 FUNCTION_CALL lazy -> callback_registered app.js:19:24 <arrow>
app.js:19:19 FUNCTION_CALL lazy -> known_edge once.js:14:1 lazy
app.js:19:30 CONSTRUCTOR_CALL AlphaPlugin -> implicit_constructor -
app.js:22:3 FUNCTION_CALL firstAlpha -> known_edge once.js:5:10 <function-expression>
app.js:22:3 FUNCTION_CALL firstAlpha -> multi_inferred app.js:16:25 <arrow>
app.js:22:3 METHOD_CALL firstAlpha().run -> known_edge alpha.js:3:3 run
app.js:23:3 FUNCTION_CALL makeBeta -> known_edge once.js:15:10 <arrow>
app.js:23:3 FUNCTION_CALL makeBeta -> multi_inferred app.js:17:23 <arrow>
app.js:23:3 METHOD_CALL makeBeta().run -> known_edge beta.js:3:3 run
app.js:24:3 FUNCTION_CALL firstBeta -> known_edge once.js:5:10 <function-expression>
app.js:24:3 FUNCTION_CALL firstBeta -> multi_inferred app.js:18:24 <arrow>
app.js:24:3 METHOD_CALL firstBeta().run -> known_edge beta.js:3:3 run
app.js:25:3 FUNCTION_CALL makeAlpha -> known_edge once.js:15:10 <arrow>
app.js:25:3 FUNCTION_CALL makeAlpha -> multi_inferred app.js:19:24 <arrow>
app.js:25:3 METHOD_CALL makeAlpha().run -> known_edge alpha.js:3:3 run
app.js:29:18 FUNCTION_CALL constant -> known_edge once.js:18:1 constant
app.js:29:27 CONSTRUCTOR_CALL BetaPlugin -> implicit_constructor -
Expand Down Expand Up @@ -57,10 +63,14 @@ declared.js:27:16 FUNCTION_CALL pickShared -> callback_registered declared.js
declared.js:27:16 FUNCTION_CALL pickShared -> known_edge declared.js:20:1 pickShared
declared.js:27:33 CONSTRUCTOR_CALL BetaPlugin -> implicit_constructor -
declared.js:30:3 FUNCTION_CALL declAlpha -> known_edge declared.js:9:3 inner
declared.js:30:3 FUNCTION_CALL declAlpha -> multi_inferred declared.js:22:28 <arrow>
declared.js:30:3 METHOD_CALL declAlpha().run -> known_edge alpha.js:3:3 run
declared.js:31:3 FUNCTION_CALL declBeta -> known_edge declared.js:9:3 inner
declared.js:31:3 FUNCTION_CALL declBeta -> multi_inferred declared.js:23:27 <arrow>
declared.js:31:3 METHOD_CALL declBeta().run -> known_edge beta.js:3:3 run
declared.js:32:3 FUNCTION_CALL declOfGetter -> known_edge declared.js:9:3 inner
declared.js:32:3 FUNCTION_CALL declOfGetter -> multi_inferred declared.js:24:26 <arrow>
declared.js:32:3 FUNCTION_CALL declOfGetter -> multi_inferred memoize.js:5:10 <arrow>
declared.js:32:3 METHOD_CALL declOfGetter().run -> known_edge alpha.js:3:3 run
declared.js:33:3 FUNCTION_CALL tappedDecl -> known_edge declared.js:15:3 inner
declared.js:33:3 METHOD_CALL tappedDecl().markOnly -> known_edge declared.js:13:14 markOnly
Expand Down Expand Up @@ -202,24 +212,33 @@ shapes.js:45:27 FUNCTION_CALL memoize -> callback_registered shapes.js:45:35
shapes.js:45:27 FUNCTION_CALL memoize -> known_edge memoize.js:2:17 <arrow>
shapes.js:45:41 CONSTRUCTOR_CALL BetaPlugin -> implicit_constructor -
shapes.js:48:3 FUNCTION_CALL getFb -> known_edge shapes.js:15:10 <arrow>
shapes.js:48:3 FUNCTION_CALL getFb -> multi_inferred shapes.js:36:28 <arrow>
shapes.js:48:3 METHOD_CALL getFb().run -> known_edge alpha.js:3:3 run
shapes.js:49:3 FUNCTION_CALL getFb -> known_edge shapes.js:15:10 <arrow>
shapes.js:49:3 FUNCTION_CALL getFb -> multi_inferred shapes.js:36:28 <arrow>
shapes.js:49:3 METHOD_CALL getFb().fallbackOnly -> known_edge shapes.js:8:18 fallbackOnly
shapes.js:50:3 FUNCTION_CALL getOr -> known_edge shapes.js:22:10 <arrow>
shapes.js:50:3 FUNCTION_CALL getOr -> multi_inferred shapes.js:38:22 <arrow>
shapes.js:50:3 METHOD_CALL getOr().run -> ambient_terminal -
shapes.js:50:3 METHOD_CALL getOr().run -> multi_inferred beta.js:3:3 run
shapes.js:51:3 FUNCTION_CALL getOr -> known_edge shapes.js:22:10 <arrow>
shapes.js:51:3 FUNCTION_CALL getOr -> multi_inferred shapes.js:38:22 <arrow>
shapes.js:51:3 METHOD_CALL getOr().mockOnly -> ambient_terminal -
shapes.js:51:3 METHOD_CALL getOr().mockOnly -> multi_inferred shapes.js:9:14 mockOnly
shapes.js:52:3 FUNCTION_CALL getD -> known_edge shapes.js:29:10 <arrow>
shapes.js:52:3 FUNCTION_CALL getD -> multi_inferred shapes.js:39:26 <arrow>
shapes.js:52:3 METHOD_CALL getD().run -> known_edge alpha.js:3:3 run
shapes.js:53:3 FUNCTION_CALL getD -> known_edge shapes.js:29:10 <arrow>
shapes.js:53:3 FUNCTION_CALL getD -> multi_inferred shapes.js:39:26 <arrow>
shapes.js:53:3 METHOD_CALL getD().defaultsOnly -> known_edge shapes.js:25:20 defaultsOnly
shapes.js:54:3 FUNCTION_CALL getDefault -> known_edge shapes.js:33:44 <arrow>
shapes.js:54:3 METHOD_CALL getDefault().defaultOnly -> known_edge shapes.js:10:19 defaultOnly
shapes.js:55:3 FUNCTION_CALL getSpread -> known_edge shapes.js:33:44 <arrow>
shapes.js:55:3 METHOD_CALL getSpread().run -> ambiguous_unknown -
shapes.js:56:3 FUNCTION_CALL warm -> known_edge shapes.js:34:32 <arrow>
shapes.js:56:3 FUNCTION_CALL warm -> multi_inferred shapes.js:43:20 <arrow>
shapes.js:56:3 METHOD_CALL warm().run -> known_edge alpha.js:3:3 run
shapes.js:57:3 FUNCTION_CALL getNested -> known_edge memoize.js:5:10 <arrow>
shapes.js:57:3 FUNCTION_CALL getNested -> multi_inferred memoize.js:5:10 <arrow>
shapes.js:57:3 FUNCTION_CALL getNested -> multi_inferred shapes.js:45:35 <arrow>
shapes.js:57:3 METHOD_CALL getNested().run -> known_edge beta.js:3:3 run
Loading
Loading