
/*
 * Copyright (C) Dmitry Volyntsev
 * Copyright (C) NGINX, Inc.
 */


#include <njs_main.h>


typedef struct {
    union {
        njs_function_t                *function;
        u_char                        *pc;
    } u;
    uint8_t                           native;
} njs_stack_entry_t;


typedef struct {
    njs_str_t                         name;
    njs_str_t                         file;
    uint32_t                          line;
} njs_backtrace_entry_t;


static njs_int_t njs_add_backtrace_entry(njs_vm_t *vm, njs_arr_t *stack,
    njs_stack_entry_t *se);
static njs_int_t njs_backtrace_to_string(njs_vm_t *vm, njs_arr_t *backtrace,
    njs_str_t *dst);


void
njs_error_new(njs_vm_t *vm, njs_value_t *dst, njs_object_t *proto,
    u_char *start, size_t size)
{
    njs_int_t     ret;
    njs_value_t   string;
    njs_object_t  *error;

    ret = njs_string_create(vm, &string, start, size);
    if (njs_slow_path(ret != NJS_OK)) {
        return;
    }

    error = njs_error_alloc(vm, proto, NULL, &string, NULL);
    if (njs_slow_path(error == NULL)) {
        return;
    }

    njs_set_object(dst, error);
}

void
njs_throw_error_va(njs_vm_t *vm, njs_object_t *proto, const char *fmt,
    va_list args)
{
    u_char   buf[NJS_MAX_ERROR_STR], *p;

    p = njs_vsprintf(buf, buf + sizeof(buf), fmt, args);

    njs_error_new(vm, &vm->exception, proto, buf, p - buf);
}


void
njs_throw_error(njs_vm_t *vm, njs_object_type_t type, const char *fmt, ...)
{
    va_list  args;

    va_start(args, fmt);
    njs_throw_error_va(vm, njs_vm_proto(vm, type), fmt, args);
    va_end(args);
}


void
njs_error_fmt_new(njs_vm_t *vm, njs_value_t *dst, njs_object_type_t type,
    const char *fmt, ...)
{
    va_list  args;
    u_char   buf[NJS_MAX_ERROR_STR], *p;

    va_start(args, fmt);
    p = njs_vsprintf(buf, buf + sizeof(buf), fmt, args);
    va_end(args);

    njs_error_new(vm, dst, njs_vm_proto(vm, type), buf, p - buf);
}


static njs_int_t
njs_error_stack_new(njs_vm_t *vm, njs_object_value_t *error)
{
    njs_arr_t           *stack;
    njs_stack_entry_t   *se;
    njs_native_frame_t  *frame;

    stack = njs_arr_create(vm->mem_pool, 4, sizeof(njs_stack_entry_t));
    if (njs_slow_path(stack == NULL)) {
        return NJS_ERROR;
    }

    frame = vm->top_frame;

    for ( ;; ) {
        if (frame->native || frame->pc != NULL) {
            se = njs_arr_add(stack);
            if (njs_slow_path(se == NULL)) {
                return NJS_ERROR;
            }

            se->native = frame->native;

            if (se->native) {
                se->u.function = frame->function;

            } else {
                se->u.pc = frame->pc;
            }
        }

        frame = frame->previous;

        if (frame == NULL) {
            break;
        }
    }

    njs_data(&error->value) = stack;

    return NJS_OK;
}


njs_int_t
njs_error_stack_attach(njs_vm_t *vm, njs_value_t value)
{
    njs_int_t  ret;

    if (njs_slow_path(!njs_is_error(&value))
        || njs_object(&value)->stack_attached)
    {
        return NJS_DECLINED;
    }

    if (njs_slow_path(!vm->options.backtrace || vm->start == NULL)) {
        return NJS_OK;
    }

    ret = njs_error_stack_new(vm, value.data.u.object_value);
    if (njs_slow_path(ret != NJS_OK)) {
        njs_internal_error(vm, "njs_error_stack_new() failed");
        return NJS_ERROR;
    }

    njs_object(&value)->stack_attached = 1;

    return NJS_OK;
}


njs_int_t
njs_error_stack(njs_vm_t *vm, njs_value_t *value, njs_value_t *stack)
{
    njs_int_t  ret;

    ret = njs_value_property(vm, value, NJS_ATOM_STRING_stack, stack);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    if (!njs_is_string(stack)) {
        return NJS_DECLINED;
    }

    return NJS_OK;
}


njs_object_t *
njs_error_alloc(njs_vm_t *vm, njs_object_t *proto, const njs_value_t *name,
    const njs_value_t *message, const njs_value_t *errors)
{
    njs_int_t            ret;
    njs_object_t         *error;
    njs_object_prop_t    *prop;
    njs_object_value_t   *ov;
    njs_flathsh_query_t  lhq;

    ov = njs_mp_alloc(vm->mem_pool, sizeof(njs_object_value_t));
    if (njs_slow_path(ov == NULL)) {
        goto memory_error;
    }

    njs_set_data(&ov->value, NULL, NJS_DATA_TAG_ANY);

    error = &ov->object;
    njs_lvlhsh_init(&error->hash);
    njs_lvlhsh_init(&error->shared_hash);
    error->type = NJS_OBJECT_VALUE;
    error->shared = 0;
    error->extensible = 1;
    error->fast_array = 0;
    error->error_data = 1;
    error->stack_attached = 0;
    error->__proto__ = proto;
    error->slots = NULL;

    lhq.replace = 0;
    lhq.pool = vm->mem_pool;
    lhq.proto = &njs_object_hash_proto;

    if (name != NULL) {
        lhq.key_hash = NJS_ATOM_STRING_name;

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

        prop = lhq.value;

        prop->type = NJS_PROPERTY;
        prop->enumerable = 1;
        prop->configurable = 1;
        prop->writable = 1;

        prop->u.value = *name;
    }

    if (message!= NULL) {
        lhq.key_hash = NJS_ATOM_STRING_message;

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

        prop = lhq.value;

        prop->type = NJS_PROPERTY;
        prop->enumerable = 0;
        prop->configurable = 1;
        prop->writable = 1;

        prop->u.value = *message;
    }

    if (errors != NULL) {
        lhq.key_hash = NJS_ATOM_STRING_errors;

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

        prop = lhq.value;

        prop->type = NJS_PROPERTY;
        prop->enumerable = 0;
        prop->configurable = 1;
        prop->writable = 1;

        prop->u.value = *errors;
    }

    return error;

memory_error:

    njs_memory_error(vm);

    return NULL;
}


njs_int_t
njs_error_constructor(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
    njs_index_t type, njs_value_t *retval)
{
    njs_int_t     ret;
    njs_value_t   *iterator, *value, list;
    njs_array_t   *array;
    njs_object_t  *error;

    if (type != NJS_OBJ_TYPE_AGGREGATE_ERROR) {
        iterator = NULL;
        value = njs_arg(args, nargs, 1);

        njs_set_undefined(&list);

    } else {
        iterator = njs_arg(args, nargs, 1);
        value = njs_arg(args, nargs, 2);

        if (njs_slow_path(iterator->type < NJS_STRING)) {
            njs_type_error(vm, "first argument is not iterable");
            return NJS_ERROR;
        }

        array = njs_iterator_to_array(vm, iterator, retval);
        if (njs_slow_path(array == NULL)) {
            return NJS_ERROR;
        }

        njs_set_array(&list, array);
    }

    if (njs_slow_path(!njs_is_string(value))) {
        if (!njs_is_undefined(value)) {
            ret = njs_value_to_string(vm, value, value);
            if (njs_slow_path(ret != NJS_OK)) {
                return ret;
            }
        }
    }

    error = njs_error_alloc(vm, njs_vm_proto(vm, type), NULL,
                            njs_is_defined(value) ? value : NULL,
                            njs_is_defined(&list) ? &list : NULL);
    if (njs_slow_path(error == NULL)) {
        return NJS_ERROR;
    }

    njs_set_object(retval, error);

    return NJS_OK;
}


static const njs_object_prop_init_t  njs_error_constructor_properties[] =
{
    NJS_DECLARE_PROP_LENGTH(1),

    NJS_DECLARE_PROP_NAME("Error"),

    NJS_DECLARE_PROP_HANDLER(STRING_prototype, njs_object_prototype_create,
                             0, 0),
};


const njs_object_init_t  njs_error_constructor_init = {
    njs_error_constructor_properties,
    njs_nitems(njs_error_constructor_properties),
};


static const njs_object_prop_init_t  njs_eval_error_constructor_properties[] =
{
    NJS_DECLARE_PROP_LENGTH(1),

    NJS_DECLARE_PROP_NAME("EvalError"),

    NJS_DECLARE_PROP_HANDLER(STRING_prototype, njs_object_prototype_create,
                             0, 0),
};


const njs_object_init_t  njs_eval_error_constructor_init = {
    njs_eval_error_constructor_properties,
    njs_nitems(njs_eval_error_constructor_properties),
};


static const njs_object_prop_init_t  njs_internal_error_constructor_properties[] =
{
    NJS_DECLARE_PROP_LENGTH(1),

    NJS_DECLARE_PROP_NAME("InternalError"),

    NJS_DECLARE_PROP_HANDLER(STRING_prototype, njs_object_prototype_create,
                             0, 0),
};


const njs_object_init_t  njs_internal_error_constructor_init = {
    njs_internal_error_constructor_properties,
    njs_nitems(njs_internal_error_constructor_properties),
};


static const njs_object_prop_init_t  njs_range_error_constructor_properties[] =
{
    NJS_DECLARE_PROP_LENGTH(1),

    NJS_DECLARE_PROP_NAME("RangeError"),

    NJS_DECLARE_PROP_HANDLER(STRING_prototype, njs_object_prototype_create,
                             0, 0),
};


const njs_object_init_t  njs_range_error_constructor_init = {
    njs_range_error_constructor_properties,
    njs_nitems(njs_range_error_constructor_properties),
};


static const njs_object_prop_init_t  njs_reference_error_constructor_properties[] =
{
    NJS_DECLARE_PROP_LENGTH(1),

    NJS_DECLARE_PROP_NAME("ReferenceError"),

    NJS_DECLARE_PROP_HANDLER(STRING_prototype, njs_object_prototype_create,
                             0, 0),
};


const njs_object_init_t  njs_reference_error_constructor_init = {
    njs_reference_error_constructor_properties,
    njs_nitems(njs_reference_error_constructor_properties),
};


static const njs_object_prop_init_t  njs_syntax_error_constructor_properties[] =
{
    NJS_DECLARE_PROP_LENGTH(1),

    NJS_DECLARE_PROP_NAME("SyntaxError"),

    NJS_DECLARE_PROP_HANDLER(STRING_prototype, njs_object_prototype_create,
                             0, 0),
};


const njs_object_init_t  njs_syntax_error_constructor_init = {
    njs_syntax_error_constructor_properties,
    njs_nitems(njs_syntax_error_constructor_properties),
};


static const njs_object_prop_init_t  njs_type_error_constructor_properties[] =
{
    NJS_DECLARE_PROP_LENGTH(1),

    NJS_DECLARE_PROP_NAME("TypeError"),

    NJS_DECLARE_PROP_HANDLER(STRING_prototype, njs_object_prototype_create,
                             0, 0),
};


const njs_object_init_t  njs_type_error_constructor_init = {
    njs_type_error_constructor_properties,
    njs_nitems(njs_type_error_constructor_properties),
};


static const njs_object_prop_init_t  njs_uri_error_constructor_properties[] =
{
    NJS_DECLARE_PROP_LENGTH(1),

    NJS_DECLARE_PROP_NAME("URIError"),

    NJS_DECLARE_PROP_HANDLER(STRING_prototype, njs_object_prototype_create,
                             0, 0),
};


const njs_object_init_t  njs_uri_error_constructor_init = {
    njs_uri_error_constructor_properties,
    njs_nitems(njs_uri_error_constructor_properties),
};


static const njs_object_prop_init_t  njs_aggregate_error_constructor_properties[] =
{
    NJS_DECLARE_PROP_LENGTH(1),

    NJS_DECLARE_PROP_NAME("AggregateError"),

    NJS_DECLARE_PROP_HANDLER(STRING_prototype, njs_object_prototype_create,
                             0, 0),
};


const njs_object_init_t  njs_aggregate_error_constructor_init = {
    njs_aggregate_error_constructor_properties,
    njs_nitems(njs_aggregate_error_constructor_properties),
};


void
njs_memory_error_set(njs_vm_t *vm, njs_value_t *value)
{
    njs_object_t        *object;
    njs_object_value_t  *ov;

    ov = &vm->memory_error_object;
    njs_set_data(&ov->value, NULL, NJS_DATA_TAG_ANY);

    object = &ov->object;
    njs_lvlhsh_init(&object->hash);
    njs_lvlhsh_init(&object->shared_hash);
    object->__proto__ = njs_vm_proto(vm, NJS_OBJ_TYPE_INTERNAL_ERROR);
    object->slots = NULL;
    object->type = NJS_OBJECT_VALUE;
    object->shared = 1;

    /*
     * Marking it nonextensible to differentiate
     * it from ordinary internal errors.
     */
    object->extensible = 0;
    object->fast_array = 0;
    object->error_data = 1;

    njs_set_object(value, object);
}


void
njs_memory_error(njs_vm_t *vm)
{
    njs_memory_error_set(vm, &vm->exception);
}


static njs_int_t
njs_memory_error_constructor(njs_vm_t *vm, njs_value_t *args,
    njs_uint_t nargs, njs_index_t unused, njs_value_t *retval)
{
    njs_memory_error_set(vm, retval);

    return NJS_OK;
}


static njs_int_t
njs_memory_error_prototype_create(njs_vm_t *vm, njs_object_prop_t *prop,
    uint32_t unused, njs_value_t *value, njs_value_t *setval,
    njs_value_t *retval)
{
    int32_t            index;
    njs_function_t     *function;
    const njs_value_t  *proto;

    /* MemoryError has no its own prototype. */

    index = NJS_OBJ_TYPE_INTERNAL_ERROR;

    function = njs_function(value);
    proto = njs_property_prototype_create(vm, &function->object.hash,
                                          njs_vm_proto(vm, index));
    if (proto == NULL) {
        proto = &njs_value_undefined;
    }

    *retval = *proto;

    return NJS_OK;
}


static const njs_object_prop_init_t  njs_memory_error_constructor_properties[] =
{
    NJS_DECLARE_PROP_LENGTH(1),

    NJS_DECLARE_PROP_NAME("MemoryError"),

    NJS_DECLARE_PROP_HANDLER(STRING_prototype,
                             njs_memory_error_prototype_create, 0, 0),
};


const njs_object_init_t  njs_memory_error_constructor_init = {
    njs_memory_error_constructor_properties,
    njs_nitems(njs_memory_error_constructor_properties),
};


static njs_int_t
njs_error_prototype_value_of(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
    njs_index_t unused, njs_value_t *retval)
{
    njs_value_assign(retval, njs_argument(args, 0));

    return NJS_OK;
}


static njs_int_t
njs_error_to_string2(njs_vm_t *vm, njs_value_t *retval,
    const njs_value_t *error, njs_bool_t want_stack)
{
    size_t             length;
    u_char             *p;
    njs_int_t          ret;
    njs_value_t        value1, value2;
    njs_value_t        *name_value, *message_value;
    njs_string_prop_t  name, message;

    njs_assert(njs_is_object(error));

    if (want_stack) {
        ret = njs_error_stack(vm, njs_value_arg(error), retval);
        if (njs_slow_path(ret == NJS_ERROR)) {
            return ret;
        }

        if (ret == NJS_OK) {
            return NJS_OK;
        }
    }

    ret = njs_value_property(vm, (njs_value_t *) error, NJS_ATOM_STRING_name,
                             &value1);
    if (njs_slow_path(ret == NJS_ERROR)) {
        return ret;
    }

    if (ret == NJS_DECLINED) {
        njs_atom_to_value(vm, &value1, NJS_ATOM_STRING_Error);
    }

    name_value = &value1;

    if (njs_slow_path(!njs_is_string(name_value))) {
        ret = njs_value_to_string(vm, &value1, name_value);
        if (njs_slow_path(ret != NJS_OK)) {
            return ret;
        }
    }

    (void) njs_string_prop(vm, &name, name_value);

    ret = njs_value_property(vm,  (njs_value_t *) error, NJS_ATOM_STRING_message,
                             &value2);
    if (njs_slow_path(ret == NJS_ERROR)) {
        return ret;
    }

    if (ret == NJS_DECLINED) {
        njs_set_empty_string(vm, &value2);
    }

    message_value = &value2;

    if (njs_slow_path(!njs_is_string(message_value))) {
        ret = njs_value_to_string(vm, &value2, message_value);
        if (njs_slow_path(ret != NJS_OK)) {
            return ret;
        }

        message_value = &value2;
    }

    (void) njs_string_prop(vm, &message, message_value);

    if (name.size == 0) {
        *retval = *message_value;
        return NJS_OK;
    }

    if (message.size == 0) {
        *retval = *name_value;
        return NJS_OK;
    }

    if (name.length != 0 && message.length != 0) {
        length = name.length + message.length + 2;

    } else {
        length = 0;
    }

    p = njs_string_alloc(vm, retval, name.size + message.size + 2, length);

    if (njs_fast_path(p != NULL)) {
        p = njs_cpymem(p, name.start, name.size);
        *p++ = ':';
        *p++ = ' ';
        memcpy(p, message.start, message.size);

        return NJS_OK;
    }

    njs_memory_error(vm);

    return NJS_ERROR;
}


static njs_int_t
njs_error_prototype_to_string(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
    njs_index_t unused, njs_value_t *retval)
{
    if (nargs < 1 || !njs_is_object(&args[0])) {
        njs_type_error(vm, "\"this\" argument is not an object");
        return NJS_ERROR;
    }

    return njs_error_to_string2(vm, retval, &args[0], 0);
}


static njs_int_t
njs_error_prototype_stack(njs_vm_t *vm, njs_object_prop_t *prop, uint32_t unused,
    njs_value_t *value, njs_value_t *setval, njs_value_t *retval)
{
    njs_int_t          ret;
    njs_str_t          string;
    njs_arr_t          *stack, *backtrace;
    njs_uint_t         i;
    njs_value_t        rv, *stackval;
    njs_stack_entry_t  *se;

    if (retval != NULL) {
        if (!njs_is_error(value)) {
            njs_set_undefined(retval);
            return NJS_DECLINED;
        }

        stackval = njs_object_value(value);

        if (setval != NULL) {
            njs_value_assign(stackval, setval);
            return NJS_OK;
        }

        if (!njs_is_data(stackval, NJS_DATA_TAG_ANY)) {
            njs_value_assign(retval, stackval);
            return NJS_OK;
        }

        stack = njs_data(stackval);
        if (stack == NULL) {
            njs_set_undefined(retval);
            return NJS_OK;
        }

        se = stack->start;

        backtrace = njs_arr_create(vm->mem_pool, stack->items,
                                   sizeof(njs_backtrace_entry_t));
        if (njs_slow_path(backtrace == NULL)) {
            return NJS_ERROR;
        }

        for (i = 0; i < stack->items; i++) {
            if (njs_add_backtrace_entry(vm, backtrace, &se[i]) != NJS_OK) {
                return NJS_ERROR;
            }
        }

        ret = njs_error_to_string2(vm, &rv, value, 0);
        if (njs_slow_path(ret != NJS_OK)) {
            return ret;
        }

        njs_string_get(vm, &rv, &string);

        ret = njs_backtrace_to_string(vm, backtrace, &string);

        njs_arr_destroy(backtrace);
        njs_arr_destroy(stack);

        if (njs_slow_path(ret != NJS_OK)) {
            return ret;
        }

        ret = njs_string_create(vm, stackval, string.start, string.length);
        if (njs_slow_path(ret != NJS_OK)) {
            return ret;
        }

        njs_value_assign(retval, stackval);

        return NJS_OK;
    }

    /* Delete. */

    if (njs_is_error(value)) {
        stackval = njs_object_value(value);
        njs_set_data(stackval, NULL, NJS_DATA_TAG_ANY);
    }

    return NJS_OK;
}


njs_int_t
njs_error_to_string(njs_vm_t *vm, njs_value_t *retval, const njs_value_t *error)
{
    if (njs_slow_path(!njs_is_object(error))) {
        njs_type_error(vm, "\"error\" is not an object");
        return NJS_ERROR;
    }

    return njs_error_to_string2(vm, retval, error, 1);
}


static const njs_object_prop_init_t  njs_error_prototype_properties[] =
{
    NJS_DECLARE_PROP_HANDLER(STRING_constructor,
                             njs_object_prototype_create_constructor, 0,
                             NJS_OBJECT_PROP_VALUE_CW),

    NJS_DECLARE_PROP_VALUE(STRING_name, njs_ascii_strval("Error"),
                           NJS_OBJECT_PROP_VALUE_CW),

    NJS_DECLARE_PROP_VALUE(STRING_message, njs_ascii_strval(""),
                           NJS_OBJECT_PROP_VALUE_CW),

    NJS_DECLARE_PROP_NATIVE(STRING_valueOf, njs_error_prototype_value_of,
                            0, 0),

    NJS_DECLARE_PROP_NATIVE(STRING_toString, njs_error_prototype_to_string,
                            0, 0),

    NJS_DECLARE_PROP_HANDLER(STRING_stack, njs_error_prototype_stack,
                             0, NJS_OBJECT_PROP_VALUE_CW),
};


const njs_object_init_t  njs_error_prototype_init = {
    njs_error_prototype_properties,
    njs_nitems(njs_error_prototype_properties),
};


const njs_object_type_init_t  njs_error_type_init = {
    .constructor = njs_native_ctor(njs_error_constructor, 1,
                                   NJS_OBJ_TYPE_ERROR),
    .constructor_props = &njs_error_constructor_init,
    .prototype_props = &njs_error_prototype_init,
    .prototype_value = { .object = { .type = NJS_OBJECT } },
};


static const njs_object_prop_init_t  njs_eval_error_prototype_properties[] =
{
    NJS_DECLARE_PROP_HANDLER(STRING_constructor,
                             njs_object_prototype_create_constructor, 0,
                             NJS_OBJECT_PROP_VALUE_CW),

    NJS_DECLARE_PROP_VALUE(STRING_name, njs_ascii_strval("EvalError"),
                           NJS_OBJECT_PROP_VALUE_CW),

    NJS_DECLARE_PROP_VALUE(STRING_message, njs_ascii_strval(""),
                           NJS_OBJECT_PROP_VALUE_CW),
};


const njs_object_init_t  njs_eval_error_prototype_init = {
    njs_eval_error_prototype_properties,
    njs_nitems(njs_eval_error_prototype_properties),
};


const njs_object_type_init_t  njs_eval_error_type_init = {
    .constructor = njs_native_ctor(njs_error_constructor, 1,
                                   NJS_OBJ_TYPE_EVAL_ERROR),
    .constructor_props = &njs_eval_error_constructor_init,
    .prototype_props = &njs_eval_error_prototype_init,
    .prototype_value = { .object = { .type = NJS_OBJECT } },
};


static njs_int_t
njs_internal_error_prototype_to_string(njs_vm_t *vm, njs_value_t *args,
    njs_uint_t nargs, njs_index_t unused, njs_value_t *retval)
{
    if (nargs >= 1 && njs_is_object(&args[0])) {
        /* MemoryError is a nonextensible internal error. */
        if (!njs_object(&args[0])->extensible) {
            njs_atom_to_value(vm, retval, NJS_ATOM_STRING_MemoryError);
            return NJS_OK;
        }
    }

    return njs_error_prototype_to_string(vm, args, nargs, unused, retval);
}


static const njs_object_prop_init_t  njs_internal_error_prototype_properties[] =
{
    NJS_DECLARE_PROP_VALUE(STRING_name, njs_ascii_strval("InternalError"),
                           NJS_OBJECT_PROP_VALUE_CW),

    NJS_DECLARE_PROP_VALUE(STRING_message, njs_ascii_strval(""),
                           NJS_OBJECT_PROP_VALUE_CW),

    NJS_DECLARE_PROP_NATIVE(STRING_toString,
                            njs_internal_error_prototype_to_string, 0, 0),
};


const njs_object_init_t  njs_internal_error_prototype_init = {
    njs_internal_error_prototype_properties,
    njs_nitems(njs_internal_error_prototype_properties),
};


const njs_object_type_init_t  njs_internal_error_type_init = {
    .constructor = njs_native_ctor(njs_error_constructor, 1,
                                   NJS_OBJ_TYPE_INTERNAL_ERROR),
    .constructor_props = &njs_internal_error_constructor_init,
    .prototype_props = &njs_internal_error_prototype_init,
    .prototype_value = { .object = { .type = NJS_OBJECT } },
};


const njs_object_type_init_t  njs_memory_error_type_init = {
    .constructor = njs_native_ctor(njs_memory_error_constructor, 1, 0),
    .constructor_props = &njs_memory_error_constructor_init,
    .prototype_props = &njs_internal_error_prototype_init,
    .prototype_value = { .object = { .type = NJS_OBJECT } },
};


static const njs_object_prop_init_t  njs_range_error_prototype_properties[] =
{
    NJS_DECLARE_PROP_HANDLER(STRING_constructor,
                             njs_object_prototype_create_constructor, 0,
                             NJS_OBJECT_PROP_VALUE_CW),

    NJS_DECLARE_PROP_VALUE(STRING_name, njs_ascii_strval("RangeError"),
                           NJS_OBJECT_PROP_VALUE_CW),

    NJS_DECLARE_PROP_VALUE(STRING_message, njs_ascii_strval(""),
                           NJS_OBJECT_PROP_VALUE_CW),
};


const njs_object_init_t  njs_range_error_prototype_init = {
    njs_range_error_prototype_properties,
    njs_nitems(njs_range_error_prototype_properties),
};


const njs_object_type_init_t  njs_range_error_type_init = {
    .constructor = njs_native_ctor(njs_error_constructor, 1,
                                   NJS_OBJ_TYPE_RANGE_ERROR),
    .constructor_props = &njs_range_error_constructor_init,
    .prototype_props = &njs_range_error_prototype_init,
    .prototype_value = { .object = { .type = NJS_OBJECT } },
};


static const njs_object_prop_init_t  njs_reference_error_prototype_properties[] =
{
    NJS_DECLARE_PROP_HANDLER(STRING_constructor,
                             njs_object_prototype_create_constructor, 0,
                             NJS_OBJECT_PROP_VALUE_CW),

    NJS_DECLARE_PROP_VALUE(STRING_name, njs_ascii_strval("ReferenceError"),
                           NJS_OBJECT_PROP_VALUE_CW),

    NJS_DECLARE_PROP_VALUE(STRING_message, njs_ascii_strval(""),
                           NJS_OBJECT_PROP_VALUE_CW),
};


const njs_object_init_t  njs_reference_error_prototype_init = {
    njs_reference_error_prototype_properties,
    njs_nitems(njs_reference_error_prototype_properties),
};


const njs_object_type_init_t  njs_reference_error_type_init = {
    .constructor = njs_native_ctor(njs_error_constructor, 1,
                                   NJS_OBJ_TYPE_REF_ERROR),
    .constructor_props = &njs_reference_error_constructor_init,
    .prototype_props = &njs_reference_error_prototype_init,
    .prototype_value = { .object = { .type = NJS_OBJECT } },
};


static const njs_object_prop_init_t  njs_syntax_error_prototype_properties[] =
{
    NJS_DECLARE_PROP_HANDLER(STRING_constructor,
                             njs_object_prototype_create_constructor, 0,
                             NJS_OBJECT_PROP_VALUE_CW),

    NJS_DECLARE_PROP_VALUE(STRING_name, njs_ascii_strval("SyntaxError"),
                           NJS_OBJECT_PROP_VALUE_CW),

    NJS_DECLARE_PROP_VALUE(STRING_message, njs_ascii_strval(""),
                           NJS_OBJECT_PROP_VALUE_CW),
};


const njs_object_init_t  njs_syntax_error_prototype_init = {
    njs_syntax_error_prototype_properties,
    njs_nitems(njs_syntax_error_prototype_properties),
};


const njs_object_type_init_t  njs_syntax_error_type_init = {
    .constructor = njs_native_ctor(njs_error_constructor, 1,
                                   NJS_OBJ_TYPE_SYNTAX_ERROR),
    .constructor_props = &njs_syntax_error_constructor_init,
    .prototype_props = &njs_syntax_error_prototype_init,
    .prototype_value = { .object = { .type = NJS_OBJECT } },
};


static const njs_object_prop_init_t  njs_type_error_prototype_properties[] =
{
    NJS_DECLARE_PROP_HANDLER(STRING_constructor,
                             njs_object_prototype_create_constructor, 0,
                             NJS_OBJECT_PROP_VALUE_CW),

    NJS_DECLARE_PROP_VALUE(STRING_name, njs_ascii_strval("TypeError"),
                           NJS_OBJECT_PROP_VALUE_CW),

    NJS_DECLARE_PROP_VALUE(STRING_message, njs_ascii_strval(""),
                           NJS_OBJECT_PROP_VALUE_CW),
};


const njs_object_init_t  njs_type_error_prototype_init = {
    njs_type_error_prototype_properties,
    njs_nitems(njs_type_error_prototype_properties),
};


const njs_object_type_init_t  njs_type_error_type_init = {
    .constructor = njs_native_ctor(njs_error_constructor, 1,
                                   NJS_OBJ_TYPE_TYPE_ERROR),
    .constructor_props = &njs_type_error_constructor_init,
    .prototype_props = &njs_type_error_prototype_init,
    .prototype_value = { .object = { .type = NJS_OBJECT } },
};


static const njs_object_prop_init_t  njs_uri_error_prototype_properties[] =
{
    NJS_DECLARE_PROP_HANDLER(STRING_constructor,
                             njs_object_prototype_create_constructor, 0,
                             NJS_OBJECT_PROP_VALUE_CW),

    NJS_DECLARE_PROP_VALUE(STRING_name, njs_ascii_strval("URIError"),
                           NJS_OBJECT_PROP_VALUE_CW),

    NJS_DECLARE_PROP_VALUE(STRING_message, njs_ascii_strval(""),
                           NJS_OBJECT_PROP_VALUE_CW),
};


const njs_object_init_t  njs_uri_error_prototype_init = {
    njs_uri_error_prototype_properties,
    njs_nitems(njs_uri_error_prototype_properties),
};


const njs_object_type_init_t  njs_uri_error_type_init = {
    .constructor = njs_native_ctor(njs_error_constructor, 1,
                                   NJS_OBJ_TYPE_URI_ERROR),
    .constructor_props = &njs_uri_error_constructor_init,
    .prototype_props = &njs_uri_error_prototype_init,
    .prototype_value = { .object = { .type = NJS_OBJECT } },
};


static const njs_object_prop_init_t  njs_aggregate_error_prototype_properties[] =
{
    NJS_DECLARE_PROP_HANDLER(STRING_constructor,
                             njs_object_prototype_create_constructor, 0,
                             NJS_OBJECT_PROP_VALUE_CW),

    NJS_DECLARE_PROP_VALUE(STRING_name, njs_ascii_strval("AggregateError"),
                           NJS_OBJECT_PROP_VALUE_CW),

    NJS_DECLARE_PROP_VALUE(STRING_message, njs_ascii_strval(""),
                           NJS_OBJECT_PROP_VALUE_CW),
};


const njs_object_init_t  njs_aggregate_error_prototype_init = {
    njs_aggregate_error_prototype_properties,
    njs_nitems(njs_aggregate_error_prototype_properties),
};


const njs_object_type_init_t  njs_aggregate_error_type_init = {
    .constructor = njs_native_ctor(njs_error_constructor, 1,
                                   NJS_OBJ_TYPE_AGGREGATE_ERROR),
    .constructor_props = &njs_aggregate_error_constructor_init,
    .prototype_props = &njs_aggregate_error_prototype_init,
    .prototype_value = { .object = { .type = NJS_OBJECT } },
};


static njs_int_t
njs_add_backtrace_entry(njs_vm_t *vm, njs_arr_t *stack,
    njs_stack_entry_t *se)
{
    njs_int_t              ret;
    njs_vm_code_t          *code;
    njs_function_t         *function;
    njs_backtrace_entry_t  *be;

    function = se->native ? se->u.function : NULL;

    if (function != NULL && function->bound != NULL) {
        /* Skip. */
        return NJS_OK;
    }

    be = njs_arr_add(stack);
    if (njs_slow_path(be == NULL)) {
        return NJS_ERROR;
    }

    be->line = 0;
    be->file = njs_str_value("");

    if (function != NULL && function->native) {
        ret = njs_builtin_match_native_function(vm, function, &be->name);
        if (ret == NJS_OK) {
            return NJS_OK;
        }

        be->name = njs_entry_native;

        return NJS_OK;
    }

    code = njs_lookup_code(vm, se->u.pc);

    if (code != NULL) {
        be->name = code->name;

        if (be->name.length == 0) {
            be->name = njs_entry_anonymous;
        }

        be->line = njs_lookup_line(code->lines, se->u.pc - code->start);
        if (!vm->options.quiet) {
            be->file = code->file;
        }

        return NJS_OK;
    }

    be->name = njs_entry_unknown;

    return NJS_OK;
}


static njs_int_t
njs_backtrace_to_string(njs_vm_t *vm, njs_arr_t *backtrace, njs_str_t *dst)
{
    size_t                 count;
    njs_chb_t              chain;
    njs_int_t              ret;
    njs_uint_t             i;
    njs_backtrace_entry_t  *be, *prev;

    if (backtrace->items == 0) {
        return NJS_OK;
    }

    NJS_CHB_MP_INIT(&chain, njs_vm_memory_pool(vm));

    njs_chb_append_str(&chain, dst);
    njs_chb_append(&chain, "\n", 1);

    count = 0;
    prev = NULL;

    be = backtrace->start;

    for (i = 0; i < backtrace->items; i++) {
        if (i != 0 && prev->name.start == be->name.start
            && prev->line == be->line)
        {
            count++;

        } else {
            if (count != 0) {
                njs_chb_sprintf(&chain, 64, "      repeats %uz times\n", count);
                count = 0;
            }

            njs_chb_sprintf(&chain, 10 + be->name.length, "    at %V ",
                            &be->name);

            if (be->line != 0) {
                njs_chb_sprintf(&chain, 12 + be->file.length,
                                "(%V:%uD)\n", &be->file, be->line);

            } else {
                njs_chb_append(&chain, "(native)\n", 9);
            }
        }

        prev = be;
        be++;
    }

    ret = njs_chb_join(&chain, dst);
    njs_chb_destroy(&chain);

    return ret;
}
