From: Gabriel Ivăncescu gabrielopcode@gmail.com
Typed Arrays have special properties (even according to the spec); they override all positive indices, and operations on them must be special cased. Presumably (on native) this is done for performance reasons, as they are used as actual data arrays which are expected to perform well with many elements (e.g. using them to store and manipulate an image). The spec is even stricter, where any number is intercepted, but for us it's simpler since native doesn't follow the spec.
Note that even the behavior is special; defining an indexed prop on a typed array for indices out of bounds of the array doesn't do anything, although it does throw if flags don't match (since it's not configurable). Redefining an index within bounds throws if flags don't match, but otherwise acts like a normal setter (coerces it into the underlying data type), which is NOT how defineProperty usually works, even on other indexed props. delete always returns false, even for out of bounds indices, despite them not being props (hasOwnProperty returns false), which is another special case.
The prototype is also not consulted for any positive indices, but is for negative (unlike the spec says, native differs here), i.e. the positive indices behavior is completely overridden, even for those out of bounds.
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com --- dlls/jscript/arraybuf.c | 347 +++++++++++++++++++++++++++++++++++++++ dlls/jscript/dispex.c | 45 ++++- dlls/jscript/error.c | 3 + dlls/jscript/jscript.h | 19 ++- dlls/jscript/jscript.rc | 3 + dlls/jscript/object.c | 8 + dlls/jscript/resource.h | 3 + dlls/mshtml/tests/es5.js | 189 ++++++++++++++++++++- 8 files changed, 611 insertions(+), 6 deletions(-)
diff --git a/dlls/jscript/arraybuf.c b/dlls/jscript/arraybuf.c index 62dfe614d65..351f7449bd5 100644 --- a/dlls/jscript/arraybuf.c +++ b/dlls/jscript/arraybuf.c @@ -39,6 +39,14 @@ typedef struct { DWORD size; } DataViewInstance;
+typedef struct { + jsdisp_t dispex; + + ArrayBufferInstance *buffer; + DWORD offset; + DWORD length; +} TypedArrayInstance; + static inline ArrayBufferInstance *arraybuf_from_jsdisp(jsdisp_t *jsdisp) { return CONTAINING_RECORD(jsdisp, ArrayBufferInstance, dispex); @@ -49,6 +57,11 @@ static inline DataViewInstance *dataview_from_jsdisp(jsdisp_t *jsdisp) return CONTAINING_RECORD(jsdisp, DataViewInstance, dispex); }
+static inline TypedArrayInstance *typedarr_from_jsdisp(jsdisp_t *jsdisp) +{ + return CONTAINING_RECORD(jsdisp, TypedArrayInstance, dispex); +} + static inline ArrayBufferInstance *arraybuf_this(jsval_t vthis) { jsdisp_t *jsdisp = is_object_instance(vthis) ? to_jsdisp(get_object(vthis)) : NULL; @@ -709,6 +722,303 @@ static const builtin_info_t DataViewConstr_info = { NULL };
+#define TYPEDARRAY_LIST \ +X(Int8Array, JSCLASS_INT8ARRAY, INT8, to_int32, INT) \ +X(Int16Array, JSCLASS_INT16ARRAY, INT16, to_int32, INT) \ +X(Int32Array, JSCLASS_INT32ARRAY, INT32, to_int32, INT) \ +X(Uint8Array, JSCLASS_UINT8ARRAY, UINT8, to_int32, INT) \ +X(Uint16Array, JSCLASS_UINT16ARRAY, UINT16, to_int32, INT) \ +X(Uint32Array, JSCLASS_UINT32ARRAY, UINT32, to_int32, INT) \ +X(Float32Array, JSCLASS_FLOAT32ARRAY, float, to_number, double) \ +X(Float64Array, JSCLASS_FLOAT64ARRAY, double, to_number, double) + +#define TYPEDARRAY_INDEX(JSCLASS) ((JSCLASS) - FIRST_TYPEDARRAY_JSCLASS) + +#define X(NAME, JSCLASS, TYPE, CONVERT, NUM_TYPE) [TYPEDARRAY_INDEX(JSCLASS)] = L"" #NAME, +static const WCHAR *const TypedArray_name[] = { TYPEDARRAY_LIST }; +#undef X + +#define X(NAME, JSCLASS, TYPE, CONVERT, NUM_TYPE) [TYPEDARRAY_INDEX(JSCLASS)] = sizeof(TYPE), +static const unsigned TypedArray_elem_size[] = { TYPEDARRAY_LIST }; +#undef X + +static inline TypedArrayInstance *typedarr_this(jsval_t vthis, jsclass_t jsclass) +{ + jsdisp_t *jsdisp = is_object_instance(vthis) ? to_jsdisp(get_object(vthis)) : NULL; + return (jsdisp && is_class(jsdisp, jsclass)) ? typedarr_from_jsdisp(jsdisp) : NULL; +} + +static HRESULT TypedArray_get_buffer(script_ctx_t *ctx, jsdisp_t *jsthis, jsval_t *r) +{ + TRACE("%p\n", jsthis); + + *r = jsval_obj(jsdisp_addref(&typedarr_from_jsdisp(jsthis)->buffer->dispex)); + return S_OK; +} + +static HRESULT TypedArray_get_byteLength(script_ctx_t *ctx, jsdisp_t *jsthis, jsval_t *r) +{ + TRACE("%p\n", jsthis); + + *r = jsval_number(typedarr_from_jsdisp(jsthis)->length * TypedArray_elem_size[TYPEDARRAY_INDEX(jsthis->builtin_info->class)]); + return S_OK; +} + +static HRESULT TypedArray_get_byteOffset(script_ctx_t *ctx, jsdisp_t *jsthis, jsval_t *r) +{ + TRACE("%p\n", jsthis); + + *r = jsval_number(typedarr_from_jsdisp(jsthis)->offset); + return S_OK; +} + +static HRESULT TypedArray_get_length(script_ctx_t *ctx, jsdisp_t *jsthis, jsval_t *r) +{ + TRACE("%p\n", jsthis); + + *r = jsval_number(typedarr_from_jsdisp(jsthis)->length); + return S_OK; +} + +static HRESULT TypedArray_set(script_ctx_t *ctx, jsval_t vthis, WORD flags, unsigned argc, jsval_t *argv, + jsval_t *r, jsclass_t jsclass) +{ + TypedArrayInstance *typedarr; + + FIXME("not implemented\n"); + + if(!(typedarr = typedarr_this(vthis, jsclass))) + return JS_E_NOT_TYPEDARRAY; + return E_NOTIMPL; +} + +static HRESULT TypedArray_subarray(script_ctx_t *ctx, jsval_t vthis, WORD flags, unsigned argc, jsval_t *argv, + jsval_t *r, jsclass_t jsclass) +{ + TypedArrayInstance *typedarr; + + FIXME("not implemented\n"); + + if(!(typedarr = typedarr_this(vthis, jsclass))) + return JS_E_NOT_TYPEDARRAY; + return E_NOTIMPL; +} + +static unsigned TypedArray_idx_length(jsdisp_t *jsdisp) +{ + TypedArrayInstance *typedarr = typedarr_from_jsdisp(jsdisp); + return typedarr->length; +} + +static void TypedArray_destructor(jsdisp_t *dispex) +{ + TypedArrayInstance *typedarr = typedarr_from_jsdisp(dispex); + if(typedarr->buffer) + jsdisp_release(&typedarr->buffer->dispex); + free(typedarr); +} + +static HRESULT TypedArray_gc_traverse(struct gc_ctx *gc_ctx, enum gc_traverse_op op, jsdisp_t *dispex) +{ + TypedArrayInstance *typedarr = typedarr_from_jsdisp(dispex); + return gc_process_linked_obj(gc_ctx, op, dispex, &typedarr->buffer->dispex, (void**)&typedarr->buffer); +} + +static const builtin_prop_t TypedArrayInst_props[] = { + {L"buffer", NULL, 0, TypedArray_get_buffer}, + {L"byteLength", NULL, 0, TypedArray_get_byteLength}, + {L"byteOffset", NULL, 0, TypedArray_get_byteOffset}, + {L"length", NULL, 0, TypedArray_get_length}, +}; + +#define X(NAME, JSCLASS, TYPE, CONVERT, NUM_TYPE) \ +static HRESULT NAME ##_idx_get(jsdisp_t *jsdisp, unsigned idx, jsval_t *r) \ +{ \ + TypedArrayInstance *typedarr = typedarr_from_jsdisp(jsdisp); \ + \ + TRACE("%p[%u]\n", typedarr, idx); \ + \ + *r = jsval_number(*(TYPE*)&typedarr->buffer->buf[typedarr->offset + idx * sizeof(TYPE)]); \ + return S_OK; \ +} \ + \ +static HRESULT NAME ##_idx_put(jsdisp_t *jsdisp, unsigned idx, jsval_t val) \ +{ \ + TypedArrayInstance *typedarr = typedarr_from_jsdisp(jsdisp); \ + HRESULT hres; \ + NUM_TYPE n; \ + \ + TRACE("%p[%u] = %s\n", typedarr, idx, debugstr_jsval(val)); \ + \ + hres = CONVERT(jsdisp->ctx, val, &n); \ + if(SUCCEEDED(hres)) \ + *(TYPE*)&typedarr->buffer->buf[typedarr->offset + idx * sizeof(TYPE)] = n; \ + return hres; \ +} \ + \ +static HRESULT NAME ##_set(script_ctx_t *ctx, jsval_t vthis, WORD flags, unsigned argc, jsval_t *argv, jsval_t *r) \ +{ \ + return TypedArray_set(ctx, vthis, flags, argc, argv, r, JSCLASS); \ +} \ + \ +static HRESULT NAME ##_subarray(script_ctx_t *ctx, jsval_t vthis, WORD flags, unsigned argc, jsval_t *argv, jsval_t *r) \ +{ \ + return TypedArray_subarray(ctx, vthis, flags, argc, argv, r, JSCLASS); \ +} \ + \ +static const builtin_prop_t NAME ##_props[] = { \ + {L"buffer", NULL, 0, TypedArray_get_buffer}, \ + {L"byteLength", NULL, 0, TypedArray_get_byteLength}, \ + {L"byteOffset", NULL, 0, TypedArray_get_byteOffset}, \ + {L"length", NULL, 0, TypedArray_get_length}, \ + {L"set", NAME ##_set, PROPF_METHOD|2}, \ + {L"subarray", NAME ##_subarray, PROPF_METHOD|2}, \ +}; +TYPEDARRAY_LIST +#undef X + +#define X(NAME, JSCLASS, TYPE, CONVERT, NUM_TYPE) \ +[TYPEDARRAY_INDEX(JSCLASS)] = \ +{ \ + JSCLASS, \ + NULL, \ + ARRAY_SIZE(NAME ##_props), \ + NAME ##_props, \ + TypedArray_destructor, \ + NULL, \ + TypedArray_idx_length, \ + NAME ##_idx_get, \ + NAME ##_idx_put, \ + TypedArray_gc_traverse \ +}, +static const builtin_info_t TypedArray_info[] = { TYPEDARRAY_LIST }; +#undef X + +#define X(NAME, JSCLASS, TYPE, CONVERT, NUM_TYPE) \ +[TYPEDARRAY_INDEX(JSCLASS)] = \ +{ \ + JSCLASS, \ + NULL, \ + ARRAY_SIZE(TypedArrayInst_props), \ + TypedArrayInst_props, \ + TypedArray_destructor, \ + NULL, \ + TypedArray_idx_length, \ + NAME ##_idx_get, \ + NAME ##_idx_put, \ + TypedArray_gc_traverse \ +}, +static const builtin_info_t TypedArrayInst_info[] = { TYPEDARRAY_LIST }; +#undef X + +static HRESULT create_typedarr(script_ctx_t *ctx, jsclass_t jsclass, ArrayBufferInstance *buffer, DWORD offset, DWORD length, jsdisp_t **ret) +{ + TypedArrayInstance *typedarr; + HRESULT hres; + + if(!(typedarr = calloc(1, sizeof(TypedArrayInstance)))) + return E_OUTOFMEMORY; + + hres = init_dispex_from_constr(&typedarr->dispex, ctx, &TypedArrayInst_info[TYPEDARRAY_INDEX(jsclass)], + ctx->typedarr_constr[TYPEDARRAY_INDEX(jsclass)]); + if(FAILED(hres)) { + free(typedarr); + return hres; + } + + jsdisp_addref(&buffer->dispex); + typedarr->buffer = buffer; + typedarr->offset = offset; + typedarr->length = length; + + *ret = &typedarr->dispex; + return S_OK; +} + +static HRESULT TypedArrayConstr_value(script_ctx_t *ctx, jsval_t vthis, WORD flags, unsigned argc, jsval_t *argv, + jsval_t *r, jsclass_t jsclass) +{ + const unsigned typedarr_idx = TYPEDARRAY_INDEX(jsclass); + unsigned elem_size = TypedArray_elem_size[typedarr_idx]; + ArrayBufferInstance *buffer = NULL; + DWORD offset = 0, length = 0; + jsdisp_t *typedarr; + HRESULT hres; + double n; + + TRACE("\n"); + + switch(flags) { + case DISPATCH_METHOD: + case DISPATCH_CONSTRUCT: { + if(argc) { + if(is_object_instance(argv[0])) { + jsdisp_t *obj = to_jsdisp(get_object(argv[0])); + + if(!obj) + return JS_E_TYPEDARRAY_BAD_CTOR_ARG; + + FIXME("Construction from object not implemented\n"); + return E_NOTIMPL; + }else if(is_number(argv[0])) { + hres = to_integer(ctx, argv[0], &n); + if(FAILED(hres)) + return hres; + if(n < 0.0) + return JS_E_TYPEDARRAY_INVALID_OFFSLEN; + if(n * elem_size > (UINT_MAX - FIELD_OFFSET(ArrayBufferInstance, buf[0]))) + return E_OUTOFMEMORY; + length = n; + }else + return JS_E_TYPEDARRAY_BAD_CTOR_ARG; + } + + if(!r) + return S_OK; + + if(!buffer) { + hres = create_arraybuf(ctx, length * elem_size, &buffer); + if(FAILED(hres)) + return hres; + } + + hres = create_typedarr(ctx, jsclass, buffer, offset, length, &typedarr); + jsdisp_release(&buffer->dispex); + if(FAILED(hres)) + return hres; + + *r = jsval_obj(typedarr); + break; + } + default: + FIXME("unimplemented flags: %x\n", flags); + return E_NOTIMPL; + } + + return S_OK; +} + +#define X(NAME, JSCLASS, TYPE, CONVERT, NUM_TYPE) \ +static HRESULT NAME ## Constr_value(script_ctx_t *ctx, jsval_t vthis, WORD flags, unsigned argc, jsval_t *argv, jsval_t *r) \ +{ \ + return TypedArrayConstr_value(ctx, vthis, flags, argc, argv, r, JSCLASS); \ +} +TYPEDARRAY_LIST +#undef X + +#define X(NAME, JSCLASS, TYPE, CONVERT, NUM_TYPE) [TYPEDARRAY_INDEX(JSCLASS)] = NAME ## Constr_value, +static const builtin_invoke_t TypedArray_constr[] = { TYPEDARRAY_LIST }; +#undef X + +static const builtin_info_t TypedArrayConstr_info = { + JSCLASS_FUNCTION, + Function_value, + 0, + NULL, + NULL, + NULL +}; + HRESULT init_arraybuf_constructors(script_ctx_t *ctx) { static const struct { @@ -720,6 +1030,7 @@ HRESULT init_arraybuf_constructors(script_ctx_t *ctx) { L"byteOffset", DataView_get_byteOffset }, }; ArrayBufferInstance *arraybuf; + TypedArrayInstance *typedarr; DataViewInstance *view; property_desc_t desc; HRESULT hres; @@ -791,6 +1102,42 @@ HRESULT init_arraybuf_constructors(script_ctx_t *ctx)
hres = jsdisp_define_data_property(ctx->global, L"DataView", PROPF_CONFIGURABLE | PROPF_WRITABLE, jsval_obj(ctx->dataview_constr)); + if(FAILED(hres)) + return hres; + + for(i = 0; i < ARRAY_SIZE(TypedArray_info); i++) { + if(!(typedarr = calloc(1, sizeof(TypedArrayInstance)))) + return E_OUTOFMEMORY; + + hres = create_arraybuf(ctx, 0, &typedarr->buffer); + if(FAILED(hres)) { + free(typedarr); + return hres; + } + + hres = init_dispex(&typedarr->dispex, ctx, &TypedArray_info[i], ctx->object_prototype); + if(FAILED(hres)) { + jsdisp_release(&typedarr->buffer->dispex); + free(typedarr); + return hres; + } + + hres = create_builtin_constructor(ctx, TypedArray_constr[i], TypedArray_name[i], &TypedArrayConstr_info, + PROPF_CONSTR|1, &typedarr->dispex, &ctx->typedarr_constr[i]); + jsdisp_release(&typedarr->dispex); + if(FAILED(hres)) + return hres; + + hres = jsdisp_define_data_property(ctx->typedarr_constr[i], L"BYTES_PER_ELEMENT", 0, + jsval_number(TypedArray_elem_size[i])); + if(FAILED(hres)) + return hres; + + hres = jsdisp_define_data_property(ctx->global, TypedArray_name[i], PROPF_CONFIGURABLE | PROPF_WRITABLE, + jsval_obj(ctx->typedarr_constr[i])); + if(FAILED(hres)) + return hres; + }
return hres; } diff --git a/dlls/jscript/dispex.c b/dlls/jscript/dispex.c index e4981912c83..da7d9255ab7 100644 --- a/dlls/jscript/dispex.c +++ b/dlls/jscript/dispex.c @@ -254,8 +254,11 @@ static BOOL get_indexed_prop_idx(jsdisp_t *This, const WCHAR *name, unsigned *re
for(ptr = name; is_digit(*ptr) && idx <= (UINT_MAX-9 / 10); ptr++) idx = idx*10 + (*ptr-'0'); - if(*ptr) - return FALSE; + if(*ptr) { + while(is_digit(*ptr)) ptr++; + if(*ptr) return FALSE; + idx = UINT_MAX; + }
*ret_idx = idx; return TRUE; @@ -264,12 +267,27 @@ static BOOL get_indexed_prop_idx(jsdisp_t *This, const WCHAR *name, unsigned *re /* Looks up the prop given the name, or its index in case of an indexed prop that we can optimize (in which case it returns S_FALSE and index into ret_idx) */ static HRESULT find_prop_name(jsdisp_t *This, unsigned hash, const WCHAR *name, BOOL case_insens, dispex_prop_t **ret, unsigned *ret_idx) { + BOOL is_typedarr = (This->builtin_info->class >= FIRST_TYPEDARRAY_JSCLASS && This->builtin_info->class <= LAST_TYPEDARRAY_JSCLASS); const builtin_prop_t *builtin; unsigned bucket, pos, prev = ~0; dispex_prop_t *prop; unsigned idx; HRESULT hres;
+ /* Typed Arrays are very much special, they override every positive index no matter what (not even consulting prototype), even if out of bounds! */ + if(is_typedarr && get_indexed_prop_idx(This, name, &idx)) { + if(idx >= This->builtin_info->idx_length(This)) { + if(ret_idx) + *ret_idx = UINT_MAX; + return DISP_E_UNKNOWNNAME; + } + if(ret_idx) { + *ret_idx = idx; + return S_FALSE; + } + is_typedarr = FALSE; /* allow PROP_IDX allocation (e.g. for enumeration) */ + } + bucket = get_props_idx(This, hash); pos = This->props[bucket].bucket_head; while(pos != ~0) { @@ -320,7 +338,7 @@ static HRESULT find_prop_name(jsdisp_t *This, unsigned hash, const WCHAR *name, return S_OK; }
- if(get_indexed_prop_idx(This, name, &idx) && idx < This->builtin_info->idx_length(This)) { + if(!is_typedarr && get_indexed_prop_idx(This, name, &idx) && idx < This->builtin_info->idx_length(This)) { unsigned flags = PROPF_ENUMERABLE;
if(ret_idx) { @@ -339,6 +357,8 @@ static HRESULT find_prop_name(jsdisp_t *This, unsigned hash, const WCHAR *name, return S_OK; }
+ if(ret_idx) + *ret_idx = 0; return DISP_E_UNKNOWNNAME; }
@@ -395,6 +415,8 @@ static HRESULT ensure_prop_name(jsdisp_t *This, const WCHAR *name, DWORD create_ if(hres != S_OK) { if(hres != DISP_E_UNKNOWNNAME) return hres; + if(ret_idx && *ret_idx == UINT_MAX) + return DISP_E_UNKNOWNNAME;
TRACE("creating new prop %s flags %lx\n", debugstr_w(name), create_flags); if(!(prop = alloc_prop(This, name, PROP_JSVAL, create_flags))) @@ -674,6 +696,8 @@ static HRESULT fill_protrefs(jsdisp_t *This) return hres; continue; } + if(idx == UINT_MAX) + continue; if(!alloc_protref(This, iter->name, iter - This->prototype->props)) return E_OUTOFMEMORY; }else if(prop->type==PROP_DELETED) { @@ -2963,7 +2987,7 @@ HRESULT disp_delete_name(script_ctx_t *ctx, IDispatch *disp, jsstr_t *name, BOOL if(hres == S_OK) hres = delete_prop(prop, ret); else { - *ret = (hres == DISP_E_UNKNOWNNAME); + *ret = (hres == DISP_E_UNKNOWNNAME && idx != UINT_MAX); hres = S_OK; }
@@ -3059,8 +3083,21 @@ HRESULT jsdisp_get_own_property(jsdisp_t *obj, const WCHAR *name, BOOL flags_onl HRESULT jsdisp_define_property(jsdisp_t *obj, const WCHAR *name, property_desc_t *desc) { dispex_prop_t *prop; + unsigned idx; HRESULT hres;
+ /* Typed Arrays have special rules here; any attempt to change their properties fails, and defining them acts like a setter! */ + if(obj->builtin_info->class >= FIRST_TYPEDARRAY_JSCLASS && obj->builtin_info->class <= LAST_TYPEDARRAY_JSCLASS && + get_indexed_prop_idx(obj, name, &idx)) { + if((desc->flags & desc->mask) != (desc->mask & (PROPF_WRITABLE | PROPF_ENUMERABLE))) + return throw_error(obj->ctx, JS_E_NONCONFIGURABLE_REDEFINED, name); + if(desc->explicit_value) + return idx < obj->builtin_info->idx_length(obj) ? obj->builtin_info->idx_put(obj, idx, desc->value) : S_OK; + if(desc->explicit_getter || desc->explicit_setter) + return throw_error(obj->ctx, JS_E_NONCONFIGURABLE_REDEFINED, name); + return idx < obj->builtin_info->idx_length(obj) ? obj->builtin_info->idx_put(obj, idx, jsval_undefined()) : S_OK; + } + hres = find_prop_name(obj, string_hash(name), name, FALSE, &prop, NULL); if(FAILED(hres)) { if(hres != DISP_E_UNKNOWNNAME) diff --git a/dlls/jscript/error.c b/dlls/jscript/error.c index 81e3a3a9739..aa5399d02c3 100644 --- a/dlls/jscript/error.c +++ b/dlls/jscript/error.c @@ -483,6 +483,8 @@ jsdisp_t *create_builtin_error(script_ctx_t *ctx) case JS_E_OBJECT_NONEXTENSIBLE: case JS_E_NONCONFIGURABLE_REDEFINED: case JS_E_NONWRITABLE_MODIFIED: + case JS_E_TYPEDARRAY_BAD_CTOR_ARG: + case JS_E_NOT_TYPEDARRAY: case JS_E_NOT_DATAVIEW: case JS_E_DATAVIEW_NO_ARGUMENT: case JS_E_WRONG_THIS: @@ -497,6 +499,7 @@ jsdisp_t *create_builtin_error(script_ctx_t *ctx) case JS_E_FRACTION_DIGITS_OUT_OF_RANGE: case JS_E_PRECISION_OUT_OF_RANGE: case JS_E_INVALID_LENGTH: + case JS_E_TYPEDARRAY_INVALID_OFFSLEN: case JS_E_DATAVIEW_INVALID_ACCESS: case JS_E_DATAVIEW_INVALID_OFFSET: constr = ctx->range_error_constr; diff --git a/dlls/jscript/jscript.h b/dlls/jscript/jscript.h index 2453cba4939..11193992f3d 100644 --- a/dlls/jscript/jscript.h +++ b/dlls/jscript/jscript.h @@ -118,11 +118,24 @@ typedef enum { JSCLASS_JSON, JSCLASS_ARRAYBUFFER, JSCLASS_DATAVIEW, + JSCLASS_INT8ARRAY, + JSCLASS_INT16ARRAY, + JSCLASS_INT32ARRAY, + JSCLASS_UINT8ARRAY, + JSCLASS_UINT16ARRAY, + JSCLASS_UINT32ARRAY, + JSCLASS_FLOAT32ARRAY, + JSCLASS_FLOAT64ARRAY, JSCLASS_MAP, JSCLASS_SET, JSCLASS_WEAKMAP, + + FIRST_TYPEDARRAY_JSCLASS = JSCLASS_INT8ARRAY, + LAST_TYPEDARRAY_JSCLASS = JSCLASS_FLOAT64ARRAY, } jsclass_t;
+enum { NUM_TYPEDARRAY_TYPES = LAST_TYPEDARRAY_JSCLASS - FIRST_TYPEDARRAY_JSCLASS + 1 }; + jsdisp_t *iface_to_jsdisp(IDispatch*);
typedef HRESULT (*builtin_invoke_t)(script_ctx_t*,jsval_t,WORD,unsigned,jsval_t*,jsval_t*); @@ -439,11 +452,12 @@ struct _script_ctx_t { jsdisp_t *vbarray_constr; jsdisp_t *arraybuf_constr; jsdisp_t *dataview_constr; + jsdisp_t *typedarr_constr[NUM_TYPEDARRAY_TYPES]; jsdisp_t *map_prototype; jsdisp_t *set_prototype; jsdisp_t *weakmap_prototype; }; - jsdisp_t *global_objects[25]; + jsdisp_t *global_objects[25 + NUM_TYPEDARRAY_TYPES]; }; }; C_ASSERT(RTL_SIZEOF_THROUGH_FIELD(script_ctx_t, weakmap_prototype) == RTL_SIZEOF_THROUGH_FIELD(script_ctx_t, global_objects)); @@ -581,6 +595,9 @@ static inline HRESULT disp_call_value(script_ctx_t *ctx, IDispatch *disp, jsval_ #define JS_E_OBJECT_NONEXTENSIBLE MAKE_JSERROR(IDS_OBJECT_NONEXTENSIBLE) #define JS_E_NONCONFIGURABLE_REDEFINED MAKE_JSERROR(IDS_NONCONFIGURABLE_REDEFINED) #define JS_E_NONWRITABLE_MODIFIED MAKE_JSERROR(IDS_NONWRITABLE_MODIFIED) +#define JS_E_TYPEDARRAY_BAD_CTOR_ARG MAKE_JSERROR(IDS_TYPEDARRAY_BAD_CTOR_ARG) +#define JS_E_NOT_TYPEDARRAY MAKE_JSERROR(IDS_NOT_TYPEDARRAY) +#define JS_E_TYPEDARRAY_INVALID_OFFSLEN MAKE_JSERROR(IDS_TYPEDARRAY_INVALID_OFFSLEN) #define JS_E_NOT_DATAVIEW MAKE_JSERROR(IDS_NOT_DATAVIEW) #define JS_E_DATAVIEW_NO_ARGUMENT MAKE_JSERROR(IDS_DATAVIEW_NO_ARGUMENT) #define JS_E_DATAVIEW_INVALID_ACCESS MAKE_JSERROR(IDS_DATAVIEW_INVALID_ACCESS) diff --git a/dlls/jscript/jscript.rc b/dlls/jscript/jscript.rc index cbdfde507b7..a224b6cd6f3 100644 --- a/dlls/jscript/jscript.rc +++ b/dlls/jscript/jscript.rc @@ -76,6 +76,9 @@ STRINGTABLE IDS_OBJECT_NONEXTENSIBLE "Cannot define property '|': object is not extensible" IDS_NONCONFIGURABLE_REDEFINED "Cannot redefine non-configurable property '|'" IDS_NONWRITABLE_MODIFIED "Cannot modify non-writable property '|'" + IDS_NOT_TYPEDARRAY "'this' is not a typed array object" + IDS_TYPEDARRAY_BAD_CTOR_ARG "Typed array constructor argument is invalid" + IDS_TYPEDARRAY_INVALID_OFFSLEN "Invalid offset/length when creating typed array" IDS_NOT_DATAVIEW "'this' is not a DataView object" IDS_DATAVIEW_NO_ARGUMENT "Required argument offset or value in DataView method is not specified" IDS_DATAVIEW_INVALID_ACCESS "DataView operation access beyond specified buffer length" diff --git a/dlls/jscript/object.c b/dlls/jscript/object.c index 16ecc3dea3c..1b33f481b3b 100644 --- a/dlls/jscript/object.c +++ b/dlls/jscript/object.c @@ -52,6 +52,14 @@ static HRESULT Object_toString(script_ctx_t *ctx, jsval_t vthis, WORD flags, uns L"[object Object]", L"[object ArrayBuffer]", L"[object Object]", + L"[object Int8Array]", + L"[object Int16Array]", + L"[object Int32Array]", + L"[object Uint8Array]", + L"[object Uint16Array]", + L"[object Uint32Array]", + L"[object Float32Array]", + L"[object Float64Array]", L"[object Object]", L"[object Object]", L"[object Object]" diff --git a/dlls/jscript/resource.h b/dlls/jscript/resource.h index 801e940b95e..feb2593e723 100644 --- a/dlls/jscript/resource.h +++ b/dlls/jscript/resource.h @@ -74,6 +74,9 @@ #define IDS_OBJECT_NONEXTENSIBLE 0x13D5 #define IDS_NONCONFIGURABLE_REDEFINED 0x13D6 #define IDS_NONWRITABLE_MODIFIED 0x13D7 +#define IDS_TYPEDARRAY_BAD_CTOR_ARG 0x13DA +#define IDS_NOT_TYPEDARRAY 0x13DB +#define IDS_TYPEDARRAY_INVALID_OFFSLEN 0x13DC #define IDS_NOT_DATAVIEW 0x13DF #define IDS_DATAVIEW_NO_ARGUMENT 0x13E0 #define IDS_DATAVIEW_INVALID_ACCESS 0x13E1 diff --git a/dlls/mshtml/tests/es5.js b/dlls/mshtml/tests/es5.js index 57fde4d7c9d..9f7a7eab9ed 100644 --- a/dlls/mshtml/tests/es5.js +++ b/dlls/mshtml/tests/es5.js @@ -32,6 +32,9 @@ var JS_E_INVALID_LENGTH = 0x800a13a5; var JS_E_INVALID_WRITABLE_PROP_DESC = 0x800a13ac; var JS_E_NONCONFIGURABLE_REDEFINED = 0x800a13d6; var JS_E_NONWRITABLE_MODIFIED = 0x800a13d7; +var JS_E_TYPEDARRAY_BAD_CTOR_ARG = 0x800a13da; +var JS_E_NOT_TYPEDARRAY = 0x800a13db; +var JS_E_TYPEDARRAY_INVALID_OFFSLEN = 0x800a13dc; var JS_E_NOT_DATAVIEW = 0x800a13df; var JS_E_DATAVIEW_NO_ARGUMENT = 0x800a13e0; var JS_E_DATAVIEW_INVALID_ACCESS = 0x800a13e1; @@ -1698,7 +1701,7 @@ sync_test("RegExp", function() { });
sync_test("ArrayBuffers & Views", function() { - var i, r, buf, buf2, view, view2, arr; + var i, r, buf, buf2, view, view2, arr, arr2;
var types = [ [ "Int8", 1 ], @@ -2041,6 +2044,190 @@ sync_test("ArrayBuffers & Views", function() { ok(r === undefined, "buf.slice(-9) view(1).setFloat64(0, 11.875) returned " + r); r = view2.getFloat64(0); ok(r === 11.875, "buf.slice(-9) view(1).getFloat64(0) returned " + r); + + for(i = 0; i < types.length; i++) { + var arrType = types[i][0] + "Array", typeSz = types[i][1]; + test_own_props(arrType, [ "BYTES_PER_ELEMENT" ]); + test_not_own_props(arrType, [ "from", "of" ]); + test_own_props(arrType + ".prototype", [ "buffer", "byteLength", "byteOffset", "length", "set", "subarray" ]); + test_not_own_props(arrType + ".prototype", [ + "at", "copyWithin", "entries", "every", "fill", "filter", "find", "findIndex", "forEach", + "includes", "indexOf", "join", "keys", "lastIndexOf", "map", "reduce", "reduceRight", + "reverse", "slice", "some", "sort", "toLocaleString", "toString", "values" + ]); + + arr = eval(arrType); + test_own_data_prop_desc(arr, "BYTES_PER_ELEMENT", false, false, false); + ok(arr.BYTES_PER_ELEMENT === typeSz, arrType + ".BYTES_PER_ELEMENT = " + arr.BYTES_PER_ELEMENT); + r = arr.length; + ok(r === 1, arrType + ".length = " + r); + r = arr.prototype.set.length; + ok(r === 2, arrType + ".prototype.set.length = " + r); + r = arr.prototype.subarray.length; + ok(r === 2, arrType + ".prototype.subarray.length = " + r); + + r = eval("Object.getPrototypeOf(" + arrType + ")"); + ok(r === Function.prototype, arrType + "'s prototype is not Function.prototype: " + r); + r = eval("Object.getPrototypeOf(" + arrType + ".prototype)"); + ok(r === Object.prototype, arrType + ".prototype's prototype is not Object.prototype: " + r); + r = eval("Object.prototype.toString.call(new " + arrType + "(3))"); + ok(r === "[object " + arrType + "]", "Object toString(new " + arrType + "(3)) = " + r); + r = eval(arrType + ".prototype"); + test_own_data_prop_desc(r, "byteLength", false, false, false); + test_own_data_prop_desc(r, "byteOffset", false, false, false); + test_own_data_prop_desc(r, "length", false, false, false); + test_own_data_prop_desc(r, "buffer", false, false, false); + + try { + eval("new " + arrType + "(-1)"); + ok(false, "new " + arrType + "(-1) did not throw exception"); + }catch(ex) { + var n = ex.number >>> 0; + ok(n === JS_E_TYPEDARRAY_INVALID_OFFSLEN, "new " + arrType + "(-1) threw " + n); + } + try { + eval("new " + arrType + "('9')"); + ok(false, "new " + arrType + "('9') did not throw exception"); + }catch(ex) { + var n = ex.number >>> 0; + ok(n === JS_E_TYPEDARRAY_BAD_CTOR_ARG, "new " + arrType + "('9') threw " + n); + } + try { + eval("new " + arrType + "(null)"); + ok(false, "new " + arrType + "(null) did not throw exception"); + }catch(ex) { + var n = ex.number >>> 0; + ok(n === JS_E_TYPEDARRAY_BAD_CTOR_ARG, "new " + arrType + "(null) threw " + n); + } + + arr = eval("new " + arrType + "()"); + ok(arr.byteLength === 0, arrType + "().byteLength = " + arr.byteLength); + ok(arr.byteOffset === 0, arrType + "().byteOffset = " + arr.byteOffset); + ok(arr.length === 0, arrType + "().length = " + arr.length); + ok(arr.buffer.byteLength === 0, arrType + "().buffer.byteLength = " + arr.buffer.byteLength); + test_readonly(arr, "byteLength", 0); + test_readonly(arr, "byteOffset", 0); + test_readonly(arr, "length", 0); + test_own_data_prop_desc(arr, "byteLength", false, false, false); + test_own_data_prop_desc(arr, "byteOffset", false, false, false); + test_own_data_prop_desc(arr, "length", false, false, false); + test_own_data_prop_desc(arr, "buffer", false, false, false); + + Object.freeze(arr); + ok(Object.isFrozen(arr) === true, arrType + "() not frozen"); + + arr = eval(arrType + "(9.1)"); + ok(arr.byteLength === 9 * typeSz, arrType + "(9.1).byteLength = " + arr.byteLength); + ok(arr.byteOffset === 0, arrType + "(9.1).byteOffset = " + arr.byteOffset); + ok(arr.length === 9, arrType + "(9.1).length = " + arr.length); + ok(arr.buffer.byteLength === arr.byteLength, arrType + "(9.1).buffer.byteLength = " + arr.buffer.byteLength); + for(var j = 0; j < 9; j++) + ok(arr[j] === 0, "arr[" + j + "] = " + arr[j]); + arr[5] = 42; + ok(arr[5] === 42, arrType + "(9.1)[5] = " + arr[5]); + arr[9] = 50; + ok(arr[9] === undefined, arrType + "(9.1)[9] = " + arr[9]); + + eval(arrType + ".prototype[6] = 'foo'"); + r = eval(arrType + ".prototype[6]"); + ok(r === undefined, arrType + ".prototype[6] = " + r); + ok(arr[6] === 0, arrType + "(9.1)[6] after set in prototype = " + arr[6]); + arr[6] = 0; + ok(Object.prototype.hasOwnProperty.call(arr, "6"), "'6' not a property of " + arrType + "(9.1)[6]"); + test_own_data_prop_desc(arr, "6", true, true, false); + r = (delete arr[6]); + ok(r === false, "delete " + arrType + "(9.1)[6] returned " + r); + try { + Object.defineProperty(arr, "6", {writable: false, enumerable: false, configurable: true, value: 10}); + ok(false, "redefining " + arrType + "(9.1)[6] with different flags did not throw exception"); + }catch(ex) { + var n = ex.number >>> 0; + ok(n === JS_E_NONCONFIGURABLE_REDEFINED, "redefining " + arrType + "(9.1)[6] with different flags threw " + n); + } + Object.defineProperty(arr, "6", {writable: true, enumerable: true, configurable: false, value: 10}); + ok(arr[6] === 10, arrType + "(9.1)[6] after definition = " + arr[6]); + Object.defineProperty(arr, "6", {writable: true, enumerable: true, configurable: false, value: "foo"}); + if(arrType.substr(0, 5) === "Float") + ok(arr[6] !== arr[6] /* NaN */, arrType + "(9.1)[6] after definition to string = " + arr[6]); + else + ok(arr[6] === 0, arrType + "(9.1)[6] after definition to string = " + arr[6]); + + eval(arrType + ".prototype[100] = 'foobar'"); + r = eval(arrType + ".prototype[100]"); + ok(r === undefined, arrType + ".prototype[100] = " + r); + ok(arr[100] === undefined, arrType + "(9.1)[100] after set in prototype = " + arr[100]); + arr[100] = 0; + ok(arr[100] === undefined, arrType + "(9.1)[100] after set to zero = " + arr[100]); + ok(!Object.prototype.hasOwnProperty.call(arr, "100"), "'100' is a property of " + arrType + "(9.1)[100]"); + r = (delete arr[100]); + ok(r === false, "delete " + arrType + "(9.1)[100] returned " + r); + try { + Object.defineProperty(arr, "100", {writable: false, enumerable: false, configurable: true, value: 10}); + ok(false, "redefining " + arrType + "(9.1)[100] with different flags did not throw exception"); + }catch(ex) { + var n = ex.number >>> 0; + ok(n === JS_E_NONCONFIGURABLE_REDEFINED, "redefining " + arrType + "(9.1)[100] with different flags threw " + n); + } + Object.defineProperty(arr, "100", {writable: true, enumerable: true, configurable: false, value: 10}); + ok(arr[100] === undefined, arrType + "(9.1)[100] after defined to 10 = " + arr[100]); + ok(!Object.prototype.hasOwnProperty.call(arr, "100"), "'100' is a property of " + arrType + "(9.1)[100] after definition"); + ok(arr[100] === undefined, arrType + "(9.1)[100] after definition = " + arr[100]); + + r = 0; + for(var idx in arr) { + ok(idx === ""+r, arrType + "(9.1) enum idx " + r + " = " + idx); + r++; + } + ok(r === 9, arrType + "(9.1) enum did " + r + " iterations"); + + eval(arrType + ".prototype[-1] = 'barfoo'"); + r = eval(arrType + ".prototype[-1]"); + ok(r === "barfoo", arrType + ".prototype[-1] = " + r); + ok(arr[-1] === "barfoo", arrType + "(9.1)[-1] after set in prototype = " + arr[-1]); + + eval(arrType + ".prototype.foo = 'bar'"); + r = eval(arrType + ".prototype.foo = 'bar'"); + ok(r === "bar", arrType + ".prototype.foo = " + r); + ok(arr.foo === "bar", arrType + "(9.1).foo after set in prototype = " + arr.foo); + Object.freeze(arr); + ok(Object.isFrozen(arr) === true, arrType + "(9.1) not frozen"); + arr = eval(arrType + ".prototype"); + delete arr[-1]; + delete arr.foo; + } + + arr = new Int16Array(2); + arr[0] = 65535; + arr[1] = -65535; + ok(arr[0] == -1, "16-bit arr[0] after overflow = " + arr[0]); + ok(arr[1] == 1, "16-bit arr[1] after overflow = " + arr[1]); + + arr = new Uint8Array(2); + arr[0] = -2; + arr[1] = 258; + ok(arr[0] == 254, "8-bit arr[0] after overflow = " + arr[0]); + ok(arr[1] == 2, "8-bit arr[1] after overflow = " + arr[1]); + + arr = new Int8Array(); + arr2 = new Int32Array(); + + /* methods are incompatible, even though thrown error is not explicit */ + ok(Uint16Array.prototype.subarray !== Int32Array.prototype.subarray, "Uint16Array and Int32Array have same subarray methods"); + ok(Int8Array.prototype.set !== Float32Array.prototype.set, "Int8Array and Float32Array have same set methods"); + try { + Uint8Array.prototype.set.call(arr, [12, 50]); + ok(false, "calling Uint8Array's set with Int8Array context did not throw exception"); + }catch(ex) { + var n = ex.number >>> 0; + ok(n === JS_E_NOT_TYPEDARRAY, "calling Uint8Array's set with Int8Array context threw " + n); + } + try { + Uint32Array.prototype.subarray.call(arr2, 0); + ok(false, "calling Uint32Array's subarray with Int32Array context did not throw exception"); + }catch(ex) { + var n = ex.number >>> 0; + ok(n === JS_E_NOT_TYPEDARRAY, "calling Uint32Array's subarray with Int32Array context threw " + n); + } });
sync_test("builtin_context", function() {