]> git.kaiwu.me - njs.git/commitdiff
Object property quering is refactored.
authorDmitry Volyntsev <xeioex@nginx.com>
Fri, 19 Oct 2018 17:52:57 +0000 (20:52 +0300)
committerDmitry Volyntsev <xeioex@nginx.com>
Fri, 19 Oct 2018 17:52:57 +0000 (20:52 +0300)
    njs_property_query() is rectified and unified
        1) returns only property descriptors. Special return codes
        NJS_PRIMITIVE_VALUE, NJS_STRING_VALUE, NJS_ARRAY_VALUE and
        NJS_EXTERNAL_VALUE are replaced with a temporary property
        descriptor of type NJS_PROPERTY_REF or NJS_PROPERTY_HANDLER.
        If NJS_PROPERTY_REF is set reference to a value is contained
        in prop->value.data.u.value.

        2) NJS_PROPERTY_HANDLER properties returned as is.

        3) njs_property_query_t.own can be used to query for an object's
            OwnProperty.

        4) NJS_PROPERTY_QUERY_IN is removed.

        The aim is to implement with it [[GetOwnProperty]] and
        [[GetProperty]] methods from specification. Which are used
        extensively in many places of the ECMAScript spec.

    njs_value_property() is introduced which corresponds to [[Get]]
    method from specification.

    This fixes #32 and #34 issues on Github.

njs/njs_extern.h
njs/njs_object.c
njs/njs_object.h
njs/njs_vm.c
njs/njs_vm.h
njs/test/njs_expect_test.exp
njs/test/njs_unit_test.c

index 6cbb99176a494cbfd9cf012e55748038b2c4e080..542d97c837286449cd966f9f365e771abe078b27 100644 (file)
@@ -11,6 +11,9 @@
 #define njs_extern_object(vm, ext)                                          \
     (*(void **) nxt_array_item((vm)->external_objects, (ext)->external.index))
 
+#define njs_extern_index(vm, idx)                                          \
+    (*(void **) nxt_array_item((vm)->external_objects, idx))
+
 
 struct njs_extern_s {
     /* A hash of inclusive njs_extern_t. */
index 372a6c53a14f2dc29304fff8cf2bc78cd0b4b4b0..bdc13076807ff621eac30b3df4cf48adc80b2c3d 100644 (file)
 
 static nxt_int_t njs_object_hash_test(nxt_lvlhsh_query_t *lhq, void *data);
 static njs_ret_t njs_object_property_query(njs_vm_t *vm,
-    njs_property_query_t *pq, njs_value_t *value, njs_object_t *object);
+    njs_property_query_t *pq, njs_object_t *object,
+    const njs_value_t *property);
 static njs_ret_t njs_array_property_query(njs_vm_t *vm,
+    njs_property_query_t *pq, njs_array_t *array, uint32_t index);
+static njs_ret_t njs_string_property_query(njs_vm_t *vm,
     njs_property_query_t *pq, njs_value_t *object, uint32_t index);
