From: hongzhidao Date: Fri, 2 Aug 2019 15:36:42 +0000 (+0800) Subject: Refactored njs_object_property.c. X-Git-Tag: 0.3.4~28 X-Git-Url: http://www.kaiwu.me/postgresql/commit/?a=commitdiff_plain;h=d7981fdbd53744fc563424cc729839236bce64e6;p=njs.git Refactored njs_object_property.c. 1) Moving generic function to njs_value.c: njs_property_query(), njs_value_property(), njs_value_property_set(). 2) Moving rest of the functions to njs_object_prop.c. --- diff --git a/auto/sources b/auto/sources index c07915a0..029a531f 100644 --- a/auto/sources +++ b/auto/sources @@ -26,7 +26,7 @@ NJS_LIB_SRCS=" \ src/njs_number.c \ src/njs_string.c \ src/njs_object.c \ - src/njs_object_property.c \ + src/njs_object_prop.c \ src/njs_array.c \ src/njs_json.c \ src/njs_function.c \ diff --git a/src/njs_object.h b/src/njs_object.h index 33b84b39..e3ddbc09 100644 --- a/src/njs_object.h +++ b/src/njs_object.h @@ -8,45 +8,6 @@ #define _NJS_OBJECT_H_INCLUDED_ -typedef enum { - NJS_PROPERTY = 0, - NJS_PROPERTY_REF, - NJS_METHOD, - NJS_PROPERTY_HANDLER, - NJS_WHITEOUT, -} njs_object_prop_type_t; - - -/* - * Attributes are generally used as Boolean values. - * The UNSET value is can be seen: - * for newly created property descriptors in njs_define_property(), - * for writable attribute of accessor descriptors (desc->writable - * cannot be used as a boolean value). - */ -typedef enum { - NJS_ATTRIBUTE_FALSE = 0, - NJS_ATTRIBUTE_TRUE = 1, - NJS_ATTRIBUTE_UNSET, -} njs_object_attribute_t; - - -typedef struct { - /* Must be aligned to njs_value_t. */ - njs_value_t value; - njs_value_t name; - njs_value_t getter; - njs_value_t setter; - - /* TODO: get rid of types */ - njs_object_prop_type_t type:8; /* 3 bits */ - - njs_object_attribute_t writable:8; /* 2 bits */ - njs_object_attribute_t enumerable:8; /* 2 bits */ - njs_object_attribute_t configurable:8; /* 2 bits */ -} njs_object_prop_t; - - #define njs_is_data_descriptor(prop) \ ((prop)->writable != NJS_ATTRIBUTE_UNSET || njs_is_valid(&(prop)->value)) @@ -59,37 +20,6 @@ typedef struct { (!njs_is_data_descriptor(prop) && !njs_is_accessor_descriptor(prop)) -typedef struct { - njs_lvlhsh_query_t lhq; - - /* 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; - njs_object_prop_t *own_whiteout; - 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)->own_whiteout = NULL; \ - (pq)->query = _query; \ - (pq)->shared = 0; \ - (pq)->own = _own; \ - } while (0) - - struct njs_object_init_s { njs_str_t name; const njs_object_prop_t *properties; @@ -124,12 +54,6 @@ njs_value_t *njs_property_constructor_create(njs_vm_t *vm, njs_lvlhsh_t *hash, njs_int_t njs_object_prototype_to_string(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused); -njs_int_t njs_property_query(njs_vm_t *vm, njs_property_query_t *pq, - njs_value_t *object, njs_value_t *property); -njs_int_t njs_value_property(njs_vm_t *vm, njs_value_t *value, - njs_value_t *property, njs_value_t *retval); -njs_int_t njs_value_property_set(njs_vm_t *vm, njs_value_t *object, - 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, diff --git a/src/njs_object_property.c b/src/njs_object_prop.c similarity index 50% rename from src/njs_object_property.c rename to src/njs_object_prop.c index ca5dcc21..fd9539fd 100644 --- a/src/njs_object_property.c +++ b/src/njs_object_prop.c @@ -4,702 +4,12 @@ * Copyright (C) NGINX, Inc. */ -#include -#include - - -static njs_int_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_int_t njs_array_property_query(njs_vm_t *vm, - njs_property_query_t *pq, njs_array_t *array, uint32_t index); -static njs_int_t njs_string_property_query(njs_vm_t *vm, - njs_property_query_t *pq, njs_value_t *object, uint32_t index); -static njs_int_t njs_external_property_query(njs_vm_t *vm, - njs_property_query_t *pq, njs_value_t *object); -static njs_int_t njs_external_property_set(njs_vm_t *vm, njs_value_t *value, - njs_value_t *setval, njs_value_t *retval); -static njs_int_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 - * NJS_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. - * NJS_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_ERROR exception has been thrown. - * - * TODO: - * Object.defineProperty([1,2], '1', {configurable:false}) - */ - -njs_int_t -njs_property_query(njs_vm_t *vm, njs_property_query_t *pq, njs_value_t *object, - njs_value_t *property) -{ - uint32_t index; - njs_int_t ret; - njs_object_t *obj; - njs_value_t prop; - njs_function_t *function; - - if (njs_slow_path(!njs_is_primitive(property))) { - ret = njs_value_to_string(vm, &prop, property); - if (ret != NJS_OK) { - return ret; - } - - property = ∝ - } - - 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 (njs_fast_path(!njs_is_null_or_undefined_or_boolean(property))) { - index = njs_value_to_index(property); - - if (njs_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 = njs_object(object); - break; - - case NJS_FUNCTION: - function = njs_function_value_copy(vm, object); - if (njs_slow_path(function == NULL)) { - return NJS_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 (njs_fast_path(ret == NJS_OK)) { - njs_string_get(&pq->value, &pq->lhq.key); - njs_type_error(vm, "cannot get property \"%V\" of undefined", - &pq->lhq.key); - return NJS_ERROR; - } - - njs_type_error(vm, "cannot get property \"unknown\" of undefined"); - - return NJS_ERROR; - } - - ret = njs_primitive_value_to_string(vm, &pq->value, property); - - if (njs_fast_path(ret == NJS_OK)) { - - njs_string_get(&pq->value, &pq->lhq.key); - pq->lhq.key_hash = njs_djb_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_int_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_int_t ret; - njs_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 (njs_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 (njs_fast_path(index < NJS_STRING_MAX_LENGTH)) { - ov = (njs_object_value_t *) proto; - ret = njs_string_property_query(vm, pq, &ov->value, index); - - if (njs_fast_path(ret != NJS_DECLINED)) { - return ret; - } - } - - default: - break; - } - } - - ret = njs_lvlhsh_find(&proto->hash, &pq->lhq); - - if (ret == NJS_OK) { - prop = pq->lhq.value; - - if (prop->type != NJS_WHITEOUT) { - return ret; - } - - if (pq->own) { - pq->own_whiteout = prop; - } - - } else { - ret = njs_lvlhsh_find(&proto->shared_hash, &pq->lhq); - - if (ret == NJS_OK) { - pq->shared = 1; - - return ret; - } - } - - if (own) { - return NJS_DECLINED; - } - - pq->own = 0; - proto = proto->__proto__; - - } while (proto != NULL); - - return NJS_DECLINED; -} - - -static njs_int_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_int_t ret; - njs_value_t *value; - njs_object_prop_t *prop; - - if (index >= array->length) { - if (pq->query != NJS_PROPERTY_QUERY_SET) { - return NJS_DECLINED; - } - - size = index - array->length; - - ret = njs_array_expand(vm, array, 0, size + 1); - if (njs_slow_path(ret != NJS_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 NJS_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 NJS_OK; -} - - -static njs_int_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 NJS_OK; - } - - return NJS_DECLINED; -} - - -static njs_int_t -njs_external_property_query(njs_vm_t *vm, njs_property_query_t *pq, - njs_value_t *object) -{ - void *obj; - njs_int_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 = njs_lvlhsh_find(&ext_proto->hash, &pq->lhq); - - if (ret == NJS_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 (njs_slow_path(ret != NJS_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 NJS_OK; - } - -done: - - if (ext_proto->type == NJS_EXTERN_METHOD) { - njs_set_function(&prop->value, ext_proto->function); - } - - pq->lhq.value = prop; - - return ret; -} - - -static njs_int_t -njs_external_property_set(njs_vm_t *vm, njs_value_t *value, njs_value_t *setval, - njs_value_t *retval) -{ - void *obj; - njs_int_t ret; - njs_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_string(vm, &s, setval); - if (njs_slow_path(ret != NJS_OK)) { - return ret; - } - - } else { - s = njs_str_value(""); - } - - *retval = *setval; - - obj = njs_extern_index(vm, pq->ext_index); - - return pq->ext_proto->set(vm, obj, pq->ext_data, &s); -} - - -static njs_int_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]]. - * NJS_OK property has been found in object, - * retval will contain the property's value - * - * NJS_DECLINED property was not found in object - * NJS_ERROR exception has been thrown. - * retval will contain undefined - */ -njs_int_t -njs_value_property(njs_vm_t *vm, njs_value_t *value, njs_value_t *property, - njs_value_t *retval) -{ - njs_int_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, value, property); - - switch (ret) { - - case NJS_OK: - prop = pq.lhq.value; - - switch (prop->type) { - - case NJS_METHOD: - if (pq.shared) { - ret = njs_prop_private_copy(vm, &pq); - - if (njs_slow_path(ret != NJS_OK)) { - return ret; - } - - prop = pq.lhq.value; - } - - /* Fall through. */ - - case NJS_PROPERTY: - if (njs_is_data_descriptor(prop)) { - *retval = prop->value; - break; - } - - if (njs_is_undefined(&prop->getter)) { - *retval = njs_value_undefined; - break; - } - - return njs_function_apply(vm, njs_function(&prop->getter), value, - 1, retval); - - case NJS_PROPERTY_HANDLER: - pq.scratch = *prop; - prop = &pq.scratch; - ret = prop->value.data.u.prop_handler(vm, value, NULL, - &prop->value); - - if (njs_slow_path(ret != NJS_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 NJS_ERROR; - } - - break; - - case NJS_DECLINED: - *retval = njs_value_undefined; - - return NJS_DECLINED; - - case NJS_ERROR: - default: - - return ret; - } - - return NJS_OK; -} - - -/* - * NJS_OK property has been set successfully - * NJS_ERROR exception has been thrown. - */ -njs_int_t -njs_value_property_set(njs_vm_t *vm, njs_value_t *object, - njs_value_t *property, njs_value_t *value) -{ - njs_int_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 NJS_ERROR; - } - - shared = NULL; - - njs_property_query_init(&pq, NJS_PROPERTY_QUERY_SET, 0); - - ret = njs_property_query(vm, &pq, object, property); - - switch (ret) { - - case NJS_OK: - prop = pq.lhq.value; - - if (njs_is_data_descriptor(prop)) { - if (!prop->writable) { - njs_type_error(vm, - "Cannot assign to read-only property \"%V\" of %s", - &pq.lhq.key, njs_type_string(object->type)); - return NJS_ERROR; - } - - } else { - if (njs_is_function(&prop->setter)) { - return njs_function_call(vm, njs_function(&prop->setter), - object, value, 1, &vm->retval); - } - - njs_type_error(vm, - "Cannot set property \"%V\" of %s which has only a getter", - &pq.lhq.key, njs_type_string(object->type)); - return NJS_ERROR; - } - - if (prop->type == NJS_PROPERTY_HANDLER) { - ret = prop->value.data.u.prop_handler(vm, object, value, - &vm->retval); - if (ret != NJS_DECLINED) { - return ret; - } - } - - if (pq.own) { - switch (prop->type) { - case NJS_PROPERTY: - case NJS_METHOD: - if (njs_slow_path(pq.shared)) { - shared = prop; - break; - } - - goto found; - case NJS_PROPERTY_REF: - *prop->value.data.u.value = *value; - return NJS_OK; - - default: - njs_internal_error(vm, "unexpected property type \"%s\" " - "while setting", - njs_prop_type_string(prop->type)); - - return NJS_ERROR; - } - - break; - } - - /* Fall through. */ - - case NJS_DECLINED: - if (njs_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_ERROR: - default: - - return ret; - } - - if (njs_slow_path(!njs_object(object)->extensible)) { - njs_type_error(vm, "Cannot add property \"%V\", " - "object is not extensible", &pq.lhq.key); - return NJS_ERROR; - } - - prop = njs_object_prop_alloc(vm, &pq.value, &njs_value_undefined, 1); - if (njs_slow_path(prop == NULL)) { - return NJS_ERROR; - } - - if (njs_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 = njs_lvlhsh_insert(njs_object_hash(object), &pq.lhq); - if (njs_slow_path(ret != NJS_OK)) { - njs_internal_error(vm, "lvlhsh insert failed"); - return NJS_ERROR; - } - -found: +#include - prop->value = *value; - return NJS_OK; -} +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 * diff --git a/src/njs_value.c b/src/njs_value.c index 371ea9d3..82b960a7 100644 --- a/src/njs_value.c +++ b/src/njs_value.c @@ -8,6 +8,21 @@ #include +static njs_int_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_int_t njs_array_property_query(njs_vm_t *vm, + njs_property_query_t *pq, njs_array_t *array, uint32_t index); +static njs_int_t njs_string_property_query(njs_vm_t *vm, + njs_property_query_t *pq, njs_value_t *object, uint32_t index); +static njs_int_t njs_external_property_query(njs_vm_t *vm, + njs_property_query_t *pq, njs_value_t *object); +static njs_int_t njs_external_property_set(njs_vm_t *vm, njs_value_t *value, + njs_value_t *setval, njs_value_t *retval); +static njs_int_t njs_external_property_delete(njs_vm_t *vm, njs_value_t *value, + njs_value_t *setval, njs_value_t *retval); + + const njs_value_t njs_value_null = njs_value(NJS_NULL, 0, 0.0); const njs_value_t njs_value_undefined = njs_value(NJS_UNDEFINED, 0, NAN); const njs_value_t njs_value_false = njs_value(NJS_BOOLEAN, 0, 0.0); @@ -452,3 +467,678 @@ njs_value_is_function(const njs_value_t *value) } +/* + * ES5.1, 8.12.1: [[GetOwnProperty]], [[GetProperty]]. + * The njs_property_query() returns values + * NJS_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. + * NJS_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_ERROR exception has been thrown. + * + * TODO: + * Object.defineProperty([1,2], '1', {configurable:false}) + */ + +njs_int_t +njs_property_query(njs_vm_t *vm, njs_property_query_t *pq, njs_value_t *object, + njs_value_t *property) +{ + uint32_t index; + njs_int_t ret; + njs_object_t *obj; + njs_value_t prop; + njs_function_t *function; + + if (njs_slow_path(!njs_is_primitive(property))) { + ret = njs_value_to_string(vm, &prop, property); + if (ret != NJS_OK) { + return ret; + } + + property = ∝ + } + + 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 (njs_fast_path(!njs_is_null_or_undefined_or_boolean(property))) { + index = njs_value_to_index(property); + + if (njs_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 = njs_object(object); + break; + + case NJS_FUNCTION: + function = njs_function_value_copy(vm, object); + if (njs_slow_path(function == NULL)) { + return NJS_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 (njs_fast_path(ret == NJS_OK)) { + njs_string_get(&pq->value, &pq->lhq.key); + njs_type_error(vm, "cannot get property \"%V\" of undefined", + &pq->lhq.key); + return NJS_ERROR; + } + + njs_type_error(vm, "cannot get property \"unknown\" of undefined"); + + return NJS_ERROR; + } + + ret = njs_primitive_value_to_string(vm, &pq->value, property); + + if (njs_fast_path(ret == NJS_OK)) { + + njs_string_get(&pq->value, &pq->lhq.key); + pq->lhq.key_hash = njs_djb_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_int_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_int_t ret; + njs_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 (njs_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 (njs_fast_path(index < NJS_STRING_MAX_LENGTH)) { + ov = (njs_object_value_t *) proto; + ret = njs_string_property_query(vm, pq, &ov->value, index); + + if (njs_fast_path(ret != NJS_DECLINED)) { + return ret; + } + } + + default: + break; + } + } + + ret = njs_lvlhsh_find(&proto->hash, &pq->lhq); + + if (ret == NJS_OK) { + prop = pq->lhq.value; + + if (prop->type != NJS_WHITEOUT) { + return ret; + } + + if (pq->own) { + pq->own_whiteout = prop; + } + + } else { + ret = njs_lvlhsh_find(&proto->shared_hash, &pq->lhq); + + if (ret == NJS_OK) { + pq->shared = 1; + + return ret; + } + } + + if (own) { + return NJS_DECLINED; + } + + pq->own = 0; + proto = proto->__proto__; + + } while (proto != NULL); + + return NJS_DECLINED; +} + + +static njs_int_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_int_t ret; + njs_value_t *value; + njs_object_prop_t *prop; + + if (index >= array->length) { + if (pq->query != NJS_PROPERTY_QUERY_SET) { + return NJS_DECLINED; + } + + size = index - array->length; + + ret = njs_array_expand(vm, array, 0, size + 1); + if (njs_slow_path(ret != NJS_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 NJS_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 NJS_OK; +} + + +static njs_int_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 NJS_OK; + } + + return NJS_DECLINED; +} + + +static njs_int_t +njs_external_property_query(njs_vm_t *vm, njs_property_query_t *pq, + njs_value_t *object) +{ + void *obj; + njs_int_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 = njs_lvlhsh_find(&ext_proto->hash, &pq->lhq); + + if (ret == NJS_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 (njs_slow_path(ret != NJS_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 NJS_OK; + } + +done: + + if (ext_proto->type == NJS_EXTERN_METHOD) { + njs_set_function(&prop->value, ext_proto->function); + } + + pq->lhq.value = prop; + + return ret; +} + + +static njs_int_t +njs_external_property_set(njs_vm_t *vm, njs_value_t *value, njs_value_t *setval, + njs_value_t *retval) +{ + void *obj; + njs_int_t ret; + njs_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_string(vm, &s, setval); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + } else { + s = njs_str_value(""); + } + + *retval = *setval; + + obj = njs_extern_index(vm, pq->ext_index); + + return pq->ext_proto->set(vm, obj, pq->ext_data, &s); +} + + +static njs_int_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]]. + * NJS_OK property has been found in object, + * retval will contain the property's value + * + * NJS_DECLINED property was not found in object + * NJS_ERROR exception has been thrown. + * retval will contain undefined + */ +njs_int_t +njs_value_property(njs_vm_t *vm, njs_value_t *value, njs_value_t *property, + njs_value_t *retval) +{ + njs_int_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, value, property); + + switch (ret) { + + case NJS_OK: + prop = pq.lhq.value; + + switch (prop->type) { + + case NJS_METHOD: + if (pq.shared) { + ret = njs_prop_private_copy(vm, &pq); + + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + prop = pq.lhq.value; + } + + /* Fall through. */ + + case NJS_PROPERTY: + if (njs_is_data_descriptor(prop)) { + *retval = prop->value; + break; + } + + if (njs_is_undefined(&prop->getter)) { + *retval = njs_value_undefined; + break; + } + + return njs_function_apply(vm, njs_function(&prop->getter), value, + 1, retval); + + case NJS_PROPERTY_HANDLER: + pq.scratch = *prop; + prop = &pq.scratch; + ret = prop->value.data.u.prop_handler(vm, value, NULL, + &prop->value); + + if (njs_slow_path(ret != NJS_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 NJS_ERROR; + } + + break; + + case NJS_DECLINED: + *retval = njs_value_undefined; + + return NJS_DECLINED; + + case NJS_ERROR: + default: + + return ret; + } + + return NJS_OK; +} + + +/* + * NJS_OK property has been set successfully + * NJS_ERROR exception has been thrown. + */ +njs_int_t +njs_value_property_set(njs_vm_t *vm, njs_value_t *object, + njs_value_t *property, njs_value_t *value) +{ + njs_int_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 NJS_ERROR; + } + + shared = NULL; + + njs_property_query_init(&pq, NJS_PROPERTY_QUERY_SET, 0); + + ret = njs_property_query(vm, &pq, object, property); + + switch (ret) { + + case NJS_OK: + prop = pq.lhq.value; + + if (njs_is_data_descriptor(prop)) { + if (!prop->writable) { + njs_type_error(vm, + "Cannot assign to read-only property \"%V\" of %s", + &pq.lhq.key, njs_type_string(object->type)); + return NJS_ERROR; + } + + } else { + if (njs_is_function(&prop->setter)) { + return njs_function_call(vm, njs_function(&prop->setter), + object, value, 1, &vm->retval); + } + + njs_type_error(vm, + "Cannot set property \"%V\" of %s which has only a getter", + &pq.lhq.key, njs_type_string(object->type)); + return NJS_ERROR; + } + + if (prop->type == NJS_PROPERTY_HANDLER) { + ret = prop->value.data.u.prop_handler(vm, object, value, + &vm->retval); + if (ret != NJS_DECLINED) { + return ret; + } + } + + if (pq.own) { + switch (prop->type) { + case NJS_PROPERTY: + case NJS_METHOD: + if (njs_slow_path(pq.shared)) { + shared = prop; + break; + } + + goto found; + + case NJS_PROPERTY_REF: + *prop->value.data.u.value = *value; + return NJS_OK; + + default: + njs_internal_error(vm, "unexpected property type \"%s\" " + "while setting", + njs_prop_type_string(prop->type)); + + return NJS_ERROR; + } + + break; + } + + /* Fall through. */ + + case NJS_DECLINED: + if (njs_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_ERROR: + default: + + return ret; + } + + if (njs_slow_path(!njs_object(object)->extensible)) { + njs_type_error(vm, "Cannot add property \"%V\", " + "object is not extensible", &pq.lhq.key); + return NJS_ERROR; + } + + prop = njs_object_prop_alloc(vm, &pq.value, &njs_value_undefined, 1); + if (njs_slow_path(prop == NULL)) { + return NJS_ERROR; + } + + if (njs_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 = njs_lvlhsh_insert(njs_object_hash(object), &pq.lhq); + if (njs_slow_path(ret != NJS_OK)) { + njs_internal_error(vm, "lvlhsh insert failed"); + return NJS_ERROR; + } + +found: + + prop->value = *value; + + return NJS_OK; +} diff --git a/src/njs_value.h b/src/njs_value.h index 0fb1ca45..20c99336 100644 --- a/src/njs_value.h +++ b/src/njs_value.h @@ -308,6 +308,65 @@ typedef enum { } njs_object_enum_t; +typedef enum { + NJS_PROPERTY = 0, + NJS_PROPERTY_REF, + NJS_METHOD, + NJS_PROPERTY_HANDLER, + NJS_WHITEOUT, +} njs_object_prop_type_t; + + +/* + * Attributes are generally used as Boolean values. + * The UNSET value is can be seen: + * for newly created property descriptors in njs_define_property(), + * for writable attribute of accessor descriptors (desc->writable + * cannot be used as a boolean value). + */ +typedef enum { + NJS_ATTRIBUTE_FALSE = 0, + NJS_ATTRIBUTE_TRUE = 1, + NJS_ATTRIBUTE_UNSET, +} njs_object_attribute_t; + + +typedef struct { + /* Must be aligned to njs_value_t. */ + njs_value_t value; + njs_value_t name; + njs_value_t getter; + njs_value_t setter; + + /* TODO: get rid of types */ + njs_object_prop_type_t type:8; /* 3 bits */ + + njs_object_attribute_t writable:8; /* 2 bits */ + njs_object_attribute_t enumerable:8; /* 2 bits */ + njs_object_attribute_t configurable:8; /* 2 bits */ +} njs_object_prop_t; + + +typedef struct { + njs_lvlhsh_query_t lhq; + + /* 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; + njs_object_prop_t *own_whiteout; + uint8_t query; + uint8_t shared; + uint8_t own; +} njs_property_query_t; + + #define njs_value(_type, _truth, _number) { \ .data = { \ .type = _type, \ @@ -740,6 +799,17 @@ njs_set_object_value(njs_value_t *value, njs_object_value_t *object_value) #endif +#define njs_property_query_init(pq, _query, _own) \ + do { \ + (pq)->lhq.key.length = 0; \ + (pq)->lhq.value = NULL; \ + (pq)->own_whiteout = NULL; \ + (pq)->query = _query; \ + (pq)->shared = 0; \ + (pq)->own = _own; \ + } while (0) + + void njs_value_retain(njs_value_t *value); void njs_value_release(njs_vm_t *vm, njs_value_t *value); njs_int_t njs_value_to_primitive(njs_vm_t *vm, njs_value_t *dst, @@ -757,6 +827,13 @@ double njs_string_to_number(const njs_value_t *value, njs_bool_t parse_float); njs_bool_t njs_string_eq(const njs_value_t *v1, const njs_value_t *v2); +njs_int_t njs_property_query(njs_vm_t *vm, njs_property_query_t *pq, + njs_value_t *object, njs_value_t *property); +njs_int_t njs_value_property(njs_vm_t *vm, njs_value_t *value, + njs_value_t *property, njs_value_t *retval); +njs_int_t njs_value_property_set(njs_vm_t *vm, njs_value_t *object, + njs_value_t *property, njs_value_t *value); + njs_inline njs_int_t njs_value_to_numeric(njs_vm_t *vm, njs_value_t *dst, njs_value_t *value)