mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-10 01:56:15 +08:00
Read array-like elements for join, flat and flatMap in specification order
join checked each index with HasProperty before reading it, so a proxy's has trap ran, and could hide a value or throw. flat converted its depth only after it had read every index, and flatMap ran every callback before it flattened any result, so a later read or callback could change an array that should already have been copied. - join reads every index with Get alone; a missing index joins as the empty string. - flat converts its depth after the length and before the first index, and flatMap checks its callback there. - Each present value, mapped first for flatMap, is flattened into the result before the next index is read, through the array dispatch's flat over `[value]`. scr_dyn.h declares scr_dyn_array_like_call and scr_dyn_builtin_prototype, the entry points of the two gated units, so every scr_* name the backend can emit has a prototype in scr_runtime.h. The array-like corpus program's logging proxy answers its has trap with `in` instead of Reflect.has, which has no static lowering, so the program stays fully static.
This commit is contained in:
@@ -1526,6 +1526,14 @@ void scr_class_throw_read_only(ScrStr *field, ScrStr *owner);
|
||||
void scr_class_throw_not_extensible(ScrStr *field);
|
||||
ScrDyn *scr_dyn_get_own_property_descriptor(ScrDyn *target, ScrDyn *key);
|
||||
ScrDyn *scr_dyn_array_proto_call(ScrDyn *target, ScrStr *method, ScrDyn *args);
|
||||
/* Array.prototype's generic methods over an array-like receiver
|
||||
* (scr_dyn_array_like.c, linked only into programs that call them from a
|
||||
* JavaScript source): +1, or NULL with the exception pending. */
|
||||
ScrDyn *scr_dyn_array_like_call(ScrDyn *recv, ScrStr *method, ScrDyn *args);
|
||||
/* Number.prototype or Boolean.prototype as a value, one object per thread
|
||||
* (scr_dyn_builtin_prototype.c, linked only into programs that read them
|
||||
* from a JavaScript source); `kind` is "Number" or "Boolean". +1. */
|
||||
ScrDyn *scr_dyn_builtin_prototype(const ScrStr *kind);
|
||||
ScrDyn *scr_dyn_array_prototype(void); /* +1, installs native method adapters */
|
||||
ScrDyn *scr_dyn_array_prototype_base(void); /* +1, shared native array identity */
|
||||
/* Names the prototype object of a compiled class `name` for util.inspect
|
||||
|
||||
@@ -485,17 +485,18 @@ ScrDyn *scr_dyn_array_like_call(ScrDyn *recv, ScrStr *method, ScrDyn *args) {
|
||||
? scr_dyn_string_coerce_js(argv[0])
|
||||
: scr_str_new(",", 1);
|
||||
if (!sep) return NULL;
|
||||
/* Get alone at each index: a missing index reads undefined, which
|
||||
* joins as the empty string. */
|
||||
ScrJsonBuf buffer;
|
||||
scr_jb_init(&buffer);
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
if (i) scr_jb_write(&buffer, sep->data, sep->len);
|
||||
if (len > 1000000 && !al_iteration(i)) { scr_str_release(sep); return NULL; }
|
||||
if (!al_present(recv, i)) {
|
||||
if (scr_exc_pending()) { scr_str_release(sep); return NULL; }
|
||||
continue;
|
||||
ScrDyn *item = len > 1000000 && !al_iteration(i) ? NULL : al_item(recv, i);
|
||||
if (!item) {
|
||||
scr_str_release(scr_jb_finish(&buffer));
|
||||
scr_str_release(sep);
|
||||
return NULL;
|
||||
}
|
||||
ScrDyn *item = al_item(recv, i);
|
||||
if (!item) { scr_str_release(sep); return NULL; }
|
||||
if (item->kind != SCR_DYN_UNDEF && item->kind != SCR_DYN_NULL) {
|
||||
ScrStr *text = scr_dyn_string_coerce_js(item);
|
||||
if (text) {
|
||||
@@ -504,7 +505,11 @@ ScrDyn *scr_dyn_array_like_call(ScrDyn *recv, ScrStr *method, ScrDyn *args) {
|
||||
}
|
||||
}
|
||||
scr_dyn_release(item);
|
||||
if (scr_exc_pending()) { scr_str_release(sep); return NULL; }
|
||||
if (scr_exc_pending()) {
|
||||
scr_str_release(scr_jb_finish(&buffer));
|
||||
scr_str_release(sep);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
scr_str_release(sep);
|
||||
ScrStr *text = scr_jb_finish(&buffer);
|
||||
@@ -710,22 +715,30 @@ ScrDyn *scr_dyn_array_like_call(ScrDyn *recv, ScrStr *method, ScrDyn *args) {
|
||||
return out;
|
||||
}
|
||||
if (al_is(method, "flat") || al_is(method, "flatMap")) {
|
||||
/* Snapshot the receiver into a native array the array dispatch
|
||||
* flattens: the flattening rules (depth counting, nested arrays and
|
||||
* wrapped array views) live in one implementation. flatMap maps first,
|
||||
* then flattens the mapped array at depth 1. */
|
||||
/* FlattenIntoArray: after the length, flat converts its depth and
|
||||
* flatMap checks its callback. Then each present index is read and,
|
||||
* for flatMap, mapped, and its value is flattened into the result
|
||||
* before the next index is read. The array dispatch flattens each
|
||||
* value as `[value].flat(depth)`, so the flattening rules (nested
|
||||
* arrays and wrapped array views) keep one implementation. */
|
||||
bool mapping = al_is(method, "flatMap");
|
||||
if (mapping && !al_cb_check(argv, argc)) return NULL;
|
||||
ScrDyn *mapped = scr_dyn_new_arr();
|
||||
double depth = 1;
|
||||
if (mapping) {
|
||||
if (!al_cb_check(argv, argc)) return NULL;
|
||||
} else if (argc > 0 && argv[0]->kind != SCR_DYN_UNDEF) {
|
||||
if (!al_number(argv[0], &depth)) return NULL;
|
||||
if (depth < 0) depth = 0;
|
||||
}
|
||||
ScrDyn *depth_arg = scr_dyn_new_num(depth);
|
||||
ScrDyn *out = scr_dyn_new_arr();
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
if (len > 1000000 && !al_iteration(i)) { scr_dyn_release(mapped); return NULL; }
|
||||
if (len > 1000000 && !al_iteration(i)) goto flat_failed;
|
||||
if (!al_present(recv, i)) {
|
||||
if (scr_exc_pending()) { scr_dyn_release(mapped); return NULL; }
|
||||
scr_dyn_arr_push_hole(mapped);
|
||||
if (scr_exc_pending()) goto flat_failed;
|
||||
continue;
|
||||
}
|
||||
ScrDyn *item = al_item(recv, i);
|
||||
if (!item) { scr_dyn_release(mapped); return NULL; }
|
||||
if (!item) goto flat_failed;
|
||||
if (mapping) {
|
||||
ScrDyn *index = scr_dyn_new_num((double)i);
|
||||
ScrDyn *cbargs[3] = { item, index, recv };
|
||||
@@ -734,25 +747,24 @@ ScrDyn *scr_dyn_array_like_call(ScrDyn *recv, ScrStr *method, ScrDyn *args) {
|
||||
scr_dyn_this_pop();
|
||||
scr_dyn_release(index);
|
||||
scr_dyn_release(item);
|
||||
if (!result) { scr_dyn_release(mapped); return NULL; }
|
||||
if (!result) goto flat_failed;
|
||||
item = result;
|
||||
}
|
||||
scr_dyn_arr_push(mapped, item);
|
||||
ScrDyn *one = scr_dyn_new_arr();
|
||||
scr_dyn_arr_push(one, item);
|
||||
ScrDyn *flattened = scr_dyn_invoke(one, "flat", &depth_arg, 1, mapping ? "flatMap" : "flat");
|
||||
scr_dyn_release(one);
|
||||
if (!flattened) goto flat_failed;
|
||||
for (size_t j = 0; j < flattened->v.arr.len; j++)
|
||||
scr_dyn_arr_push(out, scr_dyn_retain(flattened->v.arr.items[j]));
|
||||
scr_dyn_release(flattened);
|
||||
}
|
||||
ScrDyn *one = scr_dyn_new_num(1);
|
||||
ScrDyn *flatargs[1] = { one };
|
||||
ScrDyn *out;
|
||||
if (mapping) {
|
||||
out = scr_dyn_invoke(mapped, "flat", flatargs, 1, "flatMap");
|
||||
} else {
|
||||
/* flat's own depth argument: absent, undefined or a number. */
|
||||
ScrDyn *given[1] = { argc > 0 ? argv[0] : scr_dyn_undefined() };
|
||||
size_t count = argc > 0 ? 1 : 0;
|
||||
out = scr_dyn_invoke(mapped, "flat", given, count, "flat");
|
||||
}
|
||||
scr_dyn_release(one);
|
||||
scr_dyn_release(mapped);
|
||||
scr_dyn_release(depth_arg);
|
||||
return out;
|
||||
flat_failed:
|
||||
scr_dyn_release(depth_arg);
|
||||
scr_dyn_release(out);
|
||||
return NULL;
|
||||
}
|
||||
/* Every other name keeps the one implementation in the invoke unit
|
||||
* (at, slice, indexOf, the higher-order methods, and the "not supported
|
||||
|
||||
@@ -79,7 +79,7 @@ const traced = (target) => {
|
||||
},
|
||||
has(object, key) {
|
||||
log.push('has:' + String(key));
|
||||
return Reflect.has(object, key);
|
||||
return key in object;
|
||||
},
|
||||
});
|
||||
return { proxy, log };
|
||||
@@ -149,4 +149,39 @@ report('array-concat', Array.prototype.concat.call(list, ['s'], 't').join(''));
|
||||
report('array-toSpliced', Array.prototype.toSpliced.call(list, 0, 1).join(''));
|
||||
report('array-with', Array.prototype.with.call(list, 0, 'z').join(''));
|
||||
|
||||
// join reads every index with Get alone; a has trap never runs.
|
||||
const joinedLog = traced({ length: 3, 0: 'a', 2: 'c' });
|
||||
report('join', Array.prototype.join.call(joinedLog.proxy, '-'));
|
||||
report('join-reads', joinedLog.log.join(','));
|
||||
const hidden = new Proxy({ length: 2, 0: 'x', 1: 'y' }, { has() { throw new Error('has trap'); } });
|
||||
report('join-has-trap', Array.prototype.join.call(hidden, ''));
|
||||
|
||||
// flat converts its depth before it reads any index.
|
||||
const order = [];
|
||||
const depthSource = { length: 2, get 0() { order.push('read 0'); return [1, [2]]; }, 1: 3 };
|
||||
const depth = { valueOf() { order.push('depth'); return 2; } };
|
||||
report('flat-depth', JSON.stringify(Array.prototype.flat.call(depthSource, depth)));
|
||||
report('flat-order', order.join(','));
|
||||
const failing = { length: 1, get 0() { order.push('never'); return 1; } };
|
||||
try {
|
||||
Array.prototype.flat.call(failing, { valueOf() { throw new Error('depth failed'); } });
|
||||
} catch (error) {
|
||||
report('flat-depth-throws', error.message + ' ' + order.includes('never'));
|
||||
}
|
||||
|
||||
// flatMap flattens each callback's result before the next callback runs.
|
||||
const shared = [1, 2];
|
||||
const mapped = Array.prototype.flatMap.call({ length: 2, 0: 'a', 1: 'b' }, (value) => {
|
||||
if (value === 'a') return shared;
|
||||
shared[0] = 9;
|
||||
return [];
|
||||
});
|
||||
report('flatMap-order', JSON.stringify(mapped));
|
||||
// flat also flattens a nested array when its index is read.
|
||||
const nestedArray = [1, 2];
|
||||
const nestedSource = { length: 2, 0: nestedArray, get 1() { nestedArray[0] = 9; return 3; } };
|
||||
report('flat-nested-order', JSON.stringify(Array.prototype.flat.call(nestedSource)));
|
||||
report('flat-negative', JSON.stringify(Array.prototype.flat.call({ length: 1, 0: [1, [2]] }, -1)));
|
||||
report('flat-infinity', JSON.stringify(Array.prototype.flat.call({ length: 1, 0: [1, [2, [3]]] }, Infinity)));
|
||||
|
||||
console.log(resolved.join('\n'));
|
||||
|
||||
Reference in New Issue
Block a user