+static njs_ret_t njs_external_property_query(njs_vm_t *vm,
+    njs_property_query_t *pq, njs_value_t *object);
+static njs_ret_t njs_external_property_set(njs_vm_t *vm, njs_value_t *value,
+    njs_value_t *setval, njs_value_t *retval);
+static njs_ret_t njs_external_property_delete(njs_vm_t *vm, njs_value_t *value,
+    njs_value_t *setval, njs_value_t *retval);
 static njs_ret_t njs_object_query_prop_handler(njs_property_query_t *pq,
     njs_object_t *object);
 static njs_ret_t njs_define_property(njs_vm_t *vm, njs_object_t *object,
@@ -232,28 +241,39 @@ njs_object_property(njs_vm_t *vm, const njs_object_t *object,
 
 
 /*
+ * ES5.1, 8.12.1: [[GetOwnProperty]], [[GetProperty]].
  * The njs_property_query() returns values
  *   NXT_OK               property has been found in object,
+ *     retval of type njs_object_prop_t * is in pq->lhq.value.
+ *     in NJS_PROPERTY_QUERY_GET
+ *       prop->type is NJS_PROPERTY, NJS_METHOD or NJS_PROPERTY_HANDLER.
+ *     in NJS_PROPERTY_QUERY_SET, NJS_PROPERTY_QUERY_DELETE
+ *       prop->type is NJS_PROPERTY, NJS_PROPERTY_REF, NJS_METHOD or
+ *       NJS_PROPERTY_HANDLER.
  *   NXT_DECLINED         property was not found in object,
- *   NJS_PRIMITIVE_VALUE  property operation was applied to a numeric
- *                        or boolean value,
- *   NJS_STRING_VALUE     property operation was applied to a string,
- *   NJS_ARRAY_VALUE      object is array,
- *   NJS_EXTERNAL_VALUE   object is external entity,
+ *     if pq->lhq.value != NULL it contains retval of type
+ *     njs_object_prop_t * where prop->type is NJS_WHITEOUT
  *   NJS_TRAP             the property trap must be called,
  *   NXT_ERROR            exception has been thrown.
+ *
+ *   TODO:
+ *     Object.create([1,2]).length
+ *     Object.defineProperty([1,2], '1', {configurable:false})
  */
 
 njs_ret_t
 njs_property_query(njs_vm_t *vm, njs_property_query_t *pq, njs_value_t *object,
-    njs_value_t *property)
+    const njs_value_t *property)
 {
-    uint32_t            index;
-    uint32_t            (*hash)(const void *, size_t);
-    njs_ret_t           ret;
-    njs_object_t        *obj;
-    njs_function_t      *function;
-    const njs_extern_t  *ext_proto;
+    uint32_t        index;
+    uint32_t        (*hash)(const void *, size_t);
+    njs_ret_t       ret;
+    njs_object_t    *obj;
+    njs_function_t  *function;
+
+    if (nxt_slow_path(!njs_is_primitive(property))) {
+        return njs_trap(vm, NJS_TRAP_PROPERTY);
+    }
 
     hash = nxt_djb_hash;
 
@@ -261,34 +281,47 @@ njs_property_query(njs_vm_t *vm, njs_property_query_t *pq, njs_value_t *object,
 
     case NJS_BOOLEAN:
     case NJS_NUMBER:
-        if (pq->query != NJS_PROPERTY_QUERY_GET) {
-            return NJS_PRIMITIVE_VALUE;
-        }
-
         index = njs_primitive_prototype_index(object->type);
         obj = &vm->prototypes[index].object;
         break;
 
     case NJS_STRING:
-        if (pq->query == NJS_PROPERTY_QUERY_DELETE) {
-            return NXT_DECLINED;
+        if (nxt_fast_path(!njs_is_null_or_void_or_boolean(property))) {
+            index = njs_value_to_index(property);
+
+            if (nxt_fast_path(index < NJS_STRING_MAX_LENGTH)) {
+                return njs_string_property_query(vm, pq, object, index);
+            }
         }
 
         obj = &vm->prototypes[NJS_PROTOTYPE_STRING].object;
         break;
 
-    case NJS_ARRAY:
+    case NJS_OBJECT_STRING:
         if (nxt_fast_path(!njs_is_null_or_void_or_boolean(property))) {
+            index = njs_value_to_index(property);
 
-            if (nxt_fast_path(njs_is_primitive(property))) {
-                index = njs_value_to_index(property);
+            if (nxt_fast_path(index < NJS_STRING_MAX_LENGTH)) {
+                ret = njs_string_property_query(vm, pq,
+                                            &object->data.u.object_value->value,
+                                            index);
 
-                if (nxt_fast_path(index < NJS_ARRAY_MAX_LENGTH)) {
-                    return njs_array_property_query(vm, pq, object, index);
+                if (nxt_fast_path(ret != NXT_DECLINED)) {
+                    return ret;
                 }
+            }
+        }
 
-            } else {
-                return njs_trap(vm, NJS_TRAP_PROPERTY);
+        obj = object->data.u.object;
+        break;
+
+    case NJS_ARRAY:
+        if (nxt_fast_path(!njs_is_null_or_void_or_boolean(property))) {
+            index = njs_value_to_index(property);
+
+            if (nxt_fast_path(index < NJS_ARRAY_MAX_LENGTH)) {
+                return njs_array_property_query(vm, pq, object->data.u.array,
+                                                index);
             }
         }
 
@@ -297,7 +330,6 @@ njs_property_query(njs_vm_t *vm, njs_property_query_t *pq, njs_value_t *object,
     case NJS_OBJECT:
     case NJS_OBJECT_BOOLEAN:
     case NJS_OBJECT_NUMBER:
-    case NJS_OBJECT_STRING:
     case NJS_REGEXP:
     case NJS_DATE:
     case NJS_OBJECT_ERROR:
@@ -322,28 +354,19 @@ njs_property_query(njs_vm_t *vm, njs_property_query_t *pq, njs_value_t *object,
         break;
 
     case NJS_EXTERNAL:
-        ext_proto = object->external.proto;
-
-        if (ext_proto->type == NJS_EXTERN_CASELESS_OBJECT) {
-            hash = nxt_djb_hash_lowcase;
-        }
-
         obj = NULL;
         break;
 
     case NJS_VOID:
     case NJS_NULL:
     default:
-        if (nxt_fast_path(njs_is_primitive(property))) {
-
-            ret = njs_primitive_value_to_string(vm, &pq->value, property);
+        ret = njs_primitive_value_to_string(vm, &pq->value, property);
 
-            if (nxt_fast_path(ret == NXT_OK)) {
-                njs_string_get(&pq->value, &pq->lhq.key);
-                njs_type_error(vm, "cannot get property '%.*s' of undefined",
-                               (int) pq->lhq.key.length, pq->lhq.key.start);
-                return NXT_ERROR;
-            }
+        if (nxt_fast_path(ret == NXT_OK)) {
+            njs_string_get(&pq->value, &pq->lhq.key);
+            njs_type_error(vm, "cannot get property '%.*s' of undefined",
+                           (int) pq->lhq.key.length, pq->lhq.key.start);
+            return NXT_ERROR;
         }
 
         njs_type_error(vm, "cannot get property 'unknown' of undefined");
@@ -351,37 +374,34 @@ njs_property_query(njs_vm_t *vm, njs_property_query_t *pq, njs_value_t *object,
         return NXT_ERROR;
     }
 
-    if (nxt_fast_path(njs_is_primitive(property))) {
-
-        ret = njs_primitive_value_to_string(vm, &pq->value, property);
-
-        if (nxt_fast_path(ret == NXT_OK)) {
-
-            njs_string_get(&pq->value, &pq->lhq.key);
-            pq->lhq.key_hash = hash(pq->lhq.key.start, pq->lhq.key.length);
+    ret = njs_primitive_value_to_string(vm, &pq->value, property);
 
-            if (obj == NULL) {
-                pq->lhq.proto = &njs_extern_hash_proto;
+    if (nxt_fast_path(ret == NXT_OK)) {
 
-                return NJS_EXTERNAL_VALUE;
-            }
+        njs_string_get(&pq->value, &pq->lhq.key);
+        pq->lhq.key_hash = hash(pq->lhq.key.start, pq->lhq.key.length);
 
-            return njs_object_property_query(vm, pq, object, obj);
+        if (obj == NULL) {
+            return njs_external_property_query(vm, pq, object);
         }
 
-        return ret;
+        return njs_object_property_query(vm, pq, obj, property);
     }
 
-    return njs_trap(vm, NJS_TRAP_PROPERTY);
+    return ret;
 }
 
 
 njs_ret_t
 njs_object_property_query(njs_vm_t *vm, njs_property_query_t *pq,
-    njs_value_t *value, njs_object_t *object)
+    njs_object_t *object, const njs_value_t *property)
 {
-    njs_ret_t          ret;
-    njs_object_prop_t  *prop;
+    uint32_t            index;
+    njs_ret_t           ret;
+    njs_array_t         *array;
+    njs_object_t        *proto;
+    njs_object_prop_t   *prop;
+    njs_object_value_t  *ov;
 
     pq->lhq.proto = &njs_object_hash_proto;
 
@@ -392,83 +412,83 @@ njs_object_property_query(njs_vm_t *vm, njs_property_query_t *pq,
         }
     }
 
-    do {
-        pq->prototype = object;
-
-        ret = nxt_lvlhsh_find(&object->hash, &pq->lhq);
+    proto = object;
 
-        if (ret == NXT_OK) {
-            prop = pq->lhq.value;
+    do {
+        pq->prototype = proto;
 
-            if (prop->type != NJS_WHITEOUT) {
-                pq->shared = 0;
+        /* length and other shared properties should be Own property */
 
-                return ret;
-            }
+        if (nxt_fast_path(!pq->own || proto == object)) {
+            ret = nxt_lvlhsh_find(&proto->hash, &pq->lhq);
 
-            goto next;
-        }
+            if (ret == NXT_OK) {
+                prop = pq->lhq.value;
 
-        if (pq->query > NJS_PROPERTY_QUERY_IN) {
-            /* NXT_DECLINED */
-            return ret;
-        }
+                if (prop->type != NJS_WHITEOUT) {
+                    pq->shared = 0;
 
-        ret = nxt_lvlhsh_find(&object->shared_hash, &pq->lhq);
+                    return ret;
+                }
 
-        if (ret == NXT_OK) {
-            pq->shared = 1;
+                goto next;
+            }
 
-            if (pq->query == NJS_PROPERTY_QUERY_GET) {
-                prop = pq->lhq.value;
+            if (proto != object && !njs_is_null_or_void_or_boolean(property)) {
+                switch (proto->type) {
+                case NJS_ARRAY:
+                    index = njs_value_to_index(property);
+                    if (nxt_fast_path(index < NJS_ARRAY_MAX_LENGTH)) {
+                        array = (njs_array_t *) proto;
+                        return njs_array_property_query(vm, pq, array, index);
+                    }
 
-                if (prop->type == NJS_PROPERTY_HANDLER) {
-                    pq->scratch = *prop;
-                    prop = &pq->scratch;
-                    ret = prop->value.data.u.prop_handler(vm, value, NULL,
-                                                          &prop->value);
+                    break;
 
-                    if (nxt_fast_path(ret == NXT_OK)) {
-                        prop->type = NJS_PROPERTY;
-                        pq->lhq.value = prop;
+                case NJS_OBJECT_STRING:
+                    index = njs_value_to_index(property);
+                    if (nxt_fast_path(index < NJS_STRING_MAX_LENGTH)) {
+                        ov = (njs_object_value_t *) proto;
+                        return njs_string_property_query(vm, pq, &ov->value,
+                                                         index);
                     }
+
+                default:
+                    break;
                 }
             }
-
-            return ret;
         }
 
-        if (pq->query > NJS_PROPERTY_QUERY_IN) {
-            /* NXT_DECLINED */
+        ret = nxt_lvlhsh_find(&proto->shared_hash, &pq->lhq);
+
+        if (ret == NXT_OK) {
+            pq->shared = 1;
+
             return ret;
         }
 
-    next:
-
-        object = object->__proto__;
+        if (pq->query > NJS_PROPERTY_QUERY_GET) {
+            return NXT_DECLINED;
+        }
 
-    } while (object != NULL);
+next:
 
-    if (njs_is_string(value)) {
-        return NJS_STRING_VALUE;
-    }
+        proto = proto->__proto__;
 
-    /* NXT_DECLINED */
+    } while (proto != NULL);
 
-    return ret;
+    return NXT_DECLINED;
 }
 
 
 static njs_ret_t
 njs_array_property_query(njs_vm_t *vm, njs_property_query_t *pq,
-    njs_value_t *object, uint32_t index)
+    njs_array_t *array, uint32_t index)
 {
-    uint32_t     size;
-    njs_ret_t    ret;
-    njs_value_t  *value;
-    njs_array_t  *array;
-
-    array = object->data.u.array;
+    uint32_t           size;
+    njs_ret_t          ret;
+    njs_value_t        *value;
+    njs_object_prop_t  *prop;
 
     if (index >= array->length) {
         if (pq->query != NJS_PROPERTY_QUERY_SET) {
@@ -493,9 +513,200 @@ njs_array_property_query(njs_vm_t *vm, njs_property_query_t *pq,
         array->length = index + 1;
     }
 
-    pq->lhq.value = &array->start[index];
+    prop = &pq->scratch;
+
+    if (pq->query == NJS_PROPERTY_QUERY_GET) {
+        if (!njs_is_valid(&array->start[index])) {
+            return NXT_DECLINED;
+        }
+
+        prop->value = array->start[index];
+        prop->type = NJS_PROPERTY;
+
+    } else {
+        prop->value.data.u.value = &array->start[index];
+        prop->type = NJS_PROPERTY_REF;
+    }
 
-    return NJS_ARRAY_VALUE;
+    prop->configurable = 1;
+    prop->enumerable = 1;
+    prop->writable = 1;
+
+    pq->lhq.value = prop;
+
+    return NXT_OK;
+}
+
+
+static njs_ret_t
+njs_string_property_query(njs_vm_t *vm, njs_property_query_t *pq,
+    njs_value_t *object, uint32_t index)
+{
+    njs_slice_prop_t   slice;
+    njs_object_prop_t  *prop;
+    njs_string_prop_t  string;
+
+    prop = &pq->scratch;
+
+    slice.start = index;
+    slice.length = 1;
+    slice.string_length = njs_string_prop(&string, object);
+
+    if (slice.start < slice.string_length) {
+        /*
+         * A single codepoint string fits in retval
+         * so the function cannot fail.
+         */
+        (void) njs_string_slice(vm, &prop->value, &string, &slice);
+        prop->type = NJS_PROPERTY;
+        prop->configurable = 0;
+        prop->enumerable = 1;
+        prop->writable = 0;
+
+        pq->lhq.value = prop;
+
+        if (pq->query != NJS_PROPERTY_QUERY_GET) {
+            /* pq->lhq.key is used by njs_vmcode_property_set for TypeError */
+            njs_uint32_to_string(&pq->value, index);
+            njs_string_get(&pq->value, &pq->lhq.key);
+        }
+
+        return NXT_OK;
+    }
+
+    return NXT_DECLINED;
+}
+
+
+static njs_ret_t
+njs_external_property_query(njs_vm_t *vm, njs_property_query_t *pq,
+    njs_value_t *object)
+{
+    void                *obj;
+    njs_ret_t           ret;
+    uintptr_t           data;
+    njs_object_prop_t   *prop;
+    const njs_extern_t  *ext_proto;
+
+    prop = &pq->scratch;
+
+    prop->type = NJS_PROPERTY;
+    prop->configurable = 0;
+    prop->enumerable = 1;
+    prop->writable = 0;
+
+    ext_proto = object->external.proto;
+
+    pq->lhq.proto = &njs_extern_hash_proto;
+    ret = nxt_lvlhsh_find(&ext_proto->hash, &pq->lhq);
+
+    if (ret == NXT_OK) {
+        ext_proto = pq->lhq.value;
+
+        prop->value.type = NJS_EXTERNAL;
+        prop->value.data.truth = 1;
+        prop->value.external.proto = ext_proto;
+        prop->value.external.index = object->external.index;
+
+        if ((ext_proto->type & NJS_EXTERN_OBJECT) != 0) {
+            goto done;
+        }
+
+        data = ext_proto->data;
+
+    } else {
+        data = (uintptr_t) &pq->lhq.key;
+    }
+
+    switch (pq->query) {
+
+    case NJS_PROPERTY_QUERY_GET:
+        if (ext_proto->get != NULL) {
+            obj = njs_extern_object(vm, object);
+            ret = ext_proto->get(vm, &prop->value, obj, data);
+            if (nxt_slow_path(ret != NXT_OK)) {
+                return ret;
+            }
+        }
+
+        break;
+
+    case NJS_PROPERTY_QUERY_SET:
+    case NJS_PROPERTY_QUERY_DELETE:
+
+        prop->type = NJS_PROPERTY_HANDLER;
+        prop->name = *object;
+
+        if (pq->query == NJS_PROPERTY_QUERY_SET) {
+            prop->writable = (ext_proto->set != NULL);
+            prop->value.data.u.prop_handler = njs_external_property_set;
+
+        } else {
+            prop->configurable = (ext_proto->find != NULL);
+            prop->value.data.u.prop_handler = njs_external_property_delete;
+        }
+
+        pq->ext_data = data;
+        pq->ext_proto = ext_proto;
+        pq->ext_index = object->external.index;
+
+        pq->lhq.value = prop;
+
+        vm->stash = (uintptr_t) pq;
+
+        return NXT_OK;
+    }
+
+done:
+
+    if (ext_proto->type == NJS_EXTERN_METHOD) {
+        prop->value.type = NJS_FUNCTION;
+        prop->value.data.u.function = ext_proto->function;
+        prop->value.data.truth = 1;
+    }
+
+    pq->lhq.value = prop;
+
+    return ret;
+}
+
+
+static njs_ret_t
+njs_external_property_set(njs_vm_t *vm, njs_value_t *value, njs_value_t *setval,
+    njs_value_t *retval)
+{
+    void                  *obj;
+    njs_ret_t             ret;
+    nxt_str_t             s;
+    njs_property_query_t  *pq;
+
+    pq = (njs_property_query_t *) vm->stash;
+
+    ret = njs_vm_value_to_ext_string(vm, &s, setval, 0);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        return ret;
+    }
+
+    *retval = *setval;
+
+    obj = njs_extern_index(vm, pq->ext_index);
+
+    return pq->ext_proto->set(vm, obj, pq->ext_data, &s);
+}
+
+
+static njs_ret_t
+njs_external_property_delete(njs_vm_t *vm, njs_value_t *value,
+    njs_value_t *unused, njs_value_t *unused2)
+{
+    void                  *obj;
+    njs_property_query_t  *pq;
+
+    pq = (njs_property_query_t *) vm->stash;
+
+    obj = njs_extern_index(vm, pq->ext_index);
+
+    return pq->ext_proto->find(vm, obj, pq->ext_data, 1);
 }
 
 
@@ -526,6 +737,34 @@ njs_object_query_prop_handler(njs_property_query_t *pq, njs_object_t *object)
 }
 
 
+njs_ret_t
+njs_method_private_copy(njs_vm_t *vm, njs_property_query_t *pq)
+{
+    njs_function_t     *function;
+    njs_object_prop_t  *prop, *shared;
+
+    prop = nxt_mem_cache_alloc(vm->mem_cache_pool, sizeof(njs_object_prop_t));
+    if (nxt_slow_path(prop == NULL)) {
+        njs_memory_error(vm);
+        return NXT_ERROR;
+    }
+
+    shared = pq->lhq.value;
+    *prop = *shared;
+
+    function = njs_function_value_copy(vm, &prop->value);
+    if (nxt_slow_path(function == NULL)) {
+        return NXT_ERROR;
+    }
+
+    pq->lhq.replace = 0;
+    pq->lhq.value = prop;
+    pq->lhq.pool = vm->mem_cache_pool;
+
+    return nxt_lvlhsh_insert(&pq->prototype->hash, &pq->lhq);
+}
+
+
 njs_ret_t
 njs_object_constructor(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs,
     njs_index_t unused)
@@ -988,68 +1227,75 @@ static njs_ret_t
 njs_object_get_own_property_descriptor(njs_vm_t *vm, njs_value_t *args,
     nxt_uint_t nargs, njs_index_t unused)
 {
-    double                num;
-    uint32_t              index;
     nxt_int_t             ret;
-    njs_array_t           *array;
     njs_object_t          *descriptor;
-    njs_object_prop_t     *pr, *prop, array_prop;
+    njs_object_prop_t     *pr, *prop;
     const njs_value_t     *value, *property, *setval;
     nxt_lvlhsh_query_t    lhq;
     njs_property_query_t  pq;
 
     value = njs_arg(args, nargs, 1);
 
-    if (!njs_is_object(value)) {
-        if (njs_is_null_or_void(value)) {
-            njs_type_error(vm, "cannot convert %s argument to object",
-                           njs_type_string(value->type));
-            return NXT_ERROR;
-        }
-
-        vm->retval = njs_value_void;
-        return NXT_OK;
+    if (njs_is_null_or_void(value)) {
+        njs_type_error(vm, "cannot convert %s argument to object",
+                       njs_type_string(value->type));
+        return NXT_ERROR;
     }
 
-    prop = NULL;
     property = njs_arg(args, nargs, 2);
 
-    if (njs_is_array(value)) {
-        array = value->data.u.array;
-        num = njs_string_to_index(property);
-        index = num;
+    njs_property_query_init(&pq, NJS_PROPERTY_QUERY_GET, 1);
 
-        if ((double) index == num
-            && index < array->length
-            && njs_is_valid(&array->start[index]))
-        {
-            prop = &array_prop;
+    ret = njs_property_query(vm, &pq, (njs_value_t *) value, property);
 
-            array_prop.name = *property;
-            array_prop.value = array->start[index];
+    switch (ret) {
+    case NXT_OK:
+        break;
 
-            array_prop.configurable = 1;
-            array_prop.enumerable = 1;
-            array_prop.writable = 1;
-        }
+    case NXT_DECLINED:
+        vm->retval = njs_value_void;
+        return NXT_OK;
+
+    case NJS_TRAP:
+    case NXT_ERROR:
+    default:
+        return ret;
     }
 
-    lhq.proto = &njs_object_hash_proto;
+    prop = pq.lhq.value;
+
+    switch (prop->type) {
+    case NJS_PROPERTY:
+        break;
+
+    case NJS_PROPERTY_HANDLER:
+        pq.scratch = *prop;
+        prop = &pq.scratch;
+        ret = prop->value.data.u.prop_handler(vm, (njs_value_t *) value,
+                                              NULL, &prop->value);
+        if (nxt_slow_path(ret != NXT_OK)) {
+            return ret;
+        }
+
+        break;
 
-    if (prop == NULL) {
-        pq.query = NJS_PROPERTY_QUERY_GET;
-        pq.lhq.key.length = 0;
-        pq.lhq.key.start = NULL;
+    case NJS_METHOD:
+        if (pq.shared) {
+            ret = njs_method_private_copy(vm, &pq);
 
-        ret = njs_property_query(vm, &pq, (njs_value_t *) value,
-                                 (njs_value_t *) property);
+            if (nxt_slow_path(ret != NXT_OK)) {
+                return ret;
+            }
 
-        if (ret != NXT_OK) {
-            vm->retval = njs_value_void;
-            return NXT_OK;
+            prop = pq.lhq.value;
         }
 
-        prop = pq.lhq.value;
+        break;
+
+    default:
+        njs_type_error(vm, "unexpected property type: %s",
+                       njs_prop_type_string(prop->type));
+        return NXT_ERROR;
     }
 
     descriptor = njs_object_alloc(vm);
@@ -1057,6 +1303,7 @@ njs_object_get_own_property_descriptor(njs_vm_t *vm, njs_value_t *args,
         return NXT_ERROR;
     }
 
+    lhq.proto = &njs_object_hash_proto;
     lhq.replace = 0;
     lhq.pool = vm->mem_cache_pool;
     lhq.proto = &njs_object_hash_proto;
@@ -1953,3 +2200,28 @@ const njs_object_init_t  njs_object_prototype_init = {
     njs_object_prototype_properties,
     nxt_nitems(njs_object_prototype_properties),
 };
+
+
+const char *
+njs_prop_type_string(njs_object_property_type_t type)
+{
+    switch (type) {
+    case NJS_PROPERTY_REF:
+        return "property_ref";
+
+    case NJS_METHOD:
+        return "method";
+
+    case NJS_PROPERTY_HANDLER:
+        return "property handler";
+
+    case NJS_WHITEOUT:
+        return "whiteout";
+
+    case NJS_PROPERTY:
+        return "property";
+
+    default:
+        return "unknown";
+    }
+}
index 0026263237a1cac5c7edf7e194eb14a91f446b20..87d53a47ab78d9c7d7148045da5507a4d0f024c3 100644 (file)
@@ -10,8 +10,7 @@
 
 typedef enum {
     NJS_PROPERTY = 0,
-    NJS_GETTER,
-    NJS_SETTER,
+    NJS_PROPERTY_REF,
     NJS_METHOD,
     NJS_PROPERTY_HANDLER,
     NJS_WHITEOUT,
@@ -47,13 +46,27 @@ typedef struct {
     /* scratch is used to get the value of an NJS_PROPERTY_HANDLER property. */
     njs_object_prop_t           scratch;
 
+    /* These three fields are used for NJS_EXTERNAL setters. */
+    uintptr_t                   ext_data;
+    const njs_extern_t          *ext_proto;
+    uint32_t                    ext_index;
+
     njs_value_t                 value;
     njs_object_t                *prototype;
     uint8_t                     query;
     uint8_t                     shared;
+    uint8_t                     own;
 } njs_property_query_t;
 
 
+#define njs_property_query_init(pq, _query, _own)                             \
+    do {                                                                      \
+        (pq)->lhq.key.length = 0;                                             \
+        (pq)->lhq.value = NULL;                                               \
+        (pq)->query = _query;                                                 \
+        (pq)->own = _own;                                                     \
+    } while (0)
+
 
 struct njs_object_init_s {
     nxt_str_t                   name;
@@ -67,10 +80,12 @@ njs_object_t *njs_object_value_copy(njs_vm_t *vm, njs_value_t *value);
 njs_object_t *njs_object_value_alloc(njs_vm_t *vm, const njs_value_t *value,
     nxt_uint_t type);
 njs_array_t *njs_object_keys_array(njs_vm_t *vm, const njs_value_t *object);
+njs_ret_t njs_value_property(njs_vm_t *vm, njs_value_t *value,
+    const njs_value_t *property, njs_value_t *retval);
 njs_object_prop_t *njs_object_property(njs_vm_t *vm, const njs_object_t *obj,
     nxt_lvlhsh_query_t *lhq);
 njs_ret_t njs_property_query(njs_vm_t *vm, njs_property_query_t *pq,
-    njs_value_t *object, njs_value_t *property);
+    njs_value_t *object, const njs_value_t *property);
 nxt_int_t njs_object_hash_create(njs_vm_t *vm, nxt_lvlhsh_t *hash,
     const njs_object_prop_t *prop, nxt_uint_t n);
 njs_ret_t njs_object_constructor(njs_vm_t *vm, njs_value_t *args,
@@ -90,6 +105,9 @@ njs_value_t *njs_property_constructor_create(njs_vm_t *vm, nxt_lvlhsh_t *hash,
 njs_ret_t njs_object_prototype_to_string(njs_vm_t *vm, njs_value_t *args,
     nxt_uint_t nargs, njs_index_t unused);
 
+njs_ret_t njs_method_private_copy(njs_vm_t *vm, njs_property_query_t *pq);
+const char * njs_prop_type_string(njs_object_property_type_t type);
+
 extern const njs_object_init_t  njs_object_constructor_init;
 extern const njs_object_init_t  njs_object_prototype_init;
 
index 7527749d6ee8b80df181040b94712c6a54c48fd7..e10ed68c9de1ca21bb2a1be4280c7672b543402a 100644 (file)
@@ -25,8 +25,6 @@ struct njs_property_next_s {
 
 static nxt_noinline njs_ret_t njs_string_concat(njs_vm_t *vm,
     njs_value_t *val1, njs_value_t *val2);
-static njs_ret_t njs_method_private_copy(njs_vm_t *vm,
-    njs_property_query_t *pq);
 static nxt_noinline njs_ret_t njs_values_equal(njs_vm_t *vm,
     const njs_value_t *val1, const njs_value_t *val2);
 static nxt_noinline njs_ret_t njs_values_compare(njs_vm_t *vm,
@@ -476,150 +474,14 @@ njs_ret_t
 njs_vmcode_property_get(njs_vm_t *vm, njs_value_t *object,
     njs_value_t *property)
 {
-    void                  *obj;
-    int32_t               index;
-    uintptr_t             data;
-    njs_ret_t             ret;
-    njs_value_t           *val, ext_val;
-    njs_slice_prop_t      slice;
-    njs_string_prop_t     string;
-    njs_object_prop_t     *prop;
-    const njs_value_t     *retval;
-    const njs_extern_t    *ext_proto;
-    njs_property_query_t  pq;
-
-    pq.query = NJS_PROPERTY_QUERY_GET;
-
-    ret = njs_property_query(vm, &pq, object, property);
-
-    retval = &njs_value_void;
-
-    switch (ret) {
-
-    case NXT_OK:
-        prop = pq.lhq.value;
-
-        switch (prop->type) {
-
-        case NJS_METHOD:
-            if (pq.shared) {
-                ret = njs_method_private_copy(vm, &pq);
-
-                if (nxt_slow_path(ret != NXT_OK)) {
-                    return ret;
-                }
-
-                prop = pq.lhq.value;
-            }
-
-            /* Fall through. */
-
-        case NJS_PROPERTY:
-            retval = &prop->value;
-            break;
-
-        default:
-            nxt_thread_log_alert("invalid property get type:%d", prop->type);
-
-            return NXT_ERROR;
-        }
-
-        break;
-
-    case NXT_DECLINED:
-    case NJS_PRIMITIVE_VALUE:
-        break;
-
-    case NJS_STRING_VALUE:
-
-        /* string[n]. */
-
-        index = (int32_t) njs_value_to_index(property);
-
-        if (nxt_fast_path(index >= 0)) {
-            slice.start = index;
-            slice.length = 1;
-            slice.string_length = njs_string_prop(&string, object);
-
-            if (slice.start < slice.string_length) {
-                /*
-                 * A single codepoint string fits in vm->retval
-                 * so the function cannot fail.
-                 */
-                (void) njs_string_slice(vm, &vm->retval, &string, &slice);
-
-                return sizeof(njs_vmcode_prop_get_t);
-            }
-        }
-
-        break;
-
-    case NJS_ARRAY_VALUE:
-        val = pq.lhq.value;
-
-        if (njs_is_valid(val)) {
-            retval = val;
-        }
-
-        break;
-
-    case NJS_EXTERNAL_VALUE:
-        ext_proto = object->external.proto;
-
-        ret = nxt_lvlhsh_find(&ext_proto->hash, &pq.lhq);
-
-        if (ret == NXT_OK) {
-            ext_proto = pq.lhq.value;
-
-            ext_val.type = NJS_EXTERNAL;
-            ext_val.data.truth = 1;
-            ext_val.external.proto = ext_proto;
-            ext_val.external.index = object->external.index;
-
-            if ((ext_proto->type & NJS_EXTERN_OBJECT) != 0) {
-                retval = &ext_val;
-                break;
-            }
-
-            data = ext_proto->data;
-
-        } else {
-            data = (uintptr_t) &pq.lhq.key;
-        }
-
-        vm->retval = njs_value_void;
-
-        if (ext_proto->get != NULL) {
-            obj = njs_extern_object(vm, object);
-
-            ret = ext_proto->get(vm, &vm->retval, obj, data);
-            if (nxt_slow_path(ret != NXT_OK)) {
-                return ret;
-            }
-
-            /* The vm->retval is already retained by ext_proto->get(). */
-        }
-
-        if (ext_proto->type == NJS_EXTERN_METHOD) {
-            vm->retval.data.u.function = ext_proto->function;
-            vm->retval.type = NJS_FUNCTION;
-            vm->retval.data.truth = 1;
-        }
+    njs_ret_t  ret;
 
+    ret = njs_value_property(vm, object, property, &vm->retval);
+    if (ret == NXT_OK || ret == NXT_DECLINED) {
         return sizeof(njs_vmcode_prop_get_t);
-
-    case NJS_TRAP:
-    case NXT_ERROR:
-    default:
-
-        return ret;
     }
 
-    vm->retval = *retval;
-
-    /* GC: njs_retain(retval) */
-
-    return sizeof(njs_vmcode_prop_get_t);
+    return ret;
 }
 
 
@@ -627,13 +489,9 @@ njs_ret_t
 njs_vmcode_property_set(njs_vm_t *vm, njs_value_t *object,
     njs_value_t *property)
 {
-    void                   *obj;
-    uintptr_t              data;
-    nxt_str_t              s;
     njs_ret_t              ret;
-    njs_value_t            *p, *value;
+    njs_value_t            *value;
     njs_object_prop_t      *prop;
-    const njs_extern_t     *ext_proto;
     njs_property_query_t   pq;
     njs_vmcode_prop_set_t  *code;
 
@@ -646,8 +504,7 @@ 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);
 
-    pq.lhq.key.length = 0;
-    pq.query = NJS_PROPERTY_QUERY_SET;
+    njs_property_query_init(&pq, NJS_PROPERTY_QUERY_SET, 0);
 
     ret = njs_property_query(vm, &pq, object, property);
 
@@ -656,14 +513,33 @@ njs_vmcode_property_set(njs_vm_t *vm, njs_value_t *object,
     case NXT_OK:
         prop = pq.lhq.value;
 
-        if (prop->type == NJS_PROPERTY_HANDLER && prop->writable) {
-            ret = prop->value.data.u.prop_handler(vm, object, value,
-                                                  &vm->retval);
-            if (nxt_slow_path(ret != NXT_OK)) {
-                return ret;
-            }
+        switch (prop->type) {
+        case NJS_PROPERTY:
+            break;
 
+        case NJS_PROPERTY_REF:
+            *prop->value.data.u.value = *value;
             return sizeof(njs_vmcode_prop_set_t);
+
+        case NJS_PROPERTY_HANDLER:
+            if (prop->writable) {
+                ret = prop->value.data.u.prop_handler(vm, object, value,
+                                                      &vm->retval);
+                if (nxt_slow_path(ret != NXT_OK)) {
+                    return ret;
+                }
+
+                return sizeof(njs_vmcode_prop_set_t);
+            }
+
+            break;
+
+        default:
+            njs_internal_error(vm, "unexpected property type '%s' "
+                               "while setting",
+                               njs_prop_type_string(prop->type));
+
+            return NXT_ERROR;
         }
 
         break;
@@ -687,53 +563,12 @@ njs_vmcode_property_set(njs_vm_t *vm, njs_value_t *object,
 
         ret = nxt_lvlhsh_insert(&object->data.u.object->hash, &pq.lhq);
         if (nxt_slow_path(ret != NXT_OK)) {
-            /* Only NXT_ERROR can be returned here. */
-            return ret;
+            njs_internal_error(vm, "lvlhsh insert failed");
+            return NXT_ERROR;
         }
 
         break;
 
-    case NJS_PRIMITIVE_VALUE:
-    case NJS_STRING_VALUE:
-        return sizeof(njs_vmcode_prop_set_t);
-
-    case NJS_ARRAY_VALUE:
-        p = pq.lhq.value;
-        *p = *value;
-
-        return sizeof(njs_vmcode_prop_set_t);
-
-    case NJS_EXTERNAL_VALUE:
-        ext_proto = object->external.proto;
-
-        ret = nxt_lvlhsh_find(&ext_proto->hash, &pq.lhq);
-
-        if (ret == NXT_OK) {
-            ext_proto = pq.lhq.value;
-            data = ext_proto->data;
-
-        } else {
-            data = (uintptr_t) &pq.lhq.key;
-        }
-
-        if (ext_proto->set != NULL) {
-            ret = njs_vm_value_to_ext_string(vm, &s, value, 0);
-            if (nxt_slow_path(ret != NXT_OK)) {
-                return ret;
-            }
-
-            /* TODO retain value if it is string. */
-
-            obj = njs_extern_object(vm, object);
-
-            ret = ext_proto->set(vm, obj, data, &s);
-            if (nxt_slow_path(ret != NXT_OK)) {
-                return ret;
-            }
-        }
-
-        return sizeof(njs_vmcode_prop_set_t);
-
     case NJS_TRAP:
     case NXT_ERROR:
     default:
@@ -757,68 +592,34 @@ njs_vmcode_property_set(njs_vm_t *vm, njs_value_t *object,
 njs_ret_t
 njs_vmcode_property_in(njs_vm_t *vm, njs_value_t *object, njs_value_t *property)
 {
-    void                  *obj;
-    uintptr_t             data;
     njs_ret_t             ret;
-    njs_value_t           *value;
+    njs_object_prop_t     *prop;
     const njs_value_t     *retval;
-    const njs_extern_t    *ext_proto;
     njs_property_query_t  pq;
 
     retval = &njs_value_false;
 
-    pq.query = NJS_PROPERTY_QUERY_IN;
+    njs_property_query_init(&pq, NJS_PROPERTY_QUERY_GET, 0);
 
     ret = njs_property_query(vm, &pq, object, property);
 
     switch (ret) {
 
     case NXT_OK:
-        retval = &njs_value_true;
-        break;
-
-    case NXT_DECLINED:
-        break;
-
-    case NJS_PRIMITIVE_VALUE:
-    case NJS_STRING_VALUE:
-        njs_type_error(vm, "property in on a primitive value");
-
-        return NXT_ERROR;
-
-    case NJS_ARRAY_VALUE:
-        value = pq.lhq.value;
+        prop = pq.lhq.value;
 
-        if (njs_is_valid(value)) {
-            retval = &njs_value_true;
+        if (!njs_is_valid(&prop->value)) {
+            break;
         }
 
+        retval = &njs_value_true;
         break;
 
-    case NJS_EXTERNAL_VALUE:
-        ext_proto = object->external.proto;
-
-        ret = nxt_lvlhsh_find(&ext_proto->hash, &pq.lhq);
-
-        if (ret == NXT_OK) {
-            retval = &njs_value_true;
-
-        } else {
-            data = (uintptr_t) &pq.lhq.key;
-
-            if (ext_proto->find != NULL) {
-                obj = njs_extern_object(vm, object);
-
-                ret = ext_proto->find(vm, obj, data, 0);
-
-                if (nxt_slow_path(ret == NXT_ERROR)) {
-                    return ret;
-                }
+    case NXT_DECLINED:
+        if (!njs_is_object(object) && !njs_is_external(object)) {
+            njs_type_error(vm, "property in on a primitive value");
 
-                if (ret == NXT_OK) {
-                    retval = &njs_value_true;
-                }
-            }
+            return NXT_ERROR;
         }
 
         break;
@@ -840,19 +641,14 @@ njs_ret_t
 njs_vmcode_property_delete(njs_vm_t *vm, njs_value_t *object,
     njs_value_t *property)
 {
-    void                  *obj;
-    uintptr_t             data;
     njs_ret_t             ret;
-    njs_value_t           *value, ext_val;
     const njs_value_t     *retval;
     njs_object_prop_t     *prop;
-    const njs_extern_t    *ext_proto;
     njs_property_query_t  pq;
 
     retval = &njs_value_false;
 
-    pq.lhq.key.length = 0;
-    pq.query = NJS_PROPERTY_QUERY_DELETE;
+    njs_property_query_init(&pq, NJS_PROPERTY_QUERY_DELETE, 1);
 
     ret = njs_property_query(vm, &pq, object, property);
 
@@ -861,6 +657,37 @@ njs_vmcode_property_delete(njs_vm_t *vm, njs_value_t *object,
     case NXT_OK:
         prop = pq.lhq.value;
 
+        switch (prop->type) {
+        case NJS_PROPERTY:
+        case NJS_METHOD:
+            break;
+
+        case NJS_PROPERTY_REF:
+            njs_set_invalid(prop->value.data.u.value);
+            retval = &njs_value_true;
+            goto done;
+
+        case NJS_PROPERTY_HANDLER:
+            if (prop->configurable) {
+                ret = prop->value.data.u.prop_handler(vm, object, NULL, NULL);
+                if (nxt_slow_path(ret != NXT_OK)) {
+                    return ret;
+                }
+
+                retval = &njs_value_true;
+                goto done;
+            }
+
+            break;
+
+        default:
+            njs_internal_error(vm, "unexpected property type '%s' "
+                               "while deleting",
+                               njs_prop_type_string(prop->type));
+
+            return NXT_ERROR;
+        }
+
         if (nxt_slow_path(!prop->configurable)) {
             njs_type_error(vm, "Cannot delete property '%.*s' of %s",
                            pq.lhq.key.length, pq.lhq.key.start,
@@ -879,56 +706,6 @@ njs_vmcode_property_delete(njs_vm_t *vm, njs_value_t *object,
         break;
 
     case NXT_DECLINED:
-    case NJS_PRIMITIVE_VALUE:
-    case NJS_STRING_VALUE:
-        break;
-
-    case NJS_ARRAY_VALUE:
-        value = pq.lhq.value;
-        njs_set_invalid(value);
-        retval = &njs_value_true;
-        break;
-
-    case NJS_EXTERNAL_VALUE:
-
-        ext_proto = object->external.proto;
-
-        ret = nxt_lvlhsh_find(&ext_proto->hash, &pq.lhq);
-
-        if (ret == NXT_OK) {
-            ext_proto = pq.lhq.value;
-
-            if ((ext_proto->type & NJS_EXTERN_OBJECT) != 0) {
-
-                ext_val.type = NJS_EXTERNAL;
-                ext_val.data.truth = 1;
-                ext_val.external.proto = ext_proto;
-                ext_val.external.index = object->external.index;
-
-                data = (uintptr_t) &ext_val;
-
-            } else {
-                data = ext_proto->data;
-            }
-
-        } else {
-            data = (uintptr_t) &pq.lhq.key;
-        }
-
-        if (ext_proto->find != NULL) {
-            obj = njs_extern_object(vm, object);
-
-            ret = ext_proto->find(vm, obj, data, 1);
-
-            if (nxt_slow_path(ret == NXT_ERROR)) {
-                return ret;
-            }
-
-            if (ret == NXT_OK) {
-                retval = &njs_value_true;
-            }
-        }
-
         break;
 
     case NJS_TRAP:
@@ -938,40 +715,14 @@ njs_vmcode_property_delete(njs_vm_t *vm, njs_value_t *object,
         return ret;
     }
 
+done:
+
     vm->retval = *retval;
 
     return sizeof(njs_vmcode_3addr_t);
 }
 
 
-static njs_ret_t
-njs_method_private_copy(njs_vm_t *vm, njs_property_query_t *pq)
-{
-    njs_function_t     *function;
-    njs_object_prop_t  *prop, *shared;
-
-    prop = nxt_mem_cache_alloc(vm->mem_cache_pool, sizeof(njs_object_prop_t));
-    if (nxt_slow_path(prop == NULL)) {
-        njs_memory_error(vm);
-        return NXT_ERROR;
-    }
-
-    shared = pq->lhq.value;
-    *prop = *shared;
-
-    function = njs_function_value_copy(vm, &prop->value);
-    if (nxt_slow_path(function == NULL)) {
-        return NXT_ERROR;
-    }
-
-    pq->lhq.replace = 0;
-    pq->lhq.value = prop;
-    pq->lhq.pool = vm->mem_cache_pool;
-
-    return nxt_lvlhsh_insert(&pq->prototype->hash, &pq->lhq);
-}
-
-
 njs_ret_t
 njs_vmcode_property_foreach(njs_vm_t *vm, njs_value_t *object,
     njs_value_t *invld)
@@ -1097,12 +848,10 @@ njs_ret_t
 njs_vmcode_instance_of(njs_vm_t *vm, njs_value_t *object,
     njs_value_t *constructor)
 {
-    nxt_int_t             ret;
-    njs_value_t           *value;
-    njs_object_t          *prototype, *proto;
-    njs_object_prop_t     *prop;
-    const njs_value_t     *retval;
-    njs_property_query_t  pq;
+    nxt_int_t          ret;
+    njs_value_t        value;
+    njs_object_t       *prototype, *proto;
+    const njs_value_t  *retval;
 
     static njs_value_t prototype_string = njs_string("prototype");
 
@@ -1114,17 +863,17 @@ njs_vmcode_instance_of(njs_vm_t *vm, njs_value_t *object,
     retval = &njs_value_false;
 
     if (njs_is_object(object)) {
-        pq.query = NJS_PROPERTY_QUERY_GET;
-
-        ret = njs_property_query(vm, &pq, constructor, &prototype_string);
+        value = njs_value_void;
+        ret = njs_value_property(vm, constructor, &prototype_string, &value);
 
         if (nxt_fast_path(ret == NXT_OK)) {
-            prop = pq.lhq.value;
-            value = &prop->value;
 
-            /* TODO: test prop->value is object. */
+            if (nxt_slow_path(!njs_is_object(&value))) {
+                njs_internal_error(vm, "prototype is not an object");
+                return NXT_ERROR;
+            }
 
-            prototype = value->data.u.object;
+            prototype = value.data.u.object;
             proto = object->data.u.object;
 
             do {
@@ -2115,17 +1864,16 @@ njs_ret_t
 njs_vmcode_method_frame(njs_vm_t *vm, njs_value_t *object, njs_value_t *name)
 {
     njs_ret_t                  ret;
+    nxt_str_t                  string;
     njs_value_t                *value;
     njs_object_prop_t          *prop;
     njs_property_query_t       pq;
-    const njs_extern_t         *ext_proto;
     njs_vmcode_method_frame_t  *method;
 
+    value = NULL;
     method = (njs_vmcode_method_frame_t *) vm->current;
 
-    pq.lhq.key.length = 0;
-    pq.lhq.key.start = NULL;
-    pq.query = NJS_PROPERTY_QUERY_GET;
+    njs_property_query_init(&pq, NJS_PROPERTY_QUERY_GET, 0);
 
     ret = njs_property_query(vm, &pq, object, name);
 
@@ -2134,53 +1882,54 @@ njs_vmcode_method_frame(njs_vm_t *vm, njs_value_t *object, njs_value_t *name)
     case NXT_OK:
         prop = pq.lhq.value;
 
-        ret = njs_function_frame_create(vm, &prop->value, object, method->nargs,
-                                        method->code.ctor);
-        break;
-
-    case NJS_ARRAY_VALUE:
-        value = pq.lhq.value;
+        switch (prop->type) {
+        case NJS_PROPERTY:
+        case NJS_METHOD:
+            break;
 
-        ret = njs_function_frame_create(vm, value, object, method->nargs,
-                                        method->code.ctor);
-        break;
+        case NJS_PROPERTY_HANDLER:
+            pq.scratch = *prop;
+            prop = &pq.scratch;
+            ret = prop->value.data.u.prop_handler(vm, object, NULL,
+                                                  &prop->value);
+            if (nxt_slow_path(ret != NXT_OK)) {
+                return ret;
+            }
 
-    case NJS_EXTERNAL_VALUE:
-        ext_proto = object->external.proto;
+            break;
 
-        ret = nxt_lvlhsh_find(&ext_proto->hash, &pq.lhq);
+        default:
+            njs_internal_error(vm, "unexpected property type '%s' "
+                               "while getting method",
+                               njs_prop_type_string(prop->type));
 
-        if (nxt_slow_path(ret != NXT_OK)) {
-            njs_type_error(vm,
-                           "cannot find property '%.*s' of an external object",
-                           (int) pq.lhq.key.length, pq.lhq.key.start);
             return NXT_ERROR;
-
         }
 
-        ext_proto = pq.lhq.value;
+        value = &prop->value;
 
-        if (nxt_slow_path(ext_proto->type != NJS_EXTERN_METHOD)) {
-            njs_type_error(vm,
-                          "method '%.*s' of an external object is not callable",
-                          (int) pq.lhq.key.length, pq.lhq.key.start);
-            return NXT_ERROR;
-        }
+        break;
 
-        ret = njs_function_native_frame(vm, ext_proto->function, object, NULL,
-                                        method->nargs, 0, method->code.ctor);
+    case NXT_DECLINED:
         break;
 
+    case NJS_TRAP:
     case NXT_ERROR:
-        /* An exception was set in njs_property_query(). */
-        return NXT_ERROR;
-
     default:
-        njs_internal_error(vm, "method '%.*s' query failed:%d",
-                           (int) pq.lhq.key.length, pq.lhq.key.start, ret);
+
+        return ret;
+    }
+
+    if (value == NULL || !njs_is_function(value)) {
+        njs_string_get(name, &string);
+        njs_type_error(vm, "'%.*s' is not a function", (int) string.length,
+                       string.start);
         return NXT_ERROR;
     }
 
+    ret = njs_function_frame_create(vm, value, object, method->nargs,
+                                    method->code.ctor);
+
     if (nxt_fast_path(ret == NXT_OK)) {
         return sizeof(njs_vmcode_method_frame_t);
     }
@@ -3159,6 +2908,45 @@ njs_vmcode_string_argument(njs_vm_t *vm, njs_value_t *invld1,
 }
 
 
+static njs_ret_t
+njs_vmcode_restart(njs_vm_t *vm, njs_value_t *invld1, njs_value_t *invld2)
+{
+    u_char                *restart;
+    njs_ret_t             ret;
+    njs_value_t           *retval, *value1;
+    njs_native_frame_t    *frame;
+    njs_vmcode_generic_t  *vmcode;
+
+    frame = vm->top_frame;
+    restart = frame->trap_restart;
+    frame->trap_restart = NULL;
+    vm->current = restart;
+    vmcode = (njs_vmcode_generic_t *) restart;
+
+    value1 = &frame->trap_values[0];
+
+    if (frame->trap_reference) {
+        value1 = value1->data.u.value;
+    }
+
+    ret = vmcode->code.operation(vm, value1, &frame->trap_values[1]);
+
+    if (nxt_slow_path(ret == NJS_TRAP)) {
+        /* Trap handlers are not reentrant. */
+        njs_internal_error(vm, "trap inside restart instruction");
+        return NXT_ERROR;
+    }
+
+    retval = njs_vmcode_operand(vm, vmcode->operand1);
+
+    //njs_release(vm, retval);
+
+    *retval = vm->retval;
+
+    return ret;
+}
+
+
 /*
  * A hint value is 0 for numbers and 1 for strings.  The value chooses
  * method calls order specified by ECMAScript 5.1: "valueOf", "toString"
@@ -3257,42 +3045,88 @@ njs_primitive_value(njs_vm_t *vm, njs_value_t *value, nxt_uint_t hint)
 }
 
 
-static njs_ret_t
-njs_vmcode_restart(njs_vm_t *vm, njs_value_t *invld1, njs_value_t *invld2)
+/*
+ * ES5.1, 8.12.3: [[Get]].
+ *   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_ERROR            exception has been thrown.
+ *      retval will contain undefined
+ */
+njs_ret_t
+njs_value_property(njs_vm_t *vm, njs_value_t *value,
+    const njs_value_t *property, njs_value_t *retval)
 {
-    u_char                *restart;
     njs_ret_t             ret;
-    njs_value_t           *retval, *value1;
-    njs_native_frame_t    *frame;
-    njs_vmcode_generic_t  *vmcode;
+    njs_object_prop_t     *prop;
+    njs_property_query_t  pq;
 
-    frame = vm->top_frame;
-    restart = frame->trap_restart;
-    frame->trap_restart = NULL;
-    vm->current = restart;
-    vmcode = (njs_vmcode_generic_t *) restart;
+    njs_property_query_init(&pq, NJS_PROPERTY_QUERY_GET, 0);
 
-    value1 = &frame->trap_values[0];
+    ret = njs_property_query(vm, &pq, value, property);
 
-    if (frame->trap_reference) {
-        value1 = value1->data.u.value;
-    }
+    switch (ret) {
 
-    ret = vmcode->code.operation(vm, value1, &frame->trap_values[1]);
+    case NXT_OK:
+        prop = pq.lhq.value;
 
-    if (nxt_slow_path(ret == NJS_TRAP)) {
-        /* Trap handlers are not reentrant. */
-        njs_internal_error(vm, "trap inside restart instruction");
-        return NXT_ERROR;
-    }
+        switch (prop->type) {
 
-    retval = njs_vmcode_operand(vm, vmcode->operand1);
+        case NJS_METHOD:
+            if (pq.shared) {
+                ret = njs_method_private_copy(vm, &pq);
 
-    //njs_release(vm, retval);
+                if (nxt_slow_path(ret != NXT_OK)) {
+                    return ret;
+                }
 
-    *retval = vm->retval;
+                prop = pq.lhq.value;
+            }
 
-    return ret;
+            /* Fall through. */
+
+        case NJS_PROPERTY:
+            *retval = prop->value;
+            break;
+
+        case NJS_PROPERTY_HANDLER:
+            pq.scratch = *prop;
+            prop = &pq.scratch;
+            ret = prop->value.data.u.prop_handler(vm, value, NULL,
+                                                  &prop->value);
+
+            if (nxt_slow_path(ret != NXT_OK)) {
+                return ret;
+            }
+
+            *retval = prop->value;
+            break;
+
+        default:
+            njs_internal_error(vm, "unexpected property type '%s' "
+                               "while getting",
+                               njs_prop_type_string(prop->type));
+
+            return NXT_ERROR;
+        }
+
+        break;
+
+    case NXT_DECLINED:
+        *retval = njs_value_void;
+
+        return NXT_DECLINED;
+
+    case NJS_TRAP:
+    case NXT_ERROR:
+    default:
+
+        return ret;
+    }
+
+    return NXT_OK;
 }
 
 
index ceb77467adb59a0c97ae183d75d6235f381fbccc..7db4ae8eb4fe81a50f4412f513d63f99560f2fc2 100644 (file)
@@ -61,22 +61,13 @@ typedef enum {
 #define NJS_APPLIED              NXT_DONE
 
 
-/* The values must be greater than NXT_OK. */
-#define NJS_PRIMITIVE_VALUE        1
-#define NJS_STRING_VALUE           2
-#define NJS_ARRAY_VALUE            3
-#define NJS_EXTERNAL_VALUE         4
-
-
 /*
- * NJS_PROPERTY_QUERY_GET must be less or equal to NJS_PROPERTY_QUERY_IN,
- * NJS_PROPERTY_QUERY_SET and NJS_PROPERTY_QUERY_DELETE must be greater
- * than NJS_PROPERTY_QUERY_IN.
+ * NJS_PROPERTY_QUERY_GET must be less to NJS_PROPERTY_QUERY_SET
+ * and NJS_PROPERTY_QUERY_DELETE.
  */
 #define NJS_PROPERTY_QUERY_GET     0
-#define NJS_PROPERTY_QUERY_IN      1
-#define NJS_PROPERTY_QUERY_SET     2
-#define NJS_PROPERTY_QUERY_DELETE  3
+#define NJS_PROPERTY_QUERY_SET     1
+#define NJS_PROPERTY_QUERY_DELETE  2
 
 
 /*
@@ -1073,6 +1064,13 @@ struct njs_vm_s {
     nxt_array_t              *backtrace;
 
     njs_trap_t               trap:8;
+
+    /*
+     * njs_property_query() uses it to store reference to a temporary
+     * PROPERTY_HANDLERs for NJS_EXTERNAL values in NJS_PROPERTY_QUERY_SET
+     * and NJS_PROPERTY_QUERY_DELETE modes.
+     */
+    uintptr_t                stash; /* njs_property_query_t * */
 };
 
 
index 12c45d87a64498635b2db1b0c4f67bccfe4350cc..271adb04533497ab8f853ffa84f8b4e900efbc25 100644 (file)
@@ -192,7 +192,7 @@ njs_test {
 
 njs_test {
     {"console.ll()\r\n"
-     "console.ll()\r\nTypeError: cannot find property 'll' of an external object"}
+     "console.ll()\r\nTypeError: 'll' is not a function"}
 }
 
 njs_test {
index 2596014f1760f8aac16e77f9a79deb908298e8d4..8bec27ca8a605a06ebd92d24b4fec4a3e826815d 100644 (file)
@@ -5,6 +5,8 @@
  */
 
 #include <njs_core.h>
+#include <nxt_lvlhsh.h>
+#include <nxt_djb_hash.h>
 #include <string.h>
 #include <stdlib.h>
 #include <stdio.h>
@@ -2453,9 +2455,24 @@ static njs_unit_test_t  njs_test[] =
     { nxt_string("var a = {}; a.b.c"),
       nxt_string("TypeError: cannot get property 'c' of undefined") },
 
+    { nxt_string("'a'[0]"),
+      nxt_string("a") },
+
+    { nxt_string("'a'[undefined]"),
+      nxt_string("undefined") },
+
+    { nxt_string("'a'[null]"),
+      nxt_string("undefined") },
+
+    { nxt_string("'a'[false]"),
+      nxt_string("undefined") },
+
     { nxt_string("'a'.b = 1"),
       nxt_string("TypeError: property set on primitive string type") },
 
+    { nxt_string("'a'[2] = 1"),
+      nxt_string("TypeError: property set on primitive string type") },
+
     { nxt_string("var a = {}; a.b = 1; a.b"),
       nxt_string("1") },
 
@@ -2619,9 +2636,33 @@ static njs_unit_test_t  njs_test[] =
     { nxt_string("delete --[][1]"),
       nxt_string("true") },
 
+    { nxt_string("var a = [1,2,3]; a.x = 10;  delete a[1]"),
+      nxt_string("true") },
+
+    { nxt_string("var o = Object.create({a:1}); o.a = 2; delete o.a; o.a"),
+      nxt_string("1") },
+
+    { nxt_string("function Foo() {this.bar = 10;}; Foo.prototype.bar = 42; "
+                 "var v = new Foo(); delete v.bar; v.bar"),
+      nxt_string("42") },
+
+    /* Math object is immutable. */
+
+    { nxt_string("delete Math.max"),
+      nxt_string("TypeError: Cannot delete property 'max' of object") },
+
+    { nxt_string("Math.E = 1"),
+      nxt_string("TypeError: Cannot assign to read-only property 'E' of object") },
+
     { nxt_string("var a = {}; 1 in a"),
       nxt_string("false") },
 
+    { nxt_string("'a' in {a:1}"),
+      nxt_string("true") },
+
+    { nxt_string("'a' in Object.create({a:1})"),
+      nxt_string("true") },
+
     { nxt_string("var a = 1; 1 in a"),
       nxt_string("TypeError: property in on a primitive value") },
 
@@ -2785,6 +2826,17 @@ static njs_unit_test_t  njs_test[] =
     { nxt_string("var a = [1,2]; a.length"),
       nxt_string("2") },
 
+#if 0
+    { nxt_string("Object.create([1,2]).length"),
+      nxt_string("2") },
+#endif
+
+    { nxt_string("Object.create(['α','β'])[1]"),
+      nxt_string("β") },
+
+    { nxt_string("Object.create(['α','β'])[false]"),
+      nxt_string("undefined") },
+
     /* Array.length setter */
 
     { nxt_string("[].length = {}"),
@@ -3998,6 +4050,62 @@ static njs_unit_test_t  njs_test[] =
     { nxt_string("var p1 = $r.props, p2 = $r2.props; '' + p1.a + p2.a"),
       nxt_string("12") },
 
+    { nxt_string("var p = $r3.props; p.a = 1"),
+      nxt_string("TypeError: Cannot assign to read-only property 'a' of external") },
+    { nxt_string("var p = $r3.props; delete p.a"),
+      nxt_string("TypeError: Cannot delete property 'a' of external") },
+
+    { nxt_string("$r.vars.p + $r2.vars.q + $r3.vars.k"),
+      nxt_string("pvalqvalkval") },
+
+    { nxt_string("$r.vars.unset"),
+      nxt_string("undefined") },
+
+    { nxt_string("var v = $r3.vars; v.k"),
+      nxt_string("kval") },
+
+    { nxt_string("var v = $r3.vars; v.unset = 1; v.unset"),
+      nxt_string("1") },
+
+    { nxt_string("$r.vars.unset = 'a'; $r2.vars.unset = 'b';"
+                 "$r.vars.unset + $r2.vars.unset"),
+      nxt_string("ab") },
+
+    { nxt_string("$r.vars.unset = 1; $r2.vars.unset = 2;"
+                 "$r.vars.unset + $r2.vars.unset"),
+      nxt_string("12") },
+
+    { nxt_string("$r3.vars.p = 'a'; $r3.vars.p2 = 'b';"
+                 "$r3.vars.p + $r3.vars.p2"),
+      nxt_string("ab") },
+
+    { nxt_string("$r3.vars.p = 'a'; delete $r3.vars.p; $r3.vars.p"),
+      nxt_string("undefined") },
+
+    { nxt_string("$r3.vars.p = 'a'; delete $r3.vars.p; $r3.vars.p = 'b'; $r3.vars.p"),
+      nxt_string("b") },
+
+    { nxt_string("$r3.vars.error = 1"),
+      nxt_string("Error: cannot set 'error' prop") },
+
+    { nxt_string("delete $r3.vars.error"),
+      nxt_string("Error: cannot delete 'error' prop") },
+
+    { nxt_string("delete $r3.vars.e"),
+      nxt_string("true") },
+
+    { nxt_string("$r3.consts.k"),
+      nxt_string("kval") },
+
+    { nxt_string("$r3.consts.k = 1"),
+      nxt_string("TypeError: Cannot assign to read-only property 'k' of external") },
+
+    { nxt_string("delete $r3.consts.k"),
+      nxt_string("TypeError: Cannot delete property 'k' of external") },
+
+    { nxt_string("delete $r3.vars.p; $r3.vars.p"),
+      nxt_string("undefined") },
+
     { nxt_string("var a = $r.host; a +' '+ a.length +' '+ a"),
       nxt_string("АБВГДЕЁЖЗИЙ 22 АБВГДЕЁЖЗИЙ") },
 
@@ -4026,6 +4134,9 @@ static njs_unit_test_t  njs_test[] =
                  "sr.uri + sr2.uri"),
       nxt_string("ZZZYYY") },
 
+    { nxt_string("var sr = $r.create('XXX'); sr.vars.p = 'a'; sr.vars.p"),
+      nxt_string("a") },
+
     { nxt_string("var p; for (p in $r.some_method);"),
       nxt_string("undefined") },
 
@@ -4039,10 +4150,10 @@ static njs_unit_test_t  njs_test[] =
       nxt_string("true") },
 
     { nxt_string("delete $r.uri"),
-      nxt_string("false") },
+      nxt_string("TypeError: Cannot delete property 'uri' of external") },
 
     { nxt_string("delete $r.one"),
-      nxt_string("false") },
+      nxt_string("TypeError: Cannot delete property 'one' of external") },
 
     { nxt_string("$r.some_method.call($r, 'YES')"),
       nxt_string("АБВ") },
@@ -4063,7 +4174,7 @@ static njs_unit_test_t  njs_test[] =
       nxt_string("undefined") },
 
     { nxt_string("$r.error = 'OK'"),
-      nxt_string("OK") },
+      nxt_string("TypeError: Cannot assign to read-only property 'error' of external") },
 
     { nxt_string("var a = { toString: function() { return 1 } }; a"),
       nxt_string("1") },
@@ -4949,7 +5060,7 @@ static njs_unit_test_t  njs_test[] =
       nxt_string("TypeError: object is not callable") },
 
     { nxt_string("var o = {a:1}; o.a()"),
-      nxt_string("TypeError: object is not callable") },
+      nxt_string("TypeError: 'a' is not a function") },
 
     { nxt_string("(function(){})()"),
       nxt_string("undefined") },
@@ -5945,10 +6056,10 @@ static njs_unit_test_t  njs_test[] =
       nxt_string("SyntaxError: Unexpected token \"null\" in 1") },
 
     { nxt_string("'a'.f()"),
-      nxt_string("InternalError: method 'f' query failed:2") },
+      nxt_string("TypeError: 'f' is not a function") },
 
     { nxt_string("1..f()"),
-      nxt_string("InternalError: method 'f' query failed:-3") },
+      nxt_string("TypeError: 'f' is not a function") },
 
     { nxt_string("try {}"),
       nxt_string("SyntaxError: Missing catch or finally after try in 1") },
@@ -6530,6 +6641,42 @@ static njs_unit_test_t  njs_test[] =
     { nxt_string("new String([1,2,3])"),
       nxt_string("1,2,3") },
 
+    { nxt_string("var s = new String('αβ'); s.one = 1; 'one' in s"),
+      nxt_string("true") },
+
+    { nxt_string("var s = new String('αβ'); 'one' in s"),
+      nxt_string("false") },
+
+    { nxt_string("var s = new String('αβ'); s.one = 1; '1' in s"),
+      nxt_string("true") },
+
+    { nxt_string("var s = new String('αβ'); s.one = 1; 1 in s"),
+      nxt_string("true") },
+
+    { nxt_string("var s = new String('αβ'); s.one = 1; 2 in s"),
+      nxt_string("false") },
+
+    { nxt_string("var s = new String('αβ'); s[1]"),
+      nxt_string("β") },
+
+    { nxt_string("Object.create(new String('αβ'))[1]"),
+      nxt_string("β") },
+
+    { nxt_string("var s = new String('αβ'); s[1] = 'b'"),
+      nxt_string("TypeError: Cannot assign to read-only property '1' of object string") },
+
+    { nxt_string("var s = new String('αβ'); s[4] = 'ab'; s[4]"),
+      nxt_string("ab") },
+
+
+#if 0
+    { nxt_string("Object.create(new String('αβ')).length"),
+      nxt_string("2") },
+#endif
+
+    { nxt_string("var s = new String('αβ'); s.valueOf()[1]"),
+      nxt_string("β") },
+
     { nxt_string("var o = { toString: function() { return 'OK' } };"
                  "String(o)"),
       nxt_string("OK") },
@@ -7007,6 +7154,9 @@ static njs_unit_test_t  njs_test[] =
     { nxt_string("Object.getOwnPropertyDescriptor({}, 'a')"),
       nxt_string("undefined") },
 
+    { nxt_string("Object.getOwnPropertyDescriptor(Object.create({a:1}), 'a')"),
+      nxt_string("undefined") },
+
     { nxt_string("Object.getOwnPropertyDescriptor([3,4], '1').value"),
       nxt_string("4") },
 
@@ -7016,18 +7166,56 @@ static njs_unit_test_t  njs_test[] =
     { nxt_string("Object.getOwnPropertyDescriptor([], 'length').value"),
       nxt_string("0") },
 
-    { nxt_string("JSON.stringify(Object.getOwnPropertyDescriptor([3,4], 'length'))"),
-      nxt_string("{\"value\":2,\"configurable\":false,\"enumerable\":false,\"writable\":true}") },
+    { nxt_string("Object.getOwnPropertyDescriptor([], '0')"),
+      nxt_string("undefined") },
+
+    { nxt_string("Object.getOwnPropertyDescriptor([1,2], '1').value"),
+      nxt_string("2") },
+
+    { nxt_string("Object.getOwnPropertyDescriptor([1,2], new String('1')).value"),
+      nxt_string("2") },
+
+    { nxt_string("Object.getOwnPropertyDescriptor({undefined:1}, void 0).value"),
+      nxt_string("1") },
+
+    { nxt_string("Object.getOwnPropertyDescriptor([1,2], 1).value"),
+      nxt_string("2") },
 
-    { nxt_string("Object.getOwnPropertyDescriptor([3,4], '3')"),
+    { nxt_string("Object.getOwnPropertyDescriptor([1,,,3], '1')"),
       nxt_string("undefined") },
 
-    { nxt_string("Object.getOwnPropertyDescriptor([], '0')"),
+    { nxt_string("Object.getOwnPropertyDescriptor([1,2], '3')"),
       nxt_string("undefined") },
 
+    { nxt_string("JSON.stringify(Object.getOwnPropertyDescriptor([3,4], 'length'))"),
+      nxt_string("{\"value\":2,\"configurable\":false,\"enumerable\":false,\"writable\":true}") },
+
+    { nxt_string("Object.getOwnPropertyDescriptor(Array.of, 'length').value"),
+      nxt_string("0") },
+
+    { nxt_string("Object.getOwnPropertyDescriptor('αβγδ', '1').value"),
+      nxt_string("β") },
+
+    { nxt_string("Object.getOwnPropertyDescriptor(new String('αβγδ'), '1').value"),
+      nxt_string("β") },
+
+    { nxt_string("var s = new String('αβγδ'); s.a = 1;"
+                 "Object.getOwnPropertyDescriptor(s, 'a').value"),
+      nxt_string("1") },
+
+    { nxt_string("JSON.stringify(Object.getOwnPropertyDescriptor('αβγδ', '2'))"),
+      nxt_string("{\"value\":\"γ\",\"configurable\":false,\"enumerable\":true,\"writable\":false}") },
+
+    { nxt_string("JSON.stringify(Object.getOwnPropertyDescriptor(new String('abc'), 'length'))"),
+      nxt_string("{\"value\":3,\"configurable\":false,\"enumerable\":false,\"writable\":false}") },
+
     { nxt_string("Object.getOwnPropertyDescriptor(1, '0')"),
       nxt_string("undefined") },
 
+    { nxt_string("var min = Object.getOwnPropertyDescriptor(Math, 'min').value;"
+                 "[min(1,2), min(2,1), min(-1,1)]"),
+      nxt_string("1,1,-1") },
+
     { nxt_string("Object.getOwnPropertyDescriptor()"),
       nxt_string("TypeError: cannot convert void argument to object") },
 
@@ -9906,24 +10094,121 @@ static njs_unit_test_t  njs_tz_test[] =
 
 
 typedef struct {
-    nxt_str_t             uri;
-    uint32_t              a;
-    nxt_mem_cache_pool_t  *mem_cache_pool;
+    nxt_lvlhsh_t          hash;
     const njs_extern_t    *proto;
+    nxt_mem_cache_pool_t  *mem_cache_pool;
+
+    uint32_t              a;
+    nxt_str_t             uri;
 
     njs_opaque_value_t    value;
 } njs_unit_test_req_t;
 
 
+typedef struct {
+    njs_value_t           name;
+    njs_value_t           value;
+} njs_unit_test_prop_t;
+
+
+static nxt_int_t
+lvlhsh_unit_test_key_test(nxt_lvlhsh_query_t *lhq, void *data)
+{
+    nxt_str_t             name;
+    njs_unit_test_prop_t  *prop;
+
+    prop = data;
+    njs_string_get(&prop->name, &name);
+
+    if (name.length != lhq->key.length) {
+        return NXT_DECLINED;
+    }
+
+    if (memcmp(name.start, lhq->key.start, lhq->key.length) == 0) {
+        return NXT_OK;
+    }
+
+    return NXT_DECLINED;
+}
+
+
+static void *
+lvlhsh_unit_test_pool_alloc(void *pool, size_t size, nxt_uint_t nalloc)
+{
+    return nxt_mem_cache_align(pool, size, size);
+}
+
+
+static void
+lvlhsh_unit_test_pool_free(void *pool, void *p, size_t size)
+{
+    nxt_mem_cache_free(pool, p);
+}
+
+
+static const nxt_lvlhsh_proto_t  lvlhsh_proto  nxt_aligned(64) = {
+    NXT_LVLHSH_LARGE_SLAB,
+    0,
+    lvlhsh_unit_test_key_test,
+    lvlhsh_unit_test_pool_alloc,
+    lvlhsh_unit_test_pool_free,
+};
+
+
+static njs_unit_test_prop_t *
+lvlhsh_unit_test_alloc(nxt_mem_cache_pool_t *pool, const njs_value_t *name,
+    const njs_value_t *value)
+{
+    njs_unit_test_prop_t *prop;
+
+    prop = nxt_mem_cache_alloc(pool, sizeof(njs_unit_test_prop_t));
+    if (prop == NULL) {
+        return NULL;
+    }
+
+    prop->name = *name;
+    prop->value = *value;
+
+    return prop;
+}
+
+
+static nxt_int_t
+lvlhsh_unit_test_add(njs_unit_test_req_t *r, njs_unit_test_prop_t *prop)
+{
+    nxt_lvlhsh_query_t  lhq;
+
+    njs_string_get(&prop->name, &lhq.key);
+    lhq.key_hash = nxt_djb_hash(lhq.key.start, lhq.key.length);
+
+    lhq.replace = 1;
+    lhq.value = (void *) prop;
+    lhq.proto = &lvlhsh_proto;
+    lhq.pool = r->mem_cache_pool;
+
+    switch (nxt_lvlhsh_insert(&r->hash, &lhq)) {
+
+    case NXT_OK:
+        return NXT_OK;
+
+    case NXT_DECLINED:
+    default:
+        return NXT_ERROR;
+    }
+}
+
+
 static njs_ret_t
 njs_unit_test_r_get_uri_external(njs_vm_t *vm, njs_value_t *value, void *obj,
     uintptr_t data)
 {
-    njs_unit_test_req_t  *r;
+    char *p = obj;
 
-    r = (njs_unit_test_req_t *) obj;
+    nxt_str_t  *field;
+
+    field = (nxt_str_t *) (p + data);
 
-    return njs_string_create(vm, value, r->uri.start, r->uri.length, 0);
+    return njs_string_create(vm, value, field->start, field->length, 0);
 }
 
 
@@ -9931,11 +10216,13 @@ static njs_ret_t
 njs_unit_test_r_set_uri_external(njs_vm_t *vm, void *obj, uintptr_t data,
     nxt_str_t *value)
 {
-    njs_unit_test_req_t  *r;
+    char *p = obj;
 
-    r = (njs_unit_test_req_t *) obj;
+    nxt_str_t  *field;
 
-    r->uri = *value;
+    field = (nxt_str_t *) (p + data);
+
+    *field = *value;
 
     return NXT_OK;
 }
@@ -9975,6 +10262,109 @@ njs_unit_test_host_external(njs_vm_t *vm, njs_value_t *value, void *obj,
 }
 
 
+static njs_ret_t
+njs_unit_test_r_get_vars(njs_vm_t *vm, njs_value_t *value, void *obj,
+    uintptr_t data)
+{
+    nxt_int_t             ret;
+    nxt_str_t             *key;
+    nxt_lvlhsh_query_t    lhq;
+    njs_unit_test_req_t   *r;
+    njs_unit_test_prop_t  *prop;
+
+    r = (njs_unit_test_req_t *) obj;
+    key = (nxt_str_t *) data;
+
+    lhq.key = *key;
+    lhq.key_hash = nxt_djb_hash(key->start, key->length);
+    lhq.proto = &lvlhsh_proto;
+
+    ret = nxt_lvlhsh_find(&r->hash, &lhq);
+
+    prop = lhq.value;
+
+    if (ret == NXT_OK && njs_is_valid(&prop->value)) {
+        *value = prop->value;
+        return NXT_OK;
+    }
+
+    njs_value_void_set(value);
+
+    return NXT_OK;
+}
+
+
+static njs_ret_t
+njs_unit_test_r_set_vars(njs_vm_t *vm, void *obj, uintptr_t data,
+    nxt_str_t *value)
+{
+    nxt_int_t             ret;
+    nxt_str_t             *key;
+    njs_value_t           name, val;
+    njs_unit_test_req_t   *r;
+    njs_unit_test_prop_t  *prop;
+
+    r = (njs_unit_test_req_t *) obj;
+    key = (nxt_str_t *) data;
+
+    if (key->length == 5 && memcmp(key->start, "error", 5) == 0) {
+        njs_vm_error(vm, "cannot set 'error' prop");
+        return NXT_ERROR;
+    }
+
+    njs_string_create(vm, &name, key->start, key->length, 0);
+    njs_string_create(vm, &val, value->start, value->length, 0);
+
+    prop = lvlhsh_unit_test_alloc(vm->mem_cache_pool, &name, &val);
+    if (prop == NULL) {
+        njs_memory_error(vm);
+        return NXT_ERROR;
+    }
+
+    ret = lvlhsh_unit_test_add(r, prop);
+    if (ret != NXT_OK) {
+        njs_vm_error(vm, "lvlhsh_unit_test_add() failed");
+        return NXT_ERROR;
+    }
+
+    return NXT_OK;
+}
+
+
+static njs_ret_t
+njs_unit_test_r_del_vars(njs_vm_t *vm, void *obj, uintptr_t data,
+    nxt_bool_t delete)
+{
+    nxt_int_t             ret;
+    nxt_str_t             *key;
+    nxt_lvlhsh_query_t    lhq;
+    njs_unit_test_req_t   *r;
+    njs_unit_test_prop_t  *prop;
+
+    r = (njs_unit_test_req_t *) obj;
+    key = (nxt_str_t *) data;
+
+    if (key->length == 5 && memcmp(key->start, "error", 5) == 0) {
+        njs_vm_error(vm, "cannot delete 'error' prop");
+        return NXT_ERROR;
+    }
+
+    lhq.key = *key;
+    lhq.key_hash = nxt_djb_hash(key->start, key->length);
+    lhq.proto = &lvlhsh_proto;
+
+    ret = nxt_lvlhsh_find(&r->hash, &lhq);
+
+    prop = lhq.value;
+
+    if (ret == NXT_OK) {
+        njs_set_invalid(&prop->value);
+    }
+
+    return NXT_OK;
+}
+
+
 static njs_ret_t
 njs_unit_test_header_external(njs_vm_t *vm, njs_value_t *value, void *obj,
     uintptr_t data)
@@ -10146,7 +10536,7 @@ static njs_external_t  njs_unit_test_r_external[] = {
       NULL,
       NULL,
       NULL,
-      0 },
+      offsetof(njs_unit_test_req_t, uri) },
 
     { nxt_string("host"),
       NJS_EXTERN_PROPERTY,
@@ -10172,6 +10562,30 @@ static njs_external_t  njs_unit_test_r_external[] = {
       NULL,
       0 },
 
+    { nxt_string("vars"),
+      NJS_EXTERN_OBJECT,
+      NULL,
+      0,
+      njs_unit_test_r_get_vars,
+      njs_unit_test_r_set_vars,
+      njs_unit_test_r_del_vars,
+      NULL,
+      NULL,
+      NULL,
+      0 },
+
+    { nxt_string("consts"),
+      NJS_EXTERN_OBJECT,
+      NULL,
+      0,
+      njs_unit_test_r_get_vars,
+      NULL,
+      NULL,
+      NULL,
+      NULL,
+      NULL,
+      0 },
+
     { nxt_string("header"),
       NJS_EXTERN_OBJECT,
       NULL,
@@ -10231,23 +10645,55 @@ static njs_external_t  nxt_test_external[] = {
 typedef struct {
     nxt_str_t            name;
     njs_unit_test_req_t  request;
+    njs_unit_test_prop_t props[2];
 } njs_unit_test_req_t_init_t;
 
 
 static const njs_unit_test_req_t_init_t nxt_test_requests[] = {
-    { nxt_string("$r"),  {.uri = nxt_string("АБВ"), .a = 1}},
-    { nxt_string("$r2"), {.uri = nxt_string("αβγ"), .a = 2}},
-    { nxt_string("$r3"), {.uri = nxt_string("abc"), .a = 3}},
+
+    { nxt_string("$r"),
+     {
+         .uri = nxt_string("АБВ"),
+         .a = 1
+     },
+     {
+         { njs_string("p"), njs_string("pval") },
+         { njs_string("p2"), njs_string("p2val") },
+     }
+    },
+
+    { nxt_string("$r2"),
+     {
+         .uri = nxt_string("αβγ"),
+         .a = 2
+     },
+     {
+         { njs_string("q"), njs_string("qval") },
+         { njs_string("q2"), njs_string("q2val") },
+     }
+    },
+
+    { nxt_string("$r3"),
+     {
+         .uri = nxt_string("abc"),
+         .a = 3
+     },
+     {
+         { njs_string("k"), njs_string("kval") },
+         { njs_string("k2"), njs_string("k2val") },
+     }
+    },
 };
 
 
 static nxt_int_t
 njs_externals_init(njs_vm_t *vm)
 {
-    nxt_int_t            ret;
-    nxt_uint_t           i;
-    const njs_extern_t   *proto;
-    njs_unit_test_req_t  *requests;
+    nxt_int_t             ret;
+    nxt_uint_t            i, j;
+    const njs_extern_t    *proto;
+    njs_unit_test_req_t   *requests;
+    njs_unit_test_prop_t  *prop;
 
     proto = njs_vm_external_prototype(vm, &nxt_test_external[0]);
     if (proto == NULL) {
@@ -10281,6 +10727,23 @@ njs_externals_init(njs_vm_t *vm)
             printf("njs_vm_external_bind() failed\n");
             return NXT_ERROR;
         }
+
+        for (j = 0; j < nxt_nitems(nxt_test_requests[i].props); j++) {
+            prop = lvlhsh_unit_test_alloc(vm->mem_cache_pool,
+                                          &nxt_test_requests[i].props[j].name,
+                                          &nxt_test_requests[i].props[j].value);
+
+            if (prop == NULL) {
+                printf("lvlhsh_unit_test_alloc() failed\n");
+                return NXT_ERROR;
+            }
+
+            ret = lvlhsh_unit_test_add(&requests[i], prop);
+            if (ret != NXT_OK) {
+                printf("lvlhsh_unit_test_add() failed\n");
+                return NXT_ERROR;
+            }
+        }
     }
 
     return NXT_OK;