blob: 8070180f1ec6805fcde47aa1783e3c96007786c8 [file] [log] [blame]
/*
* Copyright (C) Igor Sysoev
* Copyright (C) NGINX, Inc.
*/
#include <nxt_auto_config.h>
#include <nxt_types.h>
#include <nxt_clang.h>
#include <nxt_alignment.h>
#include <nxt_string.h>
#include <nxt_stub.h>
#include <nxt_utf8.h>
#include <nxt_djb_hash.h>
#include <nxt_array.h>
#include <nxt_lvlhsh.h>
#include <nxt_random.h>
#include <nxt_mem_cache_pool.h>
#include <njscript.h>
#include <njs_vm.h>
#include <njs_number.h>
#include <njs_string.h>
#include <njs_object.h>
#include <njs_object_hash.h>
#include <njs_array.h>
#include <njs_function.h>
#include <njs_error.h>
#include <njs_extern.h>
#include <njs_variable.h>
#include <njs_parser.h>
#include <njs_regexp.h>
#include <string.h>
#include <stdio.h>
/* The values must be greater than NXT_OK. */
#define NJS_PRIMITIVE_VALUE 1
#define NJS_STRING_VALUE 2
#define NJS_ARRAY_VALUE 3
#define NJS_EXTERNAL_VALUE 4
/*
* NJS_PROPERTY_QUERY_GET must be less or equal to NJS_PROPERTY_QUERY_IN,
* NJS_PROPERTY_QUERY_SET and NJS_PROPERTY_QUERY_DELETE must be greater
* than NJS_PROPERTY_QUERY_IN.
*/
#define NJS_PROPERTY_QUERY_GET 0
#define NJS_PROPERTY_QUERY_IN 1
#define NJS_PROPERTY_QUERY_SET 2
#define NJS_PROPERTY_QUERY_DELETE 3
typedef struct {
nxt_lvlhsh_query_t lhq;
/* scratch is used to get the value of an NJS_NATIVE_GETTER property. */
njs_object_prop_t scratch;
njs_value_t value;
njs_object_t *prototype;
uint8_t query;
uint8_t shared;
} njs_property_query_t;
struct njs_property_next_s {
int32_t index;
nxt_lvlhsh_each_t lhe;
};
/*
* These functions are forbidden to inline to minimize JavaScript VM
* interpreter memory footprint. The size is less than 8K on AMD64
* and should fit in CPU L1 instruction cache.
*/
static nxt_noinline njs_ret_t njs_property_query(njs_vm_t *vm,
njs_property_query_t *pq, njs_value_t *object, njs_value_t *property);
static njs_ret_t njs_array_property_query(njs_vm_t *vm,
njs_property_query_t *pq, njs_value_t *object, uint32_t index);
static njs_ret_t njs_object_property_query(njs_vm_t *vm,
njs_property_query_t *pq, njs_value_t *value, njs_object_t *object);
static njs_ret_t njs_method_private_copy(njs_vm_t *vm,
njs_property_query_t *pq);
static nxt_noinline njs_ret_t njs_values_equal(const njs_value_t *val1,
const njs_value_t *val2);
static nxt_noinline njs_ret_t njs_values_compare(const njs_value_t *val1,
const njs_value_t *val2);
static njs_ret_t njs_function_frame_create(njs_vm_t *vm, njs_value_t *value,
const njs_value_t *this, uintptr_t nargs, nxt_bool_t ctor);
static njs_object_t *njs_function_new_object(njs_vm_t *vm, njs_value_t *value);
static void njs_vm_scopes_restore(njs_vm_t *vm, njs_frame_t *frame,
njs_native_frame_t *previous);
static njs_ret_t njs_vmcode_continuation(njs_vm_t *vm, njs_value_t *invld1,
njs_value_t *invld2);
static njs_native_frame_t *
njs_function_previous_frame(njs_native_frame_t *frame);
static njs_ret_t njs_function_frame_free(njs_vm_t *vm,
njs_native_frame_t *frame);
static void njs_vm_trap(njs_vm_t *vm, nxt_uint_t trap, njs_value_t *value1,
njs_value_t *value2);
static njs_ret_t njs_vm_trap_argument(njs_vm_t *vm, nxt_uint_t trap);
static njs_ret_t njs_vmcode_number_primitive(njs_vm_t *vm, njs_value_t *invld,
njs_value_t *narg);
static njs_ret_t njs_vmcode_string_primitive(njs_vm_t *vm, njs_value_t *invld,
njs_value_t *narg);
static njs_ret_t njs_vmcode_number_argument(njs_vm_t *vm, njs_value_t *invld1,
njs_value_t *inlvd2);
static njs_ret_t njs_vmcode_string_argument(njs_vm_t *vm, njs_value_t *invld1,
njs_value_t *inlvd2);
static njs_ret_t njs_primitive_value(njs_vm_t *vm, njs_value_t *value,
nxt_uint_t hint);
static njs_ret_t njs_vmcode_restart(njs_vm_t *vm, njs_value_t *invld1,
njs_value_t *invld2);
static njs_ret_t njs_object_value_to_string(njs_vm_t *vm, njs_value_t *value);
static njs_ret_t njs_vmcode_value_to_string(njs_vm_t *vm, njs_value_t *invld1,
njs_value_t *invld2);
static njs_ret_t njs_vm_add_backtrace_entry(njs_vm_t *vm, njs_frame_t *frame);
void njs_debug(njs_index_t index, njs_value_t *value);
const njs_value_t njs_value_null = njs_value(NJS_NULL, 0, 0.0);
const njs_value_t njs_value_void = njs_value(NJS_VOID, 0, NAN);
const njs_value_t njs_value_false = njs_value(NJS_BOOLEAN, 0, 0.0);
const njs_value_t njs_value_true = njs_value(NJS_BOOLEAN, 1, 1.0);
const njs_value_t njs_value_zero = njs_value(NJS_NUMBER, 0, 0.0);
const njs_value_t njs_value_nan = njs_value(NJS_NUMBER, 0, NAN);
const njs_value_t njs_value_invalid = njs_value(NJS_INVALID, 0, 0.0);
const njs_value_t njs_string_empty = njs_string("");
const njs_value_t njs_string_comma = njs_string(",");
const njs_value_t njs_string_null = njs_string("null");
const njs_value_t njs_string_void = njs_string("undefined");
const njs_value_t njs_string_boolean = njs_string("boolean");
const njs_value_t njs_string_false = njs_string("false");
const njs_value_t njs_string_true = njs_string("true");
const njs_value_t njs_string_number = njs_string("number");
const njs_value_t njs_string_minus_infinity =
njs_string("-Infinity");
const njs_value_t njs_string_plus_infinity =
njs_string("Infinity");
const njs_value_t njs_string_nan = njs_string("NaN");
const njs_value_t njs_string_string = njs_string("string");
const njs_value_t njs_string_object = njs_string("object");
const njs_value_t njs_string_function = njs_string("function");
const njs_value_t njs_string_memory_error = njs_string("MemoryError");
/*
* The nJSVM is optimized for an ABIs where the first several arguments
* are passed in registers (AMD64, ARM32/64): two pointers to the operand
* values is passed as arguments although they are not always used.
*/
nxt_noinline nxt_int_t
njs_vmcode_interpreter(njs_vm_t *vm)
{
u_char *catch;
njs_ret_t ret;
njs_value_t *retval, *value1, *value2;
njs_frame_t *frame;
njs_native_frame_t *previous;
njs_vmcode_generic_t *vmcode;
start:
for ( ;; ) {
vmcode = (njs_vmcode_generic_t *) vm->current;
/*
* 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_validate(),
* njs_vmcode_function_frame(),
* njs_vmcode_function_call(),
* njs_vmcode_return(),
* njs_vmcode_try_start(),
* njs_vmcode_try_next(),
* 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:
value2 = njs_vmcode_operand(vm, vmcode->operand3);
/* Fall through. */
case NJS_VMCODE_2OPERANDS:
value1 = njs_vmcode_operand(vm, vmcode->operand2);
}
ret = vmcode->code.operation(vm, value1, value2);
/*
* 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 (nxt_slow_path(ret < 0 && ret >= NJS_PREEMPT)) {
break;
}
vm->current += ret;
if (vmcode->code.retval) {
retval = njs_vmcode_operand(vm, vmcode->operand1);
//njs_release(vm, retval);
*retval = vm->retval;
}
}
switch (ret) {
case NJS_TRAP_NUMBER:
value2 = value1;
/* Fall through. */
case NJS_TRAP_NUMBERS:
case NJS_TRAP_STRINGS:
case NJS_TRAP_INCDEC:
case NJS_TRAP_PROPERTY:
njs_vm_trap(vm, ret - NJS_TRAP_BASE, value1, value2);
goto start;
case NJS_TRAP_NUMBER_ARG:
case NJS_TRAP_STRING_ARG:
ret = njs_vm_trap_argument(vm, ret - NJS_TRAP_BASE);
if (nxt_fast_path(ret == NXT_OK)) {
goto start;
}
break;
default:
break;
}
if (ret == NXT_ERROR) {
for ( ;; ) {
frame = (njs_frame_t *) vm->top_frame;
catch = frame->native.exception.catch;
if (catch != NULL) {
vm->current = catch;
if (vm->debug != NULL) {
nxt_array_reset(vm->backtrace);
}
goto start;
}
if (vm->debug != NULL
&& njs_vm_add_backtrace_entry(vm, frame) != NXT_OK)
{
return NXT_ERROR;
}
previous = frame->native.previous;
if (previous == NULL) {
return NXT_ERROR;
}
njs_vm_scopes_restore(vm, frame, previous);
if (frame->native.size != 0) {
vm->stack_size -= frame->native.size;
nxt_mem_cache_free(vm->mem_cache_pool, frame);
}
}
}
/* NXT_AGAIN, NJS_STOP. */
return ret;
}
nxt_noinline void
njs_value_retain(njs_value_t *value)
{
njs_string_t *string;
if (njs_is_string(value)) {
if (value->long_string.external != 0xff) {
string = value->long_string.data;
nxt_thread_log_debug("retain:%uxD \"%*s\"", string->retain,
value->long_string.size, string->start);
if (string->retain != 0xffff) {
string->retain++;
}
}
}
}
nxt_noinline void
njs_value_release(njs_vm_t *vm, njs_value_t *value)
{
njs_string_t *string;
if (njs_is_string(value)) {
if (value->long_string.external != 0xff) {
string = value->long_string.data;
nxt_thread_log_debug("release:%uxD \"%*s\"", string->retain,
value->long_string.size, string->start);
if (string->retain != 0xffff) {
string->retain--;
#if 0
if (string->retain == 0) {
if ((u_char *) string + sizeof(njs_string_t)
!= string->start)
{
nxt_memcache_pool_free(vm->mem_cache_pool,
string->start);
}
nxt_memcache_pool_free(vm->mem_cache_pool, string);
}
#endif
}
}
}
}
njs_ret_t
njs_vmcode_object(njs_vm_t *vm, njs_value_t *invld1, njs_value_t *invld2)
{
njs_object_t *object;
object = njs_object_alloc(vm);
if (nxt_fast_path(object != NULL)) {
vm->retval.data.u.object = object;
vm->retval.type = NJS_OBJECT;
vm->retval.data.truth = 1;
return sizeof(njs_vmcode_object_t);
}
return NXT_ERROR;
}
njs_ret_t
njs_vmcode_array(njs_vm_t *vm, njs_value_t *invld1, njs_value_t *invld2)
{
uint32_t length;
njs_array_t *array;
njs_value_t *value;
njs_vmcode_array_t *code;
code = (njs_vmcode_array_t *) vm->current;
array = njs_array_alloc(vm, code->length, NJS_ARRAY_SPARE);
if (nxt_fast_path(array != NULL)) {
if (code->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;
}
vm->retval.data.u.array = array;
vm->retval.type = NJS_ARRAY;
vm->retval.data.truth = 1;
return sizeof(njs_vmcode_array_t);
}
return NXT_ERROR;
}
njs_ret_t
njs_vmcode_function(njs_vm_t *vm, njs_value_t *invld1, njs_value_t *invld2)
{
size_t size;
nxt_uint_t n, nesting;
njs_function_t *function;
njs_function_lambda_t *lambda;
njs_vmcode_function_t *code;
code = (njs_vmcode_function_t *) vm->current;
lambda = code->lambda;
nesting = lambda->nesting;
size = sizeof(njs_function_t) + nesting * sizeof(njs_closure_t *);
function = nxt_mem_cache_zalloc(vm->mem_cache_pool, size);
if (nxt_slow_path(function == NULL)) {
return NXT_ERROR;
}
function->u.lambda = lambda;
function->object.shared_hash = vm->shared->function_prototype_hash;
function->object.__proto__ = &vm->prototypes[NJS_PROTOTYPE_FUNCTION].object;
function->object.extensible = 1;
function->args_offset = 1;
if (nesting != 0) {
function->closure = 1;
n = 0;
do {
/* GC: retain closure. */
function->closures[n] = vm->active_frame->closures[n];
n++;
} while (n < nesting);
}
vm->retval.data.u.function = function;
vm->retval.type = NJS_FUNCTION;
vm->retval.data.truth = 1;
return sizeof(njs_vmcode_function_t);
}
njs_ret_t
njs_vmcode_regexp(njs_vm_t *vm, njs_value_t *invld1, njs_value_t *invld2)
{
njs_regexp_t *regexp;
njs_vmcode_regexp_t *code;
code = (njs_vmcode_regexp_t *) vm->current;
regexp = njs_regexp_alloc(vm, code->pattern);
if (nxt_fast_path(regexp != NULL)) {
vm->retval.data.u.regexp = regexp;
vm->retval.type = NJS_REGEXP;
vm->retval.data.truth = 1;
return sizeof(njs_vmcode_regexp_t);
}
return NXT_ERROR;
}
njs_ret_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 (nxt_slow_path(object == NULL)) {
return NXT_ERROR;
}
break;
case NJS_FUNCTION:
function = njs_function_value_copy(vm, value);
if (nxt_slow_path(function == NULL)) {
return NXT_ERROR;
}
break;
default:
break;
}
vm->retval = *value;
njs_retain(value);
return sizeof(njs_vmcode_object_copy_t);
}
njs_ret_t
njs_vmcode_property_get(njs_vm_t *vm, njs_value_t *object,
njs_value_t *property)
{
void *obj;
int32_t index;
uintptr_t data;
njs_ret_t ret;
njs_value_t *val, ext_val;
njs_slice_prop_t slice;
njs_string_prop_t string;
njs_object_prop_t *prop;
const njs_value_t *retval;
const njs_extern_t *ext_proto;
njs_property_query_t pq;
pq.query = NJS_PROPERTY_QUERY_GET;
ret = njs_property_query(vm, &pq, object, property);
retval = &njs_value_void;
switch (ret) {
case NXT_OK:
prop = pq.lhq.value;
switch (prop->type) {
case NJS_METHOD:
if (pq.shared) {
ret = njs_method_private_copy(vm, &pq);
if (nxt_slow_path(ret != NXT_OK)) {
return ret;
}
prop = pq.lhq.value;
}
/* Fall through. */
case NJS_PROPERTY:
retval = &prop->value;
break;
default:
nxt_thread_log_alert("invalid property get type:%d", prop->type);
return NXT_ERROR;
}
break;
case NXT_DECLINED:
case NJS_PRIMITIVE_VALUE:
break;
case NJS_STRING_VALUE:
/* string[n]. */
index = (int32_t) njs_value_to_index(property);
if (nxt_fast_path(index >= 0)) {
slice.start = index;
slice.length = 1;
slice.string_length = njs_string_prop(&string, object);
if (slice.start < slice.string_length) {
/*
* A single codepoint string fits in vm->retval
* so the function cannot fail.
*/
(void) njs_string_slice(vm, &vm->retval, &string, &slice);
return sizeof(njs_vmcode_prop_get_t);
}
}
break;
case NJS_ARRAY_VALUE:
val = pq.lhq.value;
if (njs_is_valid(val)) {
retval = val;
}
break;
case NJS_EXTERNAL_VALUE:
ext_proto = object->external.proto;
ret = nxt_lvlhsh_find(&ext_proto->hash, &pq.lhq);
if (ret == NXT_OK) {
ext_proto = pq.lhq.value;
ext_val.type = NJS_EXTERNAL;
ext_val.data.truth = 1;
ext_val.external.proto = ext_proto;
ext_val.external.index = object->external.index;
if ((ext_proto->type & NJS_EXTERN_OBJECT) != 0) {
retval = &ext_val;
break;
}
data = ext_proto->data;
} else {
data = (uintptr_t) &pq.lhq.key;
}
vm->retval = njs_value_void;
if (ext_proto->get != NULL) {
obj = njs_extern_object(vm, object);
ret = ext_proto->get(vm, &vm->retval, obj, data);
if (nxt_slow_path(ret != NXT_OK)) {
return ret;
}
/* The vm->retval is already retained by ext_proto->get(). */
}
return sizeof(njs_vmcode_prop_get_t);
default:
/* NJS_TRAP_PROPERTY */
/* NXT_ERROR */
return ret;
}
vm->retval = *retval;
/* GC: njs_retain(retval) */
return sizeof(njs_vmcode_prop_get_t);
}
njs_ret_t
njs_vmcode_property_set(njs_vm_t *vm, njs_value_t *object,
njs_value_t *property)
{
void *obj;
uintptr_t data;
nxt_str_t s;
njs_ret_t ret;
njs_value_t *p, *value;
njs_object_prop_t *prop;
const njs_extern_t *ext_proto;
njs_property_query_t pq;
njs_vmcode_prop_set_t *code;
if (njs_is_primitive(object)) {
njs_exception_type_error(vm, "property set on primitive %s type",
njs_type_string(object->type));
return NXT_ERROR;
}
code = (njs_vmcode_prop_set_t *) vm->current;
value = njs_vmcode_operand(vm, code->value);
pq.query = NJS_PROPERTY_QUERY_SET;
ret = njs_property_query(vm, &pq, object, property);
switch (ret) {
case NXT_OK:
prop = pq.lhq.value;
break;
case NXT_DECLINED:
if (!object->data.u.object->extensible) {
return sizeof(njs_vmcode_prop_set_t);
}
prop = njs_object_prop_alloc(vm, &pq.value, &njs_value_void, 1);
if (nxt_slow_path(prop == NULL)) {
return NXT_ERROR;
}
pq.lhq.replace = 0;
pq.lhq.value = prop;
pq.lhq.pool = vm->mem_cache_pool;
ret = nxt_lvlhsh_insert(&object->data.u.object->hash, &pq.lhq);
if (nxt_slow_path(ret != NXT_OK)) {
/* Only NXT_ERROR can be returned here. */
return ret;
}
break;
case NJS_PRIMITIVE_VALUE:
case NJS_STRING_VALUE:
return sizeof(njs_vmcode_prop_set_t);
case NJS_ARRAY_VALUE:
p = pq.lhq.value;
*p = *value;
return sizeof(njs_vmcode_prop_set_t);
case NJS_EXTERNAL_VALUE:
ext_proto = object->external.proto;
ret = nxt_lvlhsh_find(&ext_proto->hash, &pq.lhq);
if (ret == NXT_OK) {
ext_proto = pq.lhq.value;
data = ext_proto->data;
} else {
data = (uintptr_t) &pq.lhq.key;
}
if (ext_proto->set != NULL) {
ret = njs_vm_value_to_ext_string(vm, &s, value, 0);
if (nxt_slow_path(ret != NXT_OK)) {
return ret;
}
/* TODO retain value if it is string. */
obj = njs_extern_object(vm, object);
ret = ext_proto->set(vm, obj, data, &s);
if (nxt_slow_path(ret != NXT_OK)) {
return ret;
}
}
return sizeof(njs_vmcode_prop_set_t);
default:
/* NJS_TRAP_PROPERTY */
/* NXT_ERROR */
return ret;
}
if (prop->writable) {
prop->value = *value;
}
return sizeof(njs_vmcode_prop_set_t);
}
njs_ret_t
njs_vmcode_property_in(njs_vm_t *vm, njs_value_t *object, njs_value_t *property)
{
void *obj;
uintptr_t data;
njs_ret_t ret;
njs_value_t *value;
const njs_value_t *retval;
const njs_extern_t *ext_proto;
njs_property_query_t pq;
retval = &njs_value_false;
pq.query = NJS_PROPERTY_QUERY_IN;
ret = njs_property_query(vm, &pq, object, property);
switch (ret) {
case NXT_OK:
retval = &njs_value_true;
break;
case NXT_DECLINED:
break;
case NJS_PRIMITIVE_VALUE:
case NJS_STRING_VALUE:
njs_exception_type_error(vm, "property in on a primitive value", NULL);
return NXT_ERROR;
case NJS_ARRAY_VALUE:
value = pq.lhq.value;
if (njs_is_valid(value)) {
retval = &njs_value_true;
}
break;
case NJS_EXTERNAL_VALUE:
ext_proto = object->external.proto;
ret = nxt_lvlhsh_find(&ext_proto->hash, &pq.lhq);
if (ret == NXT_OK) {
retval = &njs_value_true;
} else {
data = (uintptr_t) &pq.lhq.key;
if (ext_proto->find != NULL) {
obj = njs_extern_object(vm, object);
ret = ext_proto->find(vm, obj, data, 0);
if (nxt_slow_path(ret == NXT_ERROR)) {
return ret;
}
if (ret == NXT_OK) {
retval = &njs_value_true;
}
}
}
break;
default:
/* NJS_TRAP_PROPERTY */
/* NXT_ERROR */
return ret;
}
vm->retval = *retval;
return sizeof(njs_vmcode_3addr_t);
}
njs_ret_t
njs_vmcode_property_delete(njs_vm_t *vm, njs_value_t *object,
njs_value_t *property)
{
void *obj;
uintptr_t data;
njs_ret_t ret;
njs_value_t *value, ext_val;
const njs_value_t *retval;
njs_object_prop_t *prop;
const njs_extern_t *ext_proto;
njs_property_query_t pq;
retval = &njs_value_false;
pq.query = NJS_PROPERTY_QUERY_DELETE;
ret = njs_property_query(vm, &pq, object, property);
switch (ret) {
case NXT_OK:
prop = pq.lhq.value;
if (prop->configurable) {
pq.lhq.pool = vm->mem_cache_pool;
(void) nxt_lvlhsh_delete(&object->data.u.object->hash, &pq.lhq);
njs_release(vm, property);
retval = &njs_value_true;
}
break;
case NXT_DECLINED:
case NJS_PRIMITIVE_VALUE:
case NJS_STRING_VALUE:
break;
case NJS_ARRAY_VALUE:
value = pq.lhq.value;
njs_set_invalid(value);
retval = &njs_value_true;
break;
case NJS_EXTERNAL_VALUE:
ext_proto = object->external.proto;
ret = nxt_lvlhsh_find(&ext_proto->hash, &pq.lhq);
if (ret == NXT_OK) {
ext_proto = pq.lhq.value;
if ((ext_proto->type & NJS_EXTERN_OBJECT) != 0) {
ext_val.type = NJS_EXTERNAL;
ext_val.data.truth = 1;
ext_val.external.proto = ext_proto;
ext_val.external.index = object->external.index;
data = (uintptr_t) &ext_val;
} else {
data = ext_proto->data;
}
} else {
data = (uintptr_t) &pq.lhq.key;
}
if (ext_proto->find != NULL) {
obj = njs_extern_object(vm, object);
ret = ext_proto->find(vm, obj, data, 1);
if (nxt_slow_path(ret == NXT_ERROR)) {
return ret;
}
if (ret == NXT_OK) {
retval = &njs_value_true;
}
}
break;
default:
/* NJS_TRAP_PROPERTY */
/* NXT_ERROR */
return ret;
}
vm->retval = *retval;
return sizeof(njs_vmcode_3addr_t);
}
/*
* The njs_property_query() returns values
* NXT_OK property has been found in object,
* NXT_DECLINED property was not found in object,
* NJS_PRIMITIVE_VALUE property operation was applied to a numeric
* or boolean value,
* NJS_STRING_VALUE property operation was applied to a string,
* NJS_ARRAY_VALUE object is array,
* NJS_EXTERNAL_VALUE object is external entity,
* NJS_TRAP_PROPERTY the property trap must be called,
* NXT_ERROR exception has been thrown.
*/
static nxt_noinline njs_ret_t
njs_property_query(njs_vm_t *vm, njs_property_query_t *pq, njs_value_t *object,
njs_value_t *property)
{
uint32_t index;
uint32_t (*hash)(const void *, size_t);
njs_ret_t ret;
njs_object_t *obj;
njs_function_t *function;
const njs_extern_t *ext_proto;
hash = nxt_djb_hash;
switch (object->type) {
case NJS_BOOLEAN:
case NJS_NUMBER:
if (pq->query != NJS_PROPERTY_QUERY_GET) {
return NJS_PRIMITIVE_VALUE;
}
index = njs_primitive_prototype_index(object->type);
obj = &vm->prototypes[index].object;
break;
case NJS_STRING:
if (pq->query == NJS_PROPERTY_QUERY_DELETE) {
return NXT_DECLINED;
}
obj = &vm->prototypes[NJS_PROTOTYPE_STRING].object;
break;
case NJS_ARRAY:
if (nxt_fast_path(!njs_is_null_or_void_or_boolean(property))) {
if (nxt_fast_path(njs_is_primitive(property))) {
index = njs_value_to_index(property);
if (nxt_fast_path(index < NJS_ARRAY_MAX_LENGTH)) {
return njs_array_property_query(vm, pq, object, index);
}
} else {
return NJS_TRAP_PROPERTY;
}
}
/* Fall through. */
case NJS_OBJECT:
case NJS_OBJECT_BOOLEAN:
case NJS_OBJECT_NUMBER:
case NJS_OBJECT_STRING:
case NJS_REGEXP:
case NJS_DATE:
case NJS_OBJECT_ERROR:
case NJS_OBJECT_EVAL_ERROR:
case NJS_OBJECT_INTERNAL_ERROR:
case NJS_OBJECT_RANGE_ERROR:
case NJS_OBJECT_REF_ERROR:
case NJS_OBJECT_SYNTAX_ERROR:
case NJS_OBJECT_TYPE_ERROR:
case NJS_OBJECT_URI_ERROR:
obj = object->data.u.object;
break;
case NJS_FUNCTION:
function = njs_function_value_copy(vm, object);
if (nxt_slow_path(function == NULL)) {
return NXT_ERROR;
}
obj = &function->object;
break;
case NJS_EXTERNAL:
ext_proto = object->external.proto;
if (ext_proto->type == NJS_EXTERN_CASELESS_OBJECT) {
hash = nxt_djb_hash_lowcase;
}
obj = NULL;
break;
default: /* NJS_VOID, NJS_NULL. */
if (nxt_fast_path(njs_is_primitive(property))) {
ret = njs_primitive_value_to_string(vm, &pq->value, property);
if (nxt_fast_path(ret == NXT_OK)) {
njs_string_get(&pq->value, &pq->lhq.key);
njs_exception_type_error(vm,
"cannot get property '%.*s' of undefined",
(int) pq->lhq.key.length,
pq->lhq.key.start);
return NXT_ERROR;
}
}
njs_exception_type_error(vm,
"cannot get property 'unknown' of undefined", NULL);
return NXT_ERROR;
}
if (nxt_fast_path(njs_is_primitive(property))) {
ret = njs_primitive_value_to_string(vm, &pq->value, property);
if (nxt_fast_path(ret == NXT_OK)) {
njs_string_get(&pq->value, &pq->lhq.key);
pq->lhq.key_hash = hash(pq->lhq.key.start, pq->lhq.key.length);
if (obj == NULL) {
pq->lhq.proto = &njs_extern_hash_proto;
return NJS_EXTERNAL_VALUE;
}
return njs_object_property_query(vm, pq, object, obj);
}
return ret;
}
return NJS_TRAP_PROPERTY;
}
static njs_ret_t
njs_array_property_query(njs_vm_t *vm, njs_property_query_t *pq,
njs_value_t *object, uint32_t index)
{
uint32_t size;
njs_ret_t ret;
njs_value_t *value;
njs_array_t *array;
array = object->data.u.array;
if (index >= array->length) {
if (pq->query != NJS_PROPERTY_QUERY_SET) {
return NXT_DECLINED;
}
size = index - array->length;
ret = njs_array_expand(vm, array, 0, size + 1);
if (nxt_slow_path(ret != NXT_OK)) {
return ret;
}
value = &array->start[array->length];
while (size != 0) {
njs_set_invalid(value);
value++;
size--;
}
array->length = index + 1;
}
pq->lhq.value = &array->start[index];
return NJS_ARRAY_VALUE;
}
static njs_ret_t
njs_object_property_query(njs_vm_t *vm, njs_property_query_t *pq,
njs_value_t *value, njs_object_t *object)
{
njs_ret_t ret;
njs_object_prop_t *prop;
pq->lhq.proto = &njs_object_hash_proto;
do {
pq->prototype = object;
ret = nxt_lvlhsh_find(&object->hash, &pq->lhq);
if (ret == NXT_OK) {
prop = pq->lhq.value;
if (prop->type != NJS_WHITEOUT) {
pq->shared = 0;
return ret;
}
goto next;
}
if (pq->query > NJS_PROPERTY_QUERY_IN) {
/* NXT_DECLINED */
return ret;
}
ret = nxt_lvlhsh_find(&object->shared_hash, &pq->lhq);
if (ret == NXT_OK) {
pq->shared = 1;
if (pq->query == NJS_PROPERTY_QUERY_GET) {
prop = pq->lhq.value;
if (prop->type == NJS_NATIVE_GETTER) {
pq->scratch = *prop;
prop = &pq->scratch;
ret = prop->value.data.u.getter(vm, value, &prop->value);
if (nxt_fast_path(ret == NXT_OK)) {
prop->type = NJS_PROPERTY;
pq->lhq.value = prop;
}
}
}
return ret;
}
if (pq->query > NJS_PROPERTY_QUERY_IN) {
/* NXT_DECLINED */
return ret;
}
next:
object = object->__proto__;
} while (object != NULL);
if (njs_is_string(value)) {
return NJS_STRING_VALUE;
}
/* NXT_DECLINED */
return ret;
}
static njs_ret_t
njs_method_private_copy(njs_vm_t *vm, njs_property_query_t *pq)
{
njs_function_t *function;
njs_object_prop_t *prop, *shared;
prop = nxt_mem_cache_alloc(vm->mem_cache_pool, sizeof(njs_object_prop_t));
if (nxt_slow_path(prop == NULL)) {
return NXT_ERROR;
}
shared = pq->lhq.value;
*prop = *shared;
function = njs_function_value_copy(vm, &prop->value);
if (nxt_slow_path(function == NULL)) {
return NXT_ERROR;
}
pq->lhq.replace = 0;
pq->lhq.value = prop;
pq->lhq.pool = vm->mem_cache_pool;
return nxt_lvlhsh_insert(&pq->prototype->hash, &pq->lhq);
}
njs_ret_t
njs_vmcode_property_foreach(njs_vm_t *vm, njs_value_t *object,
njs_value_t *invld)
{
void *obj;
njs_ret_t ret;
njs_property_next_t *next;
const njs_extern_t *ext_proto;
njs_vmcode_prop_foreach_t *code;
if (njs_is_object(object)) {
next = nxt_mem_cache_alloc(vm->mem_cache_pool,
sizeof(njs_property_next_t));
if (nxt_slow_path(next == NULL)) {
return NXT_ERROR;
}
vm->retval.data.u.next = next;
nxt_lvlhsh_each_init(&next->lhe, &njs_object_hash_proto);
next->index = -1;
if (njs_is_array(object) && object->data.u.array->length != 0) {
next->index = 0;
}
} else if (njs_is_external(object)) {
ext_proto = object->external.proto;
if (ext_proto->foreach != NULL) {
obj = njs_extern_object(vm, object);
ret = ext_proto->foreach(vm, obj, &vm->retval);
if (nxt_slow_path(ret != NXT_OK)) {
return ret;
}
}
}
code = (njs_vmcode_prop_foreach_t *) vm->current;
return code->offset;
}
njs_ret_t
njs_vmcode_property_next(njs_vm_t *vm, njs_value_t *object, njs_value_t *value)
{
void *obj;
njs_ret_t ret;
nxt_uint_t n;
njs_value_t *retval;
njs_array_t *array;
njs_object_prop_t *prop;
njs_property_next_t *next;
const njs_extern_t *ext_proto;
njs_vmcode_prop_next_t *code;
code = (njs_vmcode_prop_next_t *) vm->current;
retval = njs_vmcode_operand(vm, code->retval);
if (njs_is_object(object)) {
next = value->data.u.next;
if (next->index >= 0) {
array = object->data.u.array;
while ((uint32_t) next->index < array->length) {
n = next->index++;
if (njs_is_valid(&array->start[n])) {
njs_value_number_set(retval, n);
return code->offset;
}
}
next->index = -1;
}
prop = nxt_lvlhsh_each(&object->data.u.object->hash, &next->lhe);
if (prop != NULL) {
*retval = prop->name;
return code->offset;
}
nxt_mem_cache_free(vm->mem_cache_pool, next);
} else if (njs_is_external(object)) {
ext_proto = object->external.proto;
if (ext_proto->next != NULL) {
obj = njs_extern_object(vm, object);
ret = ext_proto->next(vm, retval, obj, value);
if (ret == NXT_OK) {
return code->offset;
}
if (nxt_slow_path(ret == NXT_ERROR)) {
return ret;
}
/* ret == NJS_DONE. */
}
}
return sizeof(njs_vmcode_prop_next_t);
}
njs_ret_t
njs_vmcode_instance_of(njs_vm_t *vm, njs_value_t *object,
njs_value_t *constructor)
{
nxt_int_t ret;
njs_value_t *value;
njs_object_t *prototype, *proto;
njs_object_prop_t *prop;
const njs_value_t *retval;
njs_property_query_t pq;
static njs_value_t prototype_string = njs_string("prototype");
if (!njs_is_function(constructor)) {
njs_exception_type_error(vm, "right argument is not a function", NULL);
return NXT_ERROR;
}
retval = &njs_value_false;
if (njs_is_object(object)) {
pq.query = NJS_PROPERTY_QUERY_GET;
ret = njs_property_query(vm, &pq, constructor, &prototype_string);
if (nxt_fast_path(ret == NXT_OK)) {
prop = pq.lhq.value;
value = &prop->value;
/* TODO: test prop->value is object. */
prototype = value->data.u.object;
proto = object->data.u.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);
}
/*
* The increment and decrement operations require only one value parameter.
* However, if the value is not numeric, then the trap is generated and
* value parameter points to a trap frame value converted to a numeric.
* So the additional reference parameter points to the original value.
*/
njs_ret_t
njs_vmcode_increment(njs_vm_t *vm, njs_value_t *reference, njs_value_t *value)
{
double num;
if (nxt_fast_path(njs_is_numeric(value))) {
num = value->data.u.number + 1.0;
njs_release(vm, reference);
njs_value_number_set(reference, num);
vm->retval = *reference;
return sizeof(njs_vmcode_3addr_t);
}
return NJS_TRAP_INCDEC;
}
njs_ret_t
njs_vmcode_decrement(njs_vm_t *vm, njs_value_t *reference, njs_value_t *value)
{
double num;
if (nxt_fast_path(njs_is_numeric(value))) {
num = value->data.u.number - 1.0;
njs_release(vm, reference);
njs_value_number_set(reference, num);
vm->retval = *reference;
return sizeof(njs_vmcode_3addr_t);
}
return NJS_TRAP_INCDEC;
}
njs_ret_t
njs_vmcode_post_increment(njs_vm_t *vm, njs_value_t *reference,
njs_value_t *value)
{
double num;
if (nxt_fast_path(njs_is_numeric(value))) {
num = value->data.u.number;
njs_release(vm, reference);
njs_value_number_set(reference, num + 1.0);
njs_value_number_set(&vm->retval, num);
return sizeof(njs_vmcode_3addr_t);
}
return NJS_TRAP_INCDEC;
}
njs_ret_t
njs_vmcode_post_decrement(njs_vm_t *vm, njs_value_t *reference,
njs_value_t *value)
{
double num;
if (nxt_fast_path(njs_is_numeric(value))) {
num = value->data.u.number;
njs_release(vm, reference);
njs_value_number_set(reference, num - 1.0);
njs_value_number_set(&vm->retval, num);
return sizeof(njs_vmcode_3addr_t);
}
return NJS_TRAP_INCDEC;
}
njs_ret_t
njs_vmcode_typeof(njs_vm_t *vm, njs_value_t *value, njs_value_t *invld)
{
nxt_uint_t type;
/* ECMAScript 5.1: null, array and regexp are objects. */
static const njs_value_t *types[] = {
&njs_string_object,
&njs_string_void,
&njs_string_boolean,
&njs_string_number,
&njs_string_string,
&njs_string_void,
&njs_string_void,
&njs_string_void,
&njs_string_void,
&njs_string_void,
&njs_string_void,
&njs_string_void,
&njs_string_void,
&njs_string_void,
&njs_string_void,
&njs_string_void,
&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,
&njs_string_object,
&njs_string_object,
&njs_string_object,
&njs_string_object,
};
/* A zero index means non-declared variable. */
type = (value != NULL) ? value->type : NJS_VOID;
vm->retval = *types[type];
return sizeof(njs_vmcode_2addr_t);
}
njs_ret_t
njs_vmcode_void(njs_vm_t *vm, njs_value_t *invld1, njs_value_t *invld2)
{
vm->retval = njs_value_void;
return sizeof(njs_vmcode_2addr_t);
}
njs_ret_t
njs_vmcode_delete(njs_vm_t *vm, njs_value_t *value, njs_value_t *invld)
{
njs_release(vm, value);
vm->retval = njs_value_true;
return sizeof(njs_vmcode_2addr_t);
}
njs_ret_t
njs_vmcode_unary_plus(njs_vm_t *vm, njs_value_t *value, njs_value_t *invld)
{
if (nxt_fast_path(njs_is_numeric(value))) {
njs_value_number_set(&vm->retval, value->data.u.number);
return sizeof(njs_vmcode_2addr_t);
}
return NJS_TRAP_NUMBER;
}
njs_ret_t
njs_vmcode_unary_negation(njs_vm_t *vm, njs_value_t *value, njs_value_t *invld)
{
if (nxt_fast_path(njs_is_numeric(value))) {
njs_value_number_set(&vm->retval, - value->data.u.number);
return sizeof(njs_vmcode_2addr_t);
}
return NJS_TRAP_NUMBER;
}
njs_ret_t
njs_vmcode_addition(njs_vm_t *vm, njs_value_t *val1, njs_value_t *val2)
{
double num;
u_char *start;
size_t size, length;
njs_string_prop_t string1, string2;
if (nxt_fast_path(njs_is_numeric(val1) && njs_is_numeric(val2))) {
num = val1->data.u.number + val2->data.u.number;
njs_value_number_set(&vm->retval, num);
return sizeof(njs_vmcode_3addr_t);
}
if (nxt_fast_path(njs_is_string(val1) && njs_is_string(val2))) {
(void) njs_string_prop(&string1, val1);
(void) njs_string_prop(&string2, val2);
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 (nxt_slow_path(start == NULL)) {
return NXT_ERROR;
}
(void) memcpy(start, string1.start, string1.size);
(void) memcpy(start + string1.size, string2.start, string2.size);
return sizeof(njs_vmcode_3addr_t);
}
return NJS_TRAP_STRINGS;
}
njs_ret_t
njs_vmcode_substraction(njs_vm_t *vm, njs_value_t *val1, njs_value_t *val2)
{
double num;
if (nxt_fast_path(njs_is_numeric(val1) && njs_is_numeric(val2))) {
num = val1->data.u.number - val2->data.u.number;
njs_value_number_set(&vm->retval, num);
return sizeof(njs_vmcode_3addr_t);
}
return NJS_TRAP_NUMBERS;
}
njs_ret_t
njs_vmcode_multiplication(njs_vm_t *vm, njs_value_t *val1, njs_value_t *val2)
{
double num;
if (nxt_fast_path(njs_is_numeric(val1) && njs_is_numeric(val2))) {
num = val1->data.u.number * val2->data.u.number;
njs_value_number_set(&vm->retval, num);
return sizeof(njs_vmcode_3addr_t);
}
return NJS_TRAP_NUMBERS;
}
njs_ret_t
njs_vmcode_exponentiation(njs_vm_t *vm, njs_value_t *val1, njs_value_t *val2)
{
double num, base, exponent;
nxt_bool_t valid;
if (nxt_fast_path(njs_is_numeric(val1) && njs_is_numeric(val2))) {
base = val1->data.u.number;
exponent = val2->data.u.number;
/*
* According to ES7:
* 1. If exponent is NaN, the result should be NaN;
* 2. The result of +/-1 ** +/-Infinity should be NaN.
*/
valid = nxt_expect(1, fabs(base) != 1
|| (!isnan(exponent) && !isinf(exponent)));
if (valid) {
num = pow(base, exponent);
} else {
num = NAN;
}
njs_value_number_set(&vm->retval, num);
return sizeof(njs_vmcode_3addr_t);
}
return NJS_TRAP_NUMBERS;
}
njs_ret_t
njs_vmcode_division(njs_vm_t *vm, njs_value_t *val1, njs_value_t *val2)
{
double num;
if (nxt_fast_path(njs_is_numeric(val1) && njs_is_numeric(val2))) {
num = val1->data.u.number / val2->data.u.number;
njs_value_number_set(&vm->retval, num);
return sizeof(njs_vmcode_3addr_t);
}
return NJS_TRAP_NUMBERS;
}
njs_ret_t
njs_vmcode_remainder(njs_vm_t *vm, njs_value_t *val1, njs_value_t *val2)
{
double num;
if (nxt_fast_path(njs_is_numeric(val1) && njs_is_numeric(val2))) {
num = fmod(val1->data.u.number, val2->data.u.number);
njs_value_number_set(&vm->retval, num);
return sizeof(njs_vmcode_3addr_t);
}
return NJS_TRAP_NUMBERS;
}
njs_ret_t
njs_vmcode_left_shift(njs_vm_t *vm, njs_value_t *val1, njs_value_t *val2)
{
int32_t num1;
uint32_t num2;
if (nxt_fast_path(njs_is_numeric(val1) && njs_is_numeric(val2))) {
num1 = njs_number_to_integer(val1->data.u.number);
num2 = njs_number_to_integer(val2->data.u.number);
njs_value_number_set(&vm->retval, num1 << (num2 & 0x1f));
return sizeof(njs_vmcode_3addr_t);
}
return NJS_TRAP_NUMBERS;
}
njs_ret_t
njs_vmcode_right_shift(njs_vm_t *vm, njs_value_t *val1, njs_value_t *val2)
{
int32_t num1;
uint32_t num2;
if (nxt_fast_path(njs_is_numeric(val1) && njs_is_numeric(val2))) {
num1 = njs_number_to_integer(val1->data.u.number);
num2 = njs_number_to_integer(val2->data.u.number);
njs_value_number_set(&vm->retval, num1 >> (num2 & 0x1f));
return sizeof(njs_vmcode_3addr_t);
}
return NJS_TRAP_NUMBERS;
}
njs_ret_t
njs_vmcode_unsigned_right_shift(njs_vm_t *vm, njs_value_t *val1,
njs_value_t *val2)
{
int32_t num2;
uint32_t num1;
if (nxt_fast_path(njs_is_numeric(val1) && njs_is_numeric(val2))) {
num1 = njs_number_to_integer(val1->data.u.number);
num2 = njs_number_to_integer(val2->data.u.number);
njs_value_number_set(&vm->retval, num1 >> (num2 & 0x1f));
return sizeof(njs_vmcode_3addr_t);
}
return NJS_TRAP_NUMBERS;
}
njs_ret_t
njs_vmcode_logical_not(njs_vm_t *vm, njs_value_t *value, njs_value_t *inlvd)
{
const njs_value_t *retval;
if (njs_is_true(value)) {
retval = &njs_value_false;
} else {
retval = &njs_value_true;
}
vm->retval = *retval;
return sizeof(njs_vmcode_2addr_t);
}
njs_ret_t
njs_vmcode_test_if_true(njs_vm_t *vm, njs_value_t *value, njs_value_t *invld)
{
njs_vmcode_test_jump_t *test_jump;
vm->retval = *value;
if (njs_is_true(value)) {
test_jump = (njs_vmcode_test_jump_t *) vm->current;
return test_jump->offset;
}
return sizeof(njs_vmcode_3addr_t);
}
njs_ret_t
njs_vmcode_test_if_false(njs_vm_t *vm, njs_value_t *value, njs_value_t *invld)
{
njs_vmcode_test_jump_t *test_jump;
vm->retval = *value;
if (!njs_is_true(value)) {
test_jump = (njs_vmcode_test_jump_t *) vm->current;
return test_jump->offset;
}
return sizeof(njs_vmcode_3addr_t);
}
njs_ret_t
njs_vmcode_bitwise_not(njs_vm_t *vm, njs_value_t *value, njs_value_t *invld)
{
int32_t num;
if (nxt_fast_path(njs_is_numeric(value))) {
num = njs_number_to_integer(value->data.u.number);
njs_value_number_set(&vm->retval, ~num);
return sizeof(njs_vmcode_2addr_t);
}
return NJS_TRAP_NUMBER;
}
njs_ret_t
njs_vmcode_bitwise_and(njs_vm_t *vm, njs_value_t *val1, njs_value_t *val2)
{
int32_t num1, num2;
if (nxt_fast_path(njs_is_numeric(val1) && njs_is_numeric(val2))) {
num1 = njs_number_to_integer(val1->data.u.number);
num2 = njs_number_to_integer(val2->data.u.number);
njs_value_number_set(&vm->retval, num1 & num2);
return sizeof(njs_vmcode_3addr_t);
}
return NJS_TRAP_NUMBERS;
}
njs_ret_t
njs_vmcode_bitwise_xor(njs_vm_t *vm, njs_value_t *val1, njs_value_t *val2)
{
int32_t num1, num2;
if (nxt_fast_path(njs_is_numeric(val1) && njs_is_numeric(val2))) {
num1 = njs_number_to_integer(val1->data.u.number);
num2 = njs_number_to_integer(val2->data.u.number);
njs_value_number_set(&vm->retval, num1 ^ num2);
return sizeof(njs_vmcode_3addr_t);
}
return NJS_TRAP_NUMBERS;
}
njs_ret_t
njs_vmcode_bitwise_or(njs_vm_t *vm, njs_value_t *val1, njs_value_t *val2)
{
int32_t num1, num2;
if (nxt_fast_path(njs_is_numeric(val1) && njs_is_numeric(val2))) {
num1 = njs_number_to_integer(val1->data.u.number);
num2 = njs_number_to_integer(val2->data.u.number);
njs_value_number_set(&vm->retval, num1 | num2);
return sizeof(njs_vmcode_3addr_t);
}
return NJS_TRAP_NUMBERS;
}
njs_ret_t
njs_vmcode_equal(njs_vm_t *vm, njs_value_t *val1, njs_value_t *val2)
{
njs_ret_t ret;
const njs_value_t *retval;
ret = njs_values_equal(val1, val2);
if (nxt_fast_path(ret >= 0)) {
retval = (ret != 0) ? &njs_value_true : &njs_value_false;
vm->retval = *retval;
return sizeof(njs_vmcode_3addr_t);
}
return ret;
}
njs_ret_t
njs_vmcode_not_equal(njs_vm_t *vm, njs_value_t *val1, njs_value_t *val2)
{
njs_ret_t ret;
const njs_value_t *retval;
ret = njs_values_equal(val1, val2);
if (nxt_fast_path(ret >= 0)) {
retval = (ret == 0) ? &njs_value_true : &njs_value_false;
vm->retval = *retval;
return sizeof(njs_vmcode_3addr_t);
}
return ret;
}
static nxt_noinline njs_ret_t
njs_values_equal(const njs_value_t *val1, const njs_value_t *val2)
{
/* Void and null are equal and not comparable with anything else. */
if (njs_is_null_or_void(val1)) {
return (njs_is_null_or_void(val2));
}
if (njs_is_numeric(val1) && njs_is_numeric(val2)) {
/* NaNs and Infinities are handled correctly by comparision. */
return (val1->data.u.number == val2->data.u.number);
}
if (val1->type == val2->type) {
if (njs_is_string(val1)) {
return njs_string_eq(val1, val2);
}
return (val1->data.u.object == val2->data.u.object);
}
return NJS_TRAP_NUMBERS;
}
nxt_noinline njs_ret_t
njs_vmcode_less(njs_vm_t *vm, njs_value_t *val1, njs_value_t *val2)
{
njs_ret_t ret;
const njs_value_t *retval;
ret = njs_values_compare(val1, val2);
if (nxt_fast_path(ret >= -1)) {
retval = (ret > 0) ? &njs_value_true : &njs_value_false;
vm->retval = *retval;
return sizeof(njs_vmcode_3addr_t);
}
return ret;
}
njs_ret_t
njs_vmcode_greater(njs_vm_t *vm, njs_value_t *val1, njs_value_t *val2)
{
return njs_vmcode_less(vm, val2, val1);
}
njs_ret_t
njs_vmcode_less_or_equal(njs_vm_t *vm, njs_value_t *val1, njs_value_t *val2)
{
return njs_vmcode_greater_or_equal(vm, val2, val1);
}
nxt_noinline njs_ret_t
njs_vmcode_greater_or_equal(njs_vm_t *vm, njs_value_t *val1, njs_value_t *val2)
{
njs_ret_t ret;
const njs_value_t *retval;
ret = njs_values_compare(val1, val2);
if (nxt_fast_path(ret >= -1)) {
retval = (ret == 0) ? &njs_value_true : &njs_value_false;
vm->retval = *retval;
return sizeof(njs_vmcode_3addr_t);
}
return ret;
}
/*
* njs_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,
* or negative trap number if convertion to primitive is required.
*/
static nxt_noinline njs_ret_t
njs_values_compare(const njs_value_t *val1, const njs_value_t *val2)
{
if (nxt_fast_path(njs_is_numeric(val1) || njs_is_numeric(val2))) {
if (nxt_fast_path(njs_is_numeric(val1) && njs_is_numeric(val2))) {
/* NaN and void values are not comparable with anything. */
if (isnan(val1->data.u.number) || isnan(val2->data.u.number)) {
return -1;
}
/* Infinities are handled correctly by comparision. */
return (val1->data.u.number < val2->data.u.number);
}
return NJS_TRAP_NUMBERS;
}
if (nxt_fast_path(njs_is_string(val1) && njs_is_string(val2))) {
return (njs_string_cmp(val1, val2) < 0) ? 1 : 0;
}
return NJS_TRAP_STRINGS;
}
njs_ret_t
njs_vmcode_strict_equal(njs_vm_t *vm, njs_value_t *val1, njs_value_t *val2)
{
const njs_value_t *retval;
if (njs_values_strict_equal(val1, val2)) {
retval = &njs_value_true;
} else {
retval = &njs_value_false;
}
vm->retval = *retval;
return sizeof(njs_vmcode_3addr_t);
}
njs_ret_t
njs_vmcode_strict_not_equal(njs_vm_t *vm, njs_value_t *val1, njs_value_t *val2)
{
const njs_value_t *retval;
if (njs_values_strict_equal(val1, val2)) {
retval = &njs_value_false;
} else {
retval = &njs_value_true;
}
vm->retval = *retval;
return sizeof(njs_vmcode_3addr_t);
}
nxt_noinline nxt_bool_t
njs_values_strict_equal(const njs_value_t *val1, const njs_value_t *val2)
{
size_t size;
const u_char *start1, *start2;
if (val1->type != val2->type) {
return 0;
}
if (njs_is_numeric(val1)) {
if (njs_is_void(val1)) {
return 1;
}
/* Infinities are handled correctly by comparision. */
return (val1->data.u.number == val2->data.u.number);
}
if (njs_is_string(val1)) {
size = val1->short_string.size;
if (size != val2->short_string.size) {
return 0;
}
if (size != NJS_STRING_LONG) {
if (val1->short_string.length != val2->short_string.length) {
return 0;
}
start1 = val1->short_string.start;
start2 = val2->short_string.start;
} else {
size = val1->long_string.size;
if (size != val2->long_string.size) {
return 0;
}
if (val1->long_string.data->length
!= val2->long_string.data->length)
{
return 0;
}
start1 = val1->long_string.data->start;
start2 = val2->long_string.data->start;
}
return (memcmp(start1, start2, size) == 0);
}
return (val1->data.u.object == val2->data.u.object);
}
njs_ret_t
njs_vmcode_move(njs_vm_t *vm, njs_value_t *value, njs_value_t *invld)
{
vm->retval = *value;
njs_retain(value);
return sizeof(njs_vmcode_move_t);
}
njs_ret_t
njs_vmcode_jump(njs_vm_t *vm, njs_value_t *invld, njs_value_t *offset)
{
return (njs_ret_t) offset;
}
njs_ret_t
njs_vmcode_if_true_jump(njs_vm_t *vm, njs_value_t *cond, njs_value_t *offset)
{
if (njs_is_true(cond)) {
return (njs_ret_t) offset;
}
return sizeof(njs_vmcode_cond_jump_t);
}
njs_ret_t
njs_vmcode_if_false_jump(njs_vm_t *vm, njs_value_t *cond, njs_value_t *offset)
{
if (njs_is_true(cond)) {
return sizeof(njs_vmcode_cond_jump_t);
}
return (njs_ret_t) offset;
}
njs_ret_t
njs_vmcode_if_equal_jump(njs_vm_t *vm, njs_value_t *val1, njs_value_t *val2)
{
njs_vmcode_equal_jump_t *jump;
if (njs_values_strict_equal(val1, val2)) {
jump = (njs_vmcode_equal_jump_t *) vm->current;
return jump->offset;
}
return sizeof(njs_vmcode_3addr_t);
}
njs_ret_t
njs_vmcode_function_frame(njs_vm_t *vm, njs_value_t *value, njs_value_t *nargs)
{
njs_ret_t ret;
njs_vmcode_function_frame_t *function;
function = (njs_vmcode_function_frame_t *) vm->current;
/* TODO: external object instead of void this. */
ret = njs_function_frame_create(vm, value, &njs_value_void,
(uintptr_t) nargs, function->code.ctor);
if (nxt_fast_path(ret == NXT_OK)) {
return sizeof(njs_vmcode_function_frame_t);
}
return ret;
}
static njs_ret_t
njs_function_frame_create(njs_vm_t *vm, njs_value_t *value,
const njs_value_t *this, uintptr_t nargs, nxt_bool_t ctor)
{
njs_value_t val;
njs_object_t *object;
njs_function_t *function;
if (nxt_fast_path(njs_is_function(value))) {
function = value->data.u.function;
if (!function->native) {
if (ctor) {
object = njs_function_new_object(vm, value);
if (nxt_slow_path(object == NULL)) {
return NXT_ERROR;
}
val.data.u.object = object;
val.type = NJS_OBJECT;
val.data.truth = 1;
this = &val;
}
return njs_function_frame(vm, function, this, NULL, nargs, ctor);
}
if (!ctor || function->ctor) {
return njs_function_native_frame(vm, function, this, NULL,
nargs, 0, ctor);
}
}
njs_exception_type_error(vm, "object is not callable", NULL);
return NXT_ERROR;
}
static njs_object_t *
njs_function_new_object(njs_vm_t *vm, njs_value_t *value)
{
nxt_int_t ret;
njs_value_t *proto;
njs_object_t *object;
njs_function_t *function;
njs_object_prop_t *prop;
nxt_lvlhsh_query_t lhq;
object = njs_object_alloc(vm);
if (nxt_fast_path(object != NULL)) {
lhq.key_hash = NJS_PROTOTYPE_HASH;
lhq.key = nxt_string_value("prototype");
lhq.proto = &njs_object_hash_proto;
function = value->data.u.function;
ret = nxt_lvlhsh_find(&function->object.hash, &lhq);
if (ret == NXT_OK) {
prop = lhq.value;
proto = &prop->value;
} else {
proto = njs_function_property_prototype_create(vm, value);
}
if (nxt_fast_path(proto != NULL)) {
object->__proto__ = proto->data.u.object;
return object;
}
}
return NULL;
}
njs_ret_t
njs_vmcode_method_frame(njs_vm_t *vm, njs_value_t *object, njs_value_t *name)
{
void *obj;
njs_ret_t ret;
njs_value_t this, *value;
njs_object_prop_t *prop;
njs_property_query_t pq;
const njs_extern_t *ext_proto;
njs_vmcode_method_frame_t *method;
method = (njs_vmcode_method_frame_t *) vm->current;
pq.lhq.key.length = 0;
pq.lhq.key.start = NULL;
pq.query = NJS_PROPERTY_QUERY_GET;
ret = njs_property_query(vm, &pq, object, name);
switch (ret) {
case NXT_OK:
prop = pq.lhq.value;
ret = njs_function_frame_create(vm, &prop->value, object, method->nargs,
method->code.ctor);
break;
case NJS_ARRAY_VALUE:
value = pq.lhq.value;
ret = njs_function_frame_create(vm, value, object, method->nargs,
method->code.ctor);
break;
case NJS_EXTERNAL_VALUE:
ext_proto = object->external.proto;
ret = nxt_lvlhsh_find(&ext_proto->hash, &pq.lhq);
if (nxt_slow_path(ret != NXT_OK)) {
njs_exception_type_error(vm,
"cannot find property '%.*s' of an external object",
(int) pq.lhq.key.length, pq.lhq.key.start);
return NXT_ERROR;
}
ext_proto = pq.lhq.value;
if (nxt_slow_path(ext_proto->type != NJS_EXTERN_METHOD)) {
njs_exception_type_error(vm,
"method '%.*s' of an external object is not callable",
(int) pq.lhq.key.length, pq.lhq.key.start);
return NXT_ERROR;
}
obj = njs_extern_object(vm, object);
this.data.u.data = obj;
ret = njs_function_native_frame(vm, ext_proto->function, &this, NULL,
method->nargs, 0, method->code.ctor);
break;
case NXT_ERROR:
/* An exception was set in njs_property_query(). */
return NXT_ERROR;
default:
njs_exception_internal_error(vm, "method '%.*s' query failed:%d",
(int) pq.lhq.key.length, pq.lhq.key.start,
ret);
return NXT_ERROR;
}
if (nxt_fast_path(ret == NXT_OK)) {
return sizeof(njs_vmcode_method_frame_t);
}
return ret;
}
njs_ret_t
njs_vmcode_function_call(njs_vm_t *vm, njs_value_t *invld, njs_value_t *retval)
{
njs_ret_t ret;
nxt_uint_t nargs;
njs_value_t *args;
njs_function_t *function;
njs_continuation_t *cont;
njs_native_frame_t *frame;
frame = vm->top_frame;
function = frame->function;
if (!function->native) {
ret = njs_function_call(vm, (njs_index_t) retval,
sizeof(njs_vmcode_function_call_t));
if (nxt_fast_path(ret != NJS_ERROR)) {
return 0;
}
return ret;
}
args = frame->arguments;
nargs = frame->nargs;
ret = njs_normalize_args(vm, args, function->args_types, nargs);
if (ret != NJS_OK) {
return ret;
}
if (function->continuation_size != 0) {
cont = njs_vm_continuation(vm);
cont->function = function->u.native;
cont->args_types = function->args_types;
cont->retval = (njs_index_t) retval;
cont->return_address = vm->current + sizeof(njs_vmcode_function_call_t);
vm->current = (u_char *) njs_continuation_nexus;
return 0;
}
ret = function->u.native(vm, args, nargs, (njs_index_t) retval);
/*
* A native method can return:
* NXT_OK on method success;
* NJS_APPLIED by Function.apply() and Function.call();
* NXT_AGAIN to postpone nJSVM processing;
* NXT_ERROR.
*
* The callee arguments must be preserved
* for NJS_APPLIED and NXT_AGAIN cases.
*/
if (ret == NXT_OK) {
frame = vm->top_frame;
vm->top_frame = njs_function_previous_frame(frame);
(void) njs_function_frame_free(vm, frame);
/*
* If a retval is in a callee arguments scope it
* must be in the previous callee arguments scope.
*/
vm->scopes[NJS_SCOPE_CALLEE_ARGUMENTS] =
vm->top_frame->arguments + function->args_offset;
retval = njs_vmcode_operand(vm, retval);
/*
* GC: value external/internal++ depending
* on vm->retval and retval type
*/
*retval = vm->retval;
ret = sizeof(njs_vmcode_function_call_t);
} else if (ret == NJS_APPLIED) {
/* A user-defined method has been prepared to run. */
ret = 0;
}
return ret;
}
njs_ret_t
njs_normalize_args(njs_vm_t *vm, njs_value_t *args, uint8_t *args_types,
nxt_uint_t nargs)
{
njs_ret_t trap;
nxt_uint_t n;
n = nxt_min(nargs, NJS_ARGS_TYPES_MAX);
while (n != 0) {
switch (*args_types) {
case NJS_STRING_OBJECT_ARG:
if (njs_is_null_or_void(args)) {
goto type_error;
}
/* Fall through. */
case NJS_STRING_ARG:
if (njs_is_string(args)) {
break;
}
trap = NJS_TRAP_STRING_ARG;
goto trap;
case NJS_NUMBER_ARG:
if (njs_is_numeric(args)) {
break;
}
trap = NJS_TRAP_NUMBER_ARG;
goto trap;
case NJS_INTEGER_ARG:
if (njs_is_numeric(args)) {
/* Numbers are truncated to fit in 32-bit integers. */
if (isnan(args->data.u.number)) {
args->data.u.number = 0;
} else if (args->data.u.number > 2147483647.0) {
args->data.u.number = 2147483647.0;
} else if (args->data.u.number < -2147483648.0) {
args->data.u.number = -2147483648.0;
}
break;
}
trap = NJS_TRAP_NUMBER_ARG;
goto trap;
case NJS_FUNCTION_ARG:
switch (args->type) {
case NJS_STRING:
case NJS_FUNCTION:
break;
default:
trap = NJS_TRAP_STRING_ARG;
goto trap;
}
break;
case NJS_REGEXP_ARG:
switch (args->type) {
case NJS_VOID:
case NJS_STRING:
case NJS_REGEXP:
break;
default:
trap = NJS_TRAP_STRING_ARG;
goto trap;
}
break;
case NJS_DATE_ARG:
if (!njs_is_date(args)) {
goto type_error;
}
break;
case NJS_OBJECT_ARG:
if (njs_is_null_or_void(args)) {
goto type_error;
}
break;
case NJS_SKIP_ARG:
break;
case 0:
return NJS_OK;
}
args++;
args_types++;
n--;
}
return NJS_OK;
trap:
njs_vm_trap_value(vm, args);
return trap;
type_error:
njs_exception_type_error(vm, "cannot convert %s to %s",
njs_type_string(args->type),
njs_arg_type_string(*args_types));
return NXT_ERROR;
}
const char *
njs_type_string(njs_value_type_t type)
{
switch (type) {
case NJS_NULL:
return "null";
case NJS_VOID:
return "void";
case NJS_BOOLEAN:
return "boolean";
case NJS_NUMBER:
return "number";
case NJS_STRING:
return "string";
case NJS_EXTERNAL:
return "external";
case NJS_INVALID:
return "invalid";
case NJS_OBJECT:
return "object";
case NJS_ARRAY:
return "array";
case NJS_OBJECT_BOOLEAN:
return "object boolean";
case NJS_OBJECT_NUMBER:
return "object number";
case NJS_OBJECT_STRING:
return "object string";
case NJS_FUNCTION:
return "function";
case NJS_REGEXP:
return "regexp";
case NJS_DATE:
return "date";
case NJS_OBJECT_ERROR:
return "error";
case NJS_OBJECT_EVAL_ERROR:
return "eval error";
case NJS_OBJECT_INTERNAL_ERROR:
return "internal error";
case NJS_OBJECT_RANGE_ERROR:
return "range error";
case NJS_OBJECT_REF_ERROR:
return "reference error";
case NJS_OBJECT_SYNTAX_ERROR:
return "syntax error";
case NJS_OBJECT_TYPE_ERROR:
return "type error";
case NJS_OBJECT_URI_ERROR:
return "uri error";
default:
return NULL;
}
}
const char *
njs_arg_type_string(uint8_t arg)
{
switch (arg) {
case NJS_SKIP_ARG:
return "skip";
case NJS_NUMBER_ARG:
return "number";
case NJS_INTEGER_ARG:
return "integer";
case NJS_STRING_ARG:
return "string";
case NJS_OBJECT_ARG:
return "object";
case NJS_STRING_OBJECT_ARG:
return "string object";
case NJS_FUNCTION_ARG:
return "function";
case NJS_REGEXP_ARG:
return "regexp";
case NJS_DATE_ARG:
return "regexp";
default:
return "unknown";
}
}
njs_ret_t
njs_vmcode_return(njs_vm_t *vm, njs_value_t *invld, njs_value_t *retval)
{
njs_value_t *value;
njs_frame_t *frame;
njs_native_frame_t *previous;
value = njs_vmcode_operand(vm, retval);
frame = (njs_frame_t *) vm->top_frame;
if (frame->native.ctor) {
if (njs_is_object(value)) {
njs_release(vm, vm->scopes[NJS_SCOPE_ARGUMENTS]);
} else {
value = vm->scopes[NJS_SCOPE_ARGUMENTS];
}
}
previous = njs_function_previous_frame(&frame->native);
njs_vm_scopes_restore(vm, frame, previous);
/*
* If a retval is in a callee arguments scope it
* must be in the previous callee arguments scope.
*/
retval = njs_vmcode_operand(vm, frame->retval);
/* GC: value external/internal++ depending on value and retval type */
*retval = *value;
vm->current = frame->return_address;
return njs_function_frame_free(vm, &frame->native);
}
static void
njs_vm_scopes_restore(njs_vm_t *vm, njs_frame_t *frame,
njs_native_frame_t *previous)
{
nxt_uint_t n, nesting;
njs_value_t *args;
njs_function_t *function;
vm->top_frame = previous;
args = previous->arguments;
function = previous->function;
if (function != NULL) {
args += function->args_offset;
}
vm->scopes[NJS_SCOPE_CALLEE_ARGUMENTS] = args;
function = frame->native.function;
if (function->native) {
return;
}
if (function->closure) {
/* GC: release function closures. */
}
frame = frame->previous_active_frame;
vm->active_frame = frame;
/* GC: arguments, local, and local block closures. */
vm->scopes[NJS_SCOPE_ARGUMENTS] = frame->native.arguments;
vm->scopes[NJS_SCOPE_LOCAL] = frame->local;
function = frame->native.function;
nesting = (function != NULL) ? function->u.lambda->nesting : 0;
for (n = 0; n <= nesting; n++) {
vm->scopes[NJS_SCOPE_CLOSURE + n] = &frame->closures[n]->u.values;
}
while (n < NJS_MAX_NESTING) {
vm->scopes[NJS_SCOPE_CLOSURE + n] = NULL;
n++;
}
}
const njs_vmcode_1addr_t njs_continuation_nexus[] = {
{ .code = { .operation = njs_vmcode_continuation,
.operands = NJS_VMCODE_NO_OPERAND,
.retval = NJS_VMCODE_NO_RETVAL } },
};
static njs_ret_t
njs_vmcode_continuation(njs_vm_t *vm, njs_value_t *invld1, njs_value_t *invld2)
{