From f021d7714fc03400bf9772280cd21707ff13cae1 Mon Sep 17 00:00:00 2001 From: Fabrice Bellard Date: Mon, 22 Sep 2025 18:37:41 +0200 Subject: [PATCH] - added resizable array buffers (bnoordhuis) - fixed Atomics.wait, Atomics.notify, TypedArray.prototype.lastIndexOf - fixed JS_PreventExtensions() with resizable typed arrays --- TODO | 2 +- quickjs-atom.h | 1 + quickjs.c | 1004 +++++++++++++++++++++++++++++-------------- test262.conf | 2 +- test262_errors.txt | 5 - tests/test_bjson.js | 36 ++ 6 files changed, 728 insertions(+), 322 deletions(-) diff --git a/TODO b/TODO index 76d5c31..8f7dc8b 100644 --- a/TODO +++ b/TODO @@ -62,5 +62,5 @@ Optimization ideas: Test262o: 0/11262 errors, 463 excluded Test262o commit: 7da91bceb9ce7613f87db47ddd1292a2dda58b42 (es5-tests branch) -Result: 53/80840 errors, 1631 excluded, 6024 skipped +Result: 48/81760 errors, 1631 excluded, 5564 skipped Test262 commit: e7e136756cd67c1ffcf7c09d03aeb8ad5a6cec0c diff --git a/quickjs-atom.h b/quickjs-atom.h index 002e81d..b598ec6 100644 --- a/quickjs-atom.h +++ b/quickjs-atom.h @@ -191,6 +191,7 @@ DEF(timed_out, "timed-out") DEF(ok, "ok") /* */ DEF(toJSON, "toJSON") +DEF(maxByteLength, "maxByteLength") /* class names */ DEF(Object, "Object") DEF(Array, "Array") diff --git a/quickjs.c b/quickjs.c index fe67301..b0f9f00 100644 --- a/quickjs.c +++ b/quickjs.c @@ -689,6 +689,7 @@ typedef struct JSProxyData { typedef struct JSArrayBuffer { int byte_length; /* 0 if detached */ + int max_byte_length; /* -1 if not resizable; >= byte_length otherwise */ uint8_t detached; uint8_t shared; /* if shared, the array buffer cannot be detached */ uint8_t *data; /* NULL if detached */ @@ -701,8 +702,9 @@ typedef struct JSTypedArray { struct list_head link; /* link to arraybuffer */ JSObject *obj; /* back pointer to the TypedArray/DataView object */ JSObject *buffer; /* based array buffer */ - uint32_t offset; /* offset in the array buffer */ - uint32_t length; /* length in the array buffer */ + uint32_t offset; /* byte offset in the array buffer */ + uint32_t length; /* byte length in the array buffer */ + BOOL track_rab; /* auto-track length of backing array buffer */ } JSTypedArray; typedef struct JSAsyncFunctionState { @@ -1204,11 +1206,14 @@ static int js_string_memcmp(const JSString *p1, int pos1, const JSString *p2, int pos2, int len); static JSValue js_array_buffer_constructor3(JSContext *ctx, JSValueConst new_target, - uint64_t len, JSClassID class_id, + uint64_t len, uint64_t *max_len, + JSClassID class_id, uint8_t *buf, JSFreeArrayBufferDataFunc *free_func, void *opaque, BOOL alloc_flag); +static void js_array_buffer_free(JSRuntime *rt, void *opaque, void *ptr); static JSArrayBuffer *js_get_array_buffer(JSContext *ctx, JSValueConst obj); +static BOOL array_buffer_is_resizable(const JSArrayBuffer *abuf); static JSValue js_typed_array_constructor(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, @@ -1216,10 +1221,11 @@ static JSValue js_typed_array_constructor(JSContext *ctx, static JSValue js_typed_array_constructor_ta(JSContext *ctx, JSValueConst new_target, JSValueConst src_obj, - int classid); -static BOOL typed_array_is_detached(JSContext *ctx, JSObject *p); -static uint32_t typed_array_get_length(JSContext *ctx, JSObject *p); + int classid, uint32_t len); +static BOOL typed_array_is_oob(JSObject *p); +static int js_typed_array_get_length_unsafe(JSContext *ctx, JSValueConst obj); static JSValue JS_ThrowTypeErrorDetachedArrayBuffer(JSContext *ctx); +static JSValue JS_ThrowTypeErrorArrayBufferOOB(JSContext *ctx); static JSVarRef *get_var_ref(JSContext *ctx, JSStackFrame *sf, int var_idx, BOOL is_arg); static void __async_func_free(JSRuntime *rt, JSAsyncFunctionState *s); @@ -8311,9 +8317,21 @@ int JS_PreventExtensions(JSContext *ctx, JSValueConst obj) return FALSE; p = JS_VALUE_GET_OBJ(obj); if (unlikely(p->is_exotic)) { - const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; - if (em && em->prevent_extensions) { - return em->prevent_extensions(ctx, obj); + if (p->class_id >= JS_CLASS_UINT8C_ARRAY && + p->class_id <= JS_CLASS_FLOAT64_ARRAY) { + JSTypedArray *ta; + JSArrayBuffer *abuf; + /* resizable type arrays return FALSE */ + ta = p->u.typed_array; + abuf = ta->buffer->u.array_buffer; + if (ta->track_rab || + (array_buffer_is_resizable(abuf) && !abuf->shared)) + return FALSE; + } else { + const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; + if (em && em->prevent_extensions) { + return em->prevent_extensions(ctx, obj); + } } } p->extensible = FALSE; @@ -36718,6 +36736,7 @@ static int JS_WriteArrayBuffer(BCWriterState *s, JSValueConst obj) } bc_put_u8(s, BC_TAG_ARRAY_BUFFER); bc_put_leb128(s, abuf->byte_length); + bc_put_leb128(s, abuf->max_byte_length); dbuf_put(&s->dbuf, abuf->data, abuf->byte_length); return 0; } @@ -36729,6 +36748,7 @@ static int JS_WriteSharedArrayBuffer(BCWriterState *s, JSValueConst obj) assert(!abuf->detached); /* SharedArrayBuffer are never detached */ bc_put_u8(s, BC_TAG_SHARED_ARRAY_BUFFER); bc_put_leb128(s, abuf->byte_length); + bc_put_leb128(s, abuf->max_byte_length); bc_put_u64(s, (uintptr_t)abuf->data); if (js_resize_array(s->ctx, (void **)&s->sab_tab, sizeof(s->sab_tab[0]), &s->sab_tab_size, s->sab_tab_len + 1)) @@ -37790,16 +37810,31 @@ static JSValue JS_ReadTypedArray(BCReaderState *s) static JSValue JS_ReadArrayBuffer(BCReaderState *s) { JSContext *ctx = s->ctx; - uint32_t byte_length; + uint32_t byte_length, max_byte_length; + uint64_t max_byte_length_u64, *pmax_byte_length = NULL; JSValue obj; if (bc_get_leb128(s, &byte_length)) return JS_EXCEPTION; + if (bc_get_leb128(s, &max_byte_length)) + return JS_EXCEPTION; + if (max_byte_length < byte_length) + return JS_ThrowTypeError(ctx, "invalid array buffer"); + if (max_byte_length != UINT32_MAX) { + max_byte_length_u64 = max_byte_length; + pmax_byte_length = &max_byte_length_u64; + } if (unlikely(s->buf_end - s->ptr < byte_length)) { bc_read_error_end(s); return JS_EXCEPTION; } - obj = JS_NewArrayBufferCopy(ctx, s->ptr, byte_length); + // makes a copy of the input + obj = js_array_buffer_constructor3(ctx, JS_UNDEFINED, + byte_length, pmax_byte_length, + JS_CLASS_ARRAY_BUFFER, + (uint8_t*)s->ptr, + js_array_buffer_free, NULL, + /*alloc_flag*/TRUE); if (JS_IsException(obj)) goto fail; if (BC_add_object_ref(s, obj)) @@ -37814,18 +37849,28 @@ static JSValue JS_ReadArrayBuffer(BCReaderState *s) static JSValue JS_ReadSharedArrayBuffer(BCReaderState *s) { JSContext *ctx = s->ctx; - uint32_t byte_length; + uint32_t byte_length, max_byte_length; + uint64_t max_byte_length_u64, *pmax_byte_length = NULL; uint8_t *data_ptr; JSValue obj; uint64_t u64; if (bc_get_leb128(s, &byte_length)) return JS_EXCEPTION; + if (bc_get_leb128(s, &max_byte_length)) + return JS_EXCEPTION; + if (max_byte_length < byte_length) + return JS_ThrowTypeError(ctx, "invalid array buffer"); + if (max_byte_length != UINT32_MAX) { + max_byte_length_u64 = max_byte_length; + pmax_byte_length = &max_byte_length_u64; + } if (bc_get_u64(s, &u64)) return JS_EXCEPTION; data_ptr = (uint8_t *)(uintptr_t)u64; /* the SharedArrayBuffer is cloned */ - obj = js_array_buffer_constructor3(ctx, JS_UNDEFINED, byte_length, + obj = js_array_buffer_constructor3(ctx, JS_UNDEFINED, + byte_length, pmax_byte_length, JS_CLASS_SHARED_ARRAY_BUFFER, data_ptr, NULL, NULL, FALSE); @@ -40675,8 +40720,6 @@ exception: #define special_filter 4 #define special_TA 8 -static int js_typed_array_get_length_internal(JSContext *ctx, JSValueConst obj); - static JSValue js_typed_array___speciesCreate(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv); @@ -40694,7 +40737,7 @@ static JSValue js_array_every(JSContext *ctx, JSValueConst this_val, val = JS_UNDEFINED; if (special & special_TA) { obj = JS_DupValue(ctx, this_val); - len = js_typed_array_get_length_internal(ctx, obj); + len = js_typed_array_get_length_unsafe(ctx, obj); if (len < 0) goto exception; } else { @@ -40851,7 +40894,7 @@ static JSValue js_array_reduce(JSContext *ctx, JSValueConst this_val, val = JS_UNDEFINED; if (special & special_TA) { obj = JS_DupValue(ctx, this_val); - len = js_typed_array_get_length_internal(ctx, obj); + len = js_typed_array_get_length_unsafe(ctx, obj); if (len < 0) goto exception; } else { @@ -42132,8 +42175,8 @@ static JSValue js_array_iterator_next(JSContext *ctx, JSValueConst this_val, p = JS_VALUE_GET_OBJ(it->obj); if (p->class_id >= JS_CLASS_UINT8C_ARRAY && p->class_id <= JS_CLASS_FLOAT64_ARRAY) { - if (typed_array_is_detached(ctx, p)) { - JS_ThrowTypeErrorDetachedArrayBuffer(ctx); + if (typed_array_is_oob(p)) { + JS_ThrowTypeErrorArrayBufferOOB(ctx); goto fail1; } len = p->u.array.count; @@ -54166,7 +54209,8 @@ static uint8_t const typed_array_size_log2[JS_TYPED_ARRAY_COUNT] = { static JSValue js_array_buffer_constructor3(JSContext *ctx, JSValueConst new_target, - uint64_t len, JSClassID class_id, + uint64_t len, uint64_t *max_len, + JSClassID class_id, uint8_t *buf, JSFreeArrayBufferDataFunc *free_func, void *opaque, BOOL alloc_flag) @@ -54174,7 +54218,15 @@ static JSValue js_array_buffer_constructor3(JSContext *ctx, JSRuntime *rt = ctx->rt; JSValue obj; JSArrayBuffer *abuf = NULL; + uint64_t sab_alloc_len; + if (!alloc_flag && buf && max_len && free_func != js_array_buffer_free) { + // not observable from JS land, only through C API misuse; + // JS code cannot create externally managed buffers directly + return JS_ThrowInternalError(ctx, + "resizable ArrayBuffers not supported " + "for externally managed buffers"); + } obj = js_create_from_ctor(ctx, new_target, class_id); if (JS_IsException(obj)) return obj; @@ -54183,18 +54235,26 @@ static JSValue js_array_buffer_constructor3(JSContext *ctx, JS_ThrowRangeError(ctx, "invalid array buffer length"); goto fail; } + if (max_len && *max_len > INT32_MAX) { + JS_ThrowRangeError(ctx, "invalid max array buffer length"); + goto fail; + } abuf = js_malloc(ctx, sizeof(*abuf)); if (!abuf) goto fail; abuf->byte_length = len; + abuf->max_byte_length = max_len ? *max_len : -1; if (alloc_flag) { if (class_id == JS_CLASS_SHARED_ARRAY_BUFFER && rt->sab_funcs.sab_alloc) { + // TOOD(bnoordhuis) resizing backing memory for SABs atomically + // is hard so we cheat and allocate |maxByteLength| bytes upfront + sab_alloc_len = max_len ? *max_len : len; abuf->data = rt->sab_funcs.sab_alloc(rt->sab_funcs.sab_opaque, - max_int(len, 1)); + max_int(sab_alloc_len, 1)); if (!abuf->data) goto fail; - memset(abuf->data, 0, len); + memset(abuf->data, 0, sab_alloc_len); } else { /* the allocation must be done after the object creation */ abuf->data = js_mallocz(ctx, max_int(len, 1)); @@ -54230,18 +54290,19 @@ static void js_array_buffer_free(JSRuntime *rt, void *opaque, void *ptr) static JSValue js_array_buffer_constructor2(JSContext *ctx, JSValueConst new_target, - uint64_t len, JSClassID class_id) + uint64_t len, uint64_t *max_len, + JSClassID class_id) { - return js_array_buffer_constructor3(ctx, new_target, len, class_id, + return js_array_buffer_constructor3(ctx, new_target, len, max_len, class_id, NULL, js_array_buffer_free, NULL, TRUE); } static JSValue js_array_buffer_constructor1(JSContext *ctx, JSValueConst new_target, - uint64_t len) + uint64_t len, uint64_t *max_len) { - return js_array_buffer_constructor2(ctx, new_target, len, + return js_array_buffer_constructor2(ctx, new_target, len, max_len, JS_CLASS_ARRAY_BUFFER); } @@ -54249,39 +54310,70 @@ JSValue JS_NewArrayBuffer(JSContext *ctx, uint8_t *buf, size_t len, JSFreeArrayBufferDataFunc *free_func, void *opaque, BOOL is_shared) { - return js_array_buffer_constructor3(ctx, JS_UNDEFINED, len, - is_shared ? JS_CLASS_SHARED_ARRAY_BUFFER : JS_CLASS_ARRAY_BUFFER, + JSClassID class_id = + is_shared ? JS_CLASS_SHARED_ARRAY_BUFFER : JS_CLASS_ARRAY_BUFFER; + return js_array_buffer_constructor3(ctx, JS_UNDEFINED, len, NULL, class_id, buf, free_func, opaque, FALSE); } /* create a new ArrayBuffer of length 'len' and copy 'buf' to it */ JSValue JS_NewArrayBufferCopy(JSContext *ctx, const uint8_t *buf, size_t len) { - return js_array_buffer_constructor3(ctx, JS_UNDEFINED, len, + return js_array_buffer_constructor3(ctx, JS_UNDEFINED, len, NULL, JS_CLASS_ARRAY_BUFFER, (uint8_t *)buf, js_array_buffer_free, NULL, TRUE); } +static JSValue js_array_buffer_constructor0(JSContext *ctx, JSValueConst new_target, + int argc, JSValueConst *argv, + JSClassID class_id) + { + uint64_t len, max_len, *pmax_len = NULL; + JSValue obj, val; + int64_t i; + + if (JS_ToIndex(ctx, &len, argv[0])) + return JS_EXCEPTION; + if (argc < 2) + goto next; + if (!JS_IsObject(argv[1])) + goto next; + obj = JS_ToObject(ctx, argv[1]); + if (JS_IsException(obj)) + return JS_EXCEPTION; + val = JS_GetProperty(ctx, obj, JS_ATOM_maxByteLength); + JS_FreeValue(ctx, obj); + if (JS_IsException(val)) + return JS_EXCEPTION; + if (JS_IsUndefined(val)) + goto next; + if (JS_ToInt64Free(ctx, &i, val)) + return JS_EXCEPTION; + // don't have to check i < 0 because len >= 0 + if (len > i || i > MAX_SAFE_INTEGER) + return JS_ThrowRangeError(ctx, "invalid array buffer max length"); + max_len = i; + pmax_len = &max_len; +next: + return js_array_buffer_constructor2(ctx, new_target, len, pmax_len, + class_id); +} + static JSValue js_array_buffer_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv) { - uint64_t len; - if (JS_ToIndex(ctx, &len, argv[0])) - return JS_EXCEPTION; - return js_array_buffer_constructor1(ctx, new_target, len); + return js_array_buffer_constructor0(ctx, new_target, argc, argv, + JS_CLASS_ARRAY_BUFFER); } static JSValue js_shared_array_buffer_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv) { - uint64_t len; - if (JS_ToIndex(ctx, &len, argv[0])) - return JS_EXCEPTION; - return js_array_buffer_constructor2(ctx, new_target, len, + return js_array_buffer_constructor0(ctx, new_target, argc, argv, JS_CLASS_SHARED_ARRAY_BUFFER); } @@ -54347,6 +54439,11 @@ static JSValue JS_ThrowTypeErrorDetachedArrayBuffer(JSContext *ctx) return JS_ThrowTypeError(ctx, "ArrayBuffer is detached"); } +static JSValue JS_ThrowTypeErrorArrayBufferOOB(JSContext *ctx) +{ + return JS_ThrowTypeError(ctx, "ArrayBuffer is detached or resized"); +} + // #sec-get-arraybuffer.prototype.detached static JSValue js_array_buffer_get_detached(JSContext *ctx, JSValueConst this_val) @@ -54370,6 +54467,28 @@ static JSValue js_array_buffer_get_byteLength(JSContext *ctx, return JS_NewUint32(ctx, abuf->byte_length); } +static JSValue js_array_buffer_get_maxByteLength(JSContext *ctx, + JSValueConst this_val, + int class_id) +{ + JSArrayBuffer *abuf = JS_GetOpaque2(ctx, this_val, class_id); + if (!abuf) + return JS_EXCEPTION; + if (array_buffer_is_resizable(abuf)) + return JS_NewUint32(ctx, abuf->max_byte_length); + return JS_NewUint32(ctx, abuf->byte_length); +} + +static JSValue js_array_buffer_get_resizable(JSContext *ctx, + JSValueConst this_val, + int class_id) +{ + JSArrayBuffer *abuf = JS_GetOpaque2(ctx, this_val, class_id); + if (!abuf) + return JS_EXCEPTION; + return JS_NewBool(ctx, array_buffer_is_resizable(abuf)); +} + void JS_DetachArrayBuffer(JSContext *ctx, JSValueConst obj) { JSArrayBuffer *abuf = JS_GetOpaque(obj, JS_CLASS_ARRAY_BUFFER); @@ -54431,13 +54550,19 @@ uint8_t *JS_GetArrayBuffer(JSContext *ctx, size_t *psize, JSValueConst obj) return NULL; } +static BOOL array_buffer_is_resizable(const JSArrayBuffer *abuf) +{ + return abuf->max_byte_length >= 0; +} + // ES #sec-arraybuffer.prototype.transfer static JSValue js_array_buffer_transfer(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) + int argc, JSValueConst *argv, + int transfer_to_fixed_length) { JSArrayBuffer *abuf; - uint64_t new_len; + uint64_t new_len, *pmax_len, max_len; abuf = JS_GetOpaque2(ctx, this_val, JS_CLASS_ARRAY_BUFFER); if (!abuf) @@ -54450,10 +54575,21 @@ static JSValue js_array_buffer_transfer(JSContext *ctx, return JS_EXCEPTION; if (abuf->detached) return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); + pmax_len = NULL; + if (!transfer_to_fixed_length) { + if (array_buffer_is_resizable(abuf)) { // carry over maxByteLength + max_len = abuf->max_byte_length; + if (new_len > max_len) + return JS_ThrowTypeError(ctx, "invalid array buffer length"); + // TODO(bnoordhuis) support externally managed RABs + if (abuf->free_func == js_array_buffer_free) + pmax_len = &max_len; + } + } /* create an empty AB */ if (new_len == 0) { JS_DetachArrayBuffer(ctx, this_val); - return js_array_buffer_constructor2(ctx, JS_UNDEFINED, 0, JS_CLASS_ARRAY_BUFFER); + return js_array_buffer_constructor2(ctx, JS_UNDEFINED, 0, pmax_len, JS_CLASS_ARRAY_BUFFER); } else { uint64_t old_len; uint8_t *bs, *new_bs; @@ -54492,13 +54628,89 @@ static JSValue js_array_buffer_transfer(JSContext *ctx, abuf->data = NULL; abuf->byte_length = 0; abuf->detached = TRUE; - return js_array_buffer_constructor3(ctx, JS_UNDEFINED, new_len, + return js_array_buffer_constructor3(ctx, JS_UNDEFINED, new_len, pmax_len, JS_CLASS_ARRAY_BUFFER, bs, free_func, NULL, FALSE); } } +static JSValue js_array_buffer_resize(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int class_id) +{ + uint32_t size_log2, size_elem; + struct list_head *el; + JSArrayBuffer *abuf; + JSTypedArray *ta; + JSObject *p; + uint8_t *data; + int64_t len; + + abuf = JS_GetOpaque2(ctx, this_val, class_id); + if (!abuf) + return JS_EXCEPTION; + if (JS_ToInt64(ctx, &len, argv[0])) + return JS_EXCEPTION; + if (abuf->detached) + return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); + if (!array_buffer_is_resizable(abuf)) + return JS_ThrowTypeError(ctx, "array buffer is not resizable"); + // TODO(bnoordhuis) support externally managed RABs + if (abuf->free_func != js_array_buffer_free) + return JS_ThrowTypeError(ctx, "external array buffer is not resizable"); + if (len < 0 || len > abuf->max_byte_length) { + bad_length: + return JS_ThrowRangeError(ctx, "invalid array buffer length"); + } + // SABs can only grow and we don't need to realloc because + // js_array_buffer_constructor3 commits all memory upfront; + // regular RABs are resizable both ways and realloc + if (abuf->shared) { + if (len < abuf->byte_length) + goto bad_length; + // Note this is off-spec; there's supposed to be a single atomic + // |byteLength| property that's shared across SABs but we store + // it per SAB instead. That means when thread A calls sab.grow(2) + // at time t0, and thread B calls sab.grow(1) at time t1, we don't + // throw a TypeError in thread B as the spec says we should, + // instead both threads get their own view of the backing memory, + // 2 bytes big in A, and 1 byte big in B + abuf->byte_length = len; + } else { + data = js_realloc(ctx, abuf->data, max_int(len, 1)); + if (!data) + return JS_EXCEPTION; + if (len > abuf->byte_length) + memset(&data[abuf->byte_length], 0, len - abuf->byte_length); + abuf->byte_length = len; + abuf->data = data; + } + data = abuf->data; + // update lengths of all typed arrays backed by this array buffer + list_for_each(el, &abuf->array_list) { + ta = list_entry(el, JSTypedArray, link); + p = ta->obj; + if (p->class_id == JS_CLASS_DATAVIEW) + continue; + p->u.array.count = 0; + p->u.array.u.ptr = NULL; + size_log2 = typed_array_size_log2(p->class_id); + size_elem = 1 << size_log2; + if (ta->track_rab) { + if (len >= (int64_t)ta->offset + size_elem) { + p->u.array.count = (len - ta->offset) >> size_log2; + p->u.array.u.ptr = &data[ta->offset]; + } + } else { + if (len >= (int64_t)ta->offset + ta->length) { + p->u.array.count = ta->length >> size_log2; + p->u.array.u.ptr = &data[ta->offset]; + } + } + } + return JS_UNDEFINED; +} + static JSValue js_array_buffer_slice(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int class_id) @@ -54528,7 +54740,7 @@ static JSValue js_array_buffer_slice(JSContext *ctx, return ctor; if (JS_IsUndefined(ctor)) { new_obj = js_array_buffer_constructor2(ctx, JS_UNDEFINED, new_len, - class_id); + NULL, class_id); } else { JSValue args[1]; args[0] = JS_NewInt64(ctx, new_len); @@ -54567,10 +54779,13 @@ static JSValue js_array_buffer_slice(JSContext *ctx, static const JSCFunctionListEntry js_array_buffer_proto_funcs[] = { JS_CGETSET_MAGIC_DEF("byteLength", js_array_buffer_get_byteLength, NULL, JS_CLASS_ARRAY_BUFFER ), + JS_CGETSET_MAGIC_DEF("maxByteLength", js_array_buffer_get_maxByteLength, NULL, JS_CLASS_ARRAY_BUFFER ), + JS_CGETSET_MAGIC_DEF("resizable", js_array_buffer_get_resizable, NULL, JS_CLASS_ARRAY_BUFFER ), JS_CGETSET_DEF("detached", js_array_buffer_get_detached, NULL ), + JS_CFUNC_MAGIC_DEF("resize", 1, js_array_buffer_resize, JS_CLASS_ARRAY_BUFFER ), JS_CFUNC_MAGIC_DEF("slice", 2, js_array_buffer_slice, JS_CLASS_ARRAY_BUFFER ), - JS_CFUNC_DEF("transfer", 0, js_array_buffer_transfer ), - JS_CFUNC_DEF("transferToFixedLength", 0, js_array_buffer_transfer ), + JS_CFUNC_MAGIC_DEF("transfer", 0, js_array_buffer_transfer, 0 ), + JS_CFUNC_MAGIC_DEF("transferToFixedLength", 0, js_array_buffer_transfer, 1 ), JS_PROP_STRING_DEF("[Symbol.toStringTag]", "ArrayBuffer", JS_PROP_CONFIGURABLE ), }; @@ -54582,59 +54797,85 @@ static const JSCFunctionListEntry js_shared_array_buffer_funcs[] = { static const JSCFunctionListEntry js_shared_array_buffer_proto_funcs[] = { JS_CGETSET_MAGIC_DEF("byteLength", js_array_buffer_get_byteLength, NULL, JS_CLASS_SHARED_ARRAY_BUFFER ), + JS_CGETSET_MAGIC_DEF("maxByteLength", js_array_buffer_get_maxByteLength, NULL, JS_CLASS_SHARED_ARRAY_BUFFER ), + JS_CGETSET_MAGIC_DEF("growable", js_array_buffer_get_resizable, NULL, JS_CLASS_SHARED_ARRAY_BUFFER ), + JS_CFUNC_MAGIC_DEF("grow", 1, js_array_buffer_resize, JS_CLASS_SHARED_ARRAY_BUFFER ), JS_CFUNC_MAGIC_DEF("slice", 2, js_array_buffer_slice, JS_CLASS_SHARED_ARRAY_BUFFER ), JS_PROP_STRING_DEF("[Symbol.toStringTag]", "SharedArrayBuffer", JS_PROP_CONFIGURABLE ), }; -static JSObject *get_typed_array(JSContext *ctx, - JSValueConst this_val, - int is_dataview) +static JSObject *get_typed_array(JSContext *ctx, JSValueConst this_val) { JSObject *p; if (JS_VALUE_GET_TAG(this_val) != JS_TAG_OBJECT) goto fail; p = JS_VALUE_GET_OBJ(this_val); - if (is_dataview) { - if (p->class_id != JS_CLASS_DATAVIEW) - goto fail; - } else { - if (!(p->class_id >= JS_CLASS_UINT8C_ARRAY && - p->class_id <= JS_CLASS_FLOAT64_ARRAY)) { - fail: - JS_ThrowTypeError(ctx, "not a %s", is_dataview ? "DataView" : "TypedArray"); - return NULL; - } + if (!(p->class_id >= JS_CLASS_UINT8C_ARRAY && + p->class_id <= JS_CLASS_FLOAT64_ARRAY)) { + fail: + JS_ThrowTypeError(ctx, "not a TypedArray"); + return NULL; } return p; } -/* WARNING: 'p' must be a typed array */ -static BOOL typed_array_is_detached(JSContext *ctx, JSObject *p) +// is the typed array detached or out of bounds relative to its RAB? +// |p| must be a typed array, *not* a DataView +static BOOL typed_array_is_oob(JSObject *p) { - JSTypedArray *ta = p->u.typed_array; - JSArrayBuffer *abuf = ta->buffer->u.array_buffer; - /* XXX: could simplify test by ensuring that - p->u.array.u.ptr is NULL iff it is detached */ - return abuf->detached; + JSArrayBuffer *abuf; + JSTypedArray *ta; + int len, size_elem; + int64_t end; + + assert(p->class_id >= JS_CLASS_UINT8C_ARRAY); + assert(p->class_id <= JS_CLASS_FLOAT64_ARRAY); + + ta = p->u.typed_array; + abuf = ta->buffer->u.array_buffer; + if (abuf->detached) + return TRUE; + len = abuf->byte_length; + if (ta->offset > len) + return TRUE; + if (ta->track_rab) + return FALSE; + if (len < (int64_t)ta->offset + ta->length) + return TRUE; + size_elem = 1 << typed_array_size_log2(p->class_id); + end = (int64_t)ta->offset + (int64_t)p->u.array.count * size_elem; + return end > len; } -/* WARNING: 'p' must be a typed array. Works even if the array buffer - is detached */ -static uint32_t typed_array_get_length(JSContext *ctx, JSObject *p) +// Be *very* careful if you touch the typed array's memory directly: +// the length is only valid until the next call into JS land because +// JS code can detach or resize the backing array buffer. Functions +// like JS_GetProperty and JS_ToIndex call JS code. +// +// Exclusively reading or writing elements with JS_GetProperty, +// JS_GetPropertyInt64, JS_SetProperty, etc. is safe because they +// perform bounds checks, as does js_get_fast_array_element. +static int js_typed_array_get_length_unsafe(JSContext *ctx, JSValueConst obj) { - JSTypedArray *ta = p->u.typed_array; - int size_log2 = typed_array_size_log2(p->class_id); - return ta->length >> size_log2; + JSObject *p; + p = get_typed_array(ctx, obj); + if (!p) + return -1; + if (typed_array_is_oob(p)) { + JS_ThrowTypeErrorArrayBufferOOB(ctx); + return -1; + } + return p->u.array.count; } static int validate_typed_array(JSContext *ctx, JSValueConst this_val) { JSObject *p; - p = get_typed_array(ctx, this_val, 0); + p = get_typed_array(ctx, this_val); if (!p) return -1; - if (typed_array_is_detached(ctx, p)) { - JS_ThrowTypeErrorDetachedArrayBuffer(ctx); + if (typed_array_is_oob(p)) { + JS_ThrowTypeErrorArrayBufferOOB(ctx); return -1; } return 0; @@ -54644,18 +54885,18 @@ static JSValue js_typed_array_get_length(JSContext *ctx, JSValueConst this_val) { JSObject *p; - p = get_typed_array(ctx, this_val, 0); + p = get_typed_array(ctx, this_val); if (!p) return JS_EXCEPTION; return JS_NewInt32(ctx, p->u.array.count); } static JSValue js_typed_array_get_buffer(JSContext *ctx, - JSValueConst this_val, int is_dataview) + JSValueConst this_val) { JSObject *p; JSTypedArray *ta; - p = get_typed_array(ctx, this_val, is_dataview); + p = get_typed_array(ctx, this_val); if (!p) return JS_EXCEPTION; ta = p->u.typed_array; @@ -54663,43 +54904,36 @@ static JSValue js_typed_array_get_buffer(JSContext *ctx, } static JSValue js_typed_array_get_byteLength(JSContext *ctx, - JSValueConst this_val, - int is_dataview) + JSValueConst this_val) { JSObject *p; JSTypedArray *ta; - p = get_typed_array(ctx, this_val, is_dataview); + int size_log2; + + p = get_typed_array(ctx, this_val); if (!p) return JS_EXCEPTION; - if (typed_array_is_detached(ctx, p)) { - if (is_dataview) { - return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); - } else { - return JS_NewInt32(ctx, 0); - } - } + if (typed_array_is_oob(p)) + return JS_NewInt32(ctx, 0); ta = p->u.typed_array; - return JS_NewInt32(ctx, ta->length); + if (!ta->track_rab) + return JS_NewUint32(ctx, ta->length); + size_log2 = typed_array_size_log2(p->class_id); + return JS_NewInt64(ctx, (int64_t)p->u.array.count << size_log2); } static JSValue js_typed_array_get_byteOffset(JSContext *ctx, - JSValueConst this_val, - int is_dataview) + JSValueConst this_val) { JSObject *p; JSTypedArray *ta; - p = get_typed_array(ctx, this_val, is_dataview); + p = get_typed_array(ctx, this_val); if (!p) return JS_EXCEPTION; - if (typed_array_is_detached(ctx, p)) { - if (is_dataview) { - return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); - } else { - return JS_NewInt32(ctx, 0); - } - } + if (typed_array_is_oob(p)) + return JS_NewInt32(ctx, 0); ta = p->u.typed_array; - return JS_NewInt32(ctx, ta->offset); + return JS_NewUint32(ctx, ta->offset); } JSValue JS_NewTypedArray(JSContext *ctx, int argc, JSValueConst *argv, @@ -54722,11 +54956,11 @@ JSValue JS_GetTypedArrayBuffer(JSContext *ctx, JSValueConst obj, { JSObject *p; JSTypedArray *ta; - p = get_typed_array(ctx, obj, FALSE); + p = get_typed_array(ctx, obj); if (!p) return JS_EXCEPTION; - if (typed_array_is_detached(ctx, p)) - return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); + if (typed_array_is_oob(p)) + return JS_ThrowTypeErrorArrayBufferOOB(ctx); ta = p->u.typed_array; if (pbyte_offset) *pbyte_offset = ta->offset; @@ -54759,21 +54993,22 @@ static JSValue js_typed_array_set_internal(JSContext *ctx, JSObject *p; JSObject *src_p; uint32_t i; - int64_t src_len, offset; + int64_t dst_len, src_len, offset; JSValue val, src_obj = JS_UNDEFINED; - p = get_typed_array(ctx, dst, 0); + p = get_typed_array(ctx, dst); if (!p) goto fail; if (JS_ToInt64Sat(ctx, &offset, off)) goto fail; if (offset < 0) goto range_error; - if (typed_array_is_detached(ctx, p)) { + if (typed_array_is_oob(p)) { detached: - JS_ThrowTypeErrorDetachedArrayBuffer(ctx); + JS_ThrowTypeErrorArrayBufferOOB(ctx); goto fail; } + dst_len = p->u.array.count; src_obj = JS_ToObject(ctx, src); if (JS_IsException(src_obj)) goto fail; @@ -54786,11 +55021,11 @@ static JSValue js_typed_array_set_internal(JSContext *ctx, JSArrayBuffer *src_abuf = src_ta->buffer->u.array_buffer; int shift = typed_array_size_log2(p->class_id); - if (src_abuf->detached) + if (typed_array_is_oob(src_p)) goto detached; src_len = src_p->u.array.count; - if (offset > (int64_t)(p->u.array.count - src_len)) + if (offset > dst_len - src_len) goto range_error; /* copying between typed objects */ @@ -54806,9 +55041,11 @@ static JSValue js_typed_array_set_internal(JSContext *ctx, } /* otherwise, default behavior is slow but correct */ } else { + // can change |dst| as a side effect; per spec, + // perform the range check against its old length if (js_get_length64(ctx, &src_len, src_obj)) goto fail; - if (offset > (int64_t)(p->u.array.count - src_len)) { + if (offset > dst_len - src_len) { range_error: JS_ThrowRangeError(ctx, "invalid array length"); goto fail; @@ -54835,21 +55072,22 @@ static JSValue js_typed_array_at(JSContext *ctx, JSValueConst this_val, JSObject *p; int64_t idx, len; - p = get_typed_array(ctx, this_val, 0); + p = get_typed_array(ctx, this_val); if (!p) return JS_EXCEPTION; - if (typed_array_is_detached(ctx, p)) { - JS_ThrowTypeErrorDetachedArrayBuffer(ctx); - return JS_EXCEPTION; - } + if (typed_array_is_oob(p)) + return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); + len = p->u.array.count; + // note: can change p->u.array.count if (JS_ToInt64Sat(ctx, &idx, argv[0])) return JS_EXCEPTION; - len = p->u.array.count; if (idx < 0) idx = len + idx; + + len = p->u.array.count; if (idx < 0 || idx >= len) return JS_UNDEFINED; return JS_GetPropertyInt64(ctx, this_val, idx); @@ -54862,16 +55100,16 @@ static JSValue js_typed_array_with(JSContext *ctx, JSValueConst this_val, JSObject *p; int64_t idx, len; - p = get_typed_array(ctx, this_val, /*is_dataview*/0); + p = get_typed_array(ctx, this_val); if (!p) return JS_EXCEPTION; - if (typed_array_is_detached(ctx, p)) + if (typed_array_is_oob(p)) return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); + len = p->u.array.count; if (JS_ToInt64Sat(ctx, &idx, argv[0])) return JS_EXCEPTION; - len = p->u.array.count; if (idx < 0) idx = len + idx; @@ -54879,11 +55117,11 @@ static JSValue js_typed_array_with(JSContext *ctx, JSValueConst this_val, if (JS_IsException(val)) return JS_EXCEPTION; - if (typed_array_is_detached(ctx, p) || idx < 0 || idx >= len) + if (typed_array_is_oob(p) || idx < 0 || idx >= p->u.array.count) return JS_ThrowRangeError(ctx, "invalid array index"); arr = js_typed_array_constructor_ta(ctx, JS_UNDEFINED, this_val, - p->class_id); + p->class_id, len); if (JS_IsException(arr)) { JS_FreeValue(ctx, val); return JS_EXCEPTION; @@ -54914,41 +55152,6 @@ static JSValue js_create_typed_array_iterator(JSContext *ctx, JSValueConst this_ return js_create_array_iterator(ctx, this_val, argc, argv, magic); } -/* return < 0 if exception */ -static int js_typed_array_get_length_internal(JSContext *ctx, - JSValueConst obj) -{ - JSObject *p; - p = get_typed_array(ctx, obj, 0); - if (!p) - return -1; - if (typed_array_is_detached(ctx, p)) { - JS_ThrowTypeErrorDetachedArrayBuffer(ctx); - return -1; - } - return p->u.array.count; -} - -#if 0 -/* validate a typed array and return its length */ -static JSValue js_typed_array___getLength(JSContext *ctx, - JSValueConst this_val, - int argc, JSValueConst *argv) -{ - BOOL ignore_detached = JS_ToBool(ctx, argv[1]); - - if (ignore_detached) { - return js_typed_array_get_length(ctx, argv[0]); - } else { - int len; - len = js_typed_array_get_length_internal(ctx, argv[0]); - if (len < 0) - return JS_EXCEPTION; - return JS_NewInt32(ctx, len); - } -} -#endif - static JSValue js_typed_array_create(JSContext *ctx, JSValueConst ctor, int argc, JSValueConst *argv) { @@ -54960,7 +55163,7 @@ static JSValue js_typed_array_create(JSContext *ctx, JSValueConst ctor, if (JS_IsException(ret)) return ret; /* validate the typed array */ - new_len = js_typed_array_get_length_internal(ctx, ret); + new_len = js_typed_array_get_length_unsafe(ctx, ret); if (new_len < 0) goto fail; if (argc == 1) { @@ -54996,7 +55199,7 @@ static JSValue js_typed_array___speciesCreate(JSContext *ctx, int argc1; obj = argv[0]; - p = get_typed_array(ctx, obj, 0); + p = get_typed_array(ctx, obj); if (!p) return JS_EXCEPTION; ctor = JS_SpeciesConstructor(ctx, obj, JS_UNDEFINED); @@ -55117,11 +55320,14 @@ static JSValue js_typed_array_copyWithin(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSObject *p; - int len, to, from, final, count, shift; + int len, to, from, final, count, shift, space; - len = js_typed_array_get_length_internal(ctx, this_val); - if (len < 0) + p = get_typed_array(ctx, this_val); + if (!p) return JS_EXCEPTION; + if (typed_array_is_oob(p)) + return JS_ThrowTypeErrorArrayBufferOOB(ctx); + len = p->u.array.count; if (JS_ToInt32Clamp(ctx, &to, argv[0], 0, len, len)) return JS_EXCEPTION; @@ -55135,11 +55341,14 @@ static JSValue js_typed_array_copyWithin(JSContext *ctx, JSValueConst this_val, return JS_EXCEPTION; } + if (typed_array_is_oob(p)) + return JS_ThrowTypeErrorArrayBufferOOB(ctx); + + // RAB may have been resized by evil .valueOf method + space = p->u.array.count - max_int(to, from); count = min_int(final - from, len - to); + count = min_int(count, space); if (count > 0) { - p = JS_VALUE_GET_OBJ(this_val); - if (typed_array_is_detached(ctx, p)) - return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); shift = typed_array_size_log2(p->class_id); memmove(p->u.array.u.uint8_ptr + (to << shift), p->u.array.u.uint8_ptr + (from << shift), @@ -55155,10 +55364,12 @@ static JSValue js_typed_array_fill(JSContext *ctx, JSValueConst this_val, int len, k, final, shift; uint64_t v64; - len = js_typed_array_get_length_internal(ctx, this_val); - if (len < 0) + p = get_typed_array(ctx, this_val); + if (!p) return JS_EXCEPTION; - p = JS_VALUE_GET_OBJ(this_val); + if (typed_array_is_oob(p)) + return JS_ThrowTypeErrorArrayBufferOOB(ctx); + len = p->u.array.count; if (p->class_id == JS_CLASS_UINT8C_ARRAY) { int32_t v; @@ -55205,9 +55416,11 @@ static JSValue js_typed_array_fill(JSContext *ctx, JSValueConst this_val, return JS_EXCEPTION; } - if (typed_array_is_detached(ctx, p)) - return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); + if (typed_array_is_oob(p)) + return JS_ThrowTypeErrorArrayBufferOOB(ctx); + // RAB may have been resized by evil .valueOf method + final = min_int(final, p->u.array.count); shift = typed_array_size_log2(p->class_id); switch(shift) { case 0: @@ -55246,7 +55459,7 @@ static JSValue js_typed_array_find(JSContext *ctx, JSValueConst this_val, int dir; val = JS_UNDEFINED; - len = js_typed_array_get_length_internal(ctx, this_val); + len = js_typed_array_get_length_unsafe(ctx, this_val); if (len < 0) goto exception; @@ -55312,9 +55525,13 @@ static JSValue js_typed_array_indexOf(JSContext *ctx, JSValueConst this_val, float f; uint16_t hf; - len = js_typed_array_get_length_internal(ctx, this_val); - if (len < 0) - goto exception; + p = get_typed_array(ctx, this_val); + if (!p) + return JS_EXCEPTION; + if (typed_array_is_oob(p)) + return JS_ThrowTypeErrorArrayBufferOOB(ctx); + len = p->u.array.count; + if (len == 0) goto done; @@ -55340,16 +55557,23 @@ static JSValue js_typed_array_indexOf(JSContext *ctx, JSValueConst this_val, inc = 1; } - p = JS_VALUE_GET_OBJ(this_val); - /* if the array was detached, no need to go further (but no - exception is raised) */ - if (typed_array_is_detached(ctx, p)) { - /* "includes" scans all the properties, so "undefined" can match */ - if (special == special_includes && JS_IsUndefined(argv[0]) && len > 0) - res = 0; + /* includes function: 'undefined' can be found if searching out of bounds */ + if (len > p->u.array.count && special == special_includes && + JS_IsUndefined(argv[0]) && k < len) { + res = 0; goto done; } + // RAB may have been resized by evil .valueOf method + len = min_int(len, p->u.array.count); + if (len == 0) + goto done; + if (special == special_lastIndexOf) + k = min_int(k, len - 1); + else + k = min_int(k, len); + stop = min_int(stop, len); + is_bigint = 0; is_int = 0; /* avoid warning */ v64 = 0; /* avoid warning */ @@ -55412,7 +55636,9 @@ static JSValue js_typed_array_indexOf(JSContext *ctx, JSValueConst this_val, pv = p->u.array.u.uint8_ptr; v = v64; if (inc > 0) { - pp = memchr(pv + k, v, len - k); + pp = NULL; + if (pv) + pp = memchr(pv + k, v, len - k); if (pp) res = pp - pv; } else { @@ -55581,35 +55807,42 @@ static JSValue js_typed_array_join(JSContext *ctx, JSValueConst this_val, { JSValue sep = JS_UNDEFINED, el; StringBuffer b_s, *b = &b_s; - JSString *p = NULL; - int i, n; + JSString *s = NULL; + JSObject *p; + int i, len, oldlen, newlen; int c; - n = js_typed_array_get_length_internal(ctx, this_val); - if (n < 0) - goto exception; + p = get_typed_array(ctx, this_val); + if (!p) + return JS_EXCEPTION; + if (typed_array_is_oob(p)) + return JS_ThrowTypeErrorArrayBufferOOB(ctx); + len = oldlen = newlen = p->u.array.count; c = ','; /* default separator */ if (!toLocaleString && argc > 0 && !JS_IsUndefined(argv[0])) { sep = JS_ToString(ctx, argv[0]); if (JS_IsException(sep)) goto exception; - p = JS_VALUE_GET_STRING(sep); - if (p->len == 1 && !p->is_wide_char) - c = p->u.str8[0]; + s = JS_VALUE_GET_STRING(sep); + if (s->len == 1 && !s->is_wide_char) + c = s->u.str8[0]; else c = -1; + // ToString(sep) can detach or resize the arraybuffer as a side effect + newlen = p->u.array.count; + len = min_int(len, newlen); } string_buffer_init(ctx, b, 0); /* XXX: optimize with direct access */ - for(i = 0; i < n; i++) { + for(i = 0; i < len; i++) { if (i > 0) { if (c >= 0) { if (string_buffer_putc8(b, c)) goto fail; } else { - if (string_buffer_concat(b, p, 0, p->len)) + if (string_buffer_concat(b, s, 0, s->len)) goto fail; } } @@ -55625,6 +55858,19 @@ static JSValue js_typed_array_join(JSContext *ctx, JSValueConst this_val, goto fail; } } + + // add extra separators in case RAB was resized by evil .valueOf method + i = max_int(1, newlen); + for(/*empty*/; i < oldlen; i++) { + if (c >= 0) { + if (string_buffer_putc8(b, c)) + goto fail; + } else { + if (string_buffer_concat(b, s, 0, s->len)) + goto fail; + } + } + JS_FreeValue(ctx, sep); return string_buffer_end(b); @@ -55641,7 +55887,7 @@ static JSValue js_typed_array_reverse(JSContext *ctx, JSValueConst this_val, JSObject *p; int len; - len = js_typed_array_get_length_internal(ctx, this_val); + len = js_typed_array_get_length_unsafe(ctx, this_val); if (len < 0) return JS_EXCEPTION; if (len > 0) { @@ -55704,11 +55950,11 @@ static JSValue js_typed_array_toReversed(JSContext *ctx, JSValueConst this_val, JSValue arr, ret; JSObject *p; - p = get_typed_array(ctx, this_val, /*is_dataview*/0); + p = get_typed_array(ctx, this_val); if (!p) return JS_EXCEPTION; arr = js_typed_array_constructor_ta(ctx, JS_UNDEFINED, this_val, - p->class_id); + p->class_id, p->u.array.count); if (JS_IsException(arr)) return JS_EXCEPTION; ret = js_typed_array_reverse(ctx, arr, argc, argv); @@ -55734,12 +55980,15 @@ static JSValue js_typed_array_slice(JSContext *ctx, JSValueConst this_val, JSValueConst args[2]; JSValue arr, val; JSObject *p, *p1; - int n, len, start, final, count, shift; + int n, len, start, final, count, shift, space; arr = JS_UNDEFINED; - len = js_typed_array_get_length_internal(ctx, this_val); - if (len < 0) + p = get_typed_array(ctx, this_val); + if (!p) goto exception; + if (typed_array_is_oob(p)) + return JS_ThrowTypeErrorArrayBufferOOB(ctx); + len = p->u.array.count; if (JS_ToInt32Clamp(ctx, &start, argv[0], 0, len, len)) goto exception; @@ -55750,9 +55999,6 @@ static JSValue js_typed_array_slice(JSContext *ctx, JSValueConst this_val, } count = max_int(final - start, 0); - p = get_typed_array(ctx, this_val, 0); - if (p == NULL) - goto exception; shift = typed_array_size_log2(p->class_id); args[0] = this_val; @@ -55766,10 +56012,10 @@ static JSValue js_typed_array_slice(JSContext *ctx, JSValueConst this_val, || validate_typed_array(ctx, arr)) goto exception; - p1 = get_typed_array(ctx, arr, 0); - if (p1 != NULL && p->class_id == p1->class_id && - typed_array_get_length(ctx, p1) >= count && - typed_array_get_length(ctx, p) >= start + count) { + p1 = get_typed_array(ctx, arr); + space = max_int(0, p->u.array.count - start); + count = min_int(count, space); + if (p1 != NULL && p->class_id == p1->class_id) { slice_memcpy(p1->u.array.u.uint8_ptr, p->u.array.u.uint8_ptr + (start << shift), count << shift); @@ -55799,8 +56045,9 @@ static JSValue js_typed_array_subarray(JSContext *ctx, JSValueConst this_val, JSTypedArray *ta; JSObject *p; int len, start, final, count, shift, offset; - - p = get_typed_array(ctx, this_val, 0); + BOOL is_auto; + + p = get_typed_array(ctx, this_val); if (!p) goto exception; len = p->u.array.count; @@ -55813,19 +56060,22 @@ static JSValue js_typed_array_subarray(JSContext *ctx, JSValueConst this_val, offset = ta->offset + (start << shift); final = len; - if (!JS_IsUndefined(argv[1])) { + if (JS_IsUndefined(argv[1])) { + is_auto = ta->track_rab; + } else { + is_auto = FALSE; if (JS_ToInt32Clamp(ctx, &final, argv[1], 0, len, len)) goto exception; - } + } count = max_int(final - start, 0); - ta_buffer = js_typed_array_get_buffer(ctx, this_val, 0); + ta_buffer = js_typed_array_get_buffer(ctx, this_val); if (JS_IsException(ta_buffer)) goto exception; args[0] = this_val; args[1] = ta_buffer; args[2] = JS_NewInt32(ctx, offset); args[3] = JS_NewInt32(ctx, count); - arr = js_typed_array___speciesCreate(ctx, JS_UNDEFINED, 4, args); + arr = js_typed_array___speciesCreate(ctx, JS_UNDEFINED, is_auto ? 3 : 4, args); JS_FreeValue(ctx, ta_buffer); return arr; @@ -55949,7 +56199,6 @@ struct TA_sort_context { JSValueConst arr; JSValueConst cmp; JSValue (*getfun)(JSContext *ctx, const void *a); - uint8_t *array_ptr; /* cannot change unless the array is detached */ int elt_size; }; @@ -55960,16 +56209,23 @@ static int js_TA_cmp_generic(const void *a, const void *b, void *opaque) { JSValueConst argv[2]; JSValue res; int cmp; - + JSObject *p; + cmp = 0; if (!psc->exception) { /* Note: the typed array can be detached without causing an error */ a_idx = *(uint32_t *)a; b_idx = *(uint32_t *)b; - argv[0] = psc->getfun(ctx, psc->array_ptr + + p = JS_VALUE_GET_PTR(psc->arr); + if (a_idx >= p->u.array.count || b_idx >= p->u.array.count) { + /* OOB case */ + psc->exception = 2; + return 0; + } + argv[0] = psc->getfun(ctx, p->u.array.u.uint8_ptr + a_idx * (size_t)psc->elt_size); - argv[1] = psc->getfun(ctx, psc->array_ptr + + argv[1] = psc->getfun(ctx, p->u.array.u.uint8_ptr + b_idx * (size_t)(psc->elt_size)); res = JS_Call(ctx, psc->cmp, JS_UNDEFINED, 2, argv); if (JS_IsException(res)) { @@ -55992,10 +56248,6 @@ static int js_TA_cmp_generic(const void *a, const void *b, void *opaque) { /* make sort stable: compare array offsets */ cmp = (a_idx > b_idx) - (a_idx < b_idx); } - if (unlikely(typed_array_is_detached(ctx, - JS_VALUE_GET_PTR(psc->arr)))) { - psc->exception = 2; - } done: JS_FreeValue(ctx, (JSValue)argv[0]); JS_FreeValue(ctx, (JSValue)argv[1]); @@ -56010,7 +56262,6 @@ static JSValue js_typed_array_sort(JSContext *ctx, JSValueConst this_val, int len; size_t elt_size; struct TA_sort_context tsc; - void *array_ptr; int (*cmpfun)(const void *a, const void *b, void *opaque); tsc.ctx = ctx; @@ -56020,7 +56271,7 @@ static JSValue js_typed_array_sort(JSContext *ctx, JSValueConst this_val, if (!JS_IsUndefined(tsc.cmp) && check_function(ctx, tsc.cmp)) return JS_EXCEPTION; - len = js_typed_array_get_length_internal(ctx, this_val); + len = js_typed_array_get_length_unsafe(ctx, this_val); if (len < 0) return JS_EXCEPTION; @@ -56075,7 +56326,6 @@ static JSValue js_typed_array_sort(JSContext *ctx, JSValueConst this_val, default: abort(); } - array_ptr = p->u.array.u.ptr; elt_size = 1 << typed_array_size_log2(p->class_id); if (!JS_IsUndefined(tsc.cmp)) { uint32_t *array_idx; @@ -56088,7 +56338,6 @@ static JSValue js_typed_array_sort(JSContext *ctx, JSValueConst this_val, return JS_EXCEPTION; for(i = 0; i < len; i++) array_idx[i] = i; - tsc.array_ptr = array_ptr; tsc.elt_size = elt_size; rqsort(array_idx, len, sizeof(array_idx[0]), js_TA_cmp_generic, &tsc); @@ -56097,46 +56346,50 @@ static JSValue js_typed_array_sort(JSContext *ctx, JSValueConst this_val, goto fail; /* detached typed array during the sort: no error */ } else { - array_tmp = js_malloc(ctx, len * elt_size); - if (!array_tmp) { - fail: - js_free(ctx, array_idx); - return JS_EXCEPTION; + void *array_ptr = p->u.array.u.ptr; + len = min_int(len, p->u.array.count); + if (len != 0) { + array_tmp = js_malloc(ctx, len * elt_size); + if (!array_tmp) { + fail: + js_free(ctx, array_idx); + return JS_EXCEPTION; + } + memcpy(array_tmp, array_ptr, len * elt_size); + switch(elt_size) { + case 1: + for(i = 0; i < len; i++) { + j = array_idx[i]; + ((uint8_t *)array_ptr)[i] = ((uint8_t *)array_tmp)[j]; + } + break; + case 2: + for(i = 0; i < len; i++) { + j = array_idx[i]; + ((uint16_t *)array_ptr)[i] = ((uint16_t *)array_tmp)[j]; + } + break; + case 4: + for(i = 0; i < len; i++) { + j = array_idx[i]; + ((uint32_t *)array_ptr)[i] = ((uint32_t *)array_tmp)[j]; + } + break; + case 8: + for(i = 0; i < len; i++) { + j = array_idx[i]; + ((uint64_t *)array_ptr)[i] = ((uint64_t *)array_tmp)[j]; + } + break; + default: + abort(); + } + js_free(ctx, array_tmp); } - memcpy(array_tmp, array_ptr, len * elt_size); - switch(elt_size) { - case 1: - for(i = 0; i < len; i++) { - j = array_idx[i]; - ((uint8_t *)array_ptr)[i] = ((uint8_t *)array_tmp)[j]; - } - break; - case 2: - for(i = 0; i < len; i++) { - j = array_idx[i]; - ((uint16_t *)array_ptr)[i] = ((uint16_t *)array_tmp)[j]; - } - break; - case 4: - for(i = 0; i < len; i++) { - j = array_idx[i]; - ((uint32_t *)array_ptr)[i] = ((uint32_t *)array_tmp)[j]; - } - break; - case 8: - for(i = 0; i < len; i++) { - j = array_idx[i]; - ((uint64_t *)array_ptr)[i] = ((uint64_t *)array_tmp)[j]; - } - break; - default: - abort(); - } - js_free(ctx, array_tmp); } js_free(ctx, array_idx); } else { - rqsort(array_ptr, len, elt_size, cmpfun, &tsc); + rqsort(p->u.array.u.ptr, len, elt_size, cmpfun, &tsc); if (tsc.exception) return JS_EXCEPTION; } @@ -56150,11 +56403,11 @@ static JSValue js_typed_array_toSorted(JSContext *ctx, JSValueConst this_val, JSValue arr, ret; JSObject *p; - p = get_typed_array(ctx, this_val, /*is_dataview*/0); + p = get_typed_array(ctx, this_val); if (!p) return JS_EXCEPTION; arr = js_typed_array_constructor_ta(ctx, JS_UNDEFINED, this_val, - p->class_id); + p->class_id, p->u.array.count); if (JS_IsException(arr)) return JS_EXCEPTION; ret = js_typed_array_sort(ctx, arr, argc, argv); @@ -56175,9 +56428,9 @@ static const JSCFunctionListEntry js_typed_array_base_proto_funcs[] = { JS_CGETSET_DEF("length", js_typed_array_get_length, NULL ), JS_CFUNC_DEF("at", 1, js_typed_array_at ), JS_CFUNC_DEF("with", 2, js_typed_array_with ), - JS_CGETSET_MAGIC_DEF("buffer", js_typed_array_get_buffer, NULL, 0 ), - JS_CGETSET_MAGIC_DEF("byteLength", js_typed_array_get_byteLength, NULL, 0 ), - JS_CGETSET_MAGIC_DEF("byteOffset", js_typed_array_get_byteOffset, NULL, 0 ), + JS_CGETSET_DEF("buffer", js_typed_array_get_buffer, NULL ), + JS_CGETSET_DEF("byteLength", js_typed_array_get_byteLength, NULL ), + JS_CGETSET_DEF("byteOffset", js_typed_array_get_byteOffset, NULL ), JS_CFUNC_DEF("set", 1, js_typed_array_set ), JS_CFUNC_MAGIC_DEF("values", 0, js_create_typed_array_iterator, JS_ITERATOR_KIND_VALUE ), JS_ALIAS_DEF("[Symbol.iterator]", "values" ), @@ -56220,7 +56473,8 @@ static JSValue js_typed_array_base_constructor(JSContext *ctx, /* 'obj' must be an allocated typed array object */ static int typed_array_init(JSContext *ctx, JSValueConst obj, - JSValue buffer, uint64_t offset, uint64_t len) + JSValue buffer, uint64_t offset, uint64_t len, + BOOL track_rab) { JSTypedArray *ta; JSObject *p, *pbuffer; @@ -56240,6 +56494,7 @@ static int typed_array_init(JSContext *ctx, JSValueConst obj, ta->buffer = pbuffer; ta->offset = offset; ta->length = len << size_log2; + ta->track_rab = track_rab; list_add_tail(&ta->link, &abuf->array_list); p->u.typed_array = ta; p->u.array.count = len; @@ -56319,10 +56574,11 @@ static JSValue js_typed_array_constructor_obj(JSContext *ctx, } buffer = js_array_buffer_constructor1(ctx, JS_UNDEFINED, - len << size_log2); + len << size_log2, + NULL); if (JS_IsException(buffer)) goto fail; - if (typed_array_init(ctx, ret, buffer, 0, len)) + if (typed_array_init(ctx, ret, buffer, 0, len, /*track_rab*/FALSE)) goto fail; for(i = 0; i < len; i++) { @@ -56343,12 +56599,12 @@ static JSValue js_typed_array_constructor_obj(JSContext *ctx, static JSValue js_typed_array_constructor_ta(JSContext *ctx, JSValueConst new_target, JSValueConst src_obj, - int classid) + int classid, uint32_t len) { JSObject *p, *src_buffer; JSTypedArray *ta; JSValue obj, buffer; - uint32_t len, i; + uint32_t i; int size_log2; JSArrayBuffer *src_abuf, *abuf; @@ -56356,27 +56612,27 @@ static JSValue js_typed_array_constructor_ta(JSContext *ctx, if (JS_IsException(obj)) return obj; p = JS_VALUE_GET_OBJ(src_obj); - if (typed_array_is_detached(ctx, p)) { - JS_ThrowTypeErrorDetachedArrayBuffer(ctx); + if (typed_array_is_oob(p)) { + JS_ThrowTypeErrorArrayBufferOOB(ctx); goto fail; } ta = p->u.typed_array; - len = p->u.array.count; src_buffer = ta->buffer; src_abuf = src_buffer->u.array_buffer; size_log2 = typed_array_size_log2(classid); buffer = js_array_buffer_constructor1(ctx, JS_UNDEFINED, - (uint64_t)len << size_log2); + (uint64_t)len << size_log2, + NULL); if (JS_IsException(buffer)) goto fail; /* necessary because it could have been detached */ - if (typed_array_is_detached(ctx, p)) { + if (typed_array_is_oob(p)) { JS_FreeValue(ctx, buffer); - JS_ThrowTypeErrorDetachedArrayBuffer(ctx); + JS_ThrowTypeErrorArrayBufferOOB(ctx); goto fail; } abuf = JS_GetOpaque(buffer, JS_CLASS_ARRAY_BUFFER); - if (typed_array_init(ctx, obj, buffer, 0, len)) + if (typed_array_init(ctx, obj, buffer, 0, len, /*track_rab*/FALSE)) goto fail; if (p->class_id == classid) { /* same type: copy the content */ @@ -56402,6 +56658,7 @@ static JSValue js_typed_array_constructor(JSContext *ctx, int argc, JSValueConst *argv, int classid) { + BOOL track_rab = FALSE; JSValue buffer, obj; JSArrayBuffer *abuf; int size_log2; @@ -56412,7 +56669,8 @@ static JSValue js_typed_array_constructor(JSContext *ctx, if (JS_ToIndex(ctx, &len, argv[0])) return JS_EXCEPTION; buffer = js_array_buffer_constructor1(ctx, JS_UNDEFINED, - len << size_log2); + len << size_log2, + NULL); if (JS_IsException(buffer)) return JS_EXCEPTION; offset = 0; @@ -56429,8 +56687,10 @@ static JSValue js_typed_array_constructor(JSContext *ctx, offset > abuf->byte_length) return JS_ThrowRangeError(ctx, "invalid offset"); if (JS_IsUndefined(argv[2])) { - if ((abuf->byte_length & ((1 << size_log2) - 1)) != 0) - goto invalid_length; + track_rab = array_buffer_is_resizable(abuf); + if (!track_rab) + if ((abuf->byte_length & ((1 << size_log2) - 1)) != 0) + goto invalid_length; len = (abuf->byte_length - offset) >> size_log2; } else { if (JS_ToIndex(ctx, &len, argv[2])) @@ -56446,7 +56706,8 @@ static JSValue js_typed_array_constructor(JSContext *ctx, } else { if (p->class_id >= JS_CLASS_UINT8C_ARRAY && p->class_id <= JS_CLASS_FLOAT64_ARRAY) { - return js_typed_array_constructor_ta(ctx, new_target, argv[0], classid); + return js_typed_array_constructor_ta(ctx, new_target, argv[0], + classid, p->u.array.count); } else { return js_typed_array_constructor_obj(ctx, new_target, argv[0], classid); } @@ -56458,7 +56719,7 @@ static JSValue js_typed_array_constructor(JSContext *ctx, JS_FreeValue(ctx, buffer); return JS_EXCEPTION; } - if (typed_array_init(ctx, obj, buffer, offset, len)) { + if (typed_array_init(ctx, obj, buffer, offset, len, track_rab)) { JS_FreeValue(ctx, obj); return JS_EXCEPTION; } @@ -56494,6 +56755,8 @@ static JSValue js_dataview_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv) { + BOOL recompute_len = FALSE; + BOOL track_rab = FALSE; JSArrayBuffer *abuf; uint64_t offset; uint32_t len; @@ -56523,6 +56786,9 @@ static JSValue js_dataview_constructor(JSContext *ctx, if (l > len) return JS_ThrowRangeError(ctx, "invalid byteLength"); len = l; + } else { + recompute_len = TRUE; + track_rab = array_buffer_is_resizable(abuf); } obj = js_create_from_ctor(ctx, new_target, JS_CLASS_DATAVIEW); @@ -56533,6 +56799,16 @@ static JSValue js_dataview_constructor(JSContext *ctx, JS_ThrowTypeErrorDetachedArrayBuffer(ctx); goto fail; } + // RAB could have been resized in js_create_from_ctor() + if (offset > abuf->byte_length) { + goto out_of_bound; + } else if (recompute_len) { + len = abuf->byte_length - offset; + } else if (offset + len > abuf->byte_length) { + out_of_bound: + JS_ThrowRangeError(ctx, "invalid byteOffset or byteLength"); + goto fail; + } ta = js_malloc(ctx, sizeof(*ta)); if (!ta) { fail: @@ -56544,11 +56820,88 @@ static JSValue js_dataview_constructor(JSContext *ctx, ta->buffer = JS_VALUE_GET_OBJ(JS_DupValue(ctx, buffer)); ta->offset = offset; ta->length = len; + ta->track_rab = track_rab; list_add_tail(&ta->link, &abuf->array_list); p->u.typed_array = ta; return obj; } +// is the DataView out of bounds relative to its parent arraybuffer? +static BOOL dataview_is_oob(JSObject *p) +{ + JSArrayBuffer *abuf; + JSTypedArray *ta; + + assert(p->class_id == JS_CLASS_DATAVIEW); + ta = p->u.typed_array; + abuf = ta->buffer->u.array_buffer; + if (abuf->detached) + return TRUE; + if (ta->offset > abuf->byte_length) + return TRUE; + if (ta->track_rab) + return FALSE; + return (int64_t)ta->offset + ta->length > abuf->byte_length; +} + +static JSObject *get_dataview(JSContext *ctx, JSValueConst this_val) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(this_val) != JS_TAG_OBJECT) + goto fail; + p = JS_VALUE_GET_OBJ(this_val); + if (p->class_id != JS_CLASS_DATAVIEW) { + fail: + JS_ThrowTypeError(ctx, "not a DataView"); + return NULL; + } + return p; +} + +static JSValue js_dataview_get_buffer(JSContext *ctx, JSValueConst this_val) +{ + JSObject *p; + JSTypedArray *ta; + p = get_dataview(ctx, this_val); + if (!p) + return JS_EXCEPTION; + ta = p->u.typed_array; + return JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, ta->buffer)); +} + +static JSValue js_dataview_get_byteLength(JSContext *ctx, JSValueConst this_val) +{ + JSArrayBuffer *abuf; + JSTypedArray *ta; + JSObject *p; + + p = get_dataview(ctx, this_val); + if (!p) + return JS_EXCEPTION; + if (dataview_is_oob(p)) + return JS_ThrowTypeErrorArrayBufferOOB(ctx); + ta = p->u.typed_array; + if (ta->track_rab) { + abuf = ta->buffer->u.array_buffer; + return JS_NewUint32(ctx, abuf->byte_length - ta->offset); + } + return JS_NewUint32(ctx, ta->length); +} + +static JSValue js_dataview_get_byteOffset(JSContext *ctx, JSValueConst this_val) +{ + JSTypedArray *ta; + JSObject *p; + + p = get_dataview(ctx, this_val); + if (!p) + return JS_EXCEPTION; + if (dataview_is_oob(p)) + return JS_ThrowTypeErrorArrayBufferOOB(ctx); + ta = p->u.typed_array; + return JS_NewUint32(ctx, ta->offset); +} + static JSValue js_dataview_getValue(JSContext *ctx, JSValueConst this_obj, int argc, JSValueConst *argv, int class_id) @@ -56572,8 +56925,14 @@ static JSValue js_dataview_getValue(JSContext *ctx, abuf = ta->buffer->u.array_buffer; if (abuf->detached) return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); + // order matters: this check should come before the next one if ((pos + size) > ta->length) return JS_ThrowRangeError(ctx, "out of bound"); + // test262 expects a TypeError for this and V8, in its infinite wisdom, + // throws a "detached array buffer" exception, but IMO that doesn't make + // sense because the buffer is not in fact detached, it's still there + if ((int64_t)ta->offset + ta->length > abuf->byte_length) + return JS_ThrowTypeError(ctx, "out of bound"); ptr = abuf->data + ta->offset + pos; switch(class_id) { @@ -56708,8 +57067,14 @@ static JSValue js_dataview_setValue(JSContext *ctx, abuf = ta->buffer->u.array_buffer; if (abuf->detached) return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); + // order matters: this check should come before the next one if ((pos + size) > ta->length) return JS_ThrowRangeError(ctx, "out of bound"); + // test262 expects a TypeError for this and V8, in its infinite wisdom, + // throws a "detached array buffer" exception, but IMO that doesn't make + // sense because the buffer is not in fact detached, it's still there + if ((int64_t)ta->offset + ta->length > abuf->byte_length) + return JS_ThrowTypeError(ctx, "out of bound"); ptr = abuf->data + ta->offset + pos; switch(class_id) { @@ -56745,9 +57110,9 @@ static JSValue js_dataview_setValue(JSContext *ctx, } static const JSCFunctionListEntry js_dataview_proto_funcs[] = { - JS_CGETSET_MAGIC_DEF("buffer", js_typed_array_get_buffer, NULL, 1 ), - JS_CGETSET_MAGIC_DEF("byteLength", js_typed_array_get_byteLength, NULL, 1 ), - JS_CGETSET_MAGIC_DEF("byteOffset", js_typed_array_get_byteOffset, NULL, 1 ), + JS_CGETSET_DEF("buffer", js_dataview_get_buffer, NULL ), + JS_CGETSET_DEF("byteLength", js_dataview_get_byteLength, NULL ), + JS_CGETSET_DEF("byteOffset", js_dataview_get_byteOffset, NULL ), JS_CFUNC_MAGIC_DEF("getInt8", 1, js_dataview_getValue, JS_CLASS_INT8_ARRAY ), JS_CFUNC_MAGIC_DEF("getUint8", 1, js_dataview_getValue, JS_CLASS_UINT8_ARRAY ), JS_CFUNC_MAGIC_DEF("getInt16", 1, js_dataview_getValue, JS_CLASS_INT16_ARRAY ), @@ -56787,11 +57152,12 @@ typedef enum AtomicsOpEnum { ATOMICS_OP_LOAD, } AtomicsOpEnum; -static void *js_atomics_get_ptr(JSContext *ctx, - JSArrayBuffer **pabuf, - int *psize_log2, JSClassID *pclass_id, - JSValueConst obj, JSValueConst idx_val, - int is_waitable) +static int js_atomics_get_ptr(JSContext *ctx, + void **pptr, + JSArrayBuffer **pabuf, + int *psize_log2, JSClassID *pclass_id, + JSValueConst obj, JSValueConst idx_val, + int is_waitable) { JSObject *p; JSTypedArray *ta; @@ -56799,7 +57165,7 @@ static void *js_atomics_get_ptr(JSContext *ctx, void *ptr; uint64_t idx; BOOL err; - int size_log2; + int size_log2, old_len; if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) goto fail; @@ -56813,33 +57179,46 @@ static void *js_atomics_get_ptr(JSContext *ctx, if (err) { fail: JS_ThrowTypeError(ctx, "integer TypedArray expected"); - return NULL; + return -1; } ta = p->u.typed_array; abuf = ta->buffer->u.array_buffer; if (!abuf->shared) { if (is_waitable == 2) { JS_ThrowTypeError(ctx, "not a SharedArrayBuffer TypedArray"); - return NULL; + return -1; } if (abuf->detached) { JS_ThrowTypeErrorDetachedArrayBuffer(ctx); - return NULL; + return -1; } } + old_len = p->u.array.count; + if (JS_ToIndex(ctx, &idx, idx_val)) { - return NULL; + return -1; } - /* RevalidateAtomicAccess(): must test again detached after JS_ToIndex() */ - if (abuf->detached) { - JS_ThrowTypeErrorDetachedArrayBuffer(ctx); - return NULL; - } - /* if the array buffer is detached, p->u.array.count = 0 */ - if (idx >= p->u.array.count) { - JS_ThrowRangeError(ctx, "out-of-bound access"); - return NULL; + + if (idx >= old_len) + goto oob; + + if (is_waitable == 1) { + /* notify(): just avoid having an invalid pointer if overflow */ + if (idx >= p->u.array.count) + ptr = NULL; + } else { + /* RevalidateAtomicAccess() */ + if (typed_array_is_oob(p)) { + JS_ThrowTypeErrorArrayBufferOOB(ctx); + return -1; + } + if (idx >= p->u.array.count) { + oob: + JS_ThrowRangeError(ctx, "out-of-bound access"); + return -1; + } } + size_log2 = typed_array_size_log2(p->class_id); ptr = p->u.array.u.uint8_ptr + ((uintptr_t)idx << size_log2); if (pabuf) @@ -56848,7 +57227,8 @@ static void *js_atomics_get_ptr(JSContext *ctx, *psize_log2 = size_log2; if (pclass_id) *pclass_id = p->class_id; - return ptr; + *pptr = ptr; + return 0; } static JSValue js_atomics_op(JSContext *ctx, @@ -56862,9 +57242,8 @@ static JSValue js_atomics_op(JSContext *ctx, JSClassID class_id; JSArrayBuffer *abuf; - ptr = js_atomics_get_ptr(ctx, &abuf, &size_log2, &class_id, - argv[0], argv[1], 0); - if (!ptr) + if (js_atomics_get_ptr(ctx, &ptr, &abuf, &size_log2, &class_id, + argv[0], argv[1], 0)) return JS_EXCEPTION; rep_val = 0; if (op == ATOMICS_OP_LOAD) { @@ -57005,9 +57384,8 @@ static JSValue js_atomics_store(JSContext *ctx, JSValue ret; JSArrayBuffer *abuf; - ptr = js_atomics_get_ptr(ctx, &abuf, &size_log2, NULL, - argv[0], argv[1], 0); - if (!ptr) + if (js_atomics_get_ptr(ctx, &ptr, &abuf, &size_log2, NULL, + argv[0], argv[1], 0)) return JS_EXCEPTION; if (size_log2 == 3) { int64_t v64; @@ -57085,9 +57463,8 @@ static JSValue js_atomics_wait(JSContext *ctx, int ret, size_log2, res; double d; - ptr = js_atomics_get_ptr(ctx, NULL, &size_log2, NULL, - argv[0], argv[1], 2); - if (!ptr) + if (js_atomics_get_ptr(ctx, &ptr, NULL, &size_log2, NULL, + argv[0], argv[1], 2)) return JS_EXCEPTION; if (size_log2 == 3) { if (JS_ToBigInt64(ctx, &v, argv[2])) @@ -57165,18 +57542,15 @@ static JSValue js_atomics_notify(JSContext *ctx, JSAtomicsWaiter *waiter; JSArrayBuffer *abuf; - ptr = js_atomics_get_ptr(ctx, &abuf, NULL, NULL, argv[0], argv[1], 1); - if (!ptr) + if (js_atomics_get_ptr(ctx, &ptr, &abuf, NULL, NULL, argv[0], argv[1], 1)) return JS_EXCEPTION; - + if (JS_IsUndefined(argv[2])) { count = INT32_MAX; } else { if (JS_ToInt32Clamp(ctx, &count, argv[2], 0, INT32_MAX, 0)) return JS_EXCEPTION; } - if (abuf->detached) - return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); n = 0; if (abuf->shared && count > 0) { diff --git a/test262.conf b/test262.conf index 23d72ca..ffbf857 100644 --- a/test262.conf +++ b/test262.conf @@ -180,7 +180,7 @@ regexp-named-groups regexp-unicode-property-escapes regexp-v-flag RegExp.escape -resizable-arraybuffer=skip +resizable-arraybuffer rest-parameters Set set-methods diff --git a/test262_errors.txt b/test262_errors.txt index b669a6b..5134864 100644 --- a/test262_errors.txt +++ b/test262_errors.txt @@ -5,8 +5,6 @@ test262/test/annexB/language/expressions/assignmenttargettype/callexpression-in- test262/test/annexB/language/expressions/assignmenttargettype/callexpression-in-prefix-update.js:27: SyntaxError: invalid increment/decrement operand test262/test/annexB/language/expressions/assignmenttargettype/callexpression.js:33: SyntaxError: invalid assignment left-hand side test262/test/annexB/language/expressions/assignmenttargettype/cover-callexpression-and-asyncarrowhead.js:20: SyntaxError: invalid assignment left-hand side -test262/test/built-ins/Atomics/notify/retrieve-length-before-index-coercion-non-shared-detached.js:34: TypeError: ArrayBuffer is detached -test262/test/built-ins/Atomics/notify/retrieve-length-before-index-coercion-non-shared-detached.js:34: strict mode: TypeError: ArrayBuffer is detached test262/test/staging/sm/Date/UTC-convert-all-arguments.js:75: Test262Error: index 1: expected 42, got Error: didn't throw Expected SameValue(«Error: didn't throw», «42») to be true test262/test/staging/sm/Date/constructor-convert-all-arguments.js:75: Test262Error: index undefined: expected 42, got Error: didn't throw Expected SameValue(«Error: didn't throw», «42») to be true test262/test/staging/sm/Date/non-iso.js:76: Test262Error: Expected SameValue(«NaN», «-40071559730000») to be true @@ -26,8 +24,6 @@ test262/test/staging/sm/RegExp/regress-613820-3.js:12: Test262Error: Actual [aab test262/test/staging/sm/RegExp/regress-613820-3.js:12: strict mode: Test262Error: Actual [aab, a, undefined, ab] and expected [aa, undefined, a, undefined] should have the same contents. test262/test/staging/sm/TypedArray/constructor-buffer-sequence.js:73: Error: Assertion failed: expected exception ExpectedError, got Error: Poisoned Value test262/test/staging/sm/TypedArray/prototype-constructor-identity.js:17: Test262Error: Expected SameValue(«2», «6») to be true -test262/test/staging/sm/TypedArray/set-detached-bigint.js:27: Error: Assertion failed: expected exception SyntaxError, got RangeError: invalid array length -test262/test/staging/sm/TypedArray/set-detached.js:112: RangeError: invalid array length test262/test/staging/sm/TypedArray/sort_modifications.js:12: Test262Error: Int8Array at index 0 for size 4 Expected SameValue(«0», «1») to be true test262/test/staging/sm/async-functions/async-contains-unicode-escape.js:45: Error: Assertion failed: expected exception SyntaxError, no exception thrown test262/test/staging/sm/async-functions/await-error.js:12: Test262Error: Expected SameValue(«false», «true») to be true @@ -37,7 +33,6 @@ test262/test/staging/sm/class/strictExecution.js:32: Error: Assertion failed: ex test262/test/staging/sm/class/superPropOrdering.js:83: Error: Assertion failed: expected exception TypeError, no exception thrown test262/test/staging/sm/expressions/short-circuit-compound-assignment-const.js:97: TypeError: 'a' is read-only test262/test/staging/sm/expressions/short-circuit-compound-assignment-tdz.js:23: Error: Assertion failed: expected exception ReferenceError, got TypeError: 'a' is read-only -test262/test/staging/sm/extensions/TypedArray-set-object-funky-length-detaches.js:55: RangeError: invalid array length test262/test/staging/sm/generators/syntax.js:30: Error: Assertion failed: expected SyntaxError, but no exception thrown - function* g() { (function* yield() {}); } test262/test/staging/sm/lexical-environment/block-scoped-functions-annex-b-arguments.js:14: Test262Error: Expected SameValue(«"object"», «"function"») to be true test262/test/staging/sm/lexical-environment/block-scoped-functions-annex-b-eval.js:12: Test262Error: Expected SameValue(«"outer-gouter-geval-gtruefalseq"», «"outer-geval-gwith-gtruefalseq"») to be true diff --git a/tests/test_bjson.js b/tests/test_bjson.js index 8794b22..c29ded4 100644 --- a/tests/test_bjson.js +++ b/tests/test_bjson.js @@ -115,6 +115,41 @@ function bjson_test(a) } } +function bjson_test_arraybuffer() +{ + var buf, array_buffer; + + array_buffer = new ArrayBuffer(4); + assert(array_buffer.byteLength, 4); + assert(array_buffer.maxByteLength, 4); + assert(array_buffer.resizable, false); + buf = bjson.write(array_buffer); + array_buffer = bjson.read(buf, 0, buf.byteLength); + assert(array_buffer.byteLength, 4); + assert(array_buffer.maxByteLength, 4); + assert(array_buffer.resizable, false); + + array_buffer = new ArrayBuffer(4, {maxByteLength: 4}); + assert(array_buffer.byteLength, 4); + assert(array_buffer.maxByteLength, 4); + assert(array_buffer.resizable, true); + buf = bjson.write(array_buffer); + array_buffer = bjson.read(buf, 0, buf.byteLength); + assert(array_buffer.byteLength, 4); + assert(array_buffer.maxByteLength, 4); + assert(array_buffer.resizable, true); + + array_buffer = new ArrayBuffer(4, {maxByteLength: 8}); + assert(array_buffer.byteLength, 4); + assert(array_buffer.maxByteLength, 8); + assert(array_buffer.resizable, true); + buf = bjson.write(array_buffer); + array_buffer = bjson.read(buf, 0, buf.byteLength); + assert(array_buffer.byteLength, 4); + assert(array_buffer.maxByteLength, 8); + assert(array_buffer.resizable, true); +} + /* test multiple references to an object including circular references */ function bjson_test_reference() @@ -171,6 +206,7 @@ function bjson_test_all() assert(e instanceof TypeError); } + bjson_test_arraybuffer(); bjson_test_reference(); }