From: hongzhidao Date: Tue, 11 Jun 2019 02:23:56 +0000 (-0400) Subject: Added property getter/setter support in Object.defineProperty(). X-Git-Tag: 0.3.3~5 X-Git-Url: http://git.kaiwu.me/postgresql/log/contrib/postgres_fdw/%7B@url%7D?a=commitdiff_plain;h=85880f216d93d03d6107c9d828decdc211968c89;p=njs.git Added property getter/setter support in Object.defineProperty(). In collaboration with Dmitry Volyntsev. --- diff --git a/njs/njs_array.c b/njs/njs_array.c index abdf73d0..c935647f 100644 --- a/njs/njs_array.c +++ b/njs/njs_array.c @@ -29,6 +29,8 @@ typedef struct { } u; njs_value_t length; + nxt_int_t start; + nxt_int_t end; } njs_array_fill_t; @@ -100,6 +102,8 @@ static njs_ret_t njs_array_prototype_join_continuation(njs_vm_t *vm, static njs_value_t *njs_array_copy(njs_value_t *dst, njs_value_t *src); static njs_ret_t njs_array_prototype_fill_continuation(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs, njs_index_t unused); +static njs_ret_t njs_array_prototype_fill_object_continuation(njs_vm_t *vm, + njs_value_t *args, nxt_uint_t nargs, njs_index_t unused); static njs_ret_t njs_array_prototype_for_each_continuation(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs, njs_index_t unused); static njs_ret_t njs_array_prototype_some_continuation(njs_vm_t *vm, @@ -524,8 +528,13 @@ njs_array_prototype_slice(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs, slice = njs_vm_continuation(vm); slice->u.cont.function = njs_array_prototype_slice_continuation; - ret = njs_value_property(vm, &args[0], &njs_string_length, &slice->length); - if (nxt_slow_path(ret == NXT_ERROR || ret == NJS_TRAP)) { + ret = njs_value_property(vm, &args[0], &njs_string_length, &slice->length, + 0); + + if (nxt_slow_path(ret == NXT_ERROR + || ret == NJS_TRAP + || ret == NJS_APPLIED)) + { return ret; } @@ -671,7 +680,7 @@ njs_array_prototype_slice_copy(njs_vm_t *vm, njs_value_t *this, njs_uint32_to_string(&name, start++); value = &array->start[n++]; - ret = njs_value_property(vm, this, &name, value); + ret = njs_value_property(vm, this, &name, value, 0); if (ret != NXT_OK) { *value = njs_value_invalid; @@ -1426,8 +1435,13 @@ njs_array_prototype_fill(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs, fill = njs_vm_continuation(vm); fill->u.cont.function = njs_array_prototype_fill_continuation; - ret = njs_value_property(vm, this, &njs_string_length, &fill->length); - if (nxt_slow_path(ret == NXT_ERROR || ret == NJS_TRAP)) { + ret = njs_value_property(vm, this, &njs_string_length, &fill->length, + 0); + + if (nxt_slow_path(ret == NXT_ERROR + || ret == NJS_TRAP + || ret == NJS_APPLIED)) + { return ret; } } @@ -1440,10 +1454,8 @@ static njs_ret_t njs_array_prototype_fill_continuation(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs, njs_index_t unused) { - njs_ret_t ret; nxt_int_t i, start, end, length; njs_array_t *array; - njs_value_t name; njs_object_t *object; njs_array_fill_t *fill; const njs_value_t *this, *value; @@ -1465,12 +1477,13 @@ njs_array_prototype_fill_continuation(njs_vm_t *vm, njs_value_t *args, return NXT_OK; } + fill = njs_vm_continuation(vm); + if (njs_is_array(this)) { array = this->data.u.array; length = array->length; } else { - fill = njs_vm_continuation(vm); if (nxt_slow_path(!njs_is_primitive(&fill->length))) { njs_vm_trap_value(vm, &fill->length); @@ -1494,22 +1507,47 @@ njs_array_prototype_fill_continuation(njs_vm_t *vm, njs_value_t *args, value = njs_arg(args, nargs, 1); - vm->retval = *this; - if (array != NULL) { for (i = start; i < end; i++) { array->start[i] = *value; } + vm->retval = *this; + return NXT_OK; } - for (i = start; i < end; i++) { - njs_uint32_to_string(&name, i); + fill->u.cont.function = njs_array_prototype_fill_object_continuation; + fill->start = start; + fill->end = end; + + return njs_array_prototype_fill_object_continuation(vm, args, nargs, + unused); +} + + +static njs_ret_t +njs_array_prototype_fill_object_continuation(njs_vm_t *vm, njs_value_t *args, + nxt_uint_t nargs, njs_index_t unused) +{ + njs_ret_t ret; + nxt_int_t end; + njs_value_t name; + njs_array_fill_t *fill; + const njs_value_t *value; + + fill = njs_vm_continuation(vm); + end = fill->end; + + vm->retval = *njs_arg(args, nargs, 0); + value = njs_arg(args, nargs, 1); + + while (fill->start < end) { + njs_uint32_to_string(&name, fill->start++); ret = njs_value_property_set(vm, &vm->retval, &name, - (njs_value_t *) value); - if (nxt_slow_path(ret == NXT_ERROR)) { + (njs_value_t *) value, 0); + if (nxt_slow_path(ret == NXT_ERROR || ret == NJS_APPLIED)) { return ret; } } diff --git a/njs/njs_function.c b/njs/njs_function.c index f649c2a4..2c58a519 100644 --- a/njs/njs_function.c +++ b/njs/njs_function.c @@ -1042,7 +1042,7 @@ njs_function_prototype_apply(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs, return NXT_ERROR; } - ret = njs_value_property(vm, arr_like, &njs_string_length, &length); + ret = njs_value_property(vm, arr_like, &njs_string_length, &length, 0); if (nxt_slow_path(ret == NXT_ERROR)) { return ret; } @@ -1064,7 +1064,7 @@ njs_function_prototype_apply(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs, for (i = 0; i < nargs; i++) { njs_uint32_to_string(&name, i); - ret = njs_value_property(vm, arr_like, &name, &args[i]); + ret = njs_value_property(vm, arr_like, &name, &args[i], 0); if (nxt_slow_path(ret == NXT_ERROR)) { return ret; } diff --git a/njs/njs_object.c b/njs/njs_object.c index 5af86ba7..92e9ec87 100644 --- a/njs/njs_object.c +++ b/njs/njs_object.c @@ -1421,7 +1421,11 @@ njs_object_is_frozen(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs, break; } - if (prop->writable || prop->configurable) { + if (prop->configurable) { + goto done; + } + + if (njs_is_data_descriptor(prop) && prop->writable) { goto done; } } diff --git a/njs/njs_object.h b/njs/njs_object.h index daeda7ab..701f8a06 100644 --- a/njs/njs_object.h +++ b/njs/njs_object.h @@ -19,7 +19,10 @@ typedef enum { /* * Attributes are generally used as Boolean values. - * The UNSET value is used internally only by njs_define_property(). + * The UNSET value is can be seen: + * for newly created property descriptors in njs_define_property(), + * for writable attribute of accessor descriptors (desc->writable + * cannot be used as a boolean value). */ typedef enum { NJS_ATTRIBUTE_FALSE = 0, @@ -32,7 +35,10 @@ typedef struct { /* Must be aligned to njs_value_t. */ njs_value_t value; njs_value_t name; + njs_value_t getter; + njs_value_t setter; + /* TODO: get rid of types */ njs_object_prop_type_t type:8; /* 3 bits */ njs_object_attribute_t writable:8; /* 2 bits */ @@ -41,6 +47,18 @@ typedef struct { } njs_object_prop_t; +#define njs_is_data_descriptor(prop) \ + ((prop)->writable != NJS_ATTRIBUTE_UNSET || njs_is_valid(&(prop)->value)) + + +#define njs_is_accessor_descriptor(prop) \ + (njs_is_valid(&(prop)->getter) || njs_is_valid(&(prop)->setter)) + + +#define njs_is_generic_descriptor(prop) \ + (!njs_is_data_descriptor(prop) && !njs_is_accessor_descriptor(prop)) + + typedef struct { nxt_lvlhsh_query_t lhq; @@ -109,9 +127,9 @@ njs_ret_t njs_object_prototype_to_string(njs_vm_t *vm, njs_value_t *args, njs_ret_t njs_property_query(njs_vm_t *vm, njs_property_query_t *pq, njs_value_t *object, const njs_value_t *property); njs_ret_t njs_value_property(njs_vm_t *vm, const njs_value_t *value, - const njs_value_t *property, njs_value_t *retval); + const njs_value_t *property, njs_value_t *retval, size_t advance); njs_ret_t njs_value_property_set(njs_vm_t *vm, njs_value_t *object, - const njs_value_t *property, njs_value_t *value); + const njs_value_t *property, njs_value_t *value, size_t advance); njs_object_prop_t *njs_object_prop_alloc(njs_vm_t *vm, const njs_value_t *name, const njs_value_t *value, uint8_t attributes); njs_object_prop_t *njs_object_property(njs_vm_t *vm, const njs_object_t *obj, diff --git a/njs/njs_object_hash.h b/njs/njs_object_hash.h index 4c028bce..87abb7cb 100644 --- a/njs/njs_object_hash.h +++ b/njs/njs_object_hash.h @@ -271,4 +271,18 @@ 'w'), 'r'), 'i'), 't'), 'a'), 'b'), 'l'), 'e') +#define NJS_GET_HASH \ + nxt_djb_hash_add( \ + nxt_djb_hash_add( \ + nxt_djb_hash_add(NXT_DJB_HASH_INIT, \ + 'g'), 'e'), 't') + + +#define NJS_SET_HASH \ + nxt_djb_hash_add( \ + nxt_djb_hash_add( \ + nxt_djb_hash_add(NXT_DJB_HASH_INIT, \ + 's'), 'e'), 't') + + #endif /* _NJS_OBJECT_HASH_H_INCLUDED_ */ diff --git a/njs/njs_object_property.c b/njs/njs_object_property.c index f87b7c86..b415213a 100644 --- a/njs/njs_object_property.c +++ b/njs/njs_object_property.c @@ -474,14 +474,15 @@ njs_external_property_delete(njs_vm_t *vm, njs_value_t *value, * NXT_OK property has been found in object, * retval will contain the property's value * - * NXT_DECLINED property was not found in object, - * NJS_TRAP the property trap must be called, + * NXT_DECLINED property was not found in object + * NJS_TRAP the property trap must be called + * NJS_APPLIED the property getter was applied * NXT_ERROR exception has been thrown. * retval will contain undefined */ njs_ret_t njs_value_property(njs_vm_t *vm, const njs_value_t *value, - const njs_value_t *property, njs_value_t *retval) + const njs_value_t *property, njs_value_t *retval, size_t advance) { njs_ret_t ret; njs_object_prop_t *prop; @@ -512,8 +513,19 @@ njs_value_property(njs_vm_t *vm, const njs_value_t *value, /* Fall through. */ case NJS_PROPERTY: - *retval = prop->value; - break; + if (njs_is_data_descriptor(prop)) { + *retval = prop->value; + break; + } + + if (njs_is_undefined(&prop->getter)) { + *retval = njs_value_undefined; + break; + } + + return njs_function_activate(vm, prop->getter.data.u.function, + value, NULL, 0, (njs_index_t) retval, + advance); case NJS_PROPERTY_HANDLER: pq.scratch = *prop; @@ -557,11 +569,12 @@ njs_value_property(njs_vm_t *vm, const njs_value_t *value, /* * NXT_OK property has been set successfully * NJS_TRAP the property trap must be called + * NJS_APPLIED the property setter was applied * NXT_ERROR exception has been thrown. */ njs_ret_t njs_value_property_set(njs_vm_t *vm, njs_value_t *object, - const njs_value_t *property, njs_value_t *value) + const njs_value_t *property, njs_value_t *value, size_t advance) { njs_ret_t ret; njs_object_prop_t *prop, *shared; @@ -584,9 +597,17 @@ njs_value_property_set(njs_vm_t *vm, njs_value_t *object, case NXT_OK: prop = pq.lhq.value; - if (nxt_slow_path(!prop->writable)) { + if (njs_is_data_descriptor(prop)) { + if (!prop->writable) { + njs_type_error(vm, + "Cannot assign to read-only property \"%V\" of %s", + &pq.lhq.key, njs_type_string(object->type)); + return NXT_ERROR; + } + + } else if (!njs_is_function(&prop->setter)) { njs_type_error(vm, - "Cannot assign to read-only property \"%V\" of %s", + "Cannot set property \"%V\" of %s which has only a getter", &pq.lhq.key, njs_type_string(object->type)); return NXT_ERROR; } @@ -608,6 +629,14 @@ njs_value_property_set(njs_vm_t *vm, njs_value_t *object, break; } + if (njs_is_function(&prop->setter)) { + return njs_function_activate(vm, + prop->setter.data.u.function, + object, value, 1, + (njs_index_t) &vm->retval, + advance); + } + goto found; case NJS_PROPERTY_REF: @@ -704,6 +733,9 @@ njs_object_prop_alloc(njs_vm_t *vm, const njs_value_t *name, prop->enumerable = attributes; prop->configurable = attributes; + prop->getter = njs_value_invalid; + prop->setter = njs_value_invalid; + return prop; } @@ -774,12 +806,25 @@ njs_object_prop_define(njs_vm_t *vm, njs_value_t *object, if (nxt_fast_path(ret == NXT_DECLINED)) { - if (!njs_is_valid(&prop->value)) { - prop->value = njs_value_undefined; - } + /* 6.2.5.6 CompletePropertypropriptor */ + + if (njs_is_accessor_descriptor(prop)) { + if (!njs_is_valid(&prop->getter)) { + prop->getter = njs_value_undefined; + } + + if (!njs_is_valid(&prop->setter)) { + prop->setter = njs_value_undefined; + } + + } else { + if (prop->writable == NJS_ATTRIBUTE_UNSET) { + prop->writable = 0; + } - if (prop->writable == NJS_ATTRIBUTE_UNSET) { - prop->writable = 0; + if (!njs_is_valid(&prop->value)) { + prop->value = njs_value_undefined; + } } if (prop->enumerable == NJS_ATTRIBUTE_UNSET) { @@ -826,8 +871,9 @@ njs_object_prop_define(njs_vm_t *vm, njs_value_t *object, prev = pq.lhq.value; switch (prev->type) { - case NJS_PROPERTY: case NJS_METHOD: + case NJS_PROPERTY: + case NJS_PROPERTY_HANDLER: break; case NJS_PROPERTY_REF: @@ -839,18 +885,6 @@ njs_object_prop_define(njs_vm_t *vm, njs_value_t *object, return NXT_OK; - case NJS_PROPERTY_HANDLER: - if (prev->writable && njs_is_valid(&prop->value)) { - ret = prev->value.data.u.prop_handler(vm, object, &prop->value, - &vm->retval); - - if (nxt_slow_path(ret != NXT_OK)) { - return ret; - } - } - - return NXT_OK; - default: njs_internal_error(vm, "unexpected property type \"%s\" " "while defining property", @@ -859,18 +893,11 @@ njs_object_prop_define(njs_vm_t *vm, njs_value_t *object, return NXT_ERROR; } - if (!prev->configurable) { + /* 9.1.6.3 ValidateAndApplyPropertyDescriptor */ - if (njs_is_valid(&prop->value) - && prev->writable == NJS_ATTRIBUTE_FALSE - && !njs_values_strict_equal(&prop->value, &prev->value)) - { - goto exception; - } + if (!prev->configurable) { - if (prop->writable == NJS_ATTRIBUTE_TRUE - && prev->writable == NJS_ATTRIBUTE_FALSE) - { + if (prop->configurable == NJS_ATTRIBUTE_TRUE) { goto exception; } @@ -879,14 +906,100 @@ njs_object_prop_define(njs_vm_t *vm, njs_value_t *object, { goto exception; } + } - if (prop->configurable == NJS_ATTRIBUTE_TRUE) { + if (njs_is_generic_descriptor(prop)) { + goto done; + } + + if (njs_is_data_descriptor(prev) != njs_is_data_descriptor(prop)) { + if (!prev->configurable) { goto exception; } + + /* + * 6.b-c Preserve the existing values of the converted property's + * [[Configurable]] and [[Enumerable]] attributes and set the rest of + * the property's attributes to their default values. + */ + + if (njs_is_data_descriptor(prev)) { + prev->getter = njs_value_undefined; + prev->setter = njs_value_undefined; + + prev->value = njs_value_invalid; + prev->writable = NJS_ATTRIBUTE_UNSET; + + } else { + prev->value = njs_value_undefined; + prev->writable = NJS_ATTRIBUTE_FALSE; + + prev->getter = njs_value_invalid; + prev->setter = njs_value_invalid; + } + + + } else if (njs_is_data_descriptor(prev) + && njs_is_data_descriptor(prop)) + { + if (!prev->configurable && !prev->writable) { + if (prop->writable == NJS_ATTRIBUTE_TRUE) { + goto exception; + } + + if (njs_is_valid(&prop->value) + && prev->type != NJS_PROPERTY_HANDLER + && !njs_values_strict_equal(&prop->value, &prev->value)) + { + goto exception; + } + } + + } else { + if (!prev->configurable) { + if (njs_is_valid(&prop->getter) + && !njs_values_strict_equal(&prop->getter, &prev->getter)) + { + goto exception; + } + + if (njs_is_valid(&prop->setter) + && !njs_values_strict_equal(&prop->setter, &prev->setter)) + { + goto exception; + } + } + } + +done: + + /* + * 9. For each field of Desc that is present, set the corresponding + * attribute of the property named P of object O to the value of the field. + */ + + if (njs_is_valid(&prop->getter)) { + prev->getter = prop->getter; + } + + if (njs_is_valid(&prop->setter)) { + prev->setter = prop->setter; } if (njs_is_valid(&prop->value)) { - prev->value = prop->value; + if (prev->type == NJS_PROPERTY_HANDLER) { + if (njs_is_data_descriptor(prev) && prev->writable) { + ret = prev->value.data.u.prop_handler(vm, object, + &prop->value, + &vm->retval); + if (nxt_slow_path(ret != NXT_OK)) { + return ret; + } + } + + } else { + prev->value = prop->value; + } } if (prop->writable != NJS_ATTRIBUTE_UNSET) { @@ -915,20 +1028,60 @@ static njs_object_prop_t * njs_descriptor_prop(njs_vm_t *vm, const njs_value_t *name, const njs_object_t *desc) { + nxt_bool_t data, accessor; njs_object_prop_t *prop, *pr; + const njs_value_t *setter, *getter; nxt_lvlhsh_query_t pq; + data = 0; + accessor = 0; + prop = njs_object_prop_alloc(vm, name, &njs_value_invalid, NJS_ATTRIBUTE_UNSET); if (nxt_slow_path(prop == NULL)) { return NULL; } + getter = &njs_value_invalid; + pq.key = nxt_string_value("get"); + pq.key_hash = NJS_GET_HASH; + + pr = njs_object_property(vm, desc, &pq); + if (pr != NULL) { + if (!njs_is_undefined(&pr->value) && !njs_is_function(&pr->value)) { + njs_type_error(vm, "Getter must be a function"); + return NULL; + } + + accessor = 1; + getter = &pr->value; + } + + prop->getter = *getter; + + setter = &njs_value_invalid; + pq.key = nxt_string_value("set"); + pq.key_hash = NJS_SET_HASH; + + pr = njs_object_property(vm, desc, &pq); + if (pr != NULL) { + if (!njs_is_undefined(&pr->value) && !njs_is_function(&pr->value)) { + njs_type_error(vm, "Setter must be a function"); + return NULL; + } + + accessor = 1; + setter = &pr->value; + } + + prop->setter = *setter; + pq.key = nxt_string_value("value"); pq.key_hash = NJS_VALUE_HASH; pr = njs_object_property(vm, desc, &pq); if (pr != NULL) { + data = 1; prop->value = pr->value; } @@ -937,6 +1090,7 @@ njs_descriptor_prop(njs_vm_t *vm, const njs_value_t *name, pr = njs_object_property(vm, desc, &pq); if (pr != NULL) { + data = 1; prop->writable = pr->value.data.truth; } @@ -956,11 +1110,19 @@ njs_descriptor_prop(njs_vm_t *vm, const njs_value_t *name, prop->configurable = pr->value.data.truth; } + if (accessor && data) { + njs_type_error(vm, "Cannot both specify accessors " + "and a value or writable attribute"); + return NULL; + } + return prop; } static const njs_value_t njs_object_value_string = njs_string("value"); +static const njs_value_t njs_object_get_string = njs_string("get"); +static const njs_value_t njs_object_set_string = njs_string("set"); static const njs_value_t njs_object_writable_string = njs_string("writable"); static const njs_value_t njs_object_enumerable_string = @@ -1043,38 +1205,78 @@ njs_object_prop_descriptor(njs_vm_t *vm, njs_value_t *dest, lhq.replace = 0; lhq.pool = vm->mem_pool; - lhq.key = nxt_string_value("value"); - lhq.key_hash = NJS_VALUE_HASH; + if (njs_is_data_descriptor(prop)) { - pr = njs_object_prop_alloc(vm, &njs_object_value_string, &prop->value, 1); - if (nxt_slow_path(pr == NULL)) { - return NXT_ERROR; - } + lhq.key = nxt_string_value("value"); + lhq.key_hash = NJS_VALUE_HASH; - lhq.value = pr; + pr = njs_object_prop_alloc(vm, &njs_object_value_string, &prop->value, + 1); + if (nxt_slow_path(pr == NULL)) { + return NXT_ERROR; + } - ret = nxt_lvlhsh_insert(&desc->hash, &lhq); - if (nxt_slow_path(ret != NXT_OK)) { - njs_internal_error(vm, "lvlhsh insert failed"); - return NXT_ERROR; - } + lhq.value = pr; - lhq.key = nxt_string_value("writable"); - lhq.key_hash = NJS_WRITABABLE_HASH; + ret = nxt_lvlhsh_insert(&desc->hash, &lhq); + if (nxt_slow_path(ret != NXT_OK)) { + njs_internal_error(vm, "lvlhsh insert failed"); + return NXT_ERROR; + } - setval = (prop->writable == 1) ? &njs_value_true : &njs_value_false; + lhq.key = nxt_string_value("writable"); + lhq.key_hash = NJS_WRITABABLE_HASH; - pr = njs_object_prop_alloc(vm, &njs_object_writable_string, setval, 1); - if (nxt_slow_path(pr == NULL)) { - return NXT_ERROR; - } + setval = (prop->writable == 1) ? &njs_value_true : &njs_value_false; - lhq.value = pr; + pr = njs_object_prop_alloc(vm, &njs_object_writable_string, setval, 1); + if (nxt_slow_path(pr == NULL)) { + return NXT_ERROR; + } - ret = nxt_lvlhsh_insert(&desc->hash, &lhq); - if (nxt_slow_path(ret != NXT_OK)) { - njs_internal_error(vm, "lvlhsh insert failed"); - return NXT_ERROR; + lhq.value = pr; + + ret = nxt_lvlhsh_insert(&desc->hash, &lhq); + if (nxt_slow_path(ret != NXT_OK)) { + njs_internal_error(vm, "lvlhsh insert failed"); + return NXT_ERROR; + } + + } else { + + lhq.key = nxt_string_value("get"); + lhq.key_hash = NJS_GET_HASH; + + pr = njs_object_prop_alloc(vm, &njs_object_get_string, &prop->getter, + 1); + if (nxt_slow_path(pr == NULL)) { + return NXT_ERROR; + } + + lhq.value = pr; + + ret = nxt_lvlhsh_insert(&desc->hash, &lhq); + if (nxt_slow_path(ret != NXT_OK)) { + njs_internal_error(vm, "lvlhsh insert failed"); + return NXT_ERROR; + } + + lhq.key = nxt_string_value("set"); + lhq.key_hash = NJS_SET_HASH; + + pr = njs_object_prop_alloc(vm, &njs_object_set_string, &prop->setter, + 1); + if (nxt_slow_path(pr == NULL)) { + return NXT_ERROR; + } + + lhq.value = pr; + + ret = nxt_lvlhsh_insert(&desc->hash, &lhq); + if (nxt_slow_path(ret != NXT_OK)) { + njs_internal_error(vm, "lvlhsh insert failed"); + return NXT_ERROR; + } } lhq.key = nxt_string_value("enumerable"); diff --git a/njs/njs_vm.c b/njs/njs_vm.c index 2c0ecb15..c3490b20 100644 --- a/njs/njs_vm.c +++ b/njs/njs_vm.c @@ -533,14 +533,21 @@ njs_ret_t njs_vmcode_property_get(njs_vm_t *vm, njs_value_t *object, njs_value_t *property) { - njs_ret_t ret; + njs_ret_t ret; + njs_value_t *retval; + njs_vmcode_prop_get_t *code; + + code = (njs_vmcode_prop_get_t *) vm->current; + retval = njs_vmcode_operand(vm, code->value); - ret = njs_value_property(vm, object, property, &vm->retval); + ret = njs_value_property(vm, object, property, retval, + sizeof(njs_vmcode_prop_get_t)); if (ret == NXT_OK || ret == NXT_DECLINED) { + vm->retval = *retval; return sizeof(njs_vmcode_prop_get_t); } - return ret; + return (ret == NJS_APPLIED) ? 0 : ret; } @@ -665,12 +672,13 @@ njs_vmcode_property_set(njs_vm_t *vm, njs_value_t *object, code = (njs_vmcode_prop_set_t *) vm->current; value = njs_vmcode_operand(vm, code->value); - ret = njs_value_property_set(vm, object, property, value); + ret = njs_value_property_set(vm, object, property, value, + sizeof(njs_vmcode_prop_set_t)); if (ret == NXT_OK) { return sizeof(njs_vmcode_prop_set_t); } - return ret; + return (ret == NJS_APPLIED) ? 0 : ret; } @@ -936,7 +944,12 @@ njs_vmcode_instance_of(njs_vm_t *vm, njs_value_t *object, if (njs_is_object(object)) { value = njs_value_undefined; - ret = njs_value_property(vm, constructor, &prototype_string, &value); + ret = njs_value_property(vm, constructor, &prototype_string, &value, 0); + + if (nxt_slow_path(ret == NJS_APPLIED)) { + njs_internal_error(vm, "getter is not supported in instanceof"); + return NXT_ERROR; + } if (nxt_fast_path(ret == NXT_OK)) { diff --git a/njs/test/njs_unit_test.c b/njs/test/njs_unit_test.c index d4107932..2e0624dc 100644 --- a/njs/test/njs_unit_test.c +++ b/njs/test/njs_unit_test.c @@ -4009,6 +4009,29 @@ static njs_unit_test_t njs_test[] = ").every((x) => typeof x == 'object')"), nxt_string("true") }, + { nxt_string("var o = {}; Object.defineProperty(o, 'length', {get:()=>2}); " + "Array.prototype.slice.call(Array.prototype.fill.call(o, 1))"), + nxt_string("1,1") }, + + { nxt_string("var o = {}; Object.defineProperty(o, 'length', {get:()=> {throw TypeError('Boom')}}); " + "Array.prototype.fill.call(o, 1)"), + nxt_string("TypeError: Boom") }, + + { nxt_string("var o = Object({length: 3});" + "Object.defineProperty(o, '0', {set: ()=>{throw TypeError('Boom')}});" + "Array.prototype.fill.call(o, 1)"), + nxt_string("TypeError: Boom") }, + + { nxt_string("var o = Object({length: 3});" + "Object.defineProperty(o, '0', {set: function(v){this.a = 2 * v}});" + "Array.prototype.fill.call(o, 2).a"), + nxt_string("4") }, + + { nxt_string("var o = Object({length: 3});" + "Object.defineProperty(o, '0', {set: function(v){this[0] = 2 * v}});" + "Array.prototype.fill.call(o, 2)"), + nxt_string("RangeError: Maximum call stack size exceeded") }, + { nxt_string("var a = [];" "a.filter(function(v, i, a) { return v > 1 })"), nxt_string("") }, @@ -8201,6 +8224,10 @@ static njs_unit_test_t njs_test[] = { nxt_string("/./ instanceof Object"), nxt_string("true") }, + { nxt_string("Object.defineProperty(Function.prototype, \"prototype\", {get: ()=>Array.prototype});" + "[] instanceof Function.prototype"), + nxt_string("InternalError: getter is not supported in instanceof") }, + /* global this. */ { nxt_string("this"), @@ -9159,6 +9186,9 @@ static njs_unit_test_t njs_test[] = { nxt_string("var o = {}; Object.defineProperty(o, 'a', {value:1}); o.a"), nxt_string("1") }, + { nxt_string("var o = {}; Object.defineProperty(o, 'a', Object.create({value:1})); o.a"), + nxt_string("1") }, + { nxt_string("var o = {a:1, c:2}; Object.defineProperty(o, 'b', {});" "Object.keys(o)"), nxt_string("a,c") }, @@ -9308,6 +9338,32 @@ static njs_unit_test_t njs_test[] = "Object.defineProperty(o, 'a', {value:1}).a"), nxt_string("1") }, + { nxt_string("var o = {};" + "Object.defineProperty(o, 'a', {configurable:true, get:()=>1});" + "Object.defineProperty(o, 'a', {value:1});" + "var d = Object.getOwnPropertyDescriptor(o, 'a'); " + "[d.value, d.writable, d.enumerable, d.configurable, d.get, d.set]"), + nxt_string("1,false,false,true,,") }, + + { nxt_string("var o = {};" + "Object.defineProperty(o, 'a', {configurable:true, value:1});" + "Object.defineProperty(o, 'a', {set:()=>1});" + "var d = Object.getOwnPropertyDescriptor(o, 'a'); " + "[d.value, d.writable, d.enumerable, d.configurable, d.get, d.set]"), + nxt_string(",,false,true,,[object Function]") }, + + { nxt_string("var o = {}; Object.defineProperty(o, 'a', {get: ()=>1, configurable:true}); " + "Object.defineProperty(o, 'a', {value:123}); o.a =2"), + nxt_string("TypeError: Cannot assign to read-only property \"a\" of object") }, + + { nxt_string("var o = {}; Object.defineProperty(o, 'a', {get: ()=>1, configurable:true}); " + "Object.defineProperty(o, 'a', {writable:false}); o.a"), + nxt_string("undefined") }, + + { nxt_string("var o = {}; Object.defineProperty(o, 'a', {value: 1, configurable:true}); " + "Object.defineProperty(o, 'a', {get:()=>1}); o.a = 2"), + nxt_string("TypeError: Cannot set property \"a\" of object which has only a getter") }, + { nxt_string("var o = {};" "Object.defineProperty(o, 'a', { configurable: true, value: 0 });" "Object.defineProperty(o, 'a', { value: 1 });" @@ -9387,6 +9443,9 @@ static njs_unit_test_t njs_test[] = { nxt_string("var o = Object.defineProperties({a:1}, {}); o.a"), nxt_string("1") }, + { nxt_string("var arr = [0, 1]; Object.defineProperty(arr, 'length', {value:3}); arr.length"), + nxt_string("3") }, + { nxt_string("Object.defineProperties()"), nxt_string("TypeError: cannot convert undefined argument to object") }, @@ -9477,6 +9536,133 @@ static njs_unit_test_t njs_test[] = { nxt_string("Object.prototype.hasOwnProperty('hasOwnProperty')"), nxt_string("true") }, + { nxt_string("var o = {};" + "Object.defineProperty(o, 'a', {get:undefined, set:undefined}).a"), + nxt_string("undefined") }, + + { nxt_string("var o = {};" + "Object.defineProperty(o, 'a', {get:1})"), + nxt_string("TypeError: Getter must be a function") }, + + { nxt_string("var o = {};" + "Object.defineProperty(o, 'a', {get:undefined}).a"), + nxt_string("undefined") }, + + { nxt_string("var o = {};" + "Object.defineProperty(o, 'a', {get:function(){return 1}}).a"), + nxt_string("1") }, + + { nxt_string("var o = {};" + "Object.defineProperty(o, 'a', {set:1})"), + nxt_string("TypeError: Setter must be a function") }, + + { nxt_string("var o = {};" + "Object.defineProperty(o, 'a', {set:undefined}); o.a"), + nxt_string("undefined") }, + + { nxt_string("var o = {};" + "Object.defineProperty(o, 'a', {set:undefined}); o.a = 4;"), + nxt_string("TypeError: Cannot set property \"a\" of object which has only a getter") }, + + { nxt_string("var o = {a: 0};" + "Object.defineProperty(o, 'b', {set:function(x){this.a = x / 2;}}); o.b = 4; o.a;"), + nxt_string("2") }, + + { nxt_string("var o = {};" + "Object.defineProperty(o, 'a', {value:undefined});" + "Object.defineProperty(o, 'a', {get:undefined})"), + nxt_string("TypeError: Cannot redefine property: \"a\"") }, + + { nxt_string("var o = {};" + "Object.defineProperty(o, 'a', {});" + "Object.defineProperty(o, 'a', {get:undefined})"), + nxt_string("TypeError: Cannot redefine property: \"a\"") }, + + { nxt_string("var o = {};" + "Object.defineProperty(o, 'a', {get:undefined});" + "Object.defineProperty(o, 'a', {get:undefined}).a"), + nxt_string("undefined") }, + + { nxt_string("var o = {};" + "Object.defineProperty(o, 'a', {get:undefined});" + "Object.defineProperty(o, 'a', {set:undefined}).a"), + nxt_string("undefined") }, + + { nxt_string("var o = {};" + "Object.defineProperty(o, 'a', {get:undefined});" + "Object.defineProperty(o, 'a', {}); o.a"), + nxt_string("undefined") }, + + { nxt_string("var o = {};" + "Object.defineProperty(o, 'a', {get:undefined});" + "Object.defineProperty(o, 'a', {set:function(){}})"), + nxt_string("TypeError: Cannot redefine property: \"a\"") }, + + { nxt_string("var o = {};" + "Object.defineProperty(o, 'a', {get:undefined});" + "Object.defineProperty(o, 'a', {get:function(){}})"), + nxt_string("TypeError: Cannot redefine property: \"a\"") }, + + { nxt_string("var o = {};" + "Object.defineProperty(o, 'a', {get:()=>1, configurable:true});" + "Object.defineProperty(o, 'a', {get:()=>2}); o.a"), + nxt_string("2") }, + + { nxt_string("var o = {};" + "Object.defineProperty(o, 'a', {set:function(v){this.aa=v;}, configurable:true});" + "Object.defineProperty(o, 'a', {get:function(){return this.aa}}); o.a = 1; o.a"), + nxt_string("1") }, + + { nxt_string("var o = {};" + "Object.defineProperty(o, 'a', {get:()=>1, configurable:true});" + "Object.defineProperty(o, 'a', {value:2}).a"), + nxt_string("2") }, + + { nxt_string("var o = {};" + "Object.defineProperty(o, 'a', {get:()=>1, configurable:true});" + "Object.defineProperty(o, 'a', {value:2});" + "var d = Object.getOwnPropertyDescriptor(o, 'a');" + "d.get === undefined && d.set === undefined"), + nxt_string("true") }, + + { nxt_string("var o = {};" + "Object.defineProperty(o, 'a', {value:1, configurable:true});" + "Object.defineProperty(o, 'a', {get:()=>2});" + "var d = Object.getOwnPropertyDescriptor(o, 'a');" + "d.writable === undefined && d.value === undefined"), + nxt_string("true") }, + + { nxt_string("Object.defineProperty(Date.prototype, 'year', {get: function() { return this.getFullYear();}});" + "var d = new Date(0); d.year"), + nxt_string("1970") }, + + { nxt_string("var o = [];" + "Object.defineProperty(o, 'fill', {get:undefined}).fill"), + nxt_string("undefined") }, + + { nxt_string("var o = [];" + "Object.defineProperty(o, 'length', {get:undefined})"), + nxt_string("TypeError: Cannot redefine property: \"length\"") }, + + { nxt_string("var o = {};" + "Object.defineProperty(o, 'foo', {get:()=>10});" + "Object.preventExtensions(o);Object.isFrozen(o)"), + nxt_string("true"), }, + + { nxt_string("var o = {}; Object.defineProperty(o, 'r', { get: function() { return this.r_value; } }); o.r_value = 1; o.r;"), + nxt_string("1") }, + + { nxt_string("var o = {}; Object.defineProperty(o, 'rw', { get: function() { return this.rw_value; }, set: function(x) { this.rw_value = x / 2; } }); o.rw = 10; o.rw"), + nxt_string("5") }, + + { nxt_string("var o = {}; function foo() {}; Object.defineProperty(o, 'a', {get: foo});" + "Object.getOwnPropertyDescriptor(o, 'a').get === foo"), + nxt_string("true") }, + + { nxt_string("var o = {}; Object.defineProperty(o, 'a', {get: undefined});" + "JSON.stringify(Object.getOwnPropertyDescriptor(o, 'a')).set"), + nxt_string("undefined") }, + { nxt_string("var p = { a:5 }; var o = Object.create(p);" "Object.getPrototypeOf(o) === p"), nxt_string("true") },