diff options
-rw-r--r-- | nginx/ngx_http_js_module.c | 190 | ||||
-rw-r--r-- | njs/njs_extern.c | 41 | ||||
-rw-r--r-- | njs/njs_generator.c | 3 | ||||
-rw-r--r-- | njs/njs_parser.c | 8 | ||||
-rw-r--r-- | njs/njs_parser.h | 1 | ||||
-rw-r--r-- | njs/njs_vm.h | 11 | ||||
-rw-r--r-- | njs/njscript.c | 55 | ||||
-rw-r--r-- | njs/njscript.h | 12 | ||||
-rw-r--r-- | njs/test/njs_unit_test.c | 28 |
9 files changed, 267 insertions, 82 deletions
diff --git a/nginx/ngx_http_js_module.c b/nginx/ngx_http_js_module.c index 56840578..d610bb9c 100644 --- a/nginx/ngx_http_js_module.c +++ b/nginx/ngx_http_js_module.c @@ -32,19 +32,28 @@ typedef struct { - njs_vm_t *vm; + njs_vm_t *vm; + njs_function_t *function; + njs_opaque_value_t args[2]; +} ngx_http_js_ctx_t; + + +typedef struct { + ngx_http_js_ctx_t js; } ngx_http_js_loc_conf_t; typedef struct { - ngx_list_part_t *part; - ngx_uint_t item; + ngx_list_part_t *part; + ngx_uint_t item; } ngx_http_js_table_entry_t; static ngx_int_t ngx_http_js_handler(ngx_http_request_t *r); static ngx_int_t ngx_http_js_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_js_vm_run(ngx_http_request_t *r, + ngx_http_js_ctx_t *js, nxt_str_t *value); static void ngx_http_js_cleanup_mem_cache_pool(void *data); static void *ngx_http_js_alloc(void *mem, size_t size); @@ -99,7 +108,8 @@ static njs_ret_t ngx_http_js_ext_next_arg(njs_vm_t *vm, njs_value_t *value, static char *ngx_http_js_run(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_http_js_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); -static njs_vm_t *ngx_http_js_compile(ngx_conf_t *cf, ngx_str_t *script); +static char *ngx_http_js_compile(ngx_conf_t *cf, ngx_http_js_ctx_t *jctx, + ngx_str_t *script); static void *ngx_http_js_create_loc_conf(ngx_conf_t *cf); static char *ngx_http_js_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child); @@ -362,62 +372,70 @@ static njs_external_t ngx_http_js_externals[] = { static ngx_int_t ngx_http_js_handler(ngx_http_request_t *r) { - nxt_str_t value; - njs_vm_t *nvm; - ngx_pool_cleanup_t *cln; - nxt_mem_cache_pool_t *mcp; + ngx_int_t rc; ngx_http_js_loc_conf_t *jlcf; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http js handler"); - mcp = ngx_http_js_create_mem_cache_pool(); - if (mcp == NULL) { - return NGX_ERROR; - } + jlcf = ngx_http_get_module_loc_conf(r, ngx_http_js_module); - cln = ngx_pool_cleanup_add(r->pool, 0); - if (cln == NULL) { - return NGX_ERROR; + rc = ngx_http_js_vm_run(r, &jlcf->js, NULL); + + if (rc == NGX_OK) { + return rc; } - cln->handler = ngx_http_js_cleanup_mem_cache_pool; - cln->data = mcp; + return NGX_ERROR; +} - jlcf = ngx_http_get_module_loc_conf(r, ngx_http_js_module); - nvm = njs_vm_clone(jlcf->vm, mcp, (void **) &r); - if (nvm == NULL) { - return NGX_ERROR; - } +static ngx_int_t +ngx_http_js_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, + uintptr_t data) +{ + ngx_http_js_ctx_t *js = (ngx_http_js_ctx_t *) data; - if (njs_vm_run(nvm) != NJS_OK) { - njs_vm_exception(nvm, &value); + ngx_int_t rc; + nxt_str_t value; - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, - "js exception: %*s", value.len, value.data); + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http js variable handler"); + + rc = ngx_http_js_vm_run(r, js, &value); + if (rc == NXT_ERROR) { return NGX_ERROR; } + if (rc == NGX_OK) { + v->len = value.len; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = value.data; + + } else { + v->not_found = 1; + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http js variable done"); + return NGX_OK; } static ngx_int_t -ngx_http_js_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, - uintptr_t data) +ngx_http_js_vm_run(ngx_http_request_t *r, ngx_http_js_ctx_t *js, + nxt_str_t *value) { - njs_vm_t *vm = (njs_vm_t *) data; - - nxt_str_t value; njs_vm_t *nvm; + nxt_int_t ret; + nxt_str_t exception; ngx_pool_cleanup_t *cln; nxt_mem_cache_pool_t *mcp; - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "http js variable handler"); - mcp = ngx_http_js_create_mem_cache_pool(); if (mcp == NULL) { return NGX_ERROR; @@ -431,34 +449,35 @@ ngx_http_js_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, cln->handler = ngx_http_js_cleanup_mem_cache_pool; cln->data = mcp; - nvm = njs_vm_clone(vm, mcp, (void **) &r); + nvm = njs_vm_clone(js->vm, mcp, (void **) &r); if (nvm == NULL) { return NGX_ERROR; } - if (njs_vm_run(nvm) == NJS_OK) { - if (njs_vm_retval(nvm, &value) != NJS_OK) { - return NGX_ERROR; - } + if (js->function) { + ret = njs_vm_call(nvm, js->function, js->args, 2); - v->len = value.len; - v->valid = 1; - v->no_cacheable = 0; - v->not_found = 0; - v->data = value.data; + } else { + ret = njs_vm_run(nvm); + } + + if (ret == NJS_OK) { + + if (value != NULL) { + if (njs_vm_retval(nvm, value) != NJS_OK) { + return NGX_ERROR; + } + } } else { - njs_vm_exception(nvm, &value); + njs_vm_exception(nvm, &exception); ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, - "js exception: %*s", value.len, value.data); + "js exception: %*s", exception.len, exception.data); - v->not_found = 1; + return NGX_DECLINED; } - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "http js variable done"); - return NGX_OK; } @@ -1046,20 +1065,21 @@ ngx_http_js_run(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_js_loc_conf_t *jlcf = conf; + char *ret; ngx_str_t *value; ngx_http_core_loc_conf_t *clcf; value = cf->args->elts; - if (jlcf->vm) { + if (jlcf->js.vm) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "duplicate js handler \"%V\"", &value[1]); return NGX_CONF_ERROR; } - jlcf->vm = ngx_http_js_compile(cf, &value[1]); - if (jlcf->vm == NULL) { - return NGX_CONF_ERROR; + ret = ngx_http_js_compile(cf, &jlcf->js, &value[1]); + if (ret != NGX_CONF_OK) { + return ret; } clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); @@ -1072,8 +1092,9 @@ ngx_http_js_run(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) static char * ngx_http_js_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { - njs_vm_t *vm; + char *ret; ngx_str_t *value; + ngx_http_js_ctx_t *js; ngx_http_variable_t *v; value = cf->args->elts; @@ -1092,32 +1113,38 @@ ngx_http_js_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) return NGX_CONF_ERROR; } - vm = ngx_http_js_compile(cf, &value[2]); - if (vm == NULL) { + js = ngx_palloc(cf->pool, sizeof(ngx_http_js_ctx_t)); + if (js == NULL) { return NGX_CONF_ERROR; } + ret = ngx_http_js_compile(cf, js, &value[2]); + if (ret != NGX_CONF_OK) { + return ret; + } + v->get_handler = ngx_http_js_variable; - v->data = (uintptr_t) vm; + v->data = (uintptr_t) js; return NGX_CONF_OK; } -static njs_vm_t * -ngx_http_js_compile(ngx_conf_t *cf, ngx_str_t *script) +static char * +ngx_http_js_compile(ngx_conf_t *cf, ngx_http_js_ctx_t *js, ngx_str_t *script) { u_char *start, *end; nxt_int_t rc; - nxt_str_t s; + nxt_str_t s, name; njs_vm_t *vm; nxt_lvlhsh_t externals; + njs_function_t *function; njs_vm_shared_t *shared; nxt_mem_cache_pool_t *mcp; mcp = ngx_http_js_create_mem_cache_pool(); if (mcp == NULL) { - return NULL; + return NGX_CONF_ERROR; } shared = NULL; @@ -1129,35 +1156,58 @@ ngx_http_js_compile(ngx_conf_t *cf, ngx_str_t *script) != NJS_OK) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "could not add js externals"); - return NULL; + return NGX_CONF_ERROR; } vm = njs_vm_create(mcp, &shared, &externals); if (vm == NULL) { - return NULL; + return NGX_CONF_ERROR; } start = script->data; end = start + script->len; - rc = njs_vm_compile(vm, &start, end); + rc = njs_vm_compile(vm, &start, end, &function); if (rc != NJS_OK) { njs_vm_exception(vm, &s); ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "js compilation error: \"%*s\"", s.len, s.data); - return NULL; + return NGX_CONF_ERROR; } if (start != end) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "extra characters in js script: \"%*s\"", end - start, start); - return NULL; + return NGX_CONF_ERROR; + } + + js->vm = vm; + js->function = function; + + if (function) { + ngx_str_set(&name, "$r"); + + rc = njs_external_get(vm, NULL, &name, &js->args[0]); + if (rc != NXT_OK) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "could not get $r external"); + return NGX_CONF_ERROR; + } + + ngx_str_set(&name, "response"); + + rc = njs_external_get(vm, &js->args[0], &name, &js->args[1]); + if (rc != NXT_OK) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "could not get $r.response external"); + return NGX_CONF_ERROR; + } } - return vm; + return NGX_CONF_OK; } @@ -1187,8 +1237,8 @@ ngx_http_js_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) ngx_http_js_loc_conf_t *prev = parent; ngx_http_js_loc_conf_t *conf = child; - if (conf->vm == NULL) { - conf->vm = prev->vm; + if (conf->js.vm == NULL) { + conf->js = prev->js; } return NGX_CONF_OK; diff --git a/njs/njs_extern.c b/njs/njs_extern.c index 42420100..11e2358d 100644 --- a/njs/njs_extern.c +++ b/njs/njs_extern.c @@ -126,6 +126,47 @@ njs_add_external(nxt_lvlhsh_t *hash, nxt_mem_cache_pool_t *mcp, } +nxt_int_t +njs_external_get(njs_vm_t *vm, njs_opaque_value_t *obj, nxt_str_t *property, + njs_opaque_value_t *value) +{ + uint32_t (*key_hash)(const void *, size_t); + njs_value_t *object; + njs_extern_t *ext; + nxt_lvlhsh_t hash; + nxt_lvlhsh_query_t lhq; + + object = (njs_value_t *) obj; + + key_hash = nxt_djb_hash; + hash = vm->externals_hash; + + if (object != NULL) { + if (!njs_is_external(object)) { + return NXT_ERROR; + } + + ext = object->data.u.external; + hash = ext->hash; + + if (ext->type == NJS_EXTERN_CASELESS_OBJECT) { + key_hash = nxt_djb_hash_lowcase; + } + } + + lhq.key_hash = key_hash(property->data, property->len); + lhq.key = *property; + lhq.proto = &njs_extern_hash_proto; + + if (nxt_lvlhsh_find(&hash, &lhq) == NXT_OK) { + *value = *(njs_opaque_value_t *) lhq.value; + return NXT_OK; + } + + return NXT_ERROR; +} + + njs_extern_t * njs_parser_external(njs_vm_t *vm, njs_parser_t *parser) { diff --git a/njs/njs_generator.c b/njs/njs_generator.c index 5d37adb1..c6eb141a 100644 --- a/njs/njs_generator.c +++ b/njs/njs_generator.c @@ -163,6 +163,9 @@ njs_generator(njs_vm_t *vm, njs_parser_t *parser, njs_parser_node_t *node) case NJS_TOKEN_END: return njs_generate_stop_statement(vm, parser, node); + case NJS_TOKEN_CALL: + return njs_generate_children(vm, parser, node); + case NJS_TOKEN_COMMA: return njs_generate_comma_expression(vm, parser, node); diff --git a/njs/njs_parser.c b/njs/njs_parser.c index 90241ab8..39bf3ce0 100644 --- a/njs/njs_parser.c +++ b/njs/njs_parser.c @@ -99,7 +99,13 @@ njs_parser(njs_vm_t *vm, njs_parser_t *parser) node = parser->node; - if (node == NULL) { + if (node != NULL && node->right != NULL) { + if (node->right->token == NJS_TOKEN_FUNCTION) { + node->token = NJS_TOKEN_CALL; + return node; + } + + } else { /* Empty string, just semicolons or variables declarations. */ node = njs_parser_node_alloc(vm); diff --git a/njs/njs_parser.h b/njs/njs_parser.h index 74ea7622..a0a56837 100644 --- a/njs/njs_parser.h +++ b/njs/njs_parser.h @@ -14,6 +14,7 @@ typedef enum { NJS_TOKEN_ILLEGAL = 0, NJS_TOKEN_END, + NJS_TOKEN_CALL, NJS_TOKEN_SPACE, NJS_TOKEN_LINE_END, diff --git a/njs/njs_vm.h b/njs/njs_vm.h index bae66278..3b219988 100644 --- a/njs/njs_vm.h +++ b/njs/njs_vm.h @@ -130,7 +130,7 @@ struct njs_object_s { #define NJS_ARGS_TYPES_MAX 3 -typedef struct { +struct njs_function_s { njs_object_t object; uint8_t args_types[NJS_ARGS_TYPES_MAX]; @@ -158,7 +158,7 @@ typedef struct { } u; njs_value_t *bound; -} njs_function_t; +}; typedef struct njs_continuation_s njs_continuation_t; @@ -684,10 +684,10 @@ enum njs_functions_e { #define njs_scope_index(value) \ - ((njs_index_t) (value << NJS_SCOPE_SHIFT)) + ((njs_index_t) ((value) << NJS_SCOPE_SHIFT)) #define njs_global_scope_index(value) \ - ((njs_index_t) ((value << NJS_SCOPE_SHIFT) | NJS_SCOPE_GLOBAL)) + ((njs_index_t) (((value) << NJS_SCOPE_SHIFT) | NJS_SCOPE_GLOBAL)) #define NJS_INDEX_OBJECT njs_global_scope_index(NJS_FUNCTION_OBJECT) @@ -699,7 +699,8 @@ enum njs_functions_e { #define NJS_INDEX_REGEXP njs_global_scope_index(NJS_FUNCTION_REGEXP) #define NJS_INDEX_EVAL njs_global_scope_index(NJS_FUNCTION_EVAL) -#define NJS_INDEX_GLOBAL_OFFSET njs_scope_index(NJS_FUNCTION_MAX) +#define NJS_INDEX_GLOBAL_RETVAL njs_global_scope_index(NJS_FUNCTION_MAX) +#define NJS_INDEX_GLOBAL_OFFSET njs_scope_index(NJS_FUNCTION_MAX + 1) #define njs_offset(index) \ diff --git a/njs/njscript.c b/njs/njscript.c index 15a7b5ba..9d0823d5 100644 --- a/njs/njscript.c +++ b/njs/njscript.c @@ -167,10 +167,12 @@ njs_vm_destroy(njs_vm_t *vm) nxt_int_t -njs_vm_compile(njs_vm_t *vm, u_char **start, u_char *end) +njs_vm_compile(njs_vm_t *vm, u_char **start, u_char *end, + njs_function_t **function) { nxt_int_t ret; njs_lexer_t *lexer; + njs_value_t *value; njs_parser_t *parser; njs_parser_node_t *node; @@ -216,6 +218,16 @@ njs_vm_compile(njs_vm_t *vm, u_char **start, u_char *end) return NJS_ERROR; } + if (function != NULL) { + if (node->token == NJS_TOKEN_CALL) { + value = njs_variable_value(parser, node->right->index); + *function = value->data.u.function; + + } else { + *function = NULL; + } + } + *start = parser->lexer->start; ret = njs_generate_scope(vm, parser, node); @@ -318,6 +330,47 @@ fail: nxt_int_t +njs_vm_call(njs_vm_t *vm, njs_function_t *function, njs_opaque_value_t *args, + nxt_uint_t nargs) +{ + u_char *current; + njs_ret_t ret; + njs_value_t *this; + + static const njs_vmcode_stop_t stop[] = { + { .code = { .operation = njs_vmcode_stop, + .operands = NJS_VMCODE_1OPERAND, + .retval = NJS_VMCODE_NO_RETVAL }, + .retval = NJS_INDEX_GLOBAL_RETVAL }, + }; + + this = (njs_value_t *) &njs_value_void; + + ret = njs_function_frame(vm, function, this, + (njs_value_t *) args, nargs, 0); + + if (nxt_slow_path(ret != NXT_OK)) { + return ret; + } + + current = vm->current; + vm->current = (u_char *) stop; + + (void) njs_function_call(vm, NJS_INDEX_GLOBAL_RETVAL, 0); + + ret = njs_vmcode_interpreter(vm); + + vm->current = current; + + if (ret == NJS_STOP) { + ret = NXT_OK; + } + + return ret; +} + + +nxt_int_t njs_vm_run(njs_vm_t *vm) { nxt_str_t s; diff --git a/njs/njscript.h b/njs/njscript.h index 80688837..93509352 100644 --- a/njs/njscript.h +++ b/njs/njscript.h @@ -12,8 +12,13 @@ typedef intptr_t njs_ret_t; typedef uintptr_t njs_index_t; typedef struct njs_vm_s njs_vm_t; typedef union njs_value_s njs_value_t; +typedef struct njs_function_s njs_function_t; typedef struct njs_vm_shared_s njs_vm_shared_t; +typedef struct { + uint64_t filler[2]; +} njs_opaque_value_t; + /* sizeof(njs_value_t) is 16 bytes. */ #define njs_argument(args, n) \ @@ -71,14 +76,19 @@ struct njs_external_s { NXT_EXPORT nxt_int_t njs_add_external(nxt_lvlhsh_t *hash, nxt_mem_cache_pool_t *mcp, uintptr_t object, njs_external_t *external, nxt_uint_t n); +NXT_EXPORT nxt_int_t njs_external_get(njs_vm_t *vm, njs_opaque_value_t *object, + nxt_str_t *property, njs_opaque_value_t *value); NXT_EXPORT njs_vm_t *njs_vm_create(nxt_mem_cache_pool_t *mcp, njs_vm_shared_t **shared, nxt_lvlhsh_t *externals); NXT_EXPORT void njs_vm_destroy(njs_vm_t *vm); -NXT_EXPORT nxt_int_t njs_vm_compile(njs_vm_t *vm, u_char **start, u_char *end); +NXT_EXPORT nxt_int_t njs_vm_compile(njs_vm_t *vm, u_char **start, u_char *end, + njs_function_t **function); NXT_EXPORT njs_vm_t *njs_vm_clone(njs_vm_t *vm, nxt_mem_cache_pool_t *mcp, void **external); +NXT_EXPORT nxt_int_t njs_vm_call(njs_vm_t *vm, njs_function_t *function, + njs_opaque_value_t *args, nxt_uint_t nargs); NXT_EXPORT nxt_int_t njs_vm_run(njs_vm_t *vm); NXT_EXPORT njs_ret_t njs_vm_return_string(njs_vm_t *vm, u_char *start, diff --git a/njs/test/njs_unit_test.c b/njs/test/njs_unit_test.c index adbc11f7..e74bbd24 100644 --- a/njs/test/njs_unit_test.c +++ b/njs/test/njs_unit_test.c @@ -3899,6 +3899,9 @@ static njs_unit_test_t njs_test[] = { nxt_string("eval()"), nxt_string("") }, + { nxt_string("function f(req) { return req.uri }"), + nxt_string("АБВ") }, + /* Trick: number to boolean. */ { nxt_string("var a = 0; !!a"), @@ -4192,12 +4195,14 @@ njs_unit_test(nxt_bool_t disassemble) u_char *start; njs_vm_t *vm, *nvm; nxt_int_t ret; - nxt_str_t s; + nxt_str_t s, r_name; nxt_uint_t i; nxt_bool_t success; nxt_lvlhsh_t externals; + njs_function_t *function; njs_vm_shared_t *shared; njs_unit_test_req r; + njs_opaque_value_t value; nxt_mem_cache_pool_t *mcp; shared = NULL; @@ -4230,7 +4235,8 @@ njs_unit_test(nxt_bool_t disassemble) start = njs_test[i].script.data; - ret = njs_vm_compile(vm, &start, start + njs_test[i].script.len); + ret = njs_vm_compile(vm, &start, start + njs_test[i].script.len, + &function); if (ret == NXT_OK) { if (disassemble) { @@ -4245,8 +4251,22 @@ njs_unit_test(nxt_bool_t disassemble) r.uri.len = 6; r.uri.data = (u_char *) "АБВ"; - if (njs_vm_run(nvm) == NXT_OK) { + if (function != NULL) { + r_name.len = 2; + r_name.data = (u_char *) "$r"; + + ret = njs_external_get(vm, NULL, &r_name, &value); + if (ret != NXT_OK) { + return NXT_ERROR; + } + + ret = njs_vm_call(nvm, function, &value, 1); + + } else { + ret = njs_vm_run(nvm); + } + if (ret == NXT_OK) { if (njs_vm_retval(nvm, &s) != NXT_OK) { return NXT_ERROR; } @@ -4329,7 +4349,7 @@ njs_unit_test_benchmark(nxt_str_t *script, nxt_str_t *result, const char *msg, start = script->data; - ret = njs_vm_compile(vm, &start, start + script->len); + ret = njs_vm_compile(vm, &start, start + script->len, NULL); if (ret != NXT_OK) { return NXT_ERROR; } |