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
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ callback_registered FUNCTION_CALL app#<module-init>() @L4 -> app#<arrow@4>()
callback_registered FUNCTION_CALL parts#<module-init>() @L33 -> parts#UserCard({ id: string })
callback_registered FUNCTION_CALL parts#<module-init>() @L41 -> parts#FieldImpl({ label: string },unknown)
callback_registered FUNCTION_CALL parts#<module-init>() @L73 -> parts#buildDefault()
callback_registered JSX_COMPONENT_CALL app#App() @L13 -> Panel#render()
intrinsic_terminal DYNAMIC_IMPORT_CALL app#<arrow@4>() @L4 -> -
known_edge FUNCTION_CALL app#useFactory() @L23 -> parts#<arrow@22>()@L22
known_edge FUNCTION_CALL parts#FieldImpl({ label: string },unknown) @L39 -> parts#trackClick(string)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ ambiguous_unknown FUNCTION_CALL parts#render() @L35 -> -
ambiguous_unknown FUNCTION_CALL reg#<module-init>() @L18 -> -
ambiguous_unknown JSX_COMPONENT_CALL app#<arrow@13>() @L20 -> -
callback_registered FUNCTION_CALL parts#<module-init>() @L40 -> parts#<arrow@40>(?)
callback_registered JSX_COMPONENT_CALL app#<arrow@13>() @L? -> app#onPing()
known_edge FUNCTION_CALL app#onPing() @L12 -> app#helper()
known_edge FUNCTION_CALL button#<arrow@10>() @L10 -> button#leafButton()
known_edge FUNCTION_CALL card#render() @L12 -> card#leafCard()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ boundary_lib FUNCTION_CALL parts#<module-init>() @L42 -> vue#defineNuxtComponent
boundary_lib FUNCTION_CALL parts#<module-init>() @L49 -> vue#makeStore(ComponentOptions)
boundary_lib FUNCTION_CALL reg#<module-init>() @L18 -> vue#defineComponent(ComponentOptions)
callback_registered FUNCTION_CALL parts#<module-init>() @L40 -> parts#<arrow@40>(?)
callback_registered JSX_COMPONENT_CALL app#<arrow@13>() @L? -> app#onPing()
known_edge FUNCTION_CALL app#onPing() @L12 -> app#helper()
known_edge FUNCTION_CALL button#<arrow@10>() @L10 -> button#leafButton()
known_edge FUNCTION_CALL card#render() @L12 -> card#leafCard()
Expand Down
8 changes: 8 additions & 0 deletions graph/typescript/engine/call-edge-generation/call_chain.dl
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,14 @@ call_chain_edge(ce, caller, "-", m, "client", "callback_registered", kind) :-
caller != m,
invocation_site(ce, kind).

// …and a function written inside a JSX `{…}` (resolution/value-flow.dl, jsx_handed_value). The `{…}` is
// the site: an intrinsic tag has no call site of its own, and the expression positions the edge.
call_chain_edge(r, caller, "-", m, "client", "callback_registered", "JSX_COMPONENT_CALL") :-
jsx_handed_value(r, m),
method_prov(m, "client"),
jsx_root_caller(r, caller),
caller != m.

