From 8700aecba42f9f990c3fa570b6582cf0b771f870 Mon Sep 17 00:00:00 2001 From: hongzhidao Date: Mon, 10 Jun 2019 21:30:29 -0400 Subject: [PATCH] Moving object property methods to njs_object_property.c. --- auto/sources | 1 + njs/njs_object.c | 1025 +------------------------------ njs/njs_object.h | 24 +- njs/njs_object_property.c | 1216 +++++++++++++++++++++++++++++++++++++ njs/njs_vm.c | 214 ------- 5 files changed, 1247 insertions(+), 1233 deletions(-) create mode 100644 njs/njs_object_property.c diff --git a/auto/sources b/auto/sources index 4b925fac..b24d1a41 100644 --- a/auto/sources +++ b/auto/sources @@ -35,6 +35,7 @@ NJS_LIB_SRCS=" \ njs/njs_number.c \ njs/njs_string.c \ njs/njs_object.c \ + njs/njs_object_property.c \ njs/njs_array.c \ njs/njs_json.c \ njs/njs_function.c \ diff --git a/njs/njs_object.c b/njs/njs_object.c index 27d6212c..ded41480 100644 --- a/njs/njs_object.c +++ b/njs/njs_object.c @@ -9,22 +9,6 @@ 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_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_define_property(njs_vm_t *vm, njs_value_t *object, - const njs_value_t *name, const njs_object_t *descriptor); - static njs_object_prop_t *njs_object_exist_in_proto(const njs_object_t *begin, const njs_object_t *end, nxt_lvlhsh_query_t *lhq); static uint32_t njs_object_enumerate_array_length(const njs_object_t *object); @@ -179,603 +163,30 @@ njs_object_hash_test(nxt_lvlhsh_query_t *lhq, void *data) u_char *start; njs_object_prop_t *prop; - prop = data; - - size = prop->name.short_string.size; - - if (size != NJS_STRING_LONG) { - if (lhq->key.length != size) { - return NXT_DECLINED; - } - - start = prop->name.short_string.start; - - } else { - if (lhq->key.length != prop->name.long_string.size) { - return NXT_DECLINED; - } - - start = prop->name.long_string.data->start; - } - - if (memcmp(start, lhq->key.start, lhq->key.length) == 0) { - return NXT_OK; - } - - return NXT_DECLINED; -} - - -nxt_noinline njs_object_prop_t * -njs_object_prop_alloc(njs_vm_t *vm, const njs_value_t *name, - const njs_value_t *value, uint8_t attributes) -{ - njs_object_prop_t *prop; - - prop = nxt_mp_align(vm->mem_pool, sizeof(njs_value_t), - sizeof(njs_object_prop_t)); - - if (nxt_fast_path(prop != NULL)) { - /* GC: retain. */ - prop->value = *value; - - /* GC: retain. */ - prop->name = *name; - - prop->type = NJS_PROPERTY; - prop->writable = attributes; - prop->enumerable = attributes; - prop->configurable = attributes; - - return prop; - } - - njs_memory_error(vm); - - return NULL; -} - - -nxt_noinline njs_object_prop_t * -njs_object_property(njs_vm_t *vm, const njs_object_t *object, - nxt_lvlhsh_query_t *lhq) -{ - nxt_int_t ret; - - lhq->proto = &njs_object_hash_proto; - - do { - ret = nxt_lvlhsh_find(&object->hash, lhq); - - if (nxt_fast_path(ret == NXT_OK)) { - return lhq->value; - } - - ret = nxt_lvlhsh_find(&object->shared_hash, lhq); - - if (nxt_fast_path(ret == NXT_OK)) { - return lhq->value; - } - - object = object->__proto__; - - } while (object != NULL); - - return NULL; -} - - -/* - * 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, - * 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.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, - 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; - - if (nxt_slow_path(!njs_is_primitive(property))) { - return njs_trap(vm, NJS_TRAP_PROPERTY); - } - - hash = nxt_djb_hash; - - switch (object->type) { - - case NJS_BOOLEAN: - case NJS_NUMBER: - index = njs_primitive_prototype_index(object->type); - obj = &vm->prototypes[index].object; - break; - - case NJS_STRING: - if (nxt_fast_path(!njs_is_null_or_undefined_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->string_object; - break; - - case NJS_OBJECT: - case NJS_ARRAY: - case NJS_OBJECT_BOOLEAN: - case NJS_OBJECT_NUMBER: - case NJS_OBJECT_STRING: - case NJS_REGEXP: - case NJS_DATE: - case NJS_OBJECT_ERROR: - case NJS_OBJECT_EVAL_ERROR: - case NJS_OBJECT_INTERNAL_ERROR: - case NJS_OBJECT_RANGE_ERROR: - case NJS_OBJECT_REF_ERROR: - case NJS_OBJECT_SYNTAX_ERROR: - case NJS_OBJECT_TYPE_ERROR: - case NJS_OBJECT_URI_ERROR: - case NJS_OBJECT_VALUE: - obj = object->data.u.object; - break; - - case NJS_FUNCTION: - function = njs_function_value_copy(vm, object); - if (nxt_slow_path(function == NULL)) { - return NXT_ERROR; - } - - obj = &function->object; - break; - - case NJS_EXTERNAL: - obj = NULL; - break; - - case NJS_UNDEFINED: - case NJS_NULL: - default: - 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 \"%V\" of undefined", - &pq->lhq.key); - return NXT_ERROR; - } - - njs_type_error(vm, "cannot get property \"unknown\" of undefined"); - - return NXT_ERROR; - } - - 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); - - if (obj == NULL) { - pq->own = 1; - return njs_external_property_query(vm, pq, object); - } - - return njs_object_property_query(vm, pq, obj, property); - } - - return ret; -} - - -static njs_ret_t -njs_object_property_query(njs_vm_t *vm, njs_property_query_t *pq, - njs_object_t *object, const njs_value_t *property) -{ - uint32_t index; - njs_ret_t ret; - nxt_bool_t own; - 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; - - own = pq->own; - pq->own = 1; - - proto = object; - - do { - pq->prototype = proto; - - if (!njs_is_null_or_undefined_or_boolean(property)) { - switch (proto->type) { - case NJS_ARRAY: - index = njs_value_to_index(property); - if (nxt_fast_path(index < NJS_ARRAY_MAX_INDEX)) { - array = (njs_array_t *) proto; - return njs_array_property_query(vm, pq, array, index); - } - - break; - - 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; - ret = njs_string_property_query(vm, pq, &ov->value, index); - - if (nxt_fast_path(ret != NXT_DECLINED)) { - return ret; - } - } - - default: - break; - } - } - - ret = nxt_lvlhsh_find(&proto->hash, &pq->lhq); - - if (ret == NXT_OK) { - prop = pq->lhq.value; - - if (prop->type != NJS_WHITEOUT) { - return ret; - } - - if (pq->own) { - pq->own_whiteout = prop; - } - - } else { - ret = nxt_lvlhsh_find(&proto->shared_hash, &pq->lhq); - - if (ret == NXT_OK) { - pq->shared = 1; - - return ret; - } - } - - if (own) { - return NXT_DECLINED; - } - - pq->own = 0; - proto = proto->__proto__; - - } while (proto != NULL); - - return NXT_DECLINED; -} - - -static njs_ret_t -njs_array_property_query(njs_vm_t *vm, njs_property_query_t *pq, - njs_array_t *array, uint32_t index) -{ - 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) { - return NXT_DECLINED; - } - - size = index - array->length; - - ret = njs_array_expand(vm, array, 0, size + 1); - if (nxt_slow_path(ret != NXT_OK)) { - return ret; - } - - value = &array->start[array->length]; - - while (size != 0) { - njs_set_invalid(value); - value++; - size--; - } - - array->length = index + 1; - } - - 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->writable = 1; - prop->enumerable = 1; - prop->configurable = 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->writable = 0; - prop->enumerable = 1; - prop->configurable = 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->writable = 0; - prop->enumerable = 1; - prop->configurable = 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; - - if (!njs_is_null_or_undefined(setval)) { - ret = njs_vm_value_to_ext_string(vm, &s, setval, 0); - if (nxt_slow_path(ret != NXT_OK)) { - return ret; - } - - } else { - s = nxt_string_value(""); - } - - *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); -} - - -njs_ret_t -njs_prop_private_copy(njs_vm_t *vm, njs_property_query_t *pq) -{ - nxt_int_t ret; - njs_function_t *function; - njs_object_prop_t *prop, *shared, *name; - nxt_lvlhsh_query_t lhq; - - static const njs_value_t name_string = njs_string("name"); - - prop = nxt_mp_align(vm->mem_pool, sizeof(njs_value_t), - sizeof(njs_object_prop_t)); - if (nxt_slow_path(prop == NULL)) { - njs_memory_error(vm); - return NXT_ERROR; - } - - shared = pq->lhq.value; - *prop = *shared; - - pq->lhq.replace = 0; - pq->lhq.value = prop; - pq->lhq.pool = vm->mem_pool; - - ret = nxt_lvlhsh_insert(&pq->prototype->hash, &pq->lhq); - if (nxt_slow_path(ret != NXT_OK)) { - njs_internal_error(vm, "lvlhsh insert failed"); - return NXT_ERROR; - } + prop = data; - if (!njs_is_function(&prop->value)) { - return NXT_OK; - } + size = prop->name.short_string.size; - function = njs_function_value_copy(vm, &prop->value); - if (nxt_slow_path(function == NULL)) { - return NXT_ERROR; - } + if (size != NJS_STRING_LONG) { + if (lhq->key.length != size) { + return NXT_DECLINED; + } - if (function->ctor) { - function->object.shared_hash = vm->shared->function_instance_hash; + start = prop->name.short_string.start; } else { - function->object.shared_hash = vm->shared->arrow_instance_hash; - } + if (lhq->key.length != prop->name.long_string.size) { + return NXT_DECLINED; + } - name = njs_object_prop_alloc(vm, &name_string, &prop->name, 0); - if (nxt_slow_path(name == NULL)) { - return NXT_ERROR; + start = prop->name.long_string.data->start; } - name->configurable = 1; - - lhq.key_hash = NJS_NAME_HASH; - lhq.key = nxt_string_value("name"); - lhq.replace = 0; - lhq.value = name; - lhq.proto = &njs_object_hash_proto; - lhq.pool = vm->mem_pool; - - ret = nxt_lvlhsh_insert(&function->object.hash, &lhq); - if (nxt_slow_path(ret != NXT_OK)) { - njs_internal_error(vm, "lvlhsh insert failed"); - return NXT_ERROR; + if (memcmp(start, lhq->key.start, lhq->key.length) == 0) { + return NXT_OK; } - return NXT_OK; + return NXT_DECLINED; } @@ -902,7 +313,7 @@ njs_object_keys(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs, static njs_ret_t njs_object_values(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs, njs_index_t unused) - { +{ njs_array_t *array; const njs_value_t *value; @@ -931,7 +342,7 @@ njs_object_values(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs, static njs_ret_t njs_object_entries(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs, njs_index_t unused) - { +{ njs_array_t *array; const njs_value_t *value; @@ -1800,385 +1211,6 @@ njs_object_define_properties(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs, } -static njs_object_prop_t * -njs_descriptor_prop(njs_vm_t *vm, const njs_value_t *name, - const njs_object_t *descriptor) -{ - njs_object_prop_t *prop, *pr; - nxt_lvlhsh_query_t pq; - - prop = njs_object_prop_alloc(vm, name, &njs_value_invalid, - NJS_ATTRIBUTE_UNSET); - if (nxt_slow_path(prop == NULL)) { - return NULL; - } - - pq.key = nxt_string_value("value"); - pq.key_hash = NJS_VALUE_HASH; - - pr = njs_object_property(vm, descriptor, &pq); - if (pr != NULL) { - prop->value = pr->value; - } - - pq.key = nxt_string_value("writable"); - pq.key_hash = NJS_WRITABABLE_HASH; - - pr = njs_object_property(vm, descriptor, &pq); - if (pr != NULL) { - prop->writable = pr->value.data.truth; - } - - pq.key = nxt_string_value("enumerable"); - pq.key_hash = NJS_ENUMERABLE_HASH; - - pr = njs_object_property(vm, descriptor, &pq); - if (pr != NULL) { - prop->enumerable = pr->value.data.truth; - } - - pq.key = nxt_string_value("configurable"); - pq.key_hash = NJS_CONFIGURABLE_HASH; - - pr = njs_object_property(vm, descriptor, &pq); - if (pr != NULL) { - prop->configurable = pr->value.data.truth; - } - - return prop; -} - - -/* - * ES5.1, 8.12.9: [[DefineOwnProperty]] - * Limited support of special descriptors like length and array index - * (values can be set, but without property flags support). - */ -static njs_ret_t -njs_define_property(njs_vm_t *vm, njs_value_t *object, const njs_value_t *name, - const njs_object_t *descriptor) -{ - nxt_int_t ret; - njs_object_prop_t *desc, *current; - njs_property_query_t pq; - - njs_string_get(name, &pq.lhq.key); - pq.lhq.key_hash = nxt_djb_hash(pq.lhq.key.start, pq.lhq.key.length); - pq.lhq.proto = &njs_object_hash_proto; - - njs_property_query_init(&pq, NJS_PROPERTY_QUERY_SET, 1); - - ret = njs_property_query(vm, &pq, object, name); - - if (ret != NXT_OK && ret != NXT_DECLINED) { - return ret; - } - - desc = njs_descriptor_prop(vm, name, descriptor); - if (nxt_slow_path(desc == NULL)) { - return NXT_ERROR; - } - - if (nxt_fast_path(ret == NXT_DECLINED)) { - - if (!njs_is_valid(&desc->value)) { - desc->value = njs_value_undefined; - } - - if (desc->writable == NJS_ATTRIBUTE_UNSET) { - desc->writable = 0; - } - - if (desc->enumerable == NJS_ATTRIBUTE_UNSET) { - desc->enumerable = 0; - } - - if (desc->configurable == NJS_ATTRIBUTE_UNSET) { - desc->configurable = 0; - } - - if (nxt_slow_path(pq.lhq.value != NULL)) { - current = pq.lhq.value; - - if (nxt_slow_path(current->type == NJS_WHITEOUT)) { - /* Previously deleted property. */ - *current = *desc; - } - - } else { - pq.lhq.value = desc; - pq.lhq.replace = 0; - pq.lhq.pool = vm->mem_pool; - - ret = nxt_lvlhsh_insert(&object->data.u.object->hash, &pq.lhq); - if (nxt_slow_path(ret != NXT_OK)) { - njs_internal_error(vm, "lvlhsh insert failed"); - return NXT_ERROR; - } - } - - return NXT_OK; - } - - /* Updating existing prop. */ - - if (nxt_slow_path(pq.shared)) { - ret = njs_prop_private_copy(vm, &pq); - - if (nxt_slow_path(ret != NXT_OK)) { - return ret; - } - } - - current = pq.lhq.value; - - switch (current->type) { - case NJS_PROPERTY: - case NJS_METHOD: - break; - - case NJS_PROPERTY_REF: - if (njs_is_valid(&desc->value)) { - *current->value.data.u.value = desc->value; - } else { - *current->value.data.u.value = njs_value_undefined; - } - - return NXT_OK; - - case NJS_PROPERTY_HANDLER: - if (current->writable && njs_is_valid(&desc->value)) { - ret = current->value.data.u.prop_handler(vm, object, &desc->value, - &vm->retval); - - if (nxt_slow_path(ret != NXT_OK)) { - return ret; - } - } - - return NXT_OK; - - default: - njs_internal_error(vm, "unexpected property type \"%s\" " - "while defining property", - njs_prop_type_string(current->type)); - - return NXT_ERROR; - } - - if (!current->configurable) { - - if (njs_is_valid(&desc->value) - && current->writable == NJS_ATTRIBUTE_FALSE - && !njs_values_strict_equal(&desc->value, ¤t->value)) - { - goto exception; - } - - if (desc->writable == NJS_ATTRIBUTE_TRUE - && current->writable == NJS_ATTRIBUTE_FALSE) - { - goto exception; - } - - if (desc->enumerable != NJS_ATTRIBUTE_UNSET - && current->enumerable != desc->enumerable) - { - goto exception; - } - - if (desc->configurable == NJS_ATTRIBUTE_TRUE) { - goto exception; - } - } - - if (njs_is_valid(&desc->value)) { - current->value = desc->value; - } - - if (desc->writable != NJS_ATTRIBUTE_UNSET) { - current->writable = desc->writable; - } - - if (desc->enumerable != NJS_ATTRIBUTE_UNSET) { - current->enumerable = desc->enumerable; - } - - if (desc->configurable != NJS_ATTRIBUTE_UNSET) { - current->configurable = desc->configurable; - } - - return NXT_OK; - -exception: - - njs_type_error(vm, "Cannot redefine property: \"%V\"", &pq.lhq.key); - - return NXT_ERROR; -} - - -static const njs_value_t njs_object_value_string = njs_string("value"); -static const njs_value_t njs_object_writable_string = - njs_string("writable"); -static const njs_value_t njs_object_enumerable_string = - njs_string("enumerable"); -static const njs_value_t njs_object_configurable_string = - njs_string("configurable"); - - -static njs_ret_t -njs_object_property_descriptor(njs_vm_t *vm, njs_value_t *dest, - const njs_value_t *value, const njs_value_t *property) -{ - nxt_int_t ret; - njs_object_t *descriptor; - njs_object_prop_t *pr, *prop; - const njs_value_t *setval; - nxt_lvlhsh_query_t lhq; - njs_property_query_t pq; - - 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: - *dest = njs_value_undefined; - return NXT_OK; - - case NJS_TRAP: - case NXT_ERROR: - default: - return ret; - } - - 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; - - case NJS_METHOD: - if (pq.shared) { - ret = njs_prop_private_copy(vm, &pq); - - if (nxt_slow_path(ret != NXT_OK)) { - return ret; - } - - 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); - if (nxt_slow_path(descriptor == NULL)) { - return NXT_ERROR; - } - - lhq.proto = &njs_object_hash_proto; - lhq.replace = 0; - lhq.pool = vm->mem_pool; - - lhq.key = nxt_string_value("value"); - lhq.key_hash = NJS_VALUE_HASH; - - pr = njs_object_prop_alloc(vm, &njs_object_value_string, &prop->value, 1); - if (nxt_slow_path(pr == NULL)) { - return NXT_ERROR; - } - - lhq.value = pr; - - ret = nxt_lvlhsh_insert(&descriptor->hash, &lhq); - if (nxt_slow_path(ret != NXT_OK)) { - njs_internal_error(vm, "lvlhsh insert failed"); - return NXT_ERROR; - } - - lhq.key = nxt_string_value("writable"); - lhq.key_hash = NJS_WRITABABLE_HASH; - - setval = (prop->writable == 1) ? &njs_value_true : &njs_value_false; - - pr = njs_object_prop_alloc(vm, &njs_object_writable_string, setval, 1); - if (nxt_slow_path(pr == NULL)) { - return NXT_ERROR; - } - - lhq.value = pr; - - ret = nxt_lvlhsh_insert(&descriptor->hash, &lhq); - if (nxt_slow_path(ret != NXT_OK)) { - njs_internal_error(vm, "lvlhsh insert failed"); - return NXT_ERROR; - } - - lhq.key = nxt_string_value("enumerable"); - lhq.key_hash = NJS_ENUMERABLE_HASH; - - setval = (prop->enumerable == 1) ? &njs_value_true : &njs_value_false; - - pr = njs_object_prop_alloc(vm, &njs_object_enumerable_string, setval, 1); - if (nxt_slow_path(pr == NULL)) { - return NXT_ERROR; - } - - lhq.value = pr; - - ret = nxt_lvlhsh_insert(&descriptor->hash, &lhq); - if (nxt_slow_path(ret != NXT_OK)) { - njs_internal_error(vm, "lvlhsh insert failed"); - return NXT_ERROR; - } - - lhq.key = nxt_string_value("configurable"); - lhq.key_hash = NJS_CONFIGURABLE_HASH; - - setval = (prop->configurable == 1) ? &njs_value_true : &njs_value_false; - - pr = njs_object_prop_alloc(vm, &njs_object_configurable_string, setval, 1); - if (nxt_slow_path(pr == NULL)) { - return NXT_ERROR; - } - - lhq.value = pr; - - ret = nxt_lvlhsh_insert(&descriptor->hash, &lhq); - if (nxt_slow_path(ret != NXT_OK)) { - njs_internal_error(vm, "lvlhsh insert failed"); - return NXT_ERROR; - } - - dest->data.u.object = descriptor; - dest->type = NJS_OBJECT; - dest->data.truth = 1; - - return NXT_OK; -} - - 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) @@ -3297,28 +2329,3 @@ 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 2196aca0..115637c4 100644 --- a/njs/njs_object.h +++ b/njs/njs_object.h @@ -87,20 +87,10 @@ njs_array_t *njs_object_enumerate(njs_vm_t *vm, const njs_object_t *object, njs_object_enum_t kind, nxt_bool_t all); njs_array_t *njs_object_own_enumerate(njs_vm_t *vm, const njs_object_t *object, njs_object_enum_t kind, nxt_bool_t all); -njs_ret_t njs_value_property(njs_vm_t *vm, const njs_value_t *value, - const njs_value_t *property, njs_value_t *retval); -njs_ret_t njs_value_property_set(njs_vm_t *vm, njs_value_t *object, - const njs_value_t *property, njs_value_t *value); -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, 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, nxt_uint_t nargs, njs_index_t unused); -njs_object_prop_t *njs_object_prop_alloc(njs_vm_t *vm, const njs_value_t *name, - const njs_value_t *value, uint8_t attributes); njs_ret_t njs_primitive_prototype_get_proto(njs_vm_t *vm, njs_value_t *value, njs_value_t *setval, njs_value_t *retval); njs_ret_t njs_object_prototype_create(njs_vm_t *vm, njs_value_t *value, @@ -116,6 +106,20 @@ 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_property_query(njs_vm_t *vm, njs_property_query_t *pq, + njs_value_t *object, const njs_value_t *property); +njs_ret_t njs_value_property(njs_vm_t *vm, const njs_value_t *value, + const njs_value_t *property, njs_value_t *retval); +njs_ret_t njs_value_property_set(njs_vm_t *vm, njs_value_t *object, + const njs_value_t *property, njs_value_t *value); +njs_object_prop_t *njs_object_prop_alloc(njs_vm_t *vm, const njs_value_t *name, + const njs_value_t *value, uint8_t attributes); +njs_object_prop_t *njs_object_property(njs_vm_t *vm, const njs_object_t *obj, + nxt_lvlhsh_query_t *lhq); +njs_ret_t njs_define_property(njs_vm_t *vm, njs_value_t *object, + const njs_value_t *name, const njs_object_t *descriptor); +njs_ret_t njs_object_property_descriptor(njs_vm_t *vm, njs_value_t *dest, + const njs_value_t *value, const njs_value_t *property); njs_ret_t njs_prop_private_copy(njs_vm_t *vm, njs_property_query_t *pq); const char *njs_prop_type_string(njs_object_property_type_t type); diff --git a/njs/njs_object_property.c b/njs/njs_object_property.c new file mode 100644 index 00000000..60ff3d40 --- /dev/null +++ b/njs/njs_object_property.c @@ -0,0 +1,1216 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) NGINX, Inc. + */ + +#include +#include + + +static njs_ret_t njs_object_property_query(njs_vm_t *vm, + 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_object_prop_t *njs_descriptor_prop(njs_vm_t *vm, + const njs_value_t *name, const njs_object_t *descriptor); + + +/* + * 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, + * 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.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, + 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; + + if (nxt_slow_path(!njs_is_primitive(property))) { + return njs_trap(vm, NJS_TRAP_PROPERTY); + } + + hash = nxt_djb_hash; + + switch (object->type) { + + case NJS_BOOLEAN: + case NJS_NUMBER: + index = njs_primitive_prototype_index(object->type); + obj = &vm->prototypes[index].object; + break; + + case NJS_STRING: + if (nxt_fast_path(!njs_is_null_or_undefined_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->string_object; + break; + + case NJS_OBJECT: + case NJS_ARRAY: + case NJS_OBJECT_BOOLEAN: + case NJS_OBJECT_NUMBER: + case NJS_OBJECT_STRING: + case NJS_REGEXP: + case NJS_DATE: + case NJS_OBJECT_ERROR: + case NJS_OBJECT_EVAL_ERROR: + case NJS_OBJECT_INTERNAL_ERROR: + case NJS_OBJECT_RANGE_ERROR: + case NJS_OBJECT_REF_ERROR: + case NJS_OBJECT_SYNTAX_ERROR: + case NJS_OBJECT_TYPE_ERROR: + case NJS_OBJECT_URI_ERROR: + case NJS_OBJECT_VALUE: + obj = object->data.u.object; + break; + + case NJS_FUNCTION: + function = njs_function_value_copy(vm, object); + if (nxt_slow_path(function == NULL)) { + return NXT_ERROR; + } + + obj = &function->object; + break; + + case NJS_EXTERNAL: + obj = NULL; + break; + + case NJS_UNDEFINED: + case NJS_NULL: + default: + 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 \"%V\" of undefined", + &pq->lhq.key); + return NXT_ERROR; + } + + njs_type_error(vm, "cannot get property \"unknown\" of undefined"); + + return NXT_ERROR; + } + + 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); + + if (obj == NULL) { + pq->own = 1; + return njs_external_property_query(vm, pq, object); + } + + return njs_object_property_query(vm, pq, obj, property); + } + + return ret; +} + + +static njs_ret_t +njs_object_property_query(njs_vm_t *vm, njs_property_query_t *pq, + njs_object_t *object, const njs_value_t *property) +{ + uint32_t index; + njs_ret_t ret; + nxt_bool_t own; + 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; + + own = pq->own; + pq->own = 1; + + proto = object; + + do { + pq->prototype = proto; + + if (!njs_is_null_or_undefined_or_boolean(property)) { + switch (proto->type) { + case NJS_ARRAY: + index = njs_value_to_index(property); + if (nxt_fast_path(index < NJS_ARRAY_MAX_INDEX)) { + array = (njs_array_t *) proto; + return njs_array_property_query(vm, pq, array, index); + } + + break; + + 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; + ret = njs_string_property_query(vm, pq, &ov->value, index); + + if (nxt_fast_path(ret != NXT_DECLINED)) { + return ret; + } + } + + default: + break; + } + } + + ret = nxt_lvlhsh_find(&proto->hash, &pq->lhq); + + if (ret == NXT_OK) { + prop = pq->lhq.value; + + if (prop->type != NJS_WHITEOUT) { + return ret; + } + + if (pq->own) { + pq->own_whiteout = prop; + } + + } else { + ret = nxt_lvlhsh_find(&proto->shared_hash, &pq->lhq); + + if (ret == NXT_OK) { + pq->shared = 1; + + return ret; + } + } + + if (own) { + return NXT_DECLINED; + } + + pq->own = 0; + proto = proto->__proto__; + + } while (proto != NULL); + + return NXT_DECLINED; +} + + +static njs_ret_t +njs_array_property_query(njs_vm_t *vm, njs_property_query_t *pq, + njs_array_t *array, uint32_t index) +{ + 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) { + return NXT_DECLINED; + } + + size = index - array->length; + + ret = njs_array_expand(vm, array, 0, size + 1); + if (nxt_slow_path(ret != NXT_OK)) { + return ret; + } + + value = &array->start[array->length]; + + while (size != 0) { + njs_set_invalid(value); + value++; + size--; + } + + array->length = index + 1; + } + + 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->writable = 1; + prop->enumerable = 1; + prop->configurable = 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->writable = 0; + prop->enumerable = 1; + prop->configurable = 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->writable = 0; + prop->enumerable = 1; + prop->configurable = 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; + + if (!njs_is_null_or_undefined(setval)) { + ret = njs_vm_value_to_ext_string(vm, &s, setval, 0); + if (nxt_slow_path(ret != NXT_OK)) { + return ret; + } + + } else { + s = nxt_string_value(""); + } + + *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); +} + + +/* + * 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, const njs_value_t *value, + const njs_value_t *property, njs_value_t *retval) +{ + njs_ret_t ret; + njs_object_prop_t *prop; + njs_property_query_t pq; + + njs_property_query_init(&pq, NJS_PROPERTY_QUERY_GET, 0); + + ret = njs_property_query(vm, &pq, (njs_value_t *) value, property); + + switch (ret) { + + case NXT_OK: + prop = pq.lhq.value; + + switch (prop->type) { + + case NJS_METHOD: + if (pq.shared) { + ret = njs_prop_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, (njs_value_t *) 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_undefined; + + return NXT_DECLINED; + + case NJS_TRAP: + case NXT_ERROR: + default: + + return ret; + } + + return NXT_OK; +} + + +/* + * NXT_OK property has been set successfully + * NJS_TRAP the property trap must be called + * NXT_ERROR exception has been thrown. + */ +njs_ret_t +njs_value_property_set(njs_vm_t *vm, njs_value_t *object, + const njs_value_t *property, njs_value_t *value) +{ + njs_ret_t ret; + njs_object_prop_t *prop, *shared; + njs_property_query_t pq; + + if (njs_is_primitive(object)) { + njs_type_error(vm, "property set on primitive %s type", + njs_type_string(object->type)); + return NXT_ERROR; + } + + shared = NULL; + + njs_property_query_init(&pq, NJS_PROPERTY_QUERY_SET, 0); + + ret = njs_property_query(vm, &pq, object, property); + + switch (ret) { + + case NXT_OK: + prop = pq.lhq.value; + + if (nxt_slow_path(!prop->writable)) { + njs_type_error(vm, + "Cannot assign to read-only property \"%V\" of %s", + &pq.lhq.key, njs_type_string(object->type)); + return NXT_ERROR; + } + + if (prop->type == NJS_PROPERTY_HANDLER) { + ret = prop->value.data.u.prop_handler(vm, object, value, + &vm->retval); + if (ret != NXT_DECLINED) { + return ret; + } + } + + if (pq.own) { + switch (prop->type) { + case NJS_PROPERTY: + case NJS_METHOD: + if (nxt_slow_path(pq.shared)) { + shared = prop; + break; + } + + goto found; + + case NJS_PROPERTY_REF: + *prop->value.data.u.value = *value; + return NXT_OK; + + default: + njs_internal_error(vm, "unexpected property type \"%s\" " + "while setting", + njs_prop_type_string(prop->type)); + + return NXT_ERROR; + } + + break; + } + + /* Fall through. */ + + case NXT_DECLINED: + if (nxt_slow_path(pq.own_whiteout != NULL)) { + /* Previously deleted property. */ + prop = pq.own_whiteout; + + prop->type = NJS_PROPERTY; + prop->enumerable = 1; + prop->configurable = 1; + prop->writable = 1; + + goto found; + } + + break; + + case NJS_TRAP: + case NXT_ERROR: + default: + + return ret; + } + + if (nxt_slow_path(!object->data.u.object->extensible)) { + njs_type_error(vm, "Cannot add property \"%V\", " + "object is not extensible", &pq.lhq.key); + return NXT_ERROR; + } + + prop = njs_object_prop_alloc(vm, &pq.value, &njs_value_undefined, 1); + if (nxt_slow_path(prop == NULL)) { + return NXT_ERROR; + } + + if (nxt_slow_path(shared != NULL)) { + prop->enumerable = shared->enumerable; + prop->configurable = shared->configurable; + } + + pq.lhq.replace = 0; + pq.lhq.value = prop; + pq.lhq.pool = vm->mem_pool; + + ret = nxt_lvlhsh_insert(&object->data.u.object->hash, &pq.lhq); + if (nxt_slow_path(ret != NXT_OK)) { + njs_internal_error(vm, "lvlhsh insert failed"); + return NXT_ERROR; + } + +found: + + prop->value = *value; + + return NXT_OK; +} + + +nxt_noinline njs_object_prop_t * +njs_object_prop_alloc(njs_vm_t *vm, const njs_value_t *name, + const njs_value_t *value, uint8_t attributes) +{ + njs_object_prop_t *prop; + + prop = nxt_mp_align(vm->mem_pool, sizeof(njs_value_t), + sizeof(njs_object_prop_t)); + + if (nxt_fast_path(prop != NULL)) { + /* GC: retain. */ + prop->value = *value; + + /* GC: retain. */ + prop->name = *name; + + prop->type = NJS_PROPERTY; + prop->writable = attributes; + prop->enumerable = attributes; + prop->configurable = attributes; + + return prop; + } + + njs_memory_error(vm); + + return NULL; +} + + +nxt_noinline njs_object_prop_t * +njs_object_property(njs_vm_t *vm, const njs_object_t *object, + nxt_lvlhsh_query_t *lhq) +{ + nxt_int_t ret; + + lhq->proto = &njs_object_hash_proto; + + do { + ret = nxt_lvlhsh_find(&object->hash, lhq); + + if (nxt_fast_path(ret == NXT_OK)) { + return lhq->value; + } + + ret = nxt_lvlhsh_find(&object->shared_hash, lhq); + + if (nxt_fast_path(ret == NXT_OK)) { + return lhq->value; + } + + object = object->__proto__; + + } while (object != NULL); + + return NULL; +} + + +/* + * ES5.1, 8.12.9: [[DefineOwnProperty]] + * Limited support of special descriptors like length and array index + * (values can be set, but without property flags support). + */ +njs_ret_t +njs_define_property(njs_vm_t *vm, njs_value_t *object, const njs_value_t *name, + const njs_object_t *descriptor) +{ + nxt_int_t ret; + njs_object_prop_t *desc, *current; + njs_property_query_t pq; + + njs_string_get(name, &pq.lhq.key); + pq.lhq.key_hash = nxt_djb_hash(pq.lhq.key.start, pq.lhq.key.length); + pq.lhq.proto = &njs_object_hash_proto; + + njs_property_query_init(&pq, NJS_PROPERTY_QUERY_SET, 1); + + ret = njs_property_query(vm, &pq, object, name); + + if (ret != NXT_OK && ret != NXT_DECLINED) { + return ret; + } + + desc = njs_descriptor_prop(vm, name, descriptor); + if (nxt_slow_path(desc == NULL)) { + return NXT_ERROR; + } + + if (nxt_fast_path(ret == NXT_DECLINED)) { + + if (!njs_is_valid(&desc->value)) { + desc->value = njs_value_undefined; + } + + if (desc->writable == NJS_ATTRIBUTE_UNSET) { + desc->writable = 0; + } + + if (desc->enumerable == NJS_ATTRIBUTE_UNSET) { + desc->enumerable = 0; + } + + if (desc->configurable == NJS_ATTRIBUTE_UNSET) { + desc->configurable = 0; + } + + if (nxt_slow_path(pq.lhq.value != NULL)) { + current = pq.lhq.value; + + if (nxt_slow_path(current->type == NJS_WHITEOUT)) { + /* Previously deleted property. */ + *current = *desc; + } + + } else { + pq.lhq.value = desc; + pq.lhq.replace = 0; + pq.lhq.pool = vm->mem_pool; + + ret = nxt_lvlhsh_insert(&object->data.u.object->hash, &pq.lhq); + if (nxt_slow_path(ret != NXT_OK)) { + njs_internal_error(vm, "lvlhsh insert failed"); + return NXT_ERROR; + } + } + + return NXT_OK; + } + + /* Updating existing prop. */ + + if (nxt_slow_path(pq.shared)) { + ret = njs_prop_private_copy(vm, &pq); + + if (nxt_slow_path(ret != NXT_OK)) { + return ret; + } + } + + current = pq.lhq.value; + + switch (current->type) { + case NJS_PROPERTY: + case NJS_METHOD: + break; + + case NJS_PROPERTY_REF: + if (njs_is_valid(&desc->value)) { + *current->value.data.u.value = desc->value; + } else { + *current->value.data.u.value = njs_value_undefined; + } + + return NXT_OK; + + case NJS_PROPERTY_HANDLER: + if (current->writable && njs_is_valid(&desc->value)) { + ret = current->value.data.u.prop_handler(vm, object, &desc->value, + &vm->retval); + + if (nxt_slow_path(ret != NXT_OK)) { + return ret; + } + } + + return NXT_OK; + + default: + njs_internal_error(vm, "unexpected property type \"%s\" " + "while defining property", + njs_prop_type_string(current->type)); + + return NXT_ERROR; + } + + if (!current->configurable) { + + if (njs_is_valid(&desc->value) + && current->writable == NJS_ATTRIBUTE_FALSE + && !njs_values_strict_equal(&desc->value, ¤t->value)) + { + goto exception; + } + + if (desc->writable == NJS_ATTRIBUTE_TRUE + && current->writable == NJS_ATTRIBUTE_FALSE) + { + goto exception; + } + + if (desc->enumerable != NJS_ATTRIBUTE_UNSET + && current->enumerable != desc->enumerable) + { + goto exception; + } + + if (desc->configurable == NJS_ATTRIBUTE_TRUE) { + goto exception; + } + } + + if (njs_is_valid(&desc->value)) { + current->value = desc->value; + } + + if (desc->writable != NJS_ATTRIBUTE_UNSET) { + current->writable = desc->writable; + } + + if (desc->enumerable != NJS_ATTRIBUTE_UNSET) { + current->enumerable = desc->enumerable; + } + + if (desc->configurable != NJS_ATTRIBUTE_UNSET) { + current->configurable = desc->configurable; + } + + return NXT_OK; + +exception: + + njs_type_error(vm, "Cannot redefine property: \"%V\"", &pq.lhq.key); + + return NXT_ERROR; +} + + +static njs_object_prop_t * +njs_descriptor_prop(njs_vm_t *vm, const njs_value_t *name, + const njs_object_t *descriptor) +{ + njs_object_prop_t *prop, *pr; + nxt_lvlhsh_query_t pq; + + prop = njs_object_prop_alloc(vm, name, &njs_value_invalid, + NJS_ATTRIBUTE_UNSET); + if (nxt_slow_path(prop == NULL)) { + return NULL; + } + + pq.key = nxt_string_value("value"); + pq.key_hash = NJS_VALUE_HASH; + + pr = njs_object_property(vm, descriptor, &pq); + if (pr != NULL) { + prop->value = pr->value; + } + + pq.key = nxt_string_value("writable"); + pq.key_hash = NJS_WRITABABLE_HASH; + + pr = njs_object_property(vm, descriptor, &pq); + if (pr != NULL) { + prop->writable = pr->value.data.truth; + } + + pq.key = nxt_string_value("enumerable"); + pq.key_hash = NJS_ENUMERABLE_HASH; + + pr = njs_object_property(vm, descriptor, &pq); + if (pr != NULL) { + prop->enumerable = pr->value.data.truth; + } + + pq.key = nxt_string_value("configurable"); + pq.key_hash = NJS_CONFIGURABLE_HASH; + + pr = njs_object_property(vm, descriptor, &pq); + if (pr != NULL) { + prop->configurable = pr->value.data.truth; + } + + return prop; +} + + +static const njs_value_t njs_object_value_string = njs_string("value"); +static const njs_value_t njs_object_writable_string = + njs_string("writable"); +static const njs_value_t njs_object_enumerable_string = + njs_string("enumerable"); +static const njs_value_t njs_object_configurable_string = + njs_string("configurable"); + + +njs_ret_t +njs_object_property_descriptor(njs_vm_t *vm, njs_value_t *dest, + const njs_value_t *value, const njs_value_t *property) +{ + nxt_int_t ret; + njs_object_t *descriptor; + njs_object_prop_t *pr, *prop; + const njs_value_t *setval; + nxt_lvlhsh_query_t lhq; + njs_property_query_t pq; + + 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: + *dest = njs_value_undefined; + return NXT_OK; + + case NJS_TRAP: + case NXT_ERROR: + default: + return ret; + } + + 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; + + case NJS_METHOD: + if (pq.shared) { + ret = njs_prop_private_copy(vm, &pq); + + if (nxt_slow_path(ret != NXT_OK)) { + return ret; + } + + 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); + if (nxt_slow_path(descriptor == NULL)) { + return NXT_ERROR; + } + + lhq.proto = &njs_object_hash_proto; + lhq.replace = 0; + lhq.pool = vm->mem_pool; + + lhq.key = nxt_string_value("value"); + lhq.key_hash = NJS_VALUE_HASH; + + pr = njs_object_prop_alloc(vm, &njs_object_value_string, &prop->value, 1); + if (nxt_slow_path(pr == NULL)) { + return NXT_ERROR; + } + + lhq.value = pr; + + ret = nxt_lvlhsh_insert(&descriptor->hash, &lhq); + if (nxt_slow_path(ret != NXT_OK)) { + njs_internal_error(vm, "lvlhsh insert failed"); + return NXT_ERROR; + } + + lhq.key = nxt_string_value("writable"); + lhq.key_hash = NJS_WRITABABLE_HASH; + + setval = (prop->writable == 1) ? &njs_value_true : &njs_value_false; + + pr = njs_object_prop_alloc(vm, &njs_object_writable_string, setval, 1); + if (nxt_slow_path(pr == NULL)) { + return NXT_ERROR; + } + + lhq.value = pr; + + ret = nxt_lvlhsh_insert(&descriptor->hash, &lhq); + if (nxt_slow_path(ret != NXT_OK)) { + njs_internal_error(vm, "lvlhsh insert failed"); + return NXT_ERROR; + } + + lhq.key = nxt_string_value("enumerable"); + lhq.key_hash = NJS_ENUMERABLE_HASH; + + setval = (prop->enumerable == 1) ? &njs_value_true : &njs_value_false; + + pr = njs_object_prop_alloc(vm, &njs_object_enumerable_string, setval, 1); + if (nxt_slow_path(pr == NULL)) { + return NXT_ERROR; + } + + lhq.value = pr; + + ret = nxt_lvlhsh_insert(&descriptor->hash, &lhq); + if (nxt_slow_path(ret != NXT_OK)) { + njs_internal_error(vm, "lvlhsh insert failed"); + return NXT_ERROR; + } + + lhq.key = nxt_string_value("configurable"); + lhq.key_hash = NJS_CONFIGURABLE_HASH; + + setval = (prop->configurable == 1) ? &njs_value_true : &njs_value_false; + + pr = njs_object_prop_alloc(vm, &njs_object_configurable_string, setval, 1); + if (nxt_slow_path(pr == NULL)) { + return NXT_ERROR; + } + + lhq.value = pr; + + ret = nxt_lvlhsh_insert(&descriptor->hash, &lhq); + if (nxt_slow_path(ret != NXT_OK)) { + njs_internal_error(vm, "lvlhsh insert failed"); + return NXT_ERROR; + } + + dest->data.u.object = descriptor; + dest->type = NJS_OBJECT; + dest->data.truth = 1; + + return NXT_OK; +} + + +njs_ret_t +njs_prop_private_copy(njs_vm_t *vm, njs_property_query_t *pq) +{ + nxt_int_t ret; + njs_function_t *function; + njs_object_prop_t *prop, *shared, *name; + nxt_lvlhsh_query_t lhq; + + static const njs_value_t name_string = njs_string("name"); + + prop = nxt_mp_align(vm->mem_pool, sizeof(njs_value_t), + sizeof(njs_object_prop_t)); + if (nxt_slow_path(prop == NULL)) { + njs_memory_error(vm); + return NXT_ERROR; + } + + shared = pq->lhq.value; + *prop = *shared; + + pq->lhq.replace = 0; + pq->lhq.value = prop; + pq->lhq.pool = vm->mem_pool; + + ret = nxt_lvlhsh_insert(&pq->prototype->hash, &pq->lhq); + if (nxt_slow_path(ret != NXT_OK)) { + njs_internal_error(vm, "lvlhsh insert failed"); + return NXT_ERROR; + } + + if (!njs_is_function(&prop->value)) { + return NXT_OK; + } + + function = njs_function_value_copy(vm, &prop->value); + if (nxt_slow_path(function == NULL)) { + return NXT_ERROR; + } + + if (function->ctor) { + function->object.shared_hash = vm->shared->function_instance_hash; + + } else { + function->object.shared_hash = vm->shared->arrow_instance_hash; + } + + name = njs_object_prop_alloc(vm, &name_string, &prop->name, 0); + if (nxt_slow_path(name == NULL)) { + return NXT_ERROR; + } + + name->configurable = 1; + + lhq.key_hash = NJS_NAME_HASH; + lhq.key = nxt_string_value("name"); + lhq.replace = 0; + lhq.value = name; + lhq.proto = &njs_object_hash_proto; + lhq.pool = vm->mem_pool; + + ret = nxt_lvlhsh_insert(&function->object.hash, &lhq); + if (nxt_slow_path(ret != NXT_OK)) { + njs_internal_error(vm, "lvlhsh insert failed"); + return NXT_ERROR; + } + + return NXT_OK; +} + + +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_vm.c b/njs/njs_vm.c index 74886178..2c0ecb15 100644 --- a/njs/njs_vm.c +++ b/njs/njs_vm.c @@ -3038,220 +3038,6 @@ njs_primitive_value(njs_vm_t *vm, njs_value_t *value, nxt_uint_t hint) } -/* - * 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, const njs_value_t *value, - const njs_value_t *property, njs_value_t *retval) -{ - njs_ret_t ret; - njs_object_prop_t *prop; - njs_property_query_t pq; - - njs_property_query_init(&pq, NJS_PROPERTY_QUERY_GET, 0); - - ret = njs_property_query(vm, &pq, (njs_value_t *) value, property); - - switch (ret) { - - case NXT_OK: - prop = pq.lhq.value; - - switch (prop->type) { - - case NJS_METHOD: - if (pq.shared) { - ret = njs_prop_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, (njs_value_t *) 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_undefined; - - return NXT_DECLINED; - - case NJS_TRAP: - case NXT_ERROR: - default: - - return ret; - } - - return NXT_OK; -} - - -/* - * NXT_OK property has been set successfully - * NJS_TRAP the property trap must be called - * NXT_ERROR exception has been thrown. - */ -njs_ret_t -njs_value_property_set(njs_vm_t *vm, njs_value_t *object, - const njs_value_t *property, njs_value_t *value) -{ - njs_ret_t ret; - njs_object_prop_t *prop, *shared; - njs_property_query_t pq; - - if (njs_is_primitive(object)) { - njs_type_error(vm, "property set on primitive %s type", - njs_type_string(object->type)); - return NXT_ERROR; - } - - shared = NULL; - - njs_property_query_init(&pq, NJS_PROPERTY_QUERY_SET, 0); - - ret = njs_property_query(vm, &pq, object, property); - - switch (ret) { - - case NXT_OK: - prop = pq.lhq.value; - - if (nxt_slow_path(!prop->writable)) { - njs_type_error(vm, - "Cannot assign to read-only property \"%V\" of %s", - &pq.lhq.key, njs_type_string(object->type)); - return NXT_ERROR; - } - - if (prop->type == NJS_PROPERTY_HANDLER) { - ret = prop->value.data.u.prop_handler(vm, object, value, - &vm->retval); - if (ret != NXT_DECLINED) { - return ret; - } - } - - if (pq.own) { - switch (prop->type) { - case NJS_PROPERTY: - case NJS_METHOD: - if (nxt_slow_path(pq.shared)) { - shared = prop; - break; - } - - goto found; - - case NJS_PROPERTY_REF: - *prop->value.data.u.value = *value; - return NXT_OK; - - default: - njs_internal_error(vm, "unexpected property type \"%s\" " - "while setting", - njs_prop_type_string(prop->type)); - - return NXT_ERROR; - } - - break; - } - - /* Fall through. */ - - case NXT_DECLINED: - if (nxt_slow_path(pq.own_whiteout != NULL)) { - /* Previously deleted property. */ - prop = pq.own_whiteout; - - prop->type = NJS_PROPERTY; - prop->enumerable = 1; - prop->configurable = 1; - prop->writable = 1; - - goto found; - } - - break; - - case NJS_TRAP: - case NXT_ERROR: - default: - - return ret; - } - - if (nxt_slow_path(!object->data.u.object->extensible)) { - njs_type_error(vm, "Cannot add property \"%V\", " - "object is not extensible", &pq.lhq.key); - return NXT_ERROR; - } - - prop = njs_object_prop_alloc(vm, &pq.value, &njs_value_undefined, 1); - if (nxt_slow_path(prop == NULL)) { - return NXT_ERROR; - } - - if (nxt_slow_path(shared != NULL)) { - prop->enumerable = shared->enumerable; - prop->configurable = shared->configurable; - } - - pq.lhq.replace = 0; - pq.lhq.value = prop; - pq.lhq.pool = vm->mem_pool; - - ret = nxt_lvlhsh_insert(&object->data.u.object->hash, &pq.lhq); - if (nxt_slow_path(ret != NXT_OK)) { - njs_internal_error(vm, "lvlhsh insert failed"); - return NXT_ERROR; - } - -found: - - prop->value = *value; - - return NXT_OK; -} - - njs_array_t * njs_value_enumerate(njs_vm_t *vm, const njs_value_t *value, njs_object_enum_t kind, nxt_bool_t all) -- 2.47.3