[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