// ── call_runs_edge(FromMethod, ToMethod, Prov) ──────────────────────────────
// The chain a consumer should WALK: overload signatures re-pointed to the body that
// actually executes (resolution/overload-sets.dl). Kept separate from
Expand Down
37 changes: 37 additions & 0 deletions graph/typescript/engine/call-edge-generation/calls.dl
Original file line number Diff line number Diff line change
Expand Up @@ -151,5 +151,42 @@ library_value(e, spec) :- expr_kind("client", k, _, e),
library_value(e, spec) :- expr_referenced("client", "VARIABLE", v, e), var_initializer("client", _, i, v),
!var_reassigned(v), library_value(i, spec).
call_passes_project_instance(e) :- expr_child("client", e, "ARGUMENT", _, a), expr_type(a, "client", _).
// …and the runtime's own objects: `JSON.parse(s)`, `Promise.resolve(v).then(f)`, `Object.keys(o).forEach(g)`,
// `document.createElement('a')`, `console.error(e)`. With no standard library staged the name resolves to nothing
// in the run (AMBIENT_GLOBAL), so every such call was an untyped `x.parse()` to the consumers, and a project method
// that happens to be named parse, resolve, all, keys, error or getMetadata was given every one of them as a by-name
// caller — and every test around them. The receiver is the platform's, never the project's: a program that declares
// the name itself binds it as its own variable, not as an ambient global, and stays out. Only the objects whose
// members are called through the name; `window`, `globalThis` and `self` are left out, since a program may hang its
// own functions on them.
library_value(e, n) :- expr_referenced("client", "AMBIENT_GLOBAL", _, e), expr_name("client", n, e),
builtin_global_object(n).
builtin_global_object("JSON").
builtin_global_object("Math").
builtin_global_object("Object").
builtin_global_object("Reflect").
builtin_global_object("Promise").
builtin_global_object("Array").
builtin_global_object("Number").
builtin_global_object("String").
builtin_global_object("Boolean").
builtin_global_object("BigInt").
builtin_global_object("Symbol").
builtin_global_object("Date").
builtin_global_object("RegExp").
builtin_global_object("Error").
builtin_global_object("Intl").
builtin_global_object("Atomics").
builtin_global_object("ArrayBuffer").
builtin_global_object("console").
builtin_global_object("process").
builtin_global_object("Buffer").
builtin_global_object("document").
builtin_global_object("navigator").
builtin_global_object("localStorage").
builtin_global_object("sessionStorage").
builtin_global_object("performance").
builtin_global_object("crypto").
builtin_global_object("URL").
library_receiver(ce, spec) :- call_site("client", _, _, rk, recv, ce, _), receiver_kind_is_value(rk),
!call_resolved(ce), library_value(recv, spec).
21 changes: 21 additions & 0 deletions graph/typescript/engine/resolution/contextual-params.dl
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,20 @@ builtin_elem_ref(parent, x) :- ref_is_readonly_operator(parent),
builtin_elem_ref(child, x).
builtin_elem_ref(ref, x) :- ref_via_alias(ref, rhs),
builtin_elem_ref(rhs, x).
// A tuple's elements are its members: `[Module, ...Module[]]`.
builtin_elem_ref(ref, child) :- type_ref(rp, "TUPLE", _, _, _, _, _, ref),
type_ref_parent(rp, ref, _, child).
// A TYPE VARIABLE constrained to a list holds that list's elements: `<Ms extends Module[]>(ms: Ms)`,
// `<Ms extends [Module, ...Module[]]>(...modules: Ms)`, then `modules.map((m) => m.init())`. The
// constraint is what every element is at least, as for a `<T extends HasId>` receiver. Both routes
// ref_type_target takes: the exact link to the declaration, and the name in scope where it is absent.
builtin_elem_ref(ref, x) :- type_ref_type_param(rp, tp, ref),
type_param_constraint(rp, cref, tp),
builtin_elem_ref(cref, x).
builtin_elem_ref(ref, x) :- type_ref(_, "TYPE_VARIABLE", _, tn, _, owner, _, ref),
!type_ref_type_param(_, _, ref),
type_var_constraint_in_scope(owner, tn, cref),
builtin_elem_ref(cref, x).
builtin_elem_ref(ref, a) :- ref_via_alias(ref, rhs),
builtin_elem_ref(rhs, x),
type_ref(_, "TYPE_VARIABLE", _, n, _, _, _, x),
Expand Down Expand Up @@ -339,6 +353,13 @@ lambda_param_ref(lp, c) :- call_site("client", _, "then", _, recv, ce, _),
!param_type_ref(_, _, lp).
lambda_param_type(lp, prov, t) :- lambda_param_ref(lp, c),
ref_type_target(c, prov, t).
// …and its SHAPE, where the element is an object type written as a type alias or a literal rather
// than a class or an interface: `type Plugin = { init(): void }`, then `plugins.map((p) => p.init())`.
// The element has no type declaration to dispatch on, only members, so the type clause above gave
// the parameter nothing and every call on it was unresolved; an annotated parameter of the same type
// resolved, through param_shape_target. The element reference is the same one, read as a shape.
lambda_param_shape(lp, sh) :- lambda_param_ref(lp, c),
ref_shape_target(c, sh).
// The parameter's reference is its declared reference, as an annotation's would be.
expr_decl_ref(e, c) :- expr_referenced("client", "PARAMETER", p, e),
lambda_param_ref(p, c).
Expand Down
13 changes: 13 additions & 0 deletions graph/typescript/engine/resolution/type-resolution.dl
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,19 @@ ref_element_target(ref, prov, t) :- type_ref(rp, k, _, _, _, _, _, ref),
ref_kind_is_array(k),
type_ref_parent(rp, ref, _, child),
ref_type_target(child, prov, t).
// …and of a TYPE VARIABLE constrained to an array or a tuple: `<Ms extends Plugin[]>(ms: Ms)`,
// `<Ms extends [Module, ...Module[]]>(...modules: Ms)`, then `modules.map((m) => m.init())`. The
// constraint is what every element is at least, exactly as it is for a `<T extends HasId>` receiver
// (ref_type_target above), so the callback's parameter is typed by the constraint's element. Without it
// the element of a generic list had no type, and every call on it was unresolved. Same two routes as
// ref_type_target: the exact link to the declaration, and the name in scope where the link is absent.
ref_element_target(ref, prov, t) :- type_ref_type_param(rp, tp, ref),
type_param_constraint(rp, cref, tp),
ref_element_target(cref, prov, t).
ref_element_target(ref, prov, t) :- type_ref(_, "TYPE_VARIABLE", _, tn, _, owner, _, ref),
!type_ref_type_param(_, _, ref),
type_var_constraint_in_scope(owner, tn, cref),
ref_element_target(cref, prov, t).

