mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-07 08:33:50 +08:00
Decide an untyped property write with one own-property lookup (#2014)
This commit is contained in:
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7793-untyped-key-writes.js"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -9369,10 +9369,12 @@ bool scr_dyn_in(const ScrDyn *v, const ScrStr *key) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* One lookup of the own property decides the assignment: an own property
|
||||
* is written (or runs its setter, or refuses the write); an absent one
|
||||
* consults the prototype chain and is then added. */
|
||||
static void scr_dyn_object_key_set(ScrDyn *recv, ScrStr *key, ScrDyn *value, const ScrDyn *receiver) {
|
||||
for (size_t i = 0; i < recv->v.obj.len; i++) {
|
||||
ScrDynEntry *entry = &recv->v.obj.entries[i];
|
||||
if (entry->key_len != key->len || memcmp(entry->key, key->data, key->len) != 0) continue;
|
||||
ScrDynEntry *entry = scr_dyn_entry(recv, key);
|
||||
if (entry) {
|
||||
if (entry->accessor && entry->setter) {
|
||||
ScrDyn *args[] = {value};
|
||||
ScrDyn *setter = scr_dyn_retain(entry->setter);
|
||||
@@ -9388,8 +9390,14 @@ static void scr_dyn_object_key_set(ScrDyn *recv, ScrStr *key, ScrDyn *value, con
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1);
|
||||
return;
|
||||
}
|
||||
scr_dyn_region_store(recv, value);
|
||||
ScrDyn *old = entry->value;
|
||||
entry->value = scr_dyn_retain(value);
|
||||
scr_dyn_release(old);
|
||||
scr_error_sync_cause(recv, key);
|
||||
return;
|
||||
}
|
||||
if (!scr_dyn_entry(recv, key)) {
|
||||
{
|
||||
for (const ScrDyn *current = scr_dyn_proto_next(recv); current;) {
|
||||
ScrDyn *table = current->kind == SCR_DYN_FUNC ? scr_dyn_fn_properties(current) : NULL;
|
||||
const ScrDyn *object = table ? table : current;
|
||||
@@ -9421,7 +9429,7 @@ static void scr_dyn_object_key_set(ScrDyn *recv, ScrStr *key, ScrDyn *value, con
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (recv->non_extensible && !scr_dyn_entry(recv, key)) {
|
||||
if (recv->non_extensible) {
|
||||
ScrJsonBuf buffer;
|
||||
scr_jb_init(&buffer);
|
||||
scr_jb_puts(&buffer, "Cannot add property ");
|
||||
@@ -9430,9 +9438,8 @@ static void scr_dyn_object_key_set(ScrDyn *recv, ScrStr *key, ScrDyn *value, con
|
||||
scr_throw_error(SCR_ERR_TYPE, scr_jb_finish(&buffer));
|
||||
return;
|
||||
}
|
||||
bool created = scr_dyn_entry(recv, key) == NULL;
|
||||
scr_dyn_obj_set(recv, key->data, key->len, scr_dyn_retain(value));
|
||||
if (created && key->len == 5 && memcmp(key->data, "stack", 5) == 0 &&
|
||||
if (key->len == 5 && memcmp(key->data, "stack", 5) == 0 &&
|
||||
scr_errdyn_find(recv, false) != SIZE_MAX) {
|
||||
/* An error's own `stack` exists from construction as a non-enumerable
|
||||
* data property, so assigning it adds no enumerable key. */
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
"use strict";
|
||||
// Untyped property writes through one site: new keys append in order,
|
||||
// existing keys keep their position, own and inherited setters run,
|
||||
// getter-only and read-only properties refuse the write, and
|
||||
// non-extensible objects refuse new keys, as in Node.
|
||||
|
||||
/** @param {any} target @param {string} key @param {unknown} value */
|
||||
function put(target, key, value) {
|
||||
target[key] = value;
|
||||
}
|
||||
|
||||
/** @param {() => void} fn */
|
||||
function attempt(label, fn) {
|
||||
try {
|
||||
fn();
|
||||
console.log(label, "ok");
|
||||
} catch (e) {
|
||||
console.log(label, e instanceof TypeError ? "TypeError" : String(e));
|
||||
}
|
||||
}
|
||||
|
||||
const order = {};
|
||||
for (const key of ["b", "a", "c", "a", "b", "d"]) put(order, key, key.charCodeAt(0));
|
||||
console.log(JSON.stringify(order), Object.keys(order).join(""));
|
||||
|
||||
const log = [];
|
||||
const withSetter = {
|
||||
_v: 0,
|
||||
get v() {
|
||||
return this._v;
|
||||
},
|
||||
set v(value) {
|
||||
log.push(`own ${value}`);
|
||||
this._v = value * 2;
|
||||
},
|
||||
get onlyGetter() {
|
||||
return "g";
|
||||
},
|
||||
};
|
||||
put(withSetter, "v", 4);
|
||||
console.log(withSetter.v, JSON.stringify(log));
|
||||
attempt("getter-only", () => put(withSetter, "onlyGetter", 1));
|
||||
|
||||
const proto = {
|
||||
/** @param {number} value */
|
||||
set inherited(value) {
|
||||
log.push(`inherited ${value} on ${this === child}`);
|
||||
},
|
||||
};
|
||||
Object.defineProperty(proto, "fixed", { value: 1, writable: false });
|
||||
const child = Object.create(proto);
|
||||
put(child, "inherited", 7);
|
||||
attempt("inherited read-only", () => put(child, "fixed", 2));
|
||||
put(child, "own", 3);
|
||||
console.log(JSON.stringify(log), Object.keys(child).join(","), child.fixed);
|
||||
|
||||
/** @type {any} */
|
||||
const sealed = { x: 1 };
|
||||
Object.seal(sealed);
|
||||
put(sealed, "x", 2);
|
||||
attempt("sealed add", () => put(sealed, "y", 3));
|
||||
/** @type {any} */
|
||||
const closed = { x: 1 };
|
||||
Object.preventExtensions(closed);
|
||||
put(closed, "x", 5);
|
||||
attempt("closed add", () => put(closed, "z", 1));
|
||||
const frozen = Object.freeze({ x: 1 });
|
||||
attempt("frozen write", () => put(frozen, "x", 9));
|
||||
console.log(JSON.stringify(sealed), JSON.stringify(closed), JSON.stringify(frozen));
|
||||
|
||||
const readOnly = {};
|
||||
Object.defineProperty(readOnly, "k", { value: "kept", writable: false, enumerable: true });
|
||||
attempt("own read-only", () => put(readOnly, "k", "changed"));
|
||||
console.log(readOnly.k);
|
||||
|
||||
const error = new Error("boom");
|
||||
put(error, "cause", "why");
|
||||
put(error, "code", "E_X");
|
||||
console.log(error.cause, error.code);
|
||||
|
||||
const wide = {};
|
||||
for (let i = 0; i < 40; i++) put(wide, `k${i % 25}`, i);
|
||||
console.log(Object.keys(wide).length, wide.k0, wide.k24, Object.keys(wide).slice(0, 3).join(","));
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 4300, "san": 4800 }
|
||||
Reference in New Issue
Block a user