Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

No support for arguments object? #19

Closed
rwaldron opened this issue Jun 22, 2018 · 14 comments
Closed

No support for arguments object? #19

rwaldron opened this issue Jun 22, 2018 · 14 comments
Assignees

Comments

@rwaldron
Copy link

function f() {
  console.log(arguments);
}
f(1, 2, 3);

Produces:

ReferenceError: "arguments" is not defined in 1

I expect the following output:

[1,2,3]
@xeioex
Copy link
Contributor

xeioex commented Jun 22, 2018

The argument object currently is not available. What is and is not supported described in http://nginx.org/en/docs/njs_about.html

@rwaldron
Copy link
Author

@xeioex oof, sorry I missed that.

I'm trying to create an njs runtime harness so that I can run Test262 tests against njs. The minimum requirement is a print() function, which exists already in a majority of runtimes or can be defined, eg. function print() { console.log.apply(null, arguments); } or even var print = console.log.bind(console);. Unfortunately, neither of these work, the first fails because there's no arguments object and the second because:

>> console.log.bind
TypeError: cannot get property 'bind' of undefined
    at main (native)

However...

>> console.log(1)
1

@xeioex
Copy link
Contributor

xeioex commented Jun 27, 2018

@rwaldron You can try a patch posted in #20. It fixes 'var print = console.log.bind(console)'

@xeioex xeioex self-assigned this Aug 23, 2018
@xeioex
Copy link
Contributor

xeioex commented Sep 4, 2018

Hi @drsm. Probably you would want to take a look at the patch for arguments object. I am planning to add #21 rest syntax later. Arguments object is used by test262 test suite.

 
>>function sum() { var args = Array.prototype.slice.call(arguments); return args.reduce(function(prev, curr) {return prev + curr})}

>> sum(1)
1
>> sum(1,2,3)
6
>> function concat(sep) { var args = Array.prototype.slice.call(arguments, 1); return args.join(sep)};
>> concat('+',1,2,3,4)
'1+2+3+4'
# HG changeset patch
# User Dmitry Volyntsev <xeioex@nginx.com>
# Date 1536061570 -10800
#      Tue Sep 04 14:46:10 2018 +0300
# Node ID 9a4e2f805db60601268868d60cd1fc3e35687f54
# Parent  1a02c6a3bdd99d02a171571d970d21c51b5227bf
Added njs_u32_to_string().

diff --git a/njs/njs_event.c b/njs/njs_event.c
--- a/njs/njs_event.c
+++ b/njs/njs_event.c
@@ -6,7 +6,6 @@
 
 #include <njs_core.h>
 #include <string.h>
-#include <stdio.h>
 
 
 static nxt_int_t njs_event_hash_test(nxt_lvlhsh_query_t *lhq, void *data);