// ── ref_element_shape(Ref, Shape) — an element that is ITSELF a function type ──
// `fns: readonly ((data: T) => boolean)[]` then `fns.every((fn) => fn(1))`. The element
Expand Down
82 changes: 82 additions & 0 deletions graph/typescript/engine/resolution/value-flow.dl
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,69 @@ proxy_trap_name("preventExtensions").
proxy_trap_name("apply").
proxy_trap_name("construct").

// …AND A FUNCTION WRITTEN AS A JSX ATTRIBUTE OR CHILD. `<canvas onPointerMove={this.handleMove}/>`,
// `<button onClick={onSave}/>`, `<List renderItem={renderRow}/>`: the element is handed the function and
// the renderer runs it, on an event or while it renders. The parser writes each `{…}` as an expression
// ROOT of its own, linked to no element (an intrinsic tag has no call site at all), so no rule above saw
// it: a handler read off a field or named by reference was reached by nothing, and its whole body was
// cut off from the tests that render the component and fire the event. The `{…}` itself is the site that
// hands the function over, from the function it is written in, the way `xs.map(cb)` hands over `cb`;
// what the value is read through is followed exactly as for an argument (value_branch, holders, a field
// holding an arrow, a method read as a value, `bind`). A call there (`onClick={make()}`) hands over what
// the call returns, not its callee, and stays out.
jsx_value_root(r) :- expr_root_context("client", "JSX_EMBEDDED_EXPRESSION", r), expr_kind("client", _, "ROOT", r).
value_branch(r, r) :- jsx_value_root(r).
jsx_handed_value(r, m) :- jsx_value_root(r), value_branch(r, x),
!expr_kind("client", "CALL_EXPRESSION", _, x),
expr_callable(x, m).
jsx_handed_value(r, m) :- jsx_value_root(r), value_branch(r, x),
expr_referenced("client", k, h, x), holder_ref_kind(k),
holder_holds_function(h, m).
jsx_handed_value(r, m) :- jsx_value_root(r), value_branch(r, x),
ts_field_access_target(x, f),
holder_holds_function(f, m).
jsx_handed_value(r, m) :- jsx_value_root(r), value_branch(r, x),
method_value(x, m).
// the function the `{…}` is written in: its block's method, or the module initializer at top level
jsx_root_caller(r, c) :- jsx_handed_value(r, _), expr_owner("client", "BLOCK", b, _, r), block_owner("client", c, _, b), c != "".
jsx_root_caller(r, c) :- jsx_handed_value(r, _), expr_owner("client", "METHOD", c, _, r), c != "".
jsx_root_caller(r, c) :- jsx_handed_value(r, _), expr_owner("client", "MODULE_INIT", _, _, r),
expr_module("client", mod, r), module_init("client", c, mod).

