From: Tiago Natel de Moura Date: Wed, 27 Nov 2019 14:02:04 +0000 (+0000) Subject: Introduced the ArrayBuffer object. X-Git-Tag: 0.3.8~28 X-Git-Url: http://git.kaiwu.me/sitemap.xml?a=commitdiff_plain;h=7e3088c7462a5d4e09693c5aeb90a9b7e5eb7ccb;p=njs.git Introduced the ArrayBuffer object. --- diff --git a/auto/sources b/auto/sources index 675e45c6..bd182eba 100644 --- a/auto/sources +++ b/auto/sources @@ -51,6 +51,7 @@ NJS_LIB_SRCS=" \ src/njs_parser_expression.c \ src/njs_generator.c \ src/njs_disassembler.c \ + src/njs_array_buffer.c \ " NJS_LIB_TEST_SRCS=" \ diff --git a/src/njs_array_buffer.c b/src/njs_array_buffer.c new file mode 100644 index 00000000..6b200f31 --- /dev/null +++ b/src/njs_array_buffer.c @@ -0,0 +1,268 @@ +/* + * Copyright (C) Igor Sysoev + * Copyright (C) NGINX, Inc. + */ + + +#include + + +njs_array_buffer_t * +njs_array_buffer_alloc(njs_vm_t *vm, uint64_t size) +{ + njs_object_t *proto; + njs_array_buffer_t *array; + + if (njs_slow_path(size > UINT32_MAX)) { + goto overflow; + } + + array = njs_mp_alloc(vm->mem_pool, sizeof(njs_array_buffer_t)); + if (njs_slow_path(array == NULL)) { + goto memory_error; + } + + if (size > 0) { + array->u.data = njs_mp_zalloc(vm->mem_pool, size); + if (njs_slow_path(array->u.data == NULL)) { + goto memory_error; + } + } + + proto = &vm->prototypes[NJS_OBJ_TYPE_ARRAY_BUFFER].object; + + njs_lvlhsh_init(&array->object.hash); + njs_lvlhsh_init(&array->object.shared_hash); + array->object.__proto__ = proto; + array->object.type = NJS_ARRAY_BUFFER; + array->object.shared = 0; + array->object.extensible = 1; + array->size = size; + + return array; + +memory_error: + + njs_memory_error(vm); + + return NULL; + +overflow: + + njs_range_error(vm, "Invalid array length"); + + return NULL; +} + + +static njs_int_t +njs_array_buffer_constructor(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t unused) +{ + uint32_t size; + njs_int_t ret; + njs_value_t *value; + njs_array_buffer_t *array; + + if (!vm->top_frame->ctor) { + njs_type_error(vm, "Constructor ArrayBuffer requires 'new'"); + return NJS_ERROR; + } + + size = 0; + value = njs_arg(args, nargs, 1); + + ret = njs_value_to_index(vm, value, &size); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + + array = njs_array_buffer_alloc(vm, size); + if (njs_slow_path(array == NULL)) { + return NJS_ERROR; + } + + njs_set_array_buffer(&vm->retval, array); + + return NJS_OK; +} + + +static njs_int_t +njs_array_buffer_get_this(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t unused) +{ + vm->retval = args[0]; + + return NJS_OK; +} + + +static const njs_object_prop_t njs_array_buffer_constructor_properties[] = +{ + /* ArrayBuffer.name == "ArrayBuffer". */ + { + .type = NJS_PROPERTY, + .name = njs_string("name"), + .value = njs_string("ArrayBuffer"), + .configurable = 1, + }, + + /* ArrayBuffer.length == 1. */ + { + .type = NJS_PROPERTY, + .name = njs_string("length"), + .value = njs_value(NJS_NUMBER, 1, 1.0), + .configurable = 1, + }, + + /* ArrayBuffer.prototype. */ + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("prototype"), + .value = njs_prop_handler(njs_object_prototype_create), + }, + + /* ArrayBuffer[Symbol.species] */ + { + .type = NJS_PROPERTY, + .name = njs_wellknown_symbol(NJS_SYMBOL_SPECIES), + .value = njs_value(NJS_INVALID, 1, NAN), + .getter = njs_native_function(njs_array_buffer_get_this, 0), + .setter = njs_value(NJS_UNDEFINED, 0, NAN), + .writable = NJS_ATTRIBUTE_UNSET, + .configurable = 1, + .enumerable = 0, + }, +}; + + +const njs_object_init_t njs_array_buffer_constructor_init = { + njs_array_buffer_constructor_properties, + njs_nitems(njs_array_buffer_constructor_properties), +}; + + +static njs_int_t +njs_array_buffer_prototype_byte_length(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t unused) +{ + njs_value_t *value; + njs_array_buffer_t *array; + + value = njs_arg(args, nargs, 0); + + if (!njs_is_array_buffer(value)) { + njs_type_error(vm, "Method ArrayBuffer.prototype.byteLength called " + "on incompatible receiver"); + return NJS_ERROR; + } + + array = njs_array_buffer(value); + + njs_set_number(&vm->retval, array->size); + + return NJS_OK; +} + + +static njs_int_t +njs_array_buffer_prototype_slice(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t unused) +{ + int64_t len, start, end; + njs_int_t ret; + njs_value_t *value; + njs_array_buffer_t *this, *buffer; + + value = njs_arg(args, nargs, 0); + + if (!njs_is_array_buffer(value)) { + njs_type_error(vm, "Method ArrayBuffer.prototype.slice called " + "on incompatible receiver"); + return NJS_ERROR; + } + + this = njs_array_buffer(value); + len = njs_array_buffer_size(this); + end = len; + + value = njs_arg(args, nargs, 1); + + ret = njs_value_to_integer(vm, value, &start); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + value = njs_arg(args, nargs, 2); + + if (!njs_is_undefined(value)) { + ret = njs_value_to_integer(vm, value, &end); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + } + + buffer = njs_array_buffer_slice(vm, this, start, end); + if (njs_slow_path(buffer == NULL)) { + return NJS_ERROR; + } + + njs_set_array_buffer(&vm->retval, buffer); + + return NJS_OK; +} + + +static const njs_object_prop_t njs_array_buffer_prototype_properties[] = +{ + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("constructor"), + .value = njs_prop_handler(njs_object_prototype_create_constructor), + .writable = 1, + .configurable = 1, + }, + + { + .type = NJS_PROPERTY, + .name = njs_string("byteLength"), + .value = njs_value(NJS_INVALID, 1, NAN), + .getter = njs_native_function(njs_array_buffer_prototype_byte_length, + 0), + .setter = njs_value(NJS_UNDEFINED, 0, NAN), + .writable = NJS_ATTRIBUTE_UNSET, + .configurable = 1, + .enumerable = 0, + }, + + { + .type = NJS_PROPERTY, + .name = njs_string("slice"), + .value = njs_native_function(njs_array_buffer_prototype_slice, 2), + .writable = 1, + .configurable = 1, + .enumerable = 0, + }, + + { + .type = NJS_PROPERTY, + .name = njs_wellknown_symbol(NJS_SYMBOL_TO_STRING_TAG), + .value = njs_string("ArrayBuffer"), + .configurable = 1, + }, +}; + + +const njs_object_init_t njs_array_buffer_prototype_init = { + njs_array_buffer_prototype_properties, + njs_nitems(njs_array_buffer_prototype_properties), +}; + + +const njs_object_type_init_t njs_array_buffer_type_init = { + .constructor = njs_native_ctor(njs_array_buffer_constructor, 1, 0), + .prototype_props = &njs_array_buffer_prototype_init, + .constructor_props = &njs_array_buffer_constructor_init, + .prototype_value = { .object = { .type = NJS_OBJECT } }, +}; diff --git a/src/njs_array_buffer.h b/src/njs_array_buffer.h new file mode 100644 index 00000000..7616c305 --- /dev/null +++ b/src/njs_array_buffer.h @@ -0,0 +1,46 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) NGINX, Inc. + */ + +#ifndef _NJS_ARRAY_BUFFER_H_INCLUDED_ +#define _NJS_ARRAY_BUFFER_H_INCLUDED_ + + +#define njs_array_buffer_size(buffer) \ + ((buffer)->size) + + +njs_array_buffer_t *njs_array_buffer_alloc(njs_vm_t *vm, uint64_t size); + + +njs_inline njs_array_buffer_t * +njs_array_buffer_slice(njs_vm_t *vm, njs_array_buffer_t *this, int64_t start, + int64_t end) +{ + int64_t len, new_len, first, final; + njs_array_buffer_t *new_buffer; + + len = njs_array_buffer_size(this); + + first = (start < 0) ? njs_max(len + start, 0) : njs_min(start, len); + final = (end < 0) ? njs_max(len + end, 0) : njs_min(end, len); + + new_len = njs_max(final - first, 0); + + new_buffer = njs_array_buffer_alloc(vm, new_len); + if (new_buffer == NULL) { + return NULL; + } + + memcpy(new_buffer->u.u8, &this->u.u8[first], new_len); + + return new_buffer; +} + + +extern const njs_object_type_init_t njs_array_buffer_type_init; + + +#endif /* _NJS_ARRAY_BUFFER_H_INCLUDED_ */ diff --git a/src/njs_builtin.c b/src/njs_builtin.c index e429efa3..9c6b808e 100644 --- a/src/njs_builtin.c +++ b/src/njs_builtin.c @@ -58,6 +58,7 @@ static const njs_object_type_init_t *const &njs_obj_type_init, &njs_array_type_init, + &njs_array_buffer_type_init, &njs_boolean_type_init, &njs_number_type_init, &njs_symbol_type_init, @@ -1097,6 +1098,16 @@ static const njs_object_prop_t njs_global_this_object_properties[] = .configurable = 1, }, + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("ArrayBuffer"), + .value = njs_prop_handler2(njs_top_level_constructor, + NJS_OBJ_TYPE_ARRAY_BUFFER, + NJS_ARRAY_BUFFER_HASH), + .writable = 1, + .configurable = 1, + }, + { .type = NJS_PROPERTY_HANDLER, .name = njs_string("Boolean"), diff --git a/src/njs_json.c b/src/njs_json.c index be02fff7..82d5df5f 100644 --- a/src/njs_json.c +++ b/src/njs_json.c @@ -2109,6 +2109,7 @@ njs_dump_is_object(const njs_value_t *value) { return (value->type == NJS_OBJECT && !njs_object(value)->error_data) || (value->type == NJS_ARRAY) + || (value->type == NJS_ARRAY_BUFFER) || (value->type == NJS_OBJECT_VALUE) || njs_dump_is_external_object(value); } diff --git a/src/njs_main.h b/src/njs_main.h index fc83fc4d..ce12807b 100644 --- a/src/njs_main.h +++ b/src/njs_main.h @@ -59,6 +59,7 @@ #include #include #include +#include #include #include #include diff --git a/src/njs_object.c b/src/njs_object.c index 5585b516..c42bc152 100644 --- a/src/njs_object.c +++ b/src/njs_object.c @@ -2356,6 +2356,8 @@ static const njs_value_t njs_object_object_string = njs_long_string("[object Object]"); static const njs_value_t njs_object_array_string = njs_string("[object Array]"); +static const njs_value_t njs_object_array_buffer_string = + njs_long_string("[object ArrayBuffer]"); static const njs_value_t njs_object_function_string = njs_long_string("[object Function]"); static const njs_value_t njs_object_regexp_string = @@ -2408,6 +2410,7 @@ njs_object_prototype_to_string(njs_vm_t *vm, njs_value_t *args, &njs_object_regexp_string, &njs_object_date_string, &njs_object_object_string, + &njs_object_array_buffer_string, }; value = njs_argument(args, 0); diff --git a/src/njs_object_hash.h b/src/njs_object_hash.h index 4a65b202..9049ba3f 100644 --- a/src/njs_object_hash.h +++ b/src/njs_object_hash.h @@ -561,4 +561,19 @@ 'U'), 'R'), 'I'), 'E'), 'r'), 'r'), 'o'), 'r') +#define NJS_ARRAY_BUFFER_HASH \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add(NJS_DJB_HASH_INIT, \ + 'A'), 'r'), 'r'), 'a'), 'y'), 'B'), 'u'), 'f'), 'f'), 'e'), 'r') + + #endif /* _NJS_OBJECT_HASH_H_INCLUDED_ */ diff --git a/src/njs_value.c b/src/njs_value.c index 7f2b653a..2e3a65bf 100644 --- a/src/njs_value.c +++ b/src/njs_value.c @@ -338,6 +338,9 @@ njs_type_string(njs_value_type_t type) case NJS_ARRAY: return "array"; + case NJS_ARRAY_BUFFER: + return "object arraybuffer"; + case NJS_OBJECT_BOOLEAN: return "object boolean"; @@ -547,6 +550,7 @@ njs_property_query(njs_vm_t *vm, njs_property_query_t *pq, njs_value_t *value, case NJS_OBJECT: case NJS_ARRAY: + case NJS_ARRAY_BUFFER: case NJS_OBJECT_BOOLEAN: case NJS_OBJECT_NUMBER: case NJS_OBJECT_SYMBOL: diff --git a/src/njs_value.h b/src/njs_value.h index 4651fa0d..d36418f7 100644 --- a/src/njs_value.h +++ b/src/njs_value.h @@ -68,6 +68,7 @@ typedef enum { NJS_REGEXP = 0x17, NJS_DATE = 0x18, NJS_OBJECT_VALUE = 0x19, + NJS_ARRAY_BUFFER = 0x1A, NJS_VALUE_TYPE_MAX } njs_value_type_t; @@ -79,6 +80,7 @@ typedef struct njs_object_value_s njs_object_value_t; typedef struct njs_function_lambda_s njs_function_lambda_t; typedef struct njs_regexp_pattern_s njs_regexp_pattern_t; typedef struct njs_array_s njs_array_t; +typedef struct njs_array_buffer_s njs_array_buffer_t; typedef struct njs_regexp_s njs_regexp_t; typedef struct njs_date_s njs_date_t; typedef struct njs_property_next_s njs_property_next_t; @@ -138,6 +140,7 @@ union njs_value_s { double number; njs_object_t *object; njs_array_t *array; + njs_array_buffer_t *array_buffer; njs_object_value_t *object_value; njs_function_t *function; njs_function_lambda_t *lambda; @@ -223,6 +226,26 @@ struct njs_array_s { }; +struct njs_array_buffer_s { + njs_object_t object; + size_t size; + union { + uint8_t *u8; + uint16_t *u16; + uint32_t *u32; + uint64_t *u64; + int8_t *i8; + int16_t *i16; + int32_t *i32; + int64_t *i64; + float *f32; + double *f64; + + void *data; + } u; +}; + + typedef struct { union { uint32_t count; @@ -601,6 +624,10 @@ typedef struct { ((value)->type == NJS_ARRAY) +#define njs_is_array_buffer(value) \ + ((value)->type == NJS_ARRAY_BUFFER) + + #define njs_is_function(value) \ ((value)->type == NJS_FUNCTION) @@ -669,6 +696,10 @@ typedef struct { ((value)->data.u.array->length) +#define njs_array_buffer(value) \ + ((value)->data.u.array_buffer) + + #define njs_array_start(value) \ ((value)->data.u.array->start) @@ -816,6 +847,15 @@ njs_set_array(njs_value_t *value, njs_array_t *array) } +njs_inline void +njs_set_array_buffer(njs_value_t *value, njs_array_buffer_t *array) +{ + value->data.u.array_buffer = array; + value->type = NJS_ARRAY_BUFFER; + value->data.truth = 1; +} + + njs_inline void njs_set_function(njs_value_t *value, njs_function_t *function) { diff --git a/src/njs_vm.h b/src/njs_vm.h index bc44df6c..612924d2 100644 --- a/src/njs_vm.h +++ b/src/njs_vm.h @@ -82,6 +82,7 @@ typedef enum { typedef enum { NJS_OBJ_TYPE_OBJECT = 0, NJS_OBJ_TYPE_ARRAY, + NJS_OBJ_TYPE_ARRAY_BUFFER, NJS_OBJ_TYPE_BOOLEAN, NJS_OBJ_TYPE_NUMBER, NJS_OBJ_TYPE_SYMBOL, diff --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c index 7db952bb..c9ef5e42 100644 --- a/src/test/njs_unit_test.c +++ b/src/test/njs_unit_test.c @@ -4733,6 +4733,55 @@ static njs_unit_test_t njs_test[] = "Array.prototype.fill.call(o, 2).a"), njs_str("4") }, + { njs_str("ArrayBuffer()"), + njs_str("TypeError: Constructor ArrayBuffer requires 'new'") }, + + { njs_str("new ArrayBuffer()"), + njs_str("[object ArrayBuffer]") }, + + { njs_str("ArrayBuffer.prototype.constructor.name === 'ArrayBuffer'"), + njs_str("true") }, + + { njs_str("ArrayBuffer.prototype.constructor()"), + njs_str("TypeError: Constructor ArrayBuffer requires 'new'") }, + + { njs_str("ArrayBuffer.name"), + njs_str("ArrayBuffer") }, + + { njs_str("ArrayBuffer[Symbol.species]"), + njs_str("[object Function]") }, + + { njs_str("ArrayBuffer.prototype[Symbol.toStringTag]"), + njs_str("ArrayBuffer") }, + + { njs_str("var desc = Object.getOwnPropertyDescriptor(ArrayBuffer," + "Symbol.species); desc.get"), + njs_str("[object Function]") }, + + { njs_str("var ctor = ArrayBuffer[Symbol.species]; var a = new ctor(100);" + "a.byteLength;"), + njs_str("100") }, + + { njs_str("var a = new ArrayBuffer(); a.byteLength"), + njs_str("0") }, + + { njs_str("var a = new ArrayBuffer.prototype.constructor(10); a.byteLength"), + njs_str("10") }, + + { njs_str("var get = Object.getOwnPropertyDescriptor(ArrayBuffer.prototype, 'byteLength').get;" + "get.call([])"), + njs_str("TypeError: Method ArrayBuffer.prototype.byteLength called on incompatible receiver") }, + + { njs_str("[undefined, 1, 10, 1000, null, NaN, false, {}, [1,2,3], Object(1),'10'," + " -1, -Infinity, Infinity, 2**50]" + ".map(v=>{ var a; try { a = new ArrayBuffer(v) } catch (e) {return e.name} return a.byteLength})"), + njs_str("0,1,10,1000,0,0,0,0,0,1,10,RangeError,RangeError,RangeError,RangeError") }, + + { njs_str("var buffer = new ArrayBuffer(16);" + "[[4,12], [-1,-1], [-1,10], [0, -1], [0, -16], [0,-17]]" + ".map(pr=>buffer.slice(pr[0], pr[1]).byteLength)"), + njs_str("8,0,0,15,0,0") }, + #if NJS_HAVE_LARGE_STACK { njs_str("var o = Object({length: 3});" "Object.defineProperty(o, '0', {set: function(v){this[0] = 2 * v}});"