From: Dmitry Volyntsev Date: Fri, 19 Oct 2018 17:52:57 +0000 (+0300) Subject: Object property quering is refactored. X-Git-Tag: 0.2.5~10 X-Git-Url: http://www.kaiwu.me/postgresql/commit/?a=commitdiff_plain;h=075bf7f4fc091b01e215b83c2721e755fb096acd;p=njs.git 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 index 6cbb9917..542d97c8 100644 --- 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 index 372a6c53..bdc13076 100644 --- a/njs/njs_object.c +++ b/njs/njs_object.c @@ -10,9 +10,18 @@ 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"; + } +} diff --git a/njs/njs_object.h b/njs/njs_object.h index 00262632..87d53a47 100644 --- 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,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; diff --git a/njs/njs_vm.c b/njs/njs_vm.c index 7527749d..e10ed68c 100644 --- 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,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; } diff --git a/njs/njs_vm.h b/njs/njs_vm.h index ceb77467..7db4ae8e 100644 --- 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 index 12c45d87..271adb04 100644 --- 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 index 2596014f..8bec27ca 100644 --- a/njs/test/njs_unit_test.c +++ b/njs/test/njs_unit_test.c @@ -5,6 +5,8 @@ */ #include +#include +#include #include #include #include @@ -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;