From e7c2fe98b23d9559877e34aba71a69c6df511e36 Mon Sep 17 00:00:00 2001 From: Shelley Vohr Date: Fri, 25 Sep 2020 22:11:13 -0700 Subject: [PATCH 1/2] n-api: support for object freeze/seal --- doc/api/n-api.md | 47 ++++++++++++++++++++ src/js_native_api.h | 4 ++ src/js_native_api_v8.cc | 36 +++++++++++++++ test/js-native-api/test_object/test.js | 39 ++++++++++++++++ test/js-native-api/test_object/test_object.c | 26 +++++++++++ 5 files changed, 152 insertions(+) diff --git a/doc/api/n-api.md b/doc/api/n-api.md index c7ec9a14d3fe26..7ee1fd2e94263d 100644 --- a/doc/api/n-api.md +++ b/doc/api/n-api.md @@ -4227,6 +4227,53 @@ this API will set the properties on the object one at a time, as defined by `DefineOwnProperty()` (described in [Section 9.1.6][] of the ECMA-262 specification). +#### napi_object_freeze + + +> Stability: 1 - Experimental + +```c +napi_status napi_object_freeze(napi_env env, + napi_value object); +``` + +* `[in] env`: The environment that the N-API call is invoked under. +* `[in] object`: The object to freeze. + +Returns `napi_ok` if the API succeeded. + +This method freezes a given object. This prevents new properties from +being added to it, existing properties from being removed, prevents +changing the enumerability, configurability, or writability of existing +properties, and prevents the values of existing properties from being changed. +It also prevents the object's prototype from being changed. This is described +in [Section 19.1.2.6](https://tc39.es/ecma262/#sec-object.freeze) of the +ECMA-262 specification. + +#### napi_object_seal + + +> Stability: 1 - Experimental + +```c +napi_status napi_object_seal(napi_env env, + napi_value object); +``` + +* `[in] env`: The environment that the N-API call is invoked under. +* `[in] object`: The object to seal. + +Returns `napi_ok` if the API succeeded. + +This method seals a given object. This prevents new properties from being +added to it, as well as marking all existing properties as non-configurable. +This is described in [Section 19.1.2.20](https://tc39.es/ecma262/#sec-object.seal) +of the ECMA-262 specification. + ## Working with JavaScript functions N-API provides a set of APIs that allow JavaScript code to diff --git a/src/js_native_api.h b/src/js_native_api.h index bd8bd35d7b72c9..5daa20f9040096 100644 --- a/src/js_native_api.h +++ b/src/js_native_api.h @@ -550,6 +550,10 @@ napi_check_object_type_tag(napi_env env, napi_value value, const napi_type_tag* type_tag, bool* result); +NAPI_EXTERN napi_status napi_object_freeze(napi_env env, + napi_value object); +NAPI_EXTERN napi_status napi_object_seal(napi_env env, + napi_value object); #endif // NAPI_EXPERIMENTAL EXTERN_C_END diff --git a/src/js_native_api_v8.cc b/src/js_native_api_v8.cc index 0a069b3ae45b97..1feabfd879476c 100644 --- a/src/js_native_api_v8.cc +++ b/src/js_native_api_v8.cc @@ -1362,6 +1362,42 @@ napi_status napi_define_properties(napi_env env, return GET_RETURN_STATUS(env); } +napi_status napi_object_freeze(napi_env env, + napi_value object) { + NAPI_PREAMBLE(env); + + v8::Local context = env->context(); + v8::Local obj; + + CHECK_TO_OBJECT(env, context, obj, object); + + v8::Maybe set_frozen = + obj->SetIntegrityLevel(context, v8::IntegrityLevel::kFrozen); + + RETURN_STATUS_IF_FALSE_WITH_PREAMBLE(env, + set_frozen.FromMaybe(false), napi_generic_failure); + + return GET_RETURN_STATUS(env); +} + +napi_status napi_object_seal(napi_env env, + napi_value object) { + NAPI_PREAMBLE(env); + + v8::Local context = env->context(); + v8::Local obj; + + CHECK_TO_OBJECT(env, context, obj, object); + + v8::Maybe set_sealed = + obj->SetIntegrityLevel(context, v8::IntegrityLevel::kSealed); + + RETURN_STATUS_IF_FALSE_WITH_PREAMBLE(env, + set_sealed.FromMaybe(false), napi_generic_failure); + + return GET_RETURN_STATUS(env); +} + napi_status napi_is_array(napi_env env, napi_value value, bool* result) { CHECK_ENV(env); CHECK_ARG(env, value); diff --git a/test/js-native-api/test_object/test.js b/test/js-native-api/test_object/test.js index b78666995271ff..9c44ad8c5b9cdf 100644 --- a/test/js-native-api/test_object/test.js +++ b/test/js-native-api/test_object/test.js @@ -275,3 +275,42 @@ assert.deepStrictEqual(test_object.TestGetProperty(), { keyIsNull: 'Invalid argument', resultIsNull: 'Invalid argument' }); + +{ + const obj = { x: 'a', y: 'b', z: 'c' }; + + test_object.TestSeal(obj); + + assert.strictEqual(Object.isSealed(obj), true); + + assert.throws(() => { + obj['w'] = 'd' + }, /Cannot add property w, object is not extensible/); + + assert.throws(() => { + delete obj['x'] + }, /Cannot delete property 'x' of #/); + + // Sealed objects allow updating existing properties. + assert.doesNotThrow(() => { obj['x'] = 'd' }); +} + +{ + const obj = { x: 10, y: 10, z: 10 }; + + test_object.TestFreeze(obj); + + assert.strictEqual(Object.isFrozen(obj), true); + + assert.throws(() => { + obj['x'] = 10 + }, /Cannot assign to read only property 'x' of object '#/); + + assert.throws(() => { + obj['w'] = 15 + }, /Cannot add property w, object is not extensible/); + + assert.throws(() => { + delete obj['x'] + }, /Cannot delete property 'x' of #/); +} diff --git a/test/js-native-api/test_object/test_object.c b/test/js-native-api/test_object/test_object.c index f2ea89d6c60943..70a85e03c725dd 100644 --- a/test/js-native-api/test_object/test_object.c +++ b/test/js-native-api/test_object/test_object.c @@ -472,6 +472,30 @@ static napi_value TestGetProperty(napi_env env, return object; } +static napi_value TestFreeze(napi_env env, + napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + napi_value object = args[0]; + NAPI_CALL(env, napi_object_freeze(env, object)); + + return object; +} + +static napi_value TestSeal(napi_env env, + napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + napi_value object = args[0]; + NAPI_CALL(env, napi_object_seal(env, object)); + + return object; +} + // We create two type tags. They are basically 128-bit UUIDs. static const napi_type_tag type_tags[2] = { { 0xdaf987b3cc62481a, 0xb745b0497f299531 }, @@ -532,6 +556,8 @@ napi_value Init(napi_env env, napi_value exports) { DECLARE_NAPI_PROPERTY("TypeTaggedInstance", TypeTaggedInstance), DECLARE_NAPI_PROPERTY("CheckTypeTag", CheckTypeTag), DECLARE_NAPI_PROPERTY("TestGetProperty", TestGetProperty), + DECLARE_NAPI_PROPERTY("TestFreeze", TestFreeze), + DECLARE_NAPI_PROPERTY("TestSeal", TestSeal), }; init_test_null(env, exports); From ce959cfdb908bf8737ee7e0ec9c43d21d83937a2 Mon Sep 17 00:00:00 2001 From: Shelley Vohr Date: Tue, 6 Oct 2020 10:21:04 -0700 Subject: [PATCH 2/2] fixup! n-api: support for object freeze/seal --- test/js-native-api/test_object/test.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/test/js-native-api/test_object/test.js b/test/js-native-api/test_object/test.js index 9c44ad8c5b9cdf..e5923ecd16c2cf 100644 --- a/test/js-native-api/test_object/test.js +++ b/test/js-native-api/test_object/test.js @@ -284,15 +284,16 @@ assert.deepStrictEqual(test_object.TestGetProperty(), { assert.strictEqual(Object.isSealed(obj), true); assert.throws(() => { - obj['w'] = 'd' + obj.w = 'd'; }, /Cannot add property w, object is not extensible/); assert.throws(() => { - delete obj['x'] + delete obj.x; }, /Cannot delete property 'x' of #/); - // Sealed objects allow updating existing properties. - assert.doesNotThrow(() => { obj['x'] = 'd' }); + // Sealed objects allow updating existing properties, + // so this should not throw. + obj.x = 'd'; } { @@ -303,14 +304,14 @@ assert.deepStrictEqual(test_object.TestGetProperty(), { assert.strictEqual(Object.isFrozen(obj), true); assert.throws(() => { - obj['x'] = 10 + obj.x = 10; }, /Cannot assign to read only property 'x' of object '#/); assert.throws(() => { - obj['w'] = 15 + obj.w = 15; }, /Cannot add property w, object is not extensible/); assert.throws(() => { - delete obj['x'] + delete obj.x; }, /Cannot delete property 'x' of #/); }