@@ -44,13 +43,10 @@ njs_event_hash_test(nxt_lvlhsh_query_t *
 nxt_int_t
 njs_add_event(njs_vm_t *vm, njs_event_t *event)
 {
-    size_t              size;
     nxt_int_t           ret;
     nxt_lvlhsh_query_t  lhq;
 
-    size = snprintf((char *) njs_string_short_start(&event->id),
-                    NJS_STRING_SHORT, "%u", vm->event_id++);
-    njs_string_short_set(&event->id, size, size);
+    njs_u32_to_string(&event->id, vm->event_id++);
 
     njs_string_get(&event->id, &lhq.key);
     lhq.key_hash = nxt_djb_hash(lhq.key.start, lhq.key.length);
diff --git a/njs/njs_json.c b/njs/njs_json.c
--- a/njs/njs_json.c
+++ b/njs/njs_json.c
@@ -8,7 +8,6 @@
 #include <njs_json.h>
 #include <njs_date.h>
 #include <njs_regexp.h>
-#include <stdio.h>
 #include <string.h>
 
 
@@ -1036,7 +1035,6 @@ memory_error:
 static njs_ret_t
 njs_json_parse_continuation_apply(njs_vm_t *vm, njs_json_parse_t *parse)
 {
-    size_t            size;
     njs_value_t       arguments[3];
     njs_json_state_t  *state;
 
@@ -1053,9 +1051,7 @@ njs_json_parse_continuation_apply(njs_vm
         break;
 
     case NJS_JSON_ARRAY_START:
-        size = snprintf((char *) njs_string_short_start(&arguments[1]),
-                        NJS_STRING_SHORT, "%u", state->index);
-        njs_string_short_set(&arguments[1], size, size);
+        njs_u32_to_string(&arguments[1], state->index);
         arguments[2] = state->value.data.u.array->start[state->index];
 
         state->type = NJS_JSON_ARRAY_REPLACED;
@@ -1455,7 +1451,6 @@ static njs_ret_t
 njs_json_stringify_to_json(njs_vm_t *vm, njs_json_stringify_t* stringify,
     njs_function_t *function, njs_value_t *key, njs_value_t *value)
 {
-    size_t            size;
     njs_value_t       arguments[2];
     njs_json_state_t  *state;
 
@@ -1482,9 +1477,7 @@ njs_json_stringify_to_json(njs_vm_t *vm,
 
     case NJS_JSON_ARRAY_START:
     case NJS_JSON_ARRAY_CONTINUE:
-        size = snprintf((char *) njs_string_short_start(&arguments[1]),
-                        NJS_STRING_SHORT, "%u", state->index - 1);
-        njs_string_short_set(&arguments[1], size, size);
+        njs_u32_to_string(&arguments[1], state->index - 1);
 
         state->type = NJS_JSON_ARRAY_TO_JSON_REPLACED;
         break;
@@ -1504,7 +1497,6 @@ static njs_ret_t
 njs_json_stringify_replacer(njs_vm_t *vm, njs_json_stringify_t* stringify,
     njs_value_t *key, njs_value_t *value)
 {
-    size_t            size;
     njs_value_t       arguments[3];
     njs_json_state_t  *state;
 
@@ -1526,9 +1518,7 @@ njs_json_stringify_replacer(njs_vm_t *vm
     case NJS_JSON_ARRAY_START:
     case NJS_JSON_ARRAY_CONTINUE:
     case NJS_JSON_ARRAY_TO_JSON_REPLACED:
-        size = snprintf((char *) njs_string_short_start(&arguments[1]),
-                        NJS_STRING_SHORT, "%u", state->index - 1);
-        njs_string_short_set(&arguments[1], size, size);
+        njs_u32_to_string(&arguments[1], state->index - 1);
         arguments[2] = *value;
 
         state->type = NJS_JSON_ARRAY_REPLACED;
diff --git a/njs/njs_number.h b/njs/njs_number.h
--- a/njs/njs_number.h
+++ b/njs/njs_number.h
@@ -9,6 +9,7 @@
 
 
 #include <math.h>
+#include <stdio.h>
 
 
 uint32_t njs_value_to_index(const njs_value_t *value);
@@ -56,6 +57,17 @@ njs_char_to_hex(u_char c)
 }
 
 
+nxt_inline void
+njs_u32_to_string(njs_value_t *value, uint32_t u32)
+{
+    size_t  size;
+
+    size = snprintf((char *) njs_string_short_start(value),
+                    NJS_STRING_SHORT, "%u", u32);
+    njs_string_short_set(value, size, size);
+}
+
+
 extern const njs_object_init_t  njs_number_constructor_init;
 extern const njs_object_init_t  njs_number_prototype_init;
 
diff --git a/njs/njs_object.c b/njs/njs_object.c
--- a/njs/njs_object.c
+++ b/njs/njs_object.c
@@ -5,7 +5,6 @@
  */
 
 #include <njs_core.h>
-#include <stdio.h>
 #include <string.h>
 
 
@@ -651,7 +650,6 @@ njs_object_keys(njs_vm_t *vm, njs_value_
 njs_array_t *
 njs_object_keys_array(njs_vm_t *vm, const njs_value_t *object)
 {
-    size_t             size;
     uint32_t           i, n, keys_length, array_length;
     njs_value_t        *value;
     njs_array_t        *keys, *array;
@@ -704,9 +702,7 @@ njs_object_keys_array(njs_vm_t *vm, cons
              * The maximum array index is 4294967294, so
              * it can be stored as a short string inside value.
              */
-            size = snprintf((char *) njs_string_short_start(value),
-                            NJS_STRING_SHORT, "%u", i);
-            njs_string_short_set(value, size, size);
+            njs_u32_to_string(value, i);
         }
     }
 
# HG changeset patch
# User Dmitry Volyntsev <xeioex@nginx.com>
# Date 1536083411 -10800
#      Tue Sep 04 20:50:11 2018 +0300
# Node ID 8f5eaeb2817ee7b9ca890f343ed82b2cf3acb3d0
# Parent  9a4e2f805db60601268868d60cd1fc3e35687f54
Hanling object values properly in Array.prototype.slice.

diff --git a/njs/njs_array.c b/njs/njs_array.c
--- a/njs/njs_array.c
+++ b/njs/njs_array.c
@@ -434,54 +434,83 @@ static njs_ret_t
 njs_array_prototype_slice(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs,
     njs_index_t unused)
 {
-    int32_t      start, end, length;
-    uint32_t     n;
-    njs_array_t  *array;
-    njs_value_t  *value;
+    int32_t               start, end, length;
+    uint32_t              n;
+    njs_ret_t             ret;
+    njs_array_t           *array;
+    njs_value_t           *value, val;
+    //njs_slice_prop_t      slice;
+    njs_string_prop_t     string;
+    njs_object_prop_t     *prop;
+    njs_property_query_t  pq;
 
     start = 0;
     length = 0;
 
-    if (njs_is_array(&args[0])) {
+    if (nxt_fast_path(njs_is_array(&args[0]))) {
         length = args[0].data.u.array->length;
 
-        if (nargs > 1) {
-            start = args[1].data.u.number;
+    } else if (njs_is_string(&args[0])) {
+        length = njs_string_prop(&string, &args[0]);
+
+    } else if (njs_is_object(&args[0])) {
+
+        pq.query = NJS_PROPERTY_QUERY_GET;
+        pq.lhq.key = nxt_string_value("length");
+        pq.lhq.key_hash = NJS_LENGTH_HASH;
+
+        ret = njs_object_property_query(vm, &pq, &args[0],
+                                        args[0].data.u.object);
+
+        if (ret == NXT_OK) {
+            prop = pq.lhq.value;
+
+            if (!njs_is_primitive(&prop->value)) {
+                njs_internal_error(vm, "length prop value is not primitive",
+                                   (int) pq.lhq.key.length, pq.lhq.key.start);
+                return NXT_ERROR;
+            }
+
+            length = njs_primitive_value_to_uint32(&prop->value);
+        }
+    }
+
+    if (nargs > 1) {
+        start = args[1].data.u.number;
+
+        if (start < 0) {
+            start += length;
 
             if (start < 0) {
-                start += length;
-
-                if (start < 0) {
-                    start = 0;
+                start = 0;
+            }
+        }
+
+        if (start >= length) {
+            start = 0;
+            length = 0;
+
+        } else {
+            end = length;
+
+            if (nargs > 2 && !njs_is_void(&args[2])) {
+                end = args[2].data.u.number;
+
+                if (end < 0) {
+                    end += length;
                 }
             }
 
-            if (start >= length) {
-                start = 0;
-                length = 0;
+            if (length >= end) {
+                length = end - start;
+
+                if (length < 0) {
+                    start = 0;
+                    length = 0;
+                }
 
             } else {
-                end = length;
-
-                if (nargs > 2) {
-                    end = args[2].data.u.number;
-
-                    if (end < 0) {
-                        end += length;
-                    }
-                }
-
-                if (length >= end) {
-                    length = end - start;
-
-                    if (length < 0) {
-                        start = 0;
-                        length = 0;
-                    }
-
-                } else {
-                    length -= start;
-                }
+                length -= start;
             }
         }
     }
@@ -499,11 +528,44 @@ njs_array_prototype_slice(njs_vm_t *vm, 
         value = args[0].data.u.array->start;
         n = 0;
 
-        do {
-            /* GC: retain long string and object in values[start]. */
-            array->start[n++] = value[start++];
-            length--;
-        } while (length != 0);
+        if (nxt_fast_path(njs_is_array(&args[0]))) {
+
+            do {
+                /* GC: retain long string and object in values[start]. */
+                array->start[n++] = value[start++];
+                length--;
+            } while (length != 0);
+
+        } else if (njs_is_string(&args[0])) {
+
+            // TODO
+            //slice.start = start;
+            //slice.length = length;
+            //
+            //ret = njs_string_slice(vm, &val, &string, &slice);
+            //if (nxt_slow_path(ret != NXT_OK)) {
+            //return ret;
+            //}
+
+
+        } else if (njs_is_object(&args[0])) {
+            pq.query = NJS_PROPERTY_QUERY_GET;
+
+            do {
+                njs_u32_to_string(&val, start++);
+                njs_string_get(&val, &pq.lhq.key);
+                pq.lhq.key_hash = nxt_djb_hash(pq.lhq.key.start,
+                                               pq.lhq.key.length);
+
+                ret = njs_object_property_query(vm, &pq, &args[0],
+                                                args[0].data.u.object);
+                prop = (ret == NXT_OK) ? pq.lhq.value : NULL;
+
+                array->start[n++] = (prop != NULL) ? prop->value
+                                                   : njs_value_void;
+                length--;
+            } while (length != 0);
+        }
     }
 
     return NXT_OK;
diff --git a/njs/njs_number.c b/njs/njs_number.c
--- a/njs/njs_number.c
+++ b/njs/njs_number.c
@@ -64,6 +64,24 @@ njs_value_to_index(const njs_value_t *va
 
 
 double
+njs_primitive_value_to_number(const njs_value_t *value)
+{
+    if (nxt_fast_path(njs_is_numeric(value))) {
+        return value->data.u.number;
+    }
+
+    return njs_string_to_number(value, 1);
+}
+
+
+uint32_t
+njs_primitive_value_to_uint32(const njs_value_t *value)
+{
+    return njs_number_to_integer(njs_primitive_value_to_number(value));
+}
+
+
+double
 njs_number_dec_parse(const u_char **start, const u_char *end)
 {
     return nxt_strtod(start, end);
diff --git a/njs/njs_number.h b/njs/njs_number.h
--- a/njs/njs_number.h
+++ b/njs/njs_number.h
@@ -13,6 +13,8 @@
 
 
 uint32_t njs_value_to_index(const njs_value_t *value);
+double njs_primitive_value_to_number(const njs_value_t *value);
+uint32_t njs_primitive_value_to_uint32(const njs_value_t *value);
 double njs_number_dec_parse(const u_char **start, const u_char *end);
 uint64_t njs_number_oct_parse(const u_char **start, const u_char *end);
 uint64_t njs_number_bin_parse(const u_char **start, const u_char *end);
diff --git a/njs/njs_object.c b/njs/njs_object.c
--- a/njs/njs_object.c
+++ b/njs/njs_object.c
@@ -9,8 +9,6 @@
 
 
 static nxt_int_t njs_object_hash_test(nxt_lvlhsh_query_t *lhq, void *data);
-static njs_ret_t njs_object_property_query(njs_vm_t *vm,
-    njs_property_query_t *pq, njs_value_t *value, njs_object_t *object);
 static njs_ret_t njs_array_property_query(njs_vm_t *vm,
     njs_property_query_t *pq, njs_value_t *object, uint32_t index);
 static njs_ret_t njs_object_query_prop_handler(njs_property_query_t *pq,
diff --git a/njs/njs_object.h b/njs/njs_object.h
--- a/njs/njs_object.h
+++ b/njs/njs_object.h
@@ -71,6 +71,8 @@ njs_object_prop_t *njs_object_property(n
     nxt_lvlhsh_query_t *lhq);
 njs_ret_t njs_property_query(njs_vm_t *vm, njs_property_query_t *pq,
     njs_value_t *object, njs_value_t *property);
+njs_ret_t njs_object_property_query(njs_vm_t *vm, njs_property_query_t *pq,
+    njs_value_t *value, njs_object_t *object);
 nxt_int_t njs_object_hash_create(njs_vm_t *vm, nxt_lvlhsh_t *hash,
     const njs_object_prop_t *prop, nxt_uint_t n);
 njs_ret_t njs_object_constructor(njs_vm_t *vm, njs_value_t *args,
diff --git a/njs/njs_object_hash.h b/njs/njs_object_hash.h
--- a/njs/njs_object_hash.h
+++ b/njs/njs_object_hash.h
@@ -108,6 +108,16 @@
         'j'), 'o'), 'i'), 'n')
 
 
+#define NJS_LENGTH_HASH                                                       \
+    nxt_djb_hash_add(                                                         \
+    nxt_djb_hash_add(                                                         \
+    nxt_djb_hash_add(                                                         \
+    nxt_djb_hash_add(                                                         \
+    nxt_djb_hash_add(                                                         \
+    nxt_djb_hash_add(NXT_DJB_HASH_INIT,                                       \
+        'l'), 'e'), 'n'), 'g'), 't'), 'h')
+
+
 #define NJS_NAME_HASH                                                         \
     nxt_djb_hash_add(                                                         \
     nxt_djb_hash_add(                                                         \
diff --git a/njs/test/njs_unit_test.c b/njs/test/njs_unit_test.c
--- a/njs/test/njs_unit_test.c
+++ b/njs/test/njs_unit_test.c
@@ -2975,12 +2975,36 @@ static njs_unit_test_t  njs_test[] =
     { nxt_string("Array.prototype.slice(1,2)"),
       nxt_string("") },
 
+    { nxt_string("Array.prototype.slice.call({'0':'a', '1':'b', length:1})"),
+      nxt_string("a") },
+
+    { nxt_string("Array.prototype.slice.call({'0':'a', '1':'b', length:2})"),
+      nxt_string("a,b") },
+
+    { nxt_string("Array.prototype.slice.call({'0':'a', '1':'b', length:4})"),
+      nxt_string("a,b,,") },
+
+    { nxt_string("Array.prototype.slice.call({'0':'a', '1':'b', length:2}, 1)"),
+      nxt_string("b") },
+
+    { nxt_string("Array.prototype.slice.call({'0':'a', '1':'b', length:2}, 1, 2)"),
+      nxt_string("b") },
+
     { nxt_string("Array.prototype.pop()"),
       nxt_string("undefined") },
 
     { nxt_string("Array.prototype.shift()"),
       nxt_string("undefined") },
 
+    { nxt_string("[0,1].slice()"),
+      nxt_string("0,1") },
+
+    { nxt_string("[0,1].slice(undefined)"),
+      nxt_string("0,1") },
+
+    { nxt_string("[0,1].slice(undefined, undefined)"),
+      nxt_string("0,1") },
+
     { nxt_string("[0,1,2,3,4].slice(1,4)"),
       nxt_string("1,2,3") },
 
# HG changeset patch
# User Dmitry Volyntsev <xeioex@nginx.com>
# Date 1536083413 -10800
#      Tue Sep 04 20:50:13 2018 +0300
# Node ID fe71f97513ade753199d27c347925a51ba966f3c
# Parent  8f5eaeb2817ee7b9ca890f343ed82b2cf3acb3d0
Added arguments object.

This closes #19 issue on Github.

diff --git a/njs/njs_disassembler.c b/njs/njs_disassembler.c
--- a/njs/njs_disassembler.c
+++ b/njs/njs_disassembler.c
@@ -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 --git a/njs/njs_function.c b/njs/njs_function.c
--- a/njs/njs_function.c
+++ b/njs/njs_function.c
@@ -8,6 +8,8 @@
 #include <string.h>
 
 
+static njs_ret_t njs_function_arguments_thrower(njs_vm_t *vm,
+    njs_value_t *value, njs_value_t *setval, njs_value_t *retval);
 static njs_ret_t njs_function_activate(njs_vm_t *vm, njs_function_t *function,
     njs_value_t *this, njs_value_t *args, nxt_uint_t nargs, njs_index_t retval);
 
@@ -95,6 +97,137 @@ 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         val, *value;
+    njs_object_t        *obj;
+    njs_object_prop_t   *prop;
+    nxt_lvlhsh_query_t  lhq;
+
+    static const njs_value_t  njs_string_length = njs_string("length");
+    static const njs_value_t  njs_string_callee = njs_string("callee");
+    static const njs_value_t  njs_string_caller = njs_string("caller");
+
+    obj = njs_object_alloc(vm);
+    if (nxt_slow_path(obj == NULL)) {
+        return NXT_ERROR;
+    }
+
+    nargs = frame->nargs;
+
+    value = &val;
+    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(&obj->hash, &lhq);
+
+    if (nxt_slow_path(ret != NXT_OK)) {
+        njs_internal_error(vm, "lvlhsh insert failed");
+        return NXT_ERROR;
+    }
+
+    prop = njs_object_prop_handler_alloc(vm, &njs_string_callee,
+                                         njs_function_arguments_thrower, 0);
+    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);
+
+    lhq.replace = 0;
+    lhq.pool = vm->mem_cache_pool;
+    lhq.proto = &njs_object_hash_proto;
+
+    ret = nxt_lvlhsh_insert(&obj->hash, &lhq);
+
+    if (nxt_slow_path(ret != NXT_OK)) {
+        njs_internal_error(vm, "lvlhsh insert failed");
+        return NXT_ERROR;
+    }
+
+    prop = njs_object_prop_handler_alloc(vm, &njs_string_caller,
+                                         njs_function_arguments_thrower, 0);
+    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);
+
+    lhq.replace = 0;
+    lhq.pool = vm->mem_cache_pool;
+    lhq.proto = &njs_object_hash_proto;
+
+    ret = nxt_lvlhsh_insert(&obj->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_u32_to_string(&val, n);
+
+        prop = njs_object_prop_alloc(vm, &val, &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);
+
+        lhq.replace = 0;
+        lhq.pool = vm->mem_cache_pool;
+        lhq.proto = &njs_object_hash_proto;
+
+        ret = nxt_lvlhsh_insert(&obj->hash, &lhq);
+
+        if (nxt_slow_path(ret != NXT_OK)) {
+            njs_internal_error(vm, "lvlhsh insert failed");
+            return NXT_ERROR;
+        }
+
+    }
+
+    frame->arguments_object = obj;
+
+    return NXT_OK;
+}
+
+
+static 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 +448,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 +527,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 --git a/njs/njs_function.h b/njs/njs_function.h
--- a/njs/njs_function.h
+++ b/njs/njs_function.h
@@ -30,6 +30,8 @@ struct njs_function_lambda_s {
     /* Function internal block closures levels. */
     uint8_t                        block_closures;  /* 4 bits */
 
+    uint8_t                        arguments_object;/* 1 bit */
+
     /* Initial values of local scope. */
     njs_value_t                    *local_scope;
 
@@ -101,7 +103,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;
 
@@ -146,6 +150,8 @@ 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_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 --git a/njs/njs_generator.c b/njs/njs_generator.c
--- a/njs/njs_generator.c
+++ b/njs/njs_generator.c
@@ -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 --git a/njs/njs_lexer_keyword.c b/njs/njs_lexer_keyword.c
--- a/njs/njs_lexer_keyword.c
+++ b/njs/njs_lexer_keyword.c
@@ -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 --git a/njs/njs_object.c b/njs/njs_object.c
--- a/njs/njs_object.c
+++ b/njs/njs_object.c
@@ -201,6 +201,36 @@ njs_object_prop_alloc(njs_vm_t *vm, cons
 
 
 nxt_noinline njs_object_prop_t *
+njs_object_prop_handler_alloc(njs_vm_t *vm, const njs_value_t *name,
+    njs_prop_handler_t handler, uint8_t attributes)
+{
+    njs_object_prop_t  *prop;
+
+    prop = nxt_mem_cache_align(vm->mem_cache_pool, sizeof(njs_value_t),
+                               sizeof(njs_object_prop_t));
+
+    if (nxt_fast_path(prop != NULL)) {
+        /* GC: retain. */
+        njs_set_invalid(&prop->value);
+        prop->value.data.u.prop_handler = handler;
+
+        /* GC: retain. */
+        prop->name = *name;
+
+        prop->type = NJS_PROPERTY_HANDLER;
+        prop->enumerable = attributes;
+        prop->writable = attributes;
+        prop->configurable = attributes;
+        return prop;
+    }
+
+    njs_memory_error(vm);
+
+    return NULL;
+}
+
+
+nxt_noinline njs_object_prop_t *
 njs_object_property(njs_vm_t *vm, const njs_object_t *object,
     nxt_lvlhsh_query_t *lhq)
 {
@@ -398,6 +428,22 @@ njs_object_property_query(njs_vm_t *vm, 
         if (ret == NXT_OK) {
             prop = pq->lhq.value;
 
+            if (pq->query == NJS_PROPERTY_QUERY_GET) {
+                prop = pq->lhq.value;
+
+                if (prop->type == NJS_PROPERTY_HANDLER) {
+                    pq->scratch = *prop;
+                    prop = &pq->scratch;
+                    ret = prop->value.data.u.prop_handler(vm, value, NULL,
+                                                          &prop->value);
+
+                    if (nxt_fast_path(ret == NXT_OK)) {
+                        prop->type = NJS_PROPERTY;
+                        pq->lhq.value = prop;
+                    }
+                }
+            }
+
             if (prop->type != NJS_WHITEOUT) {
                 pq->shared = 0;
 
diff --git a/njs/njs_object.h b/njs/njs_object.h
--- a/njs/njs_object.h
+++ b/njs/njs_object.h
@@ -79,6 +79,8 @@ njs_ret_t njs_object_constructor(njs_vm_
     nxt_uint_t nargs, njs_index_t unused);
 njs_object_prop_t *njs_object_prop_alloc(njs_vm_t *vm, const njs_value_t *name,
         const njs_value_t *value, uint8_t attributes);
+nxt_noinline njs_object_prop_t *njs_object_prop_handler_alloc(njs_vm_t *vm,
+    const njs_value_t *name, njs_prop_handler_t handler, uint8_t attributes);
 njs_ret_t njs_primitive_prototype_get_proto(njs_vm_t *vm, njs_value_t *value,
     njs_value_t *setval, njs_value_t *retval);
 njs_ret_t njs_object_prototype_create(njs_vm_t *vm, njs_value_t *value,
diff --git a/njs/njs_parser.c b/njs/njs_parser.c
--- a/njs/njs_parser.c
+++ b/njs/njs_parser.c
@@ -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 --git a/njs/njs_parser.h b/njs/njs_parser.h
--- a/njs/njs_parser.h
+++ b/njs/njs_parser.h
@@ -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 --git a/njs/njs_vm.c b/njs/njs_vm.c
--- a/njs/njs_vm.c
+++ b/njs/njs_vm.c
@@ -415,6 +415,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 --git a/njs/njs_vm.h b/njs/njs_vm.h
--- a/njs/njs_vm.h
+++ b/njs/njs_vm.h
@@ -644,6 +644,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;
 
@@ -1114,6 +1120,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 --git a/njs/test/njs_unit_test.c b/njs/test/njs_unit_test.c
--- a/njs/test/njs_unit_test.c
+++ b/njs/test/njs_unit_test.c
@@ -5499,6 +5499,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(){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(){return arguments.callee;})()"),
+      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"),

@drsm
Copy link
Contributor

drsm commented Sep 4, 2018

Hi @xeioex !
The patch works for me.
But some issues has been found.

>> Array.prototype.slice.call({length: 3, 1: undefined })
[
 undefined,
 undefined,
 undefined
]
>> Array.prototype.slice.call({length: -1})
Segmentation fault

SyntaxError is expected here:

>> (function () { arguments = []; })(1,2,3)
ReferenceError: Invalid left-hand side in assignment in 1

@drsm
Copy link
Contributor

drsm commented Sep 4, 2018

Also, this should work according to https://www.ecma-international.org/ecma-262/5.1/#sec-9.6

>> Array.prototype.slice.call({length: new Number(5) })
InternalError: length prop value is not primitive
    at native (native)
    at Function.prototype.call (native)
    at main (native)

>> Array.prototype.slice.call({length: { valueOf: function() { return 2; } }})
InternalError: length prop value is not primitive
    at native (native)
    at Function.prototype.call (native)
    at main (native)

and fail with a TypeError only when no conversion is possible (https://www.ecma-international.org/ecma-262/5.1/#sec-8.12.8)

Array.prototype.slice.call({ length: Object.create(null) })

@xeioex
Copy link
Contributor

xeioex commented Sep 13, 2018

@drsm Here is the second try: https://gist.github.com/xeioex/d8a5c34c20853902ea0381b1097e8b82

It includes, among other things, fixes for all the issues you reported here.

@drsm
Copy link
Contributor

drsm commented Sep 13, 2018

Hi @xeioex !
this is not yet fixed ([empty,undefined,empty] is expected):

>> Array.prototype.slice.call({length: 3, 1: undefined })
[
 <empty>,
 <empty>,
 <empty>
]

also, nodejs and firefox returns an empty array here, but jjs tries to alloc:

Array.prototype.slice.call({length: -1})

not sure who is right...

@xeioex
Copy link
Contributor

xeioex commented Sep 13, 2018

also, nodejs and firefox returns an empty array here, but jjs tries to alloc:

Array.prototype.slice.call({length: -1})

The spec (https://www.ecma-international.org/ecma-262/5.1/#sec-9.6) says
Let lenVal be the result of calling the [[Get]] internal method of O with argument "length".
Let len be ToUint32(lenVal).

ToUint32(-1) is 2**32- 1

For instance Uint32Array.from([-1]) -> Uint32Array [4294967295]

@xeioex
Copy link
Contributor

xeioex commented Sep 13, 2018

this is not yet fixed ([empty,undefined,empty] is expected):

Good catch! Here is one more try:
https://gist.github.com/xeioex/b44c9d876e18a1e59ecbf7f432eca974

The patch set contain only uncommitted changes. Do not forget to pull the latest changes from the repo.

@drsm
Copy link
Contributor

drsm commented Sep 13, 2018

the last version of the patch works fine for me, thanks!

For instance Uint32Array.from([-1]) -> Uint32Array [4294967295]

Oh, I just found that the later version of the spec introduce an another abstract operation ToLength https://tc39.github.io/ecma262/#sec-tolength , ff & v8 follow it

@rwaldron
Copy link
Author

@drsm

SyntaxError is expected here:

>> (function () { arguments = []; })(1,2,3)
ReferenceError: Invalid left-hand side in assignment in 1

This will only produce a SyntaxError in strict mode code.

@xeioex
Copy link
Contributor

xeioex commented Sep 13, 2018

Hi @rwaldron, we plan to focus on strict mode only. (http://nginx.org/en/docs/njs_about.html)

@rwaldron
Copy link
Author

Hi @rwaldron, we plan to focus on strict mode only

That's awesome—good to know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants