mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-26 06:34:20 +08:00
Give prototype name stamps their own copy of the name
A construction through a plain function stamps the prototype with the
name of the function in its `constructor` property, for util.inspect.
The stamp pointed at the function's name. A bound function's name
("bound " and the target's name) is a string the bound function owns,
so after the `constructor` property was replaced and the bound function
released, inspecting an instance read freed memory. The stamp now holds
a copy of the name, freed with the stamp at exit.
The corpus program constructs through functions whose prototype's
`constructor` is a bound function (and a bound function of one), then
replaces the property and inspects the instances.
This commit is contained in:
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/bound-constructor-inspect-names.cjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -4360,16 +4360,25 @@ bool scr_dyn_data_view_is(const ScrDyn *value) {
|
||||
* `constructor` data property on its prototype chain (Node's
|
||||
* getConstructorName). The dyn printer reads runtime class names
|
||||
* (v.obj.class_name, set only on prototypes that stay alive), so a
|
||||
* construction through a plain function stamps that prototype with the
|
||||
* function's static name and keeps it alive; the stamps are cleared before
|
||||
* release at exit, so freelist reuse never sees them. */
|
||||
static SCR_TL ScrDyn **scr_dyn_named_prototypes;
|
||||
* construction through a plain function stamps that prototype with a copy
|
||||
* of the function's name and keeps it alive. The copy is the stamp's own: a
|
||||
* function's name can live in storage the function owns (a bound
|
||||
* function's "bound <name>"), and the function is released when the
|
||||
* writable `constructor` property is replaced. The stamps are cleared and
|
||||
* their copies freed before release at exit, so freelist reuse never sees
|
||||
* them. */
|
||||
typedef struct {
|
||||
ScrDyn *prototype;
|
||||
char *name;
|
||||
} ScrDynNamedPrototype;
|
||||
static SCR_TL ScrDynNamedPrototype *scr_dyn_named_prototypes;
|
||||
static SCR_TL size_t scr_dyn_named_count, scr_dyn_named_cap;
|
||||
|
||||
static void scr_dyn_named_prototypes_cleanup(void) {
|
||||
for (size_t i = 0; i < scr_dyn_named_count; i++) {
|
||||
scr_dyn_named_prototypes[i]->v.obj.class_name = NULL;
|
||||
scr_dyn_release(scr_dyn_named_prototypes[i]);
|
||||
scr_dyn_named_prototypes[i].prototype->v.obj.class_name = NULL;
|
||||
scr_dyn_release(scr_dyn_named_prototypes[i].prototype);
|
||||
free(scr_dyn_named_prototypes[i].name);
|
||||
}
|
||||
free(scr_dyn_named_prototypes);
|
||||
scr_dyn_named_prototypes = NULL;
|
||||
@@ -4386,13 +4395,17 @@ static void scr_dyn_name_prototype_chain(const ScrDyn *object) {
|
||||
if (scr_dyn_named_count == scr_dyn_named_cap) {
|
||||
if (scr_dyn_named_cap == 0) scr_atexit(scr_dyn_named_prototypes_cleanup);
|
||||
size_t cap = scr_dyn_named_cap ? scr_dyn_named_cap * 2 : 8;
|
||||
ScrDyn **grown = realloc(scr_dyn_named_prototypes, cap * sizeof *grown);
|
||||
ScrDynNamedPrototype *grown = realloc(scr_dyn_named_prototypes, cap * sizeof *grown);
|
||||
if (!grown) scr_json_oom();
|
||||
scr_dyn_named_prototypes = grown;
|
||||
scr_dyn_named_cap = cap;
|
||||
}
|
||||
p->v.obj.class_name = name;
|
||||
scr_dyn_named_prototypes[scr_dyn_named_count++] = scr_dyn_retain(p);
|
||||
size_t len = strlen(name);
|
||||
char *copy = malloc(len + 1);
|
||||
if (!copy) scr_json_oom();
|
||||
memcpy(copy, name, len + 1);
|
||||
p->v.obj.class_name = copy;
|
||||
scr_dyn_named_prototypes[scr_dyn_named_count++] = (ScrDynNamedPrototype){ scr_dyn_retain(p), copy };
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
'use strict';
|
||||
// util.inspect names an object after the function in the first own
|
||||
// `constructor` property on its prototype chain. Here that function is a
|
||||
// bound function, whose name ("bound " and the target's name) is built at
|
||||
// bind time, and the property is replaced after construction, which
|
||||
// releases the first bound function. The instance keeps the name. The
|
||||
// targets are anonymous, so the bound names are "bound " and "bound bound ".
|
||||
const util = require('util');
|
||||
|
||||
// Array elements give the functions no name.
|
||||
const [Point, Pair] = [
|
||||
function (x) {
|
||||
this.x = x;
|
||||
},
|
||||
function (a, b) {
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
},
|
||||
];
|
||||
Point.prototype.constructor = Point.bind(null);
|
||||
const p = new Point(1);
|
||||
console.log(util.inspect(p));
|
||||
|
||||
Point.prototype.constructor = Point.bind(null);
|
||||
const filler = [];
|
||||
for (let i = 0; i < 200; i++) filler.push('bound '.repeat(i % 5) + i);
|
||||
console.log(util.inspect(p));
|
||||
console.log(p);
|
||||
console.log(filler.length);
|
||||
|
||||
// A bound function of a bound function, replaced the same way.
|
||||
Pair.prototype.constructor = Pair.bind(null).bind(null);
|
||||
const q = new Pair(1, 2);
|
||||
Pair.prototype.constructor = Pair.bind(null).bind(null);
|
||||
console.log(util.inspect(q));
|
||||
console.log(q);
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 2400, "san": 2700 }
|
||||
Reference in New Issue
Block a user