[njs] Fixed Object.freeze() and friends according to the specification.

Dmitry Volyntsev xeioex at nginx.com
Fri Feb 19 17:53:41 UTC 2021


details:   https://hg.nginx.org/njs/rev/4197cc28ea9c
branches:  
changeset: 1609:4197cc28ea9c
user:      Artem S. Povalyukhin <artem.povaluhin at gmail.com>
date:      Fri Feb 19 17:27:44 2021 +0000
description:
Fixed Object.freeze() and friends according to the specification.

This fixes #340 and also closes #374 issues on Github.

diffstat:

 src/njs_object.c         |  163 ++++++++++++++----------------------------
 src/test/njs_unit_test.c |  178 ++++++++++++++++++++++++++++++++--------------
 2 files changed, 180 insertions(+), 161 deletions(-)

diffs (477 lines):

diff -r 21057966d82d -r 4197cc28ea9c src/njs_object.c
--- a/src/njs_object.c	Wed Feb 17 16:05:28 2021 +0300
+++ b/src/njs_object.c	Fri Feb 19 17:27:44 2021 +0000
@@ -8,6 +8,12 @@
 #include <njs_main.h>
 
 
+typedef enum {
+    NJS_OBJECT_INTEGRITY_SEALED,
+    NJS_OBJECT_INTEGRITY_FROZEN,
+} njs_object_integrity_level_t;
+
+
 static njs_int_t njs_object_hash_test(njs_lvlhsh_query_t *lhq, void *data);
 static njs_object_prop_t *njs_object_exist_in_proto(const njs_object_t *begin,
     const njs_object_t *end, njs_lvlhsh_query_t *lhq);
@@ -1507,10 +1513,13 @@ njs_object_set_prototype_of(njs_vm_t *vm
 }
 
 
+/* 7.3.15 SetIntegrityLevel */
+
 static njs_int_t
-njs_object_freeze(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
-    njs_index_t unused)
+njs_object_set_integrity_level(njs_vm_t *vm, njs_value_t *args,
+    njs_uint_t nargs, njs_index_t level)
 {
+    njs_int_t          ret;
     njs_value_t        *value;
     njs_lvlhsh_t       *hash;
     njs_object_t       *object;
@@ -1519,11 +1528,26 @@ njs_object_freeze(njs_vm_t *vm, njs_valu
 
     value = njs_arg(args, nargs, 1);
 
-    if (!njs_is_object(value)) {
-        njs_set_undefined(&vm->retval);
+    if (njs_slow_path(!njs_is_object(value))) {
+        vm->retval = *value;
         return NJS_OK;
     }
 
+    if (njs_slow_path(level == NJS_OBJECT_INTEGRITY_FROZEN
+                      && njs_is_typed_array(value)
+                      && njs_typed_array_length(njs_typed_array(value)) != 0))
+    {
+        njs_type_error(vm, "Cannot freeze array buffer views with elements");
+        return NJS_ERROR;
+    }
+
+    if (njs_is_fast_array(value)) {
+        ret = njs_array_convert_to_slow_array(vm, njs_array(value));
+        if (ret != NJS_OK) {
+            return ret;
+        }
+    }
+
     object = njs_object(value);
     object->extensible = 0;
 
@@ -1538,7 +1562,9 @@ njs_object_freeze(njs_vm_t *vm, njs_valu
             break;
         }
 
-        if (!njs_is_accessor_descriptor(prop)) {
+        if (level == NJS_OBJECT_INTEGRITY_FROZEN
+            && !njs_is_accessor_descriptor(prop))
+        {
             prop->writable = 0;
         }
 
@@ -1552,8 +1578,8 @@ njs_object_freeze(njs_vm_t *vm, njs_valu
 
 
 static njs_int_t
-njs_object_is_frozen(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
-    njs_index_t unused)
+njs_object_test_integrity_level(njs_vm_t *vm, njs_value_t *args,
+    njs_uint_t nargs, njs_index_t level)
 {
     njs_value_t        *value;
     njs_lvlhsh_t       *hash;
@@ -1564,7 +1590,7 @@ njs_object_is_frozen(njs_vm_t *vm, njs_v
 
     value = njs_arg(args, nargs, 1);
 
-    if (!njs_is_object(value)) {
+    if (njs_slow_path(!njs_is_object(value))) {
         vm->retval = njs_value_true;
         return NJS_OK;
     }
@@ -1572,14 +1598,22 @@ njs_object_is_frozen(njs_vm_t *vm, njs_v
     retval = &njs_value_false;
 
     object = njs_object(value);
-    njs_lvlhsh_each_init(&lhe, &njs_object_hash_proto);
-
-    hash = &object->hash;
 
     if (object->extensible) {
         goto done;
     }
 
+    if (njs_slow_path(level == NJS_OBJECT_INTEGRITY_FROZEN)
+                      && njs_is_typed_array(value)
+                      && njs_typed_array_length(njs_typed_array(value)) != 0)
+    {
+        goto done;
+    }
+
+    njs_lvlhsh_each_init(&lhe, &njs_object_hash_proto);
+
+    hash = &object->hash;
+
     for ( ;; ) {
         prop = njs_lvlhsh_each(hash, &lhe);
 
@@ -1591,98 +1625,9 @@ njs_object_is_frozen(njs_vm_t *vm, njs_v
             goto done;
         }
 
-        if (njs_is_data_descriptor(prop) && prop->writable) {
-            goto done;
-        }
-    }
-
-    retval = &njs_value_true;
-
-done:
-
-    vm->retval = *retval;
-
-    return NJS_OK;
-}
-
-
-static njs_int_t
-njs_object_seal(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
-    njs_index_t unused)
-{
-    njs_value_t        *value;
-    njs_lvlhsh_t       *hash;
-    njs_object_t       *object;
-    njs_object_prop_t  *prop;
-    njs_lvlhsh_each_t  lhe;
-
-    value = njs_arg(args, nargs, 1);
-
-    if (!njs_is_object(value)) {
-        vm->retval = *value;
-        return NJS_OK;
-    }
-
-    object = njs_object(value);
-    object->extensible = 0;
-
-    njs_lvlhsh_each_init(&lhe, &njs_object_hash_proto);
-
-    hash = &object->hash;
-
-    for ( ;; ) {
-        prop = njs_lvlhsh_each(hash, &lhe);
-
-        if (prop == NULL) {
-            break;
-        }
-
-        prop->configurable = 0;
-    }
-
-    vm->retval = *value;
-
-    return NJS_OK;
-}
-
-
-static njs_int_t
-njs_object_is_sealed(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
-    njs_index_t unused)
-{
-    njs_value_t        *value;
-    njs_lvlhsh_t       *hash;
-    njs_object_t       *object;
-    njs_object_prop_t  *prop;
-    njs_lvlhsh_each_t  lhe;
-    const njs_value_t  *retval;
-
-    value = njs_arg(args, nargs, 1);
-
-    if (!njs_is_object(value)) {
-        vm->retval = njs_value_true;
-        return NJS_OK;
-    }
-
-    retval = &njs_value_false;
-
-    object = njs_object(value);
-    njs_lvlhsh_each_init(&lhe, &njs_object_hash_proto);
-
-    hash = &object->hash;
-
-    if (object->extensible) {
-        goto done;
-    }
-
-    for ( ;; ) {
-        prop = njs_lvlhsh_each(hash, &lhe);
-
-        if (prop == NULL) {
-            break;
-        }
-
-        if (prop->configurable) {
+        if (level == NJS_OBJECT_INTEGRITY_FROZEN
+            && njs_is_data_descriptor(prop) && prop->writable)
+        {
             goto done;
         }
     }
@@ -2055,7 +2000,8 @@ static const njs_object_prop_t  njs_obje
     {
         .type = NJS_PROPERTY,
         .name = njs_string("freeze"),
-        .value = njs_native_function(njs_object_freeze, 1),
+        .value = njs_native_function2(njs_object_set_integrity_level,
+                                      1, NJS_OBJECT_INTEGRITY_FROZEN),
         .writable = 1,
         .configurable = 1,
     },
@@ -2063,7 +2009,8 @@ static const njs_object_prop_t  njs_obje
     {
         .type = NJS_PROPERTY,
         .name = njs_string("isFrozen"),
-        .value = njs_native_function(njs_object_is_frozen, 1),
+        .value = njs_native_function2(njs_object_test_integrity_level,
+                                      1, NJS_OBJECT_INTEGRITY_FROZEN),
         .writable = 1,
         .configurable = 1,
     },
@@ -2071,7 +2018,8 @@ static const njs_object_prop_t  njs_obje
     {
         .type = NJS_PROPERTY,
         .name = njs_string("seal"),
-        .value = njs_native_function(njs_object_seal, 1),
+        .value = njs_native_function2(njs_object_set_integrity_level,
+                                      1, NJS_OBJECT_INTEGRITY_SEALED),
         .writable = 1,
         .configurable = 1,
     },
@@ -2079,7 +2027,8 @@ static const njs_object_prop_t  njs_obje
     {
         .type = NJS_PROPERTY,
         .name = njs_string("isSealed"),
-        .value = njs_native_function(njs_object_is_sealed, 1),
+        .value = njs_native_function2(njs_object_test_integrity_level,
+                                      1, NJS_OBJECT_INTEGRITY_SEALED),
         .writable = 1,
         .configurable = 1,
     },
diff -r 21057966d82d -r 4197cc28ea9c src/test/njs_unit_test.c
--- a/src/test/njs_unit_test.c	Wed Feb 17 16:05:28 2021 +0300
+++ b/src/test/njs_unit_test.c	Fri Feb 19 17:27:44 2021 +0000
@@ -13932,6 +13932,42 @@ static njs_unit_test_t  njs_test[] =
     { njs_str("Object.getOwnPropertyNames(Array.isArray)"),
       njs_str("name,length") },
 
+    /* Object.freeze() */
+
+    { njs_str("[undefined, null, false, NaN, '', Symbol()]"
+              ".every((x) => Object.is(Object.freeze(x), x))"),
+      njs_str("true")
+    },
+
+    { njs_str("var buf = new ArrayBuffer(8);"
+              NJS_TYPED_ARRAY_LIST
+              ".every((ctr) => {Object.freeze(new ctr([])); "
+              "                 Object.freeze(new ctr(buf, 8)); return true; })"),
+      njs_str("true")
+    },
+
+    { njs_str("var buf = new ArrayBuffer(8);"
+              NJS_TYPED_ARRAY_LIST
+              ".map((ctr) => { try { Object.freeze(new ctr(buf)); } catch(e) { return e; } })"
+              ".every((x) => x instanceof TypeError)"),
+      njs_str("true")
+    },
+
+    { njs_str("Object.freeze([1]).pop()"),
+      njs_str("TypeError: Cannot delete property \"0\" of array") },
+
+    { njs_str("var a = Object.freeze([1]); a[0] = 2;"),
+      njs_str("TypeError: Cannot assign to read-only property \"0\" of array") },
+
+    { njs_str("var a = Object.freeze([1]); a[1] = 2;"),
+      njs_str("TypeError: Cannot add property \"1\", object is not extensible") },
+
+    { njs_str("var a = Object.freeze([1,,3]); a[1] = 2;"),
+      njs_str("TypeError: Cannot add property \"1\", object is not extensible") },
+
+    { njs_str("var o = { a: 1 }; delete o.a; Object.freeze(o).a = 2;"),
+      njs_str("TypeError: Cannot add property \"a\", object is not extensible") },
+
     { njs_str("Object.defineProperty(Object.freeze({}), 'b', {})"),
       njs_str("TypeError: Cannot add property \"b\", object is not extensible") },
 
@@ -14032,30 +14068,42 @@ static njs_unit_test_t  njs_test[] =
               "Object.getOwnPropertyDescriptor(o, 'x').writable"),
       njs_str("undefined") },
 
-    { njs_str("Object.isFrozen({a:1})"),
-      njs_str("false") },
-
-    { njs_str("Object.isFrozen([1,2])"),
-      njs_str("false") },
-
-    { njs_str("Object.isFrozen(function() {})"),
-      njs_str("false") },
-
-    { njs_str("Object.isFrozen(new Date(''))"),
-      njs_str("false") },
-
-    { njs_str("Object.isFrozen(new RegExp(''))"),
-      njs_str("false") },
+    /* Object.isFrozen() */
+
+    { njs_str("[undefined, null, false, NaN, '', Symbol()]"
+              ".every((x) => Object.isFrozen(x))"),
+      njs_str("true") },
+
+    { njs_str("[[], {}]"
+              ".every((x) => Object.isFrozen(Object.preventExtensions(x)))"),
+      njs_str("true") },
+
+    { njs_str(NJS_TYPED_ARRAY_LIST
+              ".every((ctr) => !Object.isFrozen(new ctr([])))"),
+      njs_str("true") },
+
+    { njs_str(NJS_TYPED_ARRAY_LIST
+              ".every((ctr) => Object.isFrozen(Object.preventExtensions(new ctr([]))))"),
+      njs_str("true") },
+
+    { njs_str(NJS_TYPED_ARRAY_LIST
+              ".map((ctr) => new ctr([]))"
+              ".map((x) => { x.broken = true; return x; })"
+              ".every((x) => !Object.isFrozen(Object.preventExtensions(x)))"),
+      njs_str("true") },
+
+    { njs_str("var buf = new ArrayBuffer(8);"
+              NJS_TYPED_ARRAY_LIST
+              ".every((ctr) => !Object.isFrozen(Object.preventExtensions(new ctr(buf))))"),
+      njs_str("true") },
+
+    { njs_str("[{a:1}, [1,2], function() {}, new Date(''), new RegExp('')]"
+              ".every((x) => !Object.isFrozen(x))"),
+      njs_str("true") },
 
     { njs_str("Object.isFrozen()"),
       njs_str("true") },
 
-    { njs_str("Object.isFrozen(1)"),
-      njs_str("true") },
-
-    { njs_str("Object.isFrozen('')"),
-      njs_str("true") },
-
     { njs_str("Object.isFrozen(Object.defineProperties({}, {a:{value:1}}))"),
       njs_str("false") },
 
@@ -14086,8 +14134,29 @@ static njs_unit_test_t  njs_test[] =
     { njs_str("var o = Object.freeze({a:1}); Object.isFrozen(o)"),
       njs_str("true") },
 
-    { njs_str("Object.isFrozen(undefined)"),
-      njs_str("true") },
+    /* Object.seal() */
+
+    { njs_str("[undefined, null, false, NaN, '', Symbol()]"
+              ".every((x) => Object.is(Object.seal(x), x))"),
+      njs_str("true") },
+
+    { njs_str("Object.seal()"),
+      njs_str("undefined") },
+
+    { njs_str("Object.seal([1]).pop()"),
+      njs_str("TypeError: Cannot delete property \"0\" of array") },
+
+    { njs_str("var a = Object.seal([1]); a[0] = 2; a"),
+      njs_str("2") },
+
+    { njs_str("var a = Object.seal([1]); a[1] = 2;"),
+      njs_str("TypeError: Cannot add property \"1\", object is not extensible") },
+
+    { njs_str("var a = Object.seal([1,,3]); a[1] = 2;"),
+      njs_str("TypeError: Cannot add property \"1\", object is not extensible") },
+
+    { njs_str("var o = { a: 1 }; delete o.a; Object.seal(o).a = 2"),
+      njs_str("TypeError: Cannot add property \"a\", object is not extensible") },
 
     { njs_str("var o = Object.seal({a:1}); o.a = 2; o.a"),
       njs_str("2") },
@@ -14104,42 +14173,43 @@ static njs_unit_test_t  njs_test[] =
     { njs_str("var o = Object.seal({a:{b:1}}); o.a.b = 2; o.a.b"),
       njs_str("2") },
 
-    { njs_str("Object.seal()"),
-      njs_str("undefined") },
-
-    { njs_str("Object.seal(1)"),
-      njs_str("1") },
-
-    { njs_str("Object.seal('')"),
-      njs_str("") },
-
-    { njs_str("Object.seal(undefined)"),
-      njs_str("undefined") },
-
-    { njs_str("Object.isSealed({a:1})"),
-      njs_str("false") },
-
-    { njs_str("Object.isSealed([1,2])"),
-      njs_str("false") },
-
-    { njs_str("Object.isSealed(function() {})"),
-      njs_str("false") },
-
-    { njs_str("Object.isSealed(new Date(''))"),
-      njs_str("false") },
-
-    { njs_str("Object.isSealed(new RegExp(''))"),
-      njs_str("false") },
+    /* Object.isSealed() */
+
+    { njs_str("[undefined, null, false, NaN, '', Symbol()]"
+              ".every((x) => Object.isSealed(x))"),
+      njs_str("true") },
+
+    { njs_str("[[], {}]"
+              ".every((x) => Object.isSealed(Object.preventExtensions(x)))"),
+      njs_str("true") },
+
+    { njs_str(NJS_TYPED_ARRAY_LIST
+              ".every((ctr) => !Object.isSealed(new ctr([])))"),
+      njs_str("true") },
+
+    { njs_str(NJS_TYPED_ARRAY_LIST
+              ".every((ctr) => Object.isSealed(Object.preventExtensions(new ctr([]))))"),
+      njs_str("true") },
+
+    { njs_str("var buf = new ArrayBuffer(8);"
+              NJS_TYPED_ARRAY_LIST
+              ".every((ctr) => Object.isSealed(Object.preventExtensions(new ctr(buf))))"),
+      njs_str("true") },
+
+    { njs_str("var buf = new ArrayBuffer(8);"
+              NJS_TYPED_ARRAY_LIST
+              ".map((ctr) => new ctr(buf))"
+              ".map((x) => { x.broken = true; return x; })"
+              ".every((x) => !Object.isSealed(Object.preventExtensions(x)))"),
+      njs_str("true") },
+
+    { njs_str("[{a:1}, [1,2], function() {}, new Date(''), new RegExp('')]"
+              ".every((x) => !Object.isSealed(x))"),
+      njs_str("true") },
 
     { njs_str("Object.isSealed()"),
       njs_str("true") },
 
-    { njs_str("Object.isSealed(1)"),
-      njs_str("true") },
-
-    { njs_str("Object.isSealed('')"),
-      njs_str("true") },
-
     { njs_str("Object.isSealed(Object.defineProperties({}, {a:{value:1}}))"),
       njs_str("false") },
 


More information about the nginx-devel mailing list