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 */
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 {
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,
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);
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;
}
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;
}
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))
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))
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);
#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);
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 {
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 {
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;
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)
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;
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));
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);
}
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);
}
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)
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);
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)
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;
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)
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);
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 ),
};
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;
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;
}
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,
{
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;
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;
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 */
}
/* 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;
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);
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;
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;
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)
{
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) {
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);
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;
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),
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;
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:
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;
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;
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 */
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 {
{
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;
}
}
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);
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) {
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);
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;
}
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;
|| 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);
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;
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;
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;
};
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)) {
/* 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]);
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;
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;
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;
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);
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;
- }
- 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];
+ 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;
}
- break;
- case 8:
- for(i = 0; i < len; i++) {
- j = array_idx[i];
- ((uint64_t *)array_ptr)[i] = ((uint64_t *)array_tmp)[j];
+ 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();
}
- break;
- default:
- abort();
+ js_free(ctx, array_tmp);
}
- 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;
}
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);
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" ),
/* '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;
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;
}
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++) {
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;
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 */
int argc, JSValueConst *argv,
int classid)
{
+ BOOL track_rab = FALSE;
JSValue buffer, obj;
JSArrayBuffer *abuf;
int size_log2;
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;
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]))
} 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);
}
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;
}
JSValueConst new_target,
int argc, JSValueConst *argv)
{
+ BOOL recompute_len = FALSE;
+ BOOL track_rab = FALSE;
JSArrayBuffer *abuf;
uint64_t offset;
uint32_t len;
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);
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:
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)
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) {
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) {
}
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 ),
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;
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;
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;
- }
- /* RevalidateAtomicAccess(): must test again detached after JS_ToIndex() */
- if (abuf->detached) {
- JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
- return NULL;
+ return -1;
}
- /* 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)
*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,
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) {
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;
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]))
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) {