blob: a52b0f5ee02ff80545751ea03089715c874fc295 [file] [log] [blame]
/*
* Copyright (C) Igor Sysoev
* Copyright (C) Dmitry Volyntsev
* Copyright (C) NGINX, Inc.
*/
#include <njs_main.h>
static njs_variable_t *njs_variable_scope_add(njs_vm_t *vm,
njs_parser_scope_t *scope, uintptr_t unique_id, njs_variable_type_t type);
static njs_int_t njs_variable_reference_resolve(njs_vm_t *vm,
njs_variable_reference_t *vr, njs_parser_scope_t *node_scope);
static njs_variable_t *njs_variable_alloc(njs_vm_t *vm, uintptr_t unique_id,
njs_variable_type_t type);
njs_variable_t *
njs_variable_add(njs_vm_t *vm, njs_parser_scope_t *scope, uintptr_t unique_id,
njs_variable_type_t type)
{
njs_variable_t *var;
var = njs_variable_scope_add(vm, scope, unique_id, type);
if (njs_slow_path(var == NULL)) {
return NULL;
}
if (type == NJS_VARIABLE_VAR && scope->type == NJS_SCOPE_BLOCK) {
/* A "var" declaration is stored in function or global scope. */
do {
scope = scope->parent;
var = njs_variable_scope_add(vm, scope, unique_id, type);
if (njs_slow_path(var == NULL)) {
return NULL;
}
} while (scope->type == NJS_SCOPE_BLOCK);
}
if (type == NJS_VARIABLE_FUNCTION) {
var->type = type;
}
return var;
}
njs_int_t
njs_variables_copy(njs_vm_t *vm, njs_rbtree_t *variables,
njs_rbtree_t *prev_variables)
{
njs_rbtree_node_t *node;
njs_variable_node_t *var_node;
node = njs_rbtree_min(prev_variables);
while (njs_rbtree_is_there_successor(prev_variables, node)) {
var_node = (njs_variable_node_t *) node;
var_node = njs_variable_node_alloc(vm, var_node->variable,
var_node->key);
if (njs_slow_path(var_node == NULL)) {
njs_memory_error(vm);
return NJS_ERROR;
}
njs_rbtree_insert(variables, &var_node->node);
node = njs_rbtree_node_successor(prev_variables, node);
}
return NJS_OK;
}
static njs_variable_t *
njs_variable_scope_add(njs_vm_t *vm, njs_parser_scope_t *scope,
uintptr_t unique_id, njs_variable_type_t type)
{
njs_variable_t *var;
njs_rbtree_node_t *node;
njs_variable_node_t var_node, *var_node_new;
const njs_lexer_entry_t *entry;
var_node.key = unique_id;
node = njs_rbtree_find(&scope->variables, &var_node.node);
if (node != NULL) {
var = ((njs_variable_node_t *) node)->variable;
if (scope->module || scope->type == NJS_SCOPE_BLOCK) {
if (type == NJS_VARIABLE_FUNCTION
|| var->type == NJS_VARIABLE_FUNCTION)
{
goto fail;
}
}
if (scope->type == NJS_SCOPE_GLOBAL) {
if (vm->options.module) {
if (type == NJS_VARIABLE_FUNCTION
|| var->type == NJS_VARIABLE_FUNCTION)
{
goto fail;
}
}
}
return var;
}
var = njs_variable_alloc(vm, unique_id, type);
if (njs_slow_path(var == NULL)) {
goto memory_error;
}
var_node_new = njs_variable_node_alloc(vm, var, unique_id);
if (njs_slow_path(var_node_new == NULL)) {
goto memory_error;
}
njs_rbtree_insert(&scope->variables, &var_node_new->node);
return var;
memory_error:
njs_memory_error(vm);
return NULL;
fail:
entry = njs_lexer_entry(unique_id);
njs_parser_syntax_error(vm, vm->parser,
"\"%V\" has already been declared",
&entry->name);
return NULL;
}
njs_variable_t *
njs_label_add(njs_vm_t *vm, njs_parser_scope_t *scope, uintptr_t unique_id)
{
njs_variable_t *label;
njs_rbtree_node_t *node;
njs_variable_node_t var_node, *var_node_new;
var_node.key = unique_id;
node = njs_rbtree_find(&scope->labels, &var_node.node);
if (node != NULL) {
return ((njs_variable_node_t *) node)->variable;
}
label = njs_variable_alloc(vm, unique_id, NJS_VARIABLE_CONST);
if (njs_slow_path(label == NULL)) {
goto memory_error;
}
var_node_new = njs_variable_node_alloc(vm, label, unique_id);
if (njs_slow_path(var_node_new == NULL)) {
goto memory_error;
}
njs_rbtree_insert(&scope->labels, &var_node_new->node);
return label;
memory_error:
njs_memory_error(vm);
return NULL;
}
njs_int_t
njs_label_remove(njs_vm_t *vm, njs_parser_scope_t *scope, uintptr_t unique_id)
{
njs_rbtree_node_t *node;
njs_variable_node_t var_node;
var_node.key = unique_id;
node = njs_rbtree_find(&scope->labels, &var_node.node);
if (njs_slow_path(node == NULL)) {
njs_internal_error(vm, "failed to find label while removing");
return NJS_ERROR;
}
njs_rbtree_delete(&scope->labels, (njs_rbtree_part_t *) node);
njs_variable_node_free(vm, (njs_variable_node_t *) node);
return NJS_OK;
}
njs_int_t
njs_variable_reference(njs_vm_t *vm, njs_parser_scope_t *scope,
njs_parser_node_t *node, uintptr_t unique_id, njs_reference_type_t type)
{
njs_variable_reference_t *vr;
njs_parser_rbtree_node_t *rb_node;
vr = &node->u.reference;
vr->unique_id = unique_id;
vr->type = type;
rb_node = njs_mp_alloc(vm->mem_pool, sizeof(njs_parser_rbtree_node_t));
if (njs_slow_path(rb_node == NULL)) {
return NJS_ERROR;
}
rb_node->key = unique_id;
rb_node->parser_node = node;
njs_rbtree_insert(&scope->references, &rb_node->node);
return NJS_OK;
}
static njs_int_t
njs_variables_scope_resolve(njs_vm_t *vm, njs_parser_scope_t *scope,
njs_bool_t closure)
{
njs_int_t ret;
njs_queue_t *nested;
njs_queue_link_t *lnk;
njs_rbtree_node_t *rb_node;
njs_parser_node_t *node;
njs_parser_rbtree_node_t *parser_rb_node;
njs_variable_reference_t *vr;
nested = &scope->nested;
for (lnk = njs_queue_first(nested);
lnk != njs_queue_tail(nested);
lnk = njs_queue_next(lnk))
{
scope = njs_queue_link_data(lnk, njs_parser_scope_t, link);
ret = njs_variables_scope_resolve(vm, scope, closure);
if (njs_slow_path(ret != NJS_OK)) {
return NJS_ERROR;
}
rb_node = njs_rbtree_min(&scope->references);
while (njs_rbtree_is_there_successor(&scope->references, rb_node)) {
parser_rb_node = (njs_parser_rbtree_node_t *) rb_node;
node = parser_rb_node->parser_node;
if (node == NULL) {
break;
}
vr = &node->u.reference;
if (closure) {
ret = njs_variable_reference_resolve(vm, vr, node->scope);
if (njs_slow_path(ret != NJS_OK)) {
goto next;
}
if (vr->scope_index == NJS_SCOPE_INDEX_LOCAL) {
goto next;
}
}
(void) njs_variable_resolve(vm, node);
next:
rb_node = njs_rbtree_node_successor(&scope->references, rb_node);
}
}
return NJS_OK;
}
njs_int_t
njs_variables_scope_reference(njs_vm_t *vm, njs_parser_scope_t *scope)
{
njs_int_t ret;
/*
* Calculating proper scope types for variables.
* A variable is considered to be local variable if it is referenced
* only in the local scope (reference and definition nestings are the same).
*/
ret = njs_variables_scope_resolve(vm, scope, 1);
if (njs_slow_path(ret != NJS_OK)) {
return NJS_ERROR;
}
ret = njs_variables_scope_resolve(vm, scope, 0);
if (njs_slow_path(ret != NJS_OK)) {
return NJS_ERROR;
}
return NJS_OK;
}
njs_index_t
njs_variable_typeof(njs_vm_t *vm, njs_parser_node_t *node)
{
njs_int_t ret;
njs_variable_reference_t *vr;
if (node->index != NJS_INDEX_NONE) {
return node->index;
}
vr = &node->u.reference;
ret = njs_variable_reference_resolve(vm, vr, node->scope);
if (njs_fast_path(ret == NJS_OK)) {
return vr->variable->index;
}
return NJS_INDEX_NONE;
}
njs_index_t
njs_variable_index(njs_vm_t *vm, njs_parser_node_t *node)
{
njs_variable_t *var;
if (node->index != NJS_INDEX_NONE) {
return node->index;
}
var = njs_variable_resolve(vm, node);
if (njs_fast_path(var != NULL)) {
return var->index;
}
return NJS_INDEX_NONE;
}
njs_variable_t *
njs_variable_resolve(njs_vm_t *vm, njs_parser_node_t *node)
{
njs_int_t ret;
njs_uint_t scope_index;
njs_index_t index;
njs_variable_t *var;
njs_variable_reference_t *vr;
vr = &node->u.reference;
ret = njs_variable_reference_resolve(vm, vr, node->scope);
if (njs_slow_path(ret != NJS_OK)) {
node->u.reference.not_defined = 1;
return NULL;
}
scope_index = vr->scope_index;
var = vr->variable;
index = var->index;
if (index != NJS_INDEX_NONE) {
if (scope_index == NJS_SCOPE_INDEX_LOCAL
|| njs_scope_type(index) != NJS_SCOPE_ARGUMENTS)
{
node->index = index;
return var;
}
vr->scope->argument_closures++;
index = (index >> NJS_SCOPE_SHIFT) + 1;
if (index > 255 || vr->scope->argument_closures == 0) {
njs_internal_error(vm, "too many argument closures");
return NULL;
}
var->argument = index;
}
index = njs_scope_next_index(vm, vr->scope, scope_index, &var->value);
if (njs_slow_path(index == NJS_INDEX_ERROR)) {
return NULL;
}
var->index = index;
node->index = index;
return var;
}
njs_variable_t *
njs_label_find(njs_vm_t *vm, njs_parser_scope_t *scope, uintptr_t unique_id)
{
njs_rbtree_node_t *node;
njs_variable_node_t var_node;
var_node.key = unique_id;
do {
node = njs_rbtree_find(&scope->labels, &var_node.node);
if (node != NULL) {
return ((njs_variable_node_t *) node)->variable;
}
scope = scope->parent;
} while (scope != NULL);
return NULL;
}
static njs_int_t
njs_variable_reference_resolve(njs_vm_t *vm, njs_variable_reference_t *vr,
njs_parser_scope_t *node_scope)
{
njs_rbtree_node_t *node;
njs_parser_scope_t *scope, *previous;
njs_variable_node_t var_node;
var_node.key = vr->unique_id;
scope = node_scope;
previous = NULL;
for ( ;; ) {
node = njs_rbtree_find(&scope->variables, &var_node.node);
if (node != NULL) {
vr->variable = ((njs_variable_node_t *) node)->variable;
if (scope->type == NJS_SCOPE_BLOCK
&& vr->variable->type == NJS_VARIABLE_VAR)
{
scope = scope->parent;
continue;
}
if (scope->type == NJS_SCOPE_SHIM) {
scope = previous;
} else {
/*
* Variables declared in a block with "let" or "const"
* keywords are actually stored in function or global scope.
*/
while (scope->type == NJS_SCOPE_BLOCK) {
scope = scope->parent;
}
}
vr->scope = scope;
vr->scope_index = NJS_SCOPE_INDEX_LOCAL;
if (vr->scope->type > NJS_SCOPE_GLOBAL
&& node_scope->nesting != vr->scope->nesting)
{
vr->scope_index = NJS_SCOPE_INDEX_CLOSURE;
}
return NJS_OK;
}
if (scope->parent == NULL) {
/* A global scope. */
vr->scope = scope;
return NJS_DECLINED;
}
previous = scope;
scope = scope->parent;
}
}
njs_index_t
njs_scope_next_index(njs_vm_t *vm, njs_parser_scope_t *scope,
njs_uint_t scope_index, const njs_value_t *default_value)
{
njs_arr_t *values;
njs_index_t index;
njs_value_t *value;
if (njs_scope_accumulative(vm, scope)) {
/*
* When non-clonable VM runs in accumulative mode all
* global variables should be allocated in absolute scope
* to share them among consecutive VM invocations.
*/
value = njs_mp_align(vm->mem_pool, sizeof(njs_value_t),
sizeof(njs_value_t));
if (njs_slow_path(value == NULL)) {
return NJS_INDEX_ERROR;
}
index = (njs_index_t) value;
} else {
values = scope->values[scope_index];
if (values == NULL) {
values = njs_arr_create(vm->mem_pool, 4, sizeof(njs_value_t));
if (njs_slow_path(values == NULL)) {
return NJS_INDEX_ERROR;
}
scope->values[scope_index] = values;
}
value = njs_arr_add(values);
if (njs_slow_path(value == NULL)) {
return NJS_INDEX_ERROR;
}
index = scope->next_index[scope_index];
scope->next_index[scope_index] += sizeof(njs_value_t);
}
*value = *default_value;
return index;
}
static njs_variable_t *
njs_variable_alloc(njs_vm_t *vm, uintptr_t unique_id, njs_variable_type_t type)
{
njs_variable_t *var;
var = njs_mp_zalloc(vm->mem_pool, sizeof(njs_variable_t));
if (njs_slow_path(var == NULL)) {
njs_memory_error(vm);
return NULL;
}
var->unique_id = unique_id;
var->type = type;
return var;
}
njs_int_t
njs_name_copy(njs_vm_t *vm, njs_str_t *dst, const njs_str_t *src)
{
dst->length = src->length;
dst->start = njs_mp_alloc(vm->mem_pool, src->length);
if (njs_fast_path(dst->start != NULL)) {
(void) memcpy(dst->start, src->start, src->length);
return NJS_OK;
}
njs_memory_error(vm);
return NJS_ERROR;
}