[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