GeezerVison is showing github.com — node-api: add status napi_cannot_run_js · nodejs/node@e834979
File treeExpand file treeCollapse file tree Open diff view settings test/js-native-api/test_cannot_run_js
Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: doc/api/n-api.md +16-3Lines changed: 16 additions & 3 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -580,7 +580,8 @@ typedef enum { | 580 | 580 | napi_arraybuffer_expected, | 581 | 581 | napi_detachable_arraybuffer_expected, | 582 | 582 | napi_would_deadlock, /* unused */ | 583 | | - napi_no_external_buffers_allowed | | 583 | + napi_no_external_buffers_allowed, | | 584 | + napi_cannot_run_js | 584 | 585 | } napi_status; | 585 | 586 | ``` | 586 | 587 |
| @@ -814,6 +815,18 @@ typedef void (*napi_finalize)(napi_env env, | 814 | 815 | Unless for reasons discussed in [Object Lifetime Management][], creating a | 815 | 816 | handle and/or callback scope inside the function body is not necessary. | 816 | 817 |
| | 818 | +Since these functions may be called while the JavaScript engine is in a state | | 819 | +where it cannot execute JavaScript code, some Node-API calls may return | | 820 | +`napi_pending_exception` even when there is no exception pending. | | 821 | + | | 822 | +Change History: | | 823 | + | | 824 | +* experimental (`NAPI_EXPERIMENTAL` is defined): | | 825 | + | | 826 | + Node-API calls made from a finalizer will return `napi_cannot_run_js` when | | 827 | + the JavaScript engine is unable to execute JavaScript, and will return | | 828 | + `napi_exception_pending` if there is a pending exception. | | 829 | + | 817 | 830 | #### `napi_async_execute_callback` | 818 | 831 |
| 819 | 832 | <!-- YAML | @@ -1952,11 +1965,11 @@ from [`napi_add_async_cleanup_hook`][]. | 1952 | 1965 | The Node.js environment may be torn down at an arbitrary time as soon as | 1953 | 1966 | possible with JavaScript execution disallowed, like on the request of | 1954 | 1967 | [`worker.terminate()`][]. When the environment is being torn down, the | 1955 | | -registered `napi_finalize` callbacks of JavaScript objects, Thread-safe | | 1968 | +registered `napi_finalize` callbacks of JavaScript objects, thread-safe | 1956 | 1969 | functions and environment instance data are invoked immediately and | 1957 | 1970 | independently. | 1958 | 1971 |
| 1959 | | -The invocation of `napi_finalize` callbacks are scheduled after the manually | | 1972 | +The invocation of `napi_finalize` callbacks is scheduled after the manually | 1960 | 1973 | registered cleanup hooks. In order to ensure a proper order of addon | 1961 | 1974 | finalization during environment shutdown to avoid use-after-free in the | 1962 | 1975 | `napi_finalize` callback, addons should register a cleanup hook with | |
Collapse file Copy file name to clipboardExpand all lines: src/js_native_api_types.h +2-1Lines changed: 2 additions & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -99,7 +99,8 @@ typedef enum { | 99 | 99 | napi_arraybuffer_expected, | 100 | 100 | napi_detachable_arraybuffer_expected, | 101 | 101 | napi_would_deadlock, // unused | 102 | | - napi_no_external_buffers_allowed | | 102 | + napi_no_external_buffers_allowed, | | 103 | + napi_cannot_run_js, | 103 | 104 | } napi_status; | 104 | 105 | // Note: when adding a new enum value to `napi_status`, please also update | 105 | 106 | // * `const int last_status` in the definition of `napi_get_last_error_info()' | |
Collapse file Copy file name to clipboardExpand all lines: src/js_native_api_v8.cc +2-1Lines changed: 2 additions & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -682,6 +682,7 @@ static const char* error_messages[] = { | 682 | 682 | "A detachable arraybuffer was expected", | 683 | 683 | "Main thread would deadlock", | 684 | 684 | "External buffers are not allowed", | | 685 | + "Cannot run JavaScript", | 685 | 686 | }; | 686 | 687 |
| 687 | 688 | napi_status NAPI_CDECL napi_get_last_error_info( | @@ -693,7 +694,7 @@ napi_status NAPI_CDECL napi_get_last_error_info( | 693 | 694 | // message in the `napi_status` enum each time a new error message is added. | 694 | 695 | // We don't have a napi_status_last as this would result in an ABI | 695 | 696 | // change each time a message was added. | 696 | | - const int last_status = napi_no_external_buffers_allowed; | | 697 | + const int last_status = napi_cannot_run_js; | 697 | 698 |
| 698 | 699 | static_assert(NAPI_ARRAYSIZE(error_messages) == last_status + 1, | 699 | 700 | "Count of error messages must match count of error values"); | |
Collapse file Copy file name to clipboardExpand all lines: src/js_native_api_v8.h +6-3Lines changed: 6 additions & 3 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -212,9 +212,12 @@ inline napi_status napi_set_last_error(napi_env env, | 212 | 212 | #define NAPI_PREAMBLE(env) \ | 213 | 213 | CHECK_ENV((env)); \ | 214 | 214 | RETURN_STATUS_IF_FALSE( \ | 215 | | - (env), \ | 216 | | - (env)->last_exception.IsEmpty() && (env)->can_call_into_js(), \ | 217 | | - napi_pending_exception); \ | | 215 | + (env), (env)->last_exception.IsEmpty(), napi_pending_exception); \ | | 216 | + RETURN_STATUS_IF_FALSE((env), \ | | 217 | + (env)->can_call_into_js(), \ | | 218 | + (env->module_api_version == NAPI_VERSION_EXPERIMENTAL \ | | 219 | + ? napi_cannot_run_js \ | | 220 | + : napi_pending_exception)); \ | 218 | 221 | napi_clear_last_error((env)); \ | 219 | 222 | v8impl::TryCatch try_catch((env)) | 220 | 223 |
| |
Collapse file Copy file name to clipboard +32Lines changed: 32 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +{ | | 2 | + "targets": [ | | 3 | + { | | 4 | + "target_name": "copy_entry_point", | | 5 | + "type": "none", | | 6 | + "copies": [ | | 7 | + { | | 8 | + "destination": ".", | | 9 | + "files": [ "../entry_point.c" ] | | 10 | + } | | 11 | + ] | | 12 | + }, | | 13 | + { | | 14 | + "target_name": "test_cannot_run_js", | | 15 | + "sources": [ | | 16 | + "entry_point.c", | | 17 | + "test_cannot_run_js.c" | | 18 | + ], | | 19 | + "defines": [ "NAPI_EXPERIMENTAL" ], | | 20 | + "dependencies": [ "copy_entry_point" ], | | 21 | + }, | | 22 | + { | | 23 | + "target_name": "test_pending_exception", | | 24 | + "sources": [ | | 25 | + "entry_point.c", | | 26 | + "test_cannot_run_js.c" | | 27 | + ], | | 28 | + "defines": [ "NAPI_VERSION=8" ], | | 29 | + "dependencies": [ "copy_entry_point" ], | | 30 | + } | | 31 | + ] | | 32 | +} |
Collapse file Copy file name to clipboard +24Lines changed: 24 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +'use strict'; | | 2 | + | | 3 | +// Test that `napi_call_function()` returns `napi_cannot_run_js` in experimental | | 4 | +// mode and `napi_pending_exception` otherwise. This test calls the add-on's | | 5 | +// `createRef()` method, which creates a strong reference to a JS function. When | | 6 | +// the process exits, it calls all reference finalizers. The finalizer for the | | 7 | +// strong reference created herein will attempt to call `napi_get_property()` on | | 8 | +// a property of the global object and will abort the process if the API doesn't | | 9 | +// return the correct status. | | 10 | + | | 11 | +const { buildType, mustNotCall } = require('../../common'); | | 12 | +const addon_v8 = require(`./build/${buildType}/test_pending_exception`); | | 13 | +const addon_new = require(`./build/${buildType}/test_cannot_run_js`); | | 14 | + | | 15 | +function runTests(addon, isVersion8) { | | 16 | + addon.createRef(mustNotCall()); | | 17 | +} | | 18 | + | | 19 | +function runAllTests() { | | 20 | + runTests(addon_v8, /* isVersion8 */ true); | | 21 | + runTests(addon_new, /* isVersion8 */ false); | | 22 | +} | | 23 | + | | 24 | +runAllTests(); |
Collapse file Copy file name to clipboard +49Lines changed: 49 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +#include <js_native_api.h> | | 2 | +#include "../common.h" | | 3 | +#include "stdlib.h" | | 4 | + | | 5 | +static void Finalize(napi_env env, void* data, void* hint) { | | 6 | + napi_value global, set_timeout; | | 7 | + napi_ref* ref = data; | | 8 | +#ifdef NAPI_EXPERIMENTAL | | 9 | + napi_status expected_status = napi_cannot_run_js; | | 10 | +#else | | 11 | + napi_status expected_status = napi_pending_exception; | | 12 | +#endif // NAPI_EXPERIMENTAL | | 13 | + | | 14 | + if (napi_delete_reference(env, *ref) != napi_ok) abort(); | | 15 | + if (napi_get_global(env, &global) != napi_ok) abort(); | | 16 | + if (napi_get_named_property(env, global, "setTimeout", &set_timeout) != | | 17 | + expected_status) | | 18 | + abort(); | | 19 | + free(ref); | | 20 | +} | | 21 | + | | 22 | +static napi_value CreateRef(napi_env env, napi_callback_info info) { | | 23 | + size_t argc = 1; | | 24 | + napi_value cb; | | 25 | + napi_valuetype value_type; | | 26 | + napi_ref* ref = malloc(sizeof(*ref)); | | 27 | + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, &cb, NULL, NULL)); | | 28 | + NODE_API_ASSERT(env, argc == 1, "Function takes only one argument"); | | 29 | + NODE_API_CALL(env, napi_typeof(env, cb, &value_type)); | | 30 | + NODE_API_ASSERT( | | 31 | + env, value_type == napi_function, "argument must be function"); | | 32 | + NODE_API_CALL(env, napi_add_finalizer(env, cb, ref, Finalize, NULL, ref)); | | 33 | + return cb; | | 34 | +} | | 35 | + | | 36 | +EXTERN_C_START | | 37 | +napi_value Init(napi_env env, napi_value exports) { | | 38 | + napi_property_descriptor properties[] = { | | 39 | + DECLARE_NODE_API_PROPERTY("createRef", CreateRef), | | 40 | + }; | | 41 | + | | 42 | + NODE_API_CALL( | | 43 | + env, | | 44 | + napi_define_properties( | | 45 | + env, exports, sizeof(properties) / sizeof(*properties), properties)); | | 46 | + | | 47 | + return exports; | | 48 | +} | | 49 | +EXTERN_C_END |
|