[njs] Added arguments object.

Dmitry Volyntsev xeioex at nginx.com
Tue Oct 23 17:40:18 UTC 2018


details:   http://hg.nginx.org/njs/rev/72539416c466
branches:  
changeset: 630:72539416c466
user:      Dmitry Volyntsev <xeioex at nginx.com>
date:      Tue Oct 23 20:40:08 2018 +0300
description:
Added arguments object.

This closes #19 issue on Github.

diffstat:

 njs/njs_builtin.c           |  23 +++++++++++
 njs/njs_disassembler.c      |   2 +
 njs/njs_function.c          |  89 +++++++++++++++++++++++++++++++++++++++++++++
 njs/njs_function.h          |  12 +++++-
 njs/njs_generator.c         |  30 +++++++++++++++
 njs/njs_lexer_keyword.c     |   1 +
 njs/njs_parser.c            |  39 +++++++++++++++++++
 njs/njs_parser.h            |   3 +
 njs/njs_parser_expression.c |  15 ++++++-
 njs/njs_vm.c                |  23 +++++++++++
 njs/njs_vm.h                |   9 ++++
 njs/test/njs_unit_test.c    |  68 ++++++++++++++++++++++++++++++++++
 12 files changed, 310 insertions(+), 4 deletions(-)

diffs (542 lines):

diff -r de0974c3e90f -r 72539416c466 njs/njs_builtin.c
--- a/njs/njs_builtin.c	Tue Oct 23 20:40:02 2018 +0300
+++ b/njs/njs_builtin.c	Tue Oct 23 20:40:08 2018 +0300
@@ -127,6 +127,22 @@ const njs_function_init_t  njs_native_fu
 };
 
 
+const njs_object_prop_t  njs_arguments_object_properties[] =
+{
+    {
+        .type = NJS_PROPERTY_HANDLER,
+        .name = njs_string("caller"),
+        .value = njs_prop_handler(njs_function_arguments_thrower),
+    },
+
+    {
+        .type = NJS_PROPERTY_HANDLER,
+        .name = njs_string("callee"),
+        .value = njs_prop_handler(njs_function_arguments_thrower),
+    },
+};
+
+
 static njs_ret_t
 njs_prototype_function(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs,
     njs_index_t unused)
@@ -236,6 +252,13 @@ njs_builtin_objects_create(njs_vm_t *vm)
         return NXT_ERROR;
     }
 
