[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