[njs] Added property getter/setter support in Object.defineProperty().
Dmitry Volyntsev
xeioex at nginx.com
Tue Jun 18 17:41:48 UTC 2019
details: https://hg.nginx.org/njs/rev/2fb43ddbce84
branches:
changeset: 1011:2fb43ddbce84
user: hongzhidao <hongzhidao at gmail.com>
date: Mon Jun 10 22:23:56 2019 -0400
description:
Added property getter/setter support in Object.defineProperty().
In collaboration with Dmitry Volyntsev.
diffstat:
njs/njs_array.c | 66 +++++++-
njs/njs_function.c | 4 +-
njs/njs_object.c | 6 +-
njs/njs_object.h | 24 ++-
njs/njs_object_hash.h | 14 +
njs/njs_object_property.c | 326 +++++++++++++++++++++++++++++++++++++--------
njs/njs_vm.c | 27 ++-
njs/test/njs_unit_test.c | 186 ++++++++++++++++++++++++++
8 files changed, 564 insertions(+), 89 deletions(-)
diffs (truncated from 1029 to 1000 lines):
diff -r c427fe5a4882 -r 2fb43ddbce84 njs/njs_array.c
--- a/njs/njs_array.c Tue Jun 18 20:27:39 2019 +0300
+++ b/njs/njs_array.c Mon Jun 10 22:23:56 2019 -0400
@@ -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_joi
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,
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
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, n
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(nj
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(nj
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 -r c427fe5a4882 -r 2fb43ddbce84 njs/njs_function.c
--- a/njs/njs_function.c Tue Jun 18 20:27:39 2019 +0300
+++ b/njs/njs_function.c Mon Jun 10 22:23:56 2019 -0400
@@ -1042,7 +1042,7 @@ njs_function_prototype_apply(njs_vm_t *v
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 *v
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 -r c427fe5a4882 -r 2fb43ddbce84 njs/njs_object.c
--- a/njs/njs_object.c Tue Jun 18 20:27:39 2019 +0300
+++ b/njs/njs_object.c Mon Jun 10 22:23:56 2019 -0400
@@ -1421,7 +1421,11 @@ njs_object_is_frozen(njs_vm_t *vm, njs_v
break;
}
- if (prop->writable || prop->configurable) {
+ if (prop->configurable) {
+ goto done;
+ }
+
+ if (njs_is_data_descriptor(prop) && prop->writable) {
goto done;
}
}
diff -r c427fe5a4882 -r 2fb43ddbce84 njs/njs_object.h
--- a/njs/njs_object.h Tue Jun 18 20:27:39 2019 +0300
+++ b/njs/njs_object.h Mon Jun 10 22:23:56 2019 -0400
@@ -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_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 -r c427fe5a4882 -r 2fb43ddbce84 njs/njs_object_hash.h
--- a/njs/njs_object_hash.h Tue Jun 18 20:27:39 2019 +0300
+++ b/njs/njs_object_hash.h Mon Jun 10 22:23:56 2019 -0400
@@ -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 -r c427fe5a4882 -r 2fb43ddbce84 njs/njs_object_property.c
--- a/njs/njs_object_property.c Tue Jun 18 20:27:39 2019 +0300
+++ b/njs/njs_object_property.c Mon Jun 10 22:23:56 2019 -0400
@@ -474,14 +474,15 @@ njs_external_property_delete(njs_vm_t *v
* 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 n
/* 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 n
/*
* 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
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
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, cons
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
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 (prop->writable == NJS_ATTRIBUTE_UNSET) {
- prop->writable = 0;
+ if (!njs_is_valid(&prop->setter)) {
+ prop->setter = njs_value_undefined;
+ }
+
+ } else {
+ 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
prev = pq.lhq.value;
switch (prev->type) {
+ case NJS_METHOD:
case NJS_PROPERTY:
- case NJS_METHOD:
+ case NJS_PROPERTY_HANDLER:
break;
case NJS_PROPERTY_REF:
@@ -839,18 +885,6 @@ njs_object_prop_define(njs_vm_t *vm, njs
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
return NXT_ERROR;
}
+ /* 9.1.6.3 ValidateAndApplyPropertyDescriptor */
+
if (!prev->configurable) {
- if (njs_is_valid(&prop->value)
- && prev->writable == NJS_ATTRIBUTE_FALSE
- && !njs_values_strict_equal(&prop->value, &prev->value))
- {
- goto exception;
- }
-
- 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
{
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
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
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,
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)) {
+
+ lhq.key = nxt_string_value("value");
+ lhq.key_hash = NJS_VALUE_HASH;
- pr = njs_object_prop_alloc(vm, &njs_object_value_string, &prop->value, 1);
- if (nxt_slow_path(pr == NULL)) {
- return NXT_ERROR;
- }
+ pr = njs_object_prop_alloc(vm, &njs_object_value_string, &prop->value,
+ 1);
+ if (nxt_slow_path(pr == NULL)) {
+ return NXT_ERROR;
+ }
+
+ lhq.value = pr;
- 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("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;
+
+ pr = njs_object_prop_alloc(vm, &njs_object_writable_string, setval, 1);
+ if (nxt_slow_path(pr == NULL)) {
+ 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;
+ }
+
+ } else {
- setval = (prop->writable == 1) ? &njs_value_true : &njs_value_false;
+ 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;
- 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.key = nxt_string_value("set");
+ lhq.key_hash = NJS_SET_HASH;
- lhq.value = pr;
+ pr = njs_object_prop_alloc(vm, &njs_object_set_string, &prop->setter,
+ 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;
+ }
}
lhq.key = nxt_string_value("enumerable");
diff -r c427fe5a4882 -r 2fb43ddbce84 njs/njs_vm.c
--- a/njs/njs_vm.c Tue Jun 18 20:27:39 2019 +0300
+++ b/njs/njs_vm.c Mon Jun 10 22:23:56 2019 -0400
@@ -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;
-
- ret = njs_value_property(vm, object, property, &vm->retval);
+ 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, 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, nj
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
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 -r c427fe5a4882 -r 2fb43ddbce84 njs/test/njs_unit_test.c
--- a/njs/test/njs_unit_test.c Tue Jun 18 20:27:39 2019 +0300
+++ b/njs/test/njs_unit_test.c Mon Jun 10 22:23:56 2019 -0400
@@ -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") },
@@ -9309,6 +9339,32 @@ static njs_unit_test_t njs_test[] =
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 });"
"Object.defineProperty(o, 'a', { configurable: false, value: 2 }).a"),
@@ -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 = [];"
More information about the nginx-devel
mailing list