+    ret = njs_object_hash_create(vm, &vm->shared->arguments_object_hash,
+                                 njs_arguments_object_properties,
+                                 nxt_nitems(njs_arguments_object_properties));
+    if (nxt_slow_path(ret != NXT_OK)) {
+        return NXT_ERROR;
+    }
+
     objects = vm->shared->objects;
 
     for (i = NJS_OBJECT_THIS; i < NJS_OBJECT_MAX; i++) {
diff -r de0974c3e90f -r 72539416c466 njs/njs_disassembler.c
--- a/njs/njs_disassembler.c	Tue Oct 23 20:40:02 2018 +0300
+++ b/njs/njs_disassembler.c	Tue Oct 23 20:40:08 2018 +0300
@@ -24,6 +24,8 @@ static njs_code_name_t  code_names[] = {
           nxt_string("OBJECT          ") },
     { njs_vmcode_function, sizeof(njs_vmcode_function_t),
           nxt_string("FUNCTION        ") },
+    { njs_vmcode_arguments, sizeof(njs_vmcode_arguments_t),
+          nxt_string("ARGUMENTS       ") },
     { njs_vmcode_regexp, sizeof(njs_vmcode_regexp_t),
           nxt_string("REGEXP          ") },
     { njs_vmcode_object_copy, sizeof(njs_vmcode_object_copy_t),
diff -r de0974c3e90f -r 72539416c466 njs/njs_function.c
--- a/njs/njs_function.c	Tue Oct 23 20:40:02 2018 +0300
+++ b/njs/njs_function.c	Tue Oct 23 20:40:08 2018 +0300
@@ -95,6 +95,87 @@ njs_function_value_copy(njs_vm_t *vm, nj
 }
 
 
+/*
+ * ES5.1, 10.6: CreateArgumentsObject.
+ */
+njs_ret_t
+njs_function_arguments_object_init(njs_vm_t *vm, njs_native_frame_t *frame)
+{
+    nxt_int_t           ret;
+    nxt_uint_t          nargs, n;
+    njs_value_t         value;
+    njs_object_t        *arguments;
+    njs_object_prop_t   *prop;
+    nxt_lvlhsh_query_t  lhq;
+
+    static const njs_value_t  njs_string_length = njs_string("length");
+
+    arguments = njs_object_alloc(vm);
+    if (nxt_slow_path(arguments == NULL)) {
+        return NXT_ERROR;
+    }
+
+    arguments->shared_hash = vm->shared->arguments_object_hash;
+
+    nargs = frame->nargs;
+
+    njs_value_number_set(&value, nargs);
+
+    prop = njs_object_prop_alloc(vm, &njs_string_length, &value, 1);
+    if (nxt_slow_path(prop == NULL)) {
+        return NXT_ERROR;
+    }
+
+    prop->enumerable = 0;
+
+    lhq.value = prop;
+    lhq.key_hash = NJS_LENGTH_HASH;
+    njs_string_get(&prop->name, &lhq.key);
+
+    lhq.replace = 0;
+    lhq.pool = vm->mem_cache_pool;
+    lhq.proto = &njs_object_hash_proto;
+
+    ret = nxt_lvlhsh_insert(&arguments->hash, &lhq);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        njs_internal_error(vm, "lvlhsh insert failed");
+        return NXT_ERROR;
+    }
+
+    for (n = 0; n < nargs; n++) {
+        njs_uint32_to_string(&value, n);
+
+        prop = njs_object_prop_alloc(vm, &value, &frame->arguments[n + 1], 1);
+        if (nxt_slow_path(prop == NULL)) {
+            return NXT_ERROR;
+        }
+
+        lhq.value = prop;
+        njs_string_get(&prop->name, &lhq.key);
+        lhq.key_hash = nxt_djb_hash(lhq.key.start, lhq.key.length);
+
+        ret = nxt_lvlhsh_insert(&arguments->hash, &lhq);
+        if (nxt_slow_path(ret != NXT_OK)) {
+            njs_internal_error(vm, "lvlhsh insert failed");
+            return NXT_ERROR;
+        }
+    }
+
+    frame->arguments_object = arguments;
+
+    return NXT_OK;
+}
+
+
+njs_ret_t
+njs_function_arguments_thrower(njs_vm_t *vm, njs_value_t *value,
+    njs_value_t *setval, njs_value_t *retval)
+{
+    njs_type_error(vm, "'caller', 'callee' properties may not be accessed");
+    return NXT_ERROR;
+}
+
+
 njs_ret_t
 njs_function_native_frame(njs_vm_t *vm, njs_function_t *function,
     const njs_value_t *this, njs_value_t *args, nxt_uint_t nargs,
@@ -315,6 +396,7 @@ nxt_noinline njs_ret_t
 njs_function_call(njs_vm_t *vm, njs_index_t retval, size_t advance)
 {
     size_t                 size;
+    njs_ret_t              ret;
     nxt_uint_t             n, nesting;
     njs_frame_t            *frame;
     njs_value_t            *value;
@@ -393,6 +475,13 @@ njs_function_call(njs_vm_t *vm, njs_inde
         vm->scopes[NJS_SCOPE_CLOSURE + n] = &closure->u.values;
     }
 
+    if (lambda->arguments_object) {
+        ret = njs_function_arguments_object_init(vm, &frame->native);
+        if (nxt_slow_path(ret != NXT_OK)) {
+            return NXT_ERROR;
+        }
+    }
+
     vm->active_frame = frame;
 
     return NJS_APPLIED;
diff -r de0974c3e90f -r 72539416c466 njs/njs_function.h
--- a/njs/njs_function.h	Tue Oct 23 20:40:02 2018 +0300
+++ b/njs/njs_function.h	Tue Oct 23 20:40:08 2018 +0300
@@ -25,10 +25,12 @@ struct njs_function_lambda_s {
     uint32_t                       closure_size;
 
     /* Function nesting level. */
-    uint8_t                        nesting;         /* 4 bits */
+    uint8_t                        nesting;           /* 4 bits */
 
     /* Function internal block closures levels. */
-    uint8_t                        block_closures;  /* 4 bits */
+    uint8_t                        block_closures;    /* 4 bits */
+
+    uint8_t                        arguments_object;  /* 1 bit */
 
     /* Initial values of local scope. */
     njs_value_t                    *local_scope;
@@ -102,7 +104,9 @@ struct njs_native_frame_s {
 
     njs_function_t                 *function;
     njs_native_frame_t             *previous;
+
     njs_value_t                    *arguments;
+    njs_object_t                   *arguments_object;
 
     njs_exception_t                exception;
 
@@ -147,6 +151,10 @@ struct njs_frame_s {
 njs_function_t *njs_function_alloc(njs_vm_t *vm);
 njs_function_t *njs_function_value_copy(njs_vm_t *vm, njs_value_t *value);
 njs_native_frame_t *njs_function_frame_alloc(njs_vm_t *vm, size_t size);
+njs_ret_t njs_function_arguments_object_init(njs_vm_t *vm,
+    njs_native_frame_t *frame);
+njs_ret_t njs_function_arguments_thrower(njs_vm_t *vm, njs_value_t *value,
+    njs_value_t *setval, njs_value_t *retval);
 njs_ret_t njs_function_prototype_create(njs_vm_t *vm, njs_value_t *value,
     njs_value_t *setval, njs_value_t *retval);
 njs_value_t *njs_function_property_prototype_create(njs_vm_t *vm,
diff -r de0974c3e90f -r 72539416c466 njs/njs_generator.c
--- a/njs/njs_generator.c	Tue Oct 23 20:40:02 2018 +0300
+++ b/njs/njs_generator.c	Tue Oct 23 20:40:08 2018 +0300
@@ -14,6 +14,8 @@ static nxt_int_t njs_generate_name(njs_v
     njs_parser_node_t *node);
 static nxt_int_t njs_generate_builtin_object(njs_vm_t *vm, njs_parser_t *parser,
     njs_parser_node_t *node);
+static nxt_int_t njs_generate_arguments_object(njs_vm_t *vm,
+    njs_parser_t *parser, njs_parser_node_t *node);
 static nxt_int_t njs_generate_variable(njs_vm_t *vm, njs_parser_t *parser,
     njs_parser_node_t *node);
 static nxt_int_t njs_generate_var_statement(njs_vm_t *vm, njs_parser_t *parser,
@@ -308,6 +310,9 @@ njs_generator(njs_vm_t *vm, njs_parser_t
     case NJS_TOKEN_CLEAR_TIMEOUT:
         return njs_generate_builtin_object(vm, parser, node);
 
+    case NJS_TOKEN_ARGUMENTS:
+        return njs_generate_arguments_object(vm, parser, node);
+
     case NJS_TOKEN_FUNCTION:
         return njs_generate_function_declaration(vm, parser, node);
 
@@ -396,6 +401,29 @@ njs_generate_builtin_object(njs_vm_t *vm
 
 
 static nxt_int_t
+njs_generate_arguments_object(njs_vm_t *vm, njs_parser_t *parser,
+    njs_parser_node_t *node)
+{
+    njs_vmcode_arguments_t  *gen;
+
+    parser->arguments_object = 1;
+
+    node->index = njs_generator_object_dest_index(vm, parser, node);
+    if (nxt_slow_path(node->index == NJS_INDEX_ERROR)) {
+        return NXT_ERROR;
+    }
+
+    njs_generate_code(parser, njs_vmcode_arguments_t, gen);
+    gen->code.operation = njs_vmcode_arguments;
+    gen->code.operands = NJS_VMCODE_1OPERAND;
+    gen->code.retval = NJS_VMCODE_RETVAL;
+    gen->retval = node->index;
+
+    return NXT_OK;
+}
+
+
+static nxt_int_t
 njs_generate_variable(njs_vm_t *vm, njs_parser_t *parser,
     njs_parser_node_t *node)
 {
@@ -2010,6 +2038,7 @@ njs_generate_function_scope(njs_vm_t *vm
     parser->code_size += node->scope->argument_closures
                          * sizeof(njs_vmcode_move_t);
 
+    parser->arguments_object = 0;
     ret = njs_generate_scope(vm, parser, node);
 
     if (nxt_fast_path(ret == NXT_OK)) {
@@ -2023,6 +2052,7 @@ njs_generate_function_scope(njs_vm_t *vm
 
         lambda->nesting = node->scope->nesting;
         lambda->closure_size = size;
+        lambda->arguments_object = parser->arguments_object;
 
         lambda->local_size = parser->scope_size;
         lambda->local_scope = parser->local_scope;
diff -r de0974c3e90f -r 72539416c466 njs/njs_lexer_keyword.c
--- a/njs/njs_lexer_keyword.c	Tue Oct 23 20:40:02 2018 +0300
+++ b/njs/njs_lexer_keyword.c	Tue Oct 23 20:40:08 2018 +0300
@@ -53,6 +53,7 @@ static const njs_keyword_t  njs_keywords
     /* Builtin objects. */
 
     { nxt_string("this"),          NJS_TOKEN_THIS, 0 },
+    { nxt_string("arguments"),     NJS_TOKEN_ARGUMENTS, 0 },
     { nxt_string("njs"),           NJS_TOKEN_NJS, 0 },
     { nxt_string("Math"),          NJS_TOKEN_MATH, 0 },
     { nxt_string("JSON"),          NJS_TOKEN_JSON, 0 },
diff -r de0974c3e90f -r 72539416c466 njs/njs_parser.c
--- a/njs/njs_parser.c	Tue Oct 23 20:40:02 2018 +0300
+++ b/njs/njs_parser.c	Tue Oct 23 20:40:08 2018 +0300
@@ -455,6 +455,13 @@ njs_parser_function_declaration(njs_vm_t
     }
 
     if (token != NJS_TOKEN_NAME) {
+        if (token == NJS_TOKEN_ARGUMENTS || token ==  NJS_TOKEN_EVAL) {
+            njs_parser_syntax_error(vm, parser, "Identifier \"%.*s\" "
+                                    "is forbidden in function declaration",
+                                    (int) parser->lexer->text.length,
+                                    parser->lexer->text.start);
+        }
+
         return NJS_TOKEN_ILLEGAL;
     }
 
@@ -821,6 +828,13 @@ njs_parser_var_statement(njs_vm_t *vm, n
         }
 
         if (token != NJS_TOKEN_NAME) {
+            if (token == NJS_TOKEN_ARGUMENTS || token ==  NJS_TOKEN_EVAL) {
+                njs_parser_syntax_error(vm, parser, "Identifier \"%.*s\" "
+                                        "is forbidden in var declaration",
+                                        (int) parser->lexer->text.length,
+                                        parser->lexer->text.start);
+            }
+
             return NJS_TOKEN_ILLEGAL;
         }
 
@@ -1306,6 +1320,13 @@ njs_parser_for_var_statement(njs_vm_t *v
         }
 
         if (token != NJS_TOKEN_NAME) {
+            if (token == NJS_TOKEN_ARGUMENTS || token ==  NJS_TOKEN_EVAL) {
+                njs_parser_syntax_error(vm, parser, "Identifier \"%.*s\" "
+                                       "is forbidden in for-in var declaration",
+                                       (int) parser->lexer->text.length,
+                                       parser->lexer->text.start);
+            }
+
             return NJS_TOKEN_ILLEGAL;
         }
 
@@ -1973,6 +1994,24 @@ njs_parser_terminal(njs_vm_t *vm, njs_pa
     case NJS_TOKEN_JSON:
         return njs_parser_builtin_object(vm, parser, node);
 
+    case NJS_TOKEN_ARGUMENTS:
+        nxt_thread_log_debug("JS: arguments");
+
+        if (parser->scope->type <= NJS_SCOPE_GLOBAL) {
+            njs_parser_syntax_error(vm, parser, "\"%.*s\" object "
+                                    "in global scope",
+                                    (int) parser->lexer->text.length,
+                                    parser->lexer->text.start);
+
+            return NJS_TOKEN_ILLEGAL;
+        }
+
+        node->token = NJS_TOKEN_ARGUMENTS;
+
+        parser->code_size += sizeof(njs_vmcode_arguments_t);
+
+        break;
+
     case NJS_TOKEN_OBJECT_CONSTRUCTOR:
         node->index = NJS_INDEX_OBJECT;
         break;
diff -r de0974c3e90f -r 72539416c466 njs/njs_parser.h
--- a/njs/njs_parser.h	Tue Oct 23 20:40:02 2018 +0300
+++ b/njs/njs_parser.h	Tue Oct 23 20:40:08 2018 +0300
@@ -161,6 +161,7 @@ typedef enum {
     NJS_TOKEN_THROW,
 
     NJS_TOKEN_THIS,
+    NJS_TOKEN_ARGUMENTS,
 
 #define NJS_TOKEN_FIRST_OBJECT     NJS_TOKEN_GLOBAL_THIS
 
@@ -346,6 +347,8 @@ struct njs_parser_s {
     u_char                          *code_end;
 
     njs_parser_t                    *parent;
+
+    nxt_uint_t                      arguments_object;
 };
 
 
diff -r de0974c3e90f -r 72539416c466 njs/njs_parser_expression.c
--- a/njs/njs_parser_expression.c	Tue Oct 23 20:40:02 2018 +0300
+++ b/njs/njs_parser_expression.c	Tue Oct 23 20:40:08 2018 +0300
@@ -414,8 +414,19 @@ njs_parser_assignment_expression(njs_vm_
         }
 
         if (!njs_parser_is_lvalue(parser->node)) {
-            njs_parser_ref_error(vm, parser,
-                                 "Invalid left-hand side in assignment");
+            token = parser->node->token;
+
+            if (token == NJS_TOKEN_ARGUMENTS || token ==  NJS_TOKEN_EVAL) {
+                njs_parser_syntax_error(vm, parser, "Identifier \"%s\" "
+                                      "is forbidden as left-hand in assignment",
+                                       (token == NJS_TOKEN_EVAL) ? "eval"
+                                                                 : "arguments");
+
+            } else {
+                njs_parser_ref_error(vm, parser,
+                                     "Invalid left-hand side in assignment");
+            }
+
             return NJS_TOKEN_ILLEGAL;
         }
 
diff -r de0974c3e90f -r 72539416c466 njs/njs_vm.c
--- a/njs/njs_vm.c	Tue Oct 23 20:40:02 2018 +0300
+++ b/njs/njs_vm.c	Tue Oct 23 20:40:08 2018 +0300
@@ -413,6 +413,29 @@ njs_vmcode_function(njs_vm_t *vm, njs_va
 
 
 njs_ret_t
+njs_vmcode_arguments(njs_vm_t *vm, njs_value_t *invld1, njs_value_t *invld2)
+{
+    njs_ret_t    ret;
+    njs_frame_t  *frame;
+
+    frame = (njs_frame_t *) vm->active_frame;
+
+    if (frame->native.arguments_object == NULL) {
+        ret = njs_function_arguments_object_init(vm, &frame->native);
+        if (nxt_slow_path(ret != NXT_OK)) {
+            return NXT_ERROR;
+        }
+    }
+
+    vm->retval.data.u.object = frame->native.arguments_object;
+    vm->retval.type = NJS_OBJECT;
+    vm->retval.data.truth = 1;
+
+    return sizeof(njs_vmcode_arguments_t);
+}
+
+
+njs_ret_t
 njs_vmcode_regexp(njs_vm_t *vm, njs_value_t *invld1, njs_value_t *invld2)
 {
     njs_regexp_t         *regexp;
diff -r de0974c3e90f -r 72539416c466 njs/njs_vm.h
--- a/njs/njs_vm.h	Tue Oct 23 20:40:02 2018 +0300
+++ b/njs/njs_vm.h	Tue Oct 23 20:40:08 2018 +0300
@@ -635,6 +635,12 @@ typedef struct {
 typedef struct {
     njs_vmcode_t               code;
     njs_index_t                retval;
+} njs_vmcode_arguments_t;
+
+
+typedef struct {
+    njs_vmcode_t               code;
+    njs_index_t                retval;
     uintptr_t                  length;
 } njs_vmcode_array_t;
 
@@ -1084,6 +1090,7 @@ struct njs_vm_shared_s {
     nxt_lvlhsh_t             keywords_hash;
     nxt_lvlhsh_t             values_hash;
     nxt_lvlhsh_t             function_prototype_hash;
+    nxt_lvlhsh_t             arguments_object_hash;
 
     njs_object_t             objects[NJS_OBJECT_MAX];
     njs_function_t           functions[NJS_FUNCTION_MAX];
@@ -1110,6 +1117,8 @@ njs_ret_t njs_vmcode_array(njs_vm_t *vm,
     njs_value_t *inlvd2);
 njs_ret_t njs_vmcode_function(njs_vm_t *vm, njs_value_t *inlvd1,
     njs_value_t *invld2);
+njs_ret_t njs_vmcode_arguments(njs_vm_t *vm, njs_value_t *inlvd1,
+    njs_value_t *invld2);
 njs_ret_t njs_vmcode_regexp(njs_vm_t *vm, njs_value_t *inlvd1,
     njs_value_t *invld2);
 njs_ret_t njs_vmcode_object_copy(njs_vm_t *vm, njs_value_t *value,
diff -r de0974c3e90f -r 72539416c466 njs/test/njs_unit_test.c
--- a/njs/test/njs_unit_test.c	Tue Oct 23 20:40:02 2018 +0300
+++ b/njs/test/njs_unit_test.c	Tue Oct 23 20:40:08 2018 +0300
@@ -5713,6 +5713,74 @@ static njs_unit_test_t  njs_test[] =
                  "var b = a(); b(2)"),
       nxt_string("3") },
 
+    /* arguments object. */
+
+    { nxt_string("var arguments"),
+      nxt_string("SyntaxError: Identifier \"arguments\" is forbidden in var declaration in 1") },
+
+    { nxt_string("for (var arguments in []) {}"),
+      nxt_string("SyntaxError: Identifier \"arguments\" is forbidden in for-in var declaration in 1") },
+
+    { nxt_string("function arguments(){}"),
+      nxt_string("SyntaxError: Identifier \"arguments\" is forbidden in function declaration in 1") },
+
+    { nxt_string("(function () {arguments = [];})"),
+      nxt_string("SyntaxError: Identifier \"arguments\" is forbidden as left-hand in assignment in 1") },
+
+    { nxt_string("(function(){return arguments[0];})(1,2,3)"),
+      nxt_string("1") },
+
+    { nxt_string("(function(){return arguments[2];})(1,2,3)"),
+      nxt_string("3") },
+
+    { nxt_string("(function(){return arguments[3];})(1,2,3)"),
+      nxt_string("undefined") },
+
+    { nxt_string("(function(a,b,c){return a;})(1,2,3)"),
+      nxt_string("1") },
+
+    { nxt_string("(function(a,b,c){arguments[0] = 4; return a;})(1,2,3)"),
+      nxt_string("1") },
+
+    { nxt_string("(function(a,b,c){a = 4; return arguments[0];})(1,2,3)"),
+      nxt_string("1") },
+
+    { nxt_string("function check(v) {if (v == false) {throw TypeError('Too few arguments')}}; "
+                 "function f() {check(arguments.length > 1); return 1}; f()"),
+      nxt_string("TypeError: Too few arguments") },
+
+    { nxt_string("function check(v) {if (v == false) {throw TypeError('Too few arguments')}}; "
+                 "function f() {check(arguments.length > 1); return 1}; f(1,2)"),
+      nxt_string("1") },
+
+    { nxt_string("(function(a,b){delete arguments[0]; return arguments[0]})(1,1)"),
+      nxt_string("undefined") },
+
+    { nxt_string("(function(){return arguments.length;})()"),
+      nxt_string("0") },
+
+    { nxt_string("(function(){return arguments.length;})(1,2,3)"),
+      nxt_string("3") },
+
+    { nxt_string("(function(){arguments.length = 1; return arguments.length;})(1,2,3)"),
+      nxt_string("1") },
+
+    { nxt_string("(function(){return arguments.callee;})()"),
+      nxt_string("TypeError: 'caller', 'callee' properties may not be accessed") },
+
+    { nxt_string("(function(){return arguments.caller;})()"),
+      nxt_string("TypeError: 'caller', 'callee' properties may not be accessed") },
+
+    { nxt_string("function sum() { var args = Array.prototype.slice.call(arguments); "
+                 "return args.reduce(function(prev, curr) {return prev + curr})};"
+                 "[sum(1), sum(1,2), sum(1,2,3), sum(1,2,3,4)]"),
+      nxt_string("1,3,6,10") },
+
+    { nxt_string("function concat(sep) { var args = Array.prototype.slice.call(arguments, 1); "
+                 "return args.join(sep)};"
+                 "[concat('.',1,2,3), concat('+',1,2,3,4)]"),
+      nxt_string("1.2.3,1+2+3+4") },
+
     /* Scopes. */
 
     { nxt_string("function f(x) { a = x } var a; f(5); a"),


More information about the nginx-devel mailing list