blob: 74e63fc111d41ea61a6cef1ac06703831c728eae [file] [log] [blame]
/*
* Copyright (C) Igor Sysoev
* Copyright (C) NGINX, Inc.
*/
#include <njs_main.h>
static njs_int_t njs_descriptor_prop(njs_vm_t *vm,
njs_object_prop_t *prop, const njs_value_t *desc);
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 = njs_mp_align(vm->mem_pool, sizeof(njs_value_t),
sizeof(njs_object_prop_t));
if (njs_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;
njs_set_invalid(&prop->getter);
njs_set_invalid(&prop->setter);
return prop;
}
njs_memory_error(vm);
return NULL;
}
njs_int_t
njs_object_property(njs_vm_t *vm, const njs_value_t *value,
njs_lvlhsh_query_t *lhq, njs_value_t *retval)
{
njs_int_t ret;
njs_object_t *object;
njs_object_prop_t *prop;
object = njs_object(value);
do {
ret = njs_lvlhsh_find(&object->hash, lhq);
if (njs_fast_path(ret == NJS_OK)) {
goto found;
}
ret = njs_lvlhsh_find(&object->shared_hash, lhq);
if (njs_fast_path(ret == NJS_OK)) {
goto found;
}
object = object->__proto__;
} while (object != NULL);
njs_set_undefined(retval);
return NJS_DECLINED;
found:
prop = lhq->value;
if (njs_is_data_descriptor(prop)) {
*retval = prop->value;
return NJS_OK;
}
if (njs_is_undefined(&prop->getter)) {
njs_set_undefined(retval);
return NJS_OK;
}
return njs_function_apply(vm, njs_function(&prop->getter), value,
1, retval);
}
njs_object_prop_t *
njs_object_property_add(njs_vm_t *vm, njs_value_t *object, njs_value_t *key,
njs_bool_t replace)
{
njs_int_t ret;
njs_value_t key_value;
njs_object_prop_t *prop;
njs_lvlhsh_query_t lhq;
prop = njs_object_prop_alloc(vm, key, &njs_value_invalid, 1);
if (njs_slow_path(prop == NULL)) {
return NULL;
}
ret = njs_value_to_key(vm, &key_value, key);
if (njs_slow_path(ret != NJS_OK)) {
return NULL;
}
lhq.proto = &njs_object_hash_proto;
njs_string_get(&key_value, &lhq.key);
lhq.key_hash = njs_djb_hash(lhq.key.start, lhq.key.length);
lhq.value = prop;
lhq.replace = replace;
lhq.pool = vm->mem_pool;
ret = njs_lvlhsh_insert(njs_object_hash(object), &lhq);
if (njs_slow_path(ret != NJS_OK)) {
njs_internal_error(vm, "lvlhsh insert failed");
return NULL;
}
return prop;
}
/*
* ES5.1, 8.12.9: [[DefineOwnProperty]]
*/
njs_int_t
njs_object_prop_define(njs_vm_t *vm, njs_value_t *object,
njs_value_t *name, njs_value_t *value, njs_object_prop_define_t type)
{
uint32_t length;
njs_int_t ret;
njs_array_t *array;
njs_object_prop_t *prop, *prev;
njs_property_query_t pq;
static const njs_str_t length_key = njs_str("length");
if (njs_slow_path(!njs_is_key(name))) {
ret = njs_value_to_key(vm, name, name);
if (njs_slow_path(ret != NJS_OK)) {
return ret;
}
}
again:
njs_property_query_init(&pq, NJS_PROPERTY_QUERY_SET, 1);
ret = njs_property_query(vm, &pq, object, name);
if (njs_slow_path(ret == NJS_ERROR)) {
return ret;
}
prop = njs_object_prop_alloc(vm, name, &njs_value_invalid,
NJS_ATTRIBUTE_UNSET);
if (njs_slow_path(prop == NULL)) {
return NJS_ERROR;
}
switch (type) {
case NJS_OBJECT_PROP_DESCRIPTOR:
if (njs_descriptor_prop(vm, prop, value) != NJS_OK) {
return NJS_ERROR;
}
break;
case NJS_OBJECT_PROP_GETTER:
prop->getter = *value;
njs_set_invalid(&prop->setter);
prop->enumerable = NJS_ATTRIBUTE_TRUE;
prop->configurable = NJS_ATTRIBUTE_TRUE;
break;
case NJS_OBJECT_PROP_SETTER:
prop->setter = *value;
njs_set_invalid(&prop->getter);
prop->enumerable = NJS_ATTRIBUTE_TRUE;
prop->configurable = NJS_ATTRIBUTE_TRUE;
break;
}
if (njs_fast_path(ret == NJS_DECLINED)) {
set_prop:
if (!njs_object(object)->extensible) {
njs_key_string_get(vm, &pq.key, &pq.lhq.key);
njs_type_error(vm, "Cannot add property \"%V\", "
"object is not extensible", &pq.lhq.key);
return NJS_ERROR;
}
if (njs_slow_path(njs_is_typed_array(object)
&& njs_is_string(name)))
{
/* Integer-Indexed Exotic Objects [[DefineOwnProperty]]. */
if (!isnan(njs_string_to_index(name))) {
njs_type_error(vm, "Invalid typed array index");
return NJS_ERROR;
}
}
/* 6.2.5.6 CompletePropertyDescriptor */
if (njs_is_accessor_descriptor(prop)) {
if (!njs_is_valid(&prop->getter)) {
njs_set_undefined(&prop->getter);
}
if (!njs_is_valid(&prop->setter)) {
njs_set_undefined(&prop->setter);
}
} else {
if (prop->writable == NJS_ATTRIBUTE_UNSET) {
prop->writable = 0;
}
if (!njs_is_valid(&prop->value)) {
njs_set_undefined(&prop->value);
}
}
if (prop->enumerable == NJS_ATTRIBUTE_UNSET) {
prop->enumerable = 0;
}
if (prop->configurable == NJS_ATTRIBUTE_UNSET) {
prop->configurable = 0;
}
if (njs_slow_path(pq.lhq.value != NULL)) {
prev = pq.lhq.value;
if (njs_slow_path(prev->type == NJS_WHITEOUT)) {
/* Previously deleted property. */
*prev = *prop;
}
} else {
pq.lhq.value = prop;
pq.lhq.replace = 0;
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;
}
}
return NJS_OK;
}
/* Updating existing prop. */
prev = pq.lhq.value;
switch (prev->type) {
case NJS_PROPERTY:
case NJS_PROPERTY_HANDLER:
break;
case NJS_PROPERTY_REF:
if (njs_is_accessor_descriptor(prop)
|| prop->configurable == NJS_ATTRIBUTE_FALSE
|| prop->enumerable == NJS_ATTRIBUTE_FALSE
|| prop->writable == NJS_ATTRIBUTE_FALSE)
{
array = njs_array(object);
length = array->length;
ret = njs_array_convert_to_slow_array(vm, array);
if (njs_slow_path(ret != NJS_OK)) {
return ret;
}
ret = njs_array_length_redefine(vm, object, length);
if (njs_slow_path(ret != NJS_OK)) {
return ret;
}
goto again;
}
if (njs_is_valid(&prop->value)) {
*prev->value.data.u.value = prop->value;
} else {
njs_set_undefined(prev->value.data.u.value);
}
return NJS_OK;
case NJS_PROPERTY_TYPED_ARRAY_REF:
if (njs_is_accessor_descriptor(prop)) {
goto exception;
}
if (prop->configurable == NJS_ATTRIBUTE_TRUE ||
prop->enumerable == NJS_ATTRIBUTE_FALSE ||
prop->writable == NJS_ATTRIBUTE_FALSE)
{
goto exception;
}
if (njs_is_valid(&prop->value)) {
return njs_typed_array_set_value(vm, njs_typed_array(&prev->value),
prev->value.data.magic32,
&prop->value);
}
return NJS_OK;
default:
njs_internal_error(vm, "unexpected property type \"%s\" "
"while defining property",
njs_prop_type_string(prev->type));
return NJS_ERROR;
}
/* 9.1.6.3 ValidateAndApplyPropertyDescriptor */
if (!prev->configurable) {
if (prop->configurable == NJS_ATTRIBUTE_TRUE) {
goto exception;
}
if (prop->enumerable != NJS_ATTRIBUTE_UNSET
&& prev->enumerable != prop->enumerable)
{
goto exception;
}
}
if (njs_is_generic_descriptor(prop)) {
goto done;
}
if (njs_is_data_descriptor(prev) != njs_is_data_descriptor(prop)) {
if (!prev->configurable) {
goto exception;
}
/*
* 6.b-c Preserve the existing values of the converted property's
* [[Configurable]] and [[Enumerable]] attributes and set the rest of
* the property's attributes to their default values.
*/
if (pq.temp) {
pq.lhq.value = NULL;
prop->configurable = prev->configurable;
prop->enumerable = prev->enumerable;
goto set_prop;
}
prev->type = prop->type;
if (njs_is_data_descriptor(prev)) {
njs_set_undefined(&prev->getter);
njs_set_undefined(&prev->setter);
njs_set_invalid(&prev->value);
prev->writable = NJS_ATTRIBUTE_UNSET;
} else {
njs_set_undefined(&prev->value);
prev->writable = NJS_ATTRIBUTE_FALSE;
njs_set_invalid(&prev->getter);
njs_set_invalid(&prev->setter);
}
} else if (njs_is_data_descriptor(prev)
&& njs_is_data_descriptor(prop))
{
if (!prev->configurable && !prev->writable) {
if (prop->writable == NJS_ATTRIBUTE_TRUE) {
goto exception;
}
if (njs_is_valid(&prop->value)
&& prev->type != NJS_PROPERTY_HANDLER
&& !njs_values_same(&prop->value, &prev->value))
{
goto exception;
}
}
} else {
if (!prev->configurable) {
if (njs_is_valid(&prop->getter)
&& !njs_values_strict_equal(&prop->getter, &prev->getter))
{
goto exception;
}
if (njs_is_valid(&prop->setter)
&& !njs_values_strict_equal(&prop->setter, &prev->setter))
{
goto exception;
}
}
}
done:
if (njs_is_valid(&prop->value)) {
if (prev->type == NJS_PROPERTY_HANDLER) {
if (prev->writable) {
ret = prev->value.data.u.prop_handler(vm, prev, object,
&prop->value,
&vm->retval);
if (njs_slow_path(ret == NJS_ERROR)) {
return ret;
}
if (ret == NJS_DECLINED) {
pq.lhq.value = NULL;
goto set_prop;
}
}
} else {
if (njs_slow_path(pq.lhq.key_hash == NJS_LENGTH_HASH)) {
if (njs_strstr_eq(&pq.lhq.key, &length_key)) {
ret = njs_array_length_set(vm, object, prev, &prop->value);
if (ret != NJS_DECLINED) {
return ret;
}
}
}
prev->value = prop->value;
}
}
/*
* 9. For each field of Desc that is present, set the corresponding
* attribute of the property named P of object O to the value of the field.
*/
if (njs_is_valid(&prop->getter)) {
prev->getter = prop->getter;
}
if (njs_is_valid(&prop->setter)) {
prev->setter = prop->setter;
}
if (prop->writable != NJS_ATTRIBUTE_UNSET) {
prev->writable = prop->writable;
}
if (prop->enumerable != NJS_ATTRIBUTE_UNSET) {
prev->enumerable = prop->enumerable;
}
if (prop->configurable != NJS_ATTRIBUTE_UNSET) {
prev->configurable = prop->configurable;
}
return NJS_OK;
exception:
njs_key_string_get(vm, &pq.key, &pq.lhq.key);
njs_type_error(vm, "Cannot redefine property: \"%V\"", &pq.lhq.key);
return NJS_ERROR;
}
njs_int_t
njs_prop_private_copy(njs_vm_t *vm, njs_property_query_t *pq)
{
njs_int_t ret;
njs_value_t *value;
njs_object_t *object;
njs_function_t *function;
njs_object_prop_t *prop, *shared;
prop = njs_mp_align(vm->mem_pool, sizeof(njs_value_t),
sizeof(njs_object_prop_t));
if (njs_slow_path(prop == NULL)) {
njs_memory_error(vm);
return NJS_ERROR;
}
shared = pq->lhq.value;
*prop = *shared;
pq->lhq.replace = 0;
pq->lhq.value = prop;
pq->lhq.pool = vm->mem_pool;
ret = njs_lvlhsh_insert(&pq->prototype->hash, &pq->lhq);
if (njs_slow_path(ret != NJS_OK)) {
njs_internal_error(vm, "lvlhsh insert failed");
return NJS_ERROR;
}
if (njs_is_accessor_descriptor(prop)) {
if (njs_is_function(&prop->getter)) {
function = njs_function_value_copy(vm, &prop->getter);
if (njs_slow_path(function == NULL)) {
return NJS_ERROR;
}
if (njs_is_function(&prop->setter)
&& function->native && njs_function(&prop->setter)->native
&& function->u.native == njs_function(&prop->setter)->u.native)
{
prop->setter = prop->getter;
return NJS_OK;
}
}
if (njs_is_function(&prop->setter)) {
function = njs_function_value_copy(vm, &prop->setter);
if (njs_slow_path(function == NULL)) {
return NJS_ERROR;
}
}
return NJS_OK;
}
value = &prop->value;
switch (value->type) {
case NJS_OBJECT:
case NJS_OBJECT_VALUE:
object = njs_object_value_copy(vm, value);
if (njs_slow_path(object == NULL)) {
return NJS_ERROR;
}
value->data.u.object = object;
return NJS_OK;
case NJS_FUNCTION:
function = njs_function_value_copy(vm, &prop->value);
if (njs_slow_path(function == NULL)) {
return NJS_ERROR;
}
return njs_function_name_set(vm, function, &prop->name, NULL);
default:
break;
}
return NJS_OK;
}
static njs_int_t
njs_descriptor_prop(njs_vm_t *vm, njs_object_prop_t *prop,
const njs_value_t *desc)
{
njs_int_t ret;
njs_bool_t data, accessor;
njs_value_t value;
njs_lvlhsh_query_t lhq;
static const njs_value_t get_string = njs_string("get");
if (!njs_is_object(desc)) {
njs_type_error(vm, "property descriptor must be an object");
return NJS_ERROR;
}
data = 0;
accessor = 0;
njs_object_property_init(&lhq, &get_string, NJS_GET_HASH);
ret = njs_object_property(vm, desc, &lhq, &value);
if (njs_slow_path(ret == NJS_ERROR)) {
return NJS_ERROR;
}
if (ret == NJS_OK) {
if (njs_is_defined(&value) && !njs_is_function(&value)) {
njs_type_error(vm, "Getter must be a function");
return NJS_ERROR;
}
accessor = 1;
prop->getter = value;
} else {
/* NJS_DECLINED */
njs_set_invalid(&prop->getter);
}
lhq.key = njs_str_value("set");
lhq.key_hash = NJS_SET_HASH;
ret = njs_object_property(vm, desc, &lhq, &value);
if (njs_slow_path(ret == NJS_ERROR)) {
return ret;
}
if (ret == NJS_OK) {
if (njs_is_defined(&value) && !njs_is_function(&value)) {
njs_type_error(vm, "Setter must be a function");
return NJS_ERROR;
}
accessor = 1;
prop->setter = value;
} else {
/* NJS_DECLINED */
njs_set_invalid(&prop->setter);
}
lhq.key = njs_str_value("value");
lhq.key_hash = NJS_VALUE_HASH;
ret = njs_object_property(vm, desc, &lhq, &value);
if (njs_slow_path(ret == NJS_ERROR)) {
return ret;
}
if (ret == NJS_OK) {
data = 1;
prop->value = value;
}
lhq.key = njs_str_value("writable");
lhq.key_hash = NJS_WRITABABLE_HASH;
ret = njs_object_property(vm, desc, &lhq, &value);
if (njs_slow_path(ret == NJS_ERROR)) {
return ret;
}
if (ret == NJS_OK) {
data = 1;
prop->writable = njs_is_true(&value);
}
lhq.key = njs_str_value("enumerable");
lhq.key_hash = NJS_ENUMERABLE_HASH;
ret = njs_object_property(vm, desc, &lhq, &value);
if (njs_slow_path(ret == NJS_ERROR)) {
return ret;
}
if (ret == NJS_OK) {
prop->enumerable = njs_is_true(&value);
}
lhq.key = njs_str_value("configurable");
lhq.key_hash = NJS_CONFIGURABLE_HASH;
ret = njs_object_property(vm, desc, &lhq, &value);
if (njs_slow_path(ret == NJS_ERROR)) {
return ret;
}
if (ret == NJS_OK) {
prop->configurable = njs_is_true(&value);
}
if (accessor && data) {
njs_type_error(vm, "Cannot both specify accessors "
"and a value or writable attribute");
return NJS_ERROR;
}
return NJS_OK;
}
static const njs_value_t njs_object_value_string = njs_string("value");
static const njs_value_t njs_object_get_string = njs_string("get");
static const njs_value_t njs_object_set_string = njs_string("set");
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_int_t
njs_object_prop_descriptor(njs_vm_t *vm, njs_value_t *dest,
njs_value_t *value, njs_value_t *key)
{
njs_int_t ret;
njs_object_t *desc;
njs_object_prop_t *pr, *prop;
const njs_value_t *setval;
njs_lvlhsh_query_t lhq;
njs_property_query_t pq;
njs_property_query_init(&pq, NJS_PROPERTY_QUERY_GET, 1);
if (njs_slow_path(!njs_is_key(key))) {
ret = njs_value_to_key(vm, key, key);
if (njs_slow_path(ret != NJS_OK)) {
return ret;
}
}
ret = njs_property_query(vm, &pq, value, key);
switch (ret) {
case NJS_OK:
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, prop, value, NULL,
&prop->value);
if (njs_slow_path(ret == NJS_ERROR)) {
return ret;
}
break;
default:
njs_type_error(vm, "unexpected property type: %s",
njs_prop_type_string(prop->type));
return NJS_ERROR;
}
break;
case NJS_DECLINED:
njs_set_undefined(dest);
return NJS_OK;
case NJS_ERROR:
default:
return ret;
}
desc = njs_object_alloc(vm);
if (njs_slow_path(desc == NULL)) {
return NJS_ERROR;
}
lhq.proto = &njs_object_hash_proto;
lhq.replace = 0;
lhq.pool = vm->mem_pool;
if (njs_is_data_descriptor(prop)) {
lhq.key = njs_str_value("value");
lhq.key_hash = NJS_VALUE_HASH;
pr = njs_object_prop_alloc(vm, &njs_object_value_string, &prop->value,
1);
if (njs_slow_path(pr == NULL)) {
return NJS_ERROR;
}
lhq.value = pr;
ret = njs_lvlhsh_insert(&desc->hash, &lhq);
if (njs_slow_path(ret != NJS_OK)) {
njs_internal_error(vm, "lvlhsh insert failed");
return NJS_ERROR;
}
lhq.key = njs_str_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 (njs_slow_path(pr == NULL)) {
return NJS_ERROR;
}
lhq.value = pr;
ret = njs_lvlhsh_insert(&desc->hash, &lhq);
if (njs_slow_path(ret != NJS_OK)) {
njs_internal_error(vm, "lvlhsh insert failed");
return NJS_ERROR;
}
} else {
lhq.key = njs_str_value("get");
lhq.key_hash = NJS_GET_HASH;
pr = njs_object_prop_alloc(vm, &njs_object_get_string, &prop->getter,
1);
if (njs_slow_path(pr == NULL)) {
return NJS_ERROR;
}
lhq.value = pr;
ret = njs_lvlhsh_insert(&desc->hash, &lhq);
if (njs_slow_path(ret != NJS_OK)) {
njs_internal_error(vm, "lvlhsh insert failed");
return NJS_ERROR;
}
lhq.key = njs_str_value("set");
lhq.key_hash = NJS_SET_HASH;
pr = njs_object_prop_alloc(vm, &njs_object_set_string, &prop->setter,
1);
if (njs_slow_path(pr == NULL)) {
return NJS_ERROR;
}
lhq.value = pr;
ret = njs_lvlhsh_insert(&desc->hash, &lhq);
if (njs_slow_path(ret != NJS_OK)) {
njs_internal_error(vm, "lvlhsh insert failed");
return NJS_ERROR;
}
}
lhq.key = njs_str_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 (njs_slow_path(pr == NULL)) {
return NJS_ERROR;
}
lhq.value = pr;
ret = njs_lvlhsh_insert(&desc->hash, &lhq);
if (njs_slow_path(ret != NJS_OK)) {
njs_internal_error(vm, "lvlhsh insert failed");
return NJS_ERROR;
}
lhq.key = njs_str_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 (njs_slow_path(pr == NULL)) {
return NJS_ERROR;
}
lhq.value = pr;
ret = njs_lvlhsh_insert(&desc->hash, &lhq);
if (njs_slow_path(ret != NJS_OK)) {
njs_internal_error(vm, "lvlhsh insert failed");
return NJS_ERROR;
}
njs_set_object(dest, desc);
return NJS_OK;
}
const char *
njs_prop_type_string(njs_object_prop_type_t type)
{
switch (type) {
case NJS_PROPERTY_REF:
return "property_ref";
case NJS_PROPERTY_HANDLER:
return "property handler";
case NJS_WHITEOUT:
return "whiteout";
case NJS_PROPERTY:
return "property";
default:
return "unknown";
}
}
njs_int_t
njs_object_prop_init(njs_vm_t *vm, const njs_object_init_t* init,
const njs_object_prop_t *base, njs_value_t *value, njs_value_t *retval)
{
njs_int_t ret;
njs_object_t *object;
njs_object_prop_t *prop;
njs_lvlhsh_query_t lhq;
object = njs_object_alloc(vm);
if (object == NULL) {
return NJS_ERROR;
}
ret = njs_object_hash_create(vm, &object->hash, init->properties,
init->items);
if (njs_slow_path(ret != NJS_OK)) {
return NJS_ERROR;
}
prop = njs_mp_align(vm->mem_pool, sizeof(njs_value_t),
sizeof(njs_object_prop_t));
if (njs_slow_path(prop == NULL)) {
njs_memory_error(vm);
return NJS_ERROR;
}
*prop = *base;
prop->type = NJS_PROPERTY;
njs_set_object(&prop->value, object);
lhq.proto = &njs_object_hash_proto;
njs_string_get(&prop->name, &lhq.key);
lhq.key_hash = njs_djb_hash(lhq.key.start, lhq.key.length);
lhq.value = prop;
lhq.replace = 1;
lhq.pool = vm->mem_pool;
ret = njs_lvlhsh_insert(njs_object_hash(value), &lhq);
if (njs_fast_path(ret == NJS_OK)) {
*retval = prop->value;
return NJS_OK;
}
njs_internal_error(vm, "lvlhsh insert failed");
return NJS_ERROR;
}