[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