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
22 changes: 21 additions & 1 deletion graph/javascript/engine/resolution/value-flow.dl
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@ installed_function(val, m) :- expr_kind(_, "ASSIGNMENT", _, val), expr_child(_,
// parser attributes a nested function's body to the nested function, so no
// filtering by nesting is needed. Plus `@returns {T}`.
return_value(m, k, i) :- expr_root(_, "RETURN", e), expr_owner(_, m, _, e), !expr_kind(_, "THIS", _, e),
!wrapped_result_return(e), expr_value(e, k, i).
!wrapped_result_return(e), !returns_own_class_new(m, e), expr_value(e, k, i).
// A wrapper closure's carrying return keeps every value that does NOT come from the
// parameter's call: `fn() || fallback`, `override || (cached = make())`, a variable
// that starts as a default (below). Not an `undefined` placeholder (`let result =
Expand Down Expand Up @@ -523,6 +523,26 @@ expr_value(ce, k, i) :- expr_kind(_, "CALL", _, ce), expr_resolves_to_method(ce,
expr_value(ce, k, i) :- expr_kind(_, "CALL", _, ce), expr_resolves_to_method(ce, m), method_returns_this(m),
call_site(_, ck, _, _, _, _, ce, _, _), !call_kind_is_member_form(ck),
this_value(m, k, i).
// `return new this.constructor(...)` is polymorphic the same way: `clone()` declared on
// Base and called on a Sub builds a Sub. Read merged, the construction held every
// instantiated subclass of Base and each call on the clone fanned to all their
// overrides. So it is not a value of the method either; each site builds an instance of
// its own receiver's class. (`new this()` in a static is not read this way: the compiler
// types it as the declaring class, and the oracle would score the receiver's as WRONG.)
// Returned directly, or through a `const` it initialises (`const c = new
// this.constructor(); c.x = …; return c`, the everyday clone), which nothing rebinds.
own_class_new(m, e) :- expr_kind(_, "NEW", _, e), call_site(_, "CONSTRUCTOR_CALL", "constructor", "SYNTACTIC", _, _, e, _, _),
expr_child(_, e, "RECEIVER", _, r), expr_kind(_, "THIS", _, r), expr_owner(_, m, _, r).
returns_own_class_new(m, e) :- expr_root(_, "RETURN", e), expr_owner(_, m, _, e), own_class_new(m, e).
returns_own_class_new(m, e) :- expr_root(_, "RETURN", e), expr_owner(_, m, _, e), expr_kind(_, "IDENTIFIER", _, e),
expr_binding(_, v, e), var_decl(_, _, "CONST_BLOCK_TDZ", _, _, _, v), var_binding_form(_, "IDENTIFIER", v),
var_init(_, _, n, v), own_class_new(m, n).
expr_value(ce, "inst", t) :- expr_kind(_, "CALL", _, ce), expr_resolves_to_method(ce, m), returns_own_class_new(m, _),
call_site(_, ck, _, "SYNTACTIC", _, _, ce, _, _), call_kind_is_member_form(ck),
expr_child(_, ce, "RECEIVER", _, r), expr_value(r, "inst", t).
expr_value(ce, "inst", t) :- expr_kind(_, "CALL", _, ce), expr_resolves_to_method(ce, m), returns_own_class_new(m, _),
call_site(_, ck, _, _, _, _, ce, _, _), !call_kind_is_member_form(ck),
this_value(m, "inst", t).

// ── prop_value(K, I, Name, K2, I2) — reading a property off a value ────────
// Object literal: `{ a: v }` (key + value children at the same child index),
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 @@ -397,6 +397,8 @@
.decl var_regime_is_block(c0:symbol)
.decl call_ambient_alternative(c0:symbol)
.decl method_returns_this(c0:symbol)
.decl returns_own_class_new(c0:symbol, c1:symbol)
.decl own_class_new(c0:symbol, c1:symbol)
.decl module_base_path(c0:symbol, c1:symbol, c2:symbol)
// ── resolution/arrays.dl: a class extending Map / Set (#619) ──
.decl coll_ctor(c0:symbol, c1:symbol)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
'use strict';
// `new this.constructor()` builds the RECEIVER's class at each site, not every
// subclass of the class that declares it.
class Base {
clone() { return new this.constructor(); }
copy() { return new Base(); }
run() { return 'base'; }
}
class Sub extends Base { run() { return 'sub'; } }
class Other extends Base { run() { return 'other'; } }

// The same shape on a constructor function and its prototype.
function Shape() {}
Shape.prototype.dup = function () { return new this.constructor(); };
Shape.prototype.area = function () { return 0; };
function Circle() { Shape.call(this); }
Circle.prototype = Object.create(Shape.prototype);
Circle.prototype.constructor = Circle;
Circle.prototype.area = function () { return 3; };

// The everyday clone: built into a `const`, filled in, returned.
class Query {
clone() { const q = new this.constructor(); q.opts = this.opts; return q; }
// Control: a `let` may be rebound before the return, so it keeps its merged value.
fork() { let q = new this.constructor(); return q; }
exec() { return 'query'; }
}
class Select extends Query { exec() { return 'select'; } }

function main() {
new Select().clone().exec();
new Query().clone().exec();
new Select().fork().exec();
new Sub().clone().run();
new Other().clone().run();
new Sub().clone().clone().run();
new Base().clone().run();
// Control: a fixed `new Base()` stays Base whatever the receiver.
new Sub().copy().run();
new Circle().dup().area();
new Shape().dup().area();
}
main();
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ "name": "this-constructor-per-site" }
3 changes: 1 addition & 2 deletions graph/test/javascript/expected/07-esm-class-hierarchy.edges
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ Component.js:7:15 CONSTRUCTOR_CALL AbstractBlock -> known_edge AbstractBlock.
Component.js:7:5 METHOD_CALL block.add -> known_edge Node.js:8:3 add
Component.js:7:5 METHOD_CALL block.add(new AbstractBlock(this)).walk -> known_edge AbstractBlock.js:4:3 walk
Component.js:8:18 METHOD_CALL block.clone -> known_edge Node.js:9:3 clone
Component.js:9:5 METHOD_CALL copy.walk -> multi_inferred AbstractBlock.js:4:3 walk
Component.js:9:5 METHOD_CALL copy.walk -> multi_inferred Node.js:7:3 walk
Component.js:9:5 METHOD_CALL copy.walk -> known_edge AbstractBlock.js:4:3 walk
Node.js:7:12 METHOD_CALL this.component.warn -> known_edge Component.js:3:3 warn
Node.js:7:39 METHOD_CALL this.children.forEach -> ambient_terminal -
Node.js:7:39 METHOD_CALL this.children.forEach -> callback_registered Node.js:7:61 <arrow>
Expand Down
6 changes: 2 additions & 4 deletions graph/test/javascript/expected/37-this-constructor.edges
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
main.js:11:13 CONSTRUCTOR_CALL Sub -> implicit_constructor -
main.js:12:3 METHOD_CALL s.clone -> known_edge main.js:3:3 clone
main.js:12:3 METHOD_CALL s.clone().run -> multi_inferred main.js:5:3 run
main.js:12:3 METHOD_CALL s.clone().run -> multi_inferred main.js:8:26 run
main.js:12:3 METHOD_CALL s.clone().run -> known_edge main.js:8:26 run
main.js:13:3 METHOD_CALL Sub.build -> known_edge main.js:6:3 build
main.js:13:3 METHOD_CALL Sub.build().run -> multi_inferred main.js:5:3 run
main.js:13:3 METHOD_CALL Sub.build().run -> multi_inferred main.js:8:26 run
main.js:14:3 CONSTRUCTOR_CALL Base -> implicit_constructor -
main.js:14:3 METHOD_CALL new Base().make -> known_edge main.js:4:3 make
main.js:14:3 METHOD_CALL new Base().make().run -> multi_inferred main.js:5:3 run
main.js:14:3 METHOD_CALL new Base().make().run -> multi_inferred main.js:8:26 run
main.js:14:3 METHOD_CALL new Base().make().run -> known_edge main.js:5:3 run
main.js:16:1 FUNCTION_CALL main -> known_edge main.js:10:1 main
main.js:3:20 CONSTRUCTOR_CALL this.constructor -> implicit_constructor -
main.js:4:19 CONSTRUCTOR_CALL this.constructor -> implicit_constructor -
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
package_entry this-constructor-per-site . [] DEFAULT_INDEX index.js MISSING_FILE -> -
unresolved main.js:17:20 METHOD_CALL create no_target
41 changes: 41 additions & 0 deletions graph/test/javascript/expected/69-this-constructor-per-site.edges
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
main.js:14:44 CONSTRUCTOR_CALL this.constructor -> multi_inferred main.js:13:1 Shape
main.js:14:44 CONSTRUCTOR_CALL this.constructor -> multi_inferred main.js:16:1 Circle
main.js:16:21 FUNCTION_CALL_CALL Shape.call -> known_edge main.js:13:1 Shape
main.js:17:20 METHOD_CALL Object.create -> ambient_terminal -
main.js:23:23 CONSTRUCTOR_CALL this.constructor -> implicit_constructor -
main.js:25:20 CONSTRUCTOR_CALL this.constructor -> implicit_constructor -
main.js:31:3 CONSTRUCTOR_CALL Select -> implicit_constructor -
main.js:31:3 METHOD_CALL new Select().clone -> known_edge main.js:23:3 clone
main.js:31:3 METHOD_CALL new Select().clone().exec -> known_edge main.js:28:30 exec
main.js:32:3 CONSTRUCTOR_CALL Query -> implicit_constructor -
main.js:32:3 METHOD_CALL new Query().clone -> known_edge main.js:23:3 clone
main.js:32:3 METHOD_CALL new Query().clone().exec -> known_edge main.js:26:3 exec
main.js:33:3 CONSTRUCTOR_CALL Select -> implicit_constructor -
main.js:33:3 METHOD_CALL new Select().fork -> known_edge main.js:25:3 fork
main.js:33:3 METHOD_CALL new Select().fork().exec -> multi_inferred main.js:26:3 exec
main.js:33:3 METHOD_CALL new Select().fork().exec -> multi_inferred main.js:28:30 exec
main.js:34:3 CONSTRUCTOR_CALL Sub -> implicit_constructor -
main.js:34:3 METHOD_CALL new Sub().clone -> known_edge main.js:5:3 clone
main.js:34:3 METHOD_CALL new Sub().clone().run -> known_edge main.js:9:26 run
main.js:35:3 CONSTRUCTOR_CALL Other -> implicit_constructor -
main.js:35:3 METHOD_CALL new Other().clone -> known_edge main.js:5:3 clone
main.js:35:3 METHOD_CALL new Other().clone().run -> known_edge main.js:10:28 run
main.js:36:3 CONSTRUCTOR_CALL Sub -> implicit_constructor -
main.js:36:3 METHOD_CALL new Sub().clone -> known_edge main.js:5:3 clone
main.js:36:3 METHOD_CALL new Sub().clone().clone -> known_edge main.js:5:3 clone
main.js:36:3 METHOD_CALL new Sub().clone().clone().run -> known_edge main.js:9:26 run
main.js:37:3 CONSTRUCTOR_CALL Base -> implicit_constructor -
main.js:37:3 METHOD_CALL new Base().clone -> known_edge main.js:5:3 clone
main.js:37:3 METHOD_CALL new Base().clone().run -> known_edge main.js:7:3 run
main.js:39:3 CONSTRUCTOR_CALL Sub -> implicit_constructor -
main.js:39:3 METHOD_CALL new Sub().copy -> known_edge main.js:6:3 copy
main.js:39:3 METHOD_CALL new Sub().copy().run -> known_edge main.js:7:3 run
main.js:40:3 CONSTRUCTOR_CALL Circle -> known_edge main.js:16:1 Circle
main.js:40:3 METHOD_CALL new Circle().dup -> known_edge main.js:14:23 dup
main.js:40:3 METHOD_CALL new Circle().dup().area -> known_edge main.js:19:25 area
main.js:41:3 CONSTRUCTOR_CALL Shape -> known_edge main.js:13:1 Shape
main.js:41:3 METHOD_CALL new Shape().dup -> known_edge main.js:14:23 dup
main.js:41:3 METHOD_CALL new Shape().dup().area -> known_edge main.js:15:24 area
main.js:43:1 FUNCTION_CALL main -> known_edge main.js:30:1 main
main.js:5:20 CONSTRUCTOR_CALL this.constructor -> implicit_constructor -
main.js:6:19 CONSTRUCTOR_CALL Base -> implicit_constructor -
29 changes: 29 additions & 0 deletions graph/test/javascript/expected/69-this-constructor-per-site.oracle
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
main.js:14:44 CONSTRUCTOR_CALL constructor SYNTHESIZED_OVER main.js:13:1;main.js:16:1
main.js:16:21 FUNCTION_CALL_CALL call EXACT main.js:13:1
main.js:17:20 METHOD_CALL create LIB_AMBIENT_OK
main.js:23:23 CONSTRUCTOR_CALL constructor SYNTHESIZED_OK
main.js:25:20 CONSTRUCTOR_CALL constructor SYNTHESIZED_OK
main.js:31:3 CONSTRUCTOR_CALL Select SYNTHESIZED_OK
main.js:31:3 METHOD_CALL clone EXACT main.js:23:3
main.js:32:3 CONSTRUCTOR_CALL Query SYNTHESIZED_OK
main.js:32:3 METHOD_CALL clone EXACT main.js:23:3
main.js:33:3 CONSTRUCTOR_CALL Select SYNTHESIZED_OK
main.js:33:3 METHOD_CALL fork EXACT main.js:25:3
main.js:34:3 CONSTRUCTOR_CALL Sub SYNTHESIZED_OK
main.js:34:3 METHOD_CALL clone EXACT main.js:5:3
main.js:35:3 CONSTRUCTOR_CALL Other SYNTHESIZED_OK
main.js:35:3 METHOD_CALL clone EXACT main.js:5:3
main.js:36:3 CONSTRUCTOR_CALL Sub SYNTHESIZED_OK
main.js:36:3 METHOD_CALL clone EXACT main.js:5:3
main.js:37:3 CONSTRUCTOR_CALL Base SYNTHESIZED_OK
main.js:37:3 METHOD_CALL clone EXACT main.js:5:3
main.js:39:3 CONSTRUCTOR_CALL Sub SYNTHESIZED_OK
main.js:39:3 METHOD_CALL copy EXACT main.js:6:3
main.js:39:3 METHOD_CALL run EXACT main.js:7:3
main.js:40:3 CONSTRUCTOR_CALL Circle EXACT main.js:16:1
main.js:41:3 CONSTRUCTOR_CALL Shape EXACT main.js:13:1
main.js:41:3 METHOD_CALL dup EXACT main.js:14:23
main.js:43:1 FUNCTION_CALL main EXACT main.js:30:1
main.js:5:20 CONSTRUCTOR_CALL constructor SYNTHESIZED_OK
main.js:6:19 CONSTRUCTOR_CALL Base SYNTHESIZED_OK
# defects: 0
Loading