diff --git a/packages/runtime/src/scr_dyn.h b/packages/runtime/src/scr_dyn.h index 0783ff705..d36c0d263 100644 --- a/packages/runtime/src/scr_dyn.h +++ b/packages/runtime/src/scr_dyn.h @@ -997,6 +997,11 @@ ScrDyn *scr_dyn_get_own_property_descriptor(ScrDyn *target, ScrDyn *key); ScrDyn *scr_dyn_array_proto_call(ScrDyn *target, ScrStr *method, ScrDyn *args); ScrDyn *scr_dyn_array_prototype(void); /* +1, installs native method adapters */ ScrDyn *scr_dyn_array_prototype_base(void); /* +1, shared native array identity */ +/* Native arrays read their holes as undefined without consulting + * Array.prototype, so storing an element on Array.prototype raises SC2020. + * True (with the error pending) when `recv` is Array.prototype. */ +bool scr_dyn_array_prototype_element_fence(const ScrDyn *recv); +bool scr_dyn_is_array_prototype(const ScrDyn *d); /* The checked-dynamic keyed WRITE (`h.k = v` on a dyn receiver): OBJ sets * the member (JS: later writes win, insertion order); undefined/null and * non-object kinds throw Node's catchable TypeErrors (strict-mode diff --git a/packages/runtime/src/scr_dyn_invoke.c b/packages/runtime/src/scr_dyn_invoke.c index ffe1d0330..2a010f213 100644 --- a/packages/runtime/src/scr_dyn_invoke.c +++ b/packages/runtime/src/scr_dyn_invoke.c @@ -811,6 +811,10 @@ static ScrDyn *scr_dyn_invoke_impl( if (recv->kind == SCR_DYN_HANDLE) { return scr_dynh_dispatch(recv, method, args, argc, what); } + if (scr_dyn_is_array_prototype(recv) && dyn_arr_proto_mutates(method)) { + scr_dyn_array_prototype_element_fence(recv); + return NULL; + } if (recv->kind == SCR_DYN_PROXY) { diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index adcc96560..9fae63ba7 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -674,6 +674,17 @@ void scr_dyn_from_leave(void) { */ static SCR_TL ScrDyn *scr_builtin_array_prototype; +bool scr_dyn_is_array_prototype(const ScrDyn *d) { + return d && d == scr_builtin_array_prototype; +} + +bool scr_dyn_array_prototype_element_fence(const ScrDyn *recv) { + if (!scr_dyn_is_array_prototype(recv)) return false; + static const char message[] = "Elements on Array.prototype are not supported yet"; + scr_throw_error_msg_code(SCR_ERR_ERROR, message, sizeof message - 1, "SC2020"); + return true; +} + /* Property names are immutable and often repeated across simultaneously live * objects. Entries own references to their bytes; this fixed weak table owns * none. Collisions only reduce sharing, and the final release removes a weak @@ -8527,6 +8538,7 @@ void scr_dyn_key_set(ScrDyn *recv, ScrStr *key, ScrDyn *value) { else idx = idx * 10 + (size_t)(key->data[i] - '0'); } if (is_index && idx < 4294967295ULL) { + if (scr_dyn_array_prototype_element_fence(recv)) return; if (recv->v.arr.frozen && idx < recv->v.arr.len) { static const char message[] = "Cannot assign to read only property"; scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1); diff --git a/tests/harness/errors.test.ts b/tests/harness/errors.test.ts index e1e2bb96c..8af6998ca 100644 --- a/tests/harness/errors.test.ts +++ b/tests/harness/errors.test.ts @@ -1727,3 +1727,53 @@ console.log(util.inspect(extra)); ); }); }); + +describe("elements on Array.prototype", () => { + // Holes of native arrays read undefined without consulting + // Array.prototype, so storing an element there refuses instead of + // letting later hole reads diverge from Node. Named members stay + // writable. + test("an index write is a named fence (TS lane)", async () => { + const r = await compileAndRun( + "array-prototype-element-ts", + `const proto = Array.prototype as unknown as Record; +proto.label = "named"; +console.log(proto.label); +(Array.prototype as unknown as Record)[1] = "proto"; +console.log("unreachable"); +`, + ); + expect(r.exitCode).toBe(1); + expect(r.stdout).toBe("named\n"); + expect(r.stderr).toMatch( + /^Uncaught Error: Elements on Array\.prototype are not supported yet \[SC2020[^\]]*\]\n$/, + ); + }); + + test("an index write and a mutating method are named fences (JS lane)", async () => { + const write = await compileAndRun( + "array-prototype-element-js", + `Array.prototype[0] = "proto"; +console.log("unreachable"); +`, + "cjs", + ); + expect(write.exitCode).toBe(1); + expect(write.stdout).toBe(""); + expect(write.stderr).toMatch( + /^Uncaught Error: Elements on Array\.prototype are not supported yet \[SC2020[^\]]*\]\n$/, + ); + const push = await compileAndRun( + "array-prototype-push-js", + `Array.prototype.push("proto"); +console.log("unreachable"); +`, + "cjs", + ); + expect(push.exitCode).toBe(1); + expect(push.stdout).toBe(""); + expect(push.stderr).toMatch( + /^Uncaught Error: Elements on Array\.prototype are not supported yet \[SC2020[^\]]*\]\n$/, + ); + }); +});