diff --git a/Libraries/LibJS/Bytecode/AsmInterpreter/AsmSlowPaths.cpp b/Libraries/LibJS/Bytecode/AsmInterpreter/AsmSlowPaths.cpp index 0b14ad49a1..7cb03aa32d 100644 --- a/Libraries/LibJS/Bytecode/AsmInterpreter/AsmSlowPaths.cpp +++ b/Libraries/LibJS/Bytecode/AsmInterpreter/AsmSlowPaths.cpp @@ -2873,11 +2873,14 @@ i64 asm_try_put_by_value_typed_array(VM* vm, u32, Op::PutByValue const* instruct if (array_length.is_auto()) [[unlikely]] return 1; + // NB: An out-of-bounds write is not simply a no-op: TypedArraySetElement still + // evaluates ToNumber(value) for its side effects before discarding the store. + // Fall back to the slow path so those side effects happen. if (index >= array_length.length()) [[unlikely]] - return 0; + return 1; if (!is_valid_integer_index(typed_array, CanonicalIndex { CanonicalIndex::Type::Index, index })) [[unlikely]] - return 0; + return 1; auto* buffer = typed_array.viewed_array_buffer(); auto* data = buffer->data() + typed_array.byte_offset(); diff --git a/Tests/LibJS/Runtime/builtins/TypedArray/typed-array-out-of-bounds-set.js b/Tests/LibJS/Runtime/builtins/TypedArray/typed-array-out-of-bounds-set.js new file mode 100644 index 0000000000..88c9241b60 --- /dev/null +++ b/Tests/LibJS/Runtime/builtins/TypedArray/typed-array-out-of-bounds-set.js @@ -0,0 +1,37 @@ +test("out-of-bounds element write still evaluates ToNumber for its side effects", () => { + function makeValue(counter) { + return { + valueOf() { + counter.count++; + return 1; + }, + }; + } + + // Direct assignment: ToNumber runs even though the store is discarded. + let counter = { count: 0 }; + let ta = new Int32Array(0); + for (let i = 0; i < 5; ++i) ta[0] = makeValue(counter); + expect(counter.count).toBe(5); + + // Reflect.set: same observable behavior. + counter = { count: 0 }; + ta = new Int32Array(0); + for (let i = 0; i < 5; ++i) expect(Reflect.set(ta, 0, makeValue(counter))).toBeTrue(); + expect(counter.count).toBe(5); + + // Reflect.defineProperty: does not evaluate ToNumber for an invalid index. + counter = { count: 0 }; + ta = new Int32Array(0); + for (let i = 0; i < 5; ++i) { + expect( + Reflect.defineProperty(ta, 0, { + value: makeValue(counter), + writable: true, + enumerable: true, + configurable: true, + }) + ).toBeFalse(); + } + expect(counter.count).toBe(0); +});