[njs] Added new Function() support.
Dmitry Volyntsev
xeioex at nginx.com
Mon Sep 2 15:25:37 UTC 2019
details: https://hg.nginx.org/njs/rev/98d3dd617ed7
branches:
changeset: 1155:98d3dd617ed7
user: hongzhidao <hongzhidao at gmail.com>
date: Fri Aug 23 13:25:50 2019 -0400
description:
Added new Function() support.
diffstat:
src/njs.h | 18 +++++-
src/njs_disassembler.c | 7 +-
src/njs_function.c | 114 +++++++++++++++++++++++++++++++++++++++-
src/njs_shell.c | 36 ++++++-----
src/njs_vm.c | 4 +
src/test/njs_interactive_test.c | 3 +
src/test/njs_unit_test.c | 84 +++++++++++++++++++++++++++--
test/njs_expect_test.exp | 8 ++
8 files changed, 244 insertions(+), 30 deletions(-)
diffs (530 lines):
diff -r ac633d007ac5 -r 98d3dd617ed7 src/njs.h
--- a/src/njs.h Thu Aug 29 19:18:53 2019 +0300
+++ b/src/njs.h Fri Aug 23 13:25:50 2019 -0400
@@ -146,13 +146,28 @@ typedef struct {
char **argv;
njs_uint_t argc;
+/*
+ * accumulative - enables "accumulative" mode to support incremental compiling.
+ * (REPL). Allows starting parent VM without cloning.
+ * disassemble - enables disassemble.
+ * backtrace - enables backtraces.
+ * quiet - removes filenames from backtraces. To produce comparable
+ test262 diffs.
+ * sandbox - "sandbox" mode. Disables file access.
+ * unsafe - enables unsafe language features:
+ * - Function constructors.
+ * module - ES6 "module" mode. Script mode is default.
+ */
+
uint8_t trailer; /* 1 bit */
uint8_t init; /* 1 bit */
uint8_t accumulative; /* 1 bit */
+ uint8_t disassemble; /* 1 bit */
uint8_t backtrace; /* 1 bit */
+ uint8_t quiet; /* 1 bit */
uint8_t sandbox; /* 1 bit */
+ uint8_t unsafe; /* 1 bit */
uint8_t module; /* 1 bit */
- uint8_t quiet; /* 1 bit */
} njs_vm_opt_t;
@@ -225,6 +240,7 @@ NJS_EXPORT njs_external_ptr_t njs_vm_ext
const njs_value_t *value);
NJS_EXPORT void njs_disassembler(njs_vm_t *vm);
+NJS_EXPORT void njs_disassemble(u_char *start, u_char *end);
NJS_EXPORT const njs_value_t *njs_vm_value(njs_vm_t *vm, const njs_str_t *name);
NJS_EXPORT njs_function_t *njs_vm_function(njs_vm_t *vm, const njs_str_t *name);
diff -r ac633d007ac5 -r 98d3dd617ed7 src/njs_disassembler.c
--- a/src/njs_disassembler.c Thu Aug 29 19:18:53 2019 +0300
+++ b/src/njs_disassembler.c Fri Aug 23 13:25:50 2019 -0400
@@ -8,9 +8,6 @@
#include <njs_main.h>
-static void njs_disassemble(u_char *start, u_char *end);
-
-
typedef struct {
njs_vmcode_operation_t operation;
size_t size;
@@ -152,10 +149,12 @@ njs_disassembler(njs_vm_t *vm)
code++;
n--;
}
+
+ njs_printf("\n");
}
-static void
+void
njs_disassemble(u_char *start, u_char *end)
{
u_char *p;
diff -r ac633d007ac5 -r 98d3dd617ed7 src/njs_function.c
--- a/src/njs_function.c Thu Aug 29 19:18:53 2019 +0300
+++ b/src/njs_function.c Fri Aug 23 13:25:50 2019 -0400
@@ -874,9 +874,119 @@ njs_int_t
njs_function_constructor(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
njs_index_t unused)
{
- njs_internal_error(vm, "Not implemented");
+ size_t size;
+ u_char *start, *end;
+ njs_int_t ret;
+ njs_str_t str, file;
+ njs_uint_t i;
+ njs_lexer_t lexer;
+ njs_parser_t *parser;
+ njs_generator_t generator;
+ njs_parser_scope_t *scope;
+
+ if (!vm->options.unsafe) {
+ njs_type_error(vm, "function constructor is disabled in \"safe\" mode");
+ return NJS_ERROR;
+ }
+
+ if (nargs < 2) {
+ start = (u_char *) "(function(){})";
+ end = start + njs_length("(function(){})");
+
+ } else {
+ size = njs_length("(function(") + nargs + njs_length("){})");
+
+ for (i = 1; i < nargs; i++) {
+ if (!njs_is_string(&args[i])) {
+ ret = njs_value_to_string(vm, &args[i], &args[i]);
+ if (ret != NJS_OK) {
+ return ret;
+ }
+ }
+
+ njs_string_get(&args[i], &str);
+ size += str.length;
+ }
+
+ start = njs_mp_alloc(vm->mem_pool, size);
+ if (njs_slow_path(start == NULL)) {
+ return NJS_ERROR;
+ }
+
+ end = njs_cpymem(start, "(function(", njs_length("(function("));
+
+ for (i = 1; i < nargs - 1; i++) {
+ if (i != 1) {
+ *end++ = ',';
+ }
+
+ njs_string_get(&args[i], &str);
+ end = njs_cpymem(end, str.start, str.length);
+ }
+
+ *end++ = ')';
+ *end++ = '{';
+
+ njs_string_get(&args[nargs - 1], &str);
+ end = njs_cpymem(end, str.start, str.length);
- return NJS_ERROR;
+ *end++ = '}';
+ *end++ = ')';
+ }
+
+ vm->options.accumulative = 1;
+
+ parser = njs_mp_zalloc(vm->mem_pool, sizeof(njs_parser_t));
+ if (njs_slow_path(parser == NULL)) {
+ return NJS_ERROR;
+ }
+
+ vm->parser = parser;
+
+ file = njs_str_value("runtime");
+
+ ret = njs_lexer_init(vm, &lexer, &file, start, end);
+ if (njs_slow_path(ret != NJS_OK)) {
+ return ret;
+ }
+
+ parser->lexer = &lexer;
+
+ ret = njs_parser(vm, parser, NULL);
+ if (njs_slow_path(ret != NJS_OK)) {
+ return ret;
+ }
+
+ scope = parser->scope;
+
+ ret = njs_variables_copy(vm, &scope->variables, &vm->variables_hash);
+ if (njs_slow_path(ret != NJS_OK)) {
+ return ret;
+ }
+
+ ret = njs_variables_scope_reference(vm, scope);
+ if (njs_slow_path(ret != NJS_OK)) {
+ return ret;
+ }
+
+ njs_memzero(&generator, sizeof(njs_generator_t));
+
+ ret = njs_generate_scope(vm, &generator, scope, &njs_entry_anonymous);
+ if (njs_slow_path(ret != NJS_OK)) {
+ return ret;
+ }
+
+ if (vm->options.disassemble) {
+ njs_printf("new Function:runtime\n");
+ njs_disassemble(generator.code_start, generator.code_end);
+ }
+
+ ret = njs_vmcode_interpreter(vm, generator.code_start);
+ if (njs_slow_path(ret != NJS_OK)) {
+ return ret;
+ }
+
+ return NJS_OK;
}
diff -r ac633d007ac5 -r 98d3dd617ed7 src/njs_shell.c
--- a/src/njs_shell.c Thu Aug 29 19:18:53 2019 +0300
+++ b/src/njs_shell.c Fri Aug 23 13:25:50 2019 -0400
@@ -25,6 +25,7 @@ typedef struct {
uint8_t module;
uint8_t quiet;
uint8_t sandbox;
+ uint8_t safe;
uint8_t version;
char *file;
@@ -74,7 +75,7 @@ static njs_int_t njs_interactive_shell(n
njs_vm_opt_t *vm_options);
static njs_vm_t *njs_create_vm(njs_opts_t *opts, njs_vm_opt_t *vm_options);
static njs_int_t njs_process_file(njs_opts_t *opts, njs_vm_opt_t *vm_options);
-static njs_int_t njs_process_script(njs_console_t *console, njs_opts_t *opts,
+static njs_int_t njs_process_script(njs_console_t *console,
const njs_str_t *script);
static njs_int_t njs_editline_init(void);
static char **njs_completion_handler(const char *text, int start, int end);
@@ -247,9 +248,11 @@ main(int argc, char **argv)
vm_options.init = !opts.interactive;
vm_options.accumulative = opts.interactive;
+ vm_options.disassemble = opts.disassemble;
vm_options.backtrace = 1;
vm_options.quiet = opts.quiet;
vm_options.sandbox = opts.sandbox;
+ vm_options.unsafe = !opts.safe;
vm_options.module = opts.module;
vm_options.ops = &njs_console_ops;
@@ -265,7 +268,7 @@ main(int argc, char **argv)
if (vm != NULL) {
command.start = (u_char *) opts.command;
command.length = njs_strlen(opts.command);
- ret = njs_process_script(vm_options.external, &opts, &command);
+ ret = njs_process_script(vm_options.external, &command);
}
} else {
@@ -299,6 +302,7 @@ njs_get_options(njs_opts_t *opts, int ar
" -s sandbox mode.\n"
" -t script|module source code type (script is default).\n"
" -v print njs version and exit.\n"
+ " -u disable \"unsafe\" mode.\n"
" <filename> | - run code from a file or stdin.\n";
ret = NJS_DONE;
@@ -382,6 +386,10 @@ njs_get_options(njs_opts_t *opts, int ar
opts->version = 1;
break;
+ case 'u':
+ opts->safe = 1;
+ break;
+
default:
njs_stderror("Unknown argument: \"%s\" "
"try \"%s -h\" for available options\n", argv[i],
@@ -488,7 +496,7 @@ njs_interactive_shell(njs_opts_t *opts,
add_history((char *) line.start);
- njs_process_script(vm_options->external, opts, &line);
+ njs_process_script(vm_options->external, &line);
/* editline allocs a new buffer every time. */
free(line.start);
@@ -607,7 +615,7 @@ njs_process_file(njs_opts_t *opts, njs_v
}
}
- ret = njs_process_script(vm_options->external, opts, &script);
+ ret = njs_process_script(vm_options->external, &script);
if (ret != NJS_OK) {
ret = NJS_ERROR;
goto done;
@@ -691,7 +699,7 @@ njs_create_vm(njs_opts_t *opts, njs_vm_o
static void
-njs_output(njs_vm_t *vm, njs_opts_t *opts, njs_int_t ret)
+njs_output(njs_vm_t *vm, njs_int_t ret)
{
njs_str_t out;
@@ -703,7 +711,7 @@ njs_output(njs_vm_t *vm, njs_opts_t *opt
if (ret != NJS_OK) {
njs_stderror("%V\n", &out);
- } else if (opts->interactive) {
+ } else if (vm->options.accumulative) {
njs_print(out.start, out.length);
njs_printf("\n");
}
@@ -711,7 +719,7 @@ njs_output(njs_vm_t *vm, njs_opts_t *opt
static njs_int_t
-njs_process_events(njs_console_t *console, njs_opts_t *opts)
+njs_process_events(njs_console_t *console)
{
njs_ev_t *ev;
njs_queue_t *events;
@@ -740,8 +748,7 @@ njs_process_events(njs_console_t *consol
static njs_int_t
-njs_process_script(njs_console_t *console, njs_opts_t *opts,
- const njs_str_t *script)
+njs_process_script(njs_console_t *console, const njs_str_t *script)
{
u_char *start;
njs_vm_t *vm;
@@ -753,22 +760,17 @@ njs_process_script(njs_console_t *consol
ret = njs_vm_compile(vm, &start, start + script->length);
if (ret == NJS_OK) {
- if (opts->disassemble) {
- njs_disassembler(vm);
- njs_printf("\n");
- }
-
ret = njs_vm_start(vm);
}
- njs_output(vm, opts, ret);
+ njs_output(vm, ret);
for ( ;; ) {
if (!njs_vm_pending(vm)) {
break;
}
- ret = njs_process_events(console, opts);
+ ret = njs_process_events(console);
if (njs_slow_path(ret != NJS_OK)) {
njs_stderror("njs_process_events() failed\n");
ret = NJS_ERROR;
@@ -786,7 +788,7 @@ njs_process_script(njs_console_t *consol
ret = njs_vm_run(vm);
if (ret == NJS_ERROR) {
- njs_output(vm, opts, ret);
+ njs_output(vm, ret);
}
}
diff -r ac633d007ac5 -r 98d3dd617ed7 src/njs_vm.c
--- a/src/njs_vm.c Thu Aug 29 19:18:53 2019 +0300
+++ b/src/njs_vm.c Fri Aug 23 13:25:50 2019 -0400
@@ -224,6 +224,10 @@ njs_vm_compile(njs_vm_t *vm, u_char **st
}
}
+ if (vm->options.disassemble) {
+ njs_disassembler(vm);
+ }
+
return NJS_OK;
fail:
diff -r ac633d007ac5 -r 98d3dd617ed7 src/test/njs_interactive_test.c
--- a/src/test/njs_interactive_test.c Thu Aug 29 19:18:53 2019 +0300
+++ b/src/test/njs_interactive_test.c Fri Aug 23 13:25:50 2019 -0400
@@ -173,6 +173,9 @@ static njs_interactive_test_t njs_test[
" at eval (native)\n"
" at main (native)\n") },
+ { njs_str("new Function(\n\n@)" ENTER),
+ njs_str("SyntaxError: Unexpected token \"@\" in 3") },
+
{ njs_str("require()" ENTER),
njs_str("TypeError: missing path\n"
" at require (native)\n"
diff -r ac633d007ac5 -r 98d3dd617ed7 src/test/njs_unit_test.c
--- a/src/test/njs_unit_test.c Thu Aug 29 19:18:53 2019 +0300
+++ b/src/test/njs_unit_test.c Fri Aug 23 13:25:50 2019 -0400
@@ -8,6 +8,9 @@
#include <njs_main.h>
+#define NJS_HAVE_LARGE_STACK (!NJS_HAVE_ADDRESS_SANITIZER && !NJS_HAVE_MEMORY_SANITIZER)
+
+
typedef struct {
njs_str_t script;
njs_str_t ret;
@@ -4225,7 +4228,7 @@ static njs_unit_test_t njs_test[] =
"Array.prototype.fill.call(o, 2).a"),
njs_str("4") },
-#if (!NJS_HAVE_ADDRESS_SANITIZER && !NJS_HAVE_MEMORY_SANITIZER) /* limited stack size */
+#if NJS_HAVE_LARGE_STACK
{ njs_str("var o = Object({length: 3});"
"Object.defineProperty(o, '0', {set: function(v){this[0] = 2 * v}});"
"Array.prototype.fill.call(o, 2)"),
@@ -6558,7 +6561,7 @@ static njs_unit_test_t njs_test[] =
{ njs_str("{ function f() {} { var f }}"),
njs_str("SyntaxError: \"f\" has already been declared in 1") },
-#if (!NJS_HAVE_ADDRESS_SANITIZER && !NJS_HAVE_MEMORY_SANITIZER) /* limited stack size */
+#if NJS_HAVE_LARGE_STACK
{ njs_str("function f() { return f() } f()"),
njs_str("RangeError: Maximum call stack size exceeded") },
#endif
@@ -8837,9 +8840,6 @@ static njs_unit_test_t njs_test[] =
{ njs_str("var ex; try {({}) instanceof this} catch (e) {ex = e}; ex"),
njs_str("TypeError: right argument is not callable") },
- { njs_str("Function.call(this, 'var x / = 1;')"),
- njs_str("InternalError: Not implemented") },
-
{ njs_str("njs"),
njs_str("[object Object]") },
@@ -9590,7 +9590,76 @@ static njs_unit_test_t njs_test[] =
njs_str("true") },
{ njs_str("Function()"),
- njs_str("InternalError: Not implemented") },
+ njs_str("[object Function]") },
+
+ { njs_str("new Function();"),
+ njs_str("[object Function]") },
+
+ { njs_str("(function(){}).constructor === Function"),
+ njs_str("true") },
+
+#if NJS_HAVE_LARGE_STACK
+ { njs_str("new Function(\"(\".repeat(2**13));"),
+ njs_str("RangeError: Maximum call stack size exceeded") },
+
+ { njs_str("new Function(\"{\".repeat(2**13));"),
+ njs_str("RangeError: Maximum call stack size exceeded") },
+
+ { njs_str("new Function(\"[\".repeat(2**13));"),
+ njs_str("RangeError: Maximum call stack size exceeded") },
+
+ { njs_str("new Function(\"`\".repeat(2**13));"),
+ njs_str("RangeError: Maximum call stack size exceeded") },
+
+ { njs_str("new Function(\"{[\".repeat(2**13));"),
+ njs_str("RangeError: Maximum call stack size exceeded") },
+
+ { njs_str("new Function(\"{;\".repeat(2**13));"),
+ njs_str("RangeError: Maximum call stack size exceeded") },
+
+ { njs_str("new Function(\"1;\".repeat(2**13));"),
+ njs_str("RangeError: Maximum call stack size exceeded") },
+
+ { njs_str("new Function(\"~\".repeat(2**13));"),
+ njs_str("RangeError: Maximum call stack size exceeded") },
+
+ { njs_str("new Function(\"new \".repeat(2**13));"),
+ njs_str("RangeError: Maximum call stack size exceeded") },
+
+ { njs_str("new Function(\"typeof \".repeat(2**13));"),
+ njs_str("RangeError: Maximum call stack size exceeded") },
+
+ { njs_str("new Function(\"1\" + \"** 1\".repeat(2**13));"),
+ njs_str("RangeError: Maximum call stack size exceeded") },
+
+ { njs_str("new Function(\"var a; a\" + \"= a\".repeat(2**13));"),
+ njs_str("RangeError: Maximum call stack size exceeded") },
+#endif
+
+ { njs_str("var f = new Function('return 1;'); f();"),
+ njs_str("1") },
+
+ { njs_str("var sum = new Function('a', 'b', 'return a + b');"
+ "sum(2, 4);"),
+ njs_str("6") },
+
+ { njs_str("var sum = new Function('a, b', 'return a + b');"
+ "sum(2, 4);"),
+ njs_str("6") },
+
+ { njs_str("var sum = new Function('a, b', 'c', 'return a + b + c');"
+ "sum(2, 4, 4);"),
+ njs_str("10") },
+
+ { njs_str("(new Function({ toString() { return '...a'; }}, { toString() { return 'return a;' }}))(1,2,3)"),
+ njs_str("1,2,3") },
+
+ { njs_str("var x = 10; function foo() { var x = 20; return new Function('return x;'); }"
+ "var f = foo(); f()"),
+ njs_str("10") },
+
+ { njs_str("var fn = (function() { return new Function('return this'); }).call({}), o = {}; fn.call(o) == o && fn.bind(o).call(this) == o"),
+ njs_str("true") },
{ njs_str("RegExp()"),
njs_str("/(?:)/") },
@@ -14535,6 +14604,7 @@ njs_externals_init(njs_vm_t *vm)
typedef struct {
njs_bool_t disassemble;
njs_bool_t verbose;
+ njs_bool_t unsafe;
njs_bool_t module;
njs_uint_t repeat;
} njs_opts_t;
@@ -14588,6 +14658,7 @@ njs_unit_test(njs_unit_test_t tests[], s
njs_memzero(&options, sizeof(njs_vm_opt_t));
options.module = opts->module;
+ options.unsafe = opts->unsafe;
vm = njs_vm_create(&options);
if (vm == NULL) {
@@ -15112,6 +15183,7 @@ main(int argc, char **argv)
njs_memzero(&stat, sizeof(njs_stat_t));
opts.repeat = 1;
+ opts.unsafe = 1;
ret = njs_unit_test(njs_test, njs_nitems(njs_test), "script tests",
&opts, &stat);
diff -r ac633d007ac5 -r 98d3dd617ed7 test/njs_expect_test.exp
--- a/test/njs_expect_test.exp Thu Aug 29 19:18:53 2019 +0300
+++ b/test/njs_expect_test.exp Fri Aug 23 13:25:50 2019 -0400
@@ -799,6 +799,14 @@ njs_test {
} "-s"
+# safe mode
+
+njs_test {
+ {"new Function()\r\n"
+ "TypeError: function constructor is disabled in \"safe\" mode\r\n"}
+} "-u"
+
+
# source type
njs_test {
More information about the nginx-devel
mailing list