
/*
 * Copyright (C) Igor Sysoev
 * Copyright (C) NGINX, Inc.
 */


#include <njs_main.h>


static njs_object_prop_t *njs_object_prop_alloc2(njs_vm_t *vm,
    njs_object_prop_type_t type, unsigned flags);
static njs_object_prop_t *njs_descriptor_prop(njs_vm_t *vm,
    const njs_value_t *desc);


njs_object_prop_t *
njs_object_prop_alloc(njs_vm_t *vm,
    const njs_value_t *value, uint8_t attributes)
{
    unsigned           flags;
    njs_object_prop_t  *prop;

    switch (attributes) {
    case NJS_ATTRIBUTE_FALSE:
    case NJS_ATTRIBUTE_TRUE:
        flags = attributes ? NJS_OBJECT_PROP_VALUE_ECW : 0;
        break;

    case NJS_ATTRIBUTE_UNSET:
    default:
        flags = NJS_OBJECT_PROP_UNSET;
        break;
    }

    prop = njs_object_prop_alloc2(vm, NJS_PROPERTY, flags);
    if (njs_slow_path(prop == NULL)) {
        return NULL;
    }

    njs_value_assign(njs_prop_value(prop), value);

    return prop;
}


static njs_object_prop_t *
njs_object_prop_alloc2(njs_vm_t *vm,
    njs_object_prop_type_t type, unsigned flags)
{
    njs_object_prop_t  *prop;

    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 NULL;
    }

    prop->type = type;
    prop->enum_in_object_hash = 0;

    if (flags != NJS_OBJECT_PROP_UNSET) {
        prop->enumerable = !!(flags & NJS_OBJECT_PROP_ENUMERABLE);
        prop->configurable = !!(flags & NJS_OBJECT_PROP_CONFIGURABLE);

        if (type == NJS_PROPERTY) {
            prop->writable = !!(flags & NJS_OBJECT_PROP_WRITABLE);

        } else {
            prop->writable = NJS_ATTRIBUTE_UNSET;
        }

    } else {
        prop->enumerable = NJS_ATTRIBUTE_UNSET;
        prop->configurable = NJS_ATTRIBUTE_UNSET;
        prop->writable = NJS_ATTRIBUTE_UNSET;
    }

    return prop;
}


njs_int_t
njs_object_property(njs_vm_t *vm, njs_object_t *object,
    njs_flathsh_query_t *lhq, njs_value_t *retval)
{
    njs_int_t          ret;
    njs_value_t        value;
    njs_object_prop_t  *prop;

    do {
        ret = njs_flathsh_unique_find(&object->hash, lhq);

        if (njs_fast_path(ret == NJS_OK)) {
            prop = lhq->value;

            if (prop->type != NJS_WHITEOUT) {
                goto found;
            }
        }

        ret = njs_flathsh_unique_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)) {
        njs_value_assign(retval, njs_prop_value(prop));
        return NJS_OK;
    }

    if (njs_prop_getter(prop) == NULL) {
        njs_set_undefined(retval);
        return NJS_OK;
    }

    njs_set_object(&value, object);

    return njs_function_apply(vm, njs_prop_getter(prop), &value, 1, retval);
}


