diff --git a/quickjs.c b/quickjs.c index c2dc1578c..b8777f413 100644 --- a/quickjs.c +++ b/quickjs.c @@ -60489,10 +60489,22 @@ static JSValue js_typed_array_slice(JSContext *ctx, JSValueConst this_val, if (p1 != NULL && p->class_id == p1->class_id && typed_array_length(p1) >= count && typed_array_length(p) >= start + count) { + uint8_t *src, *dst; + int nbytes; shift = typed_array_size_log2(p->class_id); - memmove(p1->u.array.u.uint8_ptr, - p->u.array.u.uint8_ptr + (start << shift), - count << shift); + src = p->u.array.u.uint8_ptr + (start << shift); + dst = p1->u.array.u.uint8_ptr; + nbytes = count << shift; + if (dst > src && dst < src + nbytes) { + /* per spec (step 14.g) the copy is performed byte by byte + in increasing order, observable when the species + constructor returns a view over the same buffer that + overlaps the source ahead of it */ + for (n = 0; n < nbytes; n++) + dst[n] = src[n]; + } else { + memmove(dst, src, nbytes); + } } else { slow_path: space = max_int(0, p->u.array.count - start); diff --git a/test262 b/test262 index de8e621cd..07dbcbca0 160000 --- a/test262 +++ b/test262 @@ -1 +1 @@ -Subproject commit de8e621cdba4f40cff3cf244e6cfb8cb48746b4a +Subproject commit 07dbcbca04c5ac73eefd752eb0a67a893c159374 diff --git a/test262_errors.txt b/test262_errors.txt index 8b6ba603b..6041da69e 100644 --- a/test262_errors.txt +++ b/test262_errors.txt @@ -17,8 +17,6 @@ test262/test/built-ins/AsyncFromSyncIteratorPrototype/throw/iterator-result-reje test262/test/built-ins/AsyncFromSyncIteratorPrototype/throw/iterator-result-rejected-promise-close.js:74: strict mode: TypeError: $DONE() not called test262/test/built-ins/AsyncFromSyncIteratorPrototype/throw/throw-result-poisoned-wrapper.js:81: TypeError: $DONE() not called test262/test/built-ins/AsyncFromSyncIteratorPrototype/throw/throw-result-poisoned-wrapper.js:81: strict mode: TypeError: $DONE() not called -test262/test/built-ins/TypedArray/prototype/slice/speciesctor-return-same-buffer-with-offset.js:24: Test262Error: Actual [20, 30, 40, 60] and expected [20, 20, 20, 60] should have the same contents. (Testing with Float64Array and makePassthrough.) -test262/test/built-ins/TypedArray/prototype/slice/speciesctor-return-same-buffer-with-offset.js:24: strict mode: Test262Error: Actual [20, 30, 40, 60] and expected [20, 20, 20, 60] should have the same contents. (Testing with Float64Array and makePassthrough.) test262/test/built-ins/TypedArrayConstructors/internals/Set/BigInt/key-is-canonical-invalid-index-prototype-chain-set.js:35: Test262Error: value should not be coerced Expected SameValue(«22», «0») to be true test262/test/built-ins/TypedArrayConstructors/internals/Set/BigInt/key-is-canonical-invalid-index-prototype-chain-set.js:35: strict mode: Test262Error: value should not be coerced Expected SameValue(«22», «0») to be true test262/test/built-ins/TypedArrayConstructors/internals/Set/BigInt/key-is-canonical-invalid-index-reflect-set.js:35: Test262Error: value should not be coerced Expected SameValue(«32», «0») to be true @@ -54,10 +52,13 @@ test262/test/language/module-code/ambiguous-export-bindings/namespace-unambiguou test262/test/language/module-code/ambiguous-export-bindings/namespace-unambiguous-if-import-star-as-and-export.js:74: SyntaxError: export 'foo' in module 'test262/test/language/module-code/ambiguous-export-bindings/nam' is ambiguous test262/test/language/module-code/top-level-await/module-graphs-does-not-hang.js:10: TypeError: $DONE() not called test262/test/language/module-code/top-level-await/rejection-order.js:20: TypeError: $DONE() not called +test262/test/language/statements/await-using/initializer-Symbol.asyncDispose-disposed-at-end-of-imported-module.js:11: SyntaxError: exported variable 'resource' does not exist +test262/test/language/statements/await-using/initializer-Symbol.dispose-disposed-at-end-of-imported-module.js:11: SyntaxError: exported variable 'resource' does not exist test262/test/language/statements/class/elements/syntax/valid/grammar-field-named-get-followed-by-generator-asi.js:40: SyntaxError: invalid property name test262/test/language/statements/class/elements/syntax/valid/grammar-field-named-get-followed-by-generator-asi.js:40: strict mode: SyntaxError: invalid property name test262/test/language/statements/class/elements/syntax/valid/grammar-field-named-set-followed-by-generator-asi.js:40: SyntaxError: invalid property name test262/test/language/statements/class/elements/syntax/valid/grammar-field-named-set-followed-by-generator-asi.js:40: strict mode: SyntaxError: invalid property name +test262/test/language/statements/using/initializer-disposed-at-end-of-imported-module.js:11: SyntaxError: exported variable 'resource' does not exist test262/test/language/statements/with/get-binding-value-call-with-proxy-env.js:39: Test262Error: Actual [has:Object, get:Symbol(Symbol.unscopables), get:Object] and expected [has:Object, get:Symbol(Symbol.unscopables), has:Object, get:Object] should have the same contents. test262/test/language/statements/with/get-binding-value-idref-with-proxy-env.js:39: Test262Error: Actual [has:Object, get:Symbol(Symbol.unscopables), get:Object] and expected [has:Object, get:Symbol(Symbol.unscopables), has:Object, get:Object] should have the same contents. test262/test/language/statements/with/get-mutable-binding-binding-deleted-in-get-unscopables-strict-mode.js:21: Test262Error: Expected a ReferenceError to be thrown but no exception was thrown at all diff --git a/tests/test_builtin.js b/tests/test_builtin.js index fb93e6206..0d41336b1 100644 --- a/tests/test_builtin.js +++ b/tests/test_builtin.js @@ -646,8 +646,11 @@ function test_typed_array() }; b = a.slice(); assert(a.buffer, b.buffer); - assert(a.toString(), "0,0,0,255"); - assert(b.toString(), "0,0,255,255"); + // the copy is byte by byte in increasing order, so bytes already + // copied are read back as source values when the target overlaps + // the source ahead of it + assert(a.toString(), "0,0,0,0"); + assert(b.toString(), "0,0,0,0"); const TypedArray = class extends Object.getPrototypeOf(Uint8Array) {}; let caught = false; diff --git a/tests/typedarray-slice-overlap.js b/tests/typedarray-slice-overlap.js new file mode 100644 index 000000000..c7943c737 --- /dev/null +++ b/tests/typedarray-slice-overlap.js @@ -0,0 +1,48 @@ +import { assert } from "./assert.js"; + +/* %TypedArray%.prototype.slice copies byte by byte in increasing order + (step 14.g). This is observable when the species constructor returns a + view over the same buffer that overlaps the source ahead of it: already + copied elements are read back as source values. */ + +function check(TA) { + const ta = new TA([10, 20, 30, 40, 50, 60]); + ta.constructor = { + [Symbol.species]: function() { + return new TA(ta.buffer, 2 * TA.BYTES_PER_ELEMENT); + } + }; + const result = ta.slice(1, 4); + assert(result.length, 4); + assert(result[0], 20); + assert(result[1], 20); + assert(result[2], 20); + assert(result[3], 60); +} + +check(Float64Array); +check(Int32Array); +check(Uint8Array); + +/* target view behind the source: plain move semantics are unaffected */ +{ + const ta = new Int32Array([10, 20, 30, 40, 50, 60]); + ta.constructor = { + [Symbol.species]: function() { + return new Int32Array(ta.buffer, 0); + } + }; + const result = ta.slice(2, 5); + assert(result[0], 30); + assert(result[1], 40); + assert(result[2], 50); +} + +/* distinct buffers keep working */ +{ + const ta = new Int32Array([1, 2, 3, 4]); + const result = ta.slice(1, 3); + assert(result.length, 2); + assert(result[0], 2); + assert(result[1], 3); +}