// …AND A CLASS COMPONENT'S RENDER AND LIFECYCLE. `<App/>` where App is a class builds an instance
// (callee-resolution rule 13 resolves the tag to the constructor), and from then on the renderer
// calls the instance's render and its lifecycle methods; nothing in the program does. Without these
// the whole component below its constructor was unreachable from every test that mounts it. Only a
// class that has a render method — its own or inherited — is a component.
jsx_class_component_site(ce, t) :- call_site("client", "JSX_COMPONENT_CALL", cn, _, _, ce, cs),
cn != "",
call_module(cs, mod),
!callee_names_local(ce),
name_binds(mod, cn, _, "TYPE", t),
method_in_scope(t, "render", "false", _).
jsx_lifecycle_name("render").
jsx_lifecycle_name("componentDidMount").
jsx_lifecycle_name("componentDidUpdate").
jsx_lifecycle_name("componentWillUnmount").
jsx_lifecycle_name("shouldComponentUpdate").
jsx_lifecycle_name("getSnapshotBeforeUpdate").
jsx_lifecycle_name("componentDidCatch").
jsx_lifecycle_name("componentWillMount").
jsx_lifecycle_name("componentWillReceiveProps").
jsx_lifecycle_name("componentWillUpdate").
jsx_lifecycle_name("UNSAFE_componentWillMount").
jsx_lifecycle_name("UNSAFE_componentWillReceiveProps").
jsx_lifecycle_name("UNSAFE_componentWillUpdate").
handed_function(ce, m) :- jsx_class_component_site(ce, t),
jsx_lifecycle_name(n),
method_in_scope(t, n, "false", m).
// the static ones the renderer calls on the class itself
jsx_static_lifecycle_name("getDerivedStateFromProps").
jsx_static_lifecycle_name("getDerivedStateFromError").
handed_function(ce, m) :- jsx_class_component_site(ce, t),
jsx_static_lifecycle_name(n),
method_in_scope(t, n, "true", m).

// …AND WHAT A LIBRARY CALL WRAPPED, KEPT IN A HOLDER. `export const plugin = fp(async (app) => …)`, then
// `app.register(plugin)`: the holder keeps what a call with no client body RETURNED, so no holder rule above
// sees a function in it, and the registration reached nothing although the same literal handed to it bare
Expand Down Expand Up @@ -555,6 +618,20 @@ call_runs(ce, m) :- callee_branch(ce, x),
call_runs(ce, m) :- callee_branch(ce, x),
ts_field_access_target(x, f),
holder_holds_function(f, m).
// A CALL THROUGH AN IMPORTED HOLDER. A package's public functions are often its default instance's methods,
// exported as consts: `export const createDraft = immer.createDraft.bind(immer)`, `export const plain =
// engine.plain`. The holder rules know what the const holds (method_value follows the read and `bind`), but a
// call names the const through an import, and the two clauses above take only a local variable, a parameter
// or a field: every caller outside the defining module — its whole test suite — ran nothing. expr_holder
// follows the import, and a namespace member, to the exporting module's own variable, never by name.
call_runs(ce, m) :- call_callee_expr(_, ce, x),
expr_referenced("client", "IMPORT_BINDING", _, x),
expr_holder(x, h),
holder_holds_function(h, m).
call_runs(ce, m) :- call_callee_expr(_, ce, x),
expr_namespace_member(x),
expr_holder(x, h),
holder_holds_function(h, m).

// ============================================================================
// A FUNCTION ASSIGNED TO AN INTERFACE'S METHOD MEMBER IMPLEMENTS IT (#1283)
Expand Down Expand Up @@ -656,6 +733,11 @@ expr_holder(x, f) :- ts_field_access_target(x, f).
// an imported `const` is the exporting module's variable
expr_holder(x, v) :- expr_referenced("client", "IMPORT_BINDING", ih, x),
import_binds(ih, _, "VARIABLE", v).
// …and so is one read off a namespace import, `api.createDraft`
expr_holder(x, v) :- property_access_recv(x, n, q),
expr_namespace(q, _, mod),
module_export(_, mod, n, "VARIABLE", v).
expr_namespace_member(x) :- property_access_recv(x, _, q), expr_namespace(q, _, _).

