From 5faaa603d84af583ddd3c68d64183e9531008b84 Mon Sep 17 00:00:00 2001 From: Shelley Vohr Date: Fri, 25 Sep 2020 22:11:13 -0700 Subject: [PATCH] n-api: support for object freeze/seal PR-URL: https://github.com/nodejs/node/pull/35359 Reviewed-By: Gabriel Schulhof Reviewed-By: Michael Dawson Reviewed-By: Gireesh Punathil --- 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 | 40 +++++++++++++++++ test/js-native-api/test_object/test_object.c | 26 +++++++++++ 5 files changed, 153 insertions(+) diff --git a/doc/api/n-api.md b/doc/api/n-api.md index a8620c209c44b3..5cfebd60dc1591 100644 --- a/doc/api/n-api.md +++ b/doc/api/n-api.md @@ -4092,6 +4092,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 5faed2570d2e39..5e3cee9ea0596f 100644 --- a/src/js_native_api_v8.cc +++ b/src/js_native_api_v8.cc @@ -1394,6 +1394,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..e5923ecd16c2cf 100644 --- a/test/js-native-api/test_object/test.js +++ b/test/js-native-api/test_object/test.js @@ -275,3 +275,43 @@ 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, + // so this should not throw. + 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);