]> git.kaiwu.me - njs.git/commitdiff
Fixed Object.freeze() and friends according to the specification.
authorArtem S. Povalyukhin <artem.povaluhin@gmail.com>
Fri, 19 Feb 2021 17:27:44 +0000 (17:27 +0000)
committerArtem S. Povalyukhin <artem.povaluhin@gmail.com>
Fri, 19 Feb 2021 17:27:44 +0000 (17:27 +0000)
This fixes #340 and also closes #374 issues on Github.

src/njs_object.c
src/test/njs_unit_test.c

index e1a0266599fe406fb17daa9a18e8d307998f3ae1..b9fadddc1283d48cf02c29950757366b05dccbee 100644 (file)
@@ -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, njs_value_t *args, njs_uint_t nargs,
 }
 
 
+/* 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_value_t *args, njs_uint_t nargs,
 
     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_value_t *args, njs_uint_t nargs,
             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_value_t *args, njs_uint_t nargs,
 
 
 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_value_t *args, njs_uint_t nargs,
 
     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,60 +1598,18 @@ njs_object_is_frozen(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
     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) {
-            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;
+    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;
     }
 
-    object = njs_object(value);
-    object->extensible = 0;
-
     njs_lvlhsh_each_init(&lhe, &njs_object_hash_proto);
 
     hash = &object->hash;
@@ -1637,52 +1621,13 @@ njs_object_seal(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
             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) {
+            goto done;
         }
 
-        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_object_constructor_properties[] =
     {
         .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_object_constructor_properties[] =
     {
         .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_object_constructor_properties[] =
     {
         .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_object_constructor_properties[] =
     {
         .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,
     },
index eae271270cbd0157f1c847c91c31777082769f1c..23588d22a43d94155a600060ad0bfecf94820ede 100644 (file)
@@ -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,28 +14068,40 @@ static njs_unit_test_t  njs_test[] =
               "Object.getOwnPropertyDescriptor(o, 'x').writable"),
       njs_str("undefined") },
 
-    { njs_str("Object.isFrozen({a:1})"),
-      njs_str("false") },
+    /* Object.isFrozen() */
 
-    { njs_str("Object.isFrozen([1,2])"),
-      njs_str("false") },
+    { njs_str("[undefined, null, false, NaN, '', Symbol()]"
+              ".every((x) => Object.isFrozen(x))"),
+      njs_str("true") },
 
-    { njs_str("Object.isFrozen(function() {})"),
-      njs_str("false") },
+    { njs_str("[[], {}]"
+              ".every((x) => Object.isFrozen(Object.preventExtensions(x)))"),
+      njs_str("true") },
 
-    { njs_str("Object.isFrozen(new Date(''))"),
-      njs_str("false") },
+    { njs_str(NJS_TYPED_ARRAY_LIST
+              ".every((ctr) => !Object.isFrozen(new ctr([])))"),
+      njs_str("true") },
 
-    { njs_str("Object.isFrozen(new RegExp(''))"),
-      njs_str("false") },
+    { njs_str(NJS_TYPED_ARRAY_LIST
+              ".every((ctr) => Object.isFrozen(Object.preventExtensions(new ctr([]))))"),
+      njs_str("true") },
 
-    { njs_str("Object.isFrozen()"),
+    { 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("Object.isFrozen(1)"),
+    { 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("Object.isFrozen()"),
       njs_str("true") },
 
     { njs_str("Object.isFrozen(Object.defineProperties({}, {a:{value:1}}))"),
@@ -14086,9 +14134,30 @@ 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)"),
+    /* 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,40 +14173,41 @@ 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") },
+    /* Object.isSealed() */
 
-    { njs_str("Object.isSealed({a:1})"),
-      njs_str("false") },
+    { njs_str("[undefined, null, false, NaN, '', Symbol()]"
+              ".every((x) => Object.isSealed(x))"),
+      njs_str("true") },
 
-    { njs_str("Object.isSealed([1,2])"),
-      njs_str("false") },
+    { njs_str("[[], {}]"
+              ".every((x) => Object.isSealed(Object.preventExtensions(x)))"),
+      njs_str("true") },
 
-    { njs_str("Object.isSealed(function() {})"),
-      njs_str("false") },
+    { njs_str(NJS_TYPED_ARRAY_LIST
+              ".every((ctr) => !Object.isSealed(new ctr([])))"),
+      njs_str("true") },
 
-    { njs_str("Object.isSealed(new Date(''))"),
-      njs_str("false") },
+    { njs_str(NJS_TYPED_ARRAY_LIST
+              ".every((ctr) => Object.isSealed(Object.preventExtensions(new ctr([]))))"),
+      njs_str("true") },
 
-    { njs_str("Object.isSealed(new RegExp(''))"),
-      njs_str("false") },
+    { 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("Object.isSealed()"),
+    { 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("Object.isSealed(1)"),
+    { 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("Object.isSealed()"),
       njs_str("true") },
 
     { njs_str("Object.isSealed(Object.defineProperties({}, {a:{value:1}}))"),