[njs] Fixed Function constructor according to the spec.

Dmitry Volyntsev xeioex at nginx.com
Tue Jan 21 13:04:42 UTC 2020


details:   https://hg.nginx.org/njs/rev/142b3fec5d4f
branches:  
changeset: 1306:142b3fec5d4f
user:      Dmitry Volyntsev <xeioex at nginx.com>
date:      Tue Jan 21 16:01:45 2020 +0300
description:
Fixed Function constructor according to the spec.

"this" value should point to the global object.

diffstat:

 src/njs.h                |    2 +-
 src/njs_function.c       |  127 ++++++++++++++++++++++++++--------------------
 src/njs_value.h          |    3 +-
 src/test/njs_unit_test.c |   12 ++++
 test/njs_expect_test.exp |    6 ++
 5 files changed, 93 insertions(+), 57 deletions(-)

diffs (242 lines):

diff -r c735708203e8 -r 142b3fec5d4f src/njs.h
--- a/src/njs.h	Fri Jan 17 10:04:28 2020 +0300
+++ b/src/njs.h	Tue Jan 21 16:01:45 2020 +0300
@@ -41,7 +41,7 @@ typedef struct {
 
 /* sizeof(njs_value_t) is 16 bytes. */
 #define njs_argument(args, n)                                                 \
-    (njs_value_t *) ((u_char *) args + n * 16)
+    (njs_value_t *) ((u_char *) args + (n) * 16)
 
 
 extern const njs_value_t            njs_value_undefined;
diff -r c735708203e8 -r 142b3fec5d4f src/njs_function.c
--- a/src/njs_function.c	Fri Jan 17 10:04:28 2020 +0300
+++ b/src/njs_function.c	Tue Jan 21 16:01:45 2020 +0300
@@ -463,7 +463,14 @@ njs_function_lambda_frame(njs_vm_t *vm, 
     native_frame->arguments = value;
 
     if (bound == NULL) {
-        *value++ = *this;
+        *value = *this;
+
+        if (njs_slow_path(function->global_this
+                          && njs_is_null_or_undefined(this))) {
+            njs_set_object(value, &vm->global_object);
+        }
+
+        value++;
 
     } else {
         n = function->args_offset;
@@ -838,64 +845,69 @@ static njs_int_t
 njs_function_constructor(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
     njs_index_t unused)
 {
-    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;
+    njs_chb_t              chain;
+    njs_int_t              ret;
+    njs_str_t              str, file;
+    njs_uint_t             i;
+    njs_value_t            *body;
+    njs_lexer_t            lexer;
+    njs_parser_t           *parser;
+    njs_function_t         *function;
+    njs_generator_t        generator;
+    njs_parser_scope_t     *scope;
+    njs_function_lambda_t  *lambda;
+    njs_vmcode_function_t  *code;
 
     if (!vm->options.unsafe) {
-        njs_type_error(vm, "function constructor is disabled in \"safe\" mode");
-        return NJS_ERROR;
+        body = njs_argument(args, nargs - 1);
+        ret = njs_value_to_string(vm, body, body);
+        if (njs_slow_path(ret != NJS_OK)) {
+            return ret;
+        }
+
+        njs_string_get(body, &str);
+
+        /*
+         * Safe mode exception:
+         * "(new Function('return this'))" is often used to get
+         * the global object in a portable way.
+         */
+
+        if (str.length != njs_length("return this")
+            || memcmp(str.start, "return this", 11) != 0)
+        {
+            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(){})");
+    njs_chb_init(&chain, vm->mem_pool);
 
-    } else {
-        size = njs_length("(function(") + nargs + njs_length("){})");
+    njs_chb_append_literal(&chain, "(function(");
 
-        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;
+    for (i = 1; i < nargs - 1; i++) {
+        ret = njs_value_to_chain(vm, &chain, njs_argument(args, i));
+        if (njs_slow_path(ret < NJS_OK)) {
+            return ret;
         }
 
-        start = njs_mp_alloc(vm->mem_pool, size);
-        if (njs_slow_path(start == NULL)) {
-            return NJS_ERROR;
-        }
+        njs_chb_append_literal(&chain, ",");
+    }
 
-        end = njs_cpymem(start, "(function(", njs_length("(function("));
-
-        for (i = 1; i < nargs - 1; i++) {
-            if (i != 1) {
-                *end++ = ',';
-            }
+    njs_chb_append_literal(&chain, "){");
 
-            njs_string_get(&args[i], &str);
-            end = njs_cpymem(end, str.start, str.length);
-        }
+    ret = njs_value_to_chain(vm, &chain, njs_argument(args, nargs - 1));
+    if (njs_slow_path(ret < NJS_OK)) {
+        return ret;
+    }
 
-        *end++ = ')';
-        *end++ = '{';
+    njs_chb_append_literal(&chain, "})");
 
-        njs_string_get(&args[nargs - 1], &str);
-        end = njs_cpymem(end, str.start, str.length);
-
-        *end++ = '}';
-        *end++ = ')';
+    ret = njs_chb_join(&chain, &str);
+    if (njs_slow_path(ret != NJS_OK)) {
+        njs_memory_error(vm);
+        return NJS_ERROR;
     }
 
     vm->options.accumulative = 1;
@@ -909,7 +921,7 @@ njs_function_constructor(njs_vm_t *vm, n
 
     file = njs_str_value("runtime");
 
-    ret = njs_lexer_init(vm, &lexer, &file, start, end);
+    ret = njs_lexer_init(vm, &lexer, &file, str.start, str.start + str.length);
     if (njs_slow_path(ret != NJS_OK)) {
         return ret;
     }
@@ -940,15 +952,20 @@ njs_function_constructor(njs_vm_t *vm, n
         return ret;
     }
 
-    if (vm->options.disassemble) {
-        njs_printf("new Function:runtime\n");
-        njs_disassemble(generator.code_start, generator.code_end);
+    njs_chb_destroy(&chain);
+
+    code = (njs_vmcode_function_t *) generator.code_start;
+    lambda = code->lambda;
+
+    function = njs_function_alloc(vm, lambda, NULL, 0);
+    if (njs_slow_path(function == NULL)) {
+        return NJS_ERROR;
     }
 
-    ret = njs_vmcode_interpreter(vm, generator.code_start);
-    if (njs_slow_path(ret != NJS_OK)) {
-        return ret;
-    }
+    function->global_this = 1;
+    function->args_count = lambda->nargs - lambda->rest_parameters;
+
+    njs_set_function(&vm->retval, function);
 
     return NJS_OK;
 }
diff -r c735708203e8 -r 142b3fec5d4f src/njs_value.h
--- a/src/njs_value.h	Fri Jan 17 10:04:28 2020 +0300
+++ b/src/njs_value.h	Tue Jan 21 16:01:45 2020 +0300
@@ -278,7 +278,7 @@ struct njs_function_s {
 
     uint8_t                           args_offset;
 
-    uint8_t                           args_count:5;
+    uint8_t                           args_count:4;
 
     /*
      * If "closure" is true njs_closure_t[] is available right after the
@@ -291,6 +291,7 @@ struct njs_function_s {
     uint8_t                           closure:1;
     uint8_t                           native:1;
     uint8_t                           ctor:1;
+    uint8_t                           global_this:1;
 
     uint8_t                           magic;
 
diff -r c735708203e8 -r 142b3fec5d4f src/test/njs_unit_test.c
--- a/src/test/njs_unit_test.c	Fri Jan 17 10:04:28 2020 +0300
+++ b/src/test/njs_unit_test.c	Tue Jan 21 16:01:45 2020 +0300
@@ -11296,6 +11296,18 @@ static njs_unit_test_t  njs_test[] =
     { 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("(new Function('return this'))() === globalThis"),
+      njs_str("true") },
+
+    { njs_str("(new Function('a', 'return a')).length"),
+      njs_str("1") },
+
+    { njs_str("(new Function('a','b', 'return a + b')).length"),
+      njs_str("2") },
+
+    { njs_str("var o = {}; (new Function('return this')).call(o) === o"),
+      njs_str("true") },
+
     { njs_str("this.NN = {}; var f = Function('eval = 42;'); f()"),
       njs_str("SyntaxError: Identifier \"eval\" is forbidden as left-hand in assignment in runtime:1") },
 
diff -r c735708203e8 -r 142b3fec5d4f test/njs_expect_test.exp
--- a/test/njs_expect_test.exp	Fri Jan 17 10:04:28 2020 +0300
+++ b/test/njs_expect_test.exp	Tue Jan 21 16:01:45 2020 +0300
@@ -849,6 +849,12 @@ njs_test {
 njs_test {
     {"new Function()\r\n"
      "TypeError: function constructor is disabled in \"safe\" mode\r\n"}
+    {"(new Function('return this'))() === globalThis\r\n"
+     "true\r\n"}
+    {"new Function('return this;')\r\n"
+     "TypeError: function constructor is disabled in \"safe\" mode\r\n"}
+    {"new Function('return thi')\r\n"
+     "TypeError: function constructor is disabled in \"safe\" mode\r\n"}
 } "-u"
 
 


More information about the nginx-devel mailing list