Created
September 20, 2018 14:06
-
-
Save xeioex/f691a64d48ddb7db0fda16ceceab5670 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# HG changeset patch | |
# User Dmitry Volyntsev <xeioex@nginx.com> | |
# Date 1537376128 -10800 | |
# Wed Sep 19 19:55:28 2018 +0300 | |
# Node ID 4a160b7b58bd2badc597c92c261ceae74b76ecff | |
# Parent 68a3580688ab3a5dcd1d02a52f13c2e6532a58ff | |
Fixed http response and parent getters. | |
Getters are expected to set resulting value to the provided | |
argument, not to vm->retval. | |
diff --git a/nginx/ngx_http_js_module.c b/nginx/ngx_http_js_module.c | |
--- a/nginx/ngx_http_js_module.c | |
+++ b/nginx/ngx_http_js_module.c | |
@@ -1844,7 +1844,7 @@ ngx_http_js_ext_get_response(njs_vm_t *v | |
ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); | |
- njs_vm_retval_set(vm, njs_value_arg(&ctx->args[1])); | |
+ njs_value_assign(value, njs_value_arg(&ctx->args[1])); | |
return NJS_OK; | |
} | |
@@ -2189,7 +2189,7 @@ ngx_http_js_ext_get_parent(njs_vm_t *vm, | |
return NJS_ERROR; | |
} | |
- njs_vm_retval_set(vm, njs_value_arg(&ctx->args[0])); | |
+ njs_value_assign(value, njs_value_arg(&ctx->args[0])); | |
return NJS_OK; | |
} | |
# HG changeset patch | |
# User Dmitry Volyntsev <xeioex@nginx.com> | |
# Date 1537449234 -10800 | |
# Thu Sep 20 16:13:54 2018 +0300 | |
# Node ID d80d5490da4d3bed45001b90e8d73bf7085ae4e8 | |
# Parent 4a160b7b58bd2badc597c92c261ceae74b76ecff | |
Object property quering is refactored. | |
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. | |
diff --git a/njs/njs_extern.h b/njs/njs_extern.h | |
--- a/njs/njs_extern.h | |
+++ b/njs/njs_extern.h | |
@@ -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. */ | |
diff --git a/njs/njs_object.c b/njs/njs_object.c | |
--- a/njs/njs_object.c | |
+++ b/njs/njs_object.c | |
@@ -13,6 +13,14 @@ static njs_ret_t njs_object_property_que | |
njs_property_query_t *pq, njs_value_t *value, njs_object_t *object); | |
static njs_ret_t njs_array_property_query(njs_vm_t *vm, | |
njs_property_query_t *pq, njs_value_t *object, 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 +240,29 @@ njs_object_property(njs_vm_t *vm, const | |
/* | |
+ * 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, | |
* NJS_TRAP the property trap must be called, | |
* NXT_ERROR exception has been thrown. | |
*/ | |
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; | |
hash = nxt_djb_hash; | |
@@ -261,30 +270,23 @@ njs_property_query(njs_vm_t *vm, njs_pro | |
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; | |
- } | |
- | |
- 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))) { | |
if (nxt_fast_path(njs_is_primitive(property))) { | |
index = njs_value_to_index(property); | |
- if (nxt_fast_path(index < NJS_ARRAY_MAX_LENGTH)) { | |
- return njs_array_property_query(vm, pq, object, index); | |
+ if (nxt_fast_path(index < NJS_STRING_MAX_LENGTH)) { | |
+ if (object->type == NJS_OBJECT_STRING) { | |
+ object = &object->data.u.object_value->value; | |
+ } | |
+ | |
+ return njs_string_property_query(vm, pq, object, index); | |
} | |
} else { | |
@@ -292,12 +294,35 @@ njs_property_query(njs_vm_t *vm, njs_pro | |
} | |
} | |
+ if (object->type == NJS_STRING) { | |
+ obj = &vm->prototypes[NJS_PROTOTYPE_STRING].object; | |
+ | |
+ } else { | |
+ obj = object->data.u.object; | |
+ } | |
+ | |
+ break; | |
+ | |
+ case NJS_ARRAY: | |
+ if (nxt_fast_path(!njs_is_null_or_void_or_boolean(property))) { | |
+ | |
+ if (nxt_fast_path(njs_is_primitive(property))) { | |
+ index = njs_value_to_index(property); | |
+ | |
+ if (nxt_fast_path(index < NJS_ARRAY_MAX_LENGTH)) { | |
+ return njs_array_property_query(vm, pq, object, index); | |
+ } | |
+ | |
+ } else { | |
+ return njs_trap(vm, NJS_TRAP_PROPERTY); | |
+ } | |
+ } | |
+ | |
/* Fall through. */ | |
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,12 +347,6 @@ njs_property_query(njs_vm_t *vm, njs_pro | |
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; | |
@@ -361,9 +380,7 @@ njs_property_query(njs_vm_t *vm, njs_pro | |
pq->lhq.key_hash = hash(pq->lhq.key.start, pq->lhq.key.length); | |
if (obj == NULL) { | |
- pq->lhq.proto = &njs_extern_hash_proto; | |
- | |
- return NJS_EXTERNAL_VALUE; | |
+ return njs_external_property_query(vm, pq, object); | |
} | |
return njs_object_property_query(vm, pq, object, obj); | |
@@ -381,6 +398,7 @@ njs_object_property_query(njs_vm_t *vm, | |
njs_value_t *value, njs_object_t *object) | |
{ | |
njs_ret_t ret; | |
+ njs_object_t *proto; | |
njs_object_prop_t *prop; | |
pq->lhq.proto = &njs_object_hash_proto; | |
@@ -392,70 +410,41 @@ njs_object_property_query(njs_vm_t *vm, | |
} | |
} | |
+ proto = object; | |
+ | |
do { | |
- pq->prototype = object; | |
- | |
- ret = nxt_lvlhsh_find(&object->hash, &pq->lhq); | |
+ pq->prototype = proto; | |
+ | |
+ /* length and other shared properties should be Own property */ | |
+ | |
+ if (nxt_fast_path(!pq->own || proto == object)) { | |
+ ret = nxt_lvlhsh_find(&proto->hash, &pq->lhq); | |
+ | |
+ if (ret == NXT_OK) { | |
+ prop = pq->lhq.value; | |
+ pq->shared = 0; | |
+ | |
+ return (prop->type != NJS_WHITEOUT) ? ret : NXT_DECLINED; | |
+ } | |
+ } | |
+ | |
+ if (pq->query > NJS_PROPERTY_QUERY_GET) { | |
+ return NXT_DECLINED; | |
+ } | |
+ | |
+ ret = nxt_lvlhsh_find(&proto->shared_hash, &pq->lhq); | |
if (ret == NXT_OK) { | |
- prop = pq->lhq.value; | |
- | |
- if (prop->type != NJS_WHITEOUT) { | |
- pq->shared = 0; | |
- | |
- return ret; | |
- } | |
- | |
- goto next; | |
- } | |
- | |
- if (pq->query > NJS_PROPERTY_QUERY_IN) { | |
- /* NXT_DECLINED */ | |
+ pq->shared = 1; | |
+ | |
return ret; | |
} | |
- ret = nxt_lvlhsh_find(&object->shared_hash, &pq->lhq); | |
- | |
- if (ret == NXT_OK) { | |
- pq->shared = 1; | |
- | |
- if (pq->query == NJS_PROPERTY_QUERY_GET) { | |
- prop = pq->lhq.value; | |
- | |
- if (prop->type == NJS_PROPERTY_HANDLER) { | |
- pq->scratch = *prop; | |
- prop = &pq->scratch; | |
- ret = prop->value.data.u.prop_handler(vm, value, NULL, | |
- &prop->value); | |
- | |
- if (nxt_fast_path(ret == NXT_OK)) { | |
- prop->type = NJS_PROPERTY; | |
- pq->lhq.value = prop; | |
- } | |
- } | |
- } | |
- | |
- return ret; | |
- } | |
- | |
- if (pq->query > NJS_PROPERTY_QUERY_IN) { | |
- /* NXT_DECLINED */ | |
- return ret; | |
- } | |
- | |
- next: | |
- | |
- object = object->__proto__; | |
- | |
- } while (object != NULL); | |
- | |
- if (njs_is_string(value)) { | |
- return NJS_STRING_VALUE; | |
- } | |
- | |
- /* NXT_DECLINED */ | |
- | |
- return ret; | |
+ proto = proto->__proto__; | |
+ | |
+ } while (proto != NULL); | |
+ | |
+ return NXT_DECLINED; | |
} | |
@@ -463,10 +452,11 @@ static njs_ret_t | |
njs_array_property_query(njs_vm_t *vm, njs_property_query_t *pq, | |
njs_value_t *object, uint32_t index) | |
{ | |
- uint32_t size; | |
- njs_ret_t ret; | |
- njs_value_t *value; | |
- njs_array_t *array; | |
+ uint32_t size; | |
+ njs_ret_t ret; | |
+ njs_value_t *value; | |
+ njs_array_t *array; | |
+ njs_object_prop_t *prop; | |
array = object->data.u.array; | |
@@ -493,9 +483,204 @@ njs_array_property_query(njs_vm_t *vm, n | |
array->length = index + 1; | |
} | |
- pq->lhq.value = &array->start[index]; | |
- | |
- return NJS_ARRAY_VALUE; | |
+ 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; | |
+ } | |
+ | |
+ 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; | |
+ | |
+ 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; | |
+ } | |
+ | |
+ obj = njs_extern_index(vm, pq->ext_index); | |
+ | |
+ ret = pq->ext_proto->set(vm, obj, pq->ext_data, &s); | |
+ if (nxt_slow_path(ret != NXT_OK)) { | |
+ return ret; | |
+ } | |
+ | |
+ *retval = *setval; | |
+ | |
+ return NXT_OK; | |
+} | |
+ | |
+ | |
+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_ret_t ret; | |
+ njs_property_query_t *pq; | |
+ | |
+ pq = (njs_property_query_t *) vm->stash; | |
+ | |
+ obj = njs_extern_index(vm, pq->ext_index); | |
+ | |
+ ret = pq->ext_proto->find(vm, obj, pq->ext_data, 1); | |
+ if (nxt_slow_path(ret != NXT_OK)) { | |
+ return ret; | |
+ } | |
+ | |
+ return NXT_OK; | |
} | |
@@ -527,6 +712,34 @@ njs_object_query_prop_handler(njs_proper | |
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 +1201,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; | |
- } | |
- | |
+ 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; | |
+ } | |
+ | |
+ property = njs_arg(args, nargs, 2); | |
+ | |
+ njs_property_query_init(&pq, NJS_PROPERTY_QUERY_GET, 1); | |
+ | |
+ ret = njs_property_query(vm, &pq, (njs_value_t *) value, property); | |
+ | |
+ switch (ret) { | |
+ case NXT_OK: | |
+ break; | |
+ | |
+ case NXT_DECLINED: | |
vm->retval = njs_value_void; | |
return NXT_OK; | |
+ | |
+ case NJS_TRAP: | |
+ case NXT_ERROR: | |
+ default: | |
+ return ret; | |
} | |
- 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; | |
- | |
- if ((double) index == num | |
- && index < array->length | |
- && njs_is_valid(&array->start[index])) | |
- { | |
- prop = &array_prop; | |
- | |
- array_prop.name = *property; | |
- array_prop.value = array->start[index]; | |
- | |
- array_prop.configurable = 1; | |
- array_prop.enumerable = 1; | |
- array_prop.writable = 1; | |
+ 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; | |
} | |
- } | |
- | |
- lhq.proto = &njs_object_hash_proto; | |
- | |
- if (prop == NULL) { | |
- pq.query = NJS_PROPERTY_QUERY_GET; | |
- pq.lhq.key.length = 0; | |
- pq.lhq.key.start = NULL; | |
- | |
- ret = njs_property_query(vm, &pq, (njs_value_t *) value, | |
- (njs_value_t *) property); | |
- | |
- if (ret != NXT_OK) { | |
- vm->retval = njs_value_void; | |
- return NXT_OK; | |
+ | |
+ break; | |
+ | |
+ 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; | |
} | |
- 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 +1277,7 @@ njs_object_get_own_property_descriptor(n | |
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 +2174,28 @@ const njs_object_init_t njs_object_prot | |
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"; | |
+ } | |
+} | |
diff --git a/njs/njs_object.h b/njs/njs_object.h | |
--- a/njs/njs_object.h | |
+++ b/njs/njs_object.h | |
@@ -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,26 @@ typedef struct { | |
/* scratch is used to get the value of an NJS_PROPERTY_HANDLER property. */ | |
njs_object_prop_t scratch; | |
+ /* scratch data 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)->query = _query; \ | |
+ (pq)->own = _own; \ | |
+ } while (0) | |
+ | |
struct njs_object_init_s { | |
nxt_str_t name; | |
@@ -67,10 +79,12 @@ njs_object_t *njs_object_value_copy(njs_ | |
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 +104,9 @@ njs_value_t *njs_property_constructor_cr | |
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; | |
diff --git a/njs/njs_vm.c b/njs/njs_vm.c | |
--- a/njs/njs_vm.c | |
+++ b/njs/njs_vm.c | |
@@ -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,147 +474,13 @@ 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; | |
- } | |
- | |
- return sizeof(njs_vmcode_prop_get_t); | |
- | |
- case NJS_TRAP: | |
- case NXT_ERROR: | |
- default: | |
- | |
+ njs_ret_t ret; | |
+ | |
+ ret = njs_value_property(vm, object, property, &vm->retval); | |
+ if (ret == NXT_ERROR || ret == NJS_TRAP) { | |
return ret; | |
} | |
- vm->retval = *retval; | |
- | |
/* GC: njs_retain(retval) */ | |
return sizeof(njs_vmcode_prop_get_t); | |
@@ -627,13 +491,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 +506,7 @@ njs_vmcode_property_set(njs_vm_t *vm, nj | |
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 +515,33 @@ njs_vmcode_property_set(njs_vm_t *vm, nj | |
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); | |
} | |
- 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; | |
@@ -693,47 +571,6 @@ njs_vmcode_property_set(njs_vm_t *vm, nj | |
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 +594,34 @@ njs_vmcode_property_set(njs_vm_t *vm, nj | |
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: | |
+ prop = pq.lhq.value; | |
+ | |
+ if (!njs_is_valid(&prop->value)) { | |
+ break; | |
+ } | |
+ | |
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; | |
- | |
- if (njs_is_valid(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) { | |
- 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; | |
- } | |
- | |
- if (ret == NXT_OK) { | |
- retval = &njs_value_true; | |
- } | |
- } | |
+ if (!njs_is_object(object) && !njs_is_external(object)) { | |
+ njs_type_error(vm, "property in on a primitive value"); | |
+ | |
+ return NXT_ERROR; | |
} | |
break; | |
@@ -840,19 +643,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 +659,36 @@ njs_vmcode_property_delete(njs_vm_t *vm, | |
case NXT_OK: | |
prop = pq.lhq.value; | |
+ switch (prop->type) { | |
+ case NJS_PROPERTY: | |
+ 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 +707,6 @@ njs_vmcode_property_delete(njs_vm_t *vm, | |
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 +716,14 @@ njs_vmcode_property_delete(njs_vm_t *vm, | |
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 +849,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, val; | |
+ njs_object_t *prototype, *proto; | |
+ const njs_value_t *retval; | |
static njs_value_t prototype_string = njs_string("prototype"); | |
@@ -1114,13 +864,10 @@ njs_vmcode_instance_of(njs_vm_t *vm, njs | |
retval = &njs_value_false; | |
if (njs_is_object(object)) { | |
- pq.query = NJS_PROPERTY_QUERY_GET; | |
- | |
- ret = njs_property_query(vm, &pq, constructor, &prototype_string); | |
+ ret = njs_value_property(vm, constructor, &prototype_string, &val); | |
if (nxt_fast_path(ret == NXT_OK)) { | |
- prop = pq.lhq.value; | |
- value = &prop->value; | |
+ value = &val; | |
/* TODO: test prop->value is object. */ | |
@@ -2115,17 +1862,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 +1880,54 @@ njs_vmcode_method_frame(njs_vm_t *vm, nj | |
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; | |
- | |
- ret = njs_function_frame_create(vm, value, object, method->nargs, | |
- method->code.ctor); | |
- break; | |
- | |
- case NJS_EXTERNAL_VALUE: | |
- ext_proto = object->external.proto; | |
- | |
- ret = nxt_lvlhsh_find(&ext_proto->hash, &pq.lhq); | |
- | |
- 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; | |
- | |
- 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); | |
+ switch (prop->type) { | |
+ case NJS_PROPERTY: | |
+ case NJS_METHOD: | |
+ 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; | |
+ } | |
+ | |
+ break; | |
+ | |
+ default: | |
+ njs_internal_error(vm, "unexpected property type '%s' " | |
+ "while getting method", | |
+ njs_prop_type_string(prop->type)); | |
+ | |
return NXT_ERROR; | |
} | |
- ret = njs_function_native_frame(vm, ext_proto->function, object, NULL, | |
- method->nargs, 0, method->code.ctor); | |
+ value = &prop->value; | |
+ | |
break; | |
+ 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 +2906,45 @@ njs_vmcode_string_argument(njs_vm_t *vm, | |
} | |
+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 +3043,87 @@ njs_primitive_value(njs_vm_t *vm, njs_va | |
} | |
-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; | |
- | |
- 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; | |
+ njs_object_prop_t *prop; | |
+ njs_property_query_t pq; | |
+ | |
+ *retval = njs_value_void; | |
+ | |
+ njs_property_query_init(&pq, NJS_PROPERTY_QUERY_GET, 0); | |
+ | |
+ ret = njs_property_query(vm, &pq, value, property); | |
+ | |
+ 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; | |
+ | |
+ case NJS_PROPERTY_HANDLER: | |
+ pq.scratch = *prop; | |
+ prop = &pq.scratch; | |
+ ret = prop->value.data.u.prop_handler(vm, value, NULL, | |
+ &prop->value); | |
+ | |
+ if (nxt_fast_path(ret == NXT_OK)) { | |
+ *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: | |
+ break; | |
+ | |
+ case NJS_TRAP: | |
+ case NXT_ERROR: | |
+ default: | |
+ | |
+ return ret; | |
} | |
- 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; | |
+ return (ret == NXT_OK) ? NXT_OK : NXT_DECLINED; | |
} | |
diff --git a/njs/njs_vm.h b/njs/njs_vm.h | |
--- a/njs/njs_vm.h | |
+++ b/njs/njs_vm.h | |
@@ -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 * */ | |
}; | |
diff --git a/njs/test/njs_expect_test.exp b/njs/test/njs_expect_test.exp | |
--- a/njs/test/njs_expect_test.exp | |
+++ b/njs/test/njs_expect_test.exp | |
@@ -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 { | |
diff --git a/njs/test/njs_unit_test.c b/njs/test/njs_unit_test.c | |
--- a/njs/test/njs_unit_test.c | |
+++ b/njs/test/njs_unit_test.c | |
@@ -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> | |
@@ -2619,9 +2621,18 @@ 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 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") }, | |
@@ -3998,6 +4009,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 +4093,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 +4109,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 +4133,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 +5019,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 +6015,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 +6600,27 @@ 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("var s = new String('αβ'); s.valueOf()[1]"), | |
+ nxt_string("β") }, | |
+ | |
{ nxt_string("var o = { toString: function() { return 'OK' } };" | |
"String(o)"), | |
nxt_string("OK") }, | |
@@ -7007,6 +7098,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 +7110,56 @@ static njs_unit_test_t njs_test[] = | |
{ nxt_string("Object.getOwnPropertyDescriptor([], 'length').value"), | |
nxt_string("0") }, | |
+ { 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([1,,,3], '1')"), | |
+ nxt_string("undefined") }, | |
+ | |
+ { 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([3,4], '3')"), | |
- nxt_string("undefined") }, | |
- | |
- { nxt_string("Object.getOwnPropertyDescriptor([], '0')"), | |
- nxt_string("undefined") }, | |
+ { 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") }, | |
@@ -9820,24 +9952,121 @@ static njs_unit_test_t njs_test[] = | |
typedef struct { | |
+ nxt_lvlhsh_t hash; | |
+ const njs_extern_t *proto; | |
+ nxt_mem_cache_pool_t *mem_cache_pool; | |
+ | |
+ uint32_t a; | |
nxt_str_t uri; | |
- uint32_t a; | |
- nxt_mem_cache_pool_t *mem_cache_pool; | |
- const njs_extern_t *proto; | |
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; | |
- | |
- r = (njs_unit_test_req_t *) obj; | |
- | |
- return njs_string_create(vm, value, r->uri.start, r->uri.length, 0); | |
+ char *p = obj; | |
+ | |
+ nxt_str_t *field; | |
+ | |
+ field = (nxt_str_t *) (p + data); | |
+ | |
+ return njs_string_create(vm, value, field->start, field->length, 0); | |
} | |
@@ -9845,11 +10074,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; | |
- | |
- r = (njs_unit_test_req_t *) obj; | |
- | |
- r->uri = *value; | |
+ char *p = obj; | |
+ | |
+ nxt_str_t *field; | |
+ | |
+ field = (nxt_str_t *) (p + data); | |
+ | |
+ *field = *value; | |
return NXT_OK; | |
} | |
@@ -9890,6 +10121,109 @@ njs_unit_test_host_external(njs_vm_t *vm | |
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) | |
{ | |
@@ -10060,7 +10394,7 @@ static njs_external_t njs_unit_test_r_e | |
NULL, | |
NULL, | |
NULL, | |
- 0 }, | |
+ offsetof(njs_unit_test_req_t, uri) }, | |
{ nxt_string("host"), | |
NJS_EXTERN_PROPERTY, | |
@@ -10086,6 +10420,30 @@ static njs_external_t njs_unit_test_r_e | |
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, | |
@@ -10145,23 +10503,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) { | |
@@ -10195,6 +10585,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; | |
# HG changeset patch | |
# User Dmitry Volyntsev <xeioex@nginx.com> | |
# Date 1537449688 -10800 | |
# Thu Sep 20 16:21:28 2018 +0300 | |
# Node ID b85a69fb2ee5419594cd84e47bdb5ac505444e30 | |
# Parent d80d5490da4d3bed45001b90e8d73bf7085ae4e8 | |
Fixed Object.prototype.hasOwnProperty() for non-object properties. | |
This fixes #9 issue on Github. | |
diff --git a/njs/njs_object.c b/njs/njs_object.c | |
--- a/njs/njs_object.c | |
+++ b/njs/njs_object.c | |
@@ -2053,45 +2053,38 @@ static njs_ret_t | |
njs_object_prototype_has_own_property(njs_vm_t *vm, njs_value_t *args, | |
nxt_uint_t nargs, njs_index_t unused) | |
{ | |
- uint32_t index; | |
- nxt_int_t ret; | |
- njs_array_t *array; | |
- const njs_value_t *value, *prop, *retval; | |
- nxt_lvlhsh_query_t lhq; | |
- | |
- retval = &njs_value_false; | |
- value = &args[0]; | |
- | |
- if (njs_is_object(value)) { | |
- | |
- prop = njs_arg(args, nargs, 1); | |
- | |
- if (njs_is_array(value)) { | |
- array = value->data.u.array; | |
- index = njs_string_to_index(prop); | |
- | |
- if (index < array->length && njs_is_valid(&array->start[index])) { | |
- retval = &njs_value_true; | |
- goto done; | |
- } | |
- } | |
- | |
- njs_string_get(prop, &lhq.key); | |
- lhq.key_hash = nxt_djb_hash(lhq.key.start, lhq.key.length); | |
- lhq.proto = &njs_object_hash_proto; | |
- | |
- ret = nxt_lvlhsh_find(&value->data.u.object->hash, &lhq); | |
- | |
- if (ret == NXT_OK) { | |
- retval = &njs_value_true; | |
- } | |
+ nxt_int_t ret; | |
+ const njs_value_t *value, *property; | |
+ njs_property_query_t pq; | |
+ | |
+ value = njs_arg(args, nargs, 0); | |
+ | |
+ 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; | |
} | |
-done: | |
- | |
- vm->retval = *retval; | |
- | |
- return NXT_OK; | |
+ property = njs_arg(args, nargs, 1); | |
+ | |
+ njs_property_query_init(&pq, NJS_PROPERTY_QUERY_GET, 1); | |
+ | |
+ ret = njs_property_query(vm, &pq, (njs_value_t *) value, property); | |
+ | |
+ switch (ret) { | |
+ case NXT_OK: | |
+ vm->retval = njs_value_true; | |
+ return NXT_OK; | |
+ | |
+ case NXT_DECLINED: | |
+ vm->retval = njs_value_false; | |
+ return NXT_OK; | |
+ | |
+ case NJS_TRAP: | |
+ case NXT_ERROR: | |
+ default: | |
+ return ret; | |
+ } | |
} | |
diff --git a/njs/test/njs_unit_test.c b/njs/test/njs_unit_test.c | |
--- a/njs/test/njs_unit_test.c | |
+++ b/njs/test/njs_unit_test.c | |
@@ -3611,6 +3611,15 @@ static njs_unit_test_t njs_test[] = | |
{ nxt_string("'abc'.length"), | |
nxt_string("3") }, | |
+ { nxt_string("''.hasOwnProperty('length')"), | |
+ nxt_string("true") }, | |
+ | |
+ { nxt_string("'abc'.hasOwnProperty('length')"), | |
+ nxt_string("true") }, | |
+ | |
+ { nxt_string("(new String('abc')).hasOwnProperty('length')"), | |
+ nxt_string("true") }, | |
+ | |
{ nxt_string("'abc'.toUTF8().length"), | |
nxt_string("3") }, | |
@@ -7025,6 +7034,21 @@ static njs_unit_test_t njs_test[] = | |
{ nxt_string("[,].hasOwnProperty()"), | |
nxt_string("false") }, | |
+ { nxt_string("[1,2].hasOwnProperty('len')"), | |
+ nxt_string("false") }, | |
+ | |
+ { nxt_string("[].hasOwnProperty('length')"), | |
+ nxt_string("true") }, | |
+ | |
+ { nxt_string("[1,2].hasOwnProperty('length')"), | |
+ nxt_string("true") }, | |
+ | |
+ { nxt_string("(new Array()).hasOwnProperty('length')"), | |
+ nxt_string("true") }, | |
+ | |
+ { nxt_string("(new Array(10)).hasOwnProperty('length')"), | |
+ nxt_string("true") }, | |
+ | |
{ nxt_string("Object.valueOf.hasOwnProperty()"), | |
nxt_string("false") }, | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment