blob: c24a599c9d5ee0345640f80d43dd4e9b8759eeb4 [file] [log] [blame]
/*
* Copyright (C) Igor Sysoev
* Copyright (C) NGINX, Inc.
*/
#include <njs_main.h>
struct njs_property_next_s {
uint32_t index;
njs_array_t *array;
};
static njs_jump_off_t njs_vmcode_object(njs_vm_t *vm);
static njs_jump_off_t njs_vmcode_array(njs_vm_t *vm, u_char *pc);
static njs_jump_off_t njs_vmcode_function(njs_vm_t *vm, u_char *pc);
static njs_jump_off_t njs_vmcode_arguments(njs_vm_t *vm, u_char *pc);
static njs_jump_off_t njs_vmcode_regexp(njs_vm_t *vm, u_char *pc);
static njs_jump_off_t njs_vmcode_template_literal(njs_vm_t *vm,
njs_value_t *inlvd1, njs_value_t *inlvd2);
static njs_jump_off_t njs_vmcode_object_copy(njs_vm_t *vm, njs_value_t *value,
njs_value_t *invld);
static njs_jump_off_t njs_vmcode_function_copy(njs_vm_t *vm, njs_value_t *value,
njs_index_t retval);
static njs_jump_off_t njs_vmcode_property_init(njs_vm_t *vm, njs_value_t *value,
njs_value_t *key, njs_value_t *retval);
static njs_jump_off_t njs_vmcode_proto_init(njs_vm_t *vm, njs_value_t *value,
njs_value_t *key, njs_value_t *retval);
static njs_jump_off_t njs_vmcode_property_in(njs_vm_t *vm,
njs_value_t *value, njs_value_t *key);
static njs_jump_off_t njs_vmcode_property_foreach(njs_vm_t *vm,
njs_value_t *object, njs_value_t *invld, u_char *pc);
static njs_jump_off_t njs_vmcode_instance_of(njs_vm_t *vm, njs_value_t *object,
njs_value_t *constructor);
static njs_jump_off_t njs_vmcode_typeof(njs_vm_t *vm, njs_value_t *value,
njs_value_t *invld);
static njs_jump_off_t njs_vmcode_debugger(njs_vm_t *vm);
static njs_jump_off_t njs_vmcode_return(njs_vm_t *vm, njs_value_t *invld,
njs_value_t *retval);
static njs_jump_off_t njs_vmcode_try_start(njs_vm_t *vm, njs_value_t *value,
njs_value_t *offset, u_char *pc);
static njs_jump_off_t njs_vmcode_try_break(njs_vm_t *vm, njs_value_t *value,
njs_value_t *offset);
static njs_jump_off_t njs_vmcode_try_continue(njs_vm_t *vm, njs_value_t *value,
njs_value_t *offset);
static njs_jump_off_t njs_vmcode_try_end(njs_vm_t *vm, njs_value_t *invld,
njs_value_t *offset);
static njs_jump_off_t njs_vmcode_finally(njs_vm_t *vm, njs_value_t *invld,
njs_value_t *retval, u_char *pc);
static void njs_vmcode_error(njs_vm_t *vm, u_char *pc);
static njs_jump_off_t njs_string_concat(njs_vm_t *vm, njs_value_t *val1,
njs_value_t *val2);
static njs_jump_off_t njs_values_equal(njs_vm_t *vm, njs_value_t *val1,
njs_value_t *val2);
static njs_jump_off_t njs_primitive_values_compare(njs_vm_t *vm,
njs_value_t *val1, njs_value_t *val2);
static njs_jump_off_t njs_function_frame_create(njs_vm_t *vm,
njs_value_t *value, const njs_value_t *this, uintptr_t nargs,
njs_bool_t ctor);
#define njs_vmcode_operand(vm, index, _retval) \
do { \
_retval = njs_scope_valid_value(vm, index); \
if (njs_slow_path(_retval == NULL)) { \
goto error; \
} \
} while (0)
njs_int_t
njs_vmcode_interpreter(njs_vm_t *vm, u_char *pc)
{
u_char *catch;
double num, exponent;
int32_t i32;
uint32_t u32;
njs_str_t string;
njs_uint_t hint;
njs_bool_t valid, lambda_call;
njs_value_t *retval, *value1, *value2;
njs_value_t *src, *s1, *s2, dst;
njs_value_t *function, name;
njs_value_t numeric1, numeric2, primitive1, primitive2;
njs_frame_t *frame;
njs_jump_off_t ret;
njs_native_frame_t *previous, *native;
njs_property_next_t *next;
njs_vmcode_finally_t *finally;
njs_vmcode_generic_t *vmcode;
njs_vmcode_variable_t *var;
njs_vmcode_move_arg_t *move_arg;
njs_vmcode_prop_get_t *get;
njs_vmcode_prop_set_t *set;
njs_vmcode_operation_t op;
njs_vmcode_prop_next_t *pnext;
njs_vmcode_test_jump_t *test_jump;
njs_vmcode_equal_jump_t *equal;
njs_vmcode_try_return_t *try_return;
njs_vmcode_method_frame_t *method_frame;
njs_vmcode_function_copy_t *fcopy;
njs_vmcode_prop_accessor_t *accessor;
njs_vmcode_try_trampoline_t *try_trampoline;
njs_vmcode_function_frame_t *function_frame;
next:
for ( ;; ) {
vmcode = (njs_vmcode_generic_t *) pc;
/*
* The first operand is passed as is in value2 to
* NJS_VMCODE_JUMP,
* NJS_VMCODE_IF_TRUE_JUMP,
* NJS_VMCODE_IF_FALSE_JUMP,
* NJS_VMCODE_FUNCTION_FRAME,
* NJS_VMCODE_FUNCTION_CALL,
* NJS_VMCODE_RETURN,
* NJS_VMCODE_TRY_START,
* NJS_VMCODE_TRY_CONTINUE,
* NJS_VMCODE_TRY_BREAK,
* NJS_VMCODE_TRY_END,
* NJS_VMCODE_CATCH,
* NJS_VMCODE_THROW,
* NJS_VMCODE_STOP.
*/
value2 = (njs_value_t *) vmcode->operand1;
value1 = NULL;
switch (vmcode->code.operands) {
case NJS_VMCODE_3OPERANDS:
njs_vmcode_operand(vm, vmcode->operand3, value2);
/* Fall through. */
case NJS_VMCODE_2OPERANDS:
njs_vmcode_operand(vm, vmcode->operand2, value1);
}
op = vmcode->code.operation;
/*
* On success an operation returns size of the bytecode,
* a jump offset or zero after the call or return operations.
* Jumps can return a negative offset. Compilers can generate
* (ret < 0 && ret >= NJS_PREEMPT)
* as a single unsigned comparision.
*/
if (op > NJS_VMCODE_NORET) {
if (op == NJS_VMCODE_MOVE) {
njs_vmcode_operand(vm, vmcode->operand1, retval);
*retval = *value1;
pc += sizeof(njs_vmcode_move_t);
goto next;
}
if (op == NJS_VMCODE_PROPERTY_GET) {
get = (njs_vmcode_prop_get_t *) pc;
njs_vmcode_operand(vm, get->value, retval);
ret = njs_value_property(vm, value1, value2, retval);
if (njs_slow_path(ret == NJS_ERROR)) {
goto error;
}
pc += sizeof(njs_vmcode_prop_get_t);
goto next;
}
switch (op) {
case NJS_VMCODE_INCREMENT:
case NJS_VMCODE_POST_INCREMENT:
case NJS_VMCODE_DECREMENT:
case NJS_VMCODE_POST_DECREMENT:
if (njs_slow_path(!njs_is_numeric(value2))) {
ret = njs_value_to_numeric(vm, value2, &numeric1);
if (njs_slow_path(ret != NJS_OK)) {
goto error;
}
num = njs_number(&numeric1);
} else {
num = njs_number(value2);
}
njs_set_number(value1,
num + (1 - 2 * ((op - NJS_VMCODE_INCREMENT) >> 1)));
njs_vmcode_operand(vm, vmcode->operand1, retval);
if (op & 1) {
njs_set_number(retval, num);
} else {
*retval = *value1;
}
pc += sizeof(njs_vmcode_3addr_t);
goto next;
case NJS_VMCODE_GLOBAL_GET:
get = (njs_vmcode_prop_get_t *) pc;
njs_vmcode_operand(vm, get->value, retval);
ret = njs_value_property(vm, value1, value2, retval);
if (njs_slow_path(ret == NJS_ERROR)) {
goto error;
}
pc += sizeof(njs_vmcode_prop_get_t);
if (ret == NJS_OK) {
pc += sizeof(njs_vmcode_error_t);
}
goto next;
/*
* njs_vmcode_try_return() saves a return value to use it later by
* njs_vmcode_finally(), and jumps to the nearest try_break block.
*/
case NJS_VMCODE_TRY_RETURN:
njs_vmcode_operand(vm, vmcode->operand1, retval);
*retval = *value1;
try_return = (njs_vmcode_try_return_t *) pc;
pc += try_return->offset;
goto next;
case NJS_VMCODE_LESS:
case NJS_VMCODE_GREATER:
case NJS_VMCODE_LESS_OR_EQUAL:
case NJS_VMCODE_GREATER_OR_EQUAL:
case NJS_VMCODE_ADDITION:
if (njs_slow_path(!njs_is_primitive(value1))) {
hint = (op == NJS_VMCODE_ADDITION) && njs_is_date(value1);
ret = njs_value_to_primitive(vm, &primitive1, value1, hint);
if (ret != NJS_OK) {
goto error;
}
value1 = &primitive1;
}
if (njs_slow_path(!njs_is_primitive(value2))) {
hint = (op == NJS_VMCODE_ADDITION) && njs_is_date(value2);
ret = njs_value_to_primitive(vm, &primitive2, value2, hint);
if (ret != NJS_OK) {
goto error;
}
value2 = &primitive2;
}
if (njs_slow_path(njs_is_symbol(value1)
|| njs_is_symbol(value2)))
{
njs_symbol_conversion_failed(vm,
(op == NJS_VMCODE_ADDITION) &&
(njs_is_string(value1) || njs_is_string(value2)));
goto error;
}
njs_vmcode_operand(vm, vmcode->operand1, retval);
if (op == NJS_VMCODE_ADDITION) {
if (njs_fast_path(njs_is_numeric(value1)
&& njs_is_numeric(value2)))
{
njs_set_number(retval, njs_number(value1)
+ njs_number(value2));
pc += sizeof(njs_vmcode_3addr_t);
goto next;
}
if (njs_is_string(value1)) {
s1 = value1;
s2 = &dst;
src = value2;
} else {
s1 = &dst;
s2 = value2;
src = value1;
}
ret = njs_primitive_value_to_string(vm, &dst, src);
if (njs_slow_path(ret != NJS_OK)) {
goto error;
}
ret = njs_string_concat(vm, s1, s2);
if (njs_slow_path(ret == NJS_ERROR)) {
goto error;
}
*retval = vm->retval;
pc += ret;
goto next;
}
if ((uint8_t) (op - NJS_VMCODE_GREATER) < 2) {
/* NJS_VMCODE_GREATER, NJS_VMCODE_LESS_OR_EQUAL */
src = value1;
value1 = value2;
value2 = src;
}
ret = njs_primitive_values_compare(vm, value1, value2);
if (op < NJS_VMCODE_LESS_OR_EQUAL) {
ret = ret > 0;
} else {
ret = ret == 0;
}
njs_set_boolean(retval, ret);
pc += sizeof(njs_vmcode_3addr_t);
goto next;
case NJS_VMCODE_EQUAL:
case NJS_VMCODE_NOT_EQUAL:
ret = njs_values_equal(vm, value1, value2);
if (njs_slow_path(ret < 0)) {
goto error;
}
ret ^= op - NJS_VMCODE_EQUAL;
njs_vmcode_operand(vm, vmcode->operand1, retval);
njs_set_boolean(retval, ret);
pc += sizeof(njs_vmcode_3addr_t);
goto next;
case NJS_VMCODE_SUBSTRACTION:
case NJS_VMCODE_MULTIPLICATION:
case NJS_VMCODE_EXPONENTIATION:
case NJS_VMCODE_DIVISION:
case NJS_VMCODE_REMAINDER:
case NJS_VMCODE_BITWISE_AND:
case NJS_VMCODE_BITWISE_OR:
case NJS_VMCODE_BITWISE_XOR:
case NJS_VMCODE_LEFT_SHIFT:
case NJS_VMCODE_RIGHT_SHIFT:
case NJS_VMCODE_UNSIGNED_RIGHT_SHIFT:
if (njs_slow_path(!njs_is_numeric(value1))) {
ret = njs_value_to_numeric(vm, value1, &numeric1);
if (njs_slow_path(ret != NJS_OK)) {
goto error;
}
value1 = &numeric1;
}
if (njs_slow_path(!njs_is_numeric(value2))) {
ret = njs_value_to_numeric(vm, value2, &numeric2);
if (njs_slow_path(ret != NJS_OK)) {
goto error;
}
value2 = &numeric2;
}
num = njs_number(value1);
njs_vmcode_operand(vm, vmcode->operand1, retval);
pc += sizeof(njs_vmcode_3addr_t);
switch (op) {
case NJS_VMCODE_SUBSTRACTION:
num -= njs_number(value2);
break;
case NJS_VMCODE_MULTIPLICATION:
num *= njs_number(value2);
break;
case NJS_VMCODE_EXPONENTIATION:
exponent = njs_number(value2);
/*
* According to ES7:
* 1. If exponent is NaN, the result should be NaN;
* 2. The result of +/-1 ** +/-Infinity should be NaN.
*/
valid = njs_expect(1, fabs(num) != 1
|| (!isnan(exponent)
&& !isinf(exponent)));
num = valid ? pow(num, exponent) : NAN;
break;
case NJS_VMCODE_DIVISION:
num /= njs_number(value2);
break;
case NJS_VMCODE_REMAINDER:
num = fmod(num, njs_number(value2));
break;
case NJS_VMCODE_BITWISE_AND:
case NJS_VMCODE_BITWISE_OR:
case NJS_VMCODE_BITWISE_XOR:
i32 = njs_number_to_int32(njs_number(value2));
switch (op) {
case NJS_VMCODE_BITWISE_AND:
i32 &= njs_number_to_int32(num);
break;
case NJS_VMCODE_BITWISE_OR:
i32 |= njs_number_to_int32(num);
break;
case NJS_VMCODE_BITWISE_XOR:
i32 ^= njs_number_to_int32(num);
break;
}
njs_set_int32(retval, i32);
goto next;
default:
u32 = njs_number_to_uint32(njs_number(value2)) & 0x1f;
switch (op) {
case NJS_VMCODE_LEFT_SHIFT:
case NJS_VMCODE_RIGHT_SHIFT:
i32 = njs_number_to_int32(num);
if (op == NJS_VMCODE_LEFT_SHIFT) {
/* Shifting of negative numbers is undefined. */
i32 = (uint32_t) i32 << u32;
} else {
i32 >>= u32;
}
njs_set_int32(retval, i32);
break;
default: /* NJS_VMCODE_UNSIGNED_RIGHT_SHIFT */
njs_set_uint32(retval,
njs_number_to_uint32(num) >> u32);
}
goto next;
}
njs_set_number(retval, num);
goto next;
case NJS_VMCODE_OBJECT_COPY:
ret = njs_vmcode_object_copy(vm, value1, value2);
break;
case NJS_VMCODE_TEMPLATE_LITERAL:
ret = njs_vmcode_template_literal(vm, value1, value2);
break;
case NJS_VMCODE_PROPERTY_IN:
ret = njs_vmcode_property_in(vm, value1, value2);
break;
case NJS_VMCODE_PROPERTY_DELETE:
ret = njs_value_property_delete(vm, value1, value2, NULL);
if (njs_fast_path(ret != NJS_ERROR)) {
vm->retval = njs_value_true;
ret = sizeof(njs_vmcode_3addr_t);
}
break;
case NJS_VMCODE_PROPERTY_FOREACH:
ret = njs_vmcode_property_foreach(vm, value1, value2, pc);
break;
case NJS_VMCODE_STRICT_EQUAL:
case NJS_VMCODE_STRICT_NOT_EQUAL:
ret = njs_values_strict_equal(value1, value2);
ret ^= op - NJS_VMCODE_STRICT_EQUAL;
njs_vmcode_operand(vm, vmcode->operand1, retval);
njs_set_boolean(retval, ret);
pc += sizeof(njs_vmcode_3addr_t);
goto next;
case NJS_VMCODE_TEST_IF_TRUE:
case NJS_VMCODE_TEST_IF_FALSE:
case NJS_VMCODE_COALESCE:
if (op == NJS_VMCODE_COALESCE) {
ret = !njs_is_null_or_undefined(value1);
} else {
ret = njs_is_true(value1);
ret ^= op - NJS_VMCODE_TEST_IF_TRUE;
}
if (ret) {
test_jump = (njs_vmcode_test_jump_t *) pc;
ret = test_jump->offset;
} else {
ret = sizeof(njs_vmcode_3addr_t);
}
njs_vmcode_operand(vm, vmcode->operand1, retval);
*retval = *value1;
pc += ret;
goto next;
case NJS_VMCODE_UNARY_PLUS:
case NJS_VMCODE_UNARY_NEGATION:
case NJS_VMCODE_BITWISE_NOT:
if (njs_slow_path(!njs_is_numeric(value1))) {
ret = njs_value_to_numeric(vm, value1, &numeric1);
if (njs_slow_path(ret != NJS_OK)) {
goto error;
}
value1 = &numeric1;
}
num = njs_number(value1);
njs_vmcode_operand(vm, vmcode->operand1, retval);
switch (op) {
case NJS_VMCODE_UNARY_NEGATION:
num = -num;
/* Fall through. */
case NJS_VMCODE_UNARY_PLUS:
njs_set_number(retval, num);
break;
case NJS_VMCODE_BITWISE_NOT:
njs_set_int32(retval, ~njs_number_to_uint32(num));
}
pc += sizeof(njs_vmcode_2addr_t);
goto next;
case NJS_VMCODE_LOGICAL_NOT:
njs_vmcode_operand(vm, vmcode->operand1, retval);
njs_set_boolean(retval, !njs_is_true(value1));
pc += sizeof(njs_vmcode_2addr_t);
goto next;
case NJS_VMCODE_OBJECT:
ret = njs_vmcode_object(vm);
break;
case NJS_VMCODE_ARRAY:
ret = njs_vmcode_array(vm, pc);
break;
case NJS_VMCODE_FUNCTION:
ret = njs_vmcode_function(vm, pc);
break;
case NJS_VMCODE_REGEXP:
ret = njs_vmcode_regexp(vm, pc);
break;
case NJS_VMCODE_INSTANCE_OF:
ret = njs_vmcode_instance_of(vm, value1, value2);
break;
case NJS_VMCODE_TYPEOF:
ret = njs_vmcode_typeof(vm, value1, value2);
break;
case NJS_VMCODE_VOID:
njs_set_undefined(&vm->retval);
ret = sizeof(njs_vmcode_2addr_t);
break;
case NJS_VMCODE_DELETE:
njs_release(vm, value1);
vm->retval = njs_value_true;
ret = sizeof(njs_vmcode_2addr_t);
break;
case NJS_VMCODE_DEBUGGER:
ret = njs_vmcode_debugger(vm);
break;
default:
njs_internal_error(vm, "%d has retval", op);
goto error;
}
if (njs_slow_path(ret < 0 && ret >= NJS_PREEMPT)) {
break;
}
njs_vmcode_operand(vm, vmcode->operand1, retval);
njs_release(vm, retval);
*retval = vm->retval;
} else {
switch (op) {
case NJS_VMCODE_MOVE_ARG:
move_arg = (njs_vmcode_move_arg_t *) pc;
native = vm->top_frame;
hint = move_arg->dst;
value1 = &native->arguments_offset[hint];
njs_vmcode_operand(vm, move_arg->src, value2);
*value1 = *value2;
ret = sizeof(njs_vmcode_move_arg_t);
break;
case NJS_VMCODE_STOP:
njs_vmcode_operand(vm, (njs_index_t) value2, value2);
vm->retval = *value2;
return NJS_OK;
case NJS_VMCODE_JUMP:
ret = (njs_jump_off_t) value2;
break;
case NJS_VMCODE_PROPERTY_SET:
set = (njs_vmcode_prop_set_t *) pc;
njs_vmcode_operand(vm, set->value, retval);
ret = njs_value_property_set(vm, value1, value2, retval);
if (njs_slow_path(ret == NJS_ERROR)) {
goto error;
}
ret = sizeof(njs_vmcode_prop_set_t);
break;
case NJS_VMCODE_PROPERTY_ACCESSOR:
accessor = (njs_vmcode_prop_accessor_t *) pc;
njs_vmcode_operand(vm, accessor->value, function);
ret = njs_value_to_key(vm, &name, value2);
if (njs_slow_path(ret != NJS_OK)) {
njs_internal_error(vm, "failed conversion of type \"%s\" "
"to string while property define",
njs_type_string(value2->type));
goto error;
}
ret = njs_object_prop_define(vm, value1, &name, function,
accessor->type);
if (njs_slow_path(ret != NJS_OK)) {
return NJS_ERROR;
}
ret = sizeof(njs_vmcode_prop_accessor_t);
break;
case NJS_VMCODE_IF_TRUE_JUMP:
case NJS_VMCODE_IF_FALSE_JUMP:
ret = njs_is_true(value1);
ret ^= op - NJS_VMCODE_IF_TRUE_JUMP;
ret = ret ? (njs_jump_off_t) value2
: (njs_jump_off_t) sizeof(njs_vmcode_cond_jump_t);
break;
case NJS_VMCODE_IF_EQUAL_JUMP:
if (njs_values_strict_equal(value1, value2)) {
equal = (njs_vmcode_equal_jump_t *) pc;
ret = equal->offset;
} else {
ret = sizeof(njs_vmcode_3addr_t);
}
break;
case NJS_VMCODE_PROPERTY_INIT:
set = (njs_vmcode_prop_set_t *) pc;
njs_vmcode_operand(vm, set->value, retval);
ret = njs_vmcode_property_init(vm, value1, value2, retval);
if (njs_slow_path(ret == NJS_ERROR)) {
goto error;
}
break;
case NJS_VMCODE_RETURN:
njs_vmcode_operand(vm, (njs_index_t) value2, value2);
return njs_vmcode_return(vm, NULL, value2);
case NJS_VMCODE_FUNCTION_COPY:
fcopy = (njs_vmcode_function_copy_t *) pc;
ret = njs_vmcode_function_copy(vm, fcopy->function,
fcopy->retval);
break;
case NJS_VMCODE_FUNCTION_FRAME:
function_frame = (njs_vmcode_function_frame_t *) pc;
/* TODO: external object instead of void this. */
ret = njs_function_frame_create(vm, value1,
&njs_value_undefined,
(uintptr_t) value2,
function_frame->ctor);
if (njs_slow_path(ret != NJS_OK)) {
goto error;
}
ret = sizeof(njs_vmcode_function_frame_t);
break;
case NJS_VMCODE_METHOD_FRAME:
method_frame = (njs_vmcode_method_frame_t *) pc;
ret = njs_value_property(vm, value1, value2, &dst);
if (njs_slow_path(ret == NJS_ERROR)) {
goto error;
}
if (njs_slow_path(!njs_is_function(&dst))) {
ret = njs_value_to_key(vm, value2, value2);
if (njs_slow_path(ret != NJS_OK)) {
return NJS_ERROR;
}
njs_key_string_get(vm, value2, &string);
njs_type_error(vm,
"(intermediate value)[\"%V\"] is not a function",
&string);
goto error;
}
ret = njs_function_frame_create(vm, &dst, value1,
method_frame->nargs,
method_frame->ctor);
if (njs_slow_path(ret != NJS_OK)) {
goto error;
}
ret = sizeof(njs_vmcode_method_frame_t);
break;
case NJS_VMCODE_FUNCTION_CALL:
vm->active_frame->native.pc = pc;
njs_vmcode_operand(vm, (njs_index_t) value2, value2);
ret = njs_function_frame_invoke(vm, value2);
if (njs_slow_path(ret == NJS_ERROR)) {
goto error;
}
ret = sizeof(njs_vmcode_function_call_t);
break;
case NJS_VMCODE_PROPERTY_NEXT:
pnext = (njs_vmcode_prop_next_t *) pc;
retval = njs_scope_value(vm, pnext->retval);
next = value2->data.u.next;
if (next->index < next->array->length) {
*retval = next->array->start[next->index++];
ret = pnext->offset;
break;
}
njs_mp_free(vm->mem_pool, next);
ret = sizeof(njs_vmcode_prop_next_t);
break;
case NJS_VMCODE_ARGUMENTS:
ret = njs_vmcode_arguments(vm, pc);
if (njs_slow_path(ret == NJS_ERROR)) {
goto error;
}
break;
case NJS_VMCODE_PROTO_INIT:
set = (njs_vmcode_prop_set_t *) pc;
njs_vmcode_operand(vm, set->value, retval);
ret = njs_vmcode_proto_init(vm, value1, value2, retval);
if (njs_slow_path(ret == NJS_ERROR)) {
goto error;
}
break;
case NJS_VMCODE_TRY_START:
ret = njs_vmcode_try_start(vm, value1, value2, pc);
if (njs_slow_path(ret == NJS_ERROR)) {
goto error;
}
break;
case NJS_VMCODE_THROW:
njs_vmcode_operand(vm, (njs_index_t) value2, value2);
vm->retval = *value2;
goto error;
case NJS_VMCODE_TRY_BREAK:
try_trampoline = (njs_vmcode_try_trampoline_t *) pc;
value1 = njs_scope_value(vm, try_trampoline->exit_value);
ret = njs_vmcode_try_break(vm, value1, value2);
break;
case NJS_VMCODE_TRY_CONTINUE:
try_trampoline = (njs_vmcode_try_trampoline_t *) pc;
value1 = njs_scope_value(vm, try_trampoline->exit_value);
ret = njs_vmcode_try_continue(vm, value1, value2);
break;
case NJS_VMCODE_TRY_END:
ret = njs_vmcode_try_end(vm, value1, value2);
break;
/*
* njs_vmcode_catch() is set on the start of a "catch" block to
* store exception and to remove a "try" block if there is no
* "finally" block or to update a catch address to the start of
* a "finally" block.
* njs_vmcode_catch() is set on the start of a "finally" block
* to store uncaught exception and to remove a "try" block.
*/
case NJS_VMCODE_CATCH:
*value1 = vm->retval;
if ((njs_jump_off_t) value2 == sizeof(njs_vmcode_catch_t)) {
ret = njs_vmcode_try_end(vm, value1, value2);
} else {
frame = (njs_frame_t *) vm->top_frame;
frame->exception.catch = pc + (njs_jump_off_t) value2;
ret = sizeof(njs_vmcode_catch_t);
}
break;
case NJS_VMCODE_FINALLY:
finally = (njs_vmcode_finally_t *) pc;
value1 = njs_scope_value(vm, finally->exit_value);
ret = njs_vmcode_finally(vm, value1, value2, pc);
switch (ret) {
case NJS_OK:
return NJS_OK;
case NJS_ERROR:
goto error;
}
break;
case NJS_VMCODE_LET:
var = (njs_vmcode_variable_t *) pc;
value1 = njs_scope_value(vm, var->dst);
if (njs_is_valid(value1)) {
value1 = njs_mp_alloc(vm->mem_pool, sizeof(njs_value_t));
if (njs_slow_path(value1 == NULL)) {
return NJS_ERROR;
}
njs_scope_value_set(vm, var->dst, value1);
}
njs_set_undefined(value1);
ret = sizeof(njs_vmcode_variable_t);
break;
case NJS_VMCODE_LET_UPDATE:
var = (njs_vmcode_variable_t *) pc;
value2 = njs_scope_value(vm, var->dst);
value1 = njs_mp_alloc(vm->mem_pool, sizeof(njs_value_t));
if (njs_slow_path(value1 == NULL)) {
return NJS_ERROR;
}
*value1 = *value2;
njs_scope_value_set(vm, var->dst, value1);
ret = sizeof(njs_vmcode_variable_t);
break;
case NJS_VMCODE_INITIALIZATION_TEST:
var = (njs_vmcode_variable_t *) pc;
value1 = njs_scope_value(vm, var->dst);
if (njs_is_valid(value1)) {
ret = sizeof(njs_vmcode_variable_t);
break;
}
/* Fall through. */
case NJS_VMCODE_NOT_INITIALIZED:
njs_reference_error(vm, "cannot access variable "
"before initialization");
goto error;
case NJS_VMCODE_ERROR:
njs_vmcode_error(vm, pc);
goto error;
case NJS_VMCODE_ASSIGNMENT_ERROR:
njs_type_error(vm, "assignment to constant variable");
goto error;
default:
njs_internal_error(vm, "%d has NO retval", op);
goto error;
}
}
pc += ret;
}
error:
if (njs_is_error(&vm->retval)) {
vm->active_frame->native.pc = pc;
(void) njs_error_stack_attach(vm, &vm->retval);
}
for ( ;; ) {
native = vm->top_frame;
if (!native->native) {
frame = (njs_frame_t *) native;
catch = frame->exception.catch;
if (catch != NULL) {
pc = catch;
goto next;
}
}
previous = native->previous;
if (previous == NULL) {
break;
}
lambda_call = (native == &vm->active_frame->native);
njs_vm_scopes_restore(vm, native, previous);
if (native->size != 0) {
vm->stack_size -= native->size;
njs_mp_free(vm->mem_pool, native);
}
if (lambda_call) {
break;
}
}
return NJS_ERROR;
}
static njs_jump_off_t
njs_vmcode_object(njs_vm_t *vm)
{
njs_object_t *object;
object = njs_object_alloc(vm);
if (njs_fast_path(object != NULL)) {
njs_set_object(&vm->retval, object);
return sizeof(njs_vmcode_object_t);
}
return NJS_ERROR;
}
static njs_jump_off_t
njs_vmcode_array(njs_vm_t *vm, u_char *pc)
{
uint32_t length;
njs_array_t *array;
njs_value_t *value;
njs_vmcode_array_t *code;
code = (njs_vmcode_array_t *) pc;
array = njs_array_alloc(vm, 0, code->length, NJS_ARRAY_SPARE);
if (njs_fast_path(array != NULL)) {
if (code->ctor) {
/* Array of the form [,,,], [1,,]. */
value = array->start;
length = array->length;
do {
njs_set_invalid(value);
value++;
length--;
} while (length != 0);
} else {
/* Array of the form [], [,,1], [1,2,3]. */
array->length = 0;
}
njs_set_array(&vm->retval, array);
return sizeof(njs_vmcode_array_t);
}
return NJS_ERROR;
}
static njs_jump_off_t
njs_vmcode_function(njs_vm_t *vm, u_char *pc)
{
njs_function_t *function;
njs_vmcode_function_t *code;
njs_function_lambda_t *lambda;
code = (njs_vmcode_function_t *) pc;
lambda = code->lambda;
function = njs_function_alloc(vm, lambda);
if (njs_slow_path(function == NULL)) {
return NJS_ERROR;
}
if (njs_function_capture_closure(vm, function, lambda) != NJS_OK) {
return NJS_ERROR;
}
function->args_count = lambda->nargs - lambda->rest_parameters;
njs_set_function(&vm->retval, function);
return sizeof(njs_vmcode_function_t);
}
static njs_jump_off_t
njs_vmcode_arguments(njs_vm_t *vm, u_char *pc)
{
njs_frame_t *frame;
njs_value_t *value;
njs_jump_off_t ret;
njs_vmcode_arguments_t *code;
frame = (njs_frame_t *) vm->active_frame;
if (frame->native.arguments_object == NULL) {
ret = njs_function_arguments_object_init(vm, &frame->native);
if (njs_slow_path(ret != NJS_OK)) {
return NJS_ERROR;
}
}
code = (njs_vmcode_arguments_t *) pc;
value = njs_scope_valid_value(vm, code->dst);
if (njs_slow_path(value == NULL)) {
return NJS_ERROR;
}
njs_set_object(value, frame->native.arguments_object);
return sizeof(njs_vmcode_arguments_t);
}
static njs_jump_off_t
njs_vmcode_regexp(njs_vm_t *vm, u_char *pc)
{
njs_regexp_t *regexp;
njs_vmcode_regexp_t *code;
code = (njs_vmcode_regexp_t *) pc;
regexp = njs_regexp_alloc(vm, code->pattern);
if (njs_fast_path(regexp != NULL)) {
njs_set_regexp(&vm->retval, regexp);
return sizeof(njs_vmcode_regexp_t);
}
return NJS_ERROR;
}
static njs_jump_off_t
njs_vmcode_template_literal(njs_vm_t *vm, njs_value_t *invld1,
njs_value_t *retval)
{
njs_array_t *array;
njs_value_t *value;
njs_jump_off_t ret;
static const njs_function_t concat = {
.native = 1,
.args_offset = 1,
.u.native = njs_string_prototype_concat
};
value = njs_scope_valid_value(vm, (njs_index_t) retval);
if (!njs_is_primitive(value)) {
array = njs_array(value);
ret = njs_function_frame(vm, (njs_function_t *) &concat,
&njs_string_empty, array->start,
array->length, 0);
if (njs_slow_path(ret != NJS_OK)) {
return ret;
}
ret = njs_function_frame_invoke(vm, value);
if (njs_slow_path(ret != NJS_OK)) {
return ret;
}
}
return sizeof(njs_vmcode_template_literal_t);
}
static njs_jump_off_t
njs_vmcode_object_copy(njs_vm_t *vm, njs_value_t *value, njs_value_t *invld)
{
njs_object_t *object;
njs_function_t *function;
switch (value->type) {
case NJS_OBJECT:
object = njs_object_value_copy(vm, value);
if (njs_slow_path(object == NULL)) {
return NJS_ERROR;
}
break;
case NJS_FUNCTION:
function = njs_function_value_copy(vm, value);
if (njs_slow_path(function == NULL)) {
return NJS_ERROR;
}
break;
default:
break;
}
vm->retval = *value;
njs_retain(value);
return sizeof(njs_vmcode_object_copy_t);
}
static njs_jump_off_t
njs_vmcode_function_copy(njs_vm_t *vm, njs_value_t *value, njs_index_t retidx)
{
njs_value_t *retval;
njs_function_t *function;
retval = njs_scope_valid_value(vm, retidx);
if (njs_is_undefined(retval)) {
*retval = *value;
function = njs_function_value_copy(vm, retval);
if (njs_slow_path(function == NULL)) {
return NJS_ERROR;
}
}
return sizeof(njs_vmcode_function_copy_t);
}
static njs_jump_off_t
njs_vmcode_property_init(njs_vm_t *vm, njs_value_t *value, njs_value_t *key,
njs_value_t *init)
{
double num;
uint32_t index, size;
njs_int_t ret;
njs_array_t *array;
njs_value_t *val, name;
njs_object_prop_t *prop;
njs_lvlhsh_query_t lhq;
switch (value->type) {
case NJS_ARRAY:
num = njs_key_to_index(key);
if (njs_slow_path(!njs_key_is_integer_index(num, key))) {
njs_internal_error(vm,
"invalid index while property initialization");
return NJS_ERROR;
}
index = (uint32_t) num;
array = value->data.u.array;
if (index >= array->length) {
size = index - array->length;
ret = njs_array_expand(vm, array, 0, size + 1);
if (njs_slow_path(ret != NJS_OK)) {
return ret;
}
val = &array->start[array->length];
while (size != 0) {
njs_set_invalid(val);
val++;
size--;
}
array->length = index + 1;
}
/* GC: retain. */
array->start[index] = *init;
break;
case NJS_OBJECT:
ret = njs_value_to_key(vm, &name, key);
if (njs_slow_path(ret != NJS_OK)) {
return NJS_ERROR;
}
njs_object_property_key_set(&lhq, &name, 0);
lhq.proto = &njs_object_hash_proto;
lhq.pool = vm->mem_pool;
prop = njs_object_prop_alloc(vm, &name, init, 1);
if (njs_slow_path(prop == NULL)) {
return NJS_ERROR;
}
lhq.value = prop;
lhq.replace = 1;
ret = njs_lvlhsh_insert(njs_object_hash(value), &lhq);
if (njs_slow_path(ret != NJS_OK)) {
njs_internal_error(vm, "lvlhsh insert/replace failed");
return NJS_ERROR;
}
break;
default:
njs_internal_error(vm, "unexpected object type \"%s\" "
"while property initialization",
njs_type_string(value->type));
return NJS_ERROR;
}
return sizeof(njs_vmcode_prop_set_t);
}
static njs_jump_off_t
njs_vmcode_proto_init(njs_vm_t *vm, njs_value_t *value, njs_value_t *unused,
njs_value_t *init)
{
njs_object_t *obj;
njs_jump_off_t ret;
njs_object_prop_t *prop;
njs_lvlhsh_query_t lhq;
lhq.key = njs_str_value("__proto__");
lhq.key_hash = NJS___PROTO___HASH;
lhq.proto = &njs_object_hash_proto;
lhq.pool = vm->mem_pool;
obj = njs_object(value);
ret = njs_lvlhsh_find(&obj->__proto__->shared_hash, &lhq);
if (njs_slow_path(ret != NJS_OK)) {
goto fail;
}
prop = lhq.value;
if (prop->type != NJS_PROPERTY_HANDLER) {
goto fail;
}
ret = prop->value.data.u.prop_handler(vm, prop, value, init, &vm->retval);
if (njs_slow_path(ret != NJS_OK)) {
goto fail;
}
return sizeof(njs_vmcode_prop_set_t);
fail:
njs_internal_error(vm, "\"__proto__\" init failed");
return NJS_ERROR;
}
static njs_jump_off_t
njs_vmcode_property_in(njs_vm_t *vm, njs_value_t *value, njs_value_t *key)
{
njs_int_t ret;
njs_property_query_t pq;
if (njs_slow_path(njs_is_primitive(value))) {
njs_type_error(vm, "property \"in\" on primitive %s type",
njs_type_string(value->type));
return NJS_ERROR;
}
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;
}
}
njs_property_query_init(&pq, NJS_PROPERTY_QUERY_GET, 0);
ret = njs_property_query(vm, &pq, value, key);
if (njs_slow_path(ret == NJS_ERROR)) {
return ret;
}
njs_set_boolean(&vm->retval, ret == NJS_OK);
return sizeof(njs_vmcode_3addr_t);
}
static njs_jump_off_t
njs_vmcode_property_foreach(njs_vm_t *vm, njs_value_t *object,
njs_value_t *invld, u_char *pc)
{
njs_property_next_t *next;
njs_vmcode_prop_foreach_t *code;
next = njs_mp_alloc(vm->mem_pool, sizeof(njs_property_next_t));
if (njs_slow_path(next == NULL)) {
njs_memory_error(vm);
return NJS_ERROR;
}
next->index = 0;
next->array = njs_value_enumerate(vm, object, NJS_ENUM_KEYS,
NJS_ENUM_STRING, 0);
if (njs_slow_path(next->array == NULL)) {
njs_memory_error(vm);
return NJS_ERROR;
}
vm->retval.data.u.next = next;
code = (njs_vmcode_prop_foreach_t *) pc;
return code->offset;
}
static njs_jump_off_t
njs_vmcode_instance_of(njs_vm_t *vm, njs_value_t *object,
njs_value_t *constructor)
{
njs_value_t value, bound;
njs_object_t *prototype, *proto;
njs_function_t *function;
njs_jump_off_t ret;
const njs_value_t *retval;
static const njs_value_t prototype_string = njs_string("prototype");
if (!njs_is_function(constructor)) {
njs_type_error(vm, "right argument is not callable");
return NJS_ERROR;
}
function = njs_function(constructor);
if (function->bound != NULL) {
function = function->u.bound_target;
njs_set_function(&bound, function);
constructor = &bound;
}
retval = &njs_value_false;
if (njs_is_object(object)) {
ret = njs_value_property(vm, constructor,
njs_value_arg(&prototype_string), &value);
if (njs_slow_path(ret == NJS_ERROR)) {
return ret;
}
if (njs_fast_path(ret == NJS_OK)) {
if (njs_slow_path(!njs_is_object(&value))) {
njs_type_error(vm, "Function has non-object prototype "
"in instanceof");
return NJS_ERROR;
}
prototype = njs_object(&value);
proto = njs_object(object);
do {
proto = proto->__proto__;
if (proto == prototype) {
retval = &njs_value_true;
break;
}
} while (proto != NULL);
}
}
vm->retval = *retval;
return sizeof(njs_vmcode_instance_of_t);
}
static njs_jump_off_t
njs_vmcode_typeof(njs_vm_t *vm, njs_value_t *value, njs_value_t *invld)
{
/* ECMAScript 5.1: null, array and regexp are objects. */
static const njs_value_t *types[NJS_VALUE_TYPE_MAX] = {
&njs_string_object,
&njs_string_undefined,
&njs_string_boolean,
&njs_string_number,
&njs_string_symbol,
&njs_string_string,
&njs_string_data,
&njs_string_external,
&njs_string_invalid,
&njs_string_undefined,
&njs_string_undefined,
&njs_string_undefined,
&njs_string_undefined,
&njs_string_undefined,
&njs_string_undefined,
&njs_string_undefined,
&njs_string_object,
&njs_string_object,
&njs_string_object,
&njs_string_object,
&njs_string_object,
&njs_string_object,
&njs_string_function,
&njs_string_object,
&njs_string_object,
&njs_string_object,
&njs_string_object,
&njs_string_object,
&njs_string_object,
};
vm->retval = *types[value->type];
return sizeof(njs_vmcode_2addr_t);
}
static njs_jump_off_t
njs_vmcode_debugger(njs_vm_t *vm)
{
/*
* HOW TO DEBUG JS CODE:
* 1) put debugger instruction when certain condition is met
* in the JS code:
* function test() {
* if (<>) debugger;
* }
* 2) set a breakpoint to njs_vmcode_debugger() in gdb.
*
* To see the values of certain indices:
* 1) use njs -d test.js to dump the byte code
* 2) find an appropriate index around DEBUGGER instruction
* 3) in gdb: p *njs_scope_value_get(vm, <index as a hex literal>)
**/
njs_set_undefined(&vm->retval);
return sizeof(njs_vmcode_debugger_t);
}
static njs_jump_off_t
njs_string_concat(njs_vm_t *vm, njs_value_t *val1, njs_value_t *val2)
{
u_char *start;
size_t size, length;
njs_string_prop_t string1, string2;
(void) njs_string_prop(&string1, val1);
(void) njs_string_prop(&string2, val2);
/*
* A result of concatenation of Byte and ASCII or UTF-8 strings
* is a Byte string.
*/
if ((string1.length != 0 || string1.size == 0)
&& (string2.length != 0 || string2.size == 0))
{
length = string1.length + string2.length;
} else {
length = 0;
}
size = string1.size + string2.size;
start = njs_string_alloc(vm, &vm->retval, size, length);
if (njs_slow_path(start == NULL)) {
return NJS_ERROR;
}
(void) memcpy(start, string1.start, string1.size);
(void) memcpy(start + string1.size, string2.start, string2.size);
return sizeof(njs_vmcode_3addr_t);
}
static njs_jump_off_t
njs_values_equal(njs_vm_t *vm, njs_value_t *val1, njs_value_t *val2)
{
njs_int_t ret;
njs_bool_t nv1, nv2;
njs_value_t primitive;
njs_value_t *hv, *lv;
again:
nv1 = njs_is_null_or_undefined(val1);
nv2 = njs_is_null_or_undefined(val2);
/* Void and null are equal and not comparable with anything else. */
if (nv1 || nv2) {
return (nv1 && nv2);
}
if (njs_is_numeric(val1) && njs_is_numeric(val2)) {
/* NaNs and Infinities are handled correctly by comparision. */
return (njs_number(val1) == njs_number(val2));
}
if (val1->type == val2->type) {
if (njs_is_string(val1)) {
return njs_string_eq(val1, val2);
}
if (njs_is_symbol(val1)) {
return njs_symbol_eq(val1, val2);
}
return (njs_object(val1) == njs_object(val2));
}
/* Sort values as: numeric < symbol < string < objects. */
if (val1->type > val2->type) {
hv = val1;
lv = val2;
} else {
hv = val2;
lv = val1;
}
/* If "lv" is an object then "hv" can only be another object. */
if (njs_is_object(lv)) {
return 0;
}
/* If "hv" is a symbol then "lv" can only be a numeric. */
if (njs_is_symbol(hv)) {
return 0;
}
/* If "hv" is a string then "lv" can be a numeric or symbol. */
if (njs_is_string(hv)) {
return !njs_is_symbol(lv)
&& (njs_number(lv) == njs_string_to_number(hv, 0));
}
/* "hv" is an object and "lv" is either a string or a symbol or a numeric. */
ret = njs_value_to_primitive(vm, &primitive, hv, 0);
if (ret != NJS_OK) {
return ret;
}
val1 = &primitive;
val2 = lv;
goto again;
}
/*
* ECMAScript 5.1: 11.8.5
* njs_primitive_values_compare() returns
* 1 if val1 is less than val2,
* 0 if val1 is greater than or equal to val2,
* -1 if the values are not comparable.
*/
static njs_jump_off_t
njs_primitive_values_compare(njs_vm_t *vm, njs_value_t *val1, njs_value_t *val2)
{
double num1, num2;
if (njs_fast_path(njs_is_numeric(val1))) {
num1 = njs_number(val1);
if (njs_fast_path(njs_is_numeric(val2))) {
num2 = njs_number(val2);
} else {
num2 = njs_string_to_number(val2, 0);
}
} else if (njs_is_numeric(val2)) {
num1 = njs_string_to_number(val1, 0);
num2 = njs_number(val2);
} else {
return (njs_string_cmp(val1, val2) < 0) ? 1 : 0;
}
/* NaN and void values are not comparable with anything. */
if (isnan(num1) || isnan(num2)) {
return -1;
}
/* Infinities are handled correctly by comparision. */
return (num1 < num2);
}
static njs_jump_off_t
njs_function_frame_create(njs_vm_t *vm, njs_value_t *value,
const njs_value_t *this, uintptr_t nargs, njs_bool_t ctor)
{
njs_value_t val;
njs_object_t *object;
njs_function_t *function;
if (njs_fast_path(njs_is_function(value))) {
function = njs_function(value);
if (ctor) {
if (!function->ctor) {
njs_type_error(vm, "%s is not a constructor",
njs_type_string(value->type));
return NJS_ERROR;
}
if (!function->native) {
object = njs_function_new_object(vm, value);
if (njs_slow_path(object == NULL)) {
return NJS_ERROR;
}
njs_set_object(&val, object);
this = &val;
}
}
return njs_function_frame(vm, function, this, NULL, nargs, ctor);
}
njs_type_error(vm, "%s is not a function", njs_type_string(value->type));
return NJS_ERROR;
}
njs_object_t *
njs_function_new_object(njs_vm_t *vm, njs_value_t *constructor)
{
njs_value_t proto, bound;
njs_object_t *object;
njs_function_t *function;
njs_jump_off_t ret;
const njs_value_t prototype_string = njs_string("prototype");
object = njs_object_alloc(vm);
if (njs_slow_path(object == NULL)) {
return NULL;
}
function = njs_function(constructor);
if (function->bound != NULL) {
function = function->u.bound_target;
njs_set_function(&bound, function);
constructor = &bound;
}
ret = njs_value_property(vm, constructor, njs_value_arg(&prototype_string),
&proto);
if (njs_slow_path(ret == NJS_ERROR)) {
return NULL;
}
if (njs_fast_path(njs_is_object(&proto))) {
object->__proto__ = njs_object(&proto);
}
return object;
}
static njs_jump_off_t
njs_vmcode_return(njs_vm_t *vm, njs_value_t *invld, njs_value_t *retval)
{
njs_frame_t *frame;
njs_native_frame_t *previous;
frame = (njs_frame_t *) vm->top_frame;
if (frame->native.ctor) {
if (njs_is_object(retval)) {
njs_release(vm, frame->native.local[0]);
} else {
retval = frame->native.local[0];
}
}
previous = njs_function_previous_frame(&frame->native);
njs_vm_scopes_restore(vm, &frame->native, previous);
*frame->native.retval = *retval;
njs_function_frame_free(vm, &frame->native);
return NJS_OK;
}
/*
* njs_vmcode_try_start() is set on the start of a "try" block to create
* a "try" block, to set a catch address to the start of a "catch" or
* "finally" blocks and to initialize a value to track uncaught exception.
*/
static njs_jump_off_t
njs_vmcode_try_start(njs_vm_t *vm, njs_value_t *exception_value,
njs_value_t *offset, u_char *pc)
{
njs_value_t *exit_value;
njs_frame_t *frame;
njs_exception_t *e;
njs_vmcode_try_start_t *try_start;
frame = (njs_frame_t *) vm->top_frame;
if (frame->exception.catch != NULL) {
e = njs_mp_alloc(vm->mem_pool, sizeof(njs_exception_t));
if (njs_slow_path(e == NULL)) {
njs_memory_error(vm);
return NJS_ERROR;
}
*e = frame->exception;
frame->exception.next = e;
}
frame->exception.catch = pc + (njs_jump_off_t) offset;
njs_set_invalid(exception_value);
try_start = (njs_vmcode_try_start_t *) pc;
exit_value = njs_scope_value(vm, try_start->exit_value);
njs_set_invalid(exit_value);
njs_number(exit_value) = 0;
return sizeof(njs_vmcode_try_start_t);
}
/*
* njs_vmcode_try_break() sets exit_value to INVALID 1, and jumps to
* the nearest try_end block. The exit_value is checked by njs_vmcode_finally().
*/
static njs_jump_off_t
njs_vmcode_try_break(njs_vm_t *vm, njs_value_t *exit_value,
njs_value_t *offset)
{
/* exit_value can contain valid value set by vmcode_try_return. */
if (!njs_is_valid(exit_value)) {
njs_number(exit_value) = 1;
}
return (njs_jump_off_t) offset;
}
/*
* njs_vmcode_try_continue() sets exit_value to INVALID -1, and jumps to
* the nearest try_end block. The exit_value is checked by njs_vmcode_finally().
*/
static njs_jump_off_t
njs_vmcode_try_continue(njs_vm_t *vm, njs_value_t *exit_value,
njs_value_t *offset)
{
njs_number(exit_value) = -1;
return (njs_jump_off_t) offset;
}
/*
* njs_vmcode_try_end() is set on the end of a "try" block to remove the block.
* It is also set on the end of a "catch" block followed by a "finally" block.
*/
static njs_jump_off_t
njs_vmcode_try_end(njs_vm_t *vm, njs_value_t *invld, njs_value_t *offset)
{
njs_frame_t *frame;
njs_exception_t *e;
frame = (njs_frame_t *) vm->top_frame;
e = frame->exception.next;
if (e == NULL) {
frame->exception.catch = NULL;
} else {
frame->exception = *e;
njs_mp_free(vm->mem_pool, e);
}
return (njs_jump_off_t) offset;
}
/*
* njs_vmcode_finally() is set on the end of a "finally" or a "catch" block.
* 1) to throw uncaught exception.
* 2) to make a jump to an enslosing loop exit if "continue" or "break"
* statement was used inside try block.
* 3) to finalize "return" instruction from "try" block.
*/
static njs_jump_off_t
njs_vmcode_finally(njs_vm_t *vm, njs_value_t *invld, njs_value_t *retval,
u_char *pc)
{
njs_value_t *exception_value, *exit_value;
njs_vmcode_finally_t *finally;
exception_value = njs_scope_value(vm, (njs_index_t) retval);
if (njs_is_valid(exception_value)) {
vm->retval = *exception_value;
return NJS_ERROR;
}
finally = (njs_vmcode_finally_t *) pc;
exit_value = njs_scope_value(vm, finally->exit_value);
/*
* exit_value is set by:
* vmcode_try_start to INVALID 0
* vmcode_try_break to INVALID 1
* vmcode_try_continue to INVALID -1
* vmcode_try_return to a valid return value
*/
if (njs_is_valid(exit_value)) {
return njs_vmcode_return(vm, NULL, exit_value);
} else if (njs_number(exit_value) != 0) {
return (njs_jump_off_t) (njs_number(exit_value) > 0)
? finally->break_offset
: finally->continue_offset;
}
return sizeof(njs_vmcode_finally_t);
}
static void
njs_vmcode_error(njs_vm_t *vm, u_char *pc)
{
njs_vmcode_error_t *err;
err = (njs_vmcode_error_t *) pc;
if (err->type == NJS_OBJ_TYPE_REF_ERROR) {
njs_reference_error(vm, "\"%V\" is not defined", &err->u.name);
} else {
njs_error_fmt_new(vm, &vm->retval, err->type, "%V", &err->u.message);
}
}