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
@@ -0,0 +1,19 @@
// The slice of the standard library this case hands methods to. Declared here, as client code, so the
// client-only pass sees `forEach`, `map` and `bind` as what they are to the project: signatures with no body.
interface Array<T> {
map<U>(fn: (v: T) => U, thisArg?: unknown): U[];
forEach(fn: (v: T) => void, thisArg?: unknown): void;
}
interface Object {}
interface Boolean {}
interface Number {}
interface String {}
interface Function {
bind(thisArg: unknown, ...args: unknown[]): any;
call(thisArg: unknown, ...args: unknown[]): any;
}
interface CallableFunction extends Function {}
interface NewableFunction extends Function {}
interface IArguments {}
interface RegExp {}
declare function setTimeout(cb: () => void, ms?: number): number;
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"noEmit": true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// A METHOD PASSED BY REFERENCE. `this.m`, `this.m.bind(this)`, `xs.forEach(this.m, this)` and a listener
// registered on a typed receiver hand the method over without calling it, exactly as `xs.map(double)` hands a
// free function over, so the method that writes the hand-off reaches the method it hands.
export interface Target {
addEventListener(type: string, fn: () => void): void;
}

export class Widget {
private scale = 2;
handle(n: number): number { return n * this.scale; }
onClick(): number { return 1; }
onHover(): number { return 2; }
onTap(): number { return 4; }
later(): () => void { return () => {}; }
label = 'w';

start(xs: number[]): number[] {
xs.forEach(this.handle.bind(this));
return xs.map(this.handle, this);
}

wire(el: Target): void {
el.addEventListener('click', this.onClick);
setTimeout(this.onHover.bind(this));
}

// a method of ANOTHER instance, handed over through a typed receiver
relay(other: Widget, xs: number[]): void {
xs.forEach(other.handle, other);
}

// CONTROL: an arrow that CALLS the method is the arrow's call, as before — the passer registers the arrow only
tap(el: Target): void {
el.addEventListener('tap', () => this.onTap());
}

// CONTROL: `call` RUNS the method here and hands over what it returns (the arrow `later` returns, not
// `later`), a data field is not a function, and a getter is READ where it is written: its value is handed over
now(xs: number[]): void {
setTimeout(this.later.call(this));
xs.forEach(this.label as any);
xs.forEach(this.current);
}

get current(): (n: number) => void { return () => {}; }
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
value values#<function-type>@4:25 -> values#<arrow>@6
value values#<function-type>@4:25 -> values#<arrow>@7
value values#<function-type>@4:25 -> values#Pipeline.tag
value_call values#Pipeline.run -> values#<arrow>@6
value_call values#apply -> values#<arrow>@6
value_call values#apply -> values#<arrow>@7
value_call values#drive -> values#Pipeline.tag
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
value lib:hooks#<function-type>@1:20 -> values#<arrow>@37
value values#<function-type>@4:25 -> values#<arrow>@6
value values#<function-type>@4:25 -> values#<arrow>@7
value values#<function-type>@4:25 -> values#Pipeline.tag
value_call values#Pipeline.run -> values#<arrow>@6
value_call values#apply -> values#<arrow>@6
value_call values#apply -> values#<arrow>@7
value_call values#drive -> values#Pipeline.tag
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
nominal globals#Function.apply -> globals#CallableFunction.apply
nominal globals#Function.bind -> globals#CallableFunction.bind
nominal globals#Function.call -> globals#CallableFunction.call
value_call strict-bind#viaArrow -> strict-bind#<arrow>
value_call strict-bind#viaDeclaredFunction -> strict-bind#localFormat
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
nominal globals#Function.apply -> globals#CallableFunction.apply
nominal globals#Function.bind -> globals#CallableFunction.bind
nominal globals#Function.call -> globals#CallableFunction.call
value_call loose-bind#viaArrow -> loose-bind#<arrow>
value_call loose-bind#viaDeclaredFunction -> loose-bind#localFormat
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
value members#<function-type>@46:20 -> members#Box.run
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ value schema#<function-type>@11:21 -> schema#<arrow>@24
value schema#Internals.parse -> schema#<arrow>@15
value schema#Internals.run -> schema#<arrow>@15
value schema#Internals.run -> schema#<arrow>@19
value schema#Internals.run -> schema#Internals.parse
value_call schema#<arrow>@19 -> schema#<arrow>@15
value_call schema#<arrow>@24 -> schema#<arrow>@15
value_call schema#<arrow>@24 -> schema#<arrow>@19
value_call schema#<arrow>@24 -> schema#Internals.parse
value_call schema#parseTyped -> schema#<arrow>@15
value_call schema#parseTyped -> schema#<arrow>@19
value_call schema#parseTyped -> schema#Internals.parse
20 changes: 20 additions & 0 deletions graph/test/typescript/expected/80-method-passed-by-reference.edges
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
ambient_terminal FUNCTION_CALL Widget#now(number[]) @L40 -> globals#setTimeout(() =,number)
ambient_terminal FUNCTION_CALL Widget#wire(Target) @L24 -> globals#setTimeout(() =,number)
ambiguous_unknown METHOD_CALL Widget#now(number[]) @L40 -> -
ambiguous_unknown METHOD_CALL Widget#start(number[]) @L18 -> -
ambiguous_unknown METHOD_CALL Widget#wire(Target) @L24 -> -
callback_registered FUNCTION_CALL Widget#wire(Target) @L24 -> Widget#onHover()
callback_registered METHOD_CALL Widget#relay(Widget,number[]) @L29 -> Widget#handle(number)
callback_registered METHOD_CALL Widget#start(number[]) @L18 -> Widget#handle(number)
callback_registered METHOD_CALL Widget#start(number[]) @L19 -> Widget#handle(number)
callback_registered METHOD_CALL Widget#tap(Target) @L34 -> Widget#<arrow@34>()
callback_registered METHOD_CALL Widget#wire(Target) @L23 -> Widget#onClick()
known_edge METHOD_CALL Widget#<arrow@34>() @L34 -> Widget#onTap()
known_edge METHOD_CALL Widget#now(number[]) @L41 -> Array#forEach((v: T) =,unknown)
known_edge METHOD_CALL Widget#now(number[]) @L42 -> Array#forEach((v: T) =,unknown)
known_edge METHOD_CALL Widget#relay(Widget,number[]) @L29 -> Array#forEach((v: T) =,unknown)
known_edge METHOD_CALL Widget#start(number[]) @L18 -> Array#forEach((v: T) =,unknown)
known_edge METHOD_CALL Widget#start(number[]) @L19 -> Array#map((v: T) =,unknown)
known_edge METHOD_CALL Widget#tap(Target) @L34 -> Target#addEventListener(string,() =)
known_edge METHOD_CALL Widget#wire(Target) @L23 -> Target#addEventListener(string,() =)
known_edge PROPERTY_READ Widget#now(number[]) @L42 -> Widget#current()
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
── entry_point (3) ──
exported_from_entry_module globals#setTimeout globals.d.ts:19
unimported_module globals#<module> globals.d.ts:1
unimported_module widget#<module> widget.ts:1
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
value widget#<function-type>@14:12 -> widget#Widget.<arrow>@14
value widget#<function-type>@45:18 -> widget#Widget.<arrow>@45
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
known_edge read Widget#handle(number) -> Widget#scale
known_edge read Widget#now(number[]) -> Widget#label
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
80-method-passed-by-reference [fields]
precision 1.0000 (2 correct, 0 wrong)
recall 1.0000 (2 of 2 the compiler resolved)
sites 2 resolved 2 (100.0%)
tiers known_edge=2
access read=2
not scored: 0 rows whose target is not a client declaration
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
oracle=8 engine=10 agree=8 missing=0 (known 0, NEW 0) extra=2
extra Widget#now(number[]) -> globals#setTimeout(() =,number)
extra Widget#wire(Target) -> globals#setTimeout(() =,number)
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
known_edge METHOD_PARAM 0 Widget [METHOD_PARAM] -> Target
known_edge METHOD_PARAM 0 Widget [METHOD_PARAM] -> Widget
known_edge SUPER_TYPE 0 CallableFunction [HERITAGE] -> Function
known_edge SUPER_TYPE 0 NewableFunction [HERITAGE] -> Function
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
80-method-passed-by-reference [types]
precision 1.0000 (4 correct, 0 wrong)
recall 1.0000 (4 of 4 the compiler resolved)
sites 5 resolved 5 (100.0%)
tiers known_edge=5
contexts METHOD_PARAM=3 SUPER_TYPE=2
not scored: 0 rows whose target is not a client declaration
40 changes: 40 additions & 0 deletions graph/typescript/engine/resolution/value-flow.dl
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,43 @@ handed_function(ce, m) :- hof_boundary_site(ce), expr_child("client", ce, "ARGUM
expr_kind("client", "CALL_EXPRESSION", _, x),
call_runs(x, f),
holder_holds_function(f, m).
handed_function(ce, m) :- hof_boundary_site(ce), expr_child("client", ce, "ARGUMENT", _, a),
value_branch(a, x),
method_value(x, m).

// ── method_value(Expr, Method): an INSTANCE method read as a value, not called ──
// `xs.forEach(this.handle, this)`, `el.addEventListener('click', this.onClick)`,
// `setTimeout(this.onHover.bind(this))`. expr_callable names a free function, an import and
// a static member (`Util.f`), and stops there on purpose: it feeds the callee resolver's own
// negation, so reading the receiver's type from it would put that negation in a cycle.
// Down here every resolver rule has already run, so the member is looked up exactly as an
// instance call `recv.name()` looks it up (callee-resolution rules 4 and 6). Without it a
// method passed by reference had no edge from the function that passes it, where a free
// function passed at the same position had one.
method_value(x, m) :- expr_kind("client", "PROPERTY_ACCESS", _, x),
property_access_name(x, n),
expr_child("client", x, "RECEIVER", _, q),
expr_type(q, _, qt),
method_in_scope(qt, n, "false", m),
!method_is_accessor(m).
method_value(x, m) :- expr_kind("client", "PROPERTY_ACCESS", _, x),
property_access_name(x, n),
expr_child("client", x, "RECEIVER", _, q),
expr_shape(q, s),
method_in_scope(s, n, _, m),
!method_is_accessor(m).
// `String(c.current)` with `get current()` READS the property: the getter runs there and its
// value is what is handed over, and a setter is never read at all.
method_is_accessor(m) :- method_kind(_, "GETTER", _, m).
method_is_accessor(m) :- method_kind(_, "SETTER", _, m).
// `f.bind(thisArg, …)` is `f` with its receiver fixed: what it hands over is `f`. Only `bind`:
// `f.call(…)` and `f.apply(…)` run `f` there and hand over what it returns.
method_value(x, m) :- call_site("client", _, "bind", _, recv, x, _),
recv != "",
method_value(recv, m).
method_value(x, m) :- call_site("client", _, "bind", _, recv, x, _),
recv != "",
expr_callable(recv, m).

// ── value_branch(Root, Expr): Expr is a value Root can evaluate to ──
value_branch(e, e) :- holder_value(_, e).
Expand Down Expand Up @@ -259,6 +296,9 @@ holder_holds_function(h, m) :- holder_value(h, e),
value_branch(e, x),
!expr_kind("client", "CALL_EXPRESSION", _, x), # `return make()` holds what make RETURNS, not make:
expr_callable(x, m). # a call node carries its callee as its referenced entity
holder_holds_function(h, m) :- holder_value(h, e),
value_branch(e, x),
method_value(x, m).
// …AND WHAT ANOTHER HOLDER HANDS OVER. `run(fn, promise, resolve)` passes the caller's own
// parameter on, and a wrapper that forwards a callback is the ordinary shape of one: the
// function reaches the inner parameter only through the outer one.
Expand Down
2 changes: 2 additions & 0 deletions graph/typescript/souffle/decls_all.dl
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,8 @@
.decl call_runs_client_body(c0:symbol)
.decl hof_boundary_site(c0:symbol)
.decl handed_function(c0:symbol,c1:symbol)
.decl method_value(c0:symbol,c1:symbol)
.decl method_is_accessor(c0:symbol)
.decl called_through(c0:symbol,c1:symbol)
.decl method_enclosing_member(c0:symbol,c1:symbol)
.decl call_runs(c0:symbol,c1:symbol)
Expand Down
Loading