// ── holder_elem_fn(Holder, Fn): an element of the holder's collection is, or holds, Fn ──
holder_elem_fn(h, m) :- holder_elem_value(h, x),
Expand Down
8 changes: 8 additions & 0 deletions graph/typescript/souffle/decls_all.dl
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,13 @@
.decl collection_receiver(c0:symbol)
.decl keyed_collection_ref(c0:symbol)
.decl handed_function(c0:symbol,c1:symbol)
.decl expr_namespace_member(c0:symbol)
.decl jsx_value_root(c0:symbol)
.decl jsx_handed_value(c0:symbol,c1:symbol)
.decl jsx_root_caller(c0:symbol,c1:symbol)
.decl jsx_class_component_site(c0:symbol,c1:symbol)
.decl jsx_lifecycle_name(c0:symbol)
.decl jsx_static_lifecycle_name(c0:symbol)
.decl proxy_site(c0:symbol)
.decl proxy_handler_value(c0:symbol,c1:symbol)
.decl proxy_handler_class(c0:symbol,c1:symbol)
Expand Down Expand Up @@ -612,6 +619,7 @@
.decl unresolved_covered(c0:symbol)
.decl package_import(c0:symbol,c1:symbol)
.decl library_value(c0:symbol,c1:symbol)
.decl builtin_global_object(c0:symbol)
.decl call_passes_project_instance(c0:symbol)
.decl library_receiver(c0:symbol,c1:symbol)
.decl unresolved_receiver(c0:symbol,c1:symbol,c2:symbol,c3:symbol)
Expand Down
2 changes: 1 addition & 1 deletion plugins/axiomcode/skills/axiomcode/scripts/ax_edges.py
Original file line number Diff line number Diff line change
Expand Up @@ -503,7 +503,7 @@ def nearest(cands): # the candidates enclosing no other candidat
# unknown, but a project `get` is not what it calls. Like a stub it keeps a row, apart from the untyped-receiver name
# matches, and it seeds no closure: listed among them it filled the first page of `impact Repo.get`.
LIBRARY_RECEIVER_KIND = 'library'
LIBRARY_BYNAME_WHY = 'calls a method of this name on a value a package returned or constructed: not this method, unless the package hands it back'
LIBRARY_BYNAME_WHY = 'calls a method of this name on a value a package or the platform (JSON, Promise, document …) returned or constructed: not this method, unless that code hands it back'


def library_receiver_sites(q):
Expand Down
2 changes: 1 addition & 1 deletion plugins/axiomcode/skills/axiomcode/scripts/dl/impact.dl
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,7 @@ direct(q, c, "uses", "calls a method of this name (receiver not typed)", "by nam
direct(q, c, "uses", "stubs a method of this name on a mock (receiver not typed): the real method does not run there", "by name", f, l) :- target(q, "method", m, _), named(n, m), unresolved(c, n, "stub", f, l), !is_target_decl(q, c).
// A site whose receiver the engine traced to a package's value (`request(app).get('/x')`, ax_edges.library_receiver_sites):
// the kind "library". A row of its own, listed after the untyped-receiver matches, and no seed of the closure.
direct(q, c, "uses", "calls a method of this name on a value a package returned or constructed: not this method, unless the package hands it back", "by name", f, l) :- target(q, "method", m, _), named(n, m), unresolved(c, n, "library", f, l), !is_target_decl(q, c).
direct(q, c, "uses", "calls a method of this name on a value a package or the platform (JSON, Promise, document …) returned or constructed: not this method, unless that code hands it back", "by name", f, l) :- target(q, "method", m, _), named(n, m), unresolved(c, n, "library", f, l), !is_target_decl(q, c).

// ── a method handed over as a VALUE, never called where it is named ──────────────────────────────────────────
// `app.get('/orders/:id', getOrder)`, `useEffect(load)`, `items.map(format)`, `handlers = {"x": handle_x}`: the
Expand Down
Loading
Loading