njs_object_prop_t *
njs_object_property_add(njs_vm_t *vm, njs_value_t *object, unsigned atom_id,
    njs_bool_t replace)
{
    njs_int_t            ret;
    njs_object_prop_t    *prop;
    njs_flathsh_query_t  lhq;

    prop = njs_object_prop_alloc(vm, &njs_value_invalid, 1);
    if (njs_slow_path(prop == NULL)) {
        return NULL;
    }

    lhq.value = prop;
    lhq.key_hash = atom_id;
    lhq.replace = replace;
    lhq.pool = vm->mem_pool;
    lhq.proto = &njs_object_hash_proto;

    ret = njs_flathsh_unique_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, unsigned atom_id,
    njs_value_t *value, unsigned flags)
{
    uint32_t              length, index;
    njs_int_t             ret;
    njs_array_t           *array;
    njs_value_t           key, retval;
    njs_object_prop_t     *prop, *prev;
    njs_property_query_t  pq;

again:

    njs_property_query_init(&pq, NJS_PROPERTY_QUERY_SET, 1);

    ret = (flags & NJS_OBJECT_PROP_CREATE)
                ? NJS_DECLINED
                : njs_property_query(vm, &pq, object, atom_id);

    if (njs_slow_path(ret == NJS_ERROR)) {
        return ret;
    }

    switch (njs_prop_type(flags)) {
    case NJS_OBJECT_PROP_DESCRIPTOR:
        prop = njs_descriptor_prop(vm, value);
        if (njs_slow_path(prop == NULL)) {
            return NJS_ERROR;
        }

        break;

    case NJS_OBJECT_PROP_VALUE:
        if (((flags & NJS_OBJECT_PROP_VALUE_ECW) == NJS_OBJECT_PROP_VALUE_ECW)
            && njs_is_fast_array(object)
            && njs_atom_is_number(atom_id))
        {
            array = njs_array(object);
            index = njs_atom_number(atom_id);

            if (index < array->length) {
                njs_value_assign(&array->start[index], value);
                return NJS_OK;
            }
        }

        prop = njs_object_prop_alloc2(vm, NJS_PROPERTY,
                                      flags & NJS_OBJECT_PROP_VALUE_ECW);
        if (njs_slow_path(prop == NULL)) {
            return NJS_ERROR;
        }

        njs_value_assign(njs_prop_value(prop), value);
        break;

    case NJS_OBJECT_PROP_GETTER:
    case NJS_OBJECT_PROP_SETTER:
    default:
        njs_assert(njs_is_function(value));

        prop = njs_object_prop_alloc2(vm, NJS_ACCESSOR,
                                      NJS_OBJECT_PROP_VALUE_EC);
        if (njs_slow_path(prop == NULL)) {
            return NJS_ERROR;
        }

        if (njs_prop_type(flags) == NJS_OBJECT_PROP_GETTER) {
            njs_prop_getter(prop) = njs_function(value);
            njs_prop_setter(prop) = NJS_PROP_PTR_UNSET;

        } else {
            njs_prop_getter(prop) = NJS_PROP_PTR_UNSET;
            njs_prop_setter(prop) = njs_function(value);
        }

        break;
    }

    if (njs_fast_path(ret == NJS_DECLINED)) {

set_prop:

        if (!njs_object(object)->extensible) {
            njs_atom_string_get(vm, atom_id, &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) &&
           (flags & NJS_OBJECT_PROP_IS_STRING)))
        {
            /* Integer-Indexed Exotic Objects [[DefineOwnProperty]]. */

            ret = njs_atom_to_value(vm, &key, atom_id);
            if (njs_slow_path(ret != NJS_OK)) {
                return ret;
            }

            if (!isnan(njs_string_to_index(&key))) {
                njs_type_error(vm, "Invalid typed array index");
                return NJS_ERROR;
            }
        }

        /* 6.2.5.6 CompletePropertyDescriptor */

        if (njs_is_accessor_descriptor(prop)) {
            if (njs_prop_getter(prop) == NJS_PROP_PTR_UNSET) {
                njs_prop_getter(prop) = NULL;
            }

            if (njs_prop_setter(prop) == NJS_PROP_PTR_UNSET) {
                njs_prop_setter(prop) = NULL;
            }

        } else {
            if (prop->writable == NJS_ATTRIBUTE_UNSET) {
                prop->writable = 0;
            }

            if (!njs_is_valid(njs_prop_value(prop))) {
                njs_set_undefined(njs_prop_value(prop));
            }
        }

        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.key_hash = atom_id;
            pq.lhq.proto = &njs_object_hash_proto;
            pq.lhq.value = prop;
            pq.lhq.replace = 0;
            pq.lhq.pool = vm->mem_pool;

            ret = njs_flathsh_unique_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_ACCESSOR:
    case NJS_PROPERTY_HANDLER:
        break;

    case NJS_PROPERTY_REF:
    case NJS_PROPERTY_PLACE_REF:
        if (prev->type == NJS_PROPERTY_REF
            && !njs_is_accessor_descriptor(prop)
            && prop->configurable != NJS_ATTRIBUTE_FALSE
            && prop->enumerable != NJS_ATTRIBUTE_FALSE
            && prop->writable != NJS_ATTRIBUTE_FALSE)
        {
            if (njs_is_valid(njs_prop_value(prop))) {
                njs_value_assign(njs_prop_ref(prev), njs_prop_value(prop));
            }

            return NJS_OK;
        }

        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, 1);
        if (njs_slow_path(ret != NJS_OK)) {
            return ret;
        }

        flags &= ~NJS_OBJECT_PROP_CREATE;

        goto again;

    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(njs_prop_value(prop))) {
            return njs_typed_array_set_value(vm,
                                         njs_typed_array(njs_prop_value(prev)),
                                         njs_prop_magic32(prev),
                                         njs_prop_value(prop));
        }

        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;
        }

        if (njs_is_data_descriptor(prev)) {
            prev->writable = NJS_ATTRIBUTE_UNSET;
            njs_prop_getter(prev) = NULL;
            njs_prop_setter(prev) = NULL;

        } else {
            prev->writable = NJS_ATTRIBUTE_FALSE;

            njs_set_undefined(njs_prop_value(prev));
        }

        prev->type = prop->type;

    } 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(njs_prop_value(prop))
                && prev->type != NJS_PROPERTY_HANDLER
                && !njs_values_same(vm, njs_prop_value(prop),
                                    njs_prop_value(prev)))
            {
                goto exception;
            }
        }

    } else {
        if (!prev->configurable) {
            if (njs_prop_getter(prop) != NJS_PROP_PTR_UNSET
                && njs_prop_getter(prop) != njs_prop_getter(prev))
            {
                goto exception;
            }

            if (njs_prop_setter(prop) != NJS_PROP_PTR_UNSET
                && njs_prop_setter(prop) != njs_prop_setter(prev))
            {
                goto exception;
            }
        }
    }

done:

    if (njs_slow_path(njs_is_fast_array(object)
                      && pq.lhq.key_hash == NJS_ATOM_STRING_length)
                      && 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, 1);
        if (njs_slow_path(ret != NJS_OK)) {
            return ret;
        }

        goto again;
    }

    if (njs_is_accessor_descriptor(prop)) {
        prev->type = prop->type;

        if (njs_prop_getter(prop) != NJS_PROP_PTR_UNSET) {
            njs_prop_getter(prev) = njs_prop_getter(prop);
        }

        if (njs_prop_setter(prop) != NJS_PROP_PTR_UNSET) {
            njs_prop_setter(prev) = njs_prop_setter(prop);
        }

    } else if (njs_is_valid(njs_prop_value(prop))) {

        if (prev->type == NJS_PROPERTY_HANDLER) {
            if (prev->writable) {
                ret = njs_prop_handler(prev)(vm, prev, atom_id, object,
                                             njs_prop_value(prop), &retval);
                if (njs_slow_path(ret == NJS_ERROR)) {
                    return ret;
                }

                if (ret == NJS_DECLINED) {
                    pq.lhq.value = NULL;
                    goto set_prop;
                }

            } else {

                prev->type = prop->type;
                njs_value_assign(njs_prop_value(prev), njs_prop_value(prop));
            }

        } else {

            if (njs_slow_path(njs_is_array(object)
                              && pq.lhq.key_hash == NJS_ATOM_STRING_length))
            {
                if (prev->configurable != NJS_ATTRIBUTE_TRUE
                    && prev->writable != NJS_ATTRIBUTE_TRUE
                    && !njs_values_strict_equal(vm, njs_prop_value(prev),
                                                njs_prop_value(prop)))
                {
                    njs_type_error(vm, "Cannot redefine property: \"length\"");
                    return NJS_ERROR;
                }

                if (prop->writable != NJS_ATTRIBUTE_UNSET) {
                    prev->writable = prop->writable;
                }

                return njs_array_length_set(vm, object, prev,
                                            njs_prop_value(prop));
            }

            njs_value_assign(njs_prop_value(prev), njs_prop_value(prop));
        }
    }

    /*
     * 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 (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_atom_string_get(vm, atom_id, &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_object_t *proto)
{
    njs_int_t          ret;
    njs_value_t        *value, prop_name;
    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_flathsh_unique_insert(&proto->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_prop_getter(prop) != NULL) {
            function = njs_function_copy(vm, njs_prop_getter(prop));
            if (njs_slow_path(function == NULL)) {
                return NJS_ERROR;
            }

            njs_prop_getter(prop) = function;

            if (njs_prop_setter(prop) != NULL
                && function->native && njs_prop_setter(prop)->native
                && function->u.native == njs_prop_setter(prop)->u.native)
            {
                njs_prop_setter(prop) = njs_prop_getter(prop);
                return NJS_OK;
            }
        }

        if (njs_prop_setter(prop) != NULL) {
            function = njs_function_copy(vm, njs_prop_setter(prop));
            if (njs_slow_path(function == NULL)) {
                return NJS_ERROR;
            }

            njs_prop_setter(prop) = function;
        }

        return NJS_OK;
    }

    value = njs_prop_value(prop);

    switch (value->type) {
    case NJS_OBJECT:
    case NJS_ARRAY:
    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, value);
        if (njs_slow_path(function == NULL)) {
            return NJS_ERROR;
        }

        ret = njs_atom_to_value(vm, &prop_name, pq->lhq.key_hash);
        if (ret != NJS_OK) {
            return NJS_ERROR;
        }

        return njs_function_name_set(vm, function, &prop_name, NULL);

    default:
        break;
    }

    return NJS_OK;
}


static njs_object_prop_t *
njs_descriptor_prop(njs_vm_t *vm, const njs_value_t *desc)
{
    njs_int_t            ret;
    njs_bool_t           data, accessor;
    njs_value_t          value;
    njs_object_t         *desc_object;
    njs_function_t       *getter, *setter;
    njs_object_prop_t    *prop;
    njs_flathsh_query_t  lhq;

    if (!njs_is_object(desc)) {
        njs_type_error(vm, "property descriptor must be an object");
        return NULL;
    }

    prop = njs_object_prop_alloc(vm, &njs_value_invalid,
                                 NJS_ATTRIBUTE_UNSET);
    if (njs_slow_path(prop == NULL)) {
        return NULL;
    }

    data = 0;
    accessor = 0;
    getter = NJS_PROP_PTR_UNSET;
    setter = NJS_PROP_PTR_UNSET;
    desc_object = njs_object(desc);

    lhq.proto = &njs_object_hash_proto;
    lhq.key_hash = NJS_ATOM_STRING_get;

    ret = njs_object_property(vm, desc_object, &lhq, &value);
    if (njs_slow_path(ret == NJS_ERROR)) {
        return NULL;
    }

    if (ret == NJS_OK) {
        if (njs_is_defined(&value) && !njs_is_function(&value)) {
            njs_type_error(vm, "Getter must be a function");
            return NULL;
        }

        accessor = 1;
        getter = njs_is_function(&value) ? njs_function(&value) : NULL;
    }

    lhq.key_hash = NJS_ATOM_STRING_set;

    ret = njs_object_property(vm, desc_object, &lhq, &value);
    if (njs_slow_path(ret == NJS_ERROR)) {
        return NULL;
    }

    if (ret == NJS_OK) {
        if (njs_is_defined(&value) && !njs_is_function(&value)) {
            njs_type_error(vm, "Setter must be a function");
            return NULL;
        }

        accessor = 1;
        setter = njs_is_function(&value) ? njs_function(&value) : NULL;
    }

    lhq.key_hash = NJS_ATOM_STRING_value;

    ret = njs_object_property(vm, desc_object, &lhq, &value);
    if (njs_slow_path(ret == NJS_ERROR)) {
        return NULL;
    }

    if (ret == NJS_OK) {
        data = 1;
        njs_value_assign(njs_prop_value(prop), &value);
    }

    lhq.key_hash = NJS_ATOM_STRING_writable;

    ret = njs_object_property(vm, desc_object, &lhq, &value);
    if (njs_slow_path(ret == NJS_ERROR)) {
        return NULL;
    }

    if (ret == NJS_OK) {
        data = 1;
        prop->writable = njs_is_true(&value);
    }

    if (accessor && data) {
        njs_type_error(vm, "Cannot both specify accessors "
                           "and a value or writable attribute");
        return NULL;
    }

    lhq.key_hash = NJS_ATOM_STRING_enumerable;

    ret = njs_object_property(vm, desc_object, &lhq, &value);
    if (njs_slow_path(ret == NJS_ERROR)) {
        return NULL;
    }

    if (ret == NJS_OK) {
        prop->enumerable = njs_is_true(&value);
    }

    lhq.key_hash = NJS_ATOM_STRING_configurable;

    ret = njs_object_property(vm, desc_object, &lhq, &value);
    if (njs_slow_path(ret == NJS_ERROR)) {
        return NULL;
    }

    if (ret == NJS_OK) {
        prop->configurable = njs_is_true(&value);
    }

    if (accessor) {
        prop->type = NJS_ACCESSOR;
        njs_prop_getter(prop) = getter;
        njs_prop_setter(prop) = setter;
    }

    return prop;
}


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_flathsh_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_val(vm, &pq, value, key);

    switch (ret) {
    case NJS_OK:
        prop = pq.lhq.value;

        switch (prop->type) {
        case NJS_PROPERTY:
        case NJS_ACCESSOR:
            break;

        case NJS_PROPERTY_HANDLER:
            pq.scratch = *prop;
            prop = &pq.scratch;
            ret = njs_prop_handler(prop)(vm, prop, key->atom_id, value, NULL,
                                         njs_prop_value(prop));
            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 NJS_ERROR;
    }

    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_hash = NJS_ATOM_STRING_value;

        pr = njs_object_prop_alloc(vm, njs_prop_value(prop), 1);
        if (njs_slow_path(pr == NULL)) {
            return NJS_ERROR;
        }

        lhq.value = pr;

        ret = njs_flathsh_unique_insert(&desc->hash, &lhq);
        if (njs_slow_path(ret != NJS_OK)) {
            njs_internal_error(vm, "lvlhsh insert failed");
            return NJS_ERROR;
        }

        lhq.key_hash = NJS_ATOM_STRING_writable;

        setval = (prop->writable == 1) ? &njs_value_true : &njs_value_false;

        pr = njs_object_prop_alloc(vm,  setval, 1);
        if (njs_slow_path(pr == NULL)) {
            return NJS_ERROR;
        }

        lhq.value = pr;

        ret = njs_flathsh_unique_insert(&desc->hash, &lhq);
        if (njs_slow_path(ret != NJS_OK)) {
            njs_internal_error(vm, "lvlhsh insert failed");
            return NJS_ERROR;
        }

    } else {

        lhq.key_hash = NJS_ATOM_STRING_get;

        pr = njs_object_prop_alloc(vm, &njs_value_undefined, 1);
        if (njs_slow_path(pr == NULL)) {
            return NJS_ERROR;
        }

        if (njs_prop_getter(prop) != NULL) {
            njs_set_function(njs_prop_value(pr), njs_prop_getter(prop));
        }

        lhq.value = pr;

        ret = njs_flathsh_unique_insert(&desc->hash, &lhq);
        if (njs_slow_path(ret != NJS_OK)) {
            njs_internal_error(vm, "lvlhsh insert failed");
            return NJS_ERROR;
        }

        lhq.key_hash = NJS_ATOM_STRING_set;

        pr = njs_object_prop_alloc(vm, &njs_value_undefined, 1);
        if (njs_slow_path(pr == NULL)) {
            return NJS_ERROR;
        }

        if (njs_prop_setter(prop) != NULL) {
            njs_set_function(njs_prop_value(pr), njs_prop_setter(prop));
        }

        lhq.value = pr;

        ret = njs_flathsh_unique_insert(&desc->hash, &lhq);
        if (njs_slow_path(ret != NJS_OK)) {
            njs_internal_error(vm, "lvlhsh insert failed");
            return NJS_ERROR;
        }
    }

    lhq.key_hash = NJS_ATOM_STRING_enumerable;

    setval = (prop->enumerable == 1) ? &njs_value_true : &njs_value_false;

    pr = njs_object_prop_alloc(vm, setval, 1);
    if (njs_slow_path(pr == NULL)) {
        return NJS_ERROR;
    }

    lhq.value = pr;

    ret = njs_flathsh_unique_insert(&desc->hash, &lhq);
    if (njs_slow_path(ret != NJS_OK)) {
        njs_internal_error(vm, "lvlhsh insert failed");
        return NJS_ERROR;
    }

    lhq.key_hash = NJS_ATOM_STRING_configurable;

    setval = (prop->configurable == 1) ? &njs_value_true : &njs_value_false;

    pr = njs_object_prop_alloc(vm, setval, 1);
    if (njs_slow_path(pr == NULL)) {
        return NJS_ERROR;
    }

    lhq.value = pr;

    ret = njs_flathsh_unique_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:
    case NJS_PROPERTY_PLACE_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,
    njs_object_prop_t *base, uint32_t atom_id, njs_value_t *value,
    njs_value_t *retval)
{
    njs_int_t            ret;
    njs_object_t         *object;
    njs_object_prop_t    *prop;
    njs_flathsh_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(njs_prop_value(prop), object);

    lhq.value = prop;
    lhq.key_hash = atom_id;
    lhq.replace = 1;
    lhq.pool = vm->mem_pool;
    lhq.proto = &njs_object_hash_proto;

    ret = njs_flathsh_unique_insert(njs_object_hash(value), &lhq);
    if (njs_fast_path(ret == NJS_OK)) {
        njs_value_assign(retval, njs_prop_value(prop));
        return NJS_OK;
    }

    njs_internal_error(vm, "lvlhsh insert failed");

    return NJS_ERROR;
}
