diff --git a/Makefile b/Makefile index c947a2a4367d05..523a3d8c541ed8 100644 --- a/Makefile +++ b/Makefile @@ -120,9 +120,10 @@ v8: test: all $(MAKE) build-addons + $(MAKE) build-addons-napi $(MAKE) cctest $(PYTHON) tools/test.py --mode=release -J \ - doctool inspector known_issues message pseudo-tty parallel sequential addons + doctool inspector known_issues message pseudo-tty parallel sequential addons addons-napi $(MAKE) lint test-parallel: all @@ -189,6 +190,41 @@ test/addons/.buildstamp: config.gypi \ # TODO(bnoordhuis) Force rebuild after gyp update. build-addons: $(NODE_EXE) test/addons/.buildstamp +ADDONS_NAPI_BINDING_GYPS := \ + $(filter-out test/addons-napi/??_*/binding.gyp, \ + $(wildcard test/addons-napi/*/binding.gyp)) + +ADDONS_NAPI_BINDING_SOURCES := \ + $(filter-out test/addons-napi/??_*/*.cc, $(wildcard test/addons-napi/*/*.cc)) \ + $(filter-out test/addons-napi/??_*/*.h, $(wildcard test/addons-napi/*/*.h)) + +# Implicitly depends on $(NODE_EXE), see the build-addons-napi rule for rationale. +test/addons-napi/.buildstamp: config.gypi \ + deps/npm/node_modules/node-gyp/package.json \ + $(ADDONS_NAPI_BINDING_GYPS) $(ADDONS_NAPI_BINDING_SOURCES) \ + deps/uv/include/*.h deps/v8/include/*.h \ + src/node.h src/node_buffer.h src/node_object_wrap.h src/node_version.h \ + src/node_api.h src/node_api_types.h +# Cannot use $(wildcard test/addons-napi/*/) here, it's evaluated before +# embedded addons have been generated from the documentation. + @for dirname in test/addons-napi/*/; do \ + printf "\nBuilding addon $$PWD/$$dirname\n" ; \ + env MAKEFLAGS="-j1" $(NODE) deps/npm/node_modules/node-gyp/bin/node-gyp \ + --loglevel=$(LOGLEVEL) rebuild \ + --python="$(PYTHON)" \ + --directory="$$PWD/$$dirname" \ + --nodedir="$$PWD" || exit 1 ; \ + done + touch $@ + +# .buildstamp and .docbuildstamp need $(NODE_EXE) but cannot depend on it +# directly because it calls make recursively. The parent make cannot know +# if the subprocess touched anything so it pessimistically assumes that +# .buildstamp and .docbuildstamp are out of date and need a rebuild. +# Just goes to show that recursive make really is harmful... +# TODO(bnoordhuis) Force rebuild after gyp or node-gyp update. +build-addons-napi: $(NODE_EXE) test/addons-napi/.buildstamp + clear-stalled: # Clean up any leftover processes but don't error if found. ps awwx | grep Release/node | grep -v grep | cat @@ -200,7 +236,9 @@ clear-stalled: test-gc: all test/gc/node_modules/weak/build/Release/weakref.node $(PYTHON) tools/test.py --mode=release gc -test-build: | all build-addons +test-build: | all build-addons build-addons-napi + +test-build-addons-napi: all build-addons-napi test-all: test-build test/gc/node_modules/weak/build/Release/weakref.node $(PYTHON) tools/test.py --mode=debug,release @@ -208,12 +246,12 @@ test-all: test-build test/gc/node_modules/weak/build/Release/weakref.node test-all-valgrind: test-build $(PYTHON) tools/test.py --mode=debug,release --valgrind -CI_NATIVE_SUITES := addons +CI_NATIVE_SUITES := addons addons-napi CI_JS_SUITES := doctool inspector known_issues message parallel pseudo-tty sequential # Build and test addons without building anything else test-ci-native: LOGLEVEL := info -test-ci-native: | test/addons/.buildstamp +test-ci-native: | test/addons/.buildstamp test/addons-napi/.buildstamp $(PYTHON) tools/test.py $(PARALLEL_ARGS) -p tap --logfile test.tap \ --mode=release --flaky-tests=$(FLAKY_TESTS) \ $(TEST_CI_ARGS) $(CI_NATIVE_SUITES) @@ -231,11 +269,11 @@ test-ci-js: | clear-stalled fi test-ci: LOGLEVEL := info -test-ci: | clear-stalled build-addons +test-ci: | clear-stalled build-addons build-addons-napi out/Release/cctest --gtest_output=tap:cctest.tap $(PYTHON) tools/test.py $(PARALLEL_ARGS) -p tap --logfile test.tap \ --mode=release --flaky-tests=$(FLAKY_TESTS) \ - $(TEST_CI_ARGS) $(CI_JS_SUITES) $(CI_NATIVE_SUITES) + $(TEST_CI_ARGS) $(CI_JS_SUITES) addons-napi $(CI_NATIVE_SUITES) # Clean up any leftover processes, error if found. ps awwx | grep Release/node | grep -v grep | cat @PS_OUT=`ps awwx | grep Release/node | grep -v grep | awk '{print $$1}'`; \ @@ -282,7 +320,10 @@ test-npm: $(NODE_EXE) test-npm-publish: $(NODE_EXE) npm_package_config_publishtest=true $(NODE) deps/npm/test/run.js -test-addons: test-build +test-addons-napi: test-build-addons-napi + $(PYTHON) tools/test.py --mode=release addons-napi + +test-addons: test-build test-addons-napi $(PYTHON) tools/test.py --mode=release addons test-addons-clean: @@ -813,6 +854,8 @@ LINT_CPP_FILES = $(filter-out $(LINT_CPP_EXCLUDE), $(wildcard \ test/addons/*/*.h \ test/cctest/*.cc \ test/cctest/*.h \ + test/addons-napi/*/*.cc \ + test/addons-napi/*/*.h \ tools/icu/*.cc \ tools/icu/*.h \ )) @@ -857,5 +900,6 @@ endif bench-buffer bench-net bench-http bench-fs bench-tls cctest run-ci \ test-v8 test-v8-intl test-v8-benchmarks test-v8-all v8 lint-ci \ bench-ci lint-js-ci doc-only $(TARBALL)-headers test-ci test-ci-native \ - test-ci-js build-ci test-hash-seed clear-stalled + test-ci-js build-ci test-hash-seed clear-stalled test-addons-napi \ + build-addons-napi diff --git a/README.md b/README.md index ad3d838dd58076..cb1a2b7a98bb32 100644 --- a/README.md +++ b/README.md @@ -194,8 +194,8 @@ us a report nonetheless. - [#14519](https://github.com/nodejs/node/issues/14519): _Internal domain function can be used to cause segfaults_. Causing program termination using - either the public Javascript APIs or the private bindings layer APIs requires - the ability to execute arbitrary Javascript code, which is already the highest + either the public JavaScript APIs or the private bindings layer APIs requires + the ability to execute arbitrary JavaScript code, which is already the highest level of privilege possible. - [#12141](https://github.com/nodejs/node/pull/12141): _buffer: zero fill diff --git a/doc/api/_toc.md b/doc/api/_toc.md index 5f7bca59e87d75..cdca642eeb9b93 100644 --- a/doc/api/_toc.md +++ b/doc/api/_toc.md @@ -7,7 +7,8 @@ * [Assertion Testing](assert.html) * [Buffer](buffer.html) -* [C/C++ Addons](addons.html) +* [C++ Addons](addons.html) +* [C/C++ Addons - N-API](n-api.html) * [Child Processes](child_process.html) * [Cluster](cluster.html) * [Command Line Options](cli.html) diff --git a/doc/api/addons.md b/doc/api/addons.md index 17811245f1c23e..4efc09b399e139 100644 --- a/doc/api/addons.md +++ b/doc/api/addons.md @@ -1,8 +1,8 @@ -# C/C++ Addons +# C++ Addons -Node.js Addons are dynamically-linked shared objects, written in C or C++, that +Node.js Addons are dynamically-linked shared objects, written in C++, that can be loaded into Node.js using the [`require()`][require] function, and used just as if they were an ordinary Node.js module. They are used primarily to provide an interface between JavaScript running in Node.js and C/C++ libraries. @@ -28,7 +28,7 @@ involving knowledge of several components and APIs : off-loading work via libuv to non-blocking system operations, worker threads or a custom use of libuv's threads. - - Internal Node.js libraries. Node.js itself exports a number of C/C++ APIs + - Internal Node.js libraries. Node.js itself exports a number of C++ APIs that Addons can use — the most important of which is the `node::ObjectWrap` class. @@ -215,6 +215,26 @@ Addon developers are recommended to use to keep compatibility between past and future releases of V8 and Node.js. See the `nan` [examples][] for an illustration of how it can be used. + +## N-API + +> Stability: 1 - Experimental + +N-API is an API for building native Addons. It is independent from +the underlying JavaScript runtime (ex V8) and is maintained as part of +Node.js itself. This API will be Application Binary Interface (ABI) stable +across version of Node.js. It is intended to insulate Addons from +changes in the underlying JavaScript engine and allow modules +compiled for one version to run on later versions of Node.js without +recompilation. Addons are built/packaged with the same approach/tools +outlined in this document (node-gyp, etc.). The only difference is the +set of APIs that are used by the native code. Instead of using the V8 +or [Native Abstractions for Node.js][] APIs, the functions available +in the N-API are used. + +The functions available and how to use them are documented in the +section titled [C/C++ Addons - N-API](n-api.html). + ## Addon examples Following are some example Addons intended to help developers get started. The diff --git a/doc/api/cli.md b/doc/api/cli.md index f1b33e883ae883..84876ea181d0a8 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -353,7 +353,6 @@ Node options that are allowed are: - `--debug-brk` - `--debug-port` - `--debug` -- `--napi-modules` - `--no-deprecation` - `--no-warnings` - `--openssl-config` diff --git a/doc/api/dns.md b/doc/api/dns.md index 7fce91970aa10e..1052a46c200f3f 100644 --- a/doc/api/dns.md +++ b/doc/api/dns.md @@ -175,7 +175,7 @@ added: v0.1.27 - `rrtype` {string} Resource record type. Default: `'A'`. - `callback` {Function} - `err` {Error} - - `records` {string[] | Object[] | string[][] | Object} + - `records` {string[] | Object[] | Object} Uses the DNS protocol to resolve a hostname (e.g. `'nodejs.org'`) into an array of the resource records. The `callback` function has arguments @@ -362,7 +362,7 @@ added: v0.1.27 - `hostname` {string} - `callback` {Function} - `err` {Error} - - `records` {string[][]} + - `records` {string[]} Uses the DNS protocol to resolve text queries (`TXT` records) for the `hostname`. The `records` argument passed to the `callback` function is a diff --git a/doc/api/n-api.md b/doc/api/n-api.md new file mode 100644 index 00000000000000..38d7b22f558ec2 --- /dev/null +++ b/doc/api/n-api.md @@ -0,0 +1,3850 @@ +# N-API + +> Stability: 1 - Experimental + +N-API (pronounced N as in the letter, followed by API) +is an API for building native Addons. It is independent from +the underlying JavaScript runtime (ex V8) and is maintained as part of +Node.js itself. This API will be Application Binary Interface (ABI) stable +across versions of Node.js. It is intended to insulate Addons from +changes in the underlying JavaScript engine and allow modules +compiled for one version to run on later versions of Node.js without +recompilation. + +Addons are built/packaged with the same approach/tools +outlined in the section titled [C++ Addons](addons.html). +The only difference is the set of APIs that are used by the native code. +Instead of using the V8 or [Native Abstractions for Node.js][] APIs, +the functions available in the N-API are used. + +APIs exposed by N-API are generally used to create and manipulate +JavaScript values. Concepts and operations generally map to ideas specified +in the ECMA262 Language Specification. The APIs have the following +properties: +- All N-API calls return a status code of type `napi_status`. This + status indicates whether the API call succeeded or failed. +- The API's return value is passed via an out parameter. +- All JavaScript values are abstracted behind an opaque type named + `napi_value`. +- In case of an error status code, additional information can be obtained + using `napi_get_last_error_info`. More information can be found in the error + handling section [Error Handling][]. + +The documentation for N-API is structured as follows: + +* [Basic N-API Data Types][] +* [Error Handling][] +* [Object Lifetime Management][] +* [Module Registration][] +* [Working with JavaScript Values][] +* [Working with JavaScript Values - Abstract Operations][] +* [Working with JavaScript Properties][] +* [Working with JavaScript Functions][] +* [Object Wrap][] +* [Simple Asynchronous Operations][] +* [Custom Asynchronous Operations][] +* [Promises][] +* [Script Execution][] + +The N-API is a C API that ensures ABI stability across Node.js versions +and different compiler levels. However, we also understand that a C++ +API can be easier to use in many cases. To support these cases we expect +there to be one or more C++ wrapper modules that provide an inlineable C++ +API. Binaries built with these wrapper modules will depend on the symbols +for the N-API C based functions exported by Node.js. These wrappers are not +part of N-API, nor will they be maintained as part of Node.js. One such +example is: [node-api](https://github.com/nodejs/node-api). + +In order to use the N-API functions, include the file +[node_api.h](https://github.com/nodejs/node/blob/master/src/node_api.h) +which is located in the src directory in the node development tree. +For example: +```C +#include +``` + +## Basic N-API Data Types + +N-API exposes the following fundamental datatypes as abstractions that are +consumed by the various APIs. These APIs should be treated as opaque, +introspectable only with other N-API calls. + +### napi_status +Integral status code indicating the success or failure of a N-API call. +Currently, the following status codes are supported. +```C +typedef enum { + napi_ok, + napi_invalid_arg, + napi_object_expected, + napi_string_expected, + napi_name_expected, + napi_function_expected, + napi_number_expected, + napi_boolean_expected, + napi_array_expected, + napi_generic_failure, + napi_pending_exception, + napi_cancelled, + napi_status_last +} napi_status; +``` +If additional information is required upon an API returning a failed status, +it can be obtained by calling `napi_get_last_error_info`. + +### napi_extended_error_info +```C +typedef struct { + const char* error_message; + void* engine_reserved; + uint32_t engine_error_code; + napi_status error_code; +} napi_extended_error_info; +``` + +- `error_message`: UTF8-encoded string containing a VM-neutral description of + the error. +- `engine_reserved`: Reserved for VM-specific error details. This is currently + not implemented for any VM. +- `engine_error_code`: VM-specific error code. This is currently + not implemented for any VM. +- `error_code`: The N-API status code that originated with the last error. + +See the [Error Handling][] section for additional information. + +### napi_env +`napi_env` is used to represent a context that the underlying N-API +implementation can use to persist VM-specific state. This structure is passed +to native functions when they're invoked, and it must be passed back when +making N-API calls. Specifically, the same `napi_env` that was passed in when +the initial native function was called must be passed to any subsequent +nested N-API calls. Caching the `napi_env` for the purpose of general reuse is +not allowed. + +### napi_value +This is an opaque pointer that is used to represent a JavaScript value. + +### N-API Memory Management types +#### napi_handle_scope +This is an abstraction used to control and modify the lifetime of objects +created within a particular scope. In general, N-API values are created within +the context of a handle scope. When a native method is called from +JavaScript, a default handle scope will exist. If the user does not explicitly +create a new handle scope, N-API values will be created in the default handle +scope. For any invocations of code outside the execution of a native method +(for instance, during a libuv callback invocation), the module is required to +create a scope before invoking any functions that can result in the creation +of JavaScript values. + +Handle scopes are created using [`napi_open_handle_scope`][] and are destroyed +using [`napi_close_handle_scope`][]. Closing the scope can indicate to the GC that +all `napi_value`s created during the lifetime of the handle scope are no longer +referenced from the current stack frame. + +For more details, review the [Object Lifetime Management][]. + +#### napi_escapable_handle_scope +Escapable handle scopes are a special type of handle scope to return values +created within a particular handle scope to a parent scope. + +#### napi_ref +This is the abstraction to use to reference a `napi_value`. This allows for +users to manage the lifetimes of JavaScript values, including defining their +minimum lifetimes explicitly. + +For more details, review the [Object Lifetime Management][]. + +### N-API Callback types +#### napi_callback_info +Opaque datatype that is passed to a callback function. It can be used for +getting additional information about the context in which the callback was +invoked. + +#### napi_callback +Function pointer type for user-provided native functions which are to be +exposed to JavaScript via N-API. Callback functions should satisfy the +following signature: +```C +typedef napi_value (*napi_callback)(napi_env, napi_callback_info); +``` + +#### napi_finalize +Function pointer type for add-on provided functions that allow the user to be +notified when externally-owned data is ready to be cleaned up because the +object with which it was associated with, has been garbage-collected. The user +must provide a function satisfying the following signature which would get +called upon the object's collection. Currently, `napi_finalize` can be used for +finding out when objects that have external data are collected. + +```C +typedef void (*napi_finalize)(napi_env env, + void* finalize_data, + void* finalize_hint); +``` + + +#### napi_async_execute_callback +Function pointer used with functions that support asynchronous +operations. Callback functions must statisfy the following signature: + +```C +typedef void (*napi_async_execute_callback)(napi_env env, void* data); +``` + +#### napi_async_complete_callback +Function pointer used with functions that support asynchronous +operations. Callback functions must statisfy the following signature: + +```C +typedef void (*napi_async_complete_callback)(napi_env env, + napi_status status, + void* data); +``` + +## Error Handling +N-API uses both return values and JavaScript exceptions for error handling. +The following sections explain the approach for each case. + +### Return values +All of the N-API functions share the same error handling pattern. The +return type of all API functions is `napi_status`. + +The return value will be `napi_ok` if the request was successful and +no uncaught JavaScript exception was thrown. If an error occurred AND +an exception was thrown, the `napi_status` value for the error +will be returned. If an exception was thrown, and no error occurred, +`napi_pending_exception` will be returned. + +In cases where a return value other than `napi_ok` or +`napi_pending_exception` is returned, [`napi_is_exception_pending`][] +must be called to check if an exception is pending. +See the section on exceptions for more details. + +The full set of possible napi_status values is defined +in `napi_api_types.h`. + +The `napi_status` return value provides a VM-independent representation of +the error which occurred. In some cases it is useful to be able to get +more detailed information, including a string representing the error as well as +VM (engine)-specific information. + +In order to retrieve this information [`napi_get_last_error_info`][] +is provided which returns a `napi_extended_error_info` structure. +The format of the `napi_extended_error_info` structure is as follows: + +```C +typedef struct napi_extended_error_info { + const char* error_message; + void* engine_reserved; + uint32_t engine_error_code; + napi_status error_code; +}; +``` +- `error_message`: Textual representation of the error that occured. +- `engine_reserved`: Opaque handle reserved for engine use only. +- `engine_error_code`: VM specific error code. +- `error_code`: n-api status code for the last error. + +[`napi_get_last_error_info`][] returns the information for the last +N-API call that was made. + +**Note:** Do not rely on the content or format of any of the extended +information as it is not subject to SemVer and may change at any time. +It is intended only for logging purposes. + +#### napi_get_last_error_info + +```C +napi_status +napi_get_last_error_info(napi_env env, + const napi_extended_error_info** result); +``` +- `[in] env`: The environment that the API is invoked under. +- `[out] result`: The `napi_extended_error_info` structure with more +information about the error. + +Returns `napi_ok` if the API succeeded. + +This API retrieves a `napi_extended_error_info` structure with information +about the last error that occured. + +*Note*: The content of the `napi_extended_error_info` returned is only +valid up until an n-api function is called on the same `env`. + +*Note*: Do not rely on the content or format of any of the extended +information as it is not subject to SemVer and may change at any time. +It is intended only for logging purposes. + +This API can be called even if there is a pending JavaScript exception. + + +### Exceptions +Any N-API function call may result in a pending JavaScript exception. This is +obviously the case for any function that may cause the execution of +JavaScript, but N-API specifies that an exception may be pending +on return from any of the API functions. + +If the `napi_status` returned by a function is `napi_ok` then no +exception is pending and no additional action is required. If the +`napi_status` returned is anything other than `napi_ok` or +`napi_pending_exception`, in order to try to recover and continue +instead of simply returning immediately, [`napi_is_exception_pending`][] +must be called in order to determine if an exception is pending or not. + +When an exception is pending one of two approaches can be employed. + +The first approach is to do any appropriate cleanup and then return so that +execution will return to JavaScript. As part of the transition back to +JavaScript the exception will be thrown at the point in the JavaScript +code where the native method was invoked. The behavior of most N-API calls +is unspecified while an exception is pending, and many will simply return +`napi_pending_exception`, so it is important to do as little as possible +and then return to JavaScript where the exception can be handled. + +The second approach is to try to handle the exception. There will be cases +where the native code can catch the exception, take the appropriate action, +and then continue. This is only recommended in specific cases +where it is known that the exception can be safely handled. In these +cases [`napi_get_and_clear_last_exception`][] can be used to get and +clear the exception. On success, result will contain the handle to +the last JavaScript Object thrown. If it is determined, after +retrieving the exception, the exception cannot be handled after all +it can be re-thrown it with [`napi_throw`][] where error is the +JavaScript Error object to be thrown. + +The following utility functions are also available in case native code +needs to throw an exception or determine if a `napi_value` is an instance +of a JavaScript `Error` object: [`napi_throw_error`][], +[`napi_throw_type_error`][], [`napi_throw_range_error`][] and +[`napi_is_error`][]. + +The following utility functions are also available in case native +code needs to create an Error object: [`napi_create_error`][], +[`napi_create_type_error`][], and [`napi_create_range_error`][]. +where result is the napi_value that refers to the newly created +JavaScript Error object. + +The Node.js project is adding error codes to all of the errors +generated internally. The goal is for applications to use these +error codes for all error checking. The associated error messages +will remain, but will only be meant to be used for logging and +display with the expectation that the message can change without +SemVer applying. In order to support this model with N-API, both +in internal functionality and for module specific functionality +(as its good practice), the `throw_` and `create_` functions +take an optional code parameter which is the string for the code +to be added to the error object. If the optional parameter is NULL +then no code will be associated with the error. If a code is provided, +the name associated with the error is also updated to be: + +```text +originalName [code] +``` + +where originalName is the original name associated with the error +and code is the code that was provided. For example if the code +is 'ERR_ERROR_1' and a TypeError is being created the name will be: + +```text +TypeError [ERR_ERROR_1] +``` + +#### napi_throw + +```C +NODE_EXTERN napi_status napi_throw(napi_env env, napi_value error); +``` +- `[in] env`: The environment that the API is invoked under. +- `[in] error`: The `napi_value` for the Error to be thrown. + +Returns `napi_ok` if the API succeeded. + +This API throws the JavaScript Error provided. + + +#### napi_throw_error + +```C +NODE_EXTERN napi_status napi_throw_error(napi_env env, + const char* code, + const char* msg); +``` +- `[in] env`: The environment that the API is invoked under. +- `[in] code`: Optional error code to be set on the error. +- `[in] msg`: C string representing the text to be associated with +the error. + +Returns `napi_ok` if the API succeeded. + +This API throws a JavaScript Error with the text provided. + +#### napi_throw_type_error + +```C +NODE_EXTERN napi_status napi_throw_type_error(napi_env env, + const char* code, + const char* msg); +``` +- `[in] env`: The environment that the API is invoked under. +- `[in] code`: Optional error code to be set on the error. +- `[in] msg`: C string representing the text to be associated with +the error. + +Returns `napi_ok` if the API succeeded. + +This API throws a JavaScript TypeError with the text provided. + +#### napi_throw_range_error + +```C +NODE_EXTERN napi_status napi_throw_range_error(napi_env env, + const char* code, + const char* msg); +``` +- `[in] env`: The environment that the API is invoked under. +- `[in] code`: Optional error code to be set on the error. +- `[in] msg`: C string representing the text to be associated with +the error. + +Returns `napi_ok` if the API succeeded. + +This API throws a JavaScript RangeError with the text provided. + + +#### napi_is_error + +```C +NODE_EXTERN napi_status napi_is_error(napi_env env, + napi_value value, + bool* result); +``` +- `[in] env`: The environment that the API is invoked under. +- `[in] msg`: The `napi_value` to be checked. +- `[out] result`: Boolean value that is set to true if `napi_value` represents +an error, false otherwise. + +Returns `napi_ok` if the API succeeded. + +This API queries a `napi_value` to check if it represents an error object. + + +#### napi_create_error + +```C +NODE_EXTERN napi_status napi_create_error(napi_env env, + napi_value code, + napi_value msg, + napi_value* result); +``` +- `[in] env`: The environment that the API is invoked under. +- `[in] code`: Optional `napi_value` with the string for the error code to + be associated with the error. +- `[in] msg`: napi_value that references a JavaScript String to be +used as the message for the Error. +- `[out] result`: `napi_value` representing the error created. + +Returns `napi_ok` if the API succeeded. + +This API returns a JavaScript Error with the text provided. + +#### napi_create_type_error + +```C +NODE_EXTERN napi_status napi_create_type_error(napi_env env, + napi_value code, + napi_value msg, + napi_value* result); +``` +- `[in] env`: The environment that the API is invoked under. +- `[in] code`: Optional `napi_value` with the string for the error code to + be associated with the error. +- `[in] msg`: napi_value that references a JavaScript String to be +used as the message for the Error. +- `[out] result`: `napi_value` representing the error created. + +Returns `napi_ok` if the API succeeded. + +This API returns a JavaScript TypeError with the text provided. + + +#### napi_create_range_error + +```C +NODE_EXTERN napi_status napi_create_range_error(napi_env env, + napi_value code, + const char* msg, + napi_value* result); +``` +- `[in] env`: The environment that the API is invoked under. +- `[in] code`: Optional `napi_value` with the string for the error code to + be associated with the error. +- `[in] msg`: napi_value that references a JavaScript String to be +used as the message for the Error. +- `[out] result`: `napi_value` representing the error created. + +Returns `napi_ok` if the API succeeded. + +This API returns a JavaScript RangeError with the text provided. + +#### napi_get_and_clear_last_exception + +```C +napi_status napi_get_and_clear_last_exception(napi_env env, + napi_value* result); +``` + +- `[in] env`: The environment that the API is invoked under. +- `[out] result`: The exception if one is pending, NULL otherwise. + +Returns `napi_ok` if the API succeeded. + +This API returns true if an exception is pending. + +This API can be called even if there is a pending JavaScript exception. + +#### napi_is_exception_pending + +```C +napi_status napi_is_exception_pending(napi_env env, bool* result); +``` + +- `[in] env`: The environment that the API is invoked under. +- `[out] result`: Boolean value that is set to true if an exception is pending. + +Returns `napi_ok` if the API succeeded. + +This API returns true if an exception is pending. + +This API can be called even if there is a pending JavaScript exception. + +#### napi_fatal_exception + +```C +napi_status napi_fatal_exception(napi_env env, napi_value err); +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] err`: The error you want to pass to `uncaughtException`. + +Trigger an `uncaughtException` in JavaScript. Useful if an async +callback throws an exception with no way to recover. + +### Fatal Errors + +In the event of an unrecoverable error in a native module, a fatal error can be +thrown to immediately terminate the process. + +#### napi_fatal_error + +```C +NAPI_NO_RETURN void napi_fatal_error(const char* location, + size_t location_len, + const char* message, + size_t message_len); +``` + +- `[in] location`: Optional location at which the error occurred. +- `[in] location_len`: The length of the location in bytes, or +`NAPI_AUTO_LENGTH` if it is null-terminated. +- `[in] message`: The message associated with the error. +- `[in] message_len`: The length of the message in bytes, or +`NAPI_AUTO_LENGTH` if it is +null-terminated. + +The function call does not return, the process will be terminated. + +This API can be called even if there is a pending JavaScript exception. + +## Object Lifetime management + +As N-API calls are made, handles to objects in the heap for the underlying +VM may be returned as `napi_values`. These handles must hold the +objects 'live' until they are no longer required by the native code, +otherwise the objects could be collected before the native code was +finished using them. + +As object handles are returned they are associated with a +'scope'. The lifespan for the default scope is tied to the lifespan +of the native method call. The result is that, by default, handles +remain valid and the objects associated with these handles will be +held live for the lifespan of the native method call. + +In many cases, however, it is necessary that the handles remain valid for +either a shorter or longer lifespan than that of the native method. +The sections which follow describe the N-API functions than can be used +to change the handle lifespan from the default. + +### Making handle lifespan shorter than that of the native method +It is often necessary to make the lifespan of handles shorter than +the lifespan of a native method. For example, consider a native method +that has a loop which iterates through the elements in a large array: + +```C +for (int i = 0; i < 1000000; i++) { + napi_value result; + napi_status status = napi_get_element(e, object, i, &result); + if (status != napi_ok) { + break; + } + // do something with element +} +``` + +This would result in a large number of handles being created, consuming +substantial resources. In addition, even though the native code could only +use the most recent handle, all of the associated objects would also be +kept alive since they all share the same scope. + +To handle this case, N-API provides the ability to establish a new 'scope' to +which newly created handles will be associated. Once those handles +are no longer required, the scope can be 'closed' and any handles associated +with the scope are invalidated. The methods available to open/close scopes are +[`napi_open_handle_scope`][] and [`napi_close_handle_scope`][]. + +N-API only supports a single nested hierarchy of scopes. There is only one +active scope at any time, and all new handles will be associated with that +scope while it is active. Scopes must be closed in the reverse order from +which they are opened. In addition, all scopes created within a native method +must be closed before returning from that method. + +Taking the earlier example, adding calls to [`napi_open_handle_scope`][] and +[`napi_close_handle_scope`][] would ensure that at most a single handle +is valid throughout the execution of the loop: + +```C +for (int i = 0; i < 1000000; i++) { + napi_handle_scope scope; + napi_status status = napi_open_handle_scope(env, &scope); + if (status != napi_ok) { + break; + } + napi_value result; + status = napi_get_element(e, object, i, &result); + if (status != napi_ok) { + break; + } + // do something with element + status = napi_close_handle_scope(env, scope); + if (status != napi_ok) { + break; + } +} +``` + +When nesting scopes, there are cases where a handle from an +inner scope needs to live beyond the lifespan of that scope. N-API supports an +'escapable scope' in order to support this case. An escapable scope +allows one handle to be 'promoted' so that it 'escapes' the +current scope and the lifespan of the handle changes from the current +scope to that of the outer scope. + +The methods available to open/close escapable scopes are +[`napi_open_escapable_handle_scope`][] and [`napi_close_escapable_handle_scope`][]. + +The request to promote a handle is made through [`napi_escape_handle`][] which +can only be called once. + +#### napi_open_handle_scope + +```C +NODE_EXTERN napi_status napi_open_handle_scope(napi_env env, + napi_handle_scope* result); +``` +- `[in] env`: The environment that the API is invoked under. +- `[out] result`: `napi_value` representing the new scope. + +Returns `napi_ok` if the API succeeded. + +This API open a new scope. + +#### napi_close_handle_scope + +```C +NODE_EXTERN napi_status napi_close_handle_scope(napi_env env, + napi_handle_scope scope); +``` +- `[in] env`: The environment that the API is invoked under. +- `[in] scope`: `napi_value` representing the scope to be closed. + +Returns `napi_ok` if the API succeeded. + +This API closes the scope passed in. Scopes must be closed in the +reverse order from which they were created. + +This API can be called even if there is a pending JavaScript exception. + +#### napi_open_escapable_handle_scope + +```C +NODE_EXTERN napi_status + napi_open_escapable_handle_scope(napi_env env, + napi_handle_scope* result); +``` +- `[in] env`: The environment that the API is invoked under. +- `[out] result`: `napi_value` representing the new scope. + +Returns `napi_ok` if the API succeeded. + +This API open a new scope from which one object can be promoted +to the outer scope. + +#### napi_close_escapable_handle_scope + +```C +NODE_EXTERN napi_status + napi_close_escapable_handle_scope(napi_env env, + napi_handle_scope scope); +``` +- `[in] env`: The environment that the API is invoked under. +- `[in] scope`: `napi_value` representing the scope to be closed. + +Returns `napi_ok` if the API succeeded. + +This API closes the scope passed in. Scopes must be closed in the +reverse order from which they were created. + +This API can be called even if there is a pending JavaScript exception. + +#### napi_escape_handle + +```C +napi_status napi_escape_handle(napi_env env, + napi_escapable_handle_scope scope, + napi_value escapee, + napi_value* result); +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] scope`: `napi_value` representing the current scope. +- `[in] escapee`: `napi_value` representing the JavaScript Object to be escaped. +- `[out] result`: `napi_value` representing the handle to the escaped +Object in the outer scope. + +Returns `napi_ok` if the API succeeded. + +This API promotes the handle to the JavaScript object so that it is valid +for the lifetime of the outer scope. It can only be called once per scope. +If it is called more than once an error will be returned. + +This API can be called even if there is a pending JavaScript exception. + +### References to objects with a lifespan longer than that of the native method + +In some cases an addon will need to be able to create and reference objects +with a lifespan longer than that of a single native method invocation. For +example, to create a constructor and later use that constructor +in a request to creates instances, it must be possible to reference +the constructor object across many different instance creation requests. This +would not be possible with a normal handle returned as a `napi_value` as +described in the earlier section. The lifespan of a normal handle is +managed by scopes and all scopes must be closed before the end of a native +method. + +N-API provides methods to create persistent references to an object. +Each persistent reference has an associated count with a value of 0 +or higher. The count determines if the reference will keep +the corresponding object live. References with a count of 0 do not +prevent the object from being collected and are often called 'weak' +references. Any count greater than 0 will prevent the object +from being collected. + +References can be created with an initial reference count. The count can +then be modified through [`napi_reference_ref`][] and +[`napi_reference_unref`][]. If an object is collected while the count +for a reference is 0, all subsequent calls to +get the object associated with the reference [`napi_get_reference_value`][] +will return NULL for the returned `napi_value`. An attempt to call +[`napi_reference_ref`][] for a reference whose object has been collected +will result in an error. + +References must be deleted once they are no longer required by the addon. When +a reference is deleted it will no longer prevent the corresponding object from +being collected. Failure to delete a persistent reference will result in +a 'memory leak' with both the native memory for the persistent reference and +the corresponding object on the heap being retained forever. + +There can be multiple persistent references created which refer to the same +object, each of which will either keep the object live or not based on its +individual count. + +#### napi_create_reference + +```C +NODE_EXTERN napi_status napi_create_reference(napi_env env, + napi_value value, + int initial_refcount, + napi_ref* result); +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] value`: `napi_value` representing the Object to which we want +a reference. +- `[in] initial_refcount`: Initial reference count for the new reference. +- `[out] result`: `napi_ref` pointing to the new reference. + +Returns `napi_ok` if the API succeeded. + +This API create a new reference with the specified reference count +to the Object passed in. + +#### napi_delete_reference + +```C +NODE_EXTERN napi_status napi_delete_reference(napi_env env, napi_ref ref); +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] ref`: `napi_ref` to be deleted. + +Returns `napi_ok` if the API succeeded. + +This API deletes the reference passed in. + +This API can be called even if there is a pending JavaScript exception. + +#### napi_reference_ref + +```C +NODE_EXTERN napi_status napi_reference_ref(napi_env env, + napi_ref ref, + int* result); +``` +- `[in] env`: The environment that the API is invoked under. +- `[in] ref`: `napi_ref` for which the reference count will be incremented. +- `[out] result`: The new reference count. + +Returns `napi_ok` if the API succeeded. + +This API increments the reference count for the reference +passed in and returns the resulting reference count. + +#### napi_reference_unref + +```C +NODE_EXTERN napi_status napi_reference_unref(napi_env env, + napi_ref ref, + int* result); +``` +- `[in] env`: The environment that the API is invoked under. +- `[in] ref`: `napi_ref` for which the reference count will be decremented. +- `[out] result`: The new reference count. + +Returns `napi_ok` if the API succeeded. + +This API decrements the reference count for the reference +passed in and returns the resulting reference count. + +#### napi_get_reference_value + +```C +NODE_EXTERN napi_status napi_get_reference_value(napi_env env, + napi_ref ref, + napi_value* result); +``` + +the `napi_value passed` in or out of these methods is a handle to the +object to which the reference is related. +- `[in] env`: The environment that the API is invoked under. +- `[in] ref`: `napi_ref` for which we requesting the corresponding Object. +- `[out] result`: The `napi_value` for the Object referenced by the +`napi_ref`. + +Returns `napi_ok` if the API succeeded. + +If still valid, this API returns the `napi_value` representing the +JavaScript Object associated with the `napi_ref`. Otherwise, result +will be NULL. + +## Module registration +N-API modules are registered in a manner similar to other modules +except that instead of using the `NODE_MODULE` macro the following +is used: + +```C +NAPI_MODULE(addon, Init) +``` + +The next difference is the signature for the `Init` method. For a N-API +module it is as follows: + +```C +napi_value Init(napi_env env, napi_value exports); +``` + +The return value from `Init` is treated as the `exports` object for the module. +The `Init` method is passed an empty object via the `exports` parameter as a +convenience. If `Init` returns NULL, the parameter passed as `exports` is +exported by the module. N-API modules cannot modify the `module` object but can +specify anything as the `exports` property of the module. + +For example, to add the method `hello` as a function so that it can be called +as a method provided by the addon: + +```C +napi_value Init(napi_env env, napi_value exports) { + napi_status status; + napi_property_descriptor desc = + {"hello", Method, 0, 0, 0, napi_default, 0}; + if (status != napi_ok) return NULL; + status = napi_define_properties(env, exports, 1, &desc); + if (status != napi_ok) return NULL; + return exports; +} +``` + +For example, to set a function to be returned by the `require()` for the addon: + +```C +napi_value Init(napi_env env, napi_value exports) { + napi_value method; + napi_status status; + status = napi_create_function(env, "exports", NAPI_AUTO_LENGTH, Method, NULL, &method); + if (status != napi_ok) return NULL; + return method; +} +``` + +For example, to define a class so that new instances can be created +(often used with [Object Wrap][]): + +```C +// NOTE: partial example, not all referenced code is included +napi_value Init(napi_env env, napi_value exports) { + napi_status status; + napi_property_descriptor properties[] = { + { "value", NULL, GetValue, SetValue, 0, napi_default, 0 }, + DECLARE_NAPI_METHOD("plusOne", PlusOne), + DECLARE_NAPI_METHOD("multiply", Multiply), + }; + + napi_value cons; + status = + napi_define_class(env, "MyObject", New, NULL, 3, properties, &cons); + if (status != napi_ok) return NULL; + + status = napi_create_reference(env, cons, 1, &constructor); + if (status != napi_ok) return NULL; + + status = napi_set_named_property(env, exports, "MyObject", cons); + if (status != napi_ok) return NULL; + + return exports; +} +``` + +For more details on setting properties on objects, see the section on +[Working with JavaScript Properties][]. + +For more details on building addon modules in general, refer to the existing API + +## Working with JavaScript Values +N-API exposes a set of APIs to create all types of JavaScript values. +Some of these types are documented under +[Section 6](https://tc39.github.io/ecma262/#sec-ecmascript-data-types-and-values) +of the [ECMAScript Language Specification][]. + +Fundamentally, these APIs are used to do one of the following: +1. Create a new JavaScript object +2. Convert from a primitive C type to an N-API value +3. Convert from N-API value to a primitive C type +4. Get global instances including `undefined` and `null` + +N-API values are represented by the type `napi_value`. +Any N-API call that requires a JavaScript value takes in a `napi_value`. +In some cases, the API does check the type of the `napi_value` up-front. +However, for better performance, it's better for the caller to make sure that +the `napi_value` in question is of the JavaScript type expected by the API. + +### Enum types +#### napi_valuetype +```C +typedef enum { + // ES6 types (corresponds to typeof) + napi_undefined, + napi_null, + napi_boolean, + napi_number, + napi_string, + napi_symbol, + napi_object, + napi_function, + napi_external, +} napi_valuetype; +``` + +Describes the type of a `napi_value`. This generally corresponds to the types +described in +[Section 6.1](https://tc39.github.io/ecma262/#sec-ecmascript-language-types) of +the ECMAScript Language Specification. +In addition to types in that section, `napi_valuetype` can also represent +Functions and Objects with external data. + +#### napi_typedarray_type +```C +typedef enum { + napi_int8_array, + napi_uint8_array, + napi_uint8_clamped_array, + napi_int16_array, + napi_uint16_array, + napi_int32_array, + napi_uint32_array, + napi_float32_array, + napi_float64_array, +} napi_typedarray_type; +``` + +This represents the underlying binary scalar datatype of the TypedArray. +Elements of this enum correspond to +[Section 22.2](https://tc39.github.io/ecma262/#sec-typedarray-objects) +of the [ECMAScript Language Specification][]. + +### Object Creation Functions +#### napi_create_array + +```C +napi_status napi_create_array(napi_env env, napi_value* result) +``` + +- `[in] env`: The environment that the N-API call is invoked under. +- `[out] result`: A `napi_value` representing a JavaScript Array. + +Returns `napi_ok` if the API succeeded. + +This API returns an N-API value corresponding to a JavaScript Array type. +JavaScript arrays are described in +[Section 22.1](https://tc39.github.io/ecma262/#sec-array-objects) of the +ECMAScript Language Specification. + +#### napi_create_array_with_length + +```C +napi_status napi_create_array_with_length(napi_env env, + size_t length, + napi_value* result) +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] length`: The initial length of the Array. +- `[out] result`: A `napi_value` representing a JavaScript Array. + +Returns `napi_ok` if the API succeeded. + +This API returns an N-API value corresponding to a JavaScript Array type. +The Array's length property is set to the passed-in length parameter. +However, the underlying buffer is not guaranteed to be pre-allocated by the VM +when the array is created - that behavior is left to the underlying VM +implementation. +If the buffer must be a contiguous block of memory that can be +directly read and/or written via C, consider using +[`napi_create_external_arraybuffer`][]. + +JavaScript arrays are described in +[Section 22.1](https://tc39.github.io/ecma262/#sec-array-objects) of the +ECMAScript Language Specification. + +#### napi_create_arraybuffer + +```C +napi_status napi_create_arraybuffer(napi_env env, + size_t byte_length, + void** data, + napi_value* result) +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] length`: The length in bytes of the array buffer to create. +- `[out] data`: Pointer to the underlying byte buffer of the ArrayBuffer. +- `[out] result`: A `napi_value` representing a JavaScript ArrayBuffer. + +Returns `napi_ok` if the API succeeded. + +This API returns an N-API value corresponding to a JavaScript ArrayBuffer. +ArrayBuffers are used to represent fixed-length binary data buffers. They are +normally used as a backing-buffer for TypedArray objects. +The ArrayBuffer allocated will have an underlying byte buffer whose size is +determined by the `length` parameter that's passed in. +The underlying buffer is optionally returned back to the caller in case the +caller wants to directly manipulate the buffer. This buffer can only be +written to directly from native code. To write to this buffer from JavaScript, +a typed array or DataView object would need to be created. + +JavaScript ArrayBuffer objects are described in +[Section 24.1](https://tc39.github.io/ecma262/#sec-arraybuffer-objects) +of the ECMAScript Language Specification. + +#### napi_create_buffer + +```C +napi_status napi_create_buffer(napi_env env, + size_t size, + void** data, + napi_value* result) +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] size`: Size in bytes of the underlying buffer. +- `[out] data`: Raw pointer to the underlying buffer. +- `[out] result`: A `napi_value` representing a `node::Buffer`. + +Returns `napi_ok` if the API succeeded. + +This API allocates a `node::Buffer` object. While this is still a +fully-supported data structure, in most cases using a TypedArray will suffice. + +#### napi_create_buffer_copy + +```C +napi_status napi_create_buffer_copy(napi_env env, + size_t length, + const void* data, + void** result_data, + napi_value* result) +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] size`: Size in bytes of the input buffer (should be the same as the + size of the new buffer). +- `[in] data`: Raw pointer to the underlying buffer to copy from. +- `[out] result_data`: Pointer to the new Buffer's underlying data buffer. +- `[out] result`: A `napi_value` representing a `node::Buffer`. + +Returns `napi_ok` if the API succeeded. + +This API allocates a `node::Buffer` object and initializes it with data copied +from the passed-in buffer. While this is still a fully-supported data +structure, in most cases using a TypedArray will suffice. + +#### napi_create_external + +```C +napi_status napi_create_external(napi_env env, + void* data, + napi_finalize finalize_cb, + void* finalize_hint, + napi_value* result) +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] data`: Raw pointer to the external data. +- `[in] finalize_cb`: Optional callback to call when the external value +is being collected. +- `[in] finalize_hint`: Optional hint to pass to the finalize callback +during collection. +- `[out] result`: A `napi_value` representing an external value. + +Returns `napi_ok` if the API succeeded. + +This API allocates a JavaScript value with external data attached to it. This +is used to pass external data through JavaScript code, so it can be retrieved +later by native code. The API allows the caller to pass in a finalize callback, +in case the underlying native resource needs to be cleaned up when the external +JavaScript value gets collected. + +*Note*: The created value is not an object, and therefore does not support +additional properties. It is considered a distinct value type: calling +`napi_typeof()` with an external value yields `napi_external`. + +#### napi_create_external_arraybuffer + +```C +napi_status +napi_create_external_arraybuffer(napi_env env, + void* external_data, + size_t byte_length, + napi_finalize finalize_cb, + void* finalize_hint, + napi_value* result) +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] external_data`: Pointer to the underlying byte buffer of the +ArrayBuffer. +- `[in] byte_length`: The length in bytes of the underlying buffer. +- `[in] finalize_cb`: Optional callback to call when the ArrayBuffer is +being collected. +- `[in] finalize_hint`: Optional hint to pass to the finalize callback +during collection. +- `[out] result`: A `napi_value` representing a JavaScript ArrayBuffer. + +Returns `napi_ok` if the API succeeded. + +This API returns an N-API value corresponding to a JavaScript ArrayBuffer. +The underlying byte buffer of the ArrayBuffer is externally allocated and +managed. The caller must ensure that the byte buffer remains valid until the +finalize callback is called. + +JavaScript ArrayBuffers are described in +[Section 24.1](https://tc39.github.io/ecma262/#sec-arraybuffer-objects) +of the ECMAScript Language Specification. + +#### napi_create_external_buffer + +```C +napi_status napi_create_external_buffer(napi_env env, + size_t length, + void* data, + napi_finalize finalize_cb, + void* finalize_hint, + napi_value* result) +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] length`: Size in bytes of the input buffer (should be the same as +the size of the new buffer). +- `[in] data`: Raw pointer to the underlying buffer to copy from. +- `[in] finalize_cb`: Optional callback to call when the ArrayBuffer is +being collected. +- `[in] finalize_hint`: Optional hint to pass to the finalize callback +during collection. +- `[out] result`: A `napi_value` representing a `node::Buffer`. + +Returns `napi_ok` if the API succeeded. + +This API allocates a `node::Buffer` object and initializes it with data +backed by the passed in buffer. While this is still a fully-supported data +structure, in most cases using a TypedArray will suffice. + +**Note:** For Node.js >=4 `Buffers` are Uint8Arrays. + +#### napi_create_function + +```C +napi_status napi_create_function(napi_env env, + const char* utf8name, + size_t length, + napi_callback cb, + void* data, + napi_value* result) +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] utf8name`: A string representing the name of the function encoded as +UTF8. +- `[in] length`: The length of the utf8name in bytes, or +`NAPI_AUTO_LENGTH` if it is null-terminated. +- `[in] cb`: A function pointer to the native function to be invoked when the +created function is invoked from JavaScript. +- `[in] data`: Optional arbitrary context data to be passed into the native +function when it is invoked. +- `[out] result`: A `napi_value` representing a JavaScript function. + +Returns `napi_ok` if the API succeeded. + +This API returns an N-API value corresponding to a JavaScript Function object. +It's used to wrap native functions so that they can be invoked from JavaScript. + +JavaScript Functions are described in +[Section 19.2](https://tc39.github.io/ecma262/#sec-function-objects) +of the ECMAScript Language Specification. + +#### napi_create_object + +```C +napi_status napi_create_object(napi_env env, napi_value* result) +``` + +- `[in] env`: The environment that the API is invoked under. +- `[out] result`: A `napi_value` representing a JavaScript Object. + +Returns `napi_ok` if the API succeeded. + +This API allocates a default JavaScript Object. +It is the equivalent of doing `new Object()` in JavaScript. + +The JavaScript Object type is described in +[Section 6.1.7](https://tc39.github.io/ecma262/#sec-object-type) of the +ECMAScript Language Specification. + +#### napi_create_symbol + +```C +napi_status napi_create_symbol(napi_env env, + napi_value description, + napi_value* result) +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] description`: Optional napi_value which refers to a JavaScript +String to be set as the description for the symbol. +- `[out] result`: A `napi_value` representing a JavaScript Symbol. + +Returns `napi_ok` if the API succeeded. + +This API creates a JavaScript Symbol object from a UTF8-encoded C string + +The JavaScript Symbol type is described in +[Section 19.4](https://tc39.github.io/ecma262/#sec-symbol-objects) +of the ECMAScript Language Specification. + +#### napi_create_typedarray + +```C +napi_status napi_create_typedarray(napi_env env, + napi_typedarray_type type, + size_t length, + napi_value arraybuffer, + size_t byte_offset, + napi_value* result) +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] type`: Scalar datatype of the elements within the TypedArray. +- `[in] length`: Number of elements in the TypedArray. +- `[in] arraybuffer`: ArrayBuffer underlying the typed array. +- `[in] byte_offset`: The byte offset within the ArrayBuffer from which to +start projecting the TypedArray. +- `[out] result`: A `napi_value` representing a JavaScript TypedArray. + +Returns `napi_ok` if the API succeeded. + +This API creates a JavaScript TypedArray object over an existing ArrayBuffer. +TypedArray objects provide an array-like view over an underlying data buffer +where each element has the same underlying binary scalar datatype. + +It's required that (length * size_of_element) + byte_offset should +be <= the size in bytes of the array passed in. If not, a RangeError exception is +raised. + +JavaScript TypedArray Objects are described in +[Section 22.2](https://tc39.github.io/ecma262/#sec-typedarray-objects) +of the ECMAScript Language Specification. + + +#### napi_create_dataview + + +```C +napi_status napi_create_dataview(napi_env env, + size_t byte_length, + napi_value arraybuffer, + size_t byte_offset, + napi_value* result) + +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] length`: Number of elements in the DataView. +- `[in] arraybuffer`: ArrayBuffer underlying the DataView. +- `[in] byte_offset`: The byte offset within the ArrayBuffer from which to + start projecting the DataView. +- `[out] result`: A `napi_value` representing a JavaScript DataView. + +Returns `napi_ok` if the API succeeded. + +This API creates a JavaScript DataView object over an existing ArrayBuffer. +DataView objects provide an array-like view over an underlying data buffer, +but one which allows items of different size and type in the ArrayBuffer. + +It is required that `byte_length + byte_offset` is less than or equal to the +size in bytes of the array passed in. If not, a RangeError exception is raised. + +JavaScript DataView Objects are described in +[Section 24.3][] of the ECMAScript Language Specification. + +### Functions to convert from C types to N-API +#### napi_create_int32 + +```C +napi_status napi_create_int32(napi_env env, int32_t value, napi_value* result) +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] value`: Integer value to be represented in JavaScript. +- `[out] result`: A `napi_value` representing a JavaScript Number. + +Returns `napi_ok` if the API succeeded. + +This API is used to convert from the C `int32_t` type to the JavaScript +Number type. + +The JavaScript Number type is described in +[Section 6.1.6](https://tc39.github.io/ecma262/#sec-ecmascript-language-types-number-type) +of the ECMAScript Language Specification. + +#### napi_create_uint32 + +```C +napi_status napi_create_uint32(napi_env env, uint32_t value, napi_value* result) +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] value`: Unsigned integer value to be represented in JavaScript. +- `[out] result`: A `napi_value` representing a JavaScript Number. + +Returns `napi_ok` if the API succeeded. + +This API is used to convert from the C `uint32_t` type to the JavaScript +Number type. + +The JavaScript Number type is described in +[Section 6.1.6](https://tc39.github.io/ecma262/#sec-ecmascript-language-types-number-type) +of the ECMAScript Language Specification. + +#### napi_create_int64 + +```C +napi_status napi_create_int64(napi_env env, int64_t value, napi_value* result) +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] value`: Integer value to be represented in JavaScript. +- `[out] result`: A `napi_value` representing a JavaScript Number. + +Returns `napi_ok` if the API succeeded. + +This API is used to convert from the C `int64_t` type to the JavaScript +Number type. + +The JavaScript Number type is described in +[Section 6.1.6](https://tc39.github.io/ecma262/#sec-ecmascript-language-types-number-type) +of the ECMAScript Language Specification. Note the complete range of `int64_t` +cannot be represented with full precision in JavaScript. Integer values +outside the range of +[`Number.MIN_SAFE_INTEGER`](https://tc39.github.io/ecma262/#sec-number.min_safe_integer) +-(2^53 - 1) - +[`Number.MAX_SAFE_INTEGER`](https://tc39.github.io/ecma262/#sec-number.max_safe_integer) +(2^53 - 1) will lose precision. + +#### napi_create_double + +```C +napi_status napi_create_double(napi_env env, double value, napi_value* result) +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] value`: Double-precision value to be represented in JavaScript. +- `[out] result`: A `napi_value` representing a JavaScript Number. + +Returns `napi_ok` if the API succeeded. + +This API is used to convert from the C `double` type to the JavaScript +Number type. + +The JavaScript Number type is described in +[Section 6.1.6](https://tc39.github.io/ecma262/#sec-ecmascript-language-types-number-type) +of the ECMAScript Language Specification. + +#### napi_create_string_latin1 + +```C +napi_status napi_create_string_latin1(napi_env env, + const char* str, + size_t length, + napi_value* result); +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] str`: Character buffer representing a ISO-8859-1-encoded string. +- `[in] length`: The length of the string in bytes, or +`NAPI_AUTO_LENGTH` if it is null-terminated. +- `[out] result`: A `napi_value` representing a JavaScript String. + +Returns `napi_ok` if the API succeeded. + +This API creates a JavaScript String object from a ISO-8859-1-encoded C string. + +The JavaScript String type is described in +[Section 6.1.4](https://tc39.github.io/ecma262/#sec-ecmascript-language-types-string-type) +of the ECMAScript Language Specification. + +#### napi_create_string_utf16 + +```C +napi_status napi_create_string_utf16(napi_env env, + const char16_t* str, + size_t length, + napi_value* result) +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] str`: Character buffer representing a UTF16-LE-encoded string. +- `[in] length`: The length of the string in two-byte code units, or +`NAPI_AUTO_LENGTH` if it is null-terminated. +- `[out] result`: A `napi_value` representing a JavaScript String. + +Returns `napi_ok` if the API succeeded. + +This API creates a JavaScript String object from a UTF16-LE-encoded C string + +The JavaScript String type is described in +[Section 6.1.4](https://tc39.github.io/ecma262/#sec-ecmascript-language-types-string-type) +of the ECMAScript Language Specification. + +#### napi_create_string_utf8 + +```C +napi_status napi_create_string_utf8(napi_env env, + const char* str, + size_t length, + napi_value* result) +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] str`: Character buffer representing a UTF8-encoded string. +- `[in] length`: The length of the string in bytes, or `NAPI_AUTO_LENGTH` +if it is null-terminated. +- `[out] result`: A `napi_value` representing a JavaScript String. + +Returns `napi_ok` if the API succeeded. + +This API creates a JavaScript String object from a UTF8-encoded C string + +The JavaScript String type is described in +[Section 6.1.4](https://tc39.github.io/ecma262/#sec-ecmascript-language-types-string-type) +of the ECMAScript Language Specification. + +### Functions to convert from N-API to C types +#### napi_get_array_length + +```C +napi_status napi_get_array_length(napi_env env, + napi_value value, + uint32_t* result) +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] value`: `napi_value` representing the JavaScript Array whose length is +being queried. +- `[out] result`: `uint32` representing length of the array. + +Returns `napi_ok` if the API succeeded. + +This API returns the length of an array. + +Array length is described in +[Section 22.1.4.1](https://tc39.github.io/ecma262/#sec-properties-of-array-instances-length) +of the ECMAScript Language Specification. + +#### napi_get_arraybuffer_info + +```C +napi_status napi_get_arraybuffer_info(napi_env env, + napi_value arraybuffer, + void** data, + size_t* byte_length) +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] arraybuffer`: `napi_value` representing the ArrayBuffer being queried. +- `[out] data`: The underlying data buffer of the ArrayBuffer. +- `[out] byte_length`: Length in bytes of the underlying data buffer. + +Returns `napi_ok` if the API succeeded. + +This API is used to retrieve the underlying data buffer of an ArrayBuffer and +its length. + +*WARNING*: Use caution while using this API. The lifetime of the underlying data +buffer is managed by the ArrayBuffer even after it's returned. A +possible safe way to use this API is in conjunction with [`napi_create_reference`][], +which can be used to guarantee control over the lifetime of the +ArrayBuffer. It's also safe to use the returned data buffer within the same +callback as long as there are no calls to other APIs that might trigger a GC. + +#### napi_get_buffer_info + +```C +napi_status napi_get_buffer_info(napi_env env, + napi_value value, + void** data, + size_t* length) +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] value`: `napi_value` representing the `node::Buffer` being queried. +- `[out] data`: The underlying data buffer of the `node::Buffer`. +- `[out] length`: Length in bytes of the underlying data buffer. + +Returns `napi_ok` if the API succeeded. + +This API is used to retrieve the underlying data buffer of a `node::Buffer` +and it's length. + +*Warning*: Use caution while using this API since the underlying data buffer's +lifetime is not guaranteed if it's managed by the VM. + +#### napi_get_prototype + +```C +napi_status napi_get_prototype(napi_env env, + napi_value object, + napi_value* result) +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] object`: `napi_value` representing JavaScript Object whose prototype +to return. This returns the equivalent of `Object.getPrototypeOf` (which is +not the same as the function's `prototype` property). +- `[out] result`: `napi_value` representing prototype of the given object. + +Returns `napi_ok` if the API succeeded. + +#### napi_get_typedarray_info + +```C +napi_status napi_get_typedarray_info(napi_env env, + napi_value typedarray, + napi_typedarray_type* type, + size_t* length, + void** data, + napi_value* arraybuffer, + size_t* byte_offset) +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] typedarray`: `napi_value` representing the TypedArray whose +properties to query. +- `[out] type`: Scalar datatype of the elements within the TypedArray. +- `[out] length`: Number of elements in the TypedArray. +- `[out] data`: The data buffer underlying the typed array. +- `[out] byte_offset`: The byte offset within the data buffer from which +to start projecting the TypedArray. + +Returns `napi_ok` if the API succeeded. + +This API returns various properties of a typed array. + +*Warning*: Use caution while using this API since the underlying data buffer +is managed by the VM + +#### napi_get_dataview_info + + +```C +napi_status napi_get_dataview_info(napi_env env, + napi_value dataview, + size_t* byte_length, + void** data, + napi_value* arraybuffer, + size_t* byte_offset) +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] dataview`: `napi_value` representing the DataView whose + properties to query. +- `[out] byte_length`: Number of bytes in the DataView. +- `[out] data`: The data buffer underlying the DataView. +- `[out] arraybuffer`: ArrayBuffer underlying the DataView. +- `[out] byte_offset`: The byte offset within the data buffer from which + to start projecting the DataView. + +Returns `napi_ok` if the API succeeded. + +This API returns various properties of a DataView. + +#### napi_get_value_bool + +```C +napi_status napi_get_value_bool(napi_env env, napi_value value, bool* result) +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] value`: `napi_value` representing JavaScript Boolean. +- `[out] result`: C boolean primitive equivalent of the given JavaScript +Boolean. + +Returns `napi_ok` if the API succeeded. If a non-boolean `napi_value` is +passed in it returns `napi_boolean_expected`. + +This API returns the C boolean primitive equivalent of the given JavaScript +Boolean. + +#### napi_get_value_double + +```C +napi_status napi_get_value_double(napi_env env, + napi_value value, + double* result) +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] value`: `napi_value` representing JavaScript Number. +- `[out] result`: C double primitive equivalent of the given JavaScript +Number. + +Returns `napi_ok` if the API succeeded. If a non-number `napi_value` is passed +in it returns `napi_number_expected`. + +This API returns the C double primitive equivalent of the given JavaScript +Number. + +#### napi_get_value_external + +```C +napi_status napi_get_value_external(napi_env env, + napi_value value, + void** result) +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] value`: `napi_value` representing JavaScript external value. +- `[out] result`: Pointer to the data wrapped by the JavaScript external value. + +Returns `napi_ok` if the API succeeded. If a non-external `napi_value` is +passed in it returns `napi_invalid_arg`. + +This API retrieves the external data pointer that was previously passed to +`napi_create_external()`. + +#### napi_get_value_int32 + +```C +napi_status napi_get_value_int32(napi_env env, + napi_value value, + int32_t* result) +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] value`: `napi_value` representing JavaScript Number. +- `[out] result`: C int32 primitive equivalent of the given JavaScript Number. + +Returns `napi_ok` if the API succeeded. If a non-number `napi_value` +is passed in `napi_number_expected`. + +This API returns the C int32 primitive equivalent +of the given JavaScript Number. + +If the number exceeds the range of the 32 bit integer, then the result is +truncated to the equivalent of the bottom 32 bits. This can result in a large +positive number becoming a negative number if the value is > 2^31 -1. + +Non-finite number values (NaN, positive infinity, or negative infinity) set the +result to zero. + +#### napi_get_value_int64 + +```C +napi_status napi_get_value_int64(napi_env env, + napi_value value, + int64_t* result) +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] value`: `napi_value` representing JavaScript Number. +- `[out] result`: C int64 primitive equivalent of the given JavaScript Number. + +Returns `napi_ok` if the API succeeded. If a non-number `napi_value` +is passed in it returns `napi_number_expected`. + +This API returns the C int64 primitive equivalent of the given JavaScript +Number. + +Number values outside the range of +[`Number.MIN_SAFE_INTEGER`](https://tc39.github.io/ecma262/#sec-number.min_safe_integer) +-(2^53 - 1) - +[`Number.MAX_SAFE_INTEGER`](https://tc39.github.io/ecma262/#sec-number.max_safe_integer) +(2^53 - 1) will lose precision. + +Non-finite number values (NaN, positive infinity, or negative infinity) set the +result to zero. + +#### napi_get_value_string_latin1 + +```C +napi_status napi_get_value_string_latin1(napi_env env, + napi_value value, + char* buf, + size_t bufsize, + size_t* result) +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] value`: `napi_value` representing JavaScript string. +- `[in] buf`: Buffer to write the ISO-8859-1-encoded string into. If NULL is +passed in, the length of the string (in bytes) is returned. +- `[in] bufsize`: Size of the destination buffer. When this value is +insufficient, the returned string will be truncated. +- `[out] result`: Number of bytes copied into the buffer, excluding the null +terminator. + +Returns `napi_ok` if the API succeeded. If a non-String `napi_value` +is passed in it returns `napi_string_expected`. + +This API returns the ISO-8859-1-encoded string corresponding the value passed +in. + +#### napi_get_value_string_utf8 + +```C +napi_status napi_get_value_string_utf8(napi_env env, + napi_value value, + char* buf, + size_t bufsize, + size_t* result) +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] value`: `napi_value` representing JavaScript string. +- `[in] buf`: Buffer to write the UTF8-encoded string into. If NULL is passed + in, the length of the string (in bytes) is returned. +- `[in] bufsize`: Size of the destination buffer. When this value is +insufficient, the returned string will be truncated. +- `[out] result`: Number of bytes copied into the buffer, excluding the null +terminator. + +Returns `napi_ok` if the API succeeded. If a non-String `napi_value` +is passed in it returns `napi_string_expected`. + +This API returns the UTF8-encoded string corresponding the value passed in. + +#### napi_get_value_string_utf16 + +```C +napi_status napi_get_value_string_utf16(napi_env env, + napi_value value, + char16_t* buf, + size_t bufsize, + size_t* result) +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] value`: `napi_value` representing JavaScript string. +- `[in] buf`: Buffer to write the UTF16-LE-encoded string into. If NULL is +passed in, the length of the string (in 2-byte code units) is returned. +- `[in] bufsize`: Size of the destination buffer. When this value is +insufficient, the returned string will be truncated. +- `[out] result`: Number of 2-byte code units copied into the buffer, excluding the null +terminator. + +Returns `napi_ok` if the API succeeded. If a non-String `napi_value` +is passed in it returns `napi_string_expected`. + +This API returns the UTF16-encoded string corresponding the value passed in. + +#### napi_get_value_uint32 + +```C +napi_status napi_get_value_uint32(napi_env env, + napi_value value, + uint32_t* result) +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] value`: `napi_value` representing JavaScript Number. +- `[out] result`: C primitive equivalent of the given `napi_value` as a +`uint32_t`. + +Returns `napi_ok` if the API succeeded. If a non-number `napi_value` +is passed in it returns `napi_number_expected`. + +This API returns the C primitive equivalent of the given `napi_value` as a +`uint32_t`. + +### Functions to get global instances +#### napi_get_boolean + +```C +napi_status napi_get_boolean(napi_env env, bool value, napi_value* result) +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] value`: The value of the boolean to retrieve. +- `[out] result`: `napi_value` representing JavaScript Boolean singleton to +retrieve. + +Returns `napi_ok` if the API succeeded. + +This API is used to return the JavaScript singleton object that is used to +represent the given boolean value + +#### napi_get_global + +```C +napi_status napi_get_global(napi_env env, napi_value* result) +``` + +- `[in] env`: The environment that the API is invoked under. +- `[out] result`: `napi_value` representing JavaScript Global Object. + +Returns `napi_ok` if the API succeeded. + +This API returns the global Object. + +#### napi_get_null + +```C +napi_status napi_get_null(napi_env env, napi_value* result) +``` + +- `[in] env`: The environment that the API is invoked under. +- `[out] result`: `napi_value` representing JavaScript Null Object. + +Returns `napi_ok` if the API succeeded. + +This API returns the null Object. + +#### napi_get_undefined + +```C +napi_status napi_get_undefined(napi_env env, napi_value* result) +``` + +- `[in] env`: The environment that the API is invoked under. +- `[out] result`: `napi_value` representing JavaScript Undefined value. + +Returns `napi_ok` if the API succeeded. + +This API returns the Undefined object. + +## Working with JavaScript Values - Abstract Operations + +N-API exposes a set of APIs to perform some abstract operations on JavaScript +values. Some of these operations are documented under +[Section 7](https://tc39.github.io/ecma262/#sec-abstract-operations) +of the [ECMAScript Language Specification](https://tc39.github.io/ecma262/). + +These APIs support doing one of the following: +1. Coerce JavaScript values to specific JavaScript types (such as Number or + String) +2. Check the type of a JavaScript value +3. Check for equality between two JavaScript values + +### napi_coerce_to_bool + +```C +napi_status napi_coerce_to_bool(napi_env env, + napi_value value, + napi_value* result) +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] value`: The JavaScript value to coerce. +- `[out] result`: `napi_value` representing the coerced JavaScript Boolean. + +Returns `napi_ok` if the API succeeded. + +This API implements the abstract operation ToBoolean as defined in +[Section 7.1.2](https://tc39.github.io/ecma262/#sec-toboolean) +of the ECMAScript Language Specification. +This API can be re-entrant if getters are defined on the passed-in Object. + +### napi_coerce_to_number + +```C +napi_status napi_coerce_to_number(napi_env env, + napi_value value, + napi_value* result) +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] value`: The JavaScript value to coerce. +- `[out] result`: `napi_value` representing the coerced JavaScript Number. + +Returns `napi_ok` if the API succeeded. + +This API implements the abstract operation ToNumber as defined in +[Section 7.1.3](https://tc39.github.io/ecma262/#sec-tonumber) +of the ECMAScript Language Specification. +This API can be re-entrant if getters are defined on the passed-in Object. + +### napi_coerce_to_object + +```C +napi_status napi_coerce_to_object(napi_env env, + napi_value value, + napi_value* result) +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] value`: The JavaScript value to coerce. +- `[out] result`: `napi_value` representing the coerced JavaScript Object. + +Returns `napi_ok` if the API succeeded. + +This API implements the abstract operation ToObject as defined in +[Section 7.1.13](https://tc39.github.io/ecma262/#sec-toobject) +of the ECMAScript Language Specification. +This API can be re-entrant if getters are defined on the passed-in Object. + +### napi_coerce_to_string + +```C +napi_status napi_coerce_to_string(napi_env env, + napi_value value, + napi_value* result) +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] value`: The JavaScript value to coerce. +- `[out] result`: `napi_value` representing the coerced JavaScript String. + +Returns `napi_ok` if the API succeeded. + +This API implements the abstract operation ToString as defined in +[Section 7.1.13](https://tc39.github.io/ecma262/#sec-tostring) +of the ECMAScript Language Specification. +This API can be re-entrant if getters are defined on the passed-in Object. + +### napi_typeof + +```C +napi_status napi_typeof(napi_env env, napi_value value, napi_valuetype* result) +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] value`: The JavaScript value whose type to query. +- `[out] result`: The type of the JavaScript value. + +Returns `napi_ok` if the API succeeded. +- `napi_invalid_arg` if the type of `value` is not a known ECMAScript type and + `value` is not an External value. + +This API represents behavior similar to invoking the `typeof` Operator on +the object as defined in [Section 12.5.5][] of the ECMAScript Language +Specification. However, it has support for detecting an External value. +If `value` has a type that is invalid, an error is returned. + +### napi_instanceof + +```C +napi_status napi_instanceof(napi_env env, + napi_value object, + napi_value constructor, + bool* result) +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] object`: The JavaScript value to check. +- `[in] constructor`: The JavaScript function object of the constructor +function to check against. +- `[out] result`: Boolean that is set to true if `object instanceof constructor` +is true. + +Returns `napi_ok` if the API succeeded. + +This API represents invoking the `instanceof` Operator on the object as +defined in +[Section 12.10.4](https://tc39.github.io/ecma262/#sec-instanceofoperator) +of the ECMAScript Language Specification. + +### napi_is_array + +```C +napi_status napi_is_array(napi_env env, napi_value value, bool* result) +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] value`: The JavaScript value to check. +- `[out] result`: Whether the given object is an array. + +Returns `napi_ok` if the API succeeded. + +This API represents invoking the `IsArray` operation on the object +as defined in [Section 7.2.2](https://tc39.github.io/ecma262/#sec-isarray) +of the ECMAScript Language Specification. + +### napi_is_arraybuffer + +```C +napi_status napi_is_arraybuffer(napi_env env, napi_value value, bool* result) +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] value`: The JavaScript value to check. +- `[out] result`: Whether the given object is an ArrayBuffer. + +Returns `napi_ok` if the API succeeded. + +This API checks if the Object passed in is an array buffer. + +### napi_is_buffer + +```C +napi_status napi_is_buffer(napi_env env, napi_value value, bool* result) +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] value`: The JavaScript value to check. +- `[out] result`: Whether the given `napi_value` represents a `node::Buffer` +object. + +Returns `napi_ok` if the API succeeded. + +This API checks if the Object passed in is a buffer. + +### napi_is_error + +```C +napi_status napi_is_error(napi_env env, napi_value value, bool* result) +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] value`: The JavaScript value to check. +- `[out] result`: Whether the given `napi_value` represents an Error object. + +Returns `napi_ok` if the API succeeded. + +This API checks if the Object passed in is an Error. + +### napi_is_typedarray + +```C +napi_status napi_is_typedarray(napi_env env, napi_value value, bool* result) +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] value`: The JavaScript value to check. +- `[out] result`: Whether the given `napi_value` represents a TypedArray. + +Returns `napi_ok` if the API succeeded. + +This API checks if the Object passed in is a typed array. + +### napi_is_dataview + + +```C +napi_status napi_is_dataview(napi_env env, napi_value value, bool* result) +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] value`: The JavaScript value to check. +- `[out] result`: Whether the given `napi_value` represents a DataView. + +Returns `napi_ok` if the API succeeded. + +This API checks if the Object passed in is a DataView. + +### napi_strict_equals + +```C +napi_status napi_strict_equals(napi_env env, + napi_value lhs, + napi_value rhs, + bool* result) +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] lhs`: The JavaScript value to check. +- `[in] rhs`: The JavaScript value to check against. +- `[out] result`: Whether the two `napi_value` objects are equal. + +Returns `napi_ok` if the API succeeded. + +This API represents the invocation of the Strict Equality algorithm as +defined in +[Section 7.2.14](https://tc39.github.io/ecma262/#sec-strict-equality-comparison) +of the ECMAScript Language Specification. + +## Working with JavaScript Properties + +N-API exposes a set of APIs to get and set properties on JavaScript +objects. Some of these types are documented under +[Section 7](https://tc39.github.io/ecma262/#sec-operations-on-objects) of the +[ECMAScript Language Specification](https://tc39.github.io/ecma262/). + +Properties in JavaScript are represented as a tuple of a key and a value. +Fundamentally, all property keys in N-API can be represented in one of the +following forms: +- Named: a simple UTF8-encoded string +- Integer-Indexed: an index value represented by `uint32_t` +- JavaScript value: these are represented in N-API by `napi_value`. This can +be a `napi_value` representing a String, Number or Symbol. + +N-API values are represented by the type `napi_value`. +Any N-API call that requires a JavaScript value takes in a `napi_value`. +However, it's the caller's responsibility to make sure that the +`napi_value` in question is of the JavaScript type expected by the API. + +The APIs documented in this section provide a simple interface to +get and set properties on arbitrary JavaScript objects represented by +`napi_value`. + +For instance, consider the following JavaScript code snippet: +```js +const obj = {}; +obj.myProp = 123; +``` +The equivalent can be done using N-API values with the following snippet: +```C +napi_status status = napi_generic_failure; + +// const obj = {} +napi_value obj, value; +status = napi_create_object(env, &obj); +if (status != napi_ok) return status; + +// Create a napi_value for 123 +status = napi_create_int32(env, 123, &value); +if (status != napi_ok) return status; + +// obj.myProp = 123 +status = napi_set_named_property(env, obj, "myProp", value); +if (status != napi_ok) return status; +``` + +Indexed properties can be set in a similar manner. Consider the following +JavaScript snippet: +```js +const arr = []; +arr[123] = 'hello'; +``` +The equivalent can be done using N-API values with the following snippet: +```C +napi_status status = napi_generic_failure; + +// const arr = []; +napi_value arr, value; +status = napi_create_array(env, &arr); +if (status != napi_ok) return status; + +// Create a napi_value for 'hello' +status = napi_create_string_utf8(env, "hello", NAPI_AUTO_LENGTH, &value); +if (status != napi_ok) return status; + +// arr[123] = 'hello'; +status = napi_set_element(env, arr, 123, value); +if (status != napi_ok) return status; +``` + +Properties can be retrieved using the APIs described in this section. +Consider the following JavaScript snippet: +```js +const arr = []; +const value = arr[123]; +``` + +The following is the approximate equivalent of the N-API counterpart: +```C +napi_status status = napi_generic_failure; + +// const arr = [] +napi_value arr, value; +status = napi_create_array(env, &arr); +if (status != napi_ok) return status; + +// const value = arr[123] +status = napi_get_element(env, arr, 123, &value); +if (status != napi_ok) return status; +``` + +Finally, multiple properties can also be defined on an object for performance +reasons. Consider the following JavaScript: +```js +const obj = {}; +Object.defineProperties(obj, { + 'foo': { value: 123, writable: true, configurable: true, enumerable: true }, + 'bar': { value: 456, writable: true, configurable: true, enumerable: true } +}); +``` + +The following is the approximate equivalent of the N-API counterpart: +```C +napi_status status = napi_status_generic_failure; + +// const obj = {}; +napi_value obj; +status = napi_create_object(env, &obj); +if (status != napi_ok) return status; + +// Create napi_values for 123 and 456 +napi_value fooValue, barValue; +status = napi_create_int32(env, 123, &fooValue); +if (status != napi_ok) return status; +status = napi_create_int32(env, 456, &barValue); +if (status != napi_ok) return status; + +// Set the properties +napi_property_descriptor descriptors[] = { + { "foo", NULL, 0, 0, 0, fooValue, napi_default, 0 }, + { "bar", NULL, 0, 0, 0, barValue, napi_default, 0 } +} +status = napi_define_properties(env, + obj, + sizeof(descriptors) / sizeof(descriptors[0]), + descriptors); +if (status != napi_ok) return status; +``` + +### Structures +#### napi_property_attributes +```C +typedef enum { + napi_default = 0, + napi_writable = 1 << 0, + napi_enumerable = 1 << 1, + napi_configurable = 1 << 2, + + // Used with napi_define_class to distinguish static properties + // from instance properties. Ignored by napi_define_properties. + napi_static = 1 << 10, +} napi_property_attributes; +``` + +`napi_property_attributes` are flags used to control the behavior of properties +set on a JavaScript object. Other than `napi_static` they correspond to the +attributes listed in [Section 6.1.7.1](https://tc39.github.io/ecma262/#table-2) +of the [ECMAScript Language Specification](https://tc39.github.io/ecma262/). +They can be one or more of the following bitflags: + +- `napi_default` - Used to indicate that no explicit attributes are set on the +given property. By default, a property is read only, not enumerable and not +configurable. +- `napi_writable` - Used to indicate that a given property is writable. +- `napi_enumerable` - Used to indicate that a given property is enumerable. +- `napi_configurable` - Used to indicate that a given property is +configurable, as defined in +[Section 6.1.7.1](https://tc39.github.io/ecma262/#table-2) of the +[ECMAScript Language Specification](https://tc39.github.io/ecma262/). +- `napi_static` - Used to indicate that the property will be defined as +a static property on a class as opposed to an instance property, which is the +default. This is used only by [`napi_define_class`][]. It is ignored by +`napi_define_properties`. + +#### napi_property_descriptor +```C +typedef struct { + // One of utf8name or name should be NULL. + const char* utf8name; + napi_value name; + + napi_callback method; + napi_callback getter; + napi_callback setter; + napi_value value; + + napi_property_attributes attributes; + void* data; +} napi_property_descriptor; +``` + +- `utf8name`: Optional String describing the key for the property, +encoded as UTF8. One of `utf8name` or `name` must be provided for the +property. +- `name`: Optional napi_value that points to a JavaScript string or symbol +to be used as the key for the property. One of `utf8name` or `name` must +be provided for the property. +- `value`: The value that's retrieved by a get access of the property if the + property is a data property. If this is passed in, set `getter`, `setter`, + `method` and `data` to `NULL` (since these members won't be used). +- `getter`: A function to call when a get access of the property is performed. +If this is passed in, set `value` and `method` to `NULL` (since these members +won't be used). The given function is called implicitly by the runtime when the +property is accessed from JavaScript code (or if a get on the property is +performed using a N-API call). +- `setter`: A function to call when a set access of the property is performed. +If this is passed in, set `value` and `method` to `NULL` (since these members +won't be used). The given function is called implicitly by the runtime when the +property is set from JavaScript code (or if a set on the property is +performed using a N-API call). +- `method`: Set this to make the property descriptor object's `value` +property to be a JavaScript function represented by `method`. If this is +passed in, set `value`, `getter` and `setter` to `NULL` (since these members +won't be used). +- `data`: The callback data passed into `method`, `getter` and `setter` if +this function is invoked. +- `attributes`: The attributes associated with the particular property. +See [`napi_property_attributes`](#napi_property_attributes). + +### Functions +#### napi_get_property_names + +```C +napi_status napi_get_property_names(napi_env env, + napi_value object, + napi_value* result); +``` + +- `[in] env`: The environment that the N-API call is invoked under. +- `[in] object`: The object from which to retrieve the properties. +- `[out] result`: A `napi_value` representing an array of JavaScript values +that represent the property names of the object. The API can be used to +iterate over `result` using [`napi_get_array_length`][] +and [`napi_get_element`][]. + +Returns `napi_ok` if the API succeeded. + +This API returns the array of properties for the Object passed in + +#### napi_set_property + +```C +napi_status napi_set_property(napi_env env, + napi_value object, + napi_value key, + napi_value value); +``` + +- `[in] env`: The environment that the N-API call is invoked under. +- `[in] object`: The object on which to set the property. +- `[in] key`: The name of the property to set. +- `[in] value`: The property value. + +Returns `napi_ok` if the API succeeded. + +This API set a property on the Object passed in. + +#### napi_get_property + +```C +napi_status napi_get_property(napi_env env, + napi_value object, + napi_value key, + napi_value* result); +``` + +- `[in] env`: The environment that the N-API call is invoked under. +- `[in] object`: The object from which to retrieve the property. +- `[in] key`: The name of the property to retrieve. +- `[out] result`: The value of the property. + +Returns `napi_ok` if the API succeeded. + +This API gets the requested property from the Object passed in. + + +#### napi_has_property + +```C +napi_status napi_has_property(napi_env env, + napi_value object, + napi_value key, + bool* result); +``` + +- `[in] env`: The environment that the N-API call is invoked under. +- `[in] object`: The object to query. +- `[in] key`: The name of the property whose existence to check. +- `[out] result`: Whether the property exists on the object or not. + +Returns `napi_ok` if the API succeeded. + +This API checks if the Object passed in has the named property. + + +#### napi_delete_property + +```C +napi_status napi_delete_property(napi_env env, + napi_value object, + napi_value key, + bool* result); +``` + +- `[in] env`: The environment that the N-API call is invoked under. +- `[in] object`: The object to query. +- `[in] key`: The name of the property to delete. +- `[out] result`: Whether the property deletion succeeded or not. `result` can +optionally be ignored by passing `NULL`. + +Returns `napi_ok` if the API succeeded. + +This API attempts to delete the `key` own property from `object`. + + +#### napi_has_own_property + +```C +napi_status napi_has_own_property(napi_env env, + napi_value object, + napi_value key, + bool* result); +``` + +- `[in] env`: The environment that the N-API call is invoked under. +- `[in] object`: The object to query. +- `[in] key`: The name of the own property whose existence to check. +- `[out] result`: Whether the own property exists on the object or not. + +Returns `napi_ok` if the API succeeded. + +This API checks if the Object passed in has the named own property. `key` must +be a string or a Symbol, or an error will be thrown. N-API will not perform any +conversion between data types. + + +#### napi_set_named_property + +```C +napi_status napi_set_named_property(napi_env env, + napi_value object, + const char* utf8Name, + napi_value value); +``` + +- `[in] env`: The environment that the N-API call is invoked under. +- `[in] object`: The object on which to set the property. +- `[in] utf8Name`: The name of the property to set. +- `[in] value`: The property value. + +Returns `napi_ok` if the API succeeded. + +This method is equivalent to calling [`napi_set_property`][] with a `napi_value` +created from the string passed in as `utf8Name` + +#### napi_get_named_property + +```C +napi_status napi_get_named_property(napi_env env, + napi_value object, + const char* utf8Name, + napi_value* result); +``` + +- `[in] env`: The environment that the N-API call is invoked under. +- `[in] object`: The object from which to retrieve the property. +- `[in] utf8Name`: The name of the property to get. +- `[out] result`: The value of the property. + +Returns `napi_ok` if the API succeeded. + +This method is equivalent to calling [`napi_get_property`][] with a `napi_value` +created from the string passed in as `utf8Name` + +#### napi_has_named_property + +```C +napi_status napi_has_named_property(napi_env env, + napi_value object, + const char* utf8Name, + bool* result); +``` + +- `[in] env`: The environment that the N-API call is invoked under. +- `[in] object`: The object to query. +- `[in] utf8Name`: The name of the property whose existence to check. +- `[out] result`: Whether the property exists on the object or not. + +Returns `napi_ok` if the API succeeded. + +This method is equivalent to calling [`napi_has_property`][] with a `napi_value` +created from the string passed in as `utf8Name` + +#### napi_set_element + +```C +napi_status napi_set_element(napi_env env, + napi_value object, + uint32_t index, + napi_value value); +``` + +- `[in] env`: The environment that the N-API call is invoked under. +- `[in] object`: The object from which to set the properties. +- `[in] index`: The index of the property to set. +- `[in] value`: The property value. + +Returns `napi_ok` if the API succeeded. + +This API sets and element on the Object passed in. + +#### napi_get_element + +```C +napi_status napi_get_element(napi_env env, + napi_value object, + uint32_t index, + napi_value* result); +``` + +- `[in] env`: The environment that the N-API call is invoked under. +- `[in] object`: The object from which to retrieve the property. +- `[in] index`: The index of the property to get. +- `[out] result`: The value of the property. + +Returns `napi_ok` if the API succeeded. + +This API gets the element at the requested index. + +#### napi_has_element + +```C +napi_status napi_has_element(napi_env env, + napi_value object, + uint32_t index, + bool* result); +``` + +- `[in] env`: The environment that the N-API call is invoked under. +- `[in] object`: The object to query. +- `[in] index`: The index of the property whose existence to check. +- `[out] result`: Whether the property exists on the object or not. + +Returns `napi_ok` if the API succeeded. + +This API returns if the Object passed in has an element at the +requested index. + +#### napi_delete_element + +```C +napi_status napi_delete_element(napi_env env, + napi_value object, + uint32_t index, + bool* result); +``` + +- `[in] env`: The environment that the N-API call is invoked under. +- `[in] object`: The object to query. +- `[in] index`: The index of the property to delete. +- `[out] result`: Whether the element deletion succeeded or not. `result` can +optionally be ignored by passing `NULL`. + +Returns `napi_ok` if the API succeeded. + +This API attempts to delete the specified `index` from `object`. + +#### napi_define_properties + +```C +napi_status napi_define_properties(napi_env env, + napi_value object, + size_t property_count, + const napi_property_descriptor* properties); +``` + +- `[in] env`: The environment that the N-API call is invoked under. +- `[in] object`: The object from which to retrieve the properties. +- `[in] property_count`: The number of elements in the `properties` array. +- `[in] properties`: The array of property descriptors. + +Returns `napi_ok` if the API succeeded. + +This method allows the efficient definition of multiple properties on a given +object. The properties are defined using property descriptors (See +[`napi_property_descriptor`][]). Given an array of such property descriptors, 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 ECMA262 specification). + +## Working with JavaScript Functions + +N-API provides a set of APIs that allow JavaScript code to +call back into native code. N-API APIs that support calling back +into native code take in a callback functions represented by +the `napi_callback` type. When the JavaScript VM calls back to +native code, the `napi_callback` function provided is invoked. The APIs +documented in this section allow the callback function to do the +following: +- Get information about the context in which the callback was invoked. +- Get the arguments passed into the callback. +- Return a `napi_value` back from the callback. + +Additionally, N-API provides a set of functions which allow calling +JavaScript functions from native code. One can either call a function +like a regular JavaScript function call, or as a constructor +function. + + +### napi_call_function + +```C +napi_status napi_call_function(napi_env env, + napi_value recv, + napi_value func, + int argc, + const napi_value* argv, + napi_value* result) +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] recv`: The `this` object passed to the called function. +- `[in] func`: `napi_value` representing the JavaScript function +to be invoked. +- `[in] argc`: The count of elements in the `argv` array. +- `[in] argv`: Array of `napi_values` representing JavaScript values passed +in as arguments to the function. +- `[out] result`: `napi_value` representing the JavaScript object returned. + +Returns `napi_ok` if the API succeeded. + +This method allows a JavaScript function object to be called from a native +add-on. This is the primary mechanism of calling back *from* the add-on's +native code *into* JavaScript. For the special case of calling into JavaScript +after an async operation, see [`napi_make_callback`][]. + +A sample use case might look as follows. Consider the following JavaScript +snippet: +```js +function AddTwo(num) { + return num + 2; +} +``` + +Then, the above function can be invoked from a native add-on using the +following code: +```C +// Get the function named "AddTwo" on the global object +napi_value global, add_two, arg; +napi_status status = napi_get_global(env, &global); +if (status != napi_ok) return; + +status = napi_get_named_property(env, global, "AddTwo", &add_two); +if (status != napi_ok) return; + +// const arg = 1337 +status = napi_create_int32(env, 1337, &arg); +if (status != napi_ok) return; + +napi_value* argv = &arg; +size_t argc = 1; + +// AddTwo(arg); +napi_value return_val; +status = napi_call_function(env, global, add_two, argc, argv, &return_val); +if (status != napi_ok) return; + +// Convert the result back to a native type +int32_t result; +status = napi_get_value_int32(env, return_val, &result); +if (status != napi_ok) return; +``` + +### napi_create_function + +```C +napi_status napi_create_function(napi_env env, + const char* utf8name, + napi_callback cb, + void* data, + napi_value* result); +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] utf8Name`: The name of the function encoded as UTF8. This is visible +within JavaScript as the new function object's `name` property. +- `[in] cb`: The native function which should be called when this function +object is invoked. +- `[in] data`: User-provided data context. This will be passed back into the +function when invoked later. +- `[out] result`: `napi_value` representing the JavaScript function object for +the newly created function. + +Returns `napi_ok` if the API succeeded. + +This API allows an add-on author to create a function object in native code. +This is the primary mechanism to allow calling *into* the add-on's native code +*from* JavaScript. + +**Note:** The newly created function is not automatically visible from +script after this call. Instead, a property must be explicitly set on any +object that is visible to JavaScript, in order for the function to be accessible +from script. + +In order to expose a function as part of the +add-on's module exports, set the newly created function on the exports +object. A sample module might look as follows: +```C +napi_value SayHello(napi_env env, napi_callback_info info) { + printf("Hello\n"); + return NULL; +} + +napi_value Init(napi_env env, napi_value exports) { + napi_status status; + + napi_value fn; + status = napi_create_function(env, NULL, 0, SayHello, NULL, &fn); + if (status != napi_ok) return NULL; + + status = napi_set_named_property(env, exports, "sayHello", fn); + if (status != napi_ok) return NULL; + + return exports; +} + +NAPI_MODULE(addon, Init) +``` + +Given the above code, the add-on can be used from JavaScript as follows: +```js +const myaddon = require('./addon'); +myaddon.sayHello(); +``` + +**Note:** The string passed to require is not necessarily the name passed into +`NAPI_MODULE` in the earlier snippet but the name of the target in `binding.gyp` +responsible for creating the `.node` file. + +### napi_get_cb_info + +```C +napi_status napi_get_cb_info(napi_env env, + napi_callback_info cbinfo, + size_t* argc, + napi_value* argv, + napi_value* thisArg, + void** data) +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] cbinfo`: The callback info passed into the callback function. +- `[in-out] argc`: Specifies the size of the provided `argv` array +and receives the actual count of arguments. +- `[out] argv`: Buffer to which the `napi_value` representing the +arguments are copied. If there are more arguments than the provided +count, only the requested number of arguments are copied. If there are fewer +arguments provided than claimed, the rest of `argv` is filled with `napi_value` +values that represent `undefined`. +- `[out] this`: Receives the JavaScript `this` argument for the call. +- `[out] data`: Receives the data pointer for the callback. + +Returns `napi_ok` if the API succeeded. + +This method is used within a callback function to retrieve details about the +call like the arguments and the `this` pointer from a given callback info. + +### napi_get_new_target + +```C +napi_status napi_get_new_target(napi_env env, + napi_callback_info cbinfo, + napi_value* result) +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] cbinfo`: The callback info passed into the callback function. +- `[out] result`: The `new.target` of the constructor call. + +Returns `napi_ok` if the API succeeded. + +This API returns the `new.target` of the constructor call. If the current +callback is not a constructor call, the result is `NULL`. + +### napi_new_instance + +```C +napi_status napi_new_instance(napi_env env, + napi_value cons, + size_t argc, + napi_value* argv, + napi_value* result) +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] cons`: `napi_value` representing the JavaScript function +to be invoked as a constructor. +- `[in] argc`: The count of elements in the `argv` array. +- `[in] argv`: Array of JavaScript values as `napi_value` +representing the arguments to the constructor. +- `[out] result`: `napi_value` representing the JavaScript object returned, +which in this case is the constructed object. + +This method is used to instantiate a new JavaScript value using a given +`napi_value` that represents the constructor for the object. For example, +consider the following snippet: +```js +function MyObject(param) { + this.param = param; +} + +const arg = 'hello'; +const value = new MyObject(arg); +``` + +The following can be approximated in N-API using the following snippet: +```C +// Get the constructor function MyObject +napi_value global, constructor, arg, value; +napi_status status = napi_get_global(env, &global); +if (status != napi_ok) return; + +status = napi_get_named_property(env, global, "MyObject", &constructor); +if (status != napi_ok) return; + +// const arg = "hello" +status = napi_create_string_utf8(env, "hello", NAPI_AUTO_LENGTH, &arg); +if (status != napi_ok) return; + +napi_value* argv = &arg; +size_t argc = 1; + +// const value = new MyObject(arg) +status = napi_new_instance(env, constructor, argc, argv, &value); +``` + +Returns `napi_ok` if the API succeeded. + +## Object Wrap + +N-API offers a way to "wrap" C++ classes and instances so that the class +constructor and methods can be called from JavaScript. + + 1. The [`napi_define_class`][] API defines a JavaScript class with constructor, + static properties and methods, and instance properties and methods that + correspond to the C++ class. + 2. When JavaScript code invokes the constructor, the constructor callback + uses [`napi_wrap`][] to wrap a new C++ instance in a JavaScript object, + then returns the wrapper object. + 3. When JavaScript code invokes a method or property accessor on the class, + the corresponding `napi_callback` C++ function is invoked. For an instance + callback, [`napi_unwrap`][] obtains the C++ instance that is the target of + the call. + +For wrapped objects it may be difficult to distinguish between a function +called on a class prototype and a function called on an instance of a class. +A common pattern used to address this problem is to save a persistent +reference to the class constructor for later `instanceof` checks. + +As an example: + +```C +napi_value MyClass_constructor = NULL; +status = napi_get_reference_value(env, MyClass::es_constructor, &MyClass_constructor); +assert(napi_ok == status); +bool is_instance = false; +status = napi_instanceof(env, es_this, MyClass_constructor, &is_instance); +assert(napi_ok == status); +if (is_instance) { + // napi_unwrap() ... +} else { + // otherwise... +} +``` + +The reference must be freed once it is no longer needed. + +### napi_define_class + +```C +napi_status napi_define_class(napi_env env, + const char* utf8name, + size_t length, + napi_callback constructor, + void* data, + size_t property_count, + const napi_property_descriptor* properties, + napi_value* result); +``` + + - `[in] env`: The environment that the API is invoked under. + - `[in] utf8name`: Name of the JavaScript constructor function; this is + not required to be the same as the C++ class name, though it is recommended + for clarity. + - `[in] length`: The length of the utf8name in bytes, or `NAPI_AUTO_LENGTH` +if it is null-terminated. + - `[in] constructor`: Callback function that handles constructing instances + of the class. (This should be a static method on the class, not an actual + C++ constructor function.) + - `[in] data`: Optional data to be passed to the constructor callback as + the `data` property of the callback info. + - `[in] property_count`: Number of items in the `properties` array argument. + - `[in] properties`: Array of property descriptors describing static and + instance data properties, accessors, and methods on the class + See `napi_property_descriptor`. + - `[out] result`: A `napi_value` representing the constructor function for + the class. + +Returns `napi_ok` if the API succeeded. + +Defines a JavaScript class that corresponds to a C++ class, including: + - A JavaScript constructor function that has the class name and invokes the + provided C++ constructor callback. + - Properties on the constructor function corresponding to _static_ data + properties, accessors, and methods of the C++ class (defined by + property descriptors with the `napi_static` attribute). + - Properties on the constructor function's `prototype` object corresponding to + _non-static_ data properties, accessors, and methods of the C++ class + (defined by property descriptors without the `napi_static` attribute). + +The C++ constructor callback should be a static method on the class that calls +the actual class constructor, then wraps the new C++ instance in a JavaScript +object, and returns the wrapper object. See `napi_wrap()` for details. + +The JavaScript constructor function returned from [`napi_define_class`][] is +often saved and used later, to construct new instances of the class from native +code, and/or check whether provided values are instances of the class. In that +case, to prevent the function value from being garbage-collected, create a +persistent reference to it using [`napi_create_reference`][] and ensure the +reference count is kept >= 1. + +### napi_wrap + +```C +napi_status napi_wrap(napi_env env, + napi_value js_object, + void* native_object, + napi_finalize finalize_cb, + void* finalize_hint, + napi_ref* result); +``` + + - `[in] env`: The environment that the API is invoked under. + - `[in] js_object`: The JavaScript object that will be the wrapper for the + native object. This object _must_ have been created from the `prototype` of + a constructor that was created using `napi_define_class()`. + - `[in] native_object`: The native instance that will be wrapped in the + JavaScript object. + - `[in] finalize_cb`: Optional native callback that can be used to free the + native instance when the JavaScript object is ready for garbage-collection. + - `[in] finalize_hint`: Optional contextual hint that is passed to the + finalize callback. + - `[out] result`: Optional reference to the wrapped object. + +Returns `napi_ok` if the API succeeded. + +Wraps a native instance in a JavaScript object. The native instance can be +retrieved later using `napi_unwrap()`. + +When JavaScript code invokes a constructor for a class that was defined using +`napi_define_class()`, the `napi_callback` for the constructor is invoked. +After constructing an instance of the native class, the callback must then call +`napi_wrap()` to wrap the newly constructed instance in the already-created +JavaScript object that is the `this` argument to the constructor callback. +(That `this` object was created from the constructor function's `prototype`, +so it already has definitions of all the instance properties and methods.) + +Typically when wrapping a class instance, a finalize callback should be +provided that simply deletes the native instance that is received as the `data` +argument to the finalize callback. + +The optional returned reference is initially a weak reference, meaning it +has a reference count of 0. Typically this reference count would be incremented +temporarily during async operations that require the instance to remain valid. + +*Caution*: The optional returned reference (if obtained) should be deleted via +[`napi_delete_reference`][] ONLY in response to the finalize callback +invocation. (If it is deleted before then, then the finalize callback may never +be invoked.) Therefore, when obtaining a reference a finalize callback is also +required in order to enable correct proper of the reference. + +*Note*: This API may modify the prototype chain of the wrapper object. +Afterward, additional manipulation of the wrapper's prototype chain may cause +`napi_unwrap()` to fail. + +Calling napi_wrap() a second time on an object will return an error. To associate +another native instance with the object, use napi_remove_wrap() first. + +### napi_unwrap + +```C +napi_status napi_unwrap(napi_env env, + napi_value js_object, + void** result); +``` + + - `[in] env`: The environment that the API is invoked under. + - `[in] js_object`: The object associated with the native instance. + - `[out] result`: Pointer to the wrapped native instance. + +Returns `napi_ok` if the API succeeded. + +Retrieves a native instance that was previously wrapped in a JavaScript +object using `napi_wrap()`. + +When JavaScript code invokes a method or property accessor on the class, the +corresponding `napi_callback` is invoked. If the callback is for an instance +method or accessor, then the `this` argument to the callback is the wrapper +object; the wrapped C++ instance that is the target of the call can be obtained +then by calling `napi_unwrap()` on the wrapper object. + +### napi_remove_wrap + +```C +napi_status napi_remove_wrap(napi_env env, + napi_value js_object, + void** result); +``` + + - `[in] env`: The environment that the API is invoked under. + - `[in] js_object`: The object associated with the native instance. + - `[out] result`: Pointer to the wrapped native instance. + +Returns `napi_ok` if the API succeeded. + +Retrieves a native instance that was previously wrapped in the JavaScript +object `js_object` using `napi_wrap()` and removes the wrapping, thereby +restoring the JavaScript object's prototype chain. If a finalize callback was +associated with the wrapping, it will no longer be called when the JavaScript +object becomes garbage-collected. + +## Simple Asynchronous Operations + +Addon modules often need to leverage async helpers from libuv as part of their +implementation. This allows them to schedule work to be executed asynchronously +so that their methods can return in advance of the work being completed. This +is important in order to allow them to avoid blocking overall execution +of the Node.js application. + +N-API provides an ABI-stable interface for these +supporting functions which covers the most common asynchronous use cases. + +N-API defines the `napi_work` structure which is used to manage +asynchronous workers. Instances are created/deleted with +[`napi_create_async_work`][] and [`napi_delete_async_work`][]. + +The `execute` and `complete` callbacks are functions that will be +invoked when the executor is ready to execute and when it completes its +task respectively. These functions implement the following interfaces: + +```C +typedef void (*napi_async_execute_callback)(napi_env env, + void* data); +typedef void (*napi_async_complete_callback)(napi_env env, + napi_status status, + void* data); +``` + + +When these methods are invoked, the `data` parameter passed will be the +addon-provided void* data that was passed into the +`napi_create_async_work` call. + +Once created the async worker can be queued +for execution using the [`napi_queue_async_work`][] function: + +```C +napi_status napi_queue_async_work(napi_env env, + napi_async_work work); +``` + +[`napi_cancel_async_work`][] can be used if the work needs +to be cancelled before the work has started execution. + +After calling [`napi_cancel_async_work`][], the `complete` callback +will be invoked with a status value of `napi_cancelled`. +The work should not be deleted before the `complete` +callback invocation, even when it was cancelled. + +### napi_create_async_work + +```C +napi_status napi_create_async_work(napi_env env, + napi_value async_resource, + napi_value async_resource_name, + napi_async_execute_callback execute, + napi_async_complete_callback complete, + void* data, + napi_async_work* result); +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] async_resource`: An optional object associated with the async work + that will be passed to possible async_hooks [`init` hooks][]. +- `[in] async_resource_name`: Identifier for the kind of resource that is +being provided for diagnostic information exposed by the `async_hooks` API. +- `[in] execute`: The native function which should be called to execute +the logic asynchronously. The given function is called from a worker pool +thread and can execute in parallel with the main event loop thread. +- `[in] complete`: The native function which will be called when the +asynchronous logic is completed or is cancelled. The given function is called +from the main event loop thread. +- `[in] data`: User-provided data context. This will be passed back into the +execute and complete functions. +- `[out] result`: `napi_async_work*` which is the handle to the newly created +async work. + +Returns `napi_ok` if the API succeeded. + +This API allocates a work object that is used to execute logic asynchronously. +It should be freed using [`napi_delete_async_work`][] once the work is no longer +required. + +`async_resource_name` should be a null-terminated, UTF-8-encoded string. + +*Note*: The `async_resource_name` identifier is provided by the user and should +be representative of the type of async work being performed. It is also +recommended to apply namespacing to the identifier, e.g. by including the +module name. See the [`async_hooks` documentation][async_hooks `type`] +for more information. + +### napi_delete_async_work + +```C +napi_status napi_delete_async_work(napi_env env, + napi_async_work work); +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] work`: The handle returned by the call to `napi_create_async_work`. + +Returns `napi_ok` if the API succeeded. + +This API frees a previously allocated work object. + +This API can be called even if there is a pending JavaScript exception. + +### napi_queue_async_work + +```C +napi_status napi_queue_async_work(napi_env env, + napi_async_work work); +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] work`: The handle returned by the call to `napi_create_async_work`. + +Returns `napi_ok` if the API succeeded. + +This API requests that the previously allocated work be scheduled +for execution. + +### napi_cancel_async_work + +```C +napi_status napi_cancel_async_work(napi_env env, + napi_async_work work); +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] work`: The handle returned by the call to `napi_create_async_work`. + +Returns `napi_ok` if the API succeeded. + +This API cancels queued work if it has not yet +been started. If it has already started executing, it cannot be +cancelled and `napi_generic_failure` will be returned. If successful, +the `complete` callback will be invoked with a status value of +`napi_cancelled`. The work should not be deleted before the `complete` +callback invocation, even if it has been successfully cancelled. + +This API can be called even if there is a pending JavaScript exception. + +## Custom Asynchronous Operations +The simple asynchronous work APIs above may not be appropriate for every +scenario. When using any other asynchronous mechanism, the following APIs +are necessary to ensure an asynchronous operation is properly tracked by +the runtime. + +### napi_async_init + +```C +napi_status napi_async_init(napi_env env, + napi_value async_resource, + napi_value async_resource_name, + napi_async_context* result) +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] async_resource`: An optional object associated with the async work + that will be passed to possible `async_hooks` [`init` hooks][]. +- `[in] async_resource_name`: Identifier for the kind of resource + that is being provided for diagnostic information exposed by the + `async_hooks` API. +- `[out] result`: The initialized async context. + +Returns `napi_ok` if the API succeeded. + +### napi_async_destroy + +```C +napi_status napi_async_destroy(napi_env env, + napi_async_context async_context); +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] async_context`: The async context to be destroyed. + +Returns `napi_ok` if the API succeeded. + +This API can be called even if there is a pending JavaScript exception. + +### napi_make_callback + +```C +napi_status napi_make_callback(napi_env env, + napi_async_context async_context, + napi_value recv, + napi_value func, + int argc, + const napi_value* argv, + napi_value* result) +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] async_context`: Context for the async operation that is + invoking the callback. This should normally be a value previously + obtained from [`napi_async_init`][]. However `NULL` is also allowed, + which indicates the current async context (if any) is to be used + for the callback. +- `[in] recv`: The `this` object passed to the called function. +- `[in] func`: `napi_value` representing the JavaScript function +to be invoked. +- `[in] argc`: The count of elements in the `argv` array. +- `[in] argv`: Array of JavaScript values as `napi_value` +representing the arguments to the function. +- `[out] result`: `napi_value` representing the JavaScript object returned. + +Returns `napi_ok` if the API succeeded. + +This method allows a JavaScript function object to be called from a native +add-on. This API is similar to `napi_call_function`. However, it is used to call +*from* native code back *into* JavaScript *after* returning from an async +operation (when there is no other script on the stack). It is a fairly simple +wrapper around `node::MakeCallback`. + +Note it is *not* necessary to use `napi_make_callback` from within a +`napi_async_complete_callback`; in that situation the callback's async +context has already been set up, so a direct call to `napi_call_function` +is sufficient and appropriate. Use of the `napi_make_callback` function +may be required when implementing custom async behavior that does not use +`napi_create_async_work`. + +### *napi_open_callback_scope* + +```C +NAPI_EXTERN napi_status napi_open_callback_scope(napi_env env, + napi_value resource_object, + napi_async_context context, + napi_callback_scope* result) +``` +- `[in] env`: The environment that the API is invoked under. +- `[in] resource_object`: An optional object associated with the async work + that will be passed to possible async_hooks [`init` hooks][]. +- `[in] context`: Context for the async operation that is +invoking the callback. This should be a value previously obtained +from [`napi_async_init`][]. +- `[out] result`: The newly created scope. + +There are cases (for example resolving promises) where it is +necessary to have the equivalent of the scope associated with a callback +in place when making certain N-API calls. If there is no other script on +the stack the [`napi_open_callback_scope`][] and +[`napi_close_callback_scope`][] functions can be used to open/close +the required scope. + +### *napi_close_callback_scope* + +```C +NAPI_EXTERN napi_status napi_close_callback_scope(napi_env env, + napi_callback_scope scope) +``` +- `[in] env`: The environment that the API is invoked under. +- `[in] scope`: The scope to be closed. + +This API can be called even if there is a pending JavaScript exception. + +## Version Management + +### napi_get_node_version + + +```C +typedef struct { + uint32_t major; + uint32_t minor; + uint32_t patch; + const char* release; +} napi_node_version; + +napi_status napi_get_node_version(napi_env env, + const napi_node_version** version); +``` + +- `[in] env`: The environment that the API is invoked under. +- `[out] version`: A pointer to version information for Node itself. + +Returns `napi_ok` if the API succeeded. + +This function fills the `version` struct with the major, minor and patch version +of Node that is currently running, and the `release` field with the +value of [`process.release.name`][`process.release`]. + +The returned buffer is statically allocated and does not need to be freed. + +### napi_get_version + +```C +napi_status napi_get_version(napi_env env, + uint32_t* result); +``` + +- `[in] env`: The environment that the API is invoked under. +- `[out] result`: The highest version of N-API supported. + +Returns `napi_ok` if the API succeeded. + +This API returns the highest N-API version supported by the +Node.js runtime. N-API is planned to be additive such that +newer releases of Node.js may support additional API functions. +In order to allow an addon to use a newer function when running with +versions of Node.js that support it, while providing +fallback behavior when running with Node.js versions that don't +support it: + +* Call `napi_get_version()` to determine if the API is available. +* If available, dynamically load a pointer to the function using `uv_dlsym()`. +* Use the dynamically loaded pointer to invoke the function. +* If the function is not available, provide an alternate implementation + that does not use the function. + +## Memory Management + +### napi_adjust_external_memory + +```C +NAPI_EXTERN napi_status napi_adjust_external_memory(napi_env env, + int64_t change_in_bytes, + int64_t* result); +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] change_in_bytes`: The change in externally allocated memory that is +kept alive by JavaScript objects. +- `[out] result`: The adjusted value + +Returns `napi_ok` if the API succeeded. + +This function gives V8 an indication of the amount of externally allocated +memory that is kept alive by JavaScript objects (i.e. a JavaScript object +that points to its own memory allocated by a native module). Registering +externally allocated memory will trigger global garbage collections more +often than it would otherwise. + + + +## Promises + +N-API provides facilities for creating `Promise` objects as described in +[Section 25.4][] of the ECMA specification. It implements promises as a pair of +objects. When a promise is created by `napi_create_promise()`, a "deferred" +object is created and returned alongside the `Promise`. The deferred object is +bound to the created `Promise` and is the only means to resolve or reject the +`Promise` using `napi_resolve_deferred()` or `napi_reject_deferred()`. The +deferred object that is created by `napi_create_promise()` is freed by +`napi_resolve_deferred()` or `napi_reject_deferred()`. The `Promise` object may +be returned to JavaScript where it can be used in the usual fashion. + +For example, to create a promise and pass it to an asynchronous worker: +```c +napi_deferred deferred; +napi_value promise; +napi_status status; + +// Create the promise. +status = napi_create_promise(env, &deferred, &promise); +if (status != napi_ok) return NULL; + +// Pass the deferred to a function that performs an asynchronous action. +do_something_asynchronous(deferred); + +// Return the promise to JS +return promise; +``` + +The above function `do_something_asynchronous()` would perform its asynchronous +action and then it would resolve or reject the deferred, thereby concluding the +promise and freeing the deferred: +```c +napi_deferred deferred; +napi_value undefined; +napi_status status; + +// Create a value with which to conclude the deferred. +status = napi_get_undefined(env, &undefined); +if (status != napi_ok) return NULL; + +// Resolve or reject the promise associated with the deferred depending on +// whether the asynchronous action succeeded. +if (asynchronous_action_succeeded) { + status = napi_resolve_deferred(env, deferred, undefined); +} else { + status = napi_reject_deferred(env, deferred, undefined); +} +if (status != napi_ok) return NULL; + +// At this point the deferred has been freed, so we should assign NULL to it. +deferred = NULL; +``` + +### napi_create_promise + +```C +napi_status napi_create_promise(napi_env env, + napi_deferred* deferred, + napi_value* promise); +``` + +- `[in] env`: The environment that the API is invoked under. +- `[out] deferred`: A newly created deferred object which can later be passed to +`napi_resolve_deferred()` or `napi_reject_deferred()` to resolve resp. reject +the associated promise. +- `[out] promise`: The JavaScript promise associated with the deferred object. + +Returns `napi_ok` if the API succeeded. + +This API creates a deferred object and a JavaScript promise. + +### napi_resolve_deferred + +```C +napi_status napi_resolve_deferred(napi_env env, + napi_deferred deferred, + napi_value resolution); +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] deferred`: The deferred object whose associated promise to resolve. +- `[in] resolution`: The value with which to resolve the promise. + +This API resolves a JavaScript promise by way of the deferred object +with which it is associated. Thus, it can only be used to resolve JavaScript +promises for which the corresponding deferred object is available. This +effectively means that the promise must have been created using +`napi_create_promise()` and the deferred object returned from that call must +have been retained in order to be passed to this API. + +The deferred object is freed upon successful completion. + +### napi_reject_deferred + +```C +napi_status napi_reject_deferred(napi_env env, + napi_deferred deferred, + napi_value rejection); +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] deferred`: The deferred object whose associated promise to resolve. +- `[in] rejection`: The value with which to reject the promise. + +This API rejects a JavaScript promise by way of the deferred object +with which it is associated. Thus, it can only be used to reject JavaScript +promises for which the corresponding deferred object is available. This +effectively means that the promise must have been created using +`napi_create_promise()` and the deferred object returned from that call must +have been retained in order to be passed to this API. + +The deferred object is freed upon successful completion. + +### napi_is_promise + +```C +napi_status napi_is_promise(napi_env env, + napi_value promise, + bool* is_promise); +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] promise`: The promise to examine +- `[out] is_promise`: Flag indicating whether `promise` is a native promise +object - that is, a promise object created by the underlying engine. + +## Script execution + +N-API provides an API for executing a string containing JavaScript using the +underlying JavaScript engine. + +### napi_run_script + +```C +NAPI_EXTERN napi_status napi_run_script(napi_env env, + napi_value script, + napi_value* result); +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] script`: A JavaScript string containing the script to execute. +- `[out] result`: The value resulting from having executed the script. + +## libuv event loop + +N-API provides a function for getting the current event loop associated with +a specific `napi_env`. + +### napi_get_uv_event_loop + +```C +NAPI_EXTERN napi_status napi_get_uv_event_loop(napi_env env, + uv_loop_t** loop); +``` + +- `[in] env`: The environment that the API is invoked under. +- `[out] loop`: The current libuv loop instance. + +[Promises]: #n_api_promises +[Simple Asynchronous Operations]: #n_api_simple_asynchronous_operations +[Custom Asynchronous Operations]: #n_api_custom_asynchronous_operations +[Basic N-API Data Types]: #n_api_basic_n_api_data_types +[ECMAScript Language Specification]: https://tc39.github.io/ecma262/ +[Error Handling]: #n_api_error_handling +[Module Registration]: #n_api_module_registration +[Native Abstractions for Node.js]: https://github.com/nodejs/nan +[Object Lifetime Management]: #n_api_object_lifetime_management +[Object Wrap]: #n_api_object_wrap +[Script Execution]: #n_api_script_execution +[Section 9.1.6]: https://tc39.github.io/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-defineownproperty-p-desc +[Section 12.5.5]: https://tc39.github.io/ecma262/#sec-typeof-operator +[Section 24.3]: https://tc39.github.io/ecma262/#sec-dataview-objects +[Section 25.4]: https://tc39.github.io/ecma262/#sec-promise-objects +[Working with JavaScript Functions]: #n_api_working_with_javascript_functions +[Working with JavaScript Properties]: #n_api_working_with_javascript_properties +[Working with JavaScript Values]: #n_api_working_with_javascript_values +[Working with JavaScript Values - Abstract Operations]: #n_api_working_with_javascript_values_abstract_operations + +[`napi_async_init`]: #n_api_napi_async_init +[`napi_cancel_async_work`]: #n_api_napi_cancel_async_work +[`napi_close_escapable_handle_scope`]: #n_api_napi_close_escapable_handle_scope +[`napi_close_callback_scope`]: #n_api_napi_close_callback_scope +[`napi_close_handle_scope`]: #n_api_napi_close_handle_scope +[`napi_create_async_work`]: #n_api_napi_create_async_work +[`napi_create_error`]: #n_api_napi_create_error +[`napi_create_external_arraybuffer`]: #n_api_napi_create_external_arraybuffer +[`napi_create_range_error`]: #n_api_napi_create_range_error +[`napi_create_reference`]: #n_api_napi_create_reference +[`napi_create_type_error`]: #n_api_napi_create_type_error +[`napi_define_class`]: #n_api_napi_define_class +[`napi_delete_async_work`]: #n_api_napi_delete_async_work +[`napi_define_class`]: #n_api_napi_define_class +[`napi_delete_element`]: #n_api_napi_delete_element +[`napi_delete_property`]: #n_api_napi_delete_property +[`napi_delete_reference`]: #n_api_napi_delete_reference +[`napi_escape_handle`]: #n_api_napi_escape_handle +[`napi_get_array_length`]: #n_api_napi_get_array_length +[`napi_get_element`]: #n_api_napi_get_element +[`napi_get_property`]: #n_api_napi_get_property +[`napi_has_property`]: #n_api_napi_has_property +[`napi_has_own_property`]: #n_api_napi_has_own_property +[`napi_set_property`]: #n_api_napi_set_property +[`napi_get_reference_value`]: #n_api_napi_get_reference_value +[`napi_is_error`]: #n_api_napi_is_error +[`napi_is_exception_pending`]: #n_api_napi_is_exception_pending +[`napi_get_last_error_info`]: #n_api_napi_get_last_error_info +[`napi_get_and_clear_last_exception`]: #n_api_napi_get_and_clear_last_exception +[`napi_make_callback`]: #n_api_napi_make_callback +[`napi_open_callback_scope`]: #n_api_napi_open_callback_scope +[`napi_open_escapable_handle_scope`]: #n_api_napi_open_escapable_handle_scope +[`napi_open_handle_scope`]: #n_api_napi_open_handle_scope +[`napi_property_descriptor`]: #n_api_napi_property_descriptor +[`napi_queue_async_work`]: #n_api_napi_queue_async_work +[`napi_reference_ref`]: #n_api_napi_reference_ref +[`napi_reference_unref`]: #n_api_napi_reference_unref +[`napi_throw`]: #n_api_napi_throw +[`napi_throw_error`]: #n_api_napi_throw_error +[`napi_throw_range_error`]: #n_api_napi_throw_range_error +[`napi_throw_type_error`]: #n_api_napi_throw_type_error +[`napi_unwrap`]: #n_api_napi_unwrap +[`napi_wrap`]: #n_api_napi_wrap + +[`process.release`]: process.html#process_process_release +[`init` hooks]: async_hooks.html#async_hooks_init_asyncid_type_triggerasyncid_resource +[async_hooks `type`]: async_hooks.html#async_hooks_type diff --git a/doc/guides/writing-tests.md b/doc/guides/writing-tests.md index aac098640aec9e..cc87886656c06d 100644 --- a/doc/guides/writing-tests.md +++ b/doc/guides/writing-tests.md @@ -92,7 +92,7 @@ The test checks functionality in the `http` module. Most tests use the `assert` module to confirm expectations of the test. The require statements are sorted in -[ASCII](http://man7.org/linux/man-pages/man7/ascii.7.html) order (digits, upper +[ASCII][] order (digits, upper case, `_`, lower case). ### **Lines 10-21** @@ -252,9 +252,9 @@ assert.throws( For performance considerations, we only use a selected subset of ES.Next features in JavaScript code in the `lib` directory. However, when writing tests, for the ease of backporting, it is encouraged to use those ES.Next -features that can be used directly without a flag in [all maintained branches] -(https://github.com/nodejs/lts). [node.green](http://node.green/) lists -available features in each release. +features that can be used directly without a flag in +[all maintained branches][]. [node.green][] lists available features +in each release. For example: @@ -279,8 +279,7 @@ functions worked correctly with the `beforeExit` event, then it might be named ### Web Platform Tests Some of the tests for the WHATWG URL implementation (named -`test-whatwg-url-*.js`) are imported from the -[Web Platform Tests Project](https://github.com/w3c/web-platform-tests/tree/master/url). +`test-whatwg-url-*.js`) are imported from the [Web Platform Tests Project][]. These imported tests will be wrapped like this: ```js diff --git a/doc/node.1 b/doc/node.1 index 1e1877376dc3aa..b36f176983787f 100644 --- a/doc/node.1 +++ b/doc/node.1 @@ -108,6 +108,11 @@ Throw errors for deprecations. .BR \-\-no\-warnings Silence all process warnings (including deprecations). +.TP +.BR \-\-napi\-modules +Enable loading native modules compiled with the ABI-stable Node.js API (N-API) +(experimental). + .TP .BR \-\-trace\-warnings Print stack traces for process warnings (including deprecations). diff --git a/node.gyp b/node.gyp index 7dff6bf3f22c1e..7458e84621b983 100644 --- a/node.gyp +++ b/node.gyp @@ -241,6 +241,10 @@ 'src/handle_wrap.cc', 'src/js_stream.cc', 'src/node.cc', + 'src/node_api_backport.cc', + 'src/node_api.cc', + 'src/node_api.h', + 'src/node_api_types.h', 'src/node_buffer.cc', 'src/node_config.cc', 'src/node_constants.cc', diff --git a/src/env-inl.h b/src/env-inl.h index 30d7950e93950d..a5e2e176c11df3 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -111,7 +111,7 @@ inline Environment::AsyncCallbackScope::~AsyncCallbackScope() { env_->makecallback_cntr_--; } -inline bool Environment::AsyncCallbackScope::in_makecallback() { +inline bool Environment::AsyncCallbackScope::in_makecallback() const { return env_->makecallback_cntr_ > 1; } diff --git a/src/env.h b/src/env.h index c4489857161b75..19c6e84642a963 100644 --- a/src/env.h +++ b/src/env.h @@ -62,6 +62,8 @@ namespace node { V(npn_buffer_private_symbol, "node:npnBuffer") \ V(processed_private_symbol, "node:processed") \ V(selected_npn_buffer_private_symbol, "node:selectedNpnBuffer") \ + V(napi_env, "node:napi:env") \ + V(napi_wrapper, "node:napi:wrapper") \ // Strings are per-isolate primitives but Environment proxies them // for the sake of convenience. Strings should be ASCII-only. @@ -295,7 +297,7 @@ class Environment { explicit AsyncCallbackScope(Environment* env); ~AsyncCallbackScope(); - inline bool in_makecallback(); + inline bool in_makecallback() const; private: Environment* env_; diff --git a/src/node.cc b/src/node.cc index be82939e00b708..3f4c02413c9aa7 100644 --- a/src/node.cc +++ b/src/node.cc @@ -2500,7 +2500,9 @@ void DLOpen(const FunctionCallbackInfo& args) { env->ThrowError("Module did not self-register."); return; } - if (mp->nm_version != NODE_MODULE_VERSION) { + + // -1 is used for N-API modules + if ((mp->nm_version != -1) && (mp->nm_version != NODE_MODULE_VERSION)) { char errmsg[1024]; snprintf(errmsg, sizeof(errmsg), @@ -3193,6 +3195,12 @@ void SetupProcessObject(Environment* env, "modules", FIXED_ONE_BYTE_STRING(env->isolate(), node_modules_version)); + const char node_napi_version[] = NODE_STRINGIFY(NAPI_VERSION); + READONLY_PROPERTY( + versions, + "napi", + FIXED_ONE_BYTE_STRING(env->isolate(), node_napi_version)); + // process._promiseRejectEvent Local promiseRejectEvent = Object::New(env->isolate()); READONLY_DONT_ENUM_PROPERTY(process, @@ -3241,7 +3249,8 @@ void SetupProcessObject(Environment* env, // process.release Local release = Object::New(env->isolate()); READONLY_PROPERTY(process, "release", release); - READONLY_PROPERTY(release, "name", OneByteString(env->isolate(), "node")); + READONLY_PROPERTY(release, "name", + OneByteString(env->isolate(), NODE_RELEASE)); #if NODE_VERSION_IS_LTS READONLY_PROPERTY(release, "lts", @@ -3719,6 +3728,8 @@ static void PrintHelp() { " --throw-deprecation throw an exception anytime a deprecated " "function is used\n" " --no-warnings silence all process warnings\n" + " --napi-modules load N-API modules (no-op - option kept for " + " compatibility)\n" " --trace-warnings show stack traces on process warnings\n" " --redirect-warnings=path\n" " write warnings to path instead of stderr\n" @@ -3964,6 +3975,8 @@ static void ParseArgs(int* argc, force_repl = true; } else if (strcmp(arg, "--no-deprecation") == 0) { no_deprecation = true; + } else if (strcmp(arg, "--napi-modules") == 0) { + // no-op } else if (strcmp(arg, "--no-warnings") == 0) { no_process_warnings = true; } else if (strcmp(arg, "--trace-warnings") == 0) { diff --git a/src/node_api.cc b/src/node_api.cc new file mode 100644 index 00000000000000..fbe468b5a30701 --- /dev/null +++ b/src/node_api.cc @@ -0,0 +1,3670 @@ +#include +#include +#include // INT_MAX +#include +#include +#include +#include +#include "uv.h" +#include "node_api.h" +#include "node_internals.h" +#include "env-inl.h" +#include "node_api_backport.h" + +static +napi_status napi_set_last_error(napi_env env, napi_status error_code, + uint32_t engine_error_code = 0, + void* engine_reserved = nullptr); +static napi_status napi_clear_last_error(napi_env env); + +struct napi_env__ { + explicit napi_env__(v8::Isolate* _isolate, uv_loop_t *_loop): + isolate(_isolate), + has_instance_available(true), + last_error(), + loop(_loop) {} + ~napi_env__() { + last_exception.Reset(); + has_instance.Reset(); + wrap_template.Reset(); + function_data_template.Reset(); + accessor_data_template.Reset(); + } + v8::Isolate* isolate; + v8::Persistent last_exception; + v8::Persistent has_instance; + v8::Persistent wrap_template; + v8::Persistent function_data_template; + v8::Persistent accessor_data_template; + bool has_instance_available; + napi_extended_error_info last_error; + int open_handle_scopes = 0; + int open_callback_scopes = 0; + uv_loop_t* loop = nullptr; +}; + +#define NAPI_PRIVATE_KEY(context, suffix) \ + (node::Environment::GetCurrent((context))->napi_ ## suffix()) + +#define ENV_OBJECT_TEMPLATE(env, prefix, destination, field_count) \ + do { \ + if ((env)->prefix ## _template.IsEmpty()) { \ + (destination) = v8::ObjectTemplate::New(isolate); \ + (destination)->SetInternalFieldCount((field_count)); \ + (env)->prefix ## _template.Reset(isolate, (destination)); \ + } else { \ + (destination) = v8::Local::New( \ + isolate, env->prefix ## _template); \ + } \ + } while (0) + + +#define RETURN_STATUS_IF_FALSE(env, condition, status) \ + do { \ + if (!(condition)) { \ + return napi_set_last_error((env), (status)); \ + } \ + } while (0) + +#define CHECK_ENV(env) \ + do { \ + if ((env) == nullptr) { \ + return napi_invalid_arg; \ + } \ + } while (0) + +#define CHECK_ARG(env, arg) \ + RETURN_STATUS_IF_FALSE((env), ((arg) != nullptr), napi_invalid_arg) + +#define CHECK_MAYBE_EMPTY(env, maybe, status) \ + RETURN_STATUS_IF_FALSE((env), !((maybe).IsEmpty()), (status)) + +#define CHECK_MAYBE_NOTHING(env, maybe, status) \ + RETURN_STATUS_IF_FALSE((env), !((maybe).IsNothing()), (status)) + +// NAPI_PREAMBLE is not wrapped in do..while: try_catch must have function scope +#define NAPI_PREAMBLE(env) \ + CHECK_ENV((env)); \ + RETURN_STATUS_IF_FALSE((env), (env)->last_exception.IsEmpty(), \ + napi_pending_exception); \ + napi_clear_last_error((env)); \ + v8impl::TryCatch try_catch((env)) + +#define CHECK_TO_TYPE(env, type, context, result, src, status) \ + do { \ + CHECK_ARG((env), (src)); \ + auto maybe = v8impl::V8LocalValueFromJsValue((src))->To##type((context)); \ + CHECK_MAYBE_EMPTY((env), maybe, (status)); \ + (result) = maybe.ToLocalChecked(); \ + } while (0) + +#define CHECK_TO_FUNCTION(env, result, src) \ + do { \ + CHECK_ARG((env), (src)); \ + v8::Local v8value = v8impl::V8LocalValueFromJsValue((src)); \ + RETURN_STATUS_IF_FALSE((env), v8value->IsFunction(), napi_invalid_arg); \ + (result) = v8value.As(); \ + } while (0) + +#define CHECK_TO_OBJECT(env, context, result, src) \ + CHECK_TO_TYPE((env), Object, (context), (result), (src), napi_object_expected) + +#define CHECK_TO_STRING(env, context, result, src) \ + CHECK_TO_TYPE((env), String, (context), (result), (src), napi_string_expected) + +#define CHECK_TO_NUMBER(env, context, result, src) \ + CHECK_TO_TYPE((env), Number, (context), (result), (src), napi_number_expected) + +#define CHECK_TO_BOOL(env, context, result, src) \ + CHECK_TO_TYPE((env), Boolean, (context), (result), (src), \ + napi_boolean_expected) + +// n-api defines NAPI_AUTO_LENGHTH as the indicator that a string +// is null terminated. For V8 the equivalent is -1. The assert +// validates that our cast of NAPI_AUTO_LENGTH results in -1 as +// needed by V8. +#define CHECK_NEW_FROM_UTF8_LEN(env, result, str, len) \ + do { \ + static_assert(static_cast(NAPI_AUTO_LENGTH) == -1, \ + "Casting NAPI_AUTO_LENGTH to int must result in -1"); \ + RETURN_STATUS_IF_FALSE((env), \ + (len == NAPI_AUTO_LENGTH) || len <= INT_MAX, \ + napi_invalid_arg); \ + auto str_maybe = v8::String::NewFromUtf8( \ + (env)->isolate, (str), v8::NewStringType::kInternalized, \ + static_cast(len)); \ + CHECK_MAYBE_EMPTY((env), str_maybe, napi_generic_failure); \ + (result) = str_maybe.ToLocalChecked(); \ + } while (0) + +#define CHECK_NEW_FROM_UTF8(env, result, str) \ + CHECK_NEW_FROM_UTF8_LEN((env), (result), (str), NAPI_AUTO_LENGTH) + +#define GET_RETURN_STATUS(env) \ + (!try_catch.HasCaught() ? napi_ok \ + : napi_set_last_error((env), napi_pending_exception)) + +#define THROW_RANGE_ERROR_IF_FALSE(env, condition, error, message) \ + do { \ + if (!(condition)) { \ + napi_throw_range_error((env), (error), (message)); \ + return napi_set_last_error((env), napi_generic_failure); \ + } \ + } while (0) + +#define CREATE_TYPED_ARRAY( \ + env, type, size_of_element, buffer, byte_offset, length, out) \ + do { \ + if ((size_of_element) > 1) { \ + THROW_RANGE_ERROR_IF_FALSE( \ + (env), (byte_offset) % (size_of_element) == 0, \ + "ERR_NAPI_INVALID_TYPEDARRAY_ALIGNMENT", \ + "start offset of "#type" should be a multiple of "#size_of_element); \ + } \ + THROW_RANGE_ERROR_IF_FALSE((env), (length) * (size_of_element) + \ + (byte_offset) <= buffer->ByteLength(), \ + "ERR_NAPI_INVALID_TYPEDARRAY_LENGTH", \ + "Invalid typed array length"); \ + (out) = v8::type::New((buffer), (byte_offset), (length)); \ + } while (0) + +#define NAPI_CALL_INTO_MODULE(env, call, handle_exception) \ + do { \ + int open_handle_scopes = (env)->open_handle_scopes; \ + int open_callback_scopes = (env)->open_callback_scopes; \ + napi_clear_last_error((env)); \ + call; \ + CHECK_EQ((env)->open_handle_scopes, open_handle_scopes); \ + CHECK_EQ((env)->open_callback_scopes, open_callback_scopes); \ + if (!(env)->last_exception.IsEmpty()) { \ + handle_exception( \ + v8::Local::New((env)->isolate, (env)->last_exception)); \ + (env)->last_exception.Reset(); \ + } \ + } while (0) + +#define NAPI_CALL_INTO_MODULE_THROW(env, call) \ + NAPI_CALL_INTO_MODULE((env), call, (env)->isolate->ThrowException) + +namespace v8impl { + +// convert from n-api property attributes to v8::PropertyAttribute +static inline v8::PropertyAttribute V8PropertyAttributesFromDescriptor( + const napi_property_descriptor* descriptor) { + unsigned int attribute_flags = v8::PropertyAttribute::None; + + if (descriptor->getter != nullptr || descriptor->setter != nullptr) { + // The napi_writable attribute is ignored for accessor descriptors, but + // V8 requires the ReadOnly attribute to match nonexistence of a setter. + attribute_flags |= (descriptor->setter == nullptr ? + v8::PropertyAttribute::ReadOnly : v8::PropertyAttribute::None); + } else if ((descriptor->attributes & napi_writable) == 0) { + attribute_flags |= v8::PropertyAttribute::ReadOnly; + } + + if ((descriptor->attributes & napi_enumerable) == 0) { + attribute_flags |= v8::PropertyAttribute::DontEnum; + } + if ((descriptor->attributes & napi_configurable) == 0) { + attribute_flags |= v8::PropertyAttribute::DontDelete; + } + + return static_cast(attribute_flags); +} + +v8::Isolate* V8IsolateFromJsEnv(napi_env e) { + return reinterpret_cast(e); +} + +class HandleScopeWrapper { + public: + explicit HandleScopeWrapper(v8::Isolate* isolate) : scope(isolate) {} + + private: + v8::HandleScope scope; +}; + +// In node v0.10 version of v8, there is no EscapableHandleScope and the +// node v0.10 port use HandleScope::Close(Local v) to mimic the behavior +// of a EscapableHandleScope::Escape(Local v), but it is not the same +// semantics. This is an example of where the api abstraction fail to work +// across different versions. +class EscapableHandleScopeWrapper { + public: + explicit EscapableHandleScopeWrapper(v8::Isolate* isolate) + : scope(isolate), escape_called_(false) {} + bool escape_called() const { + return escape_called_; + } + template + v8::Local Escape(v8::Local handle) { + escape_called_ = true; + return scope.Escape(handle); + } + + private: + v8::EscapableHandleScope scope; + bool escape_called_; +}; + +static +napi_handle_scope JsHandleScopeFromV8HandleScope(HandleScopeWrapper* s) { + return reinterpret_cast(s); +} + +static +HandleScopeWrapper* V8HandleScopeFromJsHandleScope(napi_handle_scope s) { + return reinterpret_cast(s); +} + +static +napi_escapable_handle_scope JsEscapableHandleScopeFromV8EscapableHandleScope( + EscapableHandleScopeWrapper* s) { + return reinterpret_cast(s); +} + +static +EscapableHandleScopeWrapper* +V8EscapableHandleScopeFromJsEscapableHandleScope( + napi_escapable_handle_scope s) { + return reinterpret_cast(s); +} + +static +napi_callback_scope JsCallbackScopeFromV8CallbackScope( + node::CallbackScope* s) { + return reinterpret_cast(s); +} + +static +node::CallbackScope* V8CallbackScopeFromJsCallbackScope( + napi_callback_scope s) { + return reinterpret_cast(s); +} + +//=== Conversion between V8 Handles and napi_value ======================== + +// This asserts v8::Local<> will always be implemented with a single +// pointer field so that we can pass it around as a void*. +static_assert(sizeof(v8::Local) == sizeof(napi_value), + "Cannot convert between v8::Local and napi_value"); + +static +napi_deferred JsDeferredFromV8Persistent(v8::Persistent* local) { + return reinterpret_cast(local); +} + +static +v8::Persistent* V8PersistentFromJsDeferred(napi_deferred local) { + return reinterpret_cast*>(local); +} + +static +napi_value JsValueFromV8LocalValue(v8::Local local) { + return reinterpret_cast(*local); +} + +static +v8::Local V8LocalValueFromJsValue(napi_value v) { + v8::Local local; + memcpy(&local, &v, sizeof(v)); + return local; +} + +static inline void trigger_fatal_exception( + napi_env env, v8::Local local_err) { + v8::Local local_msg = + v8::Exception::CreateMessage(env->isolate, local_err); + node::FatalException(env->isolate, local_err, local_msg); +} + +static inline napi_status V8NameFromPropertyDescriptor(napi_env env, + const napi_property_descriptor* p, + v8::Local* result) { + if (p->utf8name != nullptr) { + CHECK_NEW_FROM_UTF8(env, *result, p->utf8name); + } else { + v8::Local property_value = + v8impl::V8LocalValueFromJsValue(p->name); + + RETURN_STATUS_IF_FALSE(env, property_value->IsName(), napi_name_expected); + *result = property_value.As(); + } + + return napi_ok; +} + +// Adapter for napi_finalize callbacks. +class Finalizer { + protected: + Finalizer(napi_env env, + napi_finalize finalize_callback, + void* finalize_data, + void* finalize_hint) + : _env(env), + _finalize_callback(finalize_callback), + _finalize_data(finalize_data), + _finalize_hint(finalize_hint) { + } + + ~Finalizer() { + } + + public: + static Finalizer* New(napi_env env, + napi_finalize finalize_callback = nullptr, + void* finalize_data = nullptr, + void* finalize_hint = nullptr) { + return new Finalizer( + env, finalize_callback, finalize_data, finalize_hint); + } + + static void Delete(Finalizer* finalizer) { + delete finalizer; + } + + // node::Buffer::FreeCallback + static void FinalizeBufferCallback(char* data, void* hint) { + Finalizer* finalizer = static_cast(hint); + if (finalizer->_finalize_callback != nullptr) { + NAPI_CALL_INTO_MODULE_THROW(finalizer->_env, + finalizer->_finalize_callback( + finalizer->_env, + data, + finalizer->_finalize_hint)); + } + + Delete(finalizer); + } + + protected: + napi_env _env; + napi_finalize _finalize_callback; + void* _finalize_data; + void* _finalize_hint; +}; + +// Wrapper around v8::Persistent that implements reference counting. +class Reference : private Finalizer { + private: + Reference(napi_env env, + v8::Local value, + uint32_t initial_refcount, + bool delete_self, + napi_finalize finalize_callback, + void* finalize_data, + void* finalize_hint) + : Finalizer(env, finalize_callback, finalize_data, finalize_hint), + _persistent(env->isolate, value), + _refcount(initial_refcount), + _delete_self(delete_self) { + if (initial_refcount == 0) { + _persistent.SetWeak( + this, FinalizeCallback, v8::WeakCallbackType::kParameter); + _persistent.MarkIndependent(); + } + } + + ~Reference() { + // The V8 Persistent class currently does not reset in its destructor: + // see NonCopyablePersistentTraits::kResetInDestructor = false. + // (Comments there claim that might change in the future.) + // To avoid memory leaks, it is better to reset at this time, however + // care must be taken to avoid attempting this after the Isolate has + // shut down, for example via a static (atexit) destructor. + _persistent.Reset(); + } + + public: + void* Data() { + return _finalize_data; + } + + static Reference* New(napi_env env, + v8::Local value, + uint32_t initial_refcount, + bool delete_self, + napi_finalize finalize_callback = nullptr, + void* finalize_data = nullptr, + void* finalize_hint = nullptr) { + return new Reference(env, + value, + initial_refcount, + delete_self, + finalize_callback, + finalize_data, + finalize_hint); + } + + static void Delete(Reference* reference) { + delete reference; + } + + uint32_t Ref() { + if (++_refcount == 1) { + _persistent.ClearWeak(); + } + + return _refcount; + } + + uint32_t Unref() { + if (_refcount == 0) { + return 0; + } + if (--_refcount == 0) { + _persistent.SetWeak( + this, FinalizeCallback, v8::WeakCallbackType::kParameter); + _persistent.MarkIndependent(); + } + + return _refcount; + } + + uint32_t RefCount() { + return _refcount; + } + + v8::Local Get() { + if (_persistent.IsEmpty()) { + return v8::Local(); + } else { + return v8::Local::New(_env->isolate, _persistent); + } + } + + private: + static void FinalizeCallback(const v8::WeakCallbackInfo& data) { + Reference* reference = data.GetParameter(); + reference->_persistent.Reset(); + + // Check before calling the finalize callback, because the callback might + // delete it. + bool delete_self = reference->_delete_self; + napi_env env = reference->_env; + + if (reference->_finalize_callback != nullptr) { + NAPI_CALL_INTO_MODULE_THROW(env, + reference->_finalize_callback( + reference->_env, + reference->_finalize_data, + reference->_finalize_hint)); + } + + if (delete_self) { + Delete(reference); + } + } + + v8::Persistent _persistent; + uint32_t _refcount; + bool _delete_self; +}; + +class TryCatch : public v8::TryCatch { + public: + explicit TryCatch(napi_env env) + : v8::TryCatch(env->isolate), _env(env) {} + + ~TryCatch() { + if (HasCaught()) { + _env->last_exception.Reset(_env->isolate, Exception()); + } + } + + private: + napi_env _env; +}; + +//=== Function napi_callback wrapper ================================= + +static const int kDataIndex = 0; +static const int kEnvIndex = 1; + +static const int kFunctionIndex = 2; +static const int kFunctionFieldCount = 3; + +static const int kGetterIndex = 2; +static const int kSetterIndex = 3; +static const int kAccessorFieldCount = 4; + +// Base class extended by classes that wrap V8 function and property callback +// info. +class CallbackWrapper { + public: + CallbackWrapper(napi_value this_arg, size_t args_length, void* data) + : _this(this_arg), _args_length(args_length), _data(data) {} + + virtual napi_value GetNewTarget() = 0; + virtual void Args(napi_value* buffer, size_t bufferlength) = 0; + virtual void SetReturnValue(napi_value value) = 0; + + napi_value This() { return _this; } + + size_t ArgsLength() { return _args_length; } + + void* Data() { return _data; } + + protected: + const napi_value _this; + const size_t _args_length; + void* _data; +}; + +template +class CallbackWrapperBase : public CallbackWrapper { + public: + CallbackWrapperBase(const Info& cbinfo, const size_t args_length) + : CallbackWrapper(JsValueFromV8LocalValue(cbinfo.This()), + args_length, + nullptr), + _cbinfo(cbinfo), + _cbdata(v8::Local::Cast(cbinfo.Data())) { + _data = v8::Local::Cast(_cbdata->GetInternalField(kDataIndex)) + ->Value(); + } + + napi_value GetNewTarget() override { return nullptr; } + + protected: + void InvokeCallback() { + napi_callback_info cbinfo_wrapper = reinterpret_cast( + static_cast(this)); + napi_callback cb = reinterpret_cast( + v8::Local::Cast( + _cbdata->GetInternalField(kInternalFieldIndex))->Value()); + + napi_env env = static_cast( + v8::Local::Cast( + _cbdata->GetInternalField(kEnvIndex))->Value()); + + napi_value result; + NAPI_CALL_INTO_MODULE_THROW(env, result = cb(env, cbinfo_wrapper)); + + if (result != nullptr) { + this->SetReturnValue(result); + } + } + + const Info& _cbinfo; + const v8::Local _cbdata; +}; + +class FunctionCallbackWrapper + : public CallbackWrapperBase, + kFunctionIndex> { + public: + static void Invoke(const v8::FunctionCallbackInfo& info) { + FunctionCallbackWrapper cbwrapper(info); + cbwrapper.InvokeCallback(); + } + + explicit FunctionCallbackWrapper( + const v8::FunctionCallbackInfo& cbinfo) + : CallbackWrapperBase(cbinfo, cbinfo.Length()) {} + + napi_value GetNewTarget() override { + if (_cbinfo.IsConstructCall()) { + return v8impl::JsValueFromV8LocalValue(_cbinfo.NewTarget()); + } else { + return nullptr; + } + } + + /*virtual*/ + void Args(napi_value* buffer, size_t buffer_length) override { + size_t i = 0; + size_t min = std::min(buffer_length, _args_length); + + for (; i < min; i += 1) { + buffer[i] = v8impl::JsValueFromV8LocalValue(_cbinfo[i]); + } + + if (i < buffer_length) { + napi_value undefined = + v8impl::JsValueFromV8LocalValue(v8::Undefined(_cbinfo.GetIsolate())); + for (; i < buffer_length; i += 1) { + buffer[i] = undefined; + } + } + } + + /*virtual*/ + void SetReturnValue(napi_value value) override { + v8::Local val = v8impl::V8LocalValueFromJsValue(value); + _cbinfo.GetReturnValue().Set(val); + } +}; + +class GetterCallbackWrapper + : public CallbackWrapperBase, + kGetterIndex> { + public: + static void Invoke(v8::Local property, + const v8::PropertyCallbackInfo& info) { + GetterCallbackWrapper cbwrapper(info); + cbwrapper.InvokeCallback(); + } + + explicit GetterCallbackWrapper( + const v8::PropertyCallbackInfo& cbinfo) + : CallbackWrapperBase(cbinfo, 0) {} + + /*virtual*/ + void Args(napi_value* buffer, size_t buffer_length) override { + if (buffer_length > 0) { + napi_value undefined = + v8impl::JsValueFromV8LocalValue(v8::Undefined(_cbinfo.GetIsolate())); + for (size_t i = 0; i < buffer_length; i += 1) { + buffer[i] = undefined; + } + } + } + + /*virtual*/ + void SetReturnValue(napi_value value) override { + v8::Local val = v8impl::V8LocalValueFromJsValue(value); + _cbinfo.GetReturnValue().Set(val); + } +}; + +class SetterCallbackWrapper + : public CallbackWrapperBase, kSetterIndex> { + public: + static void Invoke(v8::Local property, + v8::Local value, + const v8::PropertyCallbackInfo& info) { + SetterCallbackWrapper cbwrapper(info, value); + cbwrapper.InvokeCallback(); + } + + SetterCallbackWrapper(const v8::PropertyCallbackInfo& cbinfo, + const v8::Local& value) + : CallbackWrapperBase(cbinfo, 1), _value(value) {} + + /*virtual*/ + void Args(napi_value* buffer, size_t buffer_length) override { + if (buffer_length > 0) { + buffer[0] = v8impl::JsValueFromV8LocalValue(_value); + + if (buffer_length > 1) { + napi_value undefined = v8impl::JsValueFromV8LocalValue( + v8::Undefined(_cbinfo.GetIsolate())); + for (size_t i = 1; i < buffer_length; i += 1) { + buffer[i] = undefined; + } + } + } + } + + /*virtual*/ + void SetReturnValue(napi_value value) override { + // Ignore any value returned from a setter callback. + } + + private: + const v8::Local& _value; +}; + +// Creates an object to be made available to the static function callback +// wrapper, used to retrieve the native callback function and data pointer. +static +v8::Local CreateFunctionCallbackData(napi_env env, + napi_callback cb, + void* data) { + v8::Isolate* isolate = env->isolate; + v8::Local context = isolate->GetCurrentContext(); + + v8::Local otpl; + ENV_OBJECT_TEMPLATE(env, function_data, otpl, v8impl::kFunctionFieldCount); + v8::Local cbdata = otpl->NewInstance(context).ToLocalChecked(); + + cbdata->SetInternalField( + v8impl::kEnvIndex, + v8::External::New(isolate, static_cast(env))); + cbdata->SetInternalField( + v8impl::kFunctionIndex, + v8::External::New(isolate, reinterpret_cast(cb))); + cbdata->SetInternalField( + v8impl::kDataIndex, + v8::External::New(isolate, data)); + return cbdata; +} + +// Creates an object to be made available to the static getter/setter +// callback wrapper, used to retrieve the native getter/setter callback +// function and data pointer. +static +v8::Local CreateAccessorCallbackData(napi_env env, + napi_callback getter, + napi_callback setter, + void* data) { + v8::Isolate* isolate = env->isolate; + v8::Local context = isolate->GetCurrentContext(); + + v8::Local otpl; + ENV_OBJECT_TEMPLATE(env, accessor_data, otpl, v8impl::kAccessorFieldCount); + v8::Local cbdata = otpl->NewInstance(context).ToLocalChecked(); + + cbdata->SetInternalField( + v8impl::kEnvIndex, + v8::External::New(isolate, static_cast(env))); + + if (getter != nullptr) { + cbdata->SetInternalField( + v8impl::kGetterIndex, + v8::External::New(isolate, reinterpret_cast(getter))); + } + + if (setter != nullptr) { + cbdata->SetInternalField( + v8impl::kSetterIndex, + v8::External::New(isolate, reinterpret_cast(setter))); + } + + cbdata->SetInternalField( + v8impl::kDataIndex, + v8::External::New(isolate, data)); + return cbdata; +} + +static void DeleteEnv(napi_env env, void* data, void* hint) { + delete env; +} + +static +napi_env GetEnv(v8::Local context) { + napi_env result; + + auto isolate = context->GetIsolate(); + auto global = context->Global(); + + // In the case of the string for which we grab the private and the value of + // the private on the global object we can call .ToLocalChecked() directly + // because we need to stop hard if either of them is empty. + // + // Re https://github.com/nodejs/node/pull/14217#discussion_r128775149 + auto value = global->GetPrivate(context, NAPI_PRIVATE_KEY(context, env)) + .ToLocalChecked(); + + if (value->IsExternal()) { + result = static_cast(value.As()->Value()); + } else { + result = new napi_env__(isolate, node::GetCurrentEventLoop(isolate)); + auto external = v8::External::New(isolate, result); + + // We must also stop hard if the result of assigning the env to the global + // is either nothing or false. + CHECK(global->SetPrivate(context, NAPI_PRIVATE_KEY(context, env), external) + .FromJust()); + + // Create a self-destructing reference to external that will get rid of the + // napi_env when external goes out of scope. + Reference::New(result, external, 0, true, DeleteEnv, nullptr, nullptr); + } + + return result; +} + +enum UnwrapAction { + KeepWrap, + RemoveWrap +}; + +static +napi_status Unwrap(napi_env env, + napi_value js_object, + void** result, + UnwrapAction action) { + NAPI_PREAMBLE(env); + CHECK_ARG(env, js_object); + if (action == KeepWrap) { + CHECK_ARG(env, result); + } + + v8::Isolate* isolate = env->isolate; + v8::Local context = isolate->GetCurrentContext(); + + v8::Local value = v8impl::V8LocalValueFromJsValue(js_object); + RETURN_STATUS_IF_FALSE(env, value->IsObject(), napi_invalid_arg); + v8::Local obj = value.As(); + + auto val = obj->GetPrivate(context, NAPI_PRIVATE_KEY(context, wrapper)) + .ToLocalChecked(); + RETURN_STATUS_IF_FALSE(env, val->IsExternal(), napi_invalid_arg); + Reference* reference = + static_cast(val.As()->Value()); + + if (result) { + *result = reference->Data(); + } + + if (action == RemoveWrap) { + CHECK(obj->DeletePrivate(context, NAPI_PRIVATE_KEY(context, wrapper)) + .FromJust()); + Reference::Delete(reference); + } + + return GET_RETURN_STATUS(env); +} + +static +napi_status ConcludeDeferred(napi_env env, + napi_deferred deferred, + napi_value result, + bool is_resolved) { + NAPI_PREAMBLE(env); + CHECK_ARG(env, result); + + v8::Local context = env->isolate->GetCurrentContext(); + v8::Persistent* deferred_ref = + V8PersistentFromJsDeferred(deferred); + v8::Local v8_deferred = + v8::Local::New(env->isolate, *deferred_ref); + + auto v8_resolver = v8::Local::Cast(v8_deferred); + + v8::Maybe success = is_resolved ? + v8_resolver->Resolve(context, v8impl::V8LocalValueFromJsValue(result)) : + v8_resolver->Reject(context, v8impl::V8LocalValueFromJsValue(result)); + + deferred_ref->Reset(); + delete deferred_ref; + + RETURN_STATUS_IF_FALSE(env, success.FromMaybe(false), napi_generic_failure); + + return GET_RETURN_STATUS(env); +} + +} // end of namespace v8impl + +// Intercepts the Node-V8 module registration callback. Converts parameters +// to NAPI equivalents and then calls the registration callback specified +// by the NAPI module. +void napi_module_register_cb(v8::Local exports, + v8::Local module, + v8::Local context, + void* priv) { + napi_module* mod = static_cast(priv); + + // Create a new napi_env for this module or reference one if a pre-existing + // one is found. + napi_env env = v8impl::GetEnv(context); + + napi_value _exports; + NAPI_CALL_INTO_MODULE_THROW(env, + _exports = mod->nm_register_func(env, + v8impl::JsValueFromV8LocalValue(exports))); + + // If register function returned a non-null exports object different from + // the exports object we passed it, set that as the "exports" property of + // the module. + if (_exports != nullptr && + _exports != v8impl::JsValueFromV8LocalValue(exports)) { + napi_value _module = v8impl::JsValueFromV8LocalValue(module); + napi_set_named_property(env, _module, "exports", _exports); + } +} + +// Registers a NAPI module. +void napi_module_register(napi_module* mod) { + node::node_module* nm = new node::node_module { + -1, + mod->nm_flags, + nullptr, + mod->nm_filename, + nullptr, + napi_module_register_cb, + mod->nm_modname, + mod, // priv + nullptr, + }; + node::node_module_register(nm); +} + +// Warning: Keep in-sync with napi_status enum +static +const char* error_messages[] = {nullptr, + "Invalid argument", + "An object was expected", + "A string was expected", + "A string or symbol was expected", + "A function was expected", + "A number was expected", + "A boolean was expected", + "An array was expected", + "Unknown failure", + "An exception is pending", + "The async work item was cancelled", + "napi_escape_handle already called on scope", + "Invalid handle scope usage", + "Invalid callback scope usage"}; + +static inline napi_status napi_clear_last_error(napi_env env) { + env->last_error.error_code = napi_ok; + + // TODO(boingoing): Should this be a callback? + env->last_error.engine_error_code = 0; + env->last_error.engine_reserved = nullptr; + return napi_ok; +} + +static inline +napi_status napi_set_last_error(napi_env env, napi_status error_code, + uint32_t engine_error_code, + void* engine_reserved) { + env->last_error.error_code = error_code; + env->last_error.engine_error_code = engine_error_code; + env->last_error.engine_reserved = engine_reserved; + return error_code; +} + +napi_status napi_get_last_error_info(napi_env env, + const napi_extended_error_info** result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + + // you must update this assert to reference the last message + // in the napi_status enum each time a new error message is added. + // We don't have a napi_status_last as this would result in an ABI + // change each time a message was added. + static_assert( + node::arraysize(error_messages) == napi_callback_scope_mismatch + 1, + "Count of error messages must match count of error values"); + CHECK_LE(env->last_error.error_code, napi_callback_scope_mismatch); + + // Wait until someone requests the last error information to fetch the error + // message string + env->last_error.error_message = + error_messages[env->last_error.error_code]; + + *result = &(env->last_error); + return napi_ok; +} + +napi_status napi_fatal_exception(napi_env env, napi_value err) { + NAPI_PREAMBLE(env); + CHECK_ARG(env, err); + + v8::Local local_err = v8impl::V8LocalValueFromJsValue(err); + v8impl::trigger_fatal_exception(env, local_err); + + return napi_clear_last_error(env); +} + +NAPI_NO_RETURN void napi_fatal_error(const char* location, + size_t location_len, + const char* message, + size_t message_len) { + std::string location_string; + std::string message_string; + + if (location_len != NAPI_AUTO_LENGTH) { + location_string.assign( + const_cast(location), location_len); + } else { + location_string.assign( + const_cast(location), strlen(location)); + } + + if (message_len != NAPI_AUTO_LENGTH) { + message_string.assign( + const_cast(message), message_len); + } else { + message_string.assign( + const_cast(message), strlen(message)); + } + + node::FatalError(location_string.c_str(), message_string.c_str()); +} + +napi_status napi_create_function(napi_env env, + const char* utf8name, + size_t length, + napi_callback cb, + void* callback_data, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(env, result); + CHECK_ARG(env, cb); + + v8::Isolate* isolate = env->isolate; + v8::Local return_value; + v8::EscapableHandleScope scope(isolate); + v8::Local cbdata = + v8impl::CreateFunctionCallbackData(env, cb, callback_data); + + RETURN_STATUS_IF_FALSE(env, !cbdata.IsEmpty(), napi_generic_failure); + + v8::Local tpl = v8::FunctionTemplate::New( + isolate, v8impl::FunctionCallbackWrapper::Invoke, cbdata); + + v8::Local context = isolate->GetCurrentContext(); + v8::MaybeLocal maybe_function = tpl->GetFunction(context); + CHECK_MAYBE_EMPTY(env, maybe_function, napi_generic_failure); + + return_value = scope.Escape(maybe_function.ToLocalChecked()); + + if (utf8name != nullptr) { + v8::Local name_string; + CHECK_NEW_FROM_UTF8_LEN(env, name_string, utf8name, length); + return_value->SetName(name_string); + } + + *result = v8impl::JsValueFromV8LocalValue(return_value); + + return GET_RETURN_STATUS(env); +} + +napi_status napi_define_class(napi_env env, + const char* utf8name, + size_t length, + napi_callback constructor, + void* callback_data, + size_t property_count, + const napi_property_descriptor* properties, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(env, result); + CHECK_ARG(env, constructor); + + v8::Isolate* isolate = env->isolate; + + v8::EscapableHandleScope scope(isolate); + v8::Local cbdata = + v8impl::CreateFunctionCallbackData(env, constructor, callback_data); + + RETURN_STATUS_IF_FALSE(env, !cbdata.IsEmpty(), napi_generic_failure); + + v8::Local tpl = v8::FunctionTemplate::New( + isolate, v8impl::FunctionCallbackWrapper::Invoke, cbdata); + + v8::Local name_string; + CHECK_NEW_FROM_UTF8_LEN(env, name_string, utf8name, length); + tpl->SetClassName(name_string); + + size_t static_property_count = 0; + for (size_t i = 0; i < property_count; i++) { + const napi_property_descriptor* p = properties + i; + + if ((p->attributes & napi_static) != 0) { + // Static properties are handled separately below. + static_property_count++; + continue; + } + + v8::Local property_name; + napi_status status = + v8impl::V8NameFromPropertyDescriptor(env, p, &property_name); + + if (status != napi_ok) { + return napi_set_last_error(env, status); + } + + v8::PropertyAttribute attributes = + v8impl::V8PropertyAttributesFromDescriptor(p); + + // This code is similar to that in napi_define_properties(); the + // difference is it applies to a template instead of an object. + if (p->getter != nullptr || p->setter != nullptr) { + v8::Local cbdata = v8impl::CreateAccessorCallbackData( + env, p->getter, p->setter, p->data); + + tpl->PrototypeTemplate()->SetAccessor( + property_name, + p->getter ? v8impl::GetterCallbackWrapper::Invoke : nullptr, + p->setter ? v8impl::SetterCallbackWrapper::Invoke : nullptr, + cbdata, + v8::AccessControl::DEFAULT, + attributes); + } else if (p->method != nullptr) { + v8::Local cbdata = + v8impl::CreateFunctionCallbackData(env, p->method, p->data); + + RETURN_STATUS_IF_FALSE(env, !cbdata.IsEmpty(), napi_generic_failure); + + v8::Local t = + v8::FunctionTemplate::New(isolate, + v8impl::FunctionCallbackWrapper::Invoke, + cbdata, + v8::Signature::New(isolate, tpl)); + + tpl->PrototypeTemplate()->Set(property_name, t, attributes); + } else { + v8::Local value = v8impl::V8LocalValueFromJsValue(p->value); + tpl->PrototypeTemplate()->Set(property_name, value, attributes); + } + } + + *result = v8impl::JsValueFromV8LocalValue(scope.Escape(tpl->GetFunction())); + + if (static_property_count > 0) { + std::vector static_descriptors; + static_descriptors.reserve(static_property_count); + + for (size_t i = 0; i < property_count; i++) { + const napi_property_descriptor* p = properties + i; + if ((p->attributes & napi_static) != 0) { + static_descriptors.push_back(*p); + } + } + + napi_status status = + napi_define_properties(env, + *result, + static_descriptors.size(), + static_descriptors.data()); + if (status != napi_ok) return status; + } + + return GET_RETURN_STATUS(env); +} + +napi_status napi_get_property_names(napi_env env, + napi_value object, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(env, result); + + v8::Isolate* isolate = env->isolate; + v8::Local context = isolate->GetCurrentContext(); + v8::Local obj; + CHECK_TO_OBJECT(env, context, obj, object); + + auto maybe_propertynames = obj->GetPropertyNames(context); + + CHECK_MAYBE_EMPTY(env, maybe_propertynames, napi_generic_failure); + + *result = v8impl::JsValueFromV8LocalValue( + maybe_propertynames.ToLocalChecked()); + return GET_RETURN_STATUS(env); +} + +napi_status napi_set_property(napi_env env, + napi_value object, + napi_value key, + napi_value value) { + NAPI_PREAMBLE(env); + CHECK_ARG(env, key); + CHECK_ARG(env, value); + + v8::Isolate* isolate = env->isolate; + v8::Local context = isolate->GetCurrentContext(); + v8::Local obj; + + CHECK_TO_OBJECT(env, context, obj, object); + + v8::Local k = v8impl::V8LocalValueFromJsValue(key); + v8::Local val = v8impl::V8LocalValueFromJsValue(value); + + v8::Maybe set_maybe = obj->Set(context, k, val); + + RETURN_STATUS_IF_FALSE(env, set_maybe.FromMaybe(false), napi_generic_failure); + return GET_RETURN_STATUS(env); +} + +napi_status napi_has_property(napi_env env, + napi_value object, + napi_value key, + bool* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(env, result); + CHECK_ARG(env, key); + + v8::Isolate* isolate = env->isolate; + v8::Local context = isolate->GetCurrentContext(); + v8::Local obj; + + CHECK_TO_OBJECT(env, context, obj, object); + + v8::Local k = v8impl::V8LocalValueFromJsValue(key); + v8::Maybe has_maybe = obj->Has(context, k); + + CHECK_MAYBE_NOTHING(env, has_maybe, napi_generic_failure); + + *result = has_maybe.FromMaybe(false); + return GET_RETURN_STATUS(env); +} + +napi_status napi_get_property(napi_env env, + napi_value object, + napi_value key, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(env, key); + CHECK_ARG(env, result); + + v8::Isolate* isolate = env->isolate; + v8::Local context = isolate->GetCurrentContext(); + v8::Local k = v8impl::V8LocalValueFromJsValue(key); + v8::Local obj; + + CHECK_TO_OBJECT(env, context, obj, object); + + auto get_maybe = obj->Get(context, k); + + CHECK_MAYBE_EMPTY(env, get_maybe, napi_generic_failure); + + v8::Local val = get_maybe.ToLocalChecked(); + *result = v8impl::JsValueFromV8LocalValue(val); + return GET_RETURN_STATUS(env); +} + +napi_status napi_delete_property(napi_env env, + napi_value object, + napi_value key, + bool* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(env, key); + + v8::Isolate* isolate = env->isolate; + v8::Local context = isolate->GetCurrentContext(); + v8::Local k = v8impl::V8LocalValueFromJsValue(key); + v8::Local obj; + + CHECK_TO_OBJECT(env, context, obj, object); + v8::Maybe delete_maybe = obj->Delete(context, k); + CHECK_MAYBE_NOTHING(env, delete_maybe, napi_generic_failure); + + if (result != nullptr) + *result = delete_maybe.FromMaybe(false); + + return GET_RETURN_STATUS(env); +} + +napi_status napi_has_own_property(napi_env env, + napi_value object, + napi_value key, + bool* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(env, key); + + v8::Isolate* isolate = env->isolate; + v8::Local context = isolate->GetCurrentContext(); + v8::Local obj; + + CHECK_TO_OBJECT(env, context, obj, object); + v8::Local k = v8impl::V8LocalValueFromJsValue(key); + RETURN_STATUS_IF_FALSE(env, k->IsName(), napi_name_expected); + v8::Maybe has_maybe = obj->HasOwnProperty(context, k.As()); + CHECK_MAYBE_NOTHING(env, has_maybe, napi_generic_failure); + *result = has_maybe.FromMaybe(false); + + return GET_RETURN_STATUS(env); +} + +napi_status napi_set_named_property(napi_env env, + napi_value object, + const char* utf8name, + napi_value value) { + NAPI_PREAMBLE(env); + CHECK_ARG(env, value); + + v8::Isolate* isolate = env->isolate; + v8::Local context = isolate->GetCurrentContext(); + v8::Local obj; + + CHECK_TO_OBJECT(env, context, obj, object); + + v8::Local key; + CHECK_NEW_FROM_UTF8(env, key, utf8name); + + v8::Local val = v8impl::V8LocalValueFromJsValue(value); + + v8::Maybe set_maybe = obj->Set(context, key, val); + + RETURN_STATUS_IF_FALSE(env, set_maybe.FromMaybe(false), napi_generic_failure); + return GET_RETURN_STATUS(env); +} + +napi_status napi_has_named_property(napi_env env, + napi_value object, + const char* utf8name, + bool* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(env, result); + + v8::Isolate* isolate = env->isolate; + v8::Local context = isolate->GetCurrentContext(); + v8::Local obj; + + CHECK_TO_OBJECT(env, context, obj, object); + + v8::Local key; + CHECK_NEW_FROM_UTF8(env, key, utf8name); + + v8::Maybe has_maybe = obj->Has(context, key); + + CHECK_MAYBE_NOTHING(env, has_maybe, napi_generic_failure); + + *result = has_maybe.FromMaybe(false); + return GET_RETURN_STATUS(env); +} + +napi_status napi_get_named_property(napi_env env, + napi_value object, + const char* utf8name, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(env, result); + + v8::Isolate* isolate = env->isolate; + v8::Local context = isolate->GetCurrentContext(); + + v8::Local key; + CHECK_NEW_FROM_UTF8(env, key, utf8name); + + v8::Local obj; + + CHECK_TO_OBJECT(env, context, obj, object); + + auto get_maybe = obj->Get(context, key); + + CHECK_MAYBE_EMPTY(env, get_maybe, napi_generic_failure); + + v8::Local val = get_maybe.ToLocalChecked(); + *result = v8impl::JsValueFromV8LocalValue(val); + return GET_RETURN_STATUS(env); +} + +napi_status napi_set_element(napi_env env, + napi_value object, + uint32_t index, + napi_value value) { + NAPI_PREAMBLE(env); + CHECK_ARG(env, value); + + v8::Isolate* isolate = env->isolate; + v8::Local context = isolate->GetCurrentContext(); + v8::Local obj; + + CHECK_TO_OBJECT(env, context, obj, object); + + v8::Local val = v8impl::V8LocalValueFromJsValue(value); + auto set_maybe = obj->Set(context, index, val); + + RETURN_STATUS_IF_FALSE(env, set_maybe.FromMaybe(false), napi_generic_failure); + + return GET_RETURN_STATUS(env); +} + +napi_status napi_has_element(napi_env env, + napi_value object, + uint32_t index, + bool* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(env, result); + + v8::Isolate* isolate = env->isolate; + v8::Local context = isolate->GetCurrentContext(); + v8::Local obj; + + CHECK_TO_OBJECT(env, context, obj, object); + + v8::Maybe has_maybe = obj->Has(context, index); + + CHECK_MAYBE_NOTHING(env, has_maybe, napi_generic_failure); + + *result = has_maybe.FromMaybe(false); + return GET_RETURN_STATUS(env); +} + +napi_status napi_get_element(napi_env env, + napi_value object, + uint32_t index, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(env, result); + + v8::Isolate* isolate = env->isolate; + v8::Local context = isolate->GetCurrentContext(); + v8::Local obj; + + CHECK_TO_OBJECT(env, context, obj, object); + + auto get_maybe = obj->Get(context, index); + + CHECK_MAYBE_EMPTY(env, get_maybe, napi_generic_failure); + + *result = v8impl::JsValueFromV8LocalValue(get_maybe.ToLocalChecked()); + return GET_RETURN_STATUS(env); +} + +napi_status napi_delete_element(napi_env env, + napi_value object, + uint32_t index, + bool* result) { + NAPI_PREAMBLE(env); + + v8::Isolate* isolate = env->isolate; + v8::Local context = isolate->GetCurrentContext(); + v8::Local obj; + + CHECK_TO_OBJECT(env, context, obj, object); + v8::Maybe delete_maybe = obj->Delete(context, index); + CHECK_MAYBE_NOTHING(env, delete_maybe, napi_generic_failure); + + if (result != nullptr) + *result = delete_maybe.FromMaybe(false); + + return GET_RETURN_STATUS(env); +} + +napi_status napi_define_properties(napi_env env, + napi_value object, + size_t property_count, + const napi_property_descriptor* properties) { + NAPI_PREAMBLE(env); + if (property_count > 0) { + CHECK_ARG(env, properties); + } + + v8::Isolate* isolate = env->isolate; + v8::Local context = isolate->GetCurrentContext(); + + v8::Local obj; + CHECK_TO_OBJECT(env, context, obj, object); + + for (size_t i = 0; i < property_count; i++) { + const napi_property_descriptor* p = &properties[i]; + + v8::Local property_name; + napi_status status = + v8impl::V8NameFromPropertyDescriptor(env, p, &property_name); + + if (status != napi_ok) { + return napi_set_last_error(env, status); + } + + v8::PropertyAttribute attributes = + v8impl::V8PropertyAttributesFromDescriptor(p); + + if (p->getter != nullptr || p->setter != nullptr) { + v8::Local cbdata = v8impl::CreateAccessorCallbackData( + env, + p->getter, + p->setter, + p->data); + + auto set_maybe = obj->SetAccessor( + context, + property_name, + p->getter ? v8impl::GetterCallbackWrapper::Invoke : nullptr, + p->setter ? v8impl::SetterCallbackWrapper::Invoke : nullptr, + cbdata, + v8::AccessControl::DEFAULT, + attributes); + + if (!set_maybe.FromMaybe(false)) { + return napi_set_last_error(env, napi_invalid_arg); + } + } else if (p->method != nullptr) { + v8::Local cbdata = + v8impl::CreateFunctionCallbackData(env, p->method, p->data); + + RETURN_STATUS_IF_FALSE(env, !cbdata.IsEmpty(), napi_generic_failure); + + v8::Local t = v8::FunctionTemplate::New( + isolate, v8impl::FunctionCallbackWrapper::Invoke, cbdata); + + auto define_maybe = obj->DefineOwnProperty( + context, property_name, t->GetFunction(), attributes); + + if (!define_maybe.FromMaybe(false)) { + return napi_set_last_error(env, napi_generic_failure); + } + } else { + v8::Local value = v8impl::V8LocalValueFromJsValue(p->value); + + auto define_maybe = + obj->DefineOwnProperty(context, property_name, value, attributes); + + if (!define_maybe.FromMaybe(false)) { + return napi_set_last_error(env, napi_invalid_arg); + } + } + } + + 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); + CHECK_ARG(env, result); + + v8::Local val = v8impl::V8LocalValueFromJsValue(value); + + *result = val->IsArray(); + return napi_clear_last_error(env); +} + +napi_status napi_get_array_length(napi_env env, + napi_value value, + uint32_t* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(env, value); + CHECK_ARG(env, result); + + v8::Local val = v8impl::V8LocalValueFromJsValue(value); + RETURN_STATUS_IF_FALSE(env, val->IsArray(), napi_array_expected); + + v8::Local arr = val.As(); + *result = arr->Length(); + + return GET_RETURN_STATUS(env); +} + +napi_status napi_strict_equals(napi_env env, + napi_value lhs, + napi_value rhs, + bool* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(env, lhs); + CHECK_ARG(env, rhs); + CHECK_ARG(env, result); + + v8::Local a = v8impl::V8LocalValueFromJsValue(lhs); + v8::Local b = v8impl::V8LocalValueFromJsValue(rhs); + + *result = a->StrictEquals(b); + return GET_RETURN_STATUS(env); +} + +napi_status napi_get_prototype(napi_env env, + napi_value object, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(env, result); + + v8::Isolate* isolate = env->isolate; + v8::Local context = isolate->GetCurrentContext(); + + v8::Local obj; + CHECK_TO_OBJECT(env, context, obj, object); + + v8::Local val = obj->GetPrototype(); + *result = v8impl::JsValueFromV8LocalValue(val); + return GET_RETURN_STATUS(env); +} + +napi_status napi_create_object(napi_env env, napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + + *result = v8impl::JsValueFromV8LocalValue( + v8::Object::New(env->isolate)); + + return napi_clear_last_error(env); +} + +napi_status napi_create_array(napi_env env, napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + + *result = v8impl::JsValueFromV8LocalValue( + v8::Array::New(env->isolate)); + + return napi_clear_last_error(env); +} + +napi_status napi_create_array_with_length(napi_env env, + size_t length, + napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + + *result = v8impl::JsValueFromV8LocalValue( + v8::Array::New(env->isolate, length)); + + return napi_clear_last_error(env); +} + +napi_status napi_create_string_latin1(napi_env env, + const char* str, + size_t length, + napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + + auto isolate = env->isolate; + auto str_maybe = + v8::String::NewFromOneByte(isolate, + reinterpret_cast(str), + v8::NewStringType::kInternalized, + length); + CHECK_MAYBE_EMPTY(env, str_maybe, napi_generic_failure); + + *result = v8impl::JsValueFromV8LocalValue(str_maybe.ToLocalChecked()); + return napi_clear_last_error(env); +} + +napi_status napi_create_string_utf8(napi_env env, + const char* str, + size_t length, + napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + + v8::Local s; + CHECK_NEW_FROM_UTF8_LEN(env, s, str, length); + + *result = v8impl::JsValueFromV8LocalValue(s); + return napi_clear_last_error(env); +} + +napi_status napi_create_string_utf16(napi_env env, + const char16_t* str, + size_t length, + napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + + auto isolate = env->isolate; + auto str_maybe = + v8::String::NewFromTwoByte(isolate, + reinterpret_cast(str), + v8::NewStringType::kInternalized, + length); + CHECK_MAYBE_EMPTY(env, str_maybe, napi_generic_failure); + + *result = v8impl::JsValueFromV8LocalValue(str_maybe.ToLocalChecked()); + return napi_clear_last_error(env); +} + +napi_status napi_create_double(napi_env env, + double value, + napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + + *result = v8impl::JsValueFromV8LocalValue( + v8::Number::New(env->isolate, value)); + + return napi_clear_last_error(env); +} + +napi_status napi_create_int32(napi_env env, + int32_t value, + napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + + *result = v8impl::JsValueFromV8LocalValue( + v8::Integer::New(env->isolate, value)); + + return napi_clear_last_error(env); +} + +napi_status napi_create_uint32(napi_env env, + uint32_t value, + napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + + *result = v8impl::JsValueFromV8LocalValue( + v8::Integer::NewFromUnsigned(env->isolate, value)); + + return napi_clear_last_error(env); +} + +napi_status napi_create_int64(napi_env env, + int64_t value, + napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + + *result = v8impl::JsValueFromV8LocalValue( + v8::Number::New(env->isolate, static_cast(value))); + + return napi_clear_last_error(env); +} + +napi_status napi_get_boolean(napi_env env, bool value, napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + + v8::Isolate* isolate = env->isolate; + + if (value) { + *result = v8impl::JsValueFromV8LocalValue(v8::True(isolate)); + } else { + *result = v8impl::JsValueFromV8LocalValue(v8::False(isolate)); + } + + return napi_clear_last_error(env); +} + +napi_status napi_create_symbol(napi_env env, + napi_value description, + napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + + v8::Isolate* isolate = env->isolate; + + if (description == nullptr) { + *result = v8impl::JsValueFromV8LocalValue(v8::Symbol::New(isolate)); + } else { + v8::Local desc = v8impl::V8LocalValueFromJsValue(description); + RETURN_STATUS_IF_FALSE(env, desc->IsString(), napi_string_expected); + + *result = v8impl::JsValueFromV8LocalValue( + v8::Symbol::New(isolate, desc.As())); + } + + return napi_clear_last_error(env); +} + +static napi_status set_error_code(napi_env env, + v8::Local error, + napi_value code, + const char* code_cstring) { + if ((code != nullptr) || (code_cstring != nullptr)) { + v8::Isolate* isolate = env->isolate; + v8::Local context = isolate->GetCurrentContext(); + v8::Local err_object = error.As(); + + v8::Local code_value = v8impl::V8LocalValueFromJsValue(code); + if (code != nullptr) { + code_value = v8impl::V8LocalValueFromJsValue(code); + RETURN_STATUS_IF_FALSE(env, code_value->IsString(), napi_string_expected); + } else { + CHECK_NEW_FROM_UTF8(env, code_value, code_cstring); + } + + v8::Local code_key; + CHECK_NEW_FROM_UTF8(env, code_key, "code"); + + v8::Maybe set_maybe = err_object->Set(context, code_key, code_value); + RETURN_STATUS_IF_FALSE(env, + set_maybe.FromMaybe(false), + napi_generic_failure); + + // now update the name to be "name [code]" where name is the + // original name and code is the code associated with the Error + v8::Local name_string; + CHECK_NEW_FROM_UTF8(env, name_string, ""); + v8::Local name_key; + CHECK_NEW_FROM_UTF8(env, name_key, "name"); + + auto maybe_name = err_object->Get(context, name_key); + if (!maybe_name.IsEmpty()) { + v8::Local name = maybe_name.ToLocalChecked(); + if (name->IsString()) { + name_string = v8::String::Concat(name_string, name.As()); + } + } + name_string = v8::String::Concat(name_string, + FIXED_ONE_BYTE_STRING(isolate, " [")); + name_string = v8::String::Concat(name_string, code_value.As()); + name_string = v8::String::Concat(name_string, + FIXED_ONE_BYTE_STRING(isolate, "]")); + + set_maybe = err_object->Set(context, name_key, name_string); + RETURN_STATUS_IF_FALSE(env, + set_maybe.FromMaybe(false), + napi_generic_failure); + } + return napi_ok; +} + +napi_status napi_create_error(napi_env env, + napi_value code, + napi_value msg, + napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, msg); + CHECK_ARG(env, result); + + v8::Local message_value = v8impl::V8LocalValueFromJsValue(msg); + RETURN_STATUS_IF_FALSE(env, message_value->IsString(), napi_string_expected); + + v8::Local error_obj = + v8::Exception::Error(message_value.As()); + napi_status status = set_error_code(env, error_obj, code, nullptr); + if (status != napi_ok) return status; + + *result = v8impl::JsValueFromV8LocalValue(error_obj); + + return napi_clear_last_error(env); +} + +napi_status napi_create_type_error(napi_env env, + napi_value code, + napi_value msg, + napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, msg); + CHECK_ARG(env, result); + + v8::Local message_value = v8impl::V8LocalValueFromJsValue(msg); + RETURN_STATUS_IF_FALSE(env, message_value->IsString(), napi_string_expected); + + v8::Local error_obj = + v8::Exception::TypeError(message_value.As()); + napi_status status = set_error_code(env, error_obj, code, nullptr); + if (status != napi_ok) return status; + + *result = v8impl::JsValueFromV8LocalValue(error_obj); + + return napi_clear_last_error(env); +} + +napi_status napi_create_range_error(napi_env env, + napi_value code, + napi_value msg, + napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, msg); + CHECK_ARG(env, result); + + v8::Local message_value = v8impl::V8LocalValueFromJsValue(msg); + RETURN_STATUS_IF_FALSE(env, message_value->IsString(), napi_string_expected); + + v8::Local error_obj = + v8::Exception::RangeError(message_value.As()); + napi_status status = set_error_code(env, error_obj, code, nullptr); + if (status != napi_ok) return status; + + *result = v8impl::JsValueFromV8LocalValue(error_obj); + + return napi_clear_last_error(env); +} + +napi_status napi_typeof(napi_env env, + napi_value value, + napi_valuetype* result) { + // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw + // JS exceptions. + CHECK_ENV(env); + CHECK_ARG(env, value); + CHECK_ARG(env, result); + + v8::Local v = v8impl::V8LocalValueFromJsValue(value); + + if (v->IsNumber()) { + *result = napi_number; + } else if (v->IsString()) { + *result = napi_string; + } else if (v->IsFunction()) { + // This test has to come before IsObject because IsFunction + // implies IsObject + *result = napi_function; + } else if (v->IsExternal()) { + // This test has to come before IsObject because IsExternal + // implies IsObject + *result = napi_external; + } else if (v->IsObject()) { + *result = napi_object; + } else if (v->IsBoolean()) { + *result = napi_boolean; + } else if (v->IsUndefined()) { + *result = napi_undefined; + } else if (v->IsSymbol()) { + *result = napi_symbol; + } else if (v->IsNull()) { + *result = napi_null; + } else { + // Should not get here unless V8 has added some new kind of value. + return napi_set_last_error(env, napi_invalid_arg); + } + + return napi_clear_last_error(env); +} + +napi_status napi_get_undefined(napi_env env, napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + + *result = v8impl::JsValueFromV8LocalValue( + v8::Undefined(env->isolate)); + + return napi_clear_last_error(env); +} + +napi_status napi_get_null(napi_env env, napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + + *result = v8impl::JsValueFromV8LocalValue( + v8::Null(env->isolate)); + + return napi_clear_last_error(env); +} + +// Gets all callback info in a single call. (Ugly, but faster.) +napi_status napi_get_cb_info( + napi_env env, // [in] NAPI environment handle + napi_callback_info cbinfo, // [in] Opaque callback-info handle + size_t* argc, // [in-out] Specifies the size of the provided argv array + // and receives the actual count of args. + napi_value* argv, // [out] Array of values + napi_value* this_arg, // [out] Receives the JS 'this' arg for the call + void** data) { // [out] Receives the data pointer for the callback. + CHECK_ENV(env); + CHECK_ARG(env, cbinfo); + + v8impl::CallbackWrapper* info = + reinterpret_cast(cbinfo); + + if (argv != nullptr) { + CHECK_ARG(env, argc); + info->Args(argv, *argc); + } + if (argc != nullptr) { + *argc = info->ArgsLength(); + } + if (this_arg != nullptr) { + *this_arg = info->This(); + } + if (data != nullptr) { + *data = info->Data(); + } + + return napi_clear_last_error(env); +} + +napi_status napi_get_new_target(napi_env env, + napi_callback_info cbinfo, + napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, cbinfo); + CHECK_ARG(env, result); + + v8impl::CallbackWrapper* info = + reinterpret_cast(cbinfo); + + *result = info->GetNewTarget(); + return napi_clear_last_error(env); +} + +napi_status napi_call_function(napi_env env, + napi_value recv, + napi_value func, + size_t argc, + const napi_value* argv, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(env, recv); + if (argc > 0) { + CHECK_ARG(env, argv); + } + + v8::Isolate* isolate = env->isolate; + v8::Local context = isolate->GetCurrentContext(); + + v8::Local v8recv = v8impl::V8LocalValueFromJsValue(recv); + + v8::Local v8func; + CHECK_TO_FUNCTION(env, v8func, func); + + auto maybe = v8func->Call(context, v8recv, argc, + reinterpret_cast*>(const_cast(argv))); + + if (try_catch.HasCaught()) { + return napi_set_last_error(env, napi_pending_exception); + } else { + if (result != nullptr) { + CHECK_MAYBE_EMPTY(env, maybe, napi_generic_failure); + *result = v8impl::JsValueFromV8LocalValue(maybe.ToLocalChecked()); + } + return napi_clear_last_error(env); + } +} + +napi_status napi_get_global(napi_env env, napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + + v8::Isolate* isolate = env->isolate; + // TODO(ianhall): what if we need the global object from a different + // context in the same isolate? + // Should napi_env be the current context rather than the current isolate? + v8::Local context = isolate->GetCurrentContext(); + *result = v8impl::JsValueFromV8LocalValue(context->Global()); + + return napi_clear_last_error(env); +} + +napi_status napi_throw(napi_env env, napi_value error) { + NAPI_PREAMBLE(env); + CHECK_ARG(env, error); + + v8::Isolate* isolate = env->isolate; + + isolate->ThrowException(v8impl::V8LocalValueFromJsValue(error)); + // any VM calls after this point and before returning + // to the javascript invoker will fail + return napi_clear_last_error(env); +} + +napi_status napi_throw_error(napi_env env, + const char* code, + const char* msg) { + NAPI_PREAMBLE(env); + + v8::Isolate* isolate = env->isolate; + v8::Local str; + CHECK_NEW_FROM_UTF8(env, str, msg); + + v8::Local error_obj = v8::Exception::Error(str); + napi_status status = set_error_code(env, error_obj, nullptr, code); + if (status != napi_ok) return status; + + isolate->ThrowException(error_obj); + // any VM calls after this point and before returning + // to the javascript invoker will fail + return napi_clear_last_error(env); +} + +napi_status napi_throw_type_error(napi_env env, + const char* code, + const char* msg) { + NAPI_PREAMBLE(env); + + v8::Isolate* isolate = env->isolate; + v8::Local str; + CHECK_NEW_FROM_UTF8(env, str, msg); + + v8::Local error_obj = v8::Exception::TypeError(str); + napi_status status = set_error_code(env, error_obj, nullptr, code); + if (status != napi_ok) return status; + + isolate->ThrowException(error_obj); + // any VM calls after this point and before returning + // to the javascript invoker will fail + return napi_clear_last_error(env); +} + +napi_status napi_throw_range_error(napi_env env, + const char* code, + const char* msg) { + NAPI_PREAMBLE(env); + + v8::Isolate* isolate = env->isolate; + v8::Local str; + CHECK_NEW_FROM_UTF8(env, str, msg); + + v8::Local error_obj = v8::Exception::RangeError(str); + napi_status status = set_error_code(env, error_obj, nullptr, code); + if (status != napi_ok) return status; + + isolate->ThrowException(error_obj); + // any VM calls after this point and before returning + // to the javascript invoker will fail + return napi_clear_last_error(env); +} + +napi_status napi_is_error(napi_env env, napi_value value, bool* result) { + // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot + // throw JS exceptions. + CHECK_ENV(env); + CHECK_ARG(env, value); + CHECK_ARG(env, result); + + v8::Local val = v8impl::V8LocalValueFromJsValue(value); + *result = val->IsNativeError(); + + return napi_clear_last_error(env); +} + +napi_status napi_get_value_double(napi_env env, + napi_value value, + double* result) { + // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw + // JS exceptions. + CHECK_ENV(env); + CHECK_ARG(env, value); + CHECK_ARG(env, result); + + v8::Local val = v8impl::V8LocalValueFromJsValue(value); + RETURN_STATUS_IF_FALSE(env, val->IsNumber(), napi_number_expected); + + *result = val.As()->Value(); + + return napi_clear_last_error(env); +} + +napi_status napi_get_value_int32(napi_env env, + napi_value value, + int32_t* result) { + // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw + // JS exceptions. + CHECK_ENV(env); + CHECK_ARG(env, value); + CHECK_ARG(env, result); + + v8::Local val = v8impl::V8LocalValueFromJsValue(value); + + if (val->IsInt32()) { + *result = val.As()->Value(); + } else { + RETURN_STATUS_IF_FALSE(env, val->IsNumber(), napi_number_expected); + + // Empty context: https://github.com/nodejs/node/issues/14379 + v8::Local context; + *result = val->Int32Value(context).FromJust(); + } + + return napi_clear_last_error(env); +} + +napi_status napi_get_value_uint32(napi_env env, + napi_value value, + uint32_t* result) { + // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw + // JS exceptions. + CHECK_ENV(env); + CHECK_ARG(env, value); + CHECK_ARG(env, result); + + v8::Local val = v8impl::V8LocalValueFromJsValue(value); + + if (val->IsUint32()) { + *result = val.As()->Value(); + } else { + RETURN_STATUS_IF_FALSE(env, val->IsNumber(), napi_number_expected); + + // Empty context: https://github.com/nodejs/node/issues/14379 + v8::Local context; + *result = val->Uint32Value(context).FromJust(); + } + + return napi_clear_last_error(env); +} + +napi_status napi_get_value_int64(napi_env env, + napi_value value, + int64_t* result) { + // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw + // JS exceptions. + CHECK_ENV(env); + CHECK_ARG(env, value); + CHECK_ARG(env, result); + + v8::Local val = v8impl::V8LocalValueFromJsValue(value); + + // This is still a fast path very likely to be taken. + if (val->IsInt32()) { + *result = val.As()->Value(); + return napi_clear_last_error(env); + } + + RETURN_STATUS_IF_FALSE(env, val->IsNumber(), napi_number_expected); + + // v8::Value::IntegerValue() converts NaN, +Inf, and -Inf to INT64_MIN, + // inconsistent with v8::Value::Int32Value() which converts those values to 0. + // Special-case all non-finite values to match that behavior. + double doubleValue = val.As()->Value(); + if (std::isfinite(doubleValue)) { + // v8::Value::IntegerValue() as shipped with v6.x returns inconsistent + // values outside of the int64_t range. We rectify that here. + if (doubleValue >= static_cast(INT64_MAX)) { + *result = INT64_MAX; + } else if (doubleValue <= static_cast((int64_t)-INT64_MAX - 1)) { + *result = -INT64_MAX - 1; + } else { + // Empty context: https://github.com/nodejs/node/issues/14379 + v8::Local context; + *result = val->IntegerValue(context).FromJust(); + } + } else { + *result = 0; + } + + return napi_clear_last_error(env); +} + +napi_status napi_get_value_bool(napi_env env, napi_value value, bool* result) { + // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw + // JS exceptions. + CHECK_ENV(env); + CHECK_ARG(env, value); + CHECK_ARG(env, result); + + v8::Local val = v8impl::V8LocalValueFromJsValue(value); + RETURN_STATUS_IF_FALSE(env, val->IsBoolean(), napi_boolean_expected); + + *result = val.As()->Value(); + + return napi_clear_last_error(env); +} + +// Copies a JavaScript string into a LATIN-1 string buffer. The result is the +// number of bytes (excluding the null terminator) copied into buf. +// A sufficient buffer size should be greater than the length of string, +// reserving space for null terminator. +// If bufsize is insufficient, the string will be truncated and null terminated. +// If buf is NULL, this method returns the length of the string (in bytes) +// via the result parameter. +// The result argument is optional unless buf is NULL. +napi_status napi_get_value_string_latin1(napi_env env, + napi_value value, + char* buf, + size_t bufsize, + size_t* result) { + CHECK_ENV(env); + CHECK_ARG(env, value); + + v8::Local val = v8impl::V8LocalValueFromJsValue(value); + RETURN_STATUS_IF_FALSE(env, val->IsString(), napi_string_expected); + + if (!buf) { + CHECK_ARG(env, result); + *result = val.As()->Length(); + } else { + int copied = val.As()->WriteOneByte( + reinterpret_cast(buf), 0, bufsize - 1, + v8::String::NO_NULL_TERMINATION); + + buf[copied] = '\0'; + if (result != nullptr) { + *result = copied; + } + } + + return napi_clear_last_error(env); +} + +// Copies a JavaScript string into a UTF-8 string buffer. The result is the +// number of bytes (excluding the null terminator) copied into buf. +// A sufficient buffer size should be greater than the length of string, +// reserving space for null terminator. +// If bufsize is insufficient, the string will be truncated and null terminated. +// If buf is NULL, this method returns the length of the string (in bytes) +// via the result parameter. +// The result argument is optional unless buf is NULL. +napi_status napi_get_value_string_utf8(napi_env env, + napi_value value, + char* buf, + size_t bufsize, + size_t* result) { + CHECK_ENV(env); + CHECK_ARG(env, value); + + v8::Local val = v8impl::V8LocalValueFromJsValue(value); + RETURN_STATUS_IF_FALSE(env, val->IsString(), napi_string_expected); + + if (!buf) { + CHECK_ARG(env, result); + *result = val.As()->Utf8Length(); + } else { + int copied = val.As()->WriteUtf8( + buf, bufsize - 1, nullptr, v8::String::REPLACE_INVALID_UTF8 | + v8::String::NO_NULL_TERMINATION); + + buf[copied] = '\0'; + if (result != nullptr) { + *result = copied; + } + } + + return napi_clear_last_error(env); +} + +// Copies a JavaScript string into a UTF-16 string buffer. The result is the +// number of 2-byte code units (excluding the null terminator) copied into buf. +// A sufficient buffer size should be greater than the length of string, +// reserving space for null terminator. +// If bufsize is insufficient, the string will be truncated and null terminated. +// If buf is NULL, this method returns the length of the string (in 2-byte +// code units) via the result parameter. +// The result argument is optional unless buf is NULL. +napi_status napi_get_value_string_utf16(napi_env env, + napi_value value, + char16_t* buf, + size_t bufsize, + size_t* result) { + CHECK_ENV(env); + CHECK_ARG(env, value); + + v8::Local val = v8impl::V8LocalValueFromJsValue(value); + RETURN_STATUS_IF_FALSE(env, val->IsString(), napi_string_expected); + + if (!buf) { + CHECK_ARG(env, result); + // V8 assumes UTF-16 length is the same as the number of characters. + *result = val.As()->Length(); + } else { + int copied = val.As()->Write( + reinterpret_cast(buf), 0, bufsize - 1, + v8::String::NO_NULL_TERMINATION); + + buf[copied] = '\0'; + if (result != nullptr) { + *result = copied; + } + } + + return napi_clear_last_error(env); +} + +napi_status napi_coerce_to_object(napi_env env, + napi_value value, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(env, value); + CHECK_ARG(env, result); + + v8::Isolate* isolate = env->isolate; + v8::Local context = isolate->GetCurrentContext(); + v8::Local obj; + CHECK_TO_OBJECT(env, context, obj, value); + + *result = v8impl::JsValueFromV8LocalValue(obj); + return GET_RETURN_STATUS(env); +} + +napi_status napi_coerce_to_bool(napi_env env, + napi_value value, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(env, value); + CHECK_ARG(env, result); + + v8::Isolate* isolate = env->isolate; + v8::Local context = isolate->GetCurrentContext(); + v8::Local b; + + CHECK_TO_BOOL(env, context, b, value); + + *result = v8impl::JsValueFromV8LocalValue(b); + return GET_RETURN_STATUS(env); +} + +napi_status napi_coerce_to_number(napi_env env, + napi_value value, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(env, value); + CHECK_ARG(env, result); + + v8::Isolate* isolate = env->isolate; + v8::Local context = isolate->GetCurrentContext(); + v8::Local num; + + CHECK_TO_NUMBER(env, context, num, value); + + *result = v8impl::JsValueFromV8LocalValue(num); + return GET_RETURN_STATUS(env); +} + +napi_status napi_coerce_to_string(napi_env env, + napi_value value, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(env, value); + CHECK_ARG(env, result); + + v8::Isolate* isolate = env->isolate; + v8::Local context = isolate->GetCurrentContext(); + v8::Local str; + + CHECK_TO_STRING(env, context, str, value); + + *result = v8impl::JsValueFromV8LocalValue(str); + return GET_RETURN_STATUS(env); +} + +napi_status napi_wrap(napi_env env, + napi_value js_object, + void* native_object, + napi_finalize finalize_cb, + void* finalize_hint, + napi_ref* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(env, js_object); + + v8::Isolate* isolate = env->isolate; + v8::Local context = isolate->GetCurrentContext(); + + v8::Local value = v8impl::V8LocalValueFromJsValue(js_object); + RETURN_STATUS_IF_FALSE(env, value->IsObject(), napi_invalid_arg); + v8::Local obj = value.As(); + + // If we've already wrapped this object, we error out. + RETURN_STATUS_IF_FALSE(env, + !obj->HasPrivate(context, NAPI_PRIVATE_KEY(context, wrapper)).FromJust(), + napi_invalid_arg); + + v8impl::Reference* reference = nullptr; + if (result != nullptr) { + // The returned reference should be deleted via napi_delete_reference() + // ONLY in response to the finalize callback invocation. (If it is deleted + // before then, then the finalize callback will never be invoked.) + // Therefore a finalize callback is required when returning a reference. + CHECK_ARG(env, finalize_cb); + reference = v8impl::Reference::New( + env, obj, 0, false, finalize_cb, native_object, finalize_hint); + *result = reinterpret_cast(reference); + } else { + // Create a self-deleting reference. + reference = v8impl::Reference::New(env, obj, 0, true, finalize_cb, + native_object, finalize_cb == nullptr ? nullptr : finalize_hint); + } + + CHECK(obj->SetPrivate(context, NAPI_PRIVATE_KEY(context, wrapper), + v8::External::New(isolate, reference)).FromJust()); + + return GET_RETURN_STATUS(env); +} + +napi_status napi_unwrap(napi_env env, napi_value obj, void** result) { + return v8impl::Unwrap(env, obj, result, v8impl::KeepWrap); +} + +napi_status napi_remove_wrap(napi_env env, napi_value obj, void** result) { + return v8impl::Unwrap(env, obj, result, v8impl::RemoveWrap); +} + +napi_status napi_create_external(napi_env env, + void* data, + napi_finalize finalize_cb, + void* finalize_hint, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(env, result); + + v8::Isolate* isolate = env->isolate; + + v8::Local external_value = v8::External::New(isolate, data); + + // The Reference object will delete itself after invoking the finalizer + // callback. + v8impl::Reference::New(env, + external_value, + 0, + true, + finalize_cb, + data, + finalize_hint); + + *result = v8impl::JsValueFromV8LocalValue(external_value); + + return napi_clear_last_error(env); +} + +napi_status napi_get_value_external(napi_env env, + napi_value value, + void** result) { + CHECK_ENV(env); + CHECK_ARG(env, value); + CHECK_ARG(env, result); + + v8::Local val = v8impl::V8LocalValueFromJsValue(value); + RETURN_STATUS_IF_FALSE(env, val->IsExternal(), napi_invalid_arg); + + v8::Local external_value = val.As(); + *result = external_value->Value(); + + return napi_clear_last_error(env); +} + +// Set initial_refcount to 0 for a weak reference, >0 for a strong reference. +napi_status napi_create_reference(napi_env env, + napi_value value, + uint32_t initial_refcount, + napi_ref* result) { + // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw + // JS exceptions. + CHECK_ENV(env); + CHECK_ARG(env, value); + CHECK_ARG(env, result); + + v8::Local v8_value = v8impl::V8LocalValueFromJsValue(value); + + if (!(v8_value->IsObject() || v8_value->IsFunction())) { + return napi_set_last_error(env, napi_object_expected); + } + + v8impl::Reference* reference = + v8impl::Reference::New(env, v8_value, initial_refcount, false); + + *result = reinterpret_cast(reference); + return napi_clear_last_error(env); +} + +// Deletes a reference. The referenced value is released, and may be GC'd unless +// there are other references to it. +napi_status napi_delete_reference(napi_env env, napi_ref ref) { + // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw + // JS exceptions. + CHECK_ENV(env); + CHECK_ARG(env, ref); + + v8impl::Reference::Delete(reinterpret_cast(ref)); + + return napi_clear_last_error(env); +} + +// Increments the reference count, optionally returning the resulting count. +// After this call the reference will be a strong reference because its +// refcount is >0, and the referenced object is effectively "pinned". +// Calling this when the refcount is 0 and the object is unavailable +// results in an error. +napi_status napi_reference_ref(napi_env env, napi_ref ref, uint32_t* result) { + // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw + // JS exceptions. + CHECK_ENV(env); + CHECK_ARG(env, ref); + + v8impl::Reference* reference = reinterpret_cast(ref); + uint32_t count = reference->Ref(); + + if (result != nullptr) { + *result = count; + } + + return napi_clear_last_error(env); +} + +// Decrements the reference count, optionally returning the resulting count. If +// the result is 0 the reference is now weak and the object may be GC'd at any +// time if there are no other references. Calling this when the refcount is +// already 0 results in an error. +napi_status napi_reference_unref(napi_env env, napi_ref ref, uint32_t* result) { + // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw + // JS exceptions. + CHECK_ENV(env); + CHECK_ARG(env, ref); + + v8impl::Reference* reference = reinterpret_cast(ref); + + if (reference->RefCount() == 0) { + return napi_set_last_error(env, napi_generic_failure); + } + + uint32_t count = reference->Unref(); + + if (result != nullptr) { + *result = count; + } + + return napi_clear_last_error(env); +} + +// Attempts to get a referenced value. If the reference is weak, the value might +// no longer be available, in that case the call is still successful but the +// result is NULL. +napi_status napi_get_reference_value(napi_env env, + napi_ref ref, + napi_value* result) { + // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw + // JS exceptions. + CHECK_ENV(env); + CHECK_ARG(env, ref); + CHECK_ARG(env, result); + + v8impl::Reference* reference = reinterpret_cast(ref); + *result = v8impl::JsValueFromV8LocalValue(reference->Get()); + + return napi_clear_last_error(env); +} + +napi_status napi_open_handle_scope(napi_env env, napi_handle_scope* result) { + // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw + // JS exceptions. + CHECK_ENV(env); + CHECK_ARG(env, result); + + *result = v8impl::JsHandleScopeFromV8HandleScope( + new v8impl::HandleScopeWrapper(env->isolate)); + env->open_handle_scopes++; + return napi_clear_last_error(env); +} + +napi_status napi_close_handle_scope(napi_env env, napi_handle_scope scope) { + // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw + // JS exceptions. + CHECK_ENV(env); + CHECK_ARG(env, scope); + if (env->open_handle_scopes == 0) { + return napi_handle_scope_mismatch; + } + + env->open_handle_scopes--; + delete v8impl::V8HandleScopeFromJsHandleScope(scope); + return napi_clear_last_error(env); +} + +napi_status napi_open_escapable_handle_scope( + napi_env env, + napi_escapable_handle_scope* result) { + // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw + // JS exceptions. + CHECK_ENV(env); + CHECK_ARG(env, result); + + *result = v8impl::JsEscapableHandleScopeFromV8EscapableHandleScope( + new v8impl::EscapableHandleScopeWrapper(env->isolate)); + env->open_handle_scopes++; + return napi_clear_last_error(env); +} + +napi_status napi_close_escapable_handle_scope( + napi_env env, + napi_escapable_handle_scope scope) { + // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw + // JS exceptions. + CHECK_ENV(env); + CHECK_ARG(env, scope); + if (env->open_handle_scopes == 0) { + return napi_handle_scope_mismatch; + } + + delete v8impl::V8EscapableHandleScopeFromJsEscapableHandleScope(scope); + env->open_handle_scopes--; + return napi_clear_last_error(env); +} + +napi_status napi_escape_handle(napi_env env, + napi_escapable_handle_scope scope, + napi_value escapee, + napi_value* result) { + // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw + // JS exceptions. + CHECK_ENV(env); + CHECK_ARG(env, scope); + CHECK_ARG(env, escapee); + CHECK_ARG(env, result); + + v8impl::EscapableHandleScopeWrapper* s = + v8impl::V8EscapableHandleScopeFromJsEscapableHandleScope(scope); + if (!s->escape_called()) { + *result = v8impl::JsValueFromV8LocalValue( + s->Escape(v8impl::V8LocalValueFromJsValue(escapee))); + return napi_clear_last_error(env); + } + return napi_set_last_error(env, napi_escape_called_twice); +} + +napi_status napi_open_callback_scope(napi_env env, + napi_value resource_object, + napi_async_context async_context_handle, + napi_callback_scope* result) { + // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw + // JS exceptions. + CHECK_ENV(env); + CHECK_ARG(env, result); + + v8::Local context = env->isolate->GetCurrentContext(); + + node::async_context* node_async_context = + reinterpret_cast(async_context_handle); + + v8::Local resource; + CHECK_TO_OBJECT(env, context, resource, resource_object); + + *result = v8impl::JsCallbackScopeFromV8CallbackScope( + new node::CallbackScope(env->isolate, + resource, + *node_async_context)); + + env->open_callback_scopes++; + return napi_clear_last_error(env); +} + +napi_status napi_close_callback_scope(napi_env env, napi_callback_scope scope) { + // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw + // JS exceptions. + CHECK_ENV(env); + CHECK_ARG(env, scope); + if (env->open_callback_scopes == 0) { + return napi_callback_scope_mismatch; + } + + env->open_callback_scopes--; + delete v8impl::V8CallbackScopeFromJsCallbackScope(scope); + return napi_clear_last_error(env); +} + +napi_status napi_new_instance(napi_env env, + napi_value constructor, + size_t argc, + const napi_value* argv, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(env, constructor); + if (argc > 0) { + CHECK_ARG(env, argv); + } + CHECK_ARG(env, result); + + v8::Isolate* isolate = env->isolate; + v8::Local context = isolate->GetCurrentContext(); + + v8::Local ctor; + CHECK_TO_FUNCTION(env, ctor, constructor); + + auto maybe = ctor->NewInstance(context, argc, + reinterpret_cast*>(const_cast(argv))); + + CHECK_MAYBE_EMPTY(env, maybe, napi_generic_failure); + + *result = v8impl::JsValueFromV8LocalValue(maybe.ToLocalChecked()); + return GET_RETURN_STATUS(env); +} + +napi_status napi_instanceof(napi_env env, + napi_value object, + napi_value constructor, + bool* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(env, object); + CHECK_ARG(env, result); + + *result = false; + + v8::Local ctor; + v8::Isolate* isolate = env->isolate; + v8::Local context = isolate->GetCurrentContext(); + + CHECK_TO_OBJECT(env, context, ctor, constructor); + + if (!ctor->IsFunction()) { + napi_throw_type_error(env, + "ERR_NAPI_CONS_FUNCTION", + "Constructor must be a function"); + + return napi_set_last_error(env, napi_function_expected); + } + + if (env->has_instance_available) { + napi_value value, js_result = nullptr, has_instance = nullptr; + napi_status status = napi_generic_failure; + napi_valuetype value_type; + + // Get "Symbol" from the global object + if (env->has_instance.IsEmpty()) { + status = napi_get_global(env, &value); + if (status != napi_ok) return status; + status = napi_get_named_property(env, value, "Symbol", &value); + if (status != napi_ok) return status; + status = napi_typeof(env, value, &value_type); + if (status != napi_ok) return status; + + // Get "hasInstance" from Symbol + if (value_type == napi_function) { + status = napi_get_named_property(env, value, "hasInstance", &value); + if (status != napi_ok) return status; + status = napi_typeof(env, value, &value_type); + if (status != napi_ok) return status; + + // Store Symbol.hasInstance in a global persistent reference + if (value_type == napi_symbol) { + env->has_instance.Reset(env->isolate, + v8impl::V8LocalValueFromJsValue(value)); + has_instance = value; + } + } + } else { + has_instance = v8impl::JsValueFromV8LocalValue( + v8::Local::New(env->isolate, env->has_instance)); + } + + if (has_instance) { + status = napi_get_property(env, constructor, has_instance, &value); + if (status != napi_ok) return status; + status = napi_typeof(env, value, &value_type); + if (status != napi_ok) return status; + + // Call the function to determine whether the object is an instance of the + // constructor + if (value_type == napi_function) { + status = napi_call_function(env, constructor, value, 1, &object, + &js_result); + if (status != napi_ok) return status; + return napi_get_value_bool(env, js_result, result); + } + } + + env->has_instance_available = false; + } + + // If running constructor[Symbol.hasInstance](object) did not work, we perform + // a traditional instanceof (early Node.js 6.x). + + v8::Local prototype_string; + CHECK_NEW_FROM_UTF8(env, prototype_string, "prototype"); + + auto maybe_prototype = ctor->Get(context, prototype_string); + CHECK_MAYBE_EMPTY(env, maybe_prototype, napi_generic_failure); + + v8::Local prototype_property = maybe_prototype.ToLocalChecked(); + if (!prototype_property->IsObject()) { + napi_throw_type_error( + env, + "ERR_NAPI_CONS_PROTOTYPE_OBJECT", + "Constructor.prototype must be an object"); + + return napi_set_last_error(env, napi_object_expected); + } + + auto maybe_ctor = prototype_property->ToObject(context); + CHECK_MAYBE_EMPTY(env, maybe_ctor, napi_generic_failure); + ctor = maybe_ctor.ToLocalChecked(); + + v8::Local current_obj = v8impl::V8LocalValueFromJsValue(object); + if (!current_obj->StrictEquals(ctor)) { + for (v8::Local original_obj = current_obj; + !(current_obj->IsNull() || current_obj->IsUndefined());) { + if (current_obj->StrictEquals(ctor)) { + *result = !(original_obj->IsNumber() || + original_obj->IsBoolean() || + original_obj->IsString()); + break; + } + v8::Local obj; + CHECK_TO_OBJECT(env, context, obj, v8impl::JsValueFromV8LocalValue( + current_obj)); + current_obj = obj->GetPrototype(); + } + } + + return GET_RETURN_STATUS(env); +} + +napi_status napi_async_init(napi_env env, + napi_value async_resource, + napi_value async_resource_name, + napi_async_context* result) { + CHECK_ENV(env); + CHECK_ARG(env, async_resource_name); + CHECK_ARG(env, result); + + v8::Isolate* isolate = env->isolate; + v8::Local context = isolate->GetCurrentContext(); + + v8::Local v8_resource; + if (async_resource != nullptr) { + CHECK_TO_OBJECT(env, context, v8_resource, async_resource); + } else { + v8_resource = v8::Object::New(isolate); + } + + v8::Local v8_resource_name; + CHECK_TO_STRING(env, context, v8_resource_name, async_resource_name); + + // TODO(jasongin): Consider avoiding allocation here by using + // a tagged pointer with 2×31 bit fields instead. + node::async_context* async_context = new node::async_context(); + + *async_context = node::EmitAsyncInit(isolate, v8_resource, v8_resource_name); + *result = reinterpret_cast(async_context); + + return napi_clear_last_error(env); +} + +napi_status napi_async_destroy(napi_env env, + napi_async_context async_context) { + CHECK_ENV(env); + CHECK_ARG(env, async_context); + + v8::Isolate* isolate = env->isolate; + node::async_context* node_async_context = + reinterpret_cast(async_context); + node::EmitAsyncDestroy(isolate, *node_async_context); + + delete node_async_context; + + return napi_clear_last_error(env); +} + +napi_status napi_make_callback(napi_env env, + napi_async_context async_context, + napi_value recv, + napi_value func, + size_t argc, + const napi_value* argv, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(env, recv); + if (argc > 0) { + CHECK_ARG(env, argv); + } + + v8::Isolate* isolate = env->isolate; + v8::Local context = isolate->GetCurrentContext(); + + v8::Local v8recv; + CHECK_TO_OBJECT(env, context, v8recv, recv); + + v8::Local v8func; + CHECK_TO_FUNCTION(env, v8func, func); + + node::async_context* node_async_context = + reinterpret_cast(async_context); + if (node_async_context == nullptr) { + static node::async_context empty_context = { 0, 0 }; + node_async_context = &empty_context; + } + + v8::MaybeLocal callback_result = node::MakeCallback( + isolate, v8recv, v8func, argc, + reinterpret_cast*>(const_cast(argv)), + *node_async_context); + + if (try_catch.HasCaught()) { + return napi_set_last_error(env, napi_pending_exception); + } else { + CHECK_MAYBE_EMPTY(env, callback_result, napi_generic_failure); + if (result != nullptr) { + *result = v8impl::JsValueFromV8LocalValue( + callback_result.ToLocalChecked()); + } + } + + return GET_RETURN_STATUS(env); +} + +// Methods to support catching exceptions +napi_status napi_is_exception_pending(napi_env env, bool* result) { + // NAPI_PREAMBLE is not used here: this function must execute when there is a + // pending exception. + CHECK_ENV(env); + CHECK_ARG(env, result); + + *result = !env->last_exception.IsEmpty(); + return napi_clear_last_error(env); +} + +napi_status napi_get_and_clear_last_exception(napi_env env, + napi_value* result) { + // NAPI_PREAMBLE is not used here: this function must execute when there is a + // pending exception. + CHECK_ENV(env); + CHECK_ARG(env, result); + + if (env->last_exception.IsEmpty()) { + return napi_get_undefined(env, result); + } else { + *result = v8impl::JsValueFromV8LocalValue( + v8::Local::New(env->isolate, env->last_exception)); + env->last_exception.Reset(); + } + + return napi_clear_last_error(env); +} + +napi_status napi_create_buffer(napi_env env, + size_t length, + void** data, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(env, result); + + auto maybe = node::Buffer::New(env->isolate, length); + + CHECK_MAYBE_EMPTY(env, maybe, napi_generic_failure); + + v8::Local buffer = maybe.ToLocalChecked(); + + *result = v8impl::JsValueFromV8LocalValue(buffer); + + if (data != nullptr) { + *data = node::Buffer::Data(buffer); + } + + return GET_RETURN_STATUS(env); +} + +napi_status napi_create_external_buffer(napi_env env, + size_t length, + void* data, + napi_finalize finalize_cb, + void* finalize_hint, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(env, result); + + v8::Isolate* isolate = env->isolate; + + // The finalizer object will delete itself after invoking the callback. + v8impl::Finalizer* finalizer = v8impl::Finalizer::New( + env, finalize_cb, nullptr, finalize_hint); + + auto maybe = node::Buffer::New(isolate, + static_cast(data), + length, + v8impl::Finalizer::FinalizeBufferCallback, + finalizer); + + CHECK_MAYBE_EMPTY(env, maybe, napi_generic_failure); + + *result = v8impl::JsValueFromV8LocalValue(maybe.ToLocalChecked()); + return GET_RETURN_STATUS(env); + // Tell coverity that 'finalizer' should not be freed when we return + // as it will be deleted when the buffer to which it is associated + // is finalized. + // coverity[leaked_storage] +} + +napi_status napi_create_buffer_copy(napi_env env, + size_t length, + const void* data, + void** result_data, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(env, result); + + auto maybe = node::Buffer::Copy(env->isolate, + static_cast(data), length); + + CHECK_MAYBE_EMPTY(env, maybe, napi_generic_failure); + + v8::Local buffer = maybe.ToLocalChecked(); + *result = v8impl::JsValueFromV8LocalValue(buffer); + + if (result_data != nullptr) { + *result_data = node::Buffer::Data(buffer); + } + + return GET_RETURN_STATUS(env); +} + +napi_status napi_is_buffer(napi_env env, napi_value value, bool* result) { + CHECK_ENV(env); + CHECK_ARG(env, value); + CHECK_ARG(env, result); + + *result = node::Buffer::HasInstance(v8impl::V8LocalValueFromJsValue(value)); + return napi_clear_last_error(env); +} + +napi_status napi_get_buffer_info(napi_env env, + napi_value value, + void** data, + size_t* length) { + CHECK_ENV(env); + CHECK_ARG(env, value); + + v8::Local buffer = v8impl::V8LocalValueFromJsValue(value); + + if (data != nullptr) { + *data = node::Buffer::Data(buffer); + } + if (length != nullptr) { + *length = node::Buffer::Length(buffer); + } + + return napi_clear_last_error(env); +} + +napi_status napi_is_arraybuffer(napi_env env, napi_value value, bool* result) { + CHECK_ENV(env); + CHECK_ARG(env, value); + CHECK_ARG(env, result); + + v8::Local val = v8impl::V8LocalValueFromJsValue(value); + *result = val->IsArrayBuffer(); + + return napi_clear_last_error(env); +} + +napi_status napi_create_arraybuffer(napi_env env, + size_t byte_length, + void** data, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(env, result); + + v8::Isolate* isolate = env->isolate; + v8::Local buffer = + v8::ArrayBuffer::New(isolate, byte_length); + + // Optionally return a pointer to the buffer's data, to avoid another call to + // retreive it. + if (data != nullptr) { + *data = buffer->GetContents().Data(); + } + + *result = v8impl::JsValueFromV8LocalValue(buffer); + return GET_RETURN_STATUS(env); +} + +napi_status napi_create_external_arraybuffer(napi_env env, + void* external_data, + size_t byte_length, + napi_finalize finalize_cb, + void* finalize_hint, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(env, result); + + v8::Isolate* isolate = env->isolate; + v8::Local buffer = + v8::ArrayBuffer::New(isolate, external_data, byte_length); + + if (finalize_cb != nullptr) { + // Create a self-deleting weak reference that invokes the finalizer + // callback. + v8impl::Reference::New(env, + buffer, + 0, + true, + finalize_cb, + external_data, + finalize_hint); + } + + *result = v8impl::JsValueFromV8LocalValue(buffer); + return GET_RETURN_STATUS(env); +} + +napi_status napi_get_arraybuffer_info(napi_env env, + napi_value arraybuffer, + void** data, + size_t* byte_length) { + CHECK_ENV(env); + CHECK_ARG(env, arraybuffer); + + v8::Local value = v8impl::V8LocalValueFromJsValue(arraybuffer); + RETURN_STATUS_IF_FALSE(env, value->IsArrayBuffer(), napi_invalid_arg); + + v8::ArrayBuffer::Contents contents = + value.As()->GetContents(); + + if (data != nullptr) { + *data = contents.Data(); + } + + if (byte_length != nullptr) { + *byte_length = contents.ByteLength(); + } + + return napi_clear_last_error(env); +} + +napi_status napi_is_typedarray(napi_env env, napi_value value, bool* result) { + CHECK_ENV(env); + CHECK_ARG(env, value); + CHECK_ARG(env, result); + + v8::Local val = v8impl::V8LocalValueFromJsValue(value); + *result = val->IsTypedArray(); + + return napi_clear_last_error(env); +} + +napi_status napi_create_typedarray(napi_env env, + napi_typedarray_type type, + size_t length, + napi_value arraybuffer, + size_t byte_offset, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(env, arraybuffer); + CHECK_ARG(env, result); + + v8::Local value = v8impl::V8LocalValueFromJsValue(arraybuffer); + RETURN_STATUS_IF_FALSE(env, value->IsArrayBuffer(), napi_invalid_arg); + + v8::Local buffer = value.As(); + v8::Local typedArray; + + switch (type) { + case napi_int8_array: + CREATE_TYPED_ARRAY( + env, Int8Array, 1, buffer, byte_offset, length, typedArray); + break; + case napi_uint8_array: + CREATE_TYPED_ARRAY( + env, Uint8Array, 1, buffer, byte_offset, length, typedArray); + break; + case napi_uint8_clamped_array: + CREATE_TYPED_ARRAY( + env, Uint8ClampedArray, 1, buffer, byte_offset, length, typedArray); + break; + case napi_int16_array: + CREATE_TYPED_ARRAY( + env, Int16Array, 2, buffer, byte_offset, length, typedArray); + break; + case napi_uint16_array: + CREATE_TYPED_ARRAY( + env, Uint16Array, 2, buffer, byte_offset, length, typedArray); + break; + case napi_int32_array: + CREATE_TYPED_ARRAY( + env, Int32Array, 4, buffer, byte_offset, length, typedArray); + break; + case napi_uint32_array: + CREATE_TYPED_ARRAY( + env, Uint32Array, 4, buffer, byte_offset, length, typedArray); + break; + case napi_float32_array: + CREATE_TYPED_ARRAY( + env, Float32Array, 4, buffer, byte_offset, length, typedArray); + break; + case napi_float64_array: + CREATE_TYPED_ARRAY( + env, Float64Array, 8, buffer, byte_offset, length, typedArray); + break; + default: + return napi_set_last_error(env, napi_invalid_arg); + } + + *result = v8impl::JsValueFromV8LocalValue(typedArray); + return GET_RETURN_STATUS(env); +} + +napi_status napi_get_typedarray_info(napi_env env, + napi_value typedarray, + napi_typedarray_type* type, + size_t* length, + void** data, + napi_value* arraybuffer, + size_t* byte_offset) { + CHECK_ENV(env); + CHECK_ARG(env, typedarray); + + v8::Local value = v8impl::V8LocalValueFromJsValue(typedarray); + RETURN_STATUS_IF_FALSE(env, value->IsTypedArray(), napi_invalid_arg); + + v8::Local array = value.As(); + + if (type != nullptr) { + if (value->IsInt8Array()) { + *type = napi_int8_array; + } else if (value->IsUint8Array()) { + *type = napi_uint8_array; + } else if (value->IsUint8ClampedArray()) { + *type = napi_uint8_clamped_array; + } else if (value->IsInt16Array()) { + *type = napi_int16_array; + } else if (value->IsUint16Array()) { + *type = napi_uint16_array; + } else if (value->IsInt32Array()) { + *type = napi_int32_array; + } else if (value->IsUint32Array()) { + *type = napi_uint32_array; + } else if (value->IsFloat32Array()) { + *type = napi_float32_array; + } else if (value->IsFloat64Array()) { + *type = napi_float64_array; + } + } + + if (length != nullptr) { + *length = array->Length(); + } + + v8::Local buffer = array->Buffer(); + if (data != nullptr) { + *data = static_cast(buffer->GetContents().Data()) + + array->ByteOffset(); + } + + if (arraybuffer != nullptr) { + *arraybuffer = v8impl::JsValueFromV8LocalValue(buffer); + } + + if (byte_offset != nullptr) { + *byte_offset = array->ByteOffset(); + } + + return napi_clear_last_error(env); +} + +napi_status napi_create_dataview(napi_env env, + size_t byte_length, + napi_value arraybuffer, + size_t byte_offset, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(env, arraybuffer); + CHECK_ARG(env, result); + + v8::Local value = v8impl::V8LocalValueFromJsValue(arraybuffer); + RETURN_STATUS_IF_FALSE(env, value->IsArrayBuffer(), napi_invalid_arg); + + v8::Local buffer = value.As(); + if (byte_length + byte_offset > buffer->ByteLength()) { + napi_throw_range_error( + env, + "ERR_NAPI_INVALID_DATAVIEW_ARGS", + "byte_offset + byte_length should be less than or " + "equal to the size in bytes of the array passed in"); + return napi_set_last_error(env, napi_pending_exception); + } + v8::Local DataView = v8::DataView::New(buffer, byte_offset, + byte_length); + + *result = v8impl::JsValueFromV8LocalValue(DataView); + return GET_RETURN_STATUS(env); +} + +napi_status napi_is_dataview(napi_env env, napi_value value, bool* result) { + CHECK_ENV(env); + CHECK_ARG(env, value); + CHECK_ARG(env, result); + + v8::Local val = v8impl::V8LocalValueFromJsValue(value); + *result = val->IsDataView(); + + return napi_clear_last_error(env); +} + +napi_status napi_get_dataview_info(napi_env env, + napi_value dataview, + size_t* byte_length, + void** data, + napi_value* arraybuffer, + size_t* byte_offset) { + CHECK_ENV(env); + CHECK_ARG(env, dataview); + + v8::Local value = v8impl::V8LocalValueFromJsValue(dataview); + RETURN_STATUS_IF_FALSE(env, value->IsDataView(), napi_invalid_arg); + + v8::Local array = value.As(); + + if (byte_length != nullptr) { + *byte_length = array->ByteLength(); + } + + v8::Local buffer = array->Buffer(); + if (data != nullptr) { + *data = static_cast(buffer->GetContents().Data()) + + array->ByteOffset(); + } + + if (arraybuffer != nullptr) { + *arraybuffer = v8impl::JsValueFromV8LocalValue(buffer); + } + + if (byte_offset != nullptr) { + *byte_offset = array->ByteOffset(); + } + + return napi_clear_last_error(env); +} + +napi_status napi_get_version(napi_env env, uint32_t* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + *result = NAPI_VERSION; + return napi_clear_last_error(env); +} + +napi_status napi_get_node_version(napi_env env, + const napi_node_version** result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + static const napi_node_version version = { + NODE_MAJOR_VERSION, + NODE_MINOR_VERSION, + NODE_PATCH_VERSION, + NODE_RELEASE + }; + *result = &version; + return napi_clear_last_error(env); +} + +napi_status napi_adjust_external_memory(napi_env env, + int64_t change_in_bytes, + int64_t* adjusted_value) { + CHECK_ENV(env); + CHECK_ARG(env, adjusted_value); + + *adjusted_value = env->isolate->AdjustAmountOfExternalAllocatedMemory( + change_in_bytes); + + return napi_clear_last_error(env); +} + +namespace { +namespace uvimpl { + +static napi_status ConvertUVErrorCode(int code) { + switch (code) { + case 0: + return napi_ok; + case UV_EINVAL: + return napi_invalid_arg; + case UV_ECANCELED: + return napi_cancelled; + } + + return napi_generic_failure; +} + +// Wrapper around uv_work_t which calls user-provided callbacks. +class Work : public node::AsyncResource { + private: + explicit Work(napi_env env, + v8::Local async_resource, + v8::Local async_resource_name, + napi_async_execute_callback execute, + napi_async_complete_callback complete = nullptr, + void* data = nullptr) + : AsyncResource(env->isolate, + async_resource, + *v8::String::Utf8Value(async_resource_name)), + _env(env), + _data(data), + _execute(execute), + _complete(complete) { + memset(&_request, 0, sizeof(_request)); + _request.data = this; + } + + ~Work() { } + + public: + static Work* New(napi_env env, + v8::Local async_resource, + v8::Local async_resource_name, + napi_async_execute_callback execute, + napi_async_complete_callback complete, + void* data) { + return new Work(env, async_resource, async_resource_name, + execute, complete, data); + } + + static void Delete(Work* work) { + delete work; + } + + static void ExecuteCallback(uv_work_t* req) { + Work* work = static_cast(req->data); + work->_execute(work->_env, work->_data); + } + + static void CompleteCallback(uv_work_t* req, int status) { + Work* work = static_cast(req->data); + + if (work->_complete != nullptr) { + napi_env env = work->_env; + + // Establish a handle scope here so that every callback doesn't have to. + // Also it is needed for the exception-handling below. + v8::HandleScope scope(env->isolate); + CallbackScope callback_scope(work); + + NAPI_CALL_INTO_MODULE(env, + work->_complete(env, ConvertUVErrorCode(status), work->_data), + [env] (v8::Local local_err) { + // If there was an unhandled exception in the complete callback, + // report it as a fatal exception. (There is no JavaScript on the + // callstack that can possibly handle it.) + v8impl::trigger_fatal_exception(env, local_err); + }); + + // Note: Don't access `work` after this point because it was + // likely deleted by the complete callback. + } + } + + uv_work_t* Request() { + return &_request; + } + + private: + napi_env _env; + void* _data; + uv_work_t _request; + napi_async_execute_callback _execute; + napi_async_complete_callback _complete; +}; + +} // end of namespace uvimpl +} // end of anonymous namespace + +#define CALL_UV(env, condition) \ + do { \ + int result = (condition); \ + napi_status status = uvimpl::ConvertUVErrorCode(result); \ + if (status != napi_ok) { \ + return napi_set_last_error(env, status, result); \ + } \ + } while (0) + +napi_status napi_create_async_work(napi_env env, + napi_value async_resource, + napi_value async_resource_name, + napi_async_execute_callback execute, + napi_async_complete_callback complete, + void* data, + napi_async_work* result) { + CHECK_ENV(env); + CHECK_ARG(env, execute); + CHECK_ARG(env, result); + + v8::Local context = env->isolate->GetCurrentContext(); + + v8::Local resource; + if (async_resource != nullptr) { + CHECK_TO_OBJECT(env, context, resource, async_resource); + } else { + resource = v8::Object::New(env->isolate); + } + + v8::Local resource_name; + CHECK_TO_STRING(env, context, resource_name, async_resource_name); + + uvimpl::Work* work = + uvimpl::Work::New(env, resource, resource_name, + execute, complete, data); + + *result = reinterpret_cast(work); + + return napi_clear_last_error(env); +} + +napi_status napi_delete_async_work(napi_env env, napi_async_work work) { + CHECK_ENV(env); + CHECK_ARG(env, work); + + uvimpl::Work::Delete(reinterpret_cast(work)); + + return napi_clear_last_error(env); +} + +napi_status napi_get_uv_event_loop(napi_env env, uv_loop_t** loop) { + CHECK_ENV(env); + CHECK_ARG(env, loop); + *loop = env->loop; + return napi_clear_last_error(env); +} + +napi_status napi_queue_async_work(napi_env env, napi_async_work work) { + CHECK_ENV(env); + CHECK_ARG(env, work); + + napi_status status; + uv_loop_t* event_loop = nullptr; + status = napi_get_uv_event_loop(env, &event_loop); + if (status != napi_ok) + return napi_set_last_error(env, status); + + uvimpl::Work* w = reinterpret_cast(work); + + CALL_UV(env, uv_queue_work(event_loop, + w->Request(), + uvimpl::Work::ExecuteCallback, + uvimpl::Work::CompleteCallback)); + + return napi_clear_last_error(env); +} + +napi_status napi_cancel_async_work(napi_env env, napi_async_work work) { + CHECK_ENV(env); + CHECK_ARG(env, work); + + uvimpl::Work* w = reinterpret_cast(work); + + CALL_UV(env, uv_cancel(reinterpret_cast(w->Request()))); + + return napi_clear_last_error(env); +} + +napi_status napi_create_promise(napi_env env, + napi_deferred* deferred, + napi_value* promise) { + NAPI_PREAMBLE(env); + CHECK_ARG(env, deferred); + CHECK_ARG(env, promise); + + auto maybe = v8::Promise::Resolver::New(env->isolate->GetCurrentContext()); + CHECK_MAYBE_EMPTY(env, maybe, napi_generic_failure); + + auto v8_resolver = maybe.ToLocalChecked(); + auto v8_deferred = new v8::Persistent(); + v8_deferred->Reset(env->isolate, v8_resolver); + + *deferred = v8impl::JsDeferredFromV8Persistent(v8_deferred); + *promise = v8impl::JsValueFromV8LocalValue(v8_resolver->GetPromise()); + return GET_RETURN_STATUS(env); +} + +napi_status napi_resolve_deferred(napi_env env, + napi_deferred deferred, + napi_value resolution) { + return v8impl::ConcludeDeferred(env, deferred, resolution, true); +} + +napi_status napi_reject_deferred(napi_env env, + napi_deferred deferred, + napi_value resolution) { + return v8impl::ConcludeDeferred(env, deferred, resolution, false); +} + +napi_status napi_is_promise(napi_env env, + napi_value promise, + bool* is_promise) { + CHECK_ENV(env); + CHECK_ARG(env, promise); + CHECK_ARG(env, is_promise); + + *is_promise = v8impl::V8LocalValueFromJsValue(promise)->IsPromise(); + + return napi_clear_last_error(env); +} + +napi_status napi_run_script(napi_env env, + napi_value script, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(env, script); + CHECK_ARG(env, result); + + v8::Local v8_script = v8impl::V8LocalValueFromJsValue(script); + + if (!v8_script->IsString()) { + return napi_set_last_error(env, napi_string_expected); + } + + v8::Local context = env->isolate->GetCurrentContext(); + + auto maybe_script = v8::Script::Compile(context, + v8::Local::Cast(v8_script)); + CHECK_MAYBE_EMPTY(env, maybe_script, napi_generic_failure); + + auto script_result = + maybe_script.ToLocalChecked()->Run(context); + CHECK_MAYBE_EMPTY(env, script_result, napi_generic_failure); + + *result = v8impl::JsValueFromV8LocalValue(script_result.ToLocalChecked()); + return GET_RETURN_STATUS(env); +} diff --git a/src/node_api.h b/src/node_api.h new file mode 100644 index 00000000000000..a2a73d5bcfd8f5 --- /dev/null +++ b/src/node_api.h @@ -0,0 +1,593 @@ +#ifndef SRC_NODE_API_H_ +#define SRC_NODE_API_H_ + +#include +#include +#include "node_api_types.h" + +struct uv_loop_s; // Forward declaration. + +#ifdef _WIN32 + #ifdef BUILDING_NODE_EXTENSION + #ifdef EXTERNAL_NAPI + // Building external N-API, or native module against external N-API + #define NAPI_EXTERN /* nothing */ + #else + // Building native module against node with built-in N-API + #define NAPI_EXTERN __declspec(dllimport) + #endif + #else + // Building node with built-in N-API + #define NAPI_EXTERN __declspec(dllexport) + #endif +#else + #define NAPI_EXTERN /* nothing */ +#endif + +#ifdef _WIN32 +# define NAPI_MODULE_EXPORT __declspec(dllexport) +#else +# define NAPI_MODULE_EXPORT __attribute__((visibility("default"))) +#endif + +#ifdef __GNUC__ +#define NAPI_NO_RETURN __attribute__((noreturn)) +#else +#define NAPI_NO_RETURN +#endif + + +typedef napi_value (*napi_addon_register_func)(napi_env env, + napi_value exports); + +typedef struct { + int nm_version; + unsigned int nm_flags; + const char* nm_filename; + napi_addon_register_func nm_register_func; + const char* nm_modname; + void* nm_priv; + void* reserved[4]; +} napi_module; + +#define NAPI_MODULE_VERSION 1 + +#if defined(_MSC_VER) +#pragma section(".CRT$XCU", read) +#define NAPI_C_CTOR(fn) \ + static void __cdecl fn(void); \ + __declspec(dllexport, allocate(".CRT$XCU")) void(__cdecl * fn##_)(void) = \ + fn; \ + static void __cdecl fn(void) +#else +#define NAPI_C_CTOR(fn) \ + static void fn(void) __attribute__((constructor)); \ + static void fn(void) +#endif + +#ifdef __cplusplus +#define EXTERN_C_START extern "C" { +#define EXTERN_C_END } +#else +#define EXTERN_C_START +#define EXTERN_C_END +#endif + +#define NAPI_MODULE_X(modname, regfunc, priv, flags) \ + EXTERN_C_START \ + static napi_module _module = \ + { \ + NAPI_MODULE_VERSION, \ + flags, \ + __FILE__, \ + regfunc, \ + #modname, \ + priv, \ + {0}, \ + }; \ + NAPI_C_CTOR(_register_ ## modname) { \ + napi_module_register(&_module); \ + } \ + EXTERN_C_END + +#define NAPI_MODULE(modname, regfunc) \ + NAPI_MODULE_X(modname, regfunc, NULL, 0) + +#define NAPI_AUTO_LENGTH SIZE_MAX + +EXTERN_C_START + +NAPI_EXTERN void napi_module_register(napi_module* mod); + +NAPI_EXTERN napi_status +napi_get_last_error_info(napi_env env, + const napi_extended_error_info** result); + +NAPI_EXTERN napi_status napi_fatal_exception(napi_env env, napi_value err); + +NAPI_EXTERN NAPI_NO_RETURN void napi_fatal_error(const char* location, + size_t location_len, + const char* message, + size_t message_len); + +// Getters for defined singletons +NAPI_EXTERN napi_status napi_get_undefined(napi_env env, napi_value* result); +NAPI_EXTERN napi_status napi_get_null(napi_env env, napi_value* result); +NAPI_EXTERN napi_status napi_get_global(napi_env env, napi_value* result); +NAPI_EXTERN napi_status napi_get_boolean(napi_env env, + bool value, + napi_value* result); + +// Methods to create Primitive types/Objects +NAPI_EXTERN napi_status napi_create_object(napi_env env, napi_value* result); +NAPI_EXTERN napi_status napi_create_array(napi_env env, napi_value* result); +NAPI_EXTERN napi_status napi_create_array_with_length(napi_env env, + size_t length, + napi_value* result); +NAPI_EXTERN napi_status napi_create_double(napi_env env, + double value, + napi_value* result); +NAPI_EXTERN napi_status napi_create_int32(napi_env env, + int32_t value, + napi_value* result); +NAPI_EXTERN napi_status napi_create_uint32(napi_env env, + uint32_t value, + napi_value* result); +NAPI_EXTERN napi_status napi_create_int64(napi_env env, + int64_t value, + napi_value* result); +NAPI_EXTERN napi_status napi_create_string_latin1(napi_env env, + const char* str, + size_t length, + napi_value* result); +NAPI_EXTERN napi_status napi_create_string_utf8(napi_env env, + const char* str, + size_t length, + napi_value* result); +NAPI_EXTERN napi_status napi_create_string_utf16(napi_env env, + const char16_t* str, + size_t length, + napi_value* result); +NAPI_EXTERN napi_status napi_create_symbol(napi_env env, + napi_value description, + napi_value* result); +NAPI_EXTERN napi_status napi_create_function(napi_env env, + const char* utf8name, + size_t length, + napi_callback cb, + void* data, + napi_value* result); +NAPI_EXTERN napi_status napi_create_error(napi_env env, + napi_value code, + napi_value msg, + napi_value* result); +NAPI_EXTERN napi_status napi_create_type_error(napi_env env, + napi_value code, + napi_value msg, + napi_value* result); +NAPI_EXTERN napi_status napi_create_range_error(napi_env env, + napi_value code, + napi_value msg, + napi_value* result); + +// Methods to get the the native napi_value from Primitive type +NAPI_EXTERN napi_status napi_typeof(napi_env env, + napi_value value, + napi_valuetype* result); +NAPI_EXTERN napi_status napi_get_value_double(napi_env env, + napi_value value, + double* result); +NAPI_EXTERN napi_status napi_get_value_int32(napi_env env, + napi_value value, + int32_t* result); +NAPI_EXTERN napi_status napi_get_value_uint32(napi_env env, + napi_value value, + uint32_t* result); +NAPI_EXTERN napi_status napi_get_value_int64(napi_env env, + napi_value value, + int64_t* result); +NAPI_EXTERN napi_status napi_get_value_bool(napi_env env, + napi_value value, + bool* result); + +// Copies LATIN-1 encoded bytes from a string into a buffer. +NAPI_EXTERN napi_status napi_get_value_string_latin1(napi_env env, + napi_value value, + char* buf, + size_t bufsize, + size_t* result); + +// Copies UTF-8 encoded bytes from a string into a buffer. +NAPI_EXTERN napi_status napi_get_value_string_utf8(napi_env env, + napi_value value, + char* buf, + size_t bufsize, + size_t* result); + +// Copies UTF-16 encoded bytes from a string into a buffer. +NAPI_EXTERN napi_status napi_get_value_string_utf16(napi_env env, + napi_value value, + char16_t* buf, + size_t bufsize, + size_t* result); + +// Methods to coerce values +// These APIs may execute user scripts +NAPI_EXTERN napi_status napi_coerce_to_bool(napi_env env, + napi_value value, + napi_value* result); +NAPI_EXTERN napi_status napi_coerce_to_number(napi_env env, + napi_value value, + napi_value* result); +NAPI_EXTERN napi_status napi_coerce_to_object(napi_env env, + napi_value value, + napi_value* result); +NAPI_EXTERN napi_status napi_coerce_to_string(napi_env env, + napi_value value, + napi_value* result); + +// Methods to work with Objects +NAPI_EXTERN napi_status napi_get_prototype(napi_env env, + napi_value object, + napi_value* result); +NAPI_EXTERN napi_status napi_get_property_names(napi_env env, + napi_value object, + napi_value* result); +NAPI_EXTERN napi_status napi_set_property(napi_env env, + napi_value object, + napi_value key, + napi_value value); +NAPI_EXTERN napi_status napi_has_property(napi_env env, + napi_value object, + napi_value key, + bool* result); +NAPI_EXTERN napi_status napi_get_property(napi_env env, + napi_value object, + napi_value key, + napi_value* result); +NAPI_EXTERN napi_status napi_delete_property(napi_env env, + napi_value object, + napi_value key, + bool* result); +NAPI_EXTERN napi_status napi_has_own_property(napi_env env, + napi_value object, + napi_value key, + bool* result); +NAPI_EXTERN napi_status napi_set_named_property(napi_env env, + napi_value object, + const char* utf8name, + napi_value value); +NAPI_EXTERN napi_status napi_has_named_property(napi_env env, + napi_value object, + const char* utf8name, + bool* result); +NAPI_EXTERN napi_status napi_get_named_property(napi_env env, + napi_value object, + const char* utf8name, + napi_value* result); +NAPI_EXTERN napi_status napi_set_element(napi_env env, + napi_value object, + uint32_t index, + napi_value value); +NAPI_EXTERN napi_status napi_has_element(napi_env env, + napi_value object, + uint32_t index, + bool* result); +NAPI_EXTERN napi_status napi_get_element(napi_env env, + napi_value object, + uint32_t index, + napi_value* result); +NAPI_EXTERN napi_status napi_delete_element(napi_env env, + napi_value object, + uint32_t index, + bool* result); +NAPI_EXTERN napi_status +napi_define_properties(napi_env env, + napi_value object, + size_t property_count, + const napi_property_descriptor* properties); + +// Methods to work with Arrays +NAPI_EXTERN napi_status napi_is_array(napi_env env, + napi_value value, + bool* result); +NAPI_EXTERN napi_status napi_get_array_length(napi_env env, + napi_value value, + uint32_t* result); + +// Methods to compare values +NAPI_EXTERN napi_status napi_strict_equals(napi_env env, + napi_value lhs, + napi_value rhs, + bool* result); + +// Methods to work with Functions +NAPI_EXTERN napi_status napi_call_function(napi_env env, + napi_value recv, + napi_value func, + size_t argc, + const napi_value* argv, + napi_value* result); +NAPI_EXTERN napi_status napi_new_instance(napi_env env, + napi_value constructor, + size_t argc, + const napi_value* argv, + napi_value* result); +NAPI_EXTERN napi_status napi_instanceof(napi_env env, + napi_value object, + napi_value constructor, + bool* result); + +// Methods to work with napi_callbacks + +// Gets all callback info in a single call. (Ugly, but faster.) +NAPI_EXTERN napi_status napi_get_cb_info( + napi_env env, // [in] NAPI environment handle + napi_callback_info cbinfo, // [in] Opaque callback-info handle + size_t* argc, // [in-out] Specifies the size of the provided argv array + // and receives the actual count of args. + napi_value* argv, // [out] Array of values + napi_value* this_arg, // [out] Receives the JS 'this' arg for the call + void** data); // [out] Receives the data pointer for the callback. + +NAPI_EXTERN napi_status napi_get_new_target(napi_env env, + napi_callback_info cbinfo, + napi_value* result); +NAPI_EXTERN napi_status +napi_define_class(napi_env env, + const char* utf8name, + size_t length, + napi_callback constructor, + void* data, + size_t property_count, + const napi_property_descriptor* properties, + napi_value* result); + +// Methods to work with external data objects +NAPI_EXTERN napi_status napi_wrap(napi_env env, + napi_value js_object, + void* native_object, + napi_finalize finalize_cb, + void* finalize_hint, + napi_ref* result); +NAPI_EXTERN napi_status napi_unwrap(napi_env env, + napi_value js_object, + void** result); +NAPI_EXTERN napi_status napi_remove_wrap(napi_env env, + napi_value js_object, + void** result); +NAPI_EXTERN napi_status napi_create_external(napi_env env, + void* data, + napi_finalize finalize_cb, + void* finalize_hint, + napi_value* result); +NAPI_EXTERN napi_status napi_get_value_external(napi_env env, + napi_value value, + void** result); + +// Methods to control object lifespan + +// Set initial_refcount to 0 for a weak reference, >0 for a strong reference. +NAPI_EXTERN napi_status napi_create_reference(napi_env env, + napi_value value, + uint32_t initial_refcount, + napi_ref* result); + +// Deletes a reference. The referenced value is released, and may +// be GC'd unless there are other references to it. +NAPI_EXTERN napi_status napi_delete_reference(napi_env env, napi_ref ref); + +// Increments the reference count, optionally returning the resulting count. +// After this call the reference will be a strong reference because its +// refcount is >0, and the referenced object is effectively "pinned". +// Calling this when the refcount is 0 and the object is unavailable +// results in an error. +NAPI_EXTERN napi_status napi_reference_ref(napi_env env, + napi_ref ref, + uint32_t* result); + +// Decrements the reference count, optionally returning the resulting count. +// If the result is 0 the reference is now weak and the object may be GC'd +// at any time if there are no other references. Calling this when the +// refcount is already 0 results in an error. +NAPI_EXTERN napi_status napi_reference_unref(napi_env env, + napi_ref ref, + uint32_t* result); + +// Attempts to get a referenced value. If the reference is weak, +// the value might no longer be available, in that case the call +// is still successful but the result is NULL. +NAPI_EXTERN napi_status napi_get_reference_value(napi_env env, + napi_ref ref, + napi_value* result); + +NAPI_EXTERN napi_status napi_open_handle_scope(napi_env env, + napi_handle_scope* result); +NAPI_EXTERN napi_status napi_close_handle_scope(napi_env env, + napi_handle_scope scope); +NAPI_EXTERN napi_status +napi_open_escapable_handle_scope(napi_env env, + napi_escapable_handle_scope* result); +NAPI_EXTERN napi_status +napi_close_escapable_handle_scope(napi_env env, + napi_escapable_handle_scope scope); + +NAPI_EXTERN napi_status napi_escape_handle(napi_env env, + napi_escapable_handle_scope scope, + napi_value escapee, + napi_value* result); + +NAPI_EXTERN napi_status napi_open_callback_scope(napi_env env, + napi_value resource_object, + napi_async_context context, + napi_callback_scope* result); + +NAPI_EXTERN napi_status napi_close_callback_scope(napi_env env, + napi_callback_scope scope); + +// Methods to support error handling +NAPI_EXTERN napi_status napi_throw(napi_env env, napi_value error); +NAPI_EXTERN napi_status napi_throw_error(napi_env env, + const char* code, + const char* msg); +NAPI_EXTERN napi_status napi_throw_type_error(napi_env env, + const char* code, + const char* msg); +NAPI_EXTERN napi_status napi_throw_range_error(napi_env env, + const char* code, + const char* msg); +NAPI_EXTERN napi_status napi_is_error(napi_env env, + napi_value value, + bool* result); + +// Methods to support catching exceptions +NAPI_EXTERN napi_status napi_is_exception_pending(napi_env env, bool* result); +NAPI_EXTERN napi_status napi_get_and_clear_last_exception(napi_env env, + napi_value* result); + +// Methods to provide node::Buffer functionality with napi types +NAPI_EXTERN napi_status napi_create_buffer(napi_env env, + size_t length, + void** data, + napi_value* result); +NAPI_EXTERN napi_status napi_create_external_buffer(napi_env env, + size_t length, + void* data, + napi_finalize finalize_cb, + void* finalize_hint, + napi_value* result); +NAPI_EXTERN napi_status napi_create_buffer_copy(napi_env env, + size_t length, + const void* data, + void** result_data, + napi_value* result); +NAPI_EXTERN napi_status napi_is_buffer(napi_env env, + napi_value value, + bool* result); +NAPI_EXTERN napi_status napi_get_buffer_info(napi_env env, + napi_value value, + void** data, + size_t* length); + +// Methods to work with array buffers and typed arrays +NAPI_EXTERN napi_status napi_is_arraybuffer(napi_env env, + napi_value value, + bool* result); +NAPI_EXTERN napi_status napi_create_arraybuffer(napi_env env, + size_t byte_length, + void** data, + napi_value* result); +NAPI_EXTERN napi_status +napi_create_external_arraybuffer(napi_env env, + void* external_data, + size_t byte_length, + napi_finalize finalize_cb, + void* finalize_hint, + napi_value* result); +NAPI_EXTERN napi_status napi_get_arraybuffer_info(napi_env env, + napi_value arraybuffer, + void** data, + size_t* byte_length); +NAPI_EXTERN napi_status napi_is_typedarray(napi_env env, + napi_value value, + bool* result); +NAPI_EXTERN napi_status napi_create_typedarray(napi_env env, + napi_typedarray_type type, + size_t length, + napi_value arraybuffer, + size_t byte_offset, + napi_value* result); +NAPI_EXTERN napi_status napi_get_typedarray_info(napi_env env, + napi_value typedarray, + napi_typedarray_type* type, + size_t* length, + void** data, + napi_value* arraybuffer, + size_t* byte_offset); + +NAPI_EXTERN napi_status napi_create_dataview(napi_env env, + size_t length, + napi_value arraybuffer, + size_t byte_offset, + napi_value* result); +NAPI_EXTERN napi_status napi_is_dataview(napi_env env, + napi_value value, + bool* result); +NAPI_EXTERN napi_status napi_get_dataview_info(napi_env env, + napi_value dataview, + size_t* bytelength, + void** data, + napi_value* arraybuffer, + size_t* byte_offset); + +// Methods to manage simple async operations +NAPI_EXTERN +napi_status napi_create_async_work(napi_env env, + napi_value async_resource, + napi_value async_resource_name, + napi_async_execute_callback execute, + napi_async_complete_callback complete, + void* data, + napi_async_work* result); +NAPI_EXTERN napi_status napi_delete_async_work(napi_env env, + napi_async_work work); +NAPI_EXTERN napi_status napi_queue_async_work(napi_env env, + napi_async_work work); +NAPI_EXTERN napi_status napi_cancel_async_work(napi_env env, + napi_async_work work); + +// Methods for custom handling of async operations +NAPI_EXTERN napi_status napi_async_init(napi_env env, + napi_value async_resource, + napi_value async_resource_name, + napi_async_context* result); + +NAPI_EXTERN napi_status napi_async_destroy(napi_env env, + napi_async_context async_context); + +NAPI_EXTERN napi_status napi_make_callback(napi_env env, + napi_async_context async_context, + napi_value recv, + napi_value func, + size_t argc, + const napi_value* argv, + napi_value* result); + +// version management +NAPI_EXTERN napi_status napi_get_version(napi_env env, uint32_t* result); + +NAPI_EXTERN +napi_status napi_get_node_version(napi_env env, + const napi_node_version** version); + +// Promises +NAPI_EXTERN napi_status napi_create_promise(napi_env env, + napi_deferred* deferred, + napi_value* promise); +NAPI_EXTERN napi_status napi_resolve_deferred(napi_env env, + napi_deferred deferred, + napi_value resolution); +NAPI_EXTERN napi_status napi_reject_deferred(napi_env env, + napi_deferred deferred, + napi_value rejection); +NAPI_EXTERN napi_status napi_is_promise(napi_env env, + napi_value promise, + bool* is_promise); + +// Memory management +NAPI_EXTERN napi_status napi_adjust_external_memory(napi_env env, + int64_t change_in_bytes, + int64_t* adjusted_value); + +// Runnig a script +NAPI_EXTERN napi_status napi_run_script(napi_env env, + napi_value script, + napi_value* result); + +// Return the current libuv event loop for a given environment +NAPI_EXTERN napi_status napi_get_uv_event_loop(napi_env env, + struct uv_loop_s** loop); + +EXTERN_C_END + +#endif // SRC_NODE_API_H_ diff --git a/src/node_api_backport.cc b/src/node_api_backport.cc new file mode 100644 index 00000000000000..52a0653bfcc591 --- /dev/null +++ b/src/node_api_backport.cc @@ -0,0 +1,117 @@ +#include "node_api_backport.h" + +using v8::Function; +using v8::HandleScope; +using v8::Local; +using v8::MaybeLocal; +using v8::Object; +using v8::TryCatch; +using v8::Value; + +namespace node { + +CallbackScope::CallbackScope(v8::Isolate *_isolate, + v8::Local _object, + node::async_context context) : + isolate(_isolate), + env(node::Environment::GetCurrent(isolate->GetCurrentContext())), + _try_catch(isolate), + object(_object), + callback_scope( + node::Environment::GetCurrent(isolate->GetCurrentContext())) { + Local pre_fn = env->async_hooks_pre_function(); + + Local async_queue_v = object->Get(env->async_queue_string()); + if (async_queue_v->IsObject()) + ran_init_callback = true; + + if (ran_init_callback && !pre_fn.IsEmpty()) { + TryCatch try_catch(env->isolate()); + MaybeLocal ar = pre_fn->Call(env->context(), object, 0, nullptr); + if (ar.IsEmpty()) { + ClearFatalExceptionHandlers(env); + FatalException(env->isolate(), try_catch); + } + } +} + +CallbackScope::~CallbackScope() { + Local post_fn = env->async_hooks_post_function(); + if (ran_init_callback && !post_fn.IsEmpty()) { + Local did_throw = v8::Boolean::New(isolate, _try_catch.HasCaught()); + // Currently there's no way to retrieve an uid from node::MakeCallback(). + // This needs to be fixed. + Local vals[] = + { Undefined(env->isolate()).As(), did_throw }; + TryCatch try_catch(env->isolate()); + MaybeLocal ar = + post_fn->Call(env->context(), object, arraysize(vals), vals); + if (ar.IsEmpty()) { + ClearFatalExceptionHandlers(env); + FatalException(env->isolate(), try_catch); + return; + } + } + + if (callback_scope.in_makecallback()) { + return; + } + + Environment::TickInfo* tick_info = env->tick_info(); + + if (tick_info->length() == 0) { + env->isolate()->RunMicrotasks(); + } + + if (tick_info->length() == 0) { + tick_info->set_index(0); + return; + } + + env->tick_callback_function()->Call(env->process_object(), 0, nullptr); +} + +uv_loop_t *GetCurrentEventLoop(v8::Isolate *isolate) { + HandleScope handle_scope(isolate); + auto context = isolate->GetCurrentContext(); + if (context.IsEmpty()) + return nullptr; + return Environment::GetCurrent(context)->event_loop(); +} + +AsyncResource::AsyncResource(v8::Isolate* _isolate, + v8::Local _object, + char* name) : isolate(_isolate) { + object.Reset(isolate, _object); +} + +AsyncResource::~AsyncResource() { + object.Reset(); +} + +async_context EmitAsyncInit(v8::Isolate* isolate, + v8::Local resource, + v8::Local name, + async_id trigger_async_id) { + return async_context(); +} + +void EmitAsyncDestroy(v8::Isolate* isolate, + async_context asyncContext) { +} + +v8::MaybeLocal MakeCallback(v8::Isolate* isolate, + v8::Local recv, + v8::Local callback, + int argc, + v8::Local* argv, + async_context asyncContext) { + return node::MakeCallback(isolate, recv, callback, argc, argv); +} + +} // end of namespace node + +CallbackScope::CallbackScope(node::AsyncResource* work) : + scope(work->isolate, + v8::Local::New(work->isolate, + work->object), {0, 0}) {} diff --git a/src/node_api_backport.h b/src/node_api_backport.h new file mode 100644 index 00000000000000..1333b33eddfe14 --- /dev/null +++ b/src/node_api_backport.h @@ -0,0 +1,70 @@ +#ifndef SRC_NODE_API_BACKPORT_H_ +#define SRC_NODE_API_BACKPORT_H_ + +// This file contains stubs for symbols and other features (such as async hooks) +// which have appeared in later versions of Node.js, and which are required for +// N-API. + +#include "node_internals.h" +#include "env-inl.h" + +namespace node { + +typedef int async_id; + +typedef struct async_context { + node::async_id async_id; + node::async_id trigger_async_id; +} async_context; + +class CallbackScope { + public: + CallbackScope(v8::Isolate *isolate, + v8::Local object, + node::async_context context); + ~CallbackScope(); + private: + v8::Isolate* isolate = nullptr; + node::Environment* env = nullptr; + v8::TryCatch _try_catch; + bool ran_init_callback = false; + v8::Local object; + Environment::AsyncCallbackScope callback_scope; +}; + +uv_loop_t *GetCurrentEventLoop(v8::Isolate *isolate); + +NODE_EXTERN async_context EmitAsyncInit(v8::Isolate* isolate, + v8::Local resource, + v8::Local name, + async_id trigger_async_id = -1); + +NODE_EXTERN void EmitAsyncDestroy(v8::Isolate* isolate, + async_context asyncContext); + +v8::MaybeLocal MakeCallback(v8::Isolate* isolate, + v8::Local recv, + v8::Local callback, + int argc, + v8::Local* argv, + async_context asyncContext); + +class AsyncResource { + public: + AsyncResource(v8::Isolate* _isolate, + v8::Local _object, + char* name); + ~AsyncResource(); + v8::Isolate* isolate; + v8::Persistent object; +}; +} // end of namespace node + +class CallbackScope { + public: + explicit CallbackScope(node::AsyncResource* work); + private: + node::CallbackScope scope; +}; + +#endif // SRC_NODE_API_BACKPORT_H_ diff --git a/src/node_api_types.h b/src/node_api_types.h new file mode 100644 index 00000000000000..76f38802e83e2e --- /dev/null +++ b/src/node_api_types.h @@ -0,0 +1,117 @@ +#ifndef SRC_NODE_API_TYPES_H_ +#define SRC_NODE_API_TYPES_H_ + +#include +#include + +#if !defined __cplusplus || (defined(_MSC_VER) && _MSC_VER < 1900) + typedef uint16_t char16_t; +#endif + +// JSVM API types are all opaque pointers for ABI stability +// typedef undefined structs instead of void* for compile time type safety +typedef struct napi_env__ *napi_env; +typedef struct napi_value__ *napi_value; +typedef struct napi_ref__ *napi_ref; +typedef struct napi_handle_scope__ *napi_handle_scope; +typedef struct napi_escapable_handle_scope__ *napi_escapable_handle_scope; +typedef struct napi_callback_scope__ *napi_callback_scope; +typedef struct napi_callback_info__ *napi_callback_info; +typedef struct napi_async_context__ *napi_async_context; +typedef struct napi_async_work__ *napi_async_work; +typedef struct napi_deferred__ *napi_deferred; + +typedef enum { + napi_default = 0, + napi_writable = 1 << 0, + napi_enumerable = 1 << 1, + napi_configurable = 1 << 2, + + // Used with napi_define_class to distinguish static properties + // from instance properties. Ignored by napi_define_properties. + napi_static = 1 << 10, +} napi_property_attributes; + +typedef enum { + // ES6 types (corresponds to typeof) + napi_undefined, + napi_null, + napi_boolean, + napi_number, + napi_string, + napi_symbol, + napi_object, + napi_function, + napi_external, +} napi_valuetype; + +typedef enum { + napi_int8_array, + napi_uint8_array, + napi_uint8_clamped_array, + napi_int16_array, + napi_uint16_array, + napi_int32_array, + napi_uint32_array, + napi_float32_array, + napi_float64_array, +} napi_typedarray_type; + +typedef enum { + napi_ok, + napi_invalid_arg, + napi_object_expected, + napi_string_expected, + napi_name_expected, + napi_function_expected, + napi_number_expected, + napi_boolean_expected, + napi_array_expected, + napi_generic_failure, + napi_pending_exception, + napi_cancelled, + napi_escape_called_twice, + napi_handle_scope_mismatch, + napi_callback_scope_mismatch +} napi_status; + +typedef napi_value (*napi_callback)(napi_env env, + napi_callback_info info); +typedef void (*napi_finalize)(napi_env env, + void* finalize_data, + void* finalize_hint); +typedef void (*napi_async_execute_callback)(napi_env env, + void* data); +typedef void (*napi_async_complete_callback)(napi_env env, + napi_status status, + void* data); + +typedef struct { + // One of utf8name or name should be NULL. + const char* utf8name; + napi_value name; + + napi_callback method; + napi_callback getter; + napi_callback setter; + napi_value value; + + napi_property_attributes attributes; + void* data; +} napi_property_descriptor; + +typedef struct { + const char* error_message; + void* engine_reserved; + uint32_t engine_error_code; + napi_status error_code; +} napi_extended_error_info; + +typedef struct { + uint32_t major; + uint32_t minor; + uint32_t patch; + const char* release; +} napi_node_version; + +#endif // SRC_NODE_API_TYPES_H_ diff --git a/src/node_internals.h b/src/node_internals.h index de1b8adc87f4d3..bfb9bf296d7285 100644 --- a/src/node_internals.h +++ b/src/node_internals.h @@ -118,6 +118,11 @@ void GetSockOrPeerName(const v8::FunctionCallbackInfo& args) { args.GetReturnValue().Set(err); } +void FatalException(v8::Isolate* isolate, + v8::Local error, + v8::Local message); + + void SignalExit(int signo); #ifdef __POSIX__ void RegisterSignalHandler(int signal, diff --git a/src/node_version.h b/src/node_version.h index a6b675108a55df..e71ed35478e3b7 100644 --- a/src/node_version.h +++ b/src/node_version.h @@ -15,6 +15,10 @@ #define NODE_STRINGIFY_HELPER(n) #n #endif +#ifndef NODE_RELEASE +#define NODE_RELEASE "node" +#endif + #ifndef NODE_TAG # if NODE_VERSION_IS_RELEASE # define NODE_TAG "" @@ -57,4 +61,7 @@ */ #define NODE_MODULE_VERSION 48 /* Node.js v6.0.0 */ +// the NAPI_VERSION provided by this version of the runtime +#define NAPI_VERSION 3 + #endif // SRC_NODE_VERSION_H_ diff --git a/test/addons-napi/.gitignore b/test/addons-napi/.gitignore new file mode 100644 index 00000000000000..bde1cf3ab9662b --- /dev/null +++ b/test/addons-napi/.gitignore @@ -0,0 +1,7 @@ +.buildstamp +.docbuildstamp +Makefile +*.Makefile +*.mk +gyp-mac-tool +/*/build diff --git a/test/addons-napi/1_hello_world/binding.c b/test/addons-napi/1_hello_world/binding.c new file mode 100644 index 00000000000000..00f75e3a615b60 --- /dev/null +++ b/test/addons-napi/1_hello_world/binding.c @@ -0,0 +1,19 @@ +#include +#include "../common.h" +#include + +napi_value Method(napi_env env, napi_callback_info info) { + napi_value world; + const char* str = "world"; + size_t str_len = strlen(str); + NAPI_CALL(env, napi_create_string_utf8(env, str, str_len, &world)); + return world; +} + +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor desc = DECLARE_NAPI_PROPERTY("hello", Method); + NAPI_CALL(env, napi_define_properties(env, exports, 1, &desc)); + return exports; +} + +NAPI_MODULE(addon, Init) diff --git a/test/addons-napi/1_hello_world/binding.gyp b/test/addons-napi/1_hello_world/binding.gyp new file mode 100644 index 00000000000000..62381d5e54f22b --- /dev/null +++ b/test/addons-napi/1_hello_world/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "binding", + "sources": [ "binding.c" ] + } + ] +} diff --git a/test/addons-napi/1_hello_world/test.js b/test/addons-napi/1_hello_world/test.js new file mode 100644 index 00000000000000..c975c48a733f5c --- /dev/null +++ b/test/addons-napi/1_hello_world/test.js @@ -0,0 +1,6 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); +const addon = require(`./build/${common.buildType}/binding`); + +assert.strictEqual(addon.hello(), 'world'); diff --git a/test/addons-napi/2_function_arguments/binding.c b/test/addons-napi/2_function_arguments/binding.c new file mode 100644 index 00000000000000..d6f7e99a6d72c7 --- /dev/null +++ b/test/addons-napi/2_function_arguments/binding.c @@ -0,0 +1,38 @@ +#include +#include "../common.h" + +napi_value Add(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value args[2]; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NAPI_ASSERT(env, argc >= 2, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NAPI_CALL(env, napi_typeof(env, args[0], &valuetype0)); + + napi_valuetype valuetype1; + NAPI_CALL(env, napi_typeof(env, args[1], &valuetype1)); + + NAPI_ASSERT(env, valuetype0 == napi_number && valuetype1 == napi_number, + "Wrong argument type. Numbers expected."); + + double value0; + NAPI_CALL(env, napi_get_value_double(env, args[0], &value0)); + + double value1; + NAPI_CALL(env, napi_get_value_double(env, args[1], &value1)); + + napi_value sum; + NAPI_CALL(env, napi_create_double(env, value0 + value1, &sum)); + + return sum; +} + +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor desc = DECLARE_NAPI_PROPERTY("add", Add); + NAPI_CALL(env, napi_define_properties(env, exports, 1, &desc)); + return exports; +} + +NAPI_MODULE(addon, Init) diff --git a/test/addons-napi/2_function_arguments/binding.gyp b/test/addons-napi/2_function_arguments/binding.gyp new file mode 100644 index 00000000000000..62381d5e54f22b --- /dev/null +++ b/test/addons-napi/2_function_arguments/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "binding", + "sources": [ "binding.c" ] + } + ] +} diff --git a/test/addons-napi/2_function_arguments/test.js b/test/addons-napi/2_function_arguments/test.js new file mode 100644 index 00000000000000..e70f76b718bd10 --- /dev/null +++ b/test/addons-napi/2_function_arguments/test.js @@ -0,0 +1,6 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); +const addon = require(`./build/${common.buildType}/binding`); + +assert.strictEqual(addon.add(3, 5), 8); diff --git a/test/addons-napi/3_callbacks/binding.c b/test/addons-napi/3_callbacks/binding.c new file mode 100644 index 00000000000000..b9e6abd3965de5 --- /dev/null +++ b/test/addons-napi/3_callbacks/binding.c @@ -0,0 +1,57 @@ +#include +#include "../common.h" +#include + +napi_value RunCallback(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value args[2]; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NAPI_ASSERT(env, argc == 1, + "Wrong number of arguments. Expects a single argument."); + + napi_valuetype valuetype0; + NAPI_CALL(env, napi_typeof(env, args[0], &valuetype0)); + NAPI_ASSERT(env, valuetype0 == napi_function, + "Wrong type of arguments. Expects a function as first argument."); + + napi_valuetype valuetype1; + NAPI_CALL(env, napi_typeof(env, args[1], &valuetype1)); + NAPI_ASSERT(env, valuetype1 == napi_undefined, + "Additional arguments should be undefined."); + + napi_value argv[1]; + const char* str = "hello world"; + size_t str_len = strlen(str); + NAPI_CALL(env, napi_create_string_utf8(env, str, str_len, argv)); + + napi_value global; + NAPI_CALL(env, napi_get_global(env, &global)); + + napi_value cb = args[0]; + NAPI_CALL(env, napi_call_function(env, global, cb, 1, argv, NULL)); + + return NULL; +} + +napi_value RunCallbackWithRecv(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value args[2]; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + napi_value cb = args[0]; + napi_value recv = args[1]; + NAPI_CALL(env, napi_call_function(env, recv, cb, 0, NULL, NULL)); + return NULL; +} + +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor desc[2] = { + DECLARE_NAPI_PROPERTY("RunCallback", RunCallback), + DECLARE_NAPI_PROPERTY("RunCallbackWithRecv", RunCallbackWithRecv), + }; + NAPI_CALL(env, napi_define_properties(env, exports, 2, desc)); + return exports; +} + +NAPI_MODULE(addon, Init) diff --git a/test/addons-napi/3_callbacks/binding.gyp b/test/addons-napi/3_callbacks/binding.gyp new file mode 100644 index 00000000000000..62381d5e54f22b --- /dev/null +++ b/test/addons-napi/3_callbacks/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "binding", + "sources": [ "binding.c" ] + } + ] +} diff --git a/test/addons-napi/3_callbacks/test.js b/test/addons-napi/3_callbacks/test.js new file mode 100644 index 00000000000000..25e070d9744529 --- /dev/null +++ b/test/addons-napi/3_callbacks/test.js @@ -0,0 +1,22 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); +const addon = require(`./build/${common.buildType}/binding`); + +addon.RunCallback(function(msg) { + assert.strictEqual(msg, 'hello world'); +}); + +function testRecv(desiredRecv) { + addon.RunCallbackWithRecv(function() { + assert.strictEqual(this, desiredRecv); + }, desiredRecv); +} + +testRecv(undefined); +testRecv(null); +testRecv(5); +testRecv(true); +testRecv('Hello'); +testRecv([]); +testRecv({}); diff --git a/test/addons-napi/4_object_factory/binding.c b/test/addons-napi/4_object_factory/binding.c new file mode 100644 index 00000000000000..6bdca80ec365be --- /dev/null +++ b/test/addons-napi/4_object_factory/binding.c @@ -0,0 +1,23 @@ +#include +#include "../common.h" + +napi_value CreateObject(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 obj; + NAPI_CALL(env, napi_create_object(env, &obj)); + + NAPI_CALL(env, napi_set_named_property(env, obj, "msg", args[0])); + + return obj; +} + +napi_value Init(napi_env env, napi_value exports) { + NAPI_CALL(env, + napi_create_function(env, "exports", -1, CreateObject, NULL, &exports)); + return exports; +} + +NAPI_MODULE(addon, Init) diff --git a/test/addons-napi/4_object_factory/binding.gyp b/test/addons-napi/4_object_factory/binding.gyp new file mode 100644 index 00000000000000..62381d5e54f22b --- /dev/null +++ b/test/addons-napi/4_object_factory/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "binding", + "sources": [ "binding.c" ] + } + ] +} diff --git a/test/addons-napi/4_object_factory/test.js b/test/addons-napi/4_object_factory/test.js new file mode 100644 index 00000000000000..15313c1547f24b --- /dev/null +++ b/test/addons-napi/4_object_factory/test.js @@ -0,0 +1,8 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); +const addon = require(`./build/${common.buildType}/binding`); + +const obj1 = addon('hello'); +const obj2 = addon('world'); +assert.strictEqual(`${obj1.msg} ${obj2.msg}`, 'hello world'); diff --git a/test/addons-napi/5_function_factory/binding.c b/test/addons-napi/5_function_factory/binding.c new file mode 100644 index 00000000000000..6b2b6f8f0ebe81 --- /dev/null +++ b/test/addons-napi/5_function_factory/binding.c @@ -0,0 +1,23 @@ +#include +#include "../common.h" + +napi_value MyFunction(napi_env env, napi_callback_info info) { + napi_value str; + NAPI_CALL(env, napi_create_string_utf8(env, "hello world", -1, &str)); + return str; +} + +napi_value CreateFunction(napi_env env, napi_callback_info info) { + napi_value fn; + NAPI_CALL(env, + napi_create_function(env, "theFunction", -1, MyFunction, NULL, &fn)); + return fn; +} + +napi_value Init(napi_env env, napi_value exports) { + NAPI_CALL(env, + napi_create_function(env, "exports", -1, CreateFunction, NULL, &exports)); + return exports; +} + +NAPI_MODULE(addon, Init) diff --git a/test/addons-napi/5_function_factory/binding.gyp b/test/addons-napi/5_function_factory/binding.gyp new file mode 100644 index 00000000000000..62381d5e54f22b --- /dev/null +++ b/test/addons-napi/5_function_factory/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "binding", + "sources": [ "binding.c" ] + } + ] +} diff --git a/test/addons-napi/5_function_factory/test.js b/test/addons-napi/5_function_factory/test.js new file mode 100644 index 00000000000000..7521824e1e000a --- /dev/null +++ b/test/addons-napi/5_function_factory/test.js @@ -0,0 +1,7 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); +const addon = require(`./build/${common.buildType}/binding`); + +const fn = addon(); +assert.strictEqual(fn(), 'hello world'); // 'hello world' diff --git a/test/addons-napi/6_object_wrap/binding.cc b/test/addons-napi/6_object_wrap/binding.cc new file mode 100644 index 00000000000000..1481573a92226c --- /dev/null +++ b/test/addons-napi/6_object_wrap/binding.cc @@ -0,0 +1,9 @@ +#include "myobject.h" +#include "../common.h" + +napi_value Init(napi_env env, napi_value exports) { + MyObject::Init(env, exports); + return exports; +} + +NAPI_MODULE(addon, Init) diff --git a/test/addons-napi/6_object_wrap/binding.gyp b/test/addons-napi/6_object_wrap/binding.gyp new file mode 100644 index 00000000000000..d8f91601e9c588 --- /dev/null +++ b/test/addons-napi/6_object_wrap/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "binding", + "sources": [ "binding.cc", "myobject.cc" ] + } + ] +} diff --git a/test/addons-napi/6_object_wrap/myobject.cc b/test/addons-napi/6_object_wrap/myobject.cc new file mode 100644 index 00000000000000..aca91877d3a2ae --- /dev/null +++ b/test/addons-napi/6_object_wrap/myobject.cc @@ -0,0 +1,150 @@ +#include "myobject.h" +#include "../common.h" + +napi_ref MyObject::constructor; + +MyObject::MyObject(double value) + : value_(value), env_(nullptr), wrapper_(nullptr) {} + +MyObject::~MyObject() { napi_delete_reference(env_, wrapper_); } + +void MyObject::Destructor( + napi_env env, void* nativeObject, void* /*finalize_hint*/) { + MyObject* obj = static_cast(nativeObject); + delete obj; +} + +void MyObject::Init(napi_env env, napi_value exports) { + napi_property_descriptor properties[] = { + { "value", nullptr, nullptr, GetValue, SetValue, 0, napi_default, 0 }, + DECLARE_NAPI_PROPERTY("plusOne", PlusOne), + DECLARE_NAPI_PROPERTY("multiply", Multiply), + }; + + napi_value cons; + NAPI_CALL_RETURN_VOID(env, napi_define_class( + env, "MyObject", -1, New, nullptr, 3, properties, &cons)); + + NAPI_CALL_RETURN_VOID(env, napi_create_reference(env, cons, 1, &constructor)); + + NAPI_CALL_RETURN_VOID(env, + napi_set_named_property(env, exports, "MyObject", cons)); +} + +napi_value MyObject::New(napi_env env, napi_callback_info info) { + napi_value new_target; + NAPI_CALL(env, napi_get_new_target(env, info, &new_target)); + bool is_constructor = (new_target != nullptr); + + size_t argc = 1; + napi_value args[1]; + napi_value _this; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, &_this, nullptr)); + + if (is_constructor) { + // Invoked as constructor: `new MyObject(...)` + double value = 0; + + napi_valuetype valuetype; + NAPI_CALL(env, napi_typeof(env, args[0], &valuetype)); + + if (valuetype != napi_undefined) { + NAPI_CALL(env, napi_get_value_double(env, args[0], &value)); + } + + MyObject* obj = new MyObject(value); + + obj->env_ = env; + NAPI_CALL(env, napi_wrap(env, + _this, + obj, + MyObject::Destructor, + nullptr, // finalize_hint + &obj->wrapper_)); + + return _this; + } + + // Invoked as plain function `MyObject(...)`, turn into construct call. + argc = 1; + napi_value argv[1] = {args[0]}; + + napi_value cons; + NAPI_CALL(env, napi_get_reference_value(env, constructor, &cons)); + + napi_value instance; + NAPI_CALL(env, napi_new_instance(env, cons, argc, argv, &instance)); + + return instance; +} + +napi_value MyObject::GetValue(napi_env env, napi_callback_info info) { + napi_value _this; + NAPI_CALL(env, + napi_get_cb_info(env, info, nullptr, nullptr, &_this, nullptr)); + + MyObject* obj; + NAPI_CALL(env, napi_unwrap(env, _this, reinterpret_cast(&obj))); + + napi_value num; + NAPI_CALL(env, napi_create_double(env, obj->value_, &num)); + + return num; +} + +napi_value MyObject::SetValue(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + napi_value _this; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, &_this, nullptr)); + + MyObject* obj; + NAPI_CALL(env, napi_unwrap(env, _this, reinterpret_cast(&obj))); + + NAPI_CALL(env, napi_get_value_double(env, args[0], &obj->value_)); + + return nullptr; +} + +napi_value MyObject::PlusOne(napi_env env, napi_callback_info info) { + napi_value _this; + NAPI_CALL(env, + napi_get_cb_info(env, info, nullptr, nullptr, &_this, nullptr)); + + MyObject* obj; + NAPI_CALL(env, napi_unwrap(env, _this, reinterpret_cast(&obj))); + + obj->value_ += 1; + + napi_value num; + NAPI_CALL(env, napi_create_double(env, obj->value_, &num)); + + return num; +} + +napi_value MyObject::Multiply(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + napi_value _this; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, &_this, nullptr)); + + double multiple = 1; + if (argc >= 1) { + NAPI_CALL(env, napi_get_value_double(env, args[0], &multiple)); + } + + MyObject* obj; + NAPI_CALL(env, napi_unwrap(env, _this, reinterpret_cast(&obj))); + + napi_value cons; + NAPI_CALL(env, napi_get_reference_value(env, constructor, &cons)); + + const int kArgCount = 1; + napi_value argv[kArgCount]; + NAPI_CALL(env, napi_create_double(env, obj->value_ * multiple, argv)); + + napi_value instance; + NAPI_CALL(env, napi_new_instance(env, cons, kArgCount, argv, &instance)); + + return instance; +} diff --git a/test/addons-napi/6_object_wrap/myobject.h b/test/addons-napi/6_object_wrap/myobject.h new file mode 100644 index 00000000000000..b7f425951dda7f --- /dev/null +++ b/test/addons-napi/6_object_wrap/myobject.h @@ -0,0 +1,26 @@ +#ifndef TEST_ADDONS_NAPI_6_OBJECT_WRAP_MYOBJECT_H_ +#define TEST_ADDONS_NAPI_6_OBJECT_WRAP_MYOBJECT_H_ + +#include + +class MyObject { + public: + static void Init(napi_env env, napi_value exports); + static void Destructor(napi_env env, void* nativeObject, void* finalize_hint); + + private: + explicit MyObject(double value_ = 0); + ~MyObject(); + + static napi_value New(napi_env env, napi_callback_info info); + static napi_value GetValue(napi_env env, napi_callback_info info); + static napi_value SetValue(napi_env env, napi_callback_info info); + static napi_value PlusOne(napi_env env, napi_callback_info info); + static napi_value Multiply(napi_env env, napi_callback_info info); + static napi_ref constructor; + double value_; + napi_env env_; + napi_ref wrapper_; +}; + +#endif // TEST_ADDONS_NAPI_6_OBJECT_WRAP_MYOBJECT_H_ diff --git a/test/addons-napi/6_object_wrap/test.js b/test/addons-napi/6_object_wrap/test.js new file mode 100644 index 00000000000000..4d89da6a4350bd --- /dev/null +++ b/test/addons-napi/6_object_wrap/test.js @@ -0,0 +1,19 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); +const addon = require(`./build/${common.buildType}/binding`); + +const obj = new addon.MyObject(9); +assert.strictEqual(obj.value, 9); +obj.value = 10; +assert.strictEqual(obj.value, 10); +assert.strictEqual(obj.plusOne(), 11); +assert.strictEqual(obj.plusOne(), 12); +assert.strictEqual(obj.plusOne(), 13); + +assert.strictEqual(obj.multiply().value, 13); +assert.strictEqual(obj.multiply(10).value, 130); + +const newobj = obj.multiply(-1); +assert.strictEqual(newobj.value, -13); +assert.notStrictEqual(obj, newobj); diff --git a/test/addons-napi/7_factory_wrap/binding.cc b/test/addons-napi/7_factory_wrap/binding.cc new file mode 100644 index 00000000000000..9d8f23c561d77a --- /dev/null +++ b/test/addons-napi/7_factory_wrap/binding.cc @@ -0,0 +1,23 @@ +#include "myobject.h" +#include "../common.h" + +napi_value CreateObject(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, nullptr, nullptr)); + + napi_value instance; + NAPI_CALL(env, MyObject::NewInstance(env, args[0], &instance)); + + return instance; +} + +napi_value Init(napi_env env, napi_value exports) { + NAPI_CALL(env, MyObject::Init(env)); + + NAPI_CALL(env, + napi_create_function(env, "exports", -1, CreateObject, NULL, &exports)); + return exports; +} + +NAPI_MODULE(addon, Init) diff --git a/test/addons-napi/7_factory_wrap/binding.gyp b/test/addons-napi/7_factory_wrap/binding.gyp new file mode 100644 index 00000000000000..d8f91601e9c588 --- /dev/null +++ b/test/addons-napi/7_factory_wrap/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "binding", + "sources": [ "binding.cc", "myobject.cc" ] + } + ] +} diff --git a/test/addons-napi/7_factory_wrap/myobject.cc b/test/addons-napi/7_factory_wrap/myobject.cc new file mode 100644 index 00000000000000..4e1d79c1febc17 --- /dev/null +++ b/test/addons-napi/7_factory_wrap/myobject.cc @@ -0,0 +1,94 @@ +#include "myobject.h" +#include "../common.h" + +MyObject::MyObject() : env_(nullptr), wrapper_(nullptr) {} + +MyObject::~MyObject() { napi_delete_reference(env_, wrapper_); } + +void MyObject::Destructor(napi_env env, + void* nativeObject, + void* /*finalize_hint*/) { + MyObject* obj = static_cast(nativeObject); + delete obj; +} + +napi_ref MyObject::constructor; + +napi_status MyObject::Init(napi_env env) { + napi_status status; + napi_property_descriptor properties[] = { + DECLARE_NAPI_PROPERTY("plusOne", PlusOne), + }; + + napi_value cons; + status = napi_define_class( + env, "MyObject", -1, New, nullptr, 1, properties, &cons); + if (status != napi_ok) return status; + + status = napi_create_reference(env, cons, 1, &constructor); + if (status != napi_ok) return status; + + return napi_ok; +} + +napi_value MyObject::New(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + napi_value _this; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, &_this, nullptr)); + + napi_valuetype valuetype; + NAPI_CALL(env, napi_typeof(env, args[0], &valuetype)); + + MyObject* obj = new MyObject(); + + if (valuetype == napi_undefined) { + obj->counter_ = 0; + } else { + NAPI_CALL(env, napi_get_value_uint32(env, args[0], &obj->counter_)); + } + + obj->env_ = env; + NAPI_CALL(env, napi_wrap(env, + _this, + obj, + MyObject::Destructor, + nullptr, /* finalize_hint */ + &obj->wrapper_)); + + return _this; +} + +napi_status MyObject::NewInstance(napi_env env, + napi_value arg, + napi_value* instance) { + napi_status status; + + const int argc = 1; + napi_value argv[argc] = {arg}; + + napi_value cons; + status = napi_get_reference_value(env, constructor, &cons); + if (status != napi_ok) return status; + + status = napi_new_instance(env, cons, argc, argv, instance); + if (status != napi_ok) return status; + + return napi_ok; +} + +napi_value MyObject::PlusOne(napi_env env, napi_callback_info info) { + napi_value _this; + NAPI_CALL(env, + napi_get_cb_info(env, info, nullptr, nullptr, &_this, nullptr)); + + MyObject* obj; + NAPI_CALL(env, napi_unwrap(env, _this, reinterpret_cast(&obj))); + + obj->counter_ += 1; + + napi_value num; + NAPI_CALL(env, napi_create_uint32(env, obj->counter_, &num)); + + return num; +} diff --git a/test/addons-napi/7_factory_wrap/myobject.h b/test/addons-napi/7_factory_wrap/myobject.h new file mode 100644 index 00000000000000..28ca94d16e30dd --- /dev/null +++ b/test/addons-napi/7_factory_wrap/myobject.h @@ -0,0 +1,26 @@ +#ifndef TEST_ADDONS_NAPI_7_FACTORY_WRAP_MYOBJECT_H_ +#define TEST_ADDONS_NAPI_7_FACTORY_WRAP_MYOBJECT_H_ + +#include + +class MyObject { + public: + static napi_status Init(napi_env env); + static void Destructor(napi_env env, void* nativeObject, void* finalize_hint); + static napi_status NewInstance(napi_env env, + napi_value arg, + napi_value* instance); + + private: + MyObject(); + ~MyObject(); + + static napi_ref constructor; + static napi_value New(napi_env env, napi_callback_info info); + static napi_value PlusOne(napi_env env, napi_callback_info info); + uint32_t counter_; + napi_env env_; + napi_ref wrapper_; +}; + +#endif // TEST_ADDONS_NAPI_7_FACTORY_WRAP_MYOBJECT_H_ diff --git a/test/addons-napi/7_factory_wrap/test.js b/test/addons-napi/7_factory_wrap/test.js new file mode 100644 index 00000000000000..20108e14b629bc --- /dev/null +++ b/test/addons-napi/7_factory_wrap/test.js @@ -0,0 +1,14 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); +const createObject = require(`./build/${common.buildType}/binding`); + +const obj = createObject(10); +assert.strictEqual(obj.plusOne(), 11); +assert.strictEqual(obj.plusOne(), 12); +assert.strictEqual(obj.plusOne(), 13); + +const obj2 = createObject(20); +assert.strictEqual(obj2.plusOne(), 21); +assert.strictEqual(obj2.plusOne(), 22); +assert.strictEqual(obj2.plusOne(), 23); diff --git a/test/addons-napi/8_passing_wrapped/binding.cc b/test/addons-napi/8_passing_wrapped/binding.cc new file mode 100644 index 00000000000000..f38861d54d8dc2 --- /dev/null +++ b/test/addons-napi/8_passing_wrapped/binding.cc @@ -0,0 +1,55 @@ +#include "myobject.h" +#include "../common.h" + +extern size_t finalize_count; + +static napi_value CreateObject(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, nullptr, nullptr)); + + napi_value instance; + NAPI_CALL(env, MyObject::NewInstance(env, args[0], &instance)); + + return instance; +} + +static napi_value Add(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value args[2]; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)); + + MyObject* obj1; + NAPI_CALL(env, napi_unwrap(env, args[0], reinterpret_cast(&obj1))); + + MyObject* obj2; + NAPI_CALL(env, napi_unwrap(env, args[1], reinterpret_cast(&obj2))); + + napi_value sum; + NAPI_CALL(env, napi_create_double(env, obj1->Val() + obj2->Val(), &sum)); + + return sum; +} + +static napi_value FinalizeCount(napi_env env, napi_callback_info info) { + napi_value return_value; + NAPI_CALL(env, napi_create_uint32(env, finalize_count, &return_value)); + return return_value; +} + +static napi_value Init(napi_env env, napi_value exports) { + MyObject::Init(env); + + napi_property_descriptor desc[] = { + DECLARE_NAPI_PROPERTY("createObject", CreateObject), + DECLARE_NAPI_PROPERTY("add", Add), + DECLARE_NAPI_PROPERTY("finalizeCount", FinalizeCount), + }; + + NAPI_CALL(env, + napi_define_properties(env, exports, sizeof(desc) / sizeof(*desc), desc)); + + return exports; +} + +NAPI_MODULE(addon, Init) diff --git a/test/addons-napi/8_passing_wrapped/binding.gyp b/test/addons-napi/8_passing_wrapped/binding.gyp new file mode 100644 index 00000000000000..d8f91601e9c588 --- /dev/null +++ b/test/addons-napi/8_passing_wrapped/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "binding", + "sources": [ "binding.cc", "myobject.cc" ] + } + ] +} diff --git a/test/addons-napi/8_passing_wrapped/myobject.cc b/test/addons-napi/8_passing_wrapped/myobject.cc new file mode 100644 index 00000000000000..0c9ca90f52f8f3 --- /dev/null +++ b/test/addons-napi/8_passing_wrapped/myobject.cc @@ -0,0 +1,84 @@ +#include "myobject.h" +#include "../common.h" + +size_t finalize_count = 0; + +MyObject::MyObject() : env_(nullptr), wrapper_(nullptr) {} + +MyObject::~MyObject() { + finalize_count++; + napi_delete_reference(env_, wrapper_); +} + +void MyObject::Destructor( + napi_env env, void* nativeObject, void* /*finalize_hint*/) { + MyObject* obj = static_cast(nativeObject); + delete obj; +} + +napi_ref MyObject::constructor; + +napi_status MyObject::Init(napi_env env) { + napi_status status; + + napi_value cons; + status = napi_define_class( + env, "MyObject", -1, New, nullptr, 0, nullptr, &cons); + if (status != napi_ok) return status; + + status = napi_create_reference(env, cons, 1, &constructor); + if (status != napi_ok) return status; + + return napi_ok; +} + +napi_value MyObject::New(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + napi_value _this; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, &_this, nullptr)); + + MyObject* obj = new MyObject(); + + napi_valuetype valuetype; + NAPI_CALL(env, napi_typeof(env, args[0], &valuetype)); + + if (valuetype == napi_undefined) { + obj->val_ = 0; + } else { + NAPI_CALL(env, napi_get_value_double(env, args[0], &obj->val_)); + } + + obj->env_ = env; + + // It is important that the below call to napi_wrap() be such that we request + // a reference to the wrapped object via the out-parameter, because this + // ensures that we test the code path that deals with a reference that is + // destroyed from its own finalizer. + NAPI_CALL(env, napi_wrap(env, + _this, + obj, + MyObject::Destructor, + nullptr, // finalize_hint + &obj->wrapper_)); + + return _this; +} + +napi_status MyObject::NewInstance(napi_env env, + napi_value arg, + napi_value* instance) { + napi_status status; + + const int argc = 1; + napi_value argv[argc] = {arg}; + + napi_value cons; + status = napi_get_reference_value(env, constructor, &cons); + if (status != napi_ok) return status; + + status = napi_new_instance(env, cons, argc, argv, instance); + if (status != napi_ok) return status; + + return napi_ok; +} diff --git a/test/addons-napi/8_passing_wrapped/myobject.h b/test/addons-napi/8_passing_wrapped/myobject.h new file mode 100644 index 00000000000000..7c6a35aa853cc5 --- /dev/null +++ b/test/addons-napi/8_passing_wrapped/myobject.h @@ -0,0 +1,26 @@ +#ifndef TEST_ADDONS_NAPI_8_PASSING_WRAPPED_MYOBJECT_H_ +#define TEST_ADDONS_NAPI_8_PASSING_WRAPPED_MYOBJECT_H_ + +#include + +class MyObject { + public: + static napi_status Init(napi_env env); + static void Destructor(napi_env env, void* nativeObject, void* finalize_hint); + static napi_status NewInstance(napi_env env, + napi_value arg, + napi_value* instance); + double Val() const { return val_; } + + private: + MyObject(); + ~MyObject(); + + static napi_ref constructor; + static napi_value New(napi_env env, napi_callback_info info); + double val_; + napi_env env_; + napi_ref wrapper_; +}; + +#endif // TEST_ADDONS_NAPI_8_PASSING_WRAPPED_MYOBJECT_H_ diff --git a/test/addons-napi/8_passing_wrapped/test.js b/test/addons-napi/8_passing_wrapped/test.js new file mode 100644 index 00000000000000..7793133f7750ba --- /dev/null +++ b/test/addons-napi/8_passing_wrapped/test.js @@ -0,0 +1,16 @@ +'use strict'; +// Flags: --expose-gc + +const common = require('../../common'); +const assert = require('assert'); +const addon = require(`./build/${common.buildType}/binding`); + +let obj1 = addon.createObject(10); +const obj2 = addon.createObject(20); +const result = addon.add(obj1, obj2); +assert.strictEqual(result, 30); + +// Make sure the native destructor gets called. +obj1 = null; +global.gc(); +assert.strictEqual(addon.finalizeCount(), 1); diff --git a/test/addons-napi/common.h b/test/addons-napi/common.h new file mode 100644 index 00000000000000..422418ced49a39 --- /dev/null +++ b/test/addons-napi/common.h @@ -0,0 +1,60 @@ +// Empty value so that macros here are able to return NULL or void +#define NAPI_RETVAL_NOTHING // Intentionally blank #define + +#define GET_AND_THROW_LAST_ERROR(env) \ + do { \ + const napi_extended_error_info *error_info; \ + napi_get_last_error_info((env), &error_info); \ + bool is_pending; \ + napi_is_exception_pending((env), &is_pending); \ + /* If an exception is already pending, don't rethrow it */ \ + if (!is_pending) { \ + const char* error_message = error_info->error_message != NULL ? \ + error_info->error_message : \ + "empty error message"; \ + napi_throw_error((env), NULL, error_message); \ + } \ + } while (0) + +#define NAPI_ASSERT_BASE(env, assertion, message, ret_val) \ + do { \ + if (!(assertion)) { \ + napi_throw_error( \ + (env), \ + NULL, \ + "assertion (" #assertion ") failed: " message); \ + return ret_val; \ + } \ + } while (0) + +// Returns NULL on failed assertion. +// This is meant to be used inside napi_callback methods. +#define NAPI_ASSERT(env, assertion, message) \ + NAPI_ASSERT_BASE(env, assertion, message, NULL) + +// Returns empty on failed assertion. +// This is meant to be used inside functions with void return type. +#define NAPI_ASSERT_RETURN_VOID(env, assertion, message) \ + NAPI_ASSERT_BASE(env, assertion, message, NAPI_RETVAL_NOTHING) + +#define NAPI_CALL_BASE(env, the_call, ret_val) \ + do { \ + if ((the_call) != napi_ok) { \ + GET_AND_THROW_LAST_ERROR((env)); \ + return ret_val; \ + } \ + } while (0) + +// Returns NULL if the_call doesn't return napi_ok. +#define NAPI_CALL(env, the_call) \ + NAPI_CALL_BASE(env, the_call, NULL) + +// Returns empty if the_call doesn't return napi_ok. +#define NAPI_CALL_RETURN_VOID(env, the_call) \ + NAPI_CALL_BASE(env, the_call, NAPI_RETVAL_NOTHING) + +#define DECLARE_NAPI_PROPERTY(name, func) \ + { (name), 0, (func), 0, 0, 0, napi_default, 0 } + +#define DECLARE_NAPI_GETTER(name, func) \ + { (name), 0, 0, (func), 0, 0, napi_default, 0 } diff --git a/test/addons-napi/test_array/binding.gyp b/test/addons-napi/test_array/binding.gyp new file mode 100644 index 00000000000000..44920de0986fdc --- /dev/null +++ b/test/addons-napi/test_array/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "test_array", + "sources": [ "test_array.c" ] + } + ] +} diff --git a/test/addons-napi/test_array/test.js b/test/addons-napi/test_array/test.js new file mode 100644 index 00000000000000..75c181d9da8269 --- /dev/null +++ b/test/addons-napi/test_array/test.js @@ -0,0 +1,60 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); + +// Testing api calls for arrays +const test_array = require(`./build/${common.buildType}/test_array`); + +const array = [ + 1, + 9, + 48, + 13493, + 9459324, + { name: 'hello' }, + [ + 'world', + 'node', + 'abi' + ] +]; + +assert.throws( + () => { + test_array.TestGetElement(array, array.length + 1); + }, + /^Error: assertion \(\(\(uint32_t\)index < length\)\) failed: Index out of bounds!$/ +); + +assert.throws( + () => { + test_array.TestGetElement(array, -2); + }, + /^Error: assertion \(index >= 0\) failed: Invalid index\. Expects a positive integer\.$/ +); + +array.forEach(function(element, index) { + assert.strictEqual(test_array.TestGetElement(array, index), element); +}); + + +assert.deepStrictEqual(test_array.New(array), array); + +assert(test_array.TestHasElement(array, 0)); +assert.strictEqual(test_array.TestHasElement(array, array.length + 1), false); + +assert(test_array.NewWithLength(0) instanceof Array); +assert(test_array.NewWithLength(1) instanceof Array); +// check max allowed length for an array 2^32 -1 +assert(test_array.NewWithLength(4294967295) instanceof Array); + +{ + // Verify that array elements can be deleted. + const arr = ['a', 'b', 'c', 'd']; + + assert.strictEqual(arr.length, 4); + assert.strictEqual(2 in arr, true); + assert.strictEqual(test_array.TestDeleteElement(arr, 2), true); + assert.strictEqual(arr.length, 4); + assert.strictEqual(2 in arr, false); +} diff --git a/test/addons-napi/test_array/test_array.c b/test/addons-napi/test_array/test_array.c new file mode 100644 index 00000000000000..74207560227c48 --- /dev/null +++ b/test/addons-napi/test_array/test_array.c @@ -0,0 +1,187 @@ +#include +#include +#include "../common.h" + +napi_value TestGetElement(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value args[2]; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NAPI_ASSERT(env, argc >= 2, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NAPI_CALL(env, napi_typeof(env, args[0], &valuetype0)); + + NAPI_ASSERT(env, valuetype0 == napi_object, + "Wrong type of arguments. Expects an array as first argument."); + + napi_valuetype valuetype1; + NAPI_CALL(env, napi_typeof(env, args[1], &valuetype1)); + + NAPI_ASSERT(env, valuetype1 == napi_number, + "Wrong type of arguments. Expects an integer as second argument."); + + napi_value array = args[0]; + int32_t index; + NAPI_CALL(env, napi_get_value_int32(env, args[1], &index)); + + NAPI_ASSERT(env, index >= 0, "Invalid index. Expects a positive integer."); + + bool isarray; + NAPI_CALL(env, napi_is_array(env, array, &isarray)); + + if (!isarray) { + return NULL; + } + + uint32_t length; + NAPI_CALL(env, napi_get_array_length(env, array, &length)); + + NAPI_ASSERT(env, ((uint32_t)index < length), "Index out of bounds!"); + + napi_value ret; + NAPI_CALL(env, napi_get_element(env, array, index, &ret)); + + return ret; +} + +napi_value TestHasElement(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value args[2]; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NAPI_ASSERT(env, argc >= 2, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NAPI_CALL(env, napi_typeof(env, args[0], &valuetype0)); + + NAPI_ASSERT(env, valuetype0 == napi_object, + "Wrong type of arguments. Expects an array as first argument."); + + napi_valuetype valuetype1; + NAPI_CALL(env, napi_typeof(env, args[1], &valuetype1)); + + NAPI_ASSERT(env, valuetype1 == napi_number, + "Wrong type of arguments. Expects an integer as second argument."); + + napi_value array = args[0]; + int32_t index; + NAPI_CALL(env, napi_get_value_int32(env, args[1], &index)); + + bool isarray; + NAPI_CALL(env, napi_is_array(env, array, &isarray)); + + if (!isarray) { + return NULL; + } + + bool has_element; + NAPI_CALL(env, napi_has_element(env, array, index, &has_element)); + + napi_value ret; + NAPI_CALL(env, napi_get_boolean(env, has_element, &ret)); + + return ret; +} + +napi_value TestDeleteElement(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value args[2]; + + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + NAPI_ASSERT(env, argc == 2, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NAPI_CALL(env, napi_typeof(env, args[0], &valuetype0)); + NAPI_ASSERT(env, valuetype0 == napi_object, + "Wrong type of arguments. Expects an array as first argument."); + + napi_valuetype valuetype1; + NAPI_CALL(env, napi_typeof(env, args[1], &valuetype1)); + NAPI_ASSERT(env, valuetype1 == napi_number, + "Wrong type of arguments. Expects an integer as second argument."); + + napi_value array = args[0]; + int32_t index; + bool result; + napi_value ret; + + NAPI_CALL(env, napi_get_value_int32(env, args[1], &index)); + NAPI_CALL(env, napi_is_array(env, array, &result)); + + if (!result) { + return NULL; + } + + NAPI_CALL(env, napi_delete_element(env, array, index, &result)); + NAPI_CALL(env, napi_get_boolean(env, result, &ret)); + + return ret; +} + +napi_value New(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_ASSERT(env, argc >= 1, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NAPI_CALL(env, napi_typeof(env, args[0], &valuetype0)); + + NAPI_ASSERT(env, valuetype0 == napi_object, + "Wrong type of arguments. Expects an array as first argument."); + + napi_value ret; + NAPI_CALL(env, napi_create_array(env, &ret)); + + uint32_t i, length; + NAPI_CALL(env, napi_get_array_length(env, args[0], &length)); + + for (i = 0; i < length; i++) { + napi_value e; + NAPI_CALL(env, napi_get_element(env, args[0], i, &e)); + NAPI_CALL(env, napi_set_element(env, ret, i, e)); + } + + return ret; +} + +napi_value NewWithLength(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_ASSERT(env, argc >= 1, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NAPI_CALL(env, napi_typeof(env, args[0], &valuetype0)); + + NAPI_ASSERT(env, valuetype0 == napi_number, + "Wrong type of arguments. Expects an integer the first argument."); + + int32_t array_length; + NAPI_CALL(env, napi_get_value_int32(env, args[0], &array_length)); + + napi_value ret; + NAPI_CALL(env, napi_create_array_with_length(env, array_length, &ret)); + + return ret; +} + +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor descriptors[] = { + DECLARE_NAPI_PROPERTY("TestGetElement", TestGetElement), + DECLARE_NAPI_PROPERTY("TestHasElement", TestHasElement), + DECLARE_NAPI_PROPERTY("TestDeleteElement", TestDeleteElement), + DECLARE_NAPI_PROPERTY("New", New), + DECLARE_NAPI_PROPERTY("NewWithLength", NewWithLength), + }; + + NAPI_CALL(env, napi_define_properties( + env, exports, sizeof(descriptors) / sizeof(*descriptors), descriptors)); + + return exports; +} + +NAPI_MODULE(addon, Init) diff --git a/test/addons-napi/test_async/binding.gyp b/test/addons-napi/test_async/binding.gyp new file mode 100644 index 00000000000000..cf8beb70c68e78 --- /dev/null +++ b/test/addons-napi/test_async/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "test_async", + "sources": [ "test_async.cc" ] + } + ] +} diff --git a/test/addons-napi/test_async/test-uncaught.js b/test/addons-napi/test_async/test-uncaught.js new file mode 100644 index 00000000000000..fdcb3203f54410 --- /dev/null +++ b/test/addons-napi/test_async/test-uncaught.js @@ -0,0 +1,18 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); +const test_async = require(`./build/${common.buildType}/test_async`); + +process.on('uncaughtException', common.mustCall(function(err) { + try { + throw new Error('should not fail'); + } catch (err) { + assert.strictEqual(err.message, 'should not fail'); + } + assert.strictEqual(err.message, 'uncaught'); +})); + +// Successful async execution and completion callback. +test_async.Test(5, {}, common.mustCall(function() { + throw new Error('uncaught'); +})); diff --git a/test/addons-napi/test_async/test.js b/test/addons-napi/test_async/test.js new file mode 100644 index 00000000000000..34ecae08e67f2e --- /dev/null +++ b/test/addons-napi/test_async/test.js @@ -0,0 +1,30 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); +const child_process = require('child_process'); +const test_async = require(`./build/${common.buildType}/test_async`); + +const testException = 'test_async_cb_exception'; + +// Exception thrown from async completion callback. +// (Tested in a spawned process because the exception is fatal.) +if (process.argv[2] === 'child') { + test_async.Test(1, {}, common.mustCall(function() { + throw new Error(testException); + })); + return; +} +const p = child_process.spawnSync( + process.execPath, [ __filename, 'child' ]); +assert.ifError(p.error); +assert.ok(p.stderr.toString().includes(testException)); + +// Successful async execution and completion callback. +test_async.Test(5, {}, common.mustCall(function(err, val) { + assert.strictEqual(err, null); + assert.strictEqual(val, 10); + process.nextTick(common.mustCall()); +})); + +// Async work item cancellation with callback. +test_async.TestCancel(common.mustCall()); diff --git a/test/addons-napi/test_async/test_async.cc b/test/addons-napi/test_async/test_async.cc new file mode 100644 index 00000000000000..f135affc0ce7b0 --- /dev/null +++ b/test/addons-napi/test_async/test_async.cc @@ -0,0 +1,188 @@ +#include +#include "../common.h" + +#if defined _WIN32 +#include +#else +#include +#endif + +// this needs to be greater than the thread pool size +#define MAX_CANCEL_THREADS 6 + +typedef struct { + int32_t _input; + int32_t _output; + napi_ref _callback; + napi_async_work _request; +} carrier; + +carrier the_carrier; +carrier async_carrier[MAX_CANCEL_THREADS]; + +void Execute(napi_env env, void* data) { +#if defined _WIN32 + Sleep(1000); +#else + sleep(1); +#endif + carrier* c = static_cast(data); + + if (c != &the_carrier) { + napi_throw_type_error(env, nullptr, "Wrong data parameter to Execute."); + return; + } + + c->_output = c->_input * 2; +} + +void Complete(napi_env env, napi_status status, void* data) { + carrier* c = static_cast(data); + + if (c != &the_carrier) { + napi_throw_type_error(env, nullptr, "Wrong data parameter to Complete."); + return; + } + + if (status != napi_ok) { + napi_throw_type_error(env, nullptr, "Execute callback failed."); + return; + } + + napi_value argv[2]; + + NAPI_CALL_RETURN_VOID(env, napi_get_null(env, &argv[0])); + NAPI_CALL_RETURN_VOID(env, napi_create_int32(env, c->_output, &argv[1])); + napi_value callback; + NAPI_CALL_RETURN_VOID(env, + napi_get_reference_value(env, c->_callback, &callback)); + napi_value global; + NAPI_CALL_RETURN_VOID(env, napi_get_global(env, &global)); + + napi_value result; + NAPI_CALL_RETURN_VOID(env, + napi_call_function(env, global, callback, 2, argv, &result)); + + NAPI_CALL_RETURN_VOID(env, napi_delete_reference(env, c->_callback)); + NAPI_CALL_RETURN_VOID(env, napi_delete_async_work(env, c->_request)); +} + +napi_value Test(napi_env env, napi_callback_info info) { + size_t argc = 3; + napi_value argv[3]; + napi_value _this; + napi_value resource_name; + void* data; + NAPI_CALL(env, + napi_get_cb_info(env, info, &argc, argv, &_this, &data)); + NAPI_ASSERT(env, argc >= 3, "Not enough arguments, expected 2."); + + napi_valuetype t; + NAPI_CALL(env, napi_typeof(env, argv[0], &t)); + NAPI_ASSERT(env, t == napi_number, + "Wrong first argument, integer expected."); + NAPI_CALL(env, napi_typeof(env, argv[1], &t)); + NAPI_ASSERT(env, t == napi_object, + "Wrong second argument, object expected."); + NAPI_CALL(env, napi_typeof(env, argv[2], &t)); + NAPI_ASSERT(env, t == napi_function, + "Wrong third argument, function expected."); + + the_carrier._output = 0; + + NAPI_CALL(env, + napi_get_value_int32(env, argv[0], &the_carrier._input)); + NAPI_CALL(env, + napi_create_reference(env, argv[2], 1, &the_carrier._callback)); + + NAPI_CALL(env, napi_create_string_utf8( + env, "TestResource", NAPI_AUTO_LENGTH, &resource_name)); + NAPI_CALL(env, napi_create_async_work(env, argv[1], resource_name, + Execute, Complete, &the_carrier, &the_carrier._request)); + NAPI_CALL(env, + napi_queue_async_work(env, the_carrier._request)); + + return nullptr; +} + +void BusyCancelComplete(napi_env env, napi_status status, void* data) { + carrier* c = static_cast(data); + NAPI_CALL_RETURN_VOID(env, napi_delete_async_work(env, c->_request)); +} + +void CancelComplete(napi_env env, napi_status status, void* data) { + carrier* c = static_cast(data); + + if (status == napi_cancelled) { + // ok we got the status we expected so make the callback to + // indicate the cancel succeeded. + napi_value callback; + NAPI_CALL_RETURN_VOID(env, + napi_get_reference_value(env, c->_callback, &callback)); + napi_value global; + NAPI_CALL_RETURN_VOID(env, napi_get_global(env, &global)); + napi_value result; + NAPI_CALL_RETURN_VOID(env, + napi_call_function(env, global, callback, 0, nullptr, &result)); + } + + NAPI_CALL_RETURN_VOID(env, napi_delete_async_work(env, c->_request)); + NAPI_CALL_RETURN_VOID(env, napi_delete_reference(env, c->_callback)); +} + +void CancelExecute(napi_env env, void* data) { +#if defined _WIN32 + Sleep(1000); +#else + sleep(1); +#endif +} + +napi_value TestCancel(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value argv[1]; + napi_value _this; + napi_value resource_name; + void* data; + + NAPI_CALL(env, napi_create_string_utf8( + env, "TestResource", NAPI_AUTO_LENGTH, &resource_name)); + + // make sure the work we are going to cancel will not be + // able to start by using all the threads in the pool + for (int i = 1; i < MAX_CANCEL_THREADS; i++) { + NAPI_CALL(env, napi_create_async_work(env, nullptr, resource_name, + CancelExecute, BusyCancelComplete, + &async_carrier[i], &async_carrier[i]._request)); + NAPI_CALL(env, napi_queue_async_work(env, async_carrier[i]._request)); + } + + // now queue the work we are going to cancel and then cancel it. + // cancel will fail if the work has already started, but + // we have prevented it from starting by consuming all of the + // workers above. + NAPI_CALL(env, + napi_get_cb_info(env, info, &argc, argv, &_this, &data)); + NAPI_CALL(env, napi_create_async_work(env, nullptr, resource_name, + CancelExecute, CancelComplete, + &async_carrier[0], &async_carrier[0]._request)); + NAPI_CALL(env, + napi_create_reference(env, argv[0], 1, &async_carrier[0]._callback)); + NAPI_CALL(env, napi_queue_async_work(env, async_carrier[0]._request)); + NAPI_CALL(env, napi_cancel_async_work(env, async_carrier[0]._request)); + return nullptr; +} + +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor properties[] = { + DECLARE_NAPI_PROPERTY("Test", Test), + DECLARE_NAPI_PROPERTY("TestCancel", TestCancel), + }; + + NAPI_CALL(env, napi_define_properties( + env, exports, sizeof(properties) / sizeof(*properties), properties)); + + return exports; +} + +NAPI_MODULE(addon, Init) diff --git a/test/addons-napi/test_buffer/binding.gyp b/test/addons-napi/test_buffer/binding.gyp new file mode 100644 index 00000000000000..e41a3993cd7c9d --- /dev/null +++ b/test/addons-napi/test_buffer/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "test_buffer", + "sources": [ "test_buffer.c" ] + } + ] +} diff --git a/test/addons-napi/test_buffer/test.js b/test/addons-napi/test_buffer/test.js new file mode 100644 index 00000000000000..740b0474a79c60 --- /dev/null +++ b/test/addons-napi/test_buffer/test.js @@ -0,0 +1,21 @@ +'use strict'; +// Flags: --expose-gc + +const common = require('../../common'); +const binding = require(`./build/${common.buildType}/test_buffer`); +const assert = require('assert'); + +assert.strictEqual(binding.newBuffer().toString(), binding.theText); +assert.strictEqual(binding.newExternalBuffer().toString(), binding.theText); +console.log('gc1'); +global.gc(); +assert.strictEqual(binding.getDeleterCallCount(), 1); +assert.strictEqual(binding.copyBuffer().toString(), binding.theText); + +let buffer = binding.staticBuffer(); +assert.strictEqual(binding.bufferHasInstance(buffer), true); +assert.strictEqual(binding.bufferInfo(buffer), true); +buffer = null; +global.gc(); +console.log('gc2'); +assert.strictEqual(binding.getDeleterCallCount(), 2); diff --git a/test/addons-napi/test_buffer/test_buffer.c b/test/addons-napi/test_buffer/test_buffer.c new file mode 100644 index 00000000000000..aafc617c7374bb --- /dev/null +++ b/test/addons-napi/test_buffer/test_buffer.c @@ -0,0 +1,145 @@ +#include +#include +#include +#include "../common.h" + +static const char theText[] = + "Lorem ipsum dolor sit amet, consectetur adipiscing elit."; + +static int deleterCallCount = 0; +static void deleteTheText(napi_env env, void* data, void* finalize_hint) { + NAPI_ASSERT_RETURN_VOID(env, data != NULL && strcmp(data, theText) == 0, "invalid data"); + (void)finalize_hint; + free(data); + deleterCallCount++; +} + +static void noopDeleter(napi_env env, void* data, void* finalize_hint) { + NAPI_ASSERT_RETURN_VOID(env, data != NULL && strcmp(data, theText) == 0, "invalid data"); + (void)finalize_hint; + deleterCallCount++; +} + +napi_value newBuffer(napi_env env, napi_callback_info info) { + napi_value theBuffer; + char* theCopy; + const unsigned int kBufferSize = sizeof(theText); + + NAPI_CALL(env, + napi_create_buffer( + env, + sizeof(theText), + (void**)(&theCopy), + &theBuffer)); + NAPI_ASSERT(env, theCopy, "Failed to copy static text for newBuffer"); + memcpy(theCopy, theText, kBufferSize); + + return theBuffer; +} + +napi_value newExternalBuffer(napi_env env, napi_callback_info info) { + napi_value theBuffer; + char* theCopy = strdup(theText); + NAPI_ASSERT(env, theCopy, "Failed to copy static text for newExternalBuffer"); + NAPI_CALL(env, + napi_create_external_buffer( + env, + sizeof(theText), + theCopy, + deleteTheText, + NULL, // finalize_hint + &theBuffer)); + + return theBuffer; +} + +napi_value getDeleterCallCount(napi_env env, napi_callback_info info) { + napi_value callCount; + NAPI_CALL(env, napi_create_int32(env, deleterCallCount, &callCount)); + return callCount; +} + +napi_value copyBuffer(napi_env env, napi_callback_info info) { + napi_value theBuffer; + NAPI_CALL(env, napi_create_buffer_copy( + env, sizeof(theText), theText, NULL, &theBuffer)); + return theBuffer; +} + +napi_value bufferHasInstance(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_ASSERT(env, argc == 1, "Wrong number of arguments"); + napi_value theBuffer = args[0]; + bool hasInstance; + napi_valuetype theType; + NAPI_CALL(env, napi_typeof(env, theBuffer, &theType)); + NAPI_ASSERT(env, + theType == napi_object, + "bufferHasInstance: instance is not an object"); + NAPI_CALL(env, napi_is_buffer(env, theBuffer, &hasInstance)); + NAPI_ASSERT(env, hasInstance, "bufferHasInstance: instance is not a buffer"); + napi_value returnValue; + NAPI_CALL(env, napi_get_boolean(env, hasInstance, &returnValue)); + return returnValue; +} + +napi_value bufferInfo(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_ASSERT(env, argc == 1, "Wrong number of arguments"); + napi_value theBuffer = args[0]; + char *bufferData; + napi_value returnValue; + size_t bufferLength; + NAPI_CALL(env, + napi_get_buffer_info( + env, + theBuffer, + (void**)(&bufferData), + &bufferLength)); + NAPI_CALL(env, napi_get_boolean(env, + !strcmp(bufferData, theText) && bufferLength == sizeof(theText), + &returnValue)); + return returnValue; +} + +napi_value staticBuffer(napi_env env, napi_callback_info info) { + napi_value theBuffer; + NAPI_CALL( + env, + napi_create_external_buffer(env, + sizeof(theText), + (void*)theText, + noopDeleter, + NULL, // finalize_hint + &theBuffer)); + return theBuffer; +} + +napi_value Init(napi_env env, napi_value exports) { + napi_value theValue; + + NAPI_CALL(env, + napi_create_string_utf8(env, theText, sizeof(theText), &theValue)); + NAPI_CALL(env, napi_set_named_property(env, exports, "theText", theValue)); + + napi_property_descriptor methods[] = { + DECLARE_NAPI_PROPERTY("newBuffer", newBuffer), + DECLARE_NAPI_PROPERTY("newExternalBuffer", newExternalBuffer), + DECLARE_NAPI_PROPERTY("getDeleterCallCount", getDeleterCallCount), + DECLARE_NAPI_PROPERTY("copyBuffer", copyBuffer), + DECLARE_NAPI_PROPERTY("bufferHasInstance", bufferHasInstance), + DECLARE_NAPI_PROPERTY("bufferInfo", bufferInfo), + DECLARE_NAPI_PROPERTY("staticBuffer", staticBuffer), + }; + + NAPI_CALL(env, napi_define_properties( + env, exports, sizeof(methods) / sizeof(methods[0]), methods)); + + return exports; +} + +NAPI_MODULE(addon, Init) diff --git a/test/addons-napi/test_callback_scope/binding.cc b/test/addons-napi/test_callback_scope/binding.cc new file mode 100644 index 00000000000000..e6631b6ac7bb52 --- /dev/null +++ b/test/addons-napi/test_callback_scope/binding.cc @@ -0,0 +1,138 @@ +#include "node_api.h" +#include "uv.h" +#include "../common.h" + +namespace { + +// the test needs to fake out the async structure, so we need to use +// the raw structure here and then cast as done behind the scenes +// in napi calls. +struct async_context { + double async_id; + double trigger_async_id; +}; + + +napi_value RunInCallbackScope(napi_env env, napi_callback_info info) { + size_t argc; + napi_value args[4]; + + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, nullptr, nullptr, nullptr)); + NAPI_ASSERT(env, argc == 4 , "Wrong number of arguments"); + + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)); + + napi_valuetype valuetype; + NAPI_CALL(env, napi_typeof(env, args[0], &valuetype)); + NAPI_ASSERT(env, valuetype == napi_object, + "Wrong type of arguments. Expects an object as first argument."); + + NAPI_CALL(env, napi_typeof(env, args[1], &valuetype)); + NAPI_ASSERT(env, valuetype == napi_number, + "Wrong type of arguments. Expects a number as second argument."); + + NAPI_CALL(env, napi_typeof(env, args[2], &valuetype)); + NAPI_ASSERT(env, valuetype == napi_number, + "Wrong type of arguments. Expects a number as third argument."); + + NAPI_CALL(env, napi_typeof(env, args[3], &valuetype)); + NAPI_ASSERT(env, valuetype == napi_function, + "Wrong type of arguments. Expects a function as third argument."); + + struct async_context context; + NAPI_CALL(env, napi_get_value_double(env, args[1], &context.async_id)); + NAPI_CALL(env, + napi_get_value_double(env, args[2], &context.trigger_async_id)); + + napi_callback_scope scope = nullptr; + NAPI_CALL( + env, + napi_open_callback_scope(env, + args[0], + reinterpret_cast(&context), + &scope)); + + // if the function has an exception pending after the call that is ok + // so we don't use NAPI_CALL as we must close the callback scope regardless + napi_value result = nullptr; + napi_status function_call_result = + napi_call_function(env, args[0], args[3], 0, nullptr, &result); + if (function_call_result != napi_ok) { + GET_AND_THROW_LAST_ERROR((env)); + } + + NAPI_CALL(env, napi_close_callback_scope(env, scope)); + + return result; +} + +static napi_env shared_env = nullptr; +static napi_deferred deferred = nullptr; + +static void Callback(uv_work_t* req, int ignored) { + napi_env env = shared_env; + + napi_handle_scope handle_scope = nullptr; + NAPI_CALL_RETURN_VOID(env, napi_open_handle_scope(env, &handle_scope)); + + napi_value resource_name; + NAPI_CALL_RETURN_VOID(env, napi_create_string_utf8( + env, "test", NAPI_AUTO_LENGTH, &resource_name)); + napi_async_context context; + NAPI_CALL_RETURN_VOID(env, + napi_async_init(env, nullptr, resource_name, &context)); + + napi_value resource_object; + NAPI_CALL_RETURN_VOID(env, napi_create_object(env, &resource_object)); + + napi_value undefined_value; + NAPI_CALL_RETURN_VOID(env, napi_get_undefined(env, &undefined_value)); + + napi_callback_scope scope = nullptr; + NAPI_CALL_RETURN_VOID(env, napi_open_callback_scope(env, + resource_object, + context, + &scope)); + + NAPI_CALL_RETURN_VOID(env, + napi_resolve_deferred(env, deferred, undefined_value)); + + NAPI_CALL_RETURN_VOID(env, napi_close_callback_scope(env, scope)); + + NAPI_CALL_RETURN_VOID(env, napi_close_handle_scope(env, handle_scope)); + delete req; +} + +napi_value TestResolveAsync(napi_env env, napi_callback_info info) { + napi_value promise = nullptr; + if (deferred == nullptr) { + shared_env = env; + NAPI_CALL(env, napi_create_promise(env, &deferred, &promise)); + + uv_loop_t* loop = nullptr; + NAPI_CALL(env, napi_get_uv_event_loop(env, &loop)); + + uv_work_t* req = new uv_work_t(); + uv_queue_work(loop, + req, + [](uv_work_t*) {}, + Callback); + } + return promise; +} + +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor descriptors[] = { + DECLARE_NAPI_PROPERTY("runInCallbackScope", RunInCallbackScope), + DECLARE_NAPI_PROPERTY("testResolveAsync", TestResolveAsync) + }; + + NAPI_CALL(env, napi_define_properties( + env, exports, sizeof(descriptors) / sizeof(*descriptors), descriptors)); + + return exports; +} + +} // anonymous namespace + +NAPI_MODULE(NODE_GYP_MODULE_NAME, Init) diff --git a/test/addons-napi/test_callback_scope/binding.gyp b/test/addons-napi/test_callback_scope/binding.gyp new file mode 100644 index 00000000000000..7ede63d94a0d77 --- /dev/null +++ b/test/addons-napi/test_callback_scope/binding.gyp @@ -0,0 +1,9 @@ +{ + 'targets': [ + { + 'target_name': 'binding', + 'defines': [ 'V8_DEPRECATION_WARNINGS=1' ], + 'sources': [ 'binding.cc' ] + } + ] +} diff --git a/test/addons-napi/test_callback_scope/test-resolve-async.js b/test/addons-napi/test_callback_scope/test-resolve-async.js new file mode 100644 index 00000000000000..77f25c9dde533f --- /dev/null +++ b/test/addons-napi/test_callback_scope/test-resolve-async.js @@ -0,0 +1,6 @@ +'use strict'; + +const common = require('../../common'); +const { testResolveAsync } = require(`./build/${common.buildType}/binding`); + +testResolveAsync().then(common.mustCall()); diff --git a/test/addons-napi/test_callback_scope/test.js b/test/addons-napi/test_callback_scope/test.js new file mode 100644 index 00000000000000..2f2efe5f47b98a --- /dev/null +++ b/test/addons-napi/test_callback_scope/test.js @@ -0,0 +1,17 @@ +'use strict'; + +const common = require('../../common'); +const assert = require('assert'); +const { runInCallbackScope } = require(`./build/${common.buildType}/binding`); + +assert.strictEqual(runInCallbackScope({}, 0, 0, () => 42), 42); + +{ + process.once('uncaughtException', common.mustCall((err) => { + assert.strictEqual(err.message, 'foo'); + })); + + runInCallbackScope({}, 0, 0, () => { + throw new Error('foo'); + }); +} diff --git a/test/addons-napi/test_constructor/binding.gyp b/test/addons-napi/test_constructor/binding.gyp new file mode 100644 index 00000000000000..1945a9fd5a711e --- /dev/null +++ b/test/addons-napi/test_constructor/binding.gyp @@ -0,0 +1,12 @@ +{ + "targets": [ + { + "target_name": "test_constructor", + "sources": [ "test_constructor.c" ] + }, + { + "target_name": "test_constructor_name", + "sources": [ "test_constructor_name.c" ] + } + ] +} diff --git a/test/addons-napi/test_constructor/test.js b/test/addons-napi/test_constructor/test.js new file mode 100644 index 00000000000000..616ba6c2a2927e --- /dev/null +++ b/test/addons-napi/test_constructor/test.js @@ -0,0 +1,50 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); + +// Testing api calls for a constructor that defines properties +const TestConstructor = require(`./build/${common.buildType}/test_constructor`); +const test_object = new TestConstructor(); + +assert.strictEqual(test_object.echo('hello'), 'hello'); + +test_object.readwriteValue = 1; +assert.strictEqual(test_object.readwriteValue, 1); +test_object.readwriteValue = 2; +assert.strictEqual(test_object.readwriteValue, 2); + +assert.throws(() => { test_object.readonlyValue = 3; }, + /^TypeError: Cannot assign to read only property 'readonlyValue' of object '#'$/); + +assert.ok(test_object.hiddenValue); + +// Properties with napi_enumerable attribute should be enumerable. +const propertyNames = []; +for (const name in test_object) { + propertyNames.push(name); +} +assert.ok(propertyNames.includes('echo')); +assert.ok(propertyNames.includes('readwriteValue')); +assert.ok(propertyNames.includes('readonlyValue')); +assert.ok(!propertyNames.includes('hiddenValue')); +assert.ok(!propertyNames.includes('readwriteAccessor1')); +assert.ok(!propertyNames.includes('readwriteAccessor2')); +assert.ok(!propertyNames.includes('readonlyAccessor1')); +assert.ok(!propertyNames.includes('readonlyAccessor2')); + +// The napi_writable attribute should be ignored for accessors. +test_object.readwriteAccessor1 = 1; +assert.strictEqual(test_object.readwriteAccessor1, 1); +assert.strictEqual(test_object.readonlyAccessor1, 1); +assert.throws(() => { test_object.readonlyAccessor1 = 3; }, + /^TypeError: Cannot assign to read only property 'readonlyAccessor1' of object '#'$/); +test_object.readwriteAccessor2 = 2; +assert.strictEqual(test_object.readwriteAccessor2, 2); +assert.strictEqual(test_object.readonlyAccessor2, 2); +assert.throws(() => { test_object.readonlyAccessor2 = 3; }, + /^TypeError: Cannot assign to read only property 'readonlyAccessor2' of object '#'$/); + +// validate that static properties are on the class as opposed +// to the instance +assert.strictEqual(TestConstructor.staticReadonlyAccessor1, 10); +assert.strictEqual(test_object.staticReadonlyAccessor1, undefined); diff --git a/test/addons-napi/test_constructor/test2.js b/test/addons-napi/test_constructor/test2.js new file mode 100644 index 00000000000000..64c03cbc684ac3 --- /dev/null +++ b/test/addons-napi/test_constructor/test2.js @@ -0,0 +1,8 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); + +// Testing api calls for a constructor that defines properties +const TestConstructor = + require(`./build/${common.buildType}/test_constructor_name`); +assert.strictEqual(TestConstructor.name, 'MyObject'); diff --git a/test/addons-napi/test_constructor/test_constructor.c b/test/addons-napi/test_constructor/test_constructor.c new file mode 100644 index 00000000000000..bed61cc55cd5e9 --- /dev/null +++ b/test/addons-napi/test_constructor/test_constructor.c @@ -0,0 +1,85 @@ +#include +#include "../common.h" + +static double value_ = 1; +static double static_value_ = 10; + +napi_value GetValue(napi_env env, napi_callback_info info) { + size_t argc = 0; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, NULL, NULL, NULL)); + + NAPI_ASSERT(env, argc == 0, "Wrong number of arguments"); + + napi_value number; + NAPI_CALL(env, napi_create_double(env, value_, &number)); + + return number; +} + +napi_value SetValue(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_ASSERT(env, argc == 1, "Wrong number of arguments"); + + NAPI_CALL(env, napi_get_value_double(env, args[0], &value_)); + + return NULL; +} + +napi_value Echo(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_ASSERT(env, argc == 1, "Wrong number of arguments"); + + return args[0]; +} + +napi_value New(napi_env env, napi_callback_info info) { + napi_value _this; + NAPI_CALL(env, napi_get_cb_info(env, info, NULL, NULL, &_this, NULL)); + + return _this; +} + +napi_value GetStaticValue(napi_env env, napi_callback_info info) { + size_t argc = 0; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, NULL, NULL, NULL)); + + NAPI_ASSERT(env, argc == 0, "Wrong number of arguments"); + + napi_value number; + NAPI_CALL(env, napi_create_double(env, static_value_, &number)); + + return number; +} + + +napi_value Init(napi_env env, napi_value exports) { + napi_value number; + NAPI_CALL(env, napi_create_double(env, value_, &number)); + + napi_property_descriptor properties[] = { + { "echo", 0, Echo, 0, 0, 0, napi_enumerable, 0 }, + { "readwriteValue", 0, 0, 0, 0, number, napi_enumerable | napi_writable, 0 }, + { "readonlyValue", 0, 0, 0, 0, number, napi_enumerable, 0}, + { "hiddenValue", 0, 0, 0, 0, number, napi_default, 0}, + { "readwriteAccessor1", 0, 0, GetValue, SetValue, 0, napi_default, 0}, + { "readwriteAccessor2", 0, 0, GetValue, SetValue, 0, napi_writable, 0}, + { "readonlyAccessor1", 0, 0, GetValue, NULL, 0, napi_default, 0}, + { "readonlyAccessor2", 0, 0, GetValue, NULL, 0, napi_writable, 0}, + { "staticReadonlyAccessor1", 0, 0, GetStaticValue, NULL, 0, + napi_default | napi_static, 0}, + }; + + napi_value cons; + NAPI_CALL(env, napi_define_class(env, "MyObject", NAPI_AUTO_LENGTH, New, + NULL, sizeof(properties)/sizeof(*properties), properties, &cons)); + + return cons; +} + +NAPI_MODULE(addon, Init) diff --git a/test/addons-napi/test_constructor/test_constructor_name.c b/test/addons-napi/test_constructor/test_constructor_name.c new file mode 100644 index 00000000000000..a5c89791f0f0cd --- /dev/null +++ b/test/addons-napi/test_constructor/test_constructor_name.c @@ -0,0 +1,23 @@ +#include +#include "../common.h" + +napi_ref constructor_; + +napi_value New(napi_env env, napi_callback_info info) { + napi_value _this; + NAPI_CALL(env, napi_get_cb_info(env, info, NULL, NULL, &_this, NULL)); + + return _this; +} + +napi_value Init(napi_env env, napi_value exports) { + napi_value cons; + NAPI_CALL(env, napi_define_class( + env, "MyObject_Extra", 8, New, NULL, 0, NULL, &cons)); + + NAPI_CALL(env, + napi_create_reference(env, cons, 1, &constructor_)); + return cons; +} + +NAPI_MODULE(NODE_GYP_MODULE_NAME, Init) diff --git a/test/addons-napi/test_conversions/binding.gyp b/test/addons-napi/test_conversions/binding.gyp new file mode 100644 index 00000000000000..8d8d6fc0128294 --- /dev/null +++ b/test/addons-napi/test_conversions/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "test_conversions", + "sources": [ "test_conversions.c" ] + } + ] +} diff --git a/test/addons-napi/test_conversions/test.js b/test/addons-napi/test_conversions/test.js new file mode 100644 index 00000000000000..73d2c3314f600b --- /dev/null +++ b/test/addons-napi/test_conversions/test.js @@ -0,0 +1,140 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); +const test = require(`./build/${common.buildType}/test_conversions`); + +const boolExpected = /boolean was expected/; +const numberExpected = /number was expected/; +const stringExpected = /string was expected/; + +const testSym = Symbol('test'); + +assert.strictEqual(false, test.asBool(false)); +assert.strictEqual(true, test.asBool(true)); +assert.throws(() => test.asBool(undefined), boolExpected); +assert.throws(() => test.asBool(null), boolExpected); +assert.throws(() => test.asBool(Number.NaN), boolExpected); +assert.throws(() => test.asBool(0), boolExpected); +assert.throws(() => test.asBool(''), boolExpected); +assert.throws(() => test.asBool('0'), boolExpected); +assert.throws(() => test.asBool(1), boolExpected); +assert.throws(() => test.asBool('1'), boolExpected); +assert.throws(() => test.asBool('true'), boolExpected); +assert.throws(() => test.asBool({}), boolExpected); +assert.throws(() => test.asBool([]), boolExpected); +assert.throws(() => test.asBool(testSym), boolExpected); + +[test.asInt32, test.asUInt32, test.asInt64].forEach((asInt) => { + assert.strictEqual(0, asInt(0)); + assert.strictEqual(1, asInt(1)); + assert.strictEqual(1, asInt(1.0)); + assert.strictEqual(1, asInt(1.1)); + assert.strictEqual(1, asInt(1.9)); + assert.strictEqual(0, asInt(0.9)); + assert.strictEqual(999, asInt(999.9)); + assert.strictEqual(0, asInt(Number.NaN)); + assert.throws(() => asInt(undefined), numberExpected); + assert.throws(() => asInt(null), numberExpected); + assert.throws(() => asInt(false), numberExpected); + assert.throws(() => asInt(''), numberExpected); + assert.throws(() => asInt('1'), numberExpected); + assert.throws(() => asInt({}), numberExpected); + assert.throws(() => asInt([]), numberExpected); + assert.throws(() => asInt(testSym), numberExpected); +}); + +assert.strictEqual(-1, test.asInt32(-1)); +assert.strictEqual(-1, test.asInt64(-1)); +assert.strictEqual(Math.pow(2, 32) - 1, test.asUInt32(-1)); + +assert.strictEqual(0, test.asDouble(0)); +assert.strictEqual(1, test.asDouble(1)); +assert.strictEqual(1.0, test.asDouble(1.0)); +assert.strictEqual(1.1, test.asDouble(1.1)); +assert.strictEqual(1.9, test.asDouble(1.9)); +assert.strictEqual(0.9, test.asDouble(0.9)); +assert.strictEqual(999.9, test.asDouble(999.9)); +assert.strictEqual(-1, test.asDouble(-1)); +assert.ok(Number.isNaN(test.asDouble(Number.NaN))); +assert.throws(() => test.asDouble(undefined), numberExpected); +assert.throws(() => test.asDouble(null), numberExpected); +assert.throws(() => test.asDouble(false), numberExpected); +assert.throws(() => test.asDouble(''), numberExpected); +assert.throws(() => test.asDouble('1'), numberExpected); +assert.throws(() => test.asDouble({}), numberExpected); +assert.throws(() => test.asDouble([]), numberExpected); +assert.throws(() => test.asDouble(testSym), numberExpected); + +assert.strictEqual('', test.asString('')); +assert.strictEqual('test', test.asString('test')); +assert.throws(() => test.asString(undefined), stringExpected); +assert.throws(() => test.asString(null), stringExpected); +assert.throws(() => test.asString(false), stringExpected); +assert.throws(() => test.asString(1), stringExpected); +assert.throws(() => test.asString(1.1), stringExpected); +assert.throws(() => test.asString(Number.NaN), stringExpected); +assert.throws(() => test.asString({}), stringExpected); +assert.throws(() => test.asString([]), stringExpected); +assert.throws(() => test.asString(testSym), stringExpected); + +assert.strictEqual(true, test.toBool(true)); +assert.strictEqual(true, test.toBool(1)); +assert.strictEqual(true, test.toBool(-1)); +assert.strictEqual(true, test.toBool('true')); +assert.strictEqual(true, test.toBool('false')); +assert.strictEqual(true, test.toBool({})); +assert.strictEqual(true, test.toBool([])); +assert.strictEqual(true, test.toBool(testSym)); +assert.strictEqual(false, test.toBool(false)); +assert.strictEqual(false, test.toBool(undefined)); +assert.strictEqual(false, test.toBool(null)); +assert.strictEqual(false, test.toBool(0)); +assert.strictEqual(false, test.toBool(Number.NaN)); +assert.strictEqual(false, test.toBool('')); + +assert.strictEqual(0, test.toNumber(0)); +assert.strictEqual(1, test.toNumber(1)); +assert.strictEqual(1.1, test.toNumber(1.1)); +assert.strictEqual(-1, test.toNumber(-1)); +assert.strictEqual(0, test.toNumber('0')); +assert.strictEqual(1, test.toNumber('1')); +assert.strictEqual(1.1, test.toNumber('1.1')); +assert.strictEqual(0, test.toNumber([])); +assert.strictEqual(0, test.toNumber(false)); +assert.strictEqual(0, test.toNumber(null)); +assert.strictEqual(0, test.toNumber('')); +assert.ok(Number.isNaN(test.toNumber(Number.NaN))); +assert.ok(Number.isNaN(test.toNumber({}))); +assert.ok(Number.isNaN(test.toNumber(undefined))); +assert.throws(() => test.toNumber(testSym), TypeError); + +assert.deepStrictEqual({}, test.toObject({})); +assert.deepStrictEqual({ 'test': 1 }, test.toObject({ 'test': 1 })); +assert.deepStrictEqual([], test.toObject([])); +assert.deepStrictEqual([ 1, 2, 3 ], test.toObject([ 1, 2, 3 ])); +assert.deepStrictEqual(new Boolean(false), test.toObject(false)); +assert.deepStrictEqual(new Boolean(true), test.toObject(true)); +assert.deepStrictEqual(new String(''), test.toObject('')); +assert.deepStrictEqual(new Number(0), test.toObject(0)); +assert.deepStrictEqual(new Number(Number.NaN), test.toObject(Number.NaN)); +assert.deepStrictEqual(new Object(testSym), test.toObject(testSym)); +assert.notDeepStrictEqual(false, test.toObject(false)); +assert.notDeepStrictEqual(true, test.toObject(true)); +assert.notDeepStrictEqual('', test.toObject('')); +assert.notDeepStrictEqual(0, test.toObject(0)); +assert.ok(!Number.isNaN(test.toObject(Number.NaN))); + +assert.strictEqual('', test.toString('')); +assert.strictEqual('test', test.toString('test')); +assert.strictEqual('undefined', test.toString(undefined)); +assert.strictEqual('null', test.toString(null)); +assert.strictEqual('false', test.toString(false)); +assert.strictEqual('true', test.toString(true)); +assert.strictEqual('0', test.toString(0)); +assert.strictEqual('1.1', test.toString(1.1)); +assert.strictEqual('NaN', test.toString(Number.NaN)); +assert.strictEqual('[object Object]', test.toString({})); +assert.strictEqual('test', test.toString({ toString: () => 'test' })); +assert.strictEqual('', test.toString([])); +assert.strictEqual('1,2,3', test.toString([ 1, 2, 3 ])); +assert.throws(() => test.toString(testSym), TypeError); diff --git a/test/addons-napi/test_conversions/test_conversions.c b/test/addons-napi/test_conversions/test_conversions.c new file mode 100644 index 00000000000000..9b4d170bb0b689 --- /dev/null +++ b/test/addons-napi/test_conversions/test_conversions.c @@ -0,0 +1,154 @@ +#include +#include "../common.h" + +napi_value AsBool(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)); + + bool value; + NAPI_CALL(env, napi_get_value_bool(env, args[0], &value)); + + napi_value output; + NAPI_CALL(env, napi_get_boolean(env, value, &output)); + + return output; +} + +napi_value AsInt32(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)); + + int32_t value; + NAPI_CALL(env, napi_get_value_int32(env, args[0], &value)); + + napi_value output; + NAPI_CALL(env, napi_create_int32(env, value, &output)); + + return output; +} + +napi_value AsUInt32(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)); + + uint32_t value; + NAPI_CALL(env, napi_get_value_uint32(env, args[0], &value)); + + napi_value output; + NAPI_CALL(env, napi_create_uint32(env, value, &output)); + + return output; +} + +napi_value AsInt64(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)); + + int64_t value; + NAPI_CALL(env, napi_get_value_int64(env, args[0], &value)); + + napi_value output; + NAPI_CALL(env, napi_create_int64(env, (double)value, &output)); + + return output; +} + +napi_value AsDouble(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)); + + double value; + NAPI_CALL(env, napi_get_value_double(env, args[0], &value)); + + napi_value output; + NAPI_CALL(env, napi_create_double(env, value, &output)); + + return output; +} + +napi_value AsString(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)); + + char value[100]; + NAPI_CALL(env, + napi_get_value_string_utf8(env, args[0], value, sizeof(value), NULL)); + + napi_value output; + NAPI_CALL(env, napi_create_string_utf8( + env, value, NAPI_AUTO_LENGTH, &output)); + + return output; +} + +napi_value ToBool(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 output; + NAPI_CALL(env, napi_coerce_to_bool(env, args[0], &output)); + + return output; +} + +napi_value ToNumber(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 output; + NAPI_CALL(env, napi_coerce_to_number(env, args[0], &output)); + + return output; +} + +napi_value ToObject(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 output; + NAPI_CALL(env, napi_coerce_to_object(env, args[0], &output)); + + return output; +} + +napi_value ToString(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 output; + NAPI_CALL(env, napi_coerce_to_string(env, args[0], &output)); + + return output; +} + +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor descriptors[] = { + DECLARE_NAPI_PROPERTY("asBool", AsBool), + DECLARE_NAPI_PROPERTY("asInt32", AsInt32), + DECLARE_NAPI_PROPERTY("asUInt32", AsUInt32), + DECLARE_NAPI_PROPERTY("asInt64", AsInt64), + DECLARE_NAPI_PROPERTY("asDouble", AsDouble), + DECLARE_NAPI_PROPERTY("asString", AsString), + DECLARE_NAPI_PROPERTY("toBool", ToBool), + DECLARE_NAPI_PROPERTY("toNumber", ToNumber), + DECLARE_NAPI_PROPERTY("toObject", ToObject), + DECLARE_NAPI_PROPERTY("toString", ToString), + }; + + NAPI_CALL(env, napi_define_properties( + env, exports, sizeof(descriptors) / sizeof(*descriptors), descriptors)); + + return exports; +} + +NAPI_MODULE(addon, Init) diff --git a/test/addons-napi/test_dataview/binding.gyp b/test/addons-napi/test_dataview/binding.gyp new file mode 100644 index 00000000000000..bf014dc9e7b373 --- /dev/null +++ b/test/addons-napi/test_dataview/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "test_dataview", + "sources": [ "test_dataview.c" ] + } + ] +} diff --git a/test/addons-napi/test_dataview/test.js b/test/addons-napi/test_dataview/test.js new file mode 100644 index 00000000000000..a6be58494069e5 --- /dev/null +++ b/test/addons-napi/test_dataview/test.js @@ -0,0 +1,24 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); + +// Testing api calls for arrays +const test_dataview = require(`./build/${common.buildType}/test_dataview`); + +// Test for creating dataview +{ + const buffer = new ArrayBuffer(128); + const template = Reflect.construct(DataView, [buffer]); + + const theDataview = test_dataview.CreateDataViewFromJSDataView(template); + assert.ok(theDataview instanceof DataView, + `Expect ${theDataview} to be a DataView`); +} + +// Test for creating dataview with invalid range +{ + const buffer = new ArrayBuffer(128); + assert.throws(() => { + test_dataview.CreateDataView(buffer, 10, 200); + }, RangeError); +} diff --git a/test/addons-napi/test_dataview/test_dataview.c b/test/addons-napi/test_dataview/test_dataview.c new file mode 100644 index 00000000000000..d65b30efb26a6a --- /dev/null +++ b/test/addons-napi/test_dataview/test_dataview.c @@ -0,0 +1,101 @@ +#include +#include +#include "../common.h" + +napi_value CreateDataView(napi_env env, napi_callback_info info) { + size_t argc = 3; + napi_value args [3]; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NAPI_ASSERT(env, argc == 3, "Wrong number of arguments"); + + napi_valuetype valuetype0; + napi_value arraybuffer = args[0]; + + NAPI_CALL(env, napi_typeof(env, arraybuffer, &valuetype0)); + NAPI_ASSERT(env, valuetype0 == napi_object, + "Wrong type of arguments. Expects a ArrayBuffer as the first " + "argument."); + + bool is_arraybuffer; + NAPI_CALL(env, napi_is_arraybuffer(env, arraybuffer, &is_arraybuffer)); + NAPI_ASSERT(env, is_arraybuffer, + "Wrong type of arguments. Expects a ArrayBuffer as the first " + "argument."); + + napi_valuetype valuetype1; + NAPI_CALL(env, napi_typeof(env, args[1], &valuetype1)); + + NAPI_ASSERT(env, valuetype1 == napi_number, + "Wrong type of arguments. Expects a number as second argument."); + + size_t byte_offset = 0; + NAPI_CALL(env, napi_get_value_uint32(env, args[1], (uint32_t*)(&byte_offset))); + + napi_valuetype valuetype2; + NAPI_CALL(env, napi_typeof(env, args[2], &valuetype2)); + + NAPI_ASSERT(env, valuetype2 == napi_number, + "Wrong type of arguments. Expects a number as third argument."); + + size_t length = 0; + NAPI_CALL(env, napi_get_value_uint32(env, args[2], (uint32_t*)(&length))); + + napi_value output_dataview; + NAPI_CALL(env, + napi_create_dataview(env, length, arraybuffer, + byte_offset, &output_dataview)); + + return output_dataview; +} + +napi_value CreateDataViewFromJSDataView(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_ASSERT(env, argc == 1, "Wrong number of arguments"); + + napi_valuetype valuetype; + napi_value input_dataview = args[0]; + + NAPI_CALL(env, napi_typeof(env, input_dataview, &valuetype)); + NAPI_ASSERT(env, valuetype == napi_object, + "Wrong type of arguments. Expects a DataView as the first " + "argument."); + + bool is_dataview; + NAPI_CALL(env, napi_is_dataview(env, input_dataview, &is_dataview)); + NAPI_ASSERT(env, is_dataview, + "Wrong type of arguments. Expects a DataView as the first " + "argument."); + size_t byte_offset = 0; + size_t length = 0; + napi_value buffer; + NAPI_CALL(env, + napi_get_dataview_info(env, input_dataview, &length, NULL, + &buffer, &byte_offset)); + + napi_value output_dataview; + NAPI_CALL(env, + napi_create_dataview(env, length, buffer, + byte_offset, &output_dataview)); + + + return output_dataview; +} + +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor descriptors[] = { + DECLARE_NAPI_PROPERTY("CreateDataView", CreateDataView), + DECLARE_NAPI_PROPERTY("CreateDataViewFromJSDataView", + CreateDataViewFromJSDataView) + }; + + NAPI_CALL(env, napi_define_properties( + env, exports, sizeof(descriptors) / sizeof(*descriptors), descriptors)); + + return exports; +} + +NAPI_MODULE(addon, Init) diff --git a/test/addons-napi/test_env_sharing/binding.gyp b/test/addons-napi/test_env_sharing/binding.gyp new file mode 100644 index 00000000000000..5699a8391dd347 --- /dev/null +++ b/test/addons-napi/test_env_sharing/binding.gyp @@ -0,0 +1,12 @@ +{ + "targets": [ + { + "target_name": "store_env", + "sources": [ "store_env.c" ] + }, + { + "target_name": "compare_env", + "sources": [ "compare_env.c" ] + } + ] +} diff --git a/test/addons-napi/test_env_sharing/compare_env.c b/test/addons-napi/test_env_sharing/compare_env.c new file mode 100644 index 00000000000000..e9f3e4be5c8e2d --- /dev/null +++ b/test/addons-napi/test_env_sharing/compare_env.c @@ -0,0 +1,23 @@ +#include +#include "../common.h" + +napi_value compare(napi_env env, napi_callback_info info) { + napi_value external; + size_t argc = 1; + void* data; + napi_value return_value; + + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, &external, NULL, NULL)); + NAPI_CALL(env, napi_get_value_external(env, external, &data)); + NAPI_CALL(env, napi_get_boolean(env, ((napi_env)data) == env, &return_value)); + + return return_value; +} + +napi_value Init(napi_env env, napi_value exports) { + NAPI_CALL(env, napi_create_function( + env, "exports", NAPI_AUTO_LENGTH, compare, NULL, &exports)); + return exports; +} + +NAPI_MODULE(compare_env, Init) diff --git a/test/addons-napi/test_env_sharing/store_env.c b/test/addons-napi/test_env_sharing/store_env.c new file mode 100644 index 00000000000000..c6105f348c7239 --- /dev/null +++ b/test/addons-napi/test_env_sharing/store_env.c @@ -0,0 +1,10 @@ +#include +#include "../common.h" + +napi_value Init(napi_env env, napi_value exports) { + napi_value external; + NAPI_CALL(env, napi_create_external(env, env, NULL, NULL, &external)); + return external; +} + +NAPI_MODULE(store_env, Init) diff --git a/test/addons-napi/test_env_sharing/test.js b/test/addons-napi/test_env_sharing/test.js new file mode 100644 index 00000000000000..6e21bf4c638b80 --- /dev/null +++ b/test/addons-napi/test_env_sharing/test.js @@ -0,0 +1,10 @@ +'use strict'; + +const common = require('../../common'); +const storeEnv = require(`./build/${common.buildType}/store_env`); +const compareEnv = require(`./build/${common.buildType}/compare_env`); +const assert = require('assert'); + +assert.strictEqual(compareEnv(storeEnv), true, + 'N-API environment pointers in two different modules have ' + + 'the same value'); diff --git a/test/addons-napi/test_error/binding.gyp b/test/addons-napi/test_error/binding.gyp new file mode 100644 index 00000000000000..c2defd9551a31b --- /dev/null +++ b/test/addons-napi/test_error/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "test_error", + "sources": [ "test_error.cc" ] + } + ] +} diff --git a/test/addons-napi/test_error/test.js b/test/addons-napi/test_error/test.js new file mode 100644 index 00000000000000..d5c92cb4c3cd2e --- /dev/null +++ b/test/addons-napi/test_error/test.js @@ -0,0 +1,116 @@ +'use strict'; + +const common = require('../../common'); +const test_error = require(`./build/${common.buildType}/test_error`); +const assert = require('assert'); +const theError = new Error('Some error'); +const theTypeError = new TypeError('Some type error'); +const theSyntaxError = new SyntaxError('Some syntax error'); +const theRangeError = new RangeError('Some type error'); +const theReferenceError = new ReferenceError('Some reference error'); +const theURIError = new URIError('Some URI error'); +const theEvalError = new EvalError('Some eval error'); + +class MyError extends Error { } +const myError = new MyError('Some MyError'); + +// Test that native error object is correctly classed +assert.strictEqual(test_error.checkError(theError), true); + +// Test that native type error object is correctly classed +assert.strictEqual(test_error.checkError(theTypeError), true); + +// Test that native syntax error object is correctly classed +assert.strictEqual(test_error.checkError(theSyntaxError), true); + +// Test that native range error object is correctly classed +assert.strictEqual(test_error.checkError(theRangeError), true); + +// Test that native reference error object is correctly classed +assert.strictEqual(test_error.checkError(theReferenceError), true); + +// Test that native URI error object is correctly classed +assert.strictEqual(test_error.checkError(theURIError), true); + +// Test that native eval error object is correctly classed +assert.strictEqual(test_error.checkError(theEvalError), true); + +// Test that class derived from native error is correctly classed +assert.strictEqual(test_error.checkError(myError), true); + +// Test that non-error object is correctly classed +assert.strictEqual(test_error.checkError({}), false); + +// Test that non-error primitive is correctly classed +assert.strictEqual(test_error.checkError('non-object'), false); + +assert.throws(() => { + test_error.throwExistingError(); +}, /^Error: existing error$/); + +assert.throws(() => { + test_error.throwError(); +}, /^Error: error$/); + +assert.throws(() => { + test_error.throwRangeError(); +}, /^RangeError: range error$/); + +assert.throws(() => { + test_error.throwTypeError(); +}, /^TypeError: type error$/); + +common.expectsError( + () => test_error.throwErrorCode(), + { + code: 'ERR_TEST_CODE', + message: 'Error [error]' + }); + +common.expectsError( + () => test_error.throwRangeErrorCode(), + { + code: 'ERR_TEST_CODE', + message: 'RangeError [range error]' + }); + +common.expectsError( + () => test_error.throwTypeErrorCode(), + { + code: 'ERR_TEST_CODE', + message: 'TypeError [type error]' + }); + +let error = test_error.createError(); +assert.ok(error instanceof Error, 'expected error to be an instance of Error'); +assert.strictEqual(error.message, 'error'); + +error = test_error.createRangeError(); +assert.ok(error instanceof RangeError, + 'expected error to be an instance of RangeError'); +assert.strictEqual(error.message, 'range error'); + +error = test_error.createTypeError(); +assert.ok(error instanceof TypeError, + 'expected error to be an instance of TypeError'); +assert.strictEqual(error.message, 'type error'); + +error = test_error.createErrorCode(); +assert.ok(error instanceof Error, 'expected error to be an instance of Error'); +assert.strictEqual(error.code, 'ERR_TEST_CODE'); +assert.strictEqual(error.message, 'Error [error]'); +assert.strictEqual(error.name, 'Error [ERR_TEST_CODE]'); + +error = test_error.createRangeErrorCode(); +assert.ok(error instanceof RangeError, + 'expected error to be an instance of RangeError'); +assert.strictEqual(error.message, 'RangeError [range error]'); +assert.strictEqual(error.code, 'ERR_TEST_CODE'); +assert.strictEqual(error.name, 'RangeError [ERR_TEST_CODE]'); + +error = test_error.createTypeErrorCode(); +assert.ok(error instanceof TypeError, + 'expected error to be an instance of TypeError'); +assert.strictEqual(error.message, 'TypeError [type error]'); +assert.strictEqual(error.code, 'ERR_TEST_CODE'); +assert.strictEqual(error.name, 'TypeError [ERR_TEST_CODE]'); diff --git a/test/addons-napi/test_error/test_error.cc b/test/addons-napi/test_error/test_error.cc new file mode 100644 index 00000000000000..deb571778dd7f9 --- /dev/null +++ b/test/addons-napi/test_error/test_error.cc @@ -0,0 +1,154 @@ +#include +#include "../common.h" + +napi_value checkError(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, nullptr, nullptr)); + + bool r; + NAPI_CALL(env, napi_is_error(env, args[0], &r)); + + napi_value result; + NAPI_CALL(env, napi_get_boolean(env, r, &result)); + + return result; +} + +napi_value throwExistingError(napi_env env, napi_callback_info info) { + napi_value message; + napi_value error; + NAPI_CALL(env, napi_create_string_utf8( + env, "existing error", NAPI_AUTO_LENGTH, &message)); + NAPI_CALL(env, napi_create_error(env, nullptr, message, &error)); + NAPI_CALL(env, napi_throw(env, error)); + return nullptr; +} + +napi_value throwError(napi_env env, napi_callback_info info) { + NAPI_CALL(env, napi_throw_error(env, nullptr, "error")); + return nullptr; +} + +napi_value throwRangeError(napi_env env, napi_callback_info info) { + NAPI_CALL(env, napi_throw_range_error(env, nullptr, "range error")); + return nullptr; +} + +napi_value throwTypeError(napi_env env, napi_callback_info info) { + NAPI_CALL(env, napi_throw_type_error(env, nullptr, "type error")); + return nullptr; +} + +napi_value throwErrorCode(napi_env env, napi_callback_info info) { + NAPI_CALL(env, napi_throw_error(env, "ERR_TEST_CODE", "Error [error]")); + return nullptr; +} + +napi_value throwRangeErrorCode(napi_env env, napi_callback_info info) { + NAPI_CALL(env, napi_throw_range_error(env, + "ERR_TEST_CODE", + "RangeError [range error]")); + return nullptr; +} + +napi_value throwTypeErrorCode(napi_env env, napi_callback_info info) { + NAPI_CALL(env, napi_throw_type_error(env, + "ERR_TEST_CODE", + "TypeError [type error]")); + return nullptr; +} + + +napi_value createError(napi_env env, napi_callback_info info) { + napi_value result; + napi_value message; + NAPI_CALL(env, napi_create_string_utf8( + env, "error", NAPI_AUTO_LENGTH, &message)); + NAPI_CALL(env, napi_create_error(env, nullptr, message, &result)); + return result; +} + +napi_value createRangeError(napi_env env, napi_callback_info info) { + napi_value result; + napi_value message; + NAPI_CALL(env, napi_create_string_utf8( + env, "range error", NAPI_AUTO_LENGTH, &message)); + NAPI_CALL(env, napi_create_range_error(env, nullptr, message, &result)); + return result; +} + +napi_value createTypeError(napi_env env, napi_callback_info info) { + napi_value result; + napi_value message; + NAPI_CALL(env, napi_create_string_utf8( + env, "type error", NAPI_AUTO_LENGTH, &message)); + NAPI_CALL(env, napi_create_type_error(env, nullptr, message, &result)); + return result; +} + +napi_value createErrorCode(napi_env env, napi_callback_info info) { + napi_value result; + napi_value message; + napi_value code; + NAPI_CALL(env, napi_create_string_utf8( + env, "Error [error]", NAPI_AUTO_LENGTH, &message)); + NAPI_CALL(env, napi_create_string_utf8( + env, "ERR_TEST_CODE", NAPI_AUTO_LENGTH, &code)); + NAPI_CALL(env, napi_create_error(env, code, message, &result)); + return result; +} + +napi_value createRangeErrorCode(napi_env env, napi_callback_info info) { + napi_value result; + napi_value message; + napi_value code; + NAPI_CALL(env, napi_create_string_utf8(env, + "RangeError [range error]", + NAPI_AUTO_LENGTH, + &message)); + NAPI_CALL(env, napi_create_string_utf8( + env, "ERR_TEST_CODE", NAPI_AUTO_LENGTH, &code)); + NAPI_CALL(env, napi_create_range_error(env, code, message, &result)); + return result; +} + +napi_value createTypeErrorCode(napi_env env, napi_callback_info info) { + napi_value result; + napi_value message; + napi_value code; + NAPI_CALL(env, napi_create_string_utf8(env, + "TypeError [type error]", + NAPI_AUTO_LENGTH, + &message)); + NAPI_CALL(env, napi_create_string_utf8( + env, "ERR_TEST_CODE", NAPI_AUTO_LENGTH, &code)); + NAPI_CALL(env, napi_create_type_error(env, code, message, &result)); + return result; +} + +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor descriptors[] = { + DECLARE_NAPI_PROPERTY("checkError", checkError), + DECLARE_NAPI_PROPERTY("throwExistingError", throwExistingError), + DECLARE_NAPI_PROPERTY("throwError", throwError), + DECLARE_NAPI_PROPERTY("throwRangeError", throwRangeError), + DECLARE_NAPI_PROPERTY("throwTypeError", throwTypeError), + DECLARE_NAPI_PROPERTY("throwErrorCode", throwErrorCode), + DECLARE_NAPI_PROPERTY("throwRangeErrorCode", throwRangeErrorCode), + DECLARE_NAPI_PROPERTY("throwTypeErrorCode", throwTypeErrorCode), + DECLARE_NAPI_PROPERTY("createError", createError), + DECLARE_NAPI_PROPERTY("createRangeError", createRangeError), + DECLARE_NAPI_PROPERTY("createTypeError", createTypeError), + DECLARE_NAPI_PROPERTY("createErrorCode", createErrorCode), + DECLARE_NAPI_PROPERTY("createRangeErrorCode", createRangeErrorCode), + DECLARE_NAPI_PROPERTY("createTypeErrorCode", createTypeErrorCode), + }; + + NAPI_CALL(env, napi_define_properties( + env, exports, sizeof(descriptors) / sizeof(*descriptors), descriptors)); + + return exports; +} + +NAPI_MODULE(addon, Init) diff --git a/test/addons-napi/test_exception/binding.gyp b/test/addons-napi/test_exception/binding.gyp new file mode 100644 index 00000000000000..d2e4586e46bc1e --- /dev/null +++ b/test/addons-napi/test_exception/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "test_exception", + "sources": [ "test_exception.c" ] + } + ] +} diff --git a/test/addons-napi/test_exception/test.js b/test/addons-napi/test_exception/test.js new file mode 100644 index 00000000000000..b9311add6c92d7 --- /dev/null +++ b/test/addons-napi/test_exception/test.js @@ -0,0 +1,80 @@ +'use strict'; +// Flags: --expose-gc + +const common = require('../../common'); +const assert = require('assert'); +const theError = new Error('Some error'); + +// The test module throws an error during Init, but in order for its exports to +// not be lost, it attaches them to the error's "bindings" property. This way, +// we can make sure that exceptions thrown during the module initialization +// phase are propagated through require() into JavaScript. +// https://github.com/nodejs/node/issues/19437 +const test_exception = (function() { + let resultingException; + try { + require(`./build/${common.buildType}/test_exception`); + } catch (anException) { + resultingException = anException; + } + assert.strictEqual(resultingException.message, 'Error during Init'); + return resultingException.binding; +})(); + +{ + const throwTheError = () => { throw theError; }; + + // Test that the native side successfully captures the exception + let returnedError = test_exception.returnException(throwTheError); + assert.strictEqual(theError, returnedError); + + // Test that the native side passes the exception through + assert.throws( + () => { test_exception.allowException(throwTheError); }, + (err) => err === theError + ); + + // Test that the exception thrown above was marked as pending + // before it was handled on the JS side + const exception_pending = test_exception.wasPending(); + assert.strictEqual(exception_pending, true, + 'Exception not pending as expected,' + + ` .wasPending() returned ${exception_pending}`); + + // Test that the native side does not capture a non-existing exception + returnedError = test_exception.returnException(common.mustCall()); + assert.strictEqual(returnedError, undefined, + 'Returned error should be undefined when no exception is' + + ` thrown, but ${returnedError} was passed`); +} + +{ + // Test that no exception appears that was not thrown by us + let caughtError; + try { + test_exception.allowException(common.mustCall()); + } catch (anError) { + caughtError = anError; + } + assert.strictEqual(caughtError, undefined, + 'No exception originated on the native side, but' + + ` ${caughtError} was passed`); + + // Test that the exception state remains clear when no exception is thrown + const exception_pending = test_exception.wasPending(); + assert.strictEqual(exception_pending, false, + 'Exception state did not remain clear as expected,' + + ` .wasPending() returned ${exception_pending}`); +} + +// Make sure that exceptions that occur during finalization are propagated. +function testFinalize(binding) { + let x = test_exception[binding](); + x = null; + assert.throws(() => { global.gc(); }, /Error during Finalize/); + + // To assuage the linter's concerns. + (function() {})(x); +} +testFinalize('createExternal'); +testFinalize('createExternalBuffer'); diff --git a/test/addons-napi/test_exception/test_exception.c b/test/addons-napi/test_exception/test_exception.c new file mode 100644 index 00000000000000..c83fd5ebcade19 --- /dev/null +++ b/test/addons-napi/test_exception/test_exception.c @@ -0,0 +1,93 @@ +#include +#include "../common.h" + +static bool exceptionWasPending = false; + +static napi_value returnException(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 global; + NAPI_CALL(env, napi_get_global(env, &global)); + + napi_value result; + napi_status status = napi_call_function(env, global, args[0], 0, 0, &result); + if (status == napi_pending_exception) { + napi_value ex; + NAPI_CALL(env, napi_get_and_clear_last_exception(env, &ex)); + return ex; + } + + return NULL; +} + +static napi_value allowException(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 global; + NAPI_CALL(env, napi_get_global(env, &global)); + + napi_value result; + napi_call_function(env, global, args[0], 0, 0, &result); + // Ignore status and check napi_is_exception_pending() instead. + + NAPI_CALL(env, napi_is_exception_pending(env, &exceptionWasPending)); + return NULL; +} + +static napi_value wasPending(napi_env env, napi_callback_info info) { + napi_value result; + NAPI_CALL(env, napi_get_boolean(env, exceptionWasPending, &result)); + + return result; +} + +static void finalizer(napi_env env, void *data, void *hint) { + NAPI_CALL_RETURN_VOID(env, + napi_throw_error(env, NULL, "Error during Finalize")); +} + +static napi_value createExternal(napi_env env, napi_callback_info info) { + napi_value external; + + NAPI_CALL(env, + napi_create_external(env, NULL, finalizer, NULL, &external)); + + return external; +} + +static char buffer_data[12]; + +static napi_value createExternalBuffer(napi_env env, napi_callback_info info) { + napi_value buffer; + NAPI_CALL(env, napi_create_external_buffer(env, sizeof(buffer_data), + buffer_data, finalizer, NULL, &buffer)); + return buffer; +} + +static napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor descriptors[] = { + DECLARE_NAPI_PROPERTY("returnException", returnException), + DECLARE_NAPI_PROPERTY("allowException", allowException), + DECLARE_NAPI_PROPERTY("wasPending", wasPending), + DECLARE_NAPI_PROPERTY("createExternal", createExternal), + DECLARE_NAPI_PROPERTY("createExternalBuffer", createExternalBuffer), + }; + NAPI_CALL(env, napi_define_properties( + env, exports, sizeof(descriptors) / sizeof(*descriptors), descriptors)); + + napi_value error, code, message; + NAPI_CALL(env, napi_create_string_utf8(env, "Error during Init", + NAPI_AUTO_LENGTH, &message)); + NAPI_CALL(env, napi_create_string_utf8(env, "", NAPI_AUTO_LENGTH, &code)); + NAPI_CALL(env, napi_create_error(env, code, message, &error)); + NAPI_CALL(env, napi_set_named_property(env, error, "binding", exports)); + NAPI_CALL(env, napi_throw(env, error)); + + return exports; +} + +NAPI_MODULE(addon, Init) diff --git a/test/addons-napi/test_fatal/binding.gyp b/test/addons-napi/test_fatal/binding.gyp new file mode 100644 index 00000000000000..ad661825f1fa9b --- /dev/null +++ b/test/addons-napi/test_fatal/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "test_fatal", + "sources": [ "test_fatal.c" ] + } + ] +} diff --git a/test/addons-napi/test_fatal/test.js b/test/addons-napi/test_fatal/test.js new file mode 100644 index 00000000000000..7ff9a395635dce --- /dev/null +++ b/test/addons-napi/test_fatal/test.js @@ -0,0 +1,18 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); +const child_process = require('child_process'); +const test_fatal = require(`./build/${common.buildType}/test_fatal`); + +// Test in a child process because the test code will trigger a fatal error +// that crashes the process. +if (process.argv[2] === 'child') { + test_fatal.Test(); + return; +} + +const p = child_process.spawnSync( + process.execPath, [ __filename, 'child' ]); +assert.ifError(p.error); +assert.ok(p.stderr.toString().includes( + 'FATAL ERROR: test_fatal::Test fatal message')); diff --git a/test/addons-napi/test_fatal/test2.js b/test/addons-napi/test_fatal/test2.js new file mode 100644 index 00000000000000..b9bde8f13016cc --- /dev/null +++ b/test/addons-napi/test_fatal/test2.js @@ -0,0 +1,18 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); +const child_process = require('child_process'); +const test_fatal = require(`./build/${common.buildType}/test_fatal`); + +// Test in a child process because the test code will trigger a fatal error +// that crashes the process. +if (process.argv[2] === 'child') { + test_fatal.TestStringLength(); + return; +} + +const p = child_process.spawnSync( + process.execPath, [ '--napi-modules', __filename, 'child' ]); +assert.ifError(p.error); +assert.ok(p.stderr.toString().includes( + 'FATAL ERROR: test_fatal::Test fatal message')); diff --git a/test/addons-napi/test_fatal/test_fatal.c b/test/addons-napi/test_fatal/test_fatal.c new file mode 100644 index 00000000000000..2059796d99acbd --- /dev/null +++ b/test/addons-napi/test_fatal/test_fatal.c @@ -0,0 +1,27 @@ +#include +#include "../common.h" + +napi_value Test(napi_env env, napi_callback_info info) { + napi_fatal_error("test_fatal::Test", NAPI_AUTO_LENGTH, + "fatal message", NAPI_AUTO_LENGTH); + return NULL; +} + +napi_value TestStringLength(napi_env env, napi_callback_info info) { + napi_fatal_error("test_fatal::TestStringLength", 16, "fatal message", 13); + return NULL; +} + +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor properties[] = { + DECLARE_NAPI_PROPERTY("Test", Test), + DECLARE_NAPI_PROPERTY("TestStringLength", TestStringLength), + }; + + NAPI_CALL(env, napi_define_properties( + env, exports, sizeof(properties) / sizeof(*properties), properties)); + + return exports; +} + +NAPI_MODULE(addon, Init) diff --git a/test/addons-napi/test_fatal_exception/binding.gyp b/test/addons-napi/test_fatal_exception/binding.gyp new file mode 100644 index 00000000000000..f4dc0a71ea2817 --- /dev/null +++ b/test/addons-napi/test_fatal_exception/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "test_fatal_exception", + "sources": [ "test_fatal_exception.c" ] + } + ] +} diff --git a/test/addons-napi/test_fatal_exception/test.js b/test/addons-napi/test_fatal_exception/test.js new file mode 100644 index 00000000000000..f02b9bce1e8169 --- /dev/null +++ b/test/addons-napi/test_fatal_exception/test.js @@ -0,0 +1,11 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); +const test_fatal = require(`./build/${common.buildType}/test_fatal_exception`); + +process.on('uncaughtException', common.mustCall(function(err) { + assert.strictEqual(err.message, 'fatal error'); +})); + +const err = new Error('fatal error'); +test_fatal.Test(err); diff --git a/test/addons-napi/test_fatal_exception/test_fatal_exception.c b/test/addons-napi/test_fatal_exception/test_fatal_exception.c new file mode 100644 index 00000000000000..fd81c56d856db8 --- /dev/null +++ b/test/addons-napi/test_fatal_exception/test_fatal_exception.c @@ -0,0 +1,26 @@ +#include +#include "../common.h" + +napi_value Test(napi_env env, napi_callback_info info) { + napi_value err; + size_t argc = 1; + + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, &err, NULL, NULL)); + + NAPI_CALL(env, napi_fatal_exception(env, err)); + + return NULL; +} + +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor properties[] = { + DECLARE_NAPI_PROPERTY("Test", Test), + }; + + NAPI_CALL(env, napi_define_properties( + env, exports, sizeof(properties) / sizeof(*properties), properties)); + + return exports; +} + +NAPI_MODULE(NODE_GYP_MODULE_NAME, Init) diff --git a/test/addons-napi/test_function/binding.gyp b/test/addons-napi/test_function/binding.gyp new file mode 100644 index 00000000000000..2b015bddd7d222 --- /dev/null +++ b/test/addons-napi/test_function/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "test_function", + "sources": [ "test_function.c" ] + } + ] +} diff --git a/test/addons-napi/test_function/test.js b/test/addons-napi/test_function/test.js new file mode 100644 index 00000000000000..752e9965b23039 --- /dev/null +++ b/test/addons-napi/test_function/test.js @@ -0,0 +1,31 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); + +// testing api calls for function +const test_function = require(`./build/${common.buildType}/test_function`); + + +function func1() { + return 1; +} +assert.strictEqual(test_function.TestCall(func1), 1); + +function func2() { + console.log('hello world!'); + return null; +} +assert.strictEqual(test_function.TestCall(func2), null); + +function func3(input) { + return input + 1; +} +assert.strictEqual(test_function.TestCall(func3, 1), 2); + +function func4(input) { + return func3(input); +} +assert.strictEqual(test_function.TestCall(func4, 1), 2); + +assert.strictEqual(test_function.TestName.name, 'Name'); +assert.strictEqual(test_function.TestNameShort.name, 'Name_'); diff --git a/test/addons-napi/test_function/test_function.c b/test/addons-napi/test_function/test_function.c new file mode 100644 index 00000000000000..48660ccd0e25ac --- /dev/null +++ b/test/addons-napi/test_function/test_function.c @@ -0,0 +1,53 @@ +#include +#include "../common.h" + +napi_value TestCallFunction(napi_env env, napi_callback_info info) { + size_t argc = 10; + napi_value args[10]; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NAPI_ASSERT(env, argc > 0, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NAPI_CALL(env, napi_typeof(env, args[0], &valuetype0)); + + NAPI_ASSERT(env, valuetype0 == napi_function, + "Wrong type of arguments. Expects a function as first argument."); + + napi_value* argv = args + 1; + argc = argc - 1; + + napi_value global; + NAPI_CALL(env, napi_get_global(env, &global)); + + napi_value result; + NAPI_CALL(env, napi_call_function(env, global, args[0], argc, argv, &result)); + + return result; +} + +napi_value TestFunctionName(napi_env env, napi_callback_info info) { + return NULL; +} + +napi_value Init(napi_env env, napi_value exports) { + napi_value fn1; + NAPI_CALL(env, napi_create_function( + env, NULL, NAPI_AUTO_LENGTH, TestCallFunction, NULL, &fn1)); + + napi_value fn2; + NAPI_CALL(env, napi_create_function( + env, "Name", NAPI_AUTO_LENGTH, TestFunctionName, NULL, &fn2)); + + napi_value fn3; + NAPI_CALL(env, napi_create_function( + env, "Name_extra", 5, TestFunctionName, NULL, &fn3)); + + NAPI_CALL(env, napi_set_named_property(env, exports, "TestCall", fn1)); + NAPI_CALL(env, napi_set_named_property(env, exports, "TestName", fn2)); + NAPI_CALL(env, napi_set_named_property(env, exports, "TestNameShort", fn3)); + + return exports; +} + +NAPI_MODULE(addon, Init) diff --git a/test/addons-napi/test_general/binding.gyp b/test/addons-napi/test_general/binding.gyp new file mode 100644 index 00000000000000..f8ef9f59613355 --- /dev/null +++ b/test/addons-napi/test_general/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "test_general", + "sources": [ "test_general.c" ] + } + ] +} diff --git a/test/addons-napi/test_general/test.js b/test/addons-napi/test_general/test.js new file mode 100644 index 00000000000000..4faf508d5db145 --- /dev/null +++ b/test/addons-napi/test_general/test.js @@ -0,0 +1,102 @@ +'use strict'; +// Flags: --expose-gc + +const common = require('../../common'); +const test_general = require(`./build/${common.buildType}/test_general`); +const assert = require('assert'); + +const val1 = '1'; +const val2 = 1; +const val3 = 1; + +class BaseClass { +} + +class ExtendedClass extends BaseClass { +} + +const baseObject = new BaseClass(); +const extendedObject = new ExtendedClass(); + +// test napi_strict_equals +assert.ok(test_general.testStrictEquals(val1, val1)); +assert.strictEqual(test_general.testStrictEquals(val1, val2), false); +assert.ok(test_general.testStrictEquals(val2, val3)); + +// test napi_get_prototype +assert.strictEqual(test_general.testGetPrototype(baseObject), + Object.getPrototypeOf(baseObject)); +assert.strictEqual(test_general.testGetPrototype(extendedObject), + Object.getPrototypeOf(extendedObject)); +// Prototypes for base and extended should be different. +assert.notStrictEqual(test_general.testGetPrototype(baseObject), + test_general.testGetPrototype(extendedObject)); + +// test version management functions +// expected version is currently 3 +assert.strictEqual(test_general.testGetVersion(), 3); + +const [ major, minor, patch, release ] = test_general.testGetNodeVersion(); +assert.strictEqual(process.version.split('-')[0], + `v${major}.${minor}.${patch}`); +assert.strictEqual(release, process.release.name); + +[ + 123, + 'test string', + function() {}, + new Object(), + true, + undefined, + Symbol() +].forEach((val) => { + assert.strictEqual(test_general.testNapiTypeof(val), typeof val); +}); + +// since typeof in js return object need to validate specific case +// for null +assert.strictEqual(test_general.testNapiTypeof(null), 'null'); + +// Ensure that garbage collecting an object with a wrapped native item results +// in the finalize callback being called. +let w = {}; +test_general.wrap(w); +w = null; +global.gc(); +const derefItemWasCalled = test_general.derefItemWasCalled(); +assert.strictEqual(derefItemWasCalled, true, + 'deref_item() was called upon garbage collecting a ' + + 'wrapped object. test_general.derefItemWasCalled() ' + + `returned ${derefItemWasCalled}`); + + +// Assert that wrapping twice fails. +const x = {}; +test_general.wrap(x); +common.expectsError(() => test_general.wrap(x), + { type: Error, message: 'Invalid argument' }); + +// Ensure that wrapping, removing the wrap, and then wrapping again works. +const y = {}; +test_general.wrap(y); +test_general.removeWrap(y); +// Wrapping twice succeeds if a remove_wrap() separates the instances +assert.doesNotThrow(() => test_general.wrap(y)); + +// Ensure that removing a wrap and garbage collecting does not fire the +// finalize callback. +let z = {}; +test_general.testFinalizeWrap(z); +test_general.removeWrap(z); +z = null; +global.gc(); +const finalizeWasCalled = test_general.finalizeWasCalled(); +assert.strictEqual(finalizeWasCalled, false, + 'finalize callback was not called upon garbage collection.' + + ' test_general.finalizeWasCalled() ' + + `returned ${finalizeWasCalled}`); + +// test napi_adjust_external_memory +const adjustedValue = test_general.testAdjustExternalMemory(); +assert.strictEqual(typeof adjustedValue, 'number'); +assert(adjustedValue > 0); diff --git a/test/addons-napi/test_general/testGlobals.js b/test/addons-napi/test_general/testGlobals.js new file mode 100644 index 00000000000000..38cf3f3edbccbc --- /dev/null +++ b/test/addons-napi/test_general/testGlobals.js @@ -0,0 +1,8 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); + +const test_globals = require(`./build/${common.buildType}/test_general`); + +assert.strictEqual(test_globals.getUndefined(), undefined); +assert.strictEqual(test_globals.getNull(), null); diff --git a/test/addons-napi/test_general/testInstanceOf.js b/test/addons-napi/test_general/testInstanceOf.js new file mode 100644 index 00000000000000..862155a967548f --- /dev/null +++ b/test/addons-napi/test_general/testInstanceOf.js @@ -0,0 +1,95 @@ +'use strict'; +const fs = require('fs'); + +const common = require('../../common'); +const assert = require('assert'); + +// addon is referenced through the eval expression in testFile +// eslint-disable-next-line no-unused-vars +const addon = require(`./build/${common.buildType}/test_general`); +const path = require('path'); + +// This test depends on a number of V8 tests. +const v8TestsDir = path.resolve(__dirname, '..', '..', '..', 'deps', 'v8', + 'test', 'mjsunit'); +const v8TestsDirExists = fs.existsSync(v8TestsDir); + +// The following assert functions are referenced by v8's unit tests +// See for instance deps/v8/test/mjsunit/instanceof.js +// eslint-disable-next-line no-unused-vars +function assertTrue(assertion) { + return assert.strictEqual(true, assertion); +} + +// eslint-disable-next-line no-unused-vars +function assertFalse(assertion) { + assert.strictEqual(false, assertion); +} + +// eslint-disable-next-line no-unused-vars +function assertEquals(leftHandSide, rightHandSide) { + assert.strictEqual(leftHandSide, rightHandSide); +} + +// eslint-disable-next-line no-unused-vars +function assertThrows(statement) { + assert.throws(function() { + eval(statement); + }, Error); +} + +function testFile(fileName) { + try { + const contents = fs.readFileSync(fileName, { encoding: 'utf8' }); + eval(contents.replace(/[(]([^\s(]+)\s+instanceof\s+([^)]+)[)]/g, + '(addon.doInstanceOf($1, $2))')); + } catch (err) { + // This test depends on V8 test files, which may not exist in downloaded + // archives. Emit a warning if the tests cannot be found instead of failing. + if (err.code === 'ENOENT' && !v8TestsDirExists) + process.emitWarning(`test file ${fileName} does not exist.`); + else + throw err; + } +} + +testFile(path.join(v8TestsDir, 'instanceof.js')); +testFile(path.join(v8TestsDir, 'instanceof-2.js')); + +// We can only perform this test if we have a working Symbol.hasInstance +if (typeof Symbol !== 'undefined' && 'hasInstance' in Symbol && + typeof Symbol.hasInstance === 'symbol') { + + function compareToNative(theObject, theConstructor) { + assert.strictEqual(addon.doInstanceOf(theObject, theConstructor), + (theObject instanceof theConstructor)); + } + + function MyClass() {} + Object.defineProperty(MyClass, Symbol.hasInstance, { + value: function(candidate) { + return 'mark' in candidate; + } + }); + + function MySubClass() {} + MySubClass.prototype = new MyClass(); + + let x = new MySubClass(); + let y = new MySubClass(); + x.mark = true; + + compareToNative(x, MySubClass); + compareToNative(y, MySubClass); + compareToNative(x, MyClass); + compareToNative(y, MyClass); + + x = new MyClass(); + y = new MyClass(); + x.mark = true; + + compareToNative(x, MySubClass); + compareToNative(y, MySubClass); + compareToNative(x, MyClass); + compareToNative(y, MyClass); +} diff --git a/test/addons-napi/test_general/testNapiRun.js b/test/addons-napi/test_general/testNapiRun.js new file mode 100644 index 00000000000000..af9f89fa29d53d --- /dev/null +++ b/test/addons-napi/test_general/testNapiRun.js @@ -0,0 +1,15 @@ +'use strict'; + +const common = require('../../common'); +const assert = require('assert'); + +// addon is referenced through the eval expression in testFile +// eslint-disable-next-line no-unused-vars +const addon = require(`./build/${common.buildType}/test_general`); + +const testCase = '(41.92 + 0.08);'; +const expected = 42; +const actual = addon.testNapiRun(testCase); + +assert.strictEqual(actual, expected); +assert.throws(() => addon.testNapiRun({ abc: 'def' }), /string was expected/); diff --git a/test/addons-napi/test_general/testNapiStatus.js b/test/addons-napi/test_general/testNapiStatus.js new file mode 100644 index 00000000000000..a588862098f68f --- /dev/null +++ b/test/addons-napi/test_general/testNapiStatus.js @@ -0,0 +1,8 @@ +'use strict'; + +const common = require('../../common'); +const addon = require(`./build/${common.buildType}/test_general`); +const assert = require('assert'); + +addon.createNapiError(); +assert(addon.testNapiErrorCleanup(), 'napi_status cleaned up for second call'); diff --git a/test/addons-napi/test_general/test_general.c b/test/addons-napi/test_general/test_general.c new file mode 100644 index 00000000000000..66d2a57c2fd7ea --- /dev/null +++ b/test/addons-napi/test_general/test_general.c @@ -0,0 +1,262 @@ +#include +#include +#include "../common.h" + +napi_value testStrictEquals(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value args[2]; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + bool bool_result; + napi_value result; + NAPI_CALL(env, napi_strict_equals(env, args[0], args[1], &bool_result)); + NAPI_CALL(env, napi_get_boolean(env, bool_result, &result)); + + return result; +} + +napi_value testGetPrototype(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 result; + NAPI_CALL(env, napi_get_prototype(env, args[0], &result)); + + return result; +} + +napi_value testGetVersion(napi_env env, napi_callback_info info) { + uint32_t version; + napi_value result; + NAPI_CALL(env, napi_get_version(env, &version)); + NAPI_CALL(env, napi_create_uint32(env, version, &result)); + return result; +} + +napi_value testGetNodeVersion(napi_env env, napi_callback_info info) { + const napi_node_version* node_version; + napi_value result, major, minor, patch, release; + NAPI_CALL(env, napi_get_node_version(env, &node_version)); + NAPI_CALL(env, napi_create_uint32(env, node_version->major, &major)); + NAPI_CALL(env, napi_create_uint32(env, node_version->minor, &minor)); + NAPI_CALL(env, napi_create_uint32(env, node_version->patch, &patch)); + NAPI_CALL(env, napi_create_string_utf8(env, + node_version->release, + NAPI_AUTO_LENGTH, + &release)); + NAPI_CALL(env, napi_create_array_with_length(env, 4, &result)); + NAPI_CALL(env, napi_set_element(env, result, 0, major)); + NAPI_CALL(env, napi_set_element(env, result, 1, minor)); + NAPI_CALL(env, napi_set_element(env, result, 2, patch)); + NAPI_CALL(env, napi_set_element(env, result, 3, release)); + return result; +} + +napi_value doInstanceOf(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value args[2]; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + bool instanceof; + NAPI_CALL(env, napi_instanceof(env, args[0], args[1], &instanceof)); + + napi_value result; + NAPI_CALL(env, napi_get_boolean(env, instanceof, &result)); + + return result; +} + +napi_value getNull(napi_env env, napi_callback_info info) { + napi_value result; + NAPI_CALL(env, napi_get_null(env, &result)); + return result; +} + +napi_value getUndefined(napi_env env, napi_callback_info info) { + napi_value result; + NAPI_CALL(env, napi_get_undefined(env, &result)); + return result; +} + +napi_value createNapiError(napi_env env, napi_callback_info info) { + napi_value value; + NAPI_CALL(env, napi_create_string_utf8(env, "xyz", 3, &value)); + + double double_value; + napi_status status = napi_get_value_double(env, value, &double_value); + + NAPI_ASSERT(env, status != napi_ok, "Failed to produce error condition"); + + const napi_extended_error_info *error_info = 0; + NAPI_CALL(env, napi_get_last_error_info(env, &error_info)); + + NAPI_ASSERT(env, error_info->error_code == status, + "Last error info code should match last status"); + NAPI_ASSERT(env, error_info->error_message, + "Last error info message should not be null"); + + return NULL; +} + +napi_value testNapiErrorCleanup(napi_env env, napi_callback_info info) { + const napi_extended_error_info *error_info = 0; + NAPI_CALL(env, napi_get_last_error_info(env, &error_info)); + + napi_value result; + bool is_ok = error_info->error_code == napi_ok; + NAPI_CALL(env, napi_get_boolean(env, is_ok, &result)); + + return result; +} + +napi_value testNapiTypeof(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_valuetype argument_type; + NAPI_CALL(env, napi_typeof(env, args[0], &argument_type)); + + napi_value result = NULL; + if (argument_type == napi_number) { + NAPI_CALL(env, napi_create_string_utf8( + env, "number", NAPI_AUTO_LENGTH, &result)); + } else if (argument_type == napi_string) { + NAPI_CALL(env, napi_create_string_utf8( + env, "string", NAPI_AUTO_LENGTH, &result)); + } else if (argument_type == napi_function) { + NAPI_CALL(env, napi_create_string_utf8( + env, "function", NAPI_AUTO_LENGTH, &result)); + } else if (argument_type == napi_object) { + NAPI_CALL(env, napi_create_string_utf8( + env, "object", NAPI_AUTO_LENGTH, &result)); + } else if (argument_type == napi_boolean) { + NAPI_CALL(env, napi_create_string_utf8( + env, "boolean", NAPI_AUTO_LENGTH, &result)); + } else if (argument_type == napi_undefined) { + NAPI_CALL(env, napi_create_string_utf8( + env, "undefined", NAPI_AUTO_LENGTH, &result)); + } else if (argument_type == napi_symbol) { + NAPI_CALL(env, napi_create_string_utf8( + env, "symbol", NAPI_AUTO_LENGTH, &result)); + } else if (argument_type == napi_null) { + NAPI_CALL(env, napi_create_string_utf8( + env, "null", NAPI_AUTO_LENGTH, &result)); + } + return result; +} + +static bool deref_item_called = false; +static void deref_item(napi_env env, void* data, void* hint) { + (void) hint; + + NAPI_ASSERT_RETURN_VOID(env, data == &deref_item_called, + "Finalize callback was called with the correct pointer"); + + deref_item_called = true; +} + +napi_value deref_item_was_called(napi_env env, napi_callback_info info) { + napi_value it_was_called; + + NAPI_CALL(env, napi_get_boolean(env, deref_item_called, &it_was_called)); + + return it_was_called; +} + +napi_value wrap(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value to_wrap; + + deref_item_called = false; + + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, &to_wrap, NULL, NULL)); + NAPI_CALL(env, napi_wrap(env, to_wrap, &deref_item_called, deref_item, NULL, NULL)); + + return NULL; +} + +napi_value remove_wrap(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value wrapped; + void* data; + + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, &wrapped, NULL, NULL)); + NAPI_CALL(env, napi_remove_wrap(env, wrapped, &data)); + + return NULL; +} + +static bool finalize_called = false; +static void test_finalize(napi_env env, void* data, void* hint) { + finalize_called = true; +} + +napi_value test_finalize_wrap(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value js_object; + + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, &js_object, NULL, NULL)); + NAPI_CALL(env, napi_wrap(env, js_object, NULL, test_finalize, NULL, NULL)); + + return NULL; +} + +napi_value finalize_was_called(napi_env env, napi_callback_info info) { + napi_value it_was_called; + + NAPI_CALL(env, napi_get_boolean(env, finalize_called, &it_was_called)); + + return it_was_called; +} + +napi_value testAdjustExternalMemory(napi_env env, napi_callback_info info) { + napi_value result; + int64_t adjustedValue; + + NAPI_CALL(env, napi_adjust_external_memory(env, 1, &adjustedValue)); + NAPI_CALL(env, napi_create_double(env, adjustedValue, &result)); + + return result; +} + +napi_value testNapiRun(napi_env env, napi_callback_info info) { + napi_value script, result; + size_t argc = 1; + + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, &script, NULL, NULL)); + + NAPI_CALL(env, napi_run_script(env, script, &result)); + + return result; +} + +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor descriptors[] = { + DECLARE_NAPI_PROPERTY("testStrictEquals", testStrictEquals), + DECLARE_NAPI_PROPERTY("testGetPrototype", testGetPrototype), + DECLARE_NAPI_PROPERTY("testGetVersion", testGetVersion), + DECLARE_NAPI_PROPERTY("testGetNodeVersion", testGetNodeVersion), + DECLARE_NAPI_PROPERTY("testNapiRun", testNapiRun), + DECLARE_NAPI_PROPERTY("doInstanceOf", doInstanceOf), + DECLARE_NAPI_PROPERTY("getUndefined", getUndefined), + DECLARE_NAPI_PROPERTY("getNull", getNull), + DECLARE_NAPI_PROPERTY("createNapiError", createNapiError), + DECLARE_NAPI_PROPERTY("testNapiErrorCleanup", testNapiErrorCleanup), + DECLARE_NAPI_PROPERTY("testNapiTypeof", testNapiTypeof), + DECLARE_NAPI_PROPERTY("wrap", wrap), + DECLARE_NAPI_PROPERTY("removeWrap", remove_wrap), + DECLARE_NAPI_PROPERTY("testFinalizeWrap", test_finalize_wrap), + DECLARE_NAPI_PROPERTY("finalizeWasCalled", finalize_was_called), + DECLARE_NAPI_PROPERTY("derefItemWasCalled", deref_item_was_called), + DECLARE_NAPI_PROPERTY("testAdjustExternalMemory", testAdjustExternalMemory) + }; + + NAPI_CALL(env, napi_define_properties( + env, exports, sizeof(descriptors) / sizeof(*descriptors), descriptors)); + + return exports; +} + +NAPI_MODULE(addon, Init) diff --git a/test/addons-napi/test_handle_scope/binding.gyp b/test/addons-napi/test_handle_scope/binding.gyp new file mode 100644 index 00000000000000..daa96681a52018 --- /dev/null +++ b/test/addons-napi/test_handle_scope/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "test_handle_scope", + "sources": [ "test_handle_scope.c" ] + } + ] +} diff --git a/test/addons-napi/test_handle_scope/test.js b/test/addons-napi/test_handle_scope/test.js new file mode 100644 index 00000000000000..53abfe178c8d7d --- /dev/null +++ b/test/addons-napi/test_handle_scope/test.js @@ -0,0 +1,19 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); + +// testing handle scope api calls +const testHandleScope = + require(`./build/${common.buildType}/test_handle_scope`); + +testHandleScope.NewScope(); + +assert.ok(testHandleScope.NewScopeEscape() instanceof Object); + +testHandleScope.NewScopeEscapeTwice(); + +assert.throws( + () => { + testHandleScope.NewScopeWithException(() => { throw new RangeError(); }); + }, + RangeError); diff --git a/test/addons-napi/test_handle_scope/test_handle_scope.c b/test/addons-napi/test_handle_scope/test_handle_scope.c new file mode 100644 index 00000000000000..102abb2be612f0 --- /dev/null +++ b/test/addons-napi/test_handle_scope/test_handle_scope.c @@ -0,0 +1,85 @@ +#include +#include "../common.h" +#include + +// these tests validate the handle scope functions in the normal +// flow. Forcing gc behaviour to fully validate they are doing +// the right right thing would be quite hard so we keep it +// simple for now. + +napi_value NewScope(napi_env env, napi_callback_info info) { + napi_handle_scope scope; + napi_value output = NULL; + + NAPI_CALL(env, napi_open_handle_scope(env, &scope)); + NAPI_CALL(env, napi_create_object(env, &output)); + NAPI_CALL(env, napi_close_handle_scope(env, scope)); + return NULL; +} + +napi_value NewScopeEscape(napi_env env, napi_callback_info info) { + napi_escapable_handle_scope scope; + napi_value output = NULL; + napi_value escapee = NULL; + + NAPI_CALL(env, napi_open_escapable_handle_scope(env, &scope)); + NAPI_CALL(env, napi_create_object(env, &output)); + NAPI_CALL(env, napi_escape_handle(env, scope, output, &escapee)); + NAPI_CALL(env, napi_close_escapable_handle_scope(env, scope)); + return escapee; +} + +napi_value NewScopeEscapeTwice(napi_env env, napi_callback_info info) { + napi_escapable_handle_scope scope; + napi_value output = NULL; + napi_value escapee = NULL; + napi_status status; + + NAPI_CALL(env, napi_open_escapable_handle_scope(env, &scope)); + NAPI_CALL(env, napi_create_object(env, &output)); + NAPI_CALL(env, napi_escape_handle(env, scope, output, &escapee)); + status = napi_escape_handle(env, scope, output, &escapee); + NAPI_ASSERT(env, status == napi_escape_called_twice, "Escaping twice fails"); + NAPI_CALL(env, napi_close_escapable_handle_scope(env, scope)); + return NULL; +} + +napi_value NewScopeWithException(napi_env env, napi_callback_info info) { + napi_handle_scope scope; + size_t argc; + napi_value exception_function; + napi_status status; + napi_value output = NULL; + + NAPI_CALL(env, napi_open_handle_scope(env, &scope)); + NAPI_CALL(env, napi_create_object(env, &output)); + + argc = 1; + NAPI_CALL(env, napi_get_cb_info( + env, info, &argc, &exception_function, NULL, NULL)); + + status = napi_call_function( + env, output, exception_function, 0, NULL, NULL); + NAPI_ASSERT(env, status == napi_pending_exception, + "Function should have thrown."); + + // Closing a handle scope should still work while an exception is pending. + NAPI_CALL(env, napi_close_handle_scope(env, scope)); + return NULL; +} + +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor properties[] = { + DECLARE_NAPI_PROPERTY("NewScope", NewScope), + DECLARE_NAPI_PROPERTY("NewScopeEscape", NewScopeEscape), + DECLARE_NAPI_PROPERTY("NewScopeEscapeTwice", NewScopeEscapeTwice), + DECLARE_NAPI_PROPERTY("NewScopeWithException", NewScopeWithException), + }; + + NAPI_CALL(env, napi_define_properties( + env, exports, sizeof(properties) / sizeof(*properties), properties)); + + return exports; +} + +NAPI_MODULE(addon, Init) diff --git a/test/addons-napi/test_make_callback/binding.c b/test/addons-napi/test_make_callback/binding.c new file mode 100644 index 00000000000000..f893319987cb89 --- /dev/null +++ b/test/addons-napi/test_make_callback/binding.c @@ -0,0 +1,56 @@ +#include +#include "../common.h" + +#define MAX_ARGUMENTS 10 + +static +napi_value MakeCallback(napi_env env, napi_callback_info info) { + size_t argc = MAX_ARGUMENTS; + size_t n; + napi_value args[MAX_ARGUMENTS]; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NAPI_ASSERT(env, argc > 0, "Wrong number of arguments"); + + napi_value recv = args[0]; + napi_value func = args[1]; + + napi_value argv[MAX_ARGUMENTS - 2]; + for (n = 2; n < argc; n += 1) { + argv[n - 2] = args[n]; + } + + napi_valuetype func_type; + + NAPI_CALL(env, napi_typeof(env, func, &func_type)); + + napi_value resource_name; + NAPI_CALL(env, napi_create_string_utf8( + env, "test", NAPI_AUTO_LENGTH, &resource_name)); + + napi_async_context context; + NAPI_CALL(env, napi_async_init(env, func, resource_name, &context)); + + napi_value result; + if (func_type == napi_function) { + NAPI_CALL(env, napi_make_callback( + env, context, recv, func, argc - 2, argv, &result)); + } else { + NAPI_ASSERT(env, false, "Unexpected argument type"); + } + + NAPI_CALL(env, napi_async_destroy(env, context)); + + return result; +} + +static +napi_value Init(napi_env env, napi_value exports) { + napi_value fn; + NAPI_CALL(env, napi_create_function( + env, NULL, NAPI_AUTO_LENGTH, MakeCallback, NULL, &fn)); + NAPI_CALL(env, napi_set_named_property(env, exports, "makeCallback", fn)); + return exports; +} + +NAPI_MODULE(binding, Init) diff --git a/test/addons-napi/test_make_callback/binding.gyp b/test/addons-napi/test_make_callback/binding.gyp new file mode 100644 index 00000000000000..23daf507916ff6 --- /dev/null +++ b/test/addons-napi/test_make_callback/binding.gyp @@ -0,0 +1,9 @@ +{ + 'targets': [ + { + 'target_name': 'binding', + 'defines': [ 'V8_DEPRECATION_WARNINGS=1' ], + 'sources': [ 'binding.c' ] + } + ] +} diff --git a/test/addons-napi/test_make_callback/test.js b/test/addons-napi/test_make_callback/test.js new file mode 100644 index 00000000000000..56e2b3f4e2b6c6 --- /dev/null +++ b/test/addons-napi/test_make_callback/test.js @@ -0,0 +1,82 @@ +'use strict'; + +const common = require('../../common'); +const assert = require('assert'); +const vm = require('vm'); +const binding = require(`./build/${common.buildType}/binding`); +const makeCallback = binding.makeCallback; + +function myMultiArgFunc(arg1, arg2, arg3) { + assert.strictEqual(arg1, 1); + assert.strictEqual(arg2, 2); + assert.strictEqual(arg3, 3); + return 42; +} + +assert.strictEqual(42, makeCallback(process, common.mustCall(function() { + assert.strictEqual(0, arguments.length); + assert.strictEqual(this, process); + return 42; +}))); + +assert.strictEqual(42, makeCallback(process, common.mustCall(function(x) { + assert.strictEqual(1, arguments.length); + assert.strictEqual(this, process); + assert.strictEqual(x, 1337); + return 42; +}), 1337)); + +assert.strictEqual(42, + makeCallback(this, + common.mustCall(myMultiArgFunc), 1, 2, 3)); + +// TODO(node-api): napi_make_callback needs to support +// strings passed for the func argument +/* +const recv = { + one: common.mustCall(function() { + assert.strictEqual(0, arguments.length); + assert.strictEqual(this, recv); + return 42; + }), + two: common.mustCall(function(x) { + assert.strictEqual(1, arguments.length); + assert.strictEqual(this, recv); + assert.strictEqual(x, 1337); + return 42; + }), +}; + +assert.strictEqual(42, makeCallback(recv, 'one')); +assert.strictEqual(42, makeCallback(recv, 'two', 1337)); + +// Check that callbacks on a receiver from a different context works. +const foreignObject = vm.runInNewContext('({ fortytwo() { return 42; } })'); +assert.strictEqual(42, makeCallback(foreignObject, 'fortytwo')); +*/ + +// Check that the callback is made in the context of the receiver. +const target = vm.runInNewContext(` + (function($Object) { + if (Object === $Object) + throw new Error('bad'); + return Object; + }) +`); +assert.notStrictEqual(Object, makeCallback(process, target, Object)); + +// Runs in inner context. +const forward = vm.runInNewContext(` + (function(forward) { + return forward(Object); + }) +`); + +// Runs in outer context. +function endpoint($Object) { + if (Object === $Object) + throw new Error('bad'); + return Object; +} + +assert.strictEqual(Object, makeCallback(process, forward, endpoint)); diff --git a/test/addons-napi/test_make_callback_recurse/binding.cc b/test/addons-napi/test_make_callback_recurse/binding.cc new file mode 100644 index 00000000000000..8a32b88b9dec91 --- /dev/null +++ b/test/addons-napi/test_make_callback_recurse/binding.cc @@ -0,0 +1,44 @@ +#include +#include "../common.h" +#include + +namespace { + +napi_value MakeCallback(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value args[2]; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + napi_value recv = args[0]; + napi_value func = args[1]; + + napi_status status = napi_make_callback(env, nullptr /* async_context */, + recv, func, 0 /* argc */, nullptr /* argv */, nullptr /* result */); + + bool isExceptionPending; + NAPI_CALL(env, napi_is_exception_pending(env, &isExceptionPending)); + if (isExceptionPending && !(status == napi_pending_exception)) { + // if there is an exception pending we don't expect any + // other error + napi_value pending_error; + status = napi_get_and_clear_last_exception(env, &pending_error); + NAPI_CALL(env, + napi_throw_error((env), + nullptr, + "error when only pending exception expected")); + } + + return recv; +} + +napi_value Init(napi_env env, napi_value exports) { + napi_value fn; + NAPI_CALL(env, napi_create_function( + env, NULL, NAPI_AUTO_LENGTH, MakeCallback, NULL, &fn)); + NAPI_CALL(env, napi_set_named_property(env, exports, "makeCallback", fn)); + return exports; +} + +} // namespace + +NAPI_MODULE(binding, Init) diff --git a/test/addons-napi/test_make_callback_recurse/binding.gyp b/test/addons-napi/test_make_callback_recurse/binding.gyp new file mode 100644 index 00000000000000..7ede63d94a0d77 --- /dev/null +++ b/test/addons-napi/test_make_callback_recurse/binding.gyp @@ -0,0 +1,9 @@ +{ + 'targets': [ + { + 'target_name': 'binding', + 'defines': [ 'V8_DEPRECATION_WARNINGS=1' ], + 'sources': [ 'binding.cc' ] + } + ] +} diff --git a/test/addons-napi/test_make_callback_recurse/test.js b/test/addons-napi/test_make_callback_recurse/test.js new file mode 100644 index 00000000000000..895769bc33029a --- /dev/null +++ b/test/addons-napi/test_make_callback_recurse/test.js @@ -0,0 +1,151 @@ +'use strict'; + +const common = require('../../common'); +const assert = require('assert'); +const domain = require('domain'); +const binding = require(`./build/${common.buildType}/binding`); +const makeCallback = binding.makeCallback; + +// Make sure this is run in the future. +const mustCallCheckDomains = common.mustCall(checkDomains); + + +// Make sure that using MakeCallback allows the error to propagate. +assert.throws(function() { + makeCallback({}, function() { + throw new Error('hi from domain error'); + }); +}, /^Error: hi from domain error$/); + + +// Check the execution order of the nextTickQueue and MicrotaskQueue in +// relation to running multiple MakeCallback's from bootstrap, +// node::MakeCallback() and node::AsyncWrap::MakeCallback(). +// TODO(trevnorris): Is there a way to verify this is being run during +// bootstrap? +(function verifyExecutionOrder(arg) { + const results = []; + + // Processing of the MicrotaskQueue is manually handled by node. They are not + // processed until after the nextTickQueue has been processed. + Promise.resolve(1).then(common.mustCall(function() { + results.push(7); + })); + + // The nextTick should run after all immediately invoked calls. + process.nextTick(common.mustCall(function() { + results.push(3); + + // Run same test again but while processing the nextTickQueue to make sure + // the following MakeCallback call breaks in the middle of processing the + // queue and allows the script to run normally. + process.nextTick(common.mustCall(function() { + results.push(6); + })); + + makeCallback({}, common.mustCall(function() { + results.push(4); + })); + + results.push(5); + })); + + results.push(0); + + // MakeCallback is calling the function immediately, but should then detect + // that a script is already in the middle of execution and return before + // either the nextTickQueue or MicrotaskQueue are processed. + makeCallback({}, common.mustCall(function() { + results.push(1); + })); + + // This should run before either the nextTickQueue or MicrotaskQueue are + // processed. Previously MakeCallback would not detect this circumstance + // and process them immediately. + results.push(2); + + setImmediate(common.mustCall(function() { + for (let i = 0; i < results.length; i++) { + assert.strictEqual(results[i], i, + `verifyExecutionOrder(${arg}) results: ${results}`); + } + if (arg === 1) { + // The tests are first run on bootstrap during LoadEnvironment() in + // src/node.cc. Now run the tests through node::MakeCallback(). + setImmediate(function() { + makeCallback({}, common.mustCall(function() { + verifyExecutionOrder(2); + })); + }); + } else if (arg === 2) { + // setTimeout runs via the TimerWrap, which runs through + // AsyncWrap::MakeCallback(). Make sure there are no conflicts using + // node::MakeCallback() within it. + setTimeout(common.mustCall(function() { + verifyExecutionOrder(3); + }), 10); + } else if (arg === 3) { + mustCallCheckDomains(); + } else { + throw new Error('UNREACHABLE'); + } + })); +}(1)); + + +function checkDomains() { + // Check that domains are properly entered/exited when called in multiple + // levels from both node::MakeCallback() and AsyncWrap::MakeCallback + setImmediate(common.mustCall(function() { + const d1 = domain.create(); + const d2 = domain.create(); + const d3 = domain.create(); + + makeCallback({domain: d1}, common.mustCall(function() { + assert.strictEqual(d1, process.domain); + makeCallback({domain: d2}, common.mustCall(function() { + assert.strictEqual(d2, process.domain); + makeCallback({domain: d3}, common.mustCall(function() { + assert.strictEqual(d3, process.domain); + })); + assert.strictEqual(d2, process.domain); + })); + assert.strictEqual(d1, process.domain); + })); + })); + + setTimeout(common.mustCall(function() { + const d1 = domain.create(); + const d2 = domain.create(); + const d3 = domain.create(); + + makeCallback({domain: d1}, common.mustCall(function() { + assert.strictEqual(d1, process.domain); + makeCallback({domain: d2}, common.mustCall(function() { + assert.strictEqual(d2, process.domain); + makeCallback({domain: d3}, common.mustCall(function() { + assert.strictEqual(d3, process.domain); + })); + assert.strictEqual(d2, process.domain); + })); + assert.strictEqual(d1, process.domain); + })); + }), 1); + + function testTimer(id) { + // Make sure nextTick, setImmediate and setTimeout can all recover properly + // after a thrown makeCallback call. + const d = domain.create(); + d.on('error', common.mustCall(function(e) { + assert.strictEqual(e.message, `throw from domain ${id}`); + })); + makeCallback({domain: d}, function() { + throw new Error(`throw from domain ${id}`); + }); + throw new Error('UNREACHABLE'); + } + + process.nextTick(common.mustCall(testTimer), 3); + setImmediate(common.mustCall(testTimer), 2); + setTimeout(common.mustCall(testTimer), 1, 1); +} diff --git a/test/addons-napi/test_new_target/binding.c b/test/addons-napi/test_new_target/binding.c new file mode 100644 index 00000000000000..a74d4bb2f877be --- /dev/null +++ b/test/addons-napi/test_new_target/binding.c @@ -0,0 +1,69 @@ +#include +#include "../common.h" + +napi_value BaseClass(napi_env env, napi_callback_info info) { + napi_value newTargetArg; + NAPI_CALL(env, napi_get_new_target(env, info, &newTargetArg)); + napi_value thisArg; + NAPI_CALL(env, napi_get_cb_info(env, info, NULL, NULL, &thisArg, NULL)); + napi_value undefined; + NAPI_CALL(env, napi_get_undefined(env, &undefined)); + + // this !== new.target since we are being invoked through super() + bool result; + NAPI_CALL(env, napi_strict_equals(env, newTargetArg, thisArg, &result)); + NAPI_ASSERT(env, !result, "this !== new.target"); + + // new.target !== undefined because we should be called as a new expression + NAPI_ASSERT(env, newTargetArg != NULL, "newTargetArg != NULL"); + NAPI_CALL(env, napi_strict_equals(env, newTargetArg, undefined, &result)); + NAPI_ASSERT(env, !result, "new.target !== undefined"); + + return thisArg; +} + +napi_value Constructor(napi_env env, napi_callback_info info) { + bool result; + napi_value newTargetArg; + NAPI_CALL(env, napi_get_new_target(env, info, &newTargetArg)); + size_t argc = 1; + napi_value argv; + napi_value thisArg; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, &argv, &thisArg, NULL)); + napi_value undefined; + NAPI_CALL(env, napi_get_undefined(env, &undefined)); + + // new.target !== undefined because we should be called as a new expression + NAPI_ASSERT(env, newTargetArg != NULL, "newTargetArg != NULL"); + NAPI_CALL(env, napi_strict_equals(env, newTargetArg, undefined, &result)); + NAPI_ASSERT(env, !result, "new.target !== undefined"); + + // arguments[0] should be Constructor itself (test harness passed it) + NAPI_CALL(env, napi_strict_equals(env, newTargetArg, argv, &result)); + NAPI_ASSERT(env, result, "new.target === Constructor"); + + return thisArg; +} + +napi_value OrdinaryFunction(napi_env env, napi_callback_info info) { + napi_value newTargetArg; + NAPI_CALL(env, napi_get_new_target(env, info, &newTargetArg)); + + NAPI_ASSERT(env, newTargetArg == NULL, "newTargetArg == NULL"); + + napi_value _true; + NAPI_CALL(env, napi_get_boolean(env, true, &_true)); + return _true; +} + +napi_value Init(napi_env env, napi_value exports) { + const napi_property_descriptor desc[] = { + DECLARE_NAPI_PROPERTY("BaseClass", BaseClass), + DECLARE_NAPI_PROPERTY("OrdinaryFunction", OrdinaryFunction), + DECLARE_NAPI_PROPERTY("Constructor", Constructor) + }; + NAPI_CALL(env, napi_define_properties(env, exports, 3, desc)); + return exports; +} + +NAPI_MODULE(NODE_GYP_MODULE_NAME, Init) diff --git a/test/addons-napi/test_new_target/binding.gyp b/test/addons-napi/test_new_target/binding.gyp new file mode 100644 index 00000000000000..23daf507916ff6 --- /dev/null +++ b/test/addons-napi/test_new_target/binding.gyp @@ -0,0 +1,9 @@ +{ + 'targets': [ + { + 'target_name': 'binding', + 'defines': [ 'V8_DEPRECATION_WARNINGS=1' ], + 'sources': [ 'binding.c' ] + } + ] +} diff --git a/test/addons-napi/test_new_target/test.js b/test/addons-napi/test_new_target/test.js new file mode 100644 index 00000000000000..702e8ca8b4387d --- /dev/null +++ b/test/addons-napi/test_new_target/test.js @@ -0,0 +1,21 @@ +'use strict'; + +const common = require('../../common'); +const assert = require('assert'); +const binding = require(`./build/${common.buildType}/binding`); + +class Class extends binding.BaseClass { + constructor() { + super(); + this.method(); + } + method() { + this.ok = true; + } +} + +assert.ok(new Class() instanceof binding.BaseClass); +assert.ok(new Class().ok); +assert.ok(binding.OrdinaryFunction()); +assert.ok( + new binding.Constructor(binding.Constructor) instanceof binding.Constructor); diff --git a/test/addons-napi/test_number/binding.gyp b/test/addons-napi/test_number/binding.gyp new file mode 100644 index 00000000000000..c934d5ef03bfc7 --- /dev/null +++ b/test/addons-napi/test_number/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "test_number", + "sources": [ "test_number.c" ] + } + ] +} diff --git a/test/addons-napi/test_number/test.js b/test/addons-napi/test_number/test.js new file mode 100644 index 00000000000000..3dbbb64932e502 --- /dev/null +++ b/test/addons-napi/test_number/test.js @@ -0,0 +1,117 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); +const test_number = require(`./build/${common.buildType}/test_number`); + + +// testing api calls for number +function testNumber(num) { + assert.strictEqual(num, test_number.Test(num)); +} + +testNumber(0); +testNumber(-0); +testNumber(1); +testNumber(-1); +testNumber(100); +testNumber(2121); +testNumber(-1233); +testNumber(986583); +testNumber(-976675); + +testNumber( + 98765432213456789876546896323445679887645323232436587988766545658); +testNumber( + -4350987086545760976737453646576078997096876957864353245245769809); +testNumber(Number.MIN_SAFE_INTEGER); +testNumber(Number.MAX_SAFE_INTEGER); +testNumber(Number.MAX_SAFE_INTEGER + 10); + +testNumber(Number.MIN_VALUE); +testNumber(Number.MAX_VALUE); +testNumber(Number.MAX_VALUE + 10); + +testNumber(Number.POSITIVE_INFINITY); +testNumber(Number.NEGATIVE_INFINITY); +assert(Object.is(NaN, test_number.Test(NaN))); + +// validate documented behavior when value is retrieved as 32-bit integer with +// `napi_get_value_int32` +function testInt32(input, expected = input) { + assert.strictEqual(expected, test_number.TestInt32Truncation(input)); +} + +// Test zero +testInt32(0.0, 0); +testInt32(-0.0, 0); + +// Test min/max int32 range +testInt32(-Math.pow(2, 31)); +testInt32(Math.pow(2, 31) - 1); + +// Test overflow scenarios +testInt32(4294967297, 1); +testInt32(4294967296, 0); +testInt32(4294967295, -1); +testInt32(4294967296 * 5 + 3, 3); + +// Test min/max safe integer range +testInt32(Number.MIN_SAFE_INTEGER, 1); +testInt32(Number.MAX_SAFE_INTEGER, -1); + +// Test within int64_t range (with precision loss) +testInt32(-Math.pow(2, 63) + (Math.pow(2, 9) + 1), 1024); +testInt32(Math.pow(2, 63) - (Math.pow(2, 9) + 1), -1024); + +// Test min/max double value +testInt32(-Number.MIN_VALUE, 0); +testInt32(Number.MIN_VALUE, 0); +testInt32(-Number.MAX_VALUE, 0); +testInt32(Number.MAX_VALUE, 0); + +// Test outside int64_t range +testInt32(-Math.pow(2, 63) + (Math.pow(2, 9)), 0); +testInt32(Math.pow(2, 63) - (Math.pow(2, 9)), 0); + +// Test non-finite numbers +testInt32(Number.POSITIVE_INFINITY, 0); +testInt32(Number.NEGATIVE_INFINITY, 0); +testInt32(Number.NaN, 0); + +// validate documented behavior when value is retrieved as 64-bit integer with +// `napi_get_value_int64` +function testInt64(input, expected = input) { + assert.strictEqual(expected, test_number.TestInt64Truncation(input)); +} + +// Both V8 and ChakraCore return a sentinel value of `0x8000000000000000` when +// the conversion goes out of range, but V8 treats it as unsigned in some cases. +const RANGEERROR_POSITIVE = Math.pow(2, 63); +const RANGEERROR_NEGATIVE = -Math.pow(2, 63); + +// Test zero +testInt64(0.0, 0); +testInt64(-0.0, 0); + +// Test min/max safe integer range +testInt64(Number.MIN_SAFE_INTEGER); +testInt64(Number.MAX_SAFE_INTEGER); + +// Test within int64_t range (with precision loss) +testInt64(-Math.pow(2, 63) + (Math.pow(2, 9) + 1)); +testInt64(Math.pow(2, 63) - (Math.pow(2, 9) + 1)); + +// Test min/max double value +testInt64(-Number.MIN_VALUE, 0); +testInt64(Number.MIN_VALUE, 0); +testInt64(-Number.MAX_VALUE, RANGEERROR_NEGATIVE); +testInt64(Number.MAX_VALUE, RANGEERROR_POSITIVE); + +// Test outside int64_t range +testInt64(-Math.pow(2, 63) + (Math.pow(2, 9)), RANGEERROR_NEGATIVE); +testInt64(Math.pow(2, 63) - (Math.pow(2, 9)), RANGEERROR_POSITIVE); + +// Test non-finite numbers +testInt64(Number.POSITIVE_INFINITY, 0); +testInt64(Number.NEGATIVE_INFINITY, 0); +testInt64(Number.NaN, 0); diff --git a/test/addons-napi/test_number/test_number.c b/test/addons-napi/test_number/test_number.c new file mode 100644 index 00000000000000..35223dc3d878da --- /dev/null +++ b/test/addons-napi/test_number/test_number.c @@ -0,0 +1,83 @@ +#include +#include "../common.h" + +napi_value Test(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_ASSERT(env, argc >= 1, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NAPI_CALL(env, napi_typeof(env, args[0], &valuetype0)); + + NAPI_ASSERT(env, valuetype0 == napi_number, + "Wrong type of arguments. Expects a number as first argument."); + + double input; + NAPI_CALL(env, napi_get_value_double(env, args[0], &input)); + + napi_value output; + NAPI_CALL(env, napi_create_double(env, input, &output)); + + return output; +} + +napi_value TestInt32Truncation(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_ASSERT(env, argc >= 1, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NAPI_CALL(env, napi_typeof(env, args[0], &valuetype0)); + + NAPI_ASSERT(env, valuetype0 == napi_number, + "Wrong type of arguments. Expects a number as first argument."); + + int32_t input; + NAPI_CALL(env, napi_get_value_int32(env, args[0], &input)); + + napi_value output; + NAPI_CALL(env, napi_create_int32(env, input, &output)); + + return output; +} + +napi_value TestInt64Truncation(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_ASSERT(env, argc >= 1, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NAPI_CALL(env, napi_typeof(env, args[0], &valuetype0)); + + NAPI_ASSERT(env, valuetype0 == napi_number, + "Wrong type of arguments. Expects a number as first argument."); + + int64_t input; + NAPI_CALL(env, napi_get_value_int64(env, args[0], &input)); + + napi_value output; + NAPI_CALL(env, napi_create_int64(env, input, &output)); + + return output; +} + +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor descriptors[] = { + DECLARE_NAPI_PROPERTY("Test", Test), + DECLARE_NAPI_PROPERTY("TestInt32Truncation", TestInt32Truncation), + DECLARE_NAPI_PROPERTY("TestInt64Truncation", TestInt64Truncation), + }; + + NAPI_CALL(env, napi_define_properties( + env, exports, sizeof(descriptors) / sizeof(*descriptors), descriptors)); + + return exports; +} + +NAPI_MODULE(addon, Init) diff --git a/test/addons-napi/test_object/binding.gyp b/test/addons-napi/test_object/binding.gyp new file mode 100644 index 00000000000000..be225ace779027 --- /dev/null +++ b/test/addons-napi/test_object/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "test_object", + "sources": [ "test_object.c" ] + } + ] +} diff --git a/test/addons-napi/test_object/test.js b/test/addons-napi/test_object/test.js new file mode 100644 index 00000000000000..9e8f17de28287c --- /dev/null +++ b/test/addons-napi/test_object/test.js @@ -0,0 +1,200 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); + +// Testing api calls for objects +const test_object = require(`./build/${common.buildType}/test_object`); + + +const object = { + hello: 'world', + array: [ + 1, 94, 'str', 12.321, { test: 'obj in arr' } + ], + newObject: { + test: 'obj in obj' + } +}; + +assert.strictEqual(test_object.Get(object, 'hello'), 'world'); +assert.deepStrictEqual(test_object.Get(object, 'array'), + [ 1, 94, 'str', 12.321, { test: 'obj in arr' } ]); +assert.deepStrictEqual(test_object.Get(object, 'newObject'), + { test: 'obj in obj' }); + +assert(test_object.Has(object, 'hello')); +assert(test_object.Has(object, 'array')); +assert(test_object.Has(object, 'newObject')); + +const newObject = test_object.New(); +assert(test_object.Has(newObject, 'test_number')); +assert.strictEqual(newObject.test_number, 987654321); +assert.strictEqual(newObject.test_string, 'test string'); + +{ + // Verify that napi_get_property() walks the prototype chain. + function MyObject() { + this.foo = 42; + this.bar = 43; + } + + MyObject.prototype.bar = 44; + MyObject.prototype.baz = 45; + + const obj = new MyObject(); + + assert.strictEqual(test_object.Get(obj, 'foo'), 42); + assert.strictEqual(test_object.Get(obj, 'bar'), 43); + assert.strictEqual(test_object.Get(obj, 'baz'), 45); + assert.strictEqual(test_object.Get(obj, 'toString'), + Object.prototype.toString); +} + +{ + // Verify that napi_has_own_property() fails if property is not a name. + [true, false, null, undefined, {}, [], 0, 1, () => {}].forEach((value) => { + assert.throws(() => { + test_object.HasOwn({}, value); + }, /^Error: A string or symbol was expected$/); + }); +} + +{ + // Verify that napi_has_own_property() does not walk the prototype chain. + const symbol1 = Symbol(); + const symbol2 = Symbol(); + + function MyObject() { + this.foo = 42; + this.bar = 43; + this[symbol1] = 44; + } + + MyObject.prototype.bar = 45; + MyObject.prototype.baz = 46; + MyObject.prototype[symbol2] = 47; + + const obj = new MyObject(); + + assert.strictEqual(test_object.HasOwn(obj, 'foo'), true); + assert.strictEqual(test_object.HasOwn(obj, 'bar'), true); + assert.strictEqual(test_object.HasOwn(obj, symbol1), true); + assert.strictEqual(test_object.HasOwn(obj, 'baz'), false); + assert.strictEqual(test_object.HasOwn(obj, 'toString'), false); + assert.strictEqual(test_object.HasOwn(obj, symbol2), false); +} + +{ + // test_object.Inflate increases all properties by 1 + const cube = { + x: 10, + y: 10, + z: 10 + }; + + assert.deepStrictEqual(test_object.Inflate(cube), {x: 11, y: 11, z: 11}); + assert.deepStrictEqual(test_object.Inflate(cube), {x: 12, y: 12, z: 12}); + assert.deepStrictEqual(test_object.Inflate(cube), {x: 13, y: 13, z: 13}); + cube.t = 13; + assert.deepStrictEqual( + test_object.Inflate(cube), {x: 14, y: 14, z: 14, t: 14}); + + const sym1 = Symbol('1'); + const sym2 = Symbol('2'); + const sym3 = Symbol('3'); + const sym4 = Symbol('4'); + const object2 = { + [sym1]: '@@iterator', + [sym2]: sym3 + }; + + assert(test_object.Has(object2, sym1)); + assert(test_object.Has(object2, sym2)); + assert.strictEqual(test_object.Get(object2, sym1), '@@iterator'); + assert.strictEqual(test_object.Get(object2, sym2), sym3); + assert(test_object.Set(object2, 'string', 'value')); + assert(test_object.Set(object2, sym4, 123)); + assert(test_object.Has(object2, 'string')); + assert(test_object.Has(object2, sym4)); + assert.strictEqual(test_object.Get(object2, 'string'), 'value'); + assert.strictEqual(test_object.Get(object2, sym4), 123); +} + +{ + // Wrap a pointer in a JS object, then verify the pointer can be unwrapped. + const wrapper = {}; + test_object.Wrap(wrapper); + + assert(test_object.Unwrap(wrapper)); +} + +{ + // Verify that wrapping doesn't break an object's prototype chain. + const wrapper = {}; + const protoA = { protoA: true }; + Object.setPrototypeOf(wrapper, protoA); + test_object.Wrap(wrapper); + + assert(test_object.Unwrap(wrapper)); + assert(wrapper.protoA); +} + +{ + // Verify the pointer can be unwrapped after inserting in the prototype chain. + const wrapper = {}; + const protoA = { protoA: true }; + Object.setPrototypeOf(wrapper, protoA); + test_object.Wrap(wrapper); + + const protoB = { protoB: true }; + Object.setPrototypeOf(protoB, Object.getPrototypeOf(wrapper)); + Object.setPrototypeOf(wrapper, protoB); + + assert(test_object.Unwrap(wrapper)); + assert(wrapper.protoA, true); + assert(wrapper.protoB, true); +} + +{ + // Verify that normal and nonexistent properties can be deleted. + const sym = Symbol(); + const obj = { foo: 'bar', [sym]: 'baz' }; + + assert.strictEqual('foo' in obj, true); + assert.strictEqual(sym in obj, true); + assert.strictEqual('does_not_exist' in obj, false); + assert.strictEqual(test_object.Delete(obj, 'foo'), true); + assert.strictEqual('foo' in obj, false); + assert.strictEqual(sym in obj, true); + assert.strictEqual('does_not_exist' in obj, false); + assert.strictEqual(test_object.Delete(obj, sym), true); + assert.strictEqual('foo' in obj, false); + assert.strictEqual(sym in obj, false); + assert.strictEqual('does_not_exist' in obj, false); +} + +{ + // Verify that non-configurable properties are not deleted. + const obj = {}; + + Object.defineProperty(obj, 'foo', { configurable: false }); + assert.strictEqual(test_object.Delete(obj, 'foo'), false); + assert.strictEqual('foo' in obj, true); +} + +{ + // Verify that prototype properties are not deleted. + function Foo() { + this.foo = 'bar'; + } + + Foo.prototype.foo = 'baz'; + + const obj = new Foo(); + + assert.strictEqual(obj.foo, 'bar'); + assert.strictEqual(test_object.Delete(obj, 'foo'), true); + assert.strictEqual(obj.foo, 'baz'); + assert.strictEqual(test_object.Delete(obj, 'foo'), true); + assert.strictEqual(obj.foo, 'baz'); +} diff --git a/test/addons-napi/test_object/test_object.c b/test/addons-napi/test_object/test_object.c new file mode 100644 index 00000000000000..a84da27365ff2e --- /dev/null +++ b/test/addons-napi/test_object/test_object.c @@ -0,0 +1,238 @@ +#include +#include "../common.h" +#include + +static int test_value = 3; + +napi_value Get(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value args[2]; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NAPI_ASSERT(env, argc >= 2, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NAPI_CALL(env, napi_typeof(env, args[0], &valuetype0)); + + NAPI_ASSERT(env, valuetype0 == napi_object, + "Wrong type of arguments. Expects an object as first argument."); + + napi_valuetype valuetype1; + NAPI_CALL(env, napi_typeof(env, args[1], &valuetype1)); + + NAPI_ASSERT(env, valuetype1 == napi_string || valuetype1 == napi_symbol, + "Wrong type of arguments. Expects a string or symbol as second."); + + napi_value object = args[0]; + napi_value output; + NAPI_CALL(env, napi_get_property(env, object, args[1], &output)); + + return output; +} + +napi_value Set(napi_env env, napi_callback_info info) { + size_t argc = 3; + napi_value args[3]; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NAPI_ASSERT(env, argc >= 3, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NAPI_CALL(env, napi_typeof(env, args[0], &valuetype0)); + + NAPI_ASSERT(env, valuetype0 == napi_object, + "Wrong type of arguments. Expects an object as first argument."); + + napi_valuetype valuetype1; + NAPI_CALL(env, napi_typeof(env, args[1], &valuetype1)); + + NAPI_ASSERT(env, valuetype1 == napi_string || valuetype1 == napi_symbol, + "Wrong type of arguments. Expects a string or symbol as second."); + + NAPI_CALL(env, napi_set_property(env, args[0], args[1], args[2])); + + napi_value valuetrue; + NAPI_CALL(env, napi_get_boolean(env, true, &valuetrue)); + + return valuetrue; +} + +napi_value Has(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value args[2]; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NAPI_ASSERT(env, argc >= 2, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NAPI_CALL(env, napi_typeof(env, args[0], &valuetype0)); + + NAPI_ASSERT(env, valuetype0 == napi_object, + "Wrong type of arguments. Expects an object as first argument."); + + napi_valuetype valuetype1; + NAPI_CALL(env, napi_typeof(env, args[1], &valuetype1)); + + NAPI_ASSERT(env, valuetype1 == napi_string || valuetype1 == napi_symbol, + "Wrong type of arguments. Expects a string or symbol as second."); + + bool has_property; + NAPI_CALL(env, napi_has_property(env, args[0], args[1], &has_property)); + + napi_value ret; + NAPI_CALL(env, napi_get_boolean(env, has_property, &ret)); + + return ret; +} + +napi_value HasOwn(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value args[2]; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NAPI_ASSERT(env, argc == 2, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NAPI_CALL(env, napi_typeof(env, args[0], &valuetype0)); + + NAPI_ASSERT(env, valuetype0 == napi_object, + "Wrong type of arguments. Expects an object as first argument."); + + // napi_valuetype valuetype1; + // NAPI_CALL(env, napi_typeof(env, args[1], &valuetype1)); + // + // NAPI_ASSERT(env, valuetype1 == napi_string || valuetype1 == napi_symbol, + // "Wrong type of arguments. Expects a string or symbol as second."); + + bool has_property; + NAPI_CALL(env, napi_has_own_property(env, args[0], args[1], &has_property)); + + napi_value ret; + NAPI_CALL(env, napi_get_boolean(env, has_property, &ret)); + + return ret; +} + +napi_value Delete(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value args[2]; + + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + NAPI_ASSERT(env, argc == 2, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NAPI_CALL(env, napi_typeof(env, args[0], &valuetype0)); + NAPI_ASSERT(env, valuetype0 == napi_object, + "Wrong type of arguments. Expects an object as first argument."); + + napi_valuetype valuetype1; + NAPI_CALL(env, napi_typeof(env, args[1], &valuetype1)); + NAPI_ASSERT(env, valuetype1 == napi_string || valuetype1 == napi_symbol, + "Wrong type of arguments. Expects a string or symbol as second."); + + bool result; + napi_value ret; + NAPI_CALL(env, napi_delete_property(env, args[0], args[1], &result)); + NAPI_CALL(env, napi_get_boolean(env, result, &ret)); + + return ret; +} + +napi_value New(napi_env env, napi_callback_info info) { + napi_value ret; + NAPI_CALL(env, napi_create_object(env, &ret)); + + napi_value num; + NAPI_CALL(env, napi_create_int32(env, 987654321, &num)); + + NAPI_CALL(env, napi_set_named_property(env, ret, "test_number", num)); + + napi_value str; + const char* str_val = "test string"; + size_t str_len = strlen(str_val); + NAPI_CALL(env, napi_create_string_utf8(env, str_val, str_len, &str)); + + NAPI_CALL(env, napi_set_named_property(env, ret, "test_string", str)); + + return ret; +} + +napi_value Inflate(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_ASSERT(env, argc >= 1, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NAPI_CALL(env, napi_typeof(env, args[0], &valuetype0)); + + NAPI_ASSERT(env, valuetype0 == napi_object, + "Wrong type of arguments. Expects an object as first argument."); + + napi_value obj = args[0]; + napi_value propertynames; + NAPI_CALL(env, napi_get_property_names(env, obj, &propertynames)); + + uint32_t i, length; + NAPI_CALL(env, napi_get_array_length(env, propertynames, &length)); + + for (i = 0; i < length; i++) { + napi_value property_str; + NAPI_CALL(env, napi_get_element(env, propertynames, i, &property_str)); + + napi_value value; + NAPI_CALL(env, napi_get_property(env, obj, property_str, &value)); + + double double_val; + NAPI_CALL(env, napi_get_value_double(env, value, &double_val)); + NAPI_CALL(env, napi_create_double(env, double_val + 1, &value)); + NAPI_CALL(env, napi_set_property(env, obj, property_str, value)); + } + + return obj; +} + +napi_value Wrap(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value arg; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, &arg, NULL, NULL)); + + NAPI_CALL(env, napi_wrap(env, arg, &test_value, NULL, NULL, NULL)); + return NULL; +} + +napi_value Unwrap(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value arg; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, &arg, NULL, NULL)); + + void* data; + NAPI_CALL(env, napi_unwrap(env, arg, &data)); + + bool is_expected = (data != NULL && *(int*)data == 3); + napi_value result; + NAPI_CALL(env, napi_get_boolean(env, is_expected, &result)); + return result; +} + +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor descriptors[] = { + DECLARE_NAPI_PROPERTY("Get", Get), + DECLARE_NAPI_PROPERTY("Set", Set), + DECLARE_NAPI_PROPERTY("Has", Has), + DECLARE_NAPI_PROPERTY("HasOwn", HasOwn), + DECLARE_NAPI_PROPERTY("Delete", Delete), + DECLARE_NAPI_PROPERTY("New", New), + DECLARE_NAPI_PROPERTY("Inflate", Inflate), + DECLARE_NAPI_PROPERTY("Wrap", Wrap), + DECLARE_NAPI_PROPERTY("Unwrap", Unwrap), + }; + + NAPI_CALL(env, napi_define_properties( + env, exports, sizeof(descriptors) / sizeof(*descriptors), descriptors)); + + return exports; +} + +NAPI_MODULE(addon, Init) diff --git a/test/addons-napi/test_promise/binding.gyp b/test/addons-napi/test_promise/binding.gyp new file mode 100644 index 00000000000000..bf266f93db74be --- /dev/null +++ b/test/addons-napi/test_promise/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "test_promise", + "sources": [ "test_promise.c" ] + } + ] +} diff --git a/test/addons-napi/test_promise/test.js b/test/addons-napi/test_promise/test.js new file mode 100644 index 00000000000000..477ceb75969d51 --- /dev/null +++ b/test/addons-napi/test_promise/test.js @@ -0,0 +1,63 @@ +'use strict'; + +const common = require('../../common'); + +// This tests the promise-related n-api calls + +const assert = require('assert'); +const test_promise = require(`./build/${common.buildType}/test_promise`); + +common.crashOnUnhandledRejection(); + +// A resolution +{ + const expected_result = 42; + const promise = test_promise.createPromise(); + promise.then( + common.mustCall(function(result) { + assert.strictEqual(result, expected_result); + }), + common.mustNotCall()); + test_promise.concludeCurrentPromise(expected_result, true); +} + +// A rejection +{ + const expected_result = 'It\'s not you, it\'s me.'; + const promise = test_promise.createPromise(); + promise.then( + common.mustNotCall(), + common.mustCall(function(result) { + assert.strictEqual(result, expected_result); + })); + test_promise.concludeCurrentPromise(expected_result, false); +} + +// Chaining +{ + const expected_result = 'chained answer'; + const promise = test_promise.createPromise(); + promise.then( + common.mustCall(function(result) { + assert.strictEqual(result, expected_result); + }), + common.mustNotCall()); + test_promise.concludeCurrentPromise(Promise.resolve('chained answer'), true); +} + +const promiseTypeTestPromise = test_promise.createPromise(); +assert.strictEqual(test_promise.isPromise(promiseTypeTestPromise), true); +test_promise.concludeCurrentPromise(undefined, true); + +const rejectPromise = Promise.reject(-1); +const expected_reason = -1; +assert.strictEqual(test_promise.isPromise(rejectPromise), true); +rejectPromise.catch((reason) => { + assert.strictEqual(reason, expected_reason); +}); + +assert.strictEqual(test_promise.isPromise(2.4), false); +assert.strictEqual(test_promise.isPromise('I promise!'), false); +assert.strictEqual(test_promise.isPromise(undefined), false); +assert.strictEqual(test_promise.isPromise(null), false); +assert.strictEqual(test_promise.isPromise({}), false); diff --git a/test/addons-napi/test_promise/test_promise.c b/test/addons-napi/test_promise/test_promise.c new file mode 100644 index 00000000000000..0ec3e0c3d84379 --- /dev/null +++ b/test/addons-napi/test_promise/test_promise.c @@ -0,0 +1,62 @@ +#include +#include "../common.h" + +napi_deferred deferred = NULL; + +napi_value createPromise(napi_env env, napi_callback_info info) { + napi_value promise; + + // We do not overwrite an existing deferred. + if (deferred != NULL) { + return NULL; + } + + NAPI_CALL(env, napi_create_promise(env, &deferred, &promise)); + + return promise; +} + +napi_value concludeCurrentPromise(napi_env env, napi_callback_info info) { + napi_value argv[2]; + size_t argc = 2; + bool resolution; + + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, NULL, NULL)); + NAPI_CALL(env, napi_get_value_bool(env, argv[1], &resolution)); + if (resolution) { + NAPI_CALL(env, napi_resolve_deferred(env, deferred, argv[0])); + } else { + NAPI_CALL(env, napi_reject_deferred(env, deferred, argv[0])); + } + + deferred = NULL; + + return NULL; +} + +napi_value isPromise(napi_env env, napi_callback_info info) { + napi_value promise, result; + size_t argc = 1; + bool is_promise; + + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, &promise, NULL, NULL)); + NAPI_CALL(env, napi_is_promise(env, promise, &is_promise)); + NAPI_CALL(env, napi_get_boolean(env, is_promise, &result)); + + return result; +} + +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor descriptors[] = { + DECLARE_NAPI_PROPERTY("createPromise", createPromise), + DECLARE_NAPI_PROPERTY("concludeCurrentPromise", concludeCurrentPromise), + DECLARE_NAPI_PROPERTY("isPromise", isPromise), + }; + + NAPI_CALL(env, napi_define_properties( + env, exports, sizeof(descriptors) / sizeof(*descriptors), descriptors)); + + return exports; +} + +NAPI_MODULE(addon, Init) diff --git a/test/addons-napi/test_properties/binding.gyp b/test/addons-napi/test_properties/binding.gyp new file mode 100644 index 00000000000000..345e5c88d7c6b9 --- /dev/null +++ b/test/addons-napi/test_properties/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "test_properties", + "sources": [ "test_properties.c" ] + } + ] +} diff --git a/test/addons-napi/test_properties/test.js b/test/addons-napi/test_properties/test.js new file mode 100644 index 00000000000000..0a3bbee853610f --- /dev/null +++ b/test/addons-napi/test_properties/test.js @@ -0,0 +1,56 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); +const readonlyErrorRE = /^TypeError: Cannot assign to read only property '.*' of object '#'$/; + +// Testing api calls for defining properties +const test_object = require(`./build/${common.buildType}/test_properties`); + +assert.strictEqual(test_object.echo('hello'), 'hello'); + +test_object.readwriteValue = 1; +assert.strictEqual(test_object.readwriteValue, 1); +test_object.readwriteValue = 2; +assert.strictEqual(test_object.readwriteValue, 2); + +assert.throws(() => { test_object.readonlyValue = 3; }, readonlyErrorRE); + +assert.ok(test_object.hiddenValue); + +// Properties with napi_enumerable attribute should be enumerable. +const propertyNames = []; +for (const name in test_object) { + propertyNames.push(name); +} +assert.ok(propertyNames.includes('echo')); +assert.ok(propertyNames.includes('readwriteValue')); +assert.ok(propertyNames.includes('readonlyValue')); +assert.ok(!propertyNames.includes('hiddenValue')); +assert.ok(propertyNames.includes('NameKeyValue')); +assert.ok(!propertyNames.includes('readwriteAccessor1')); +assert.ok(!propertyNames.includes('readwriteAccessor2')); +assert.ok(!propertyNames.includes('readonlyAccessor1')); +assert.ok(!propertyNames.includes('readonlyAccessor2')); + +// validate property created with symbol +const start = 'Symbol('.length; +const end = start + 'NameKeySymbol'.length; +const symbolDescription = + String(Object.getOwnPropertySymbols(test_object)[0]).slice(start, end); +assert.strictEqual(symbolDescription, 'NameKeySymbol'); + +// The napi_writable attribute should be ignored for accessors. +test_object.readwriteAccessor1 = 1; +assert.strictEqual(test_object.readwriteAccessor1, 1); +assert.strictEqual(test_object.readonlyAccessor1, 1); +assert.throws(() => { test_object.readonlyAccessor1 = 3; }, readonlyErrorRE); +test_object.readwriteAccessor2 = 2; +assert.strictEqual(test_object.readwriteAccessor2, 2); +assert.strictEqual(test_object.readonlyAccessor2, 2); +assert.throws(() => { test_object.readonlyAccessor2 = 3; }, readonlyErrorRE); + +assert.strictEqual(test_object.hasNamedProperty(test_object, 'echo'), true); +assert.strictEqual(test_object.hasNamedProperty(test_object, 'hiddenValue'), + true); +assert.strictEqual(test_object.hasNamedProperty(test_object, 'doesnotexist'), + false); diff --git a/test/addons-napi/test_properties/test_properties.c b/test/addons-napi/test_properties/test_properties.c new file mode 100644 index 00000000000000..f0442bd0c6fa4b --- /dev/null +++ b/test/addons-napi/test_properties/test_properties.c @@ -0,0 +1,102 @@ +#include +#include "../common.h" + +static double value_ = 1; + +napi_value GetValue(napi_env env, napi_callback_info info) { + size_t argc = 0; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, NULL, NULL, NULL)); + + NAPI_ASSERT(env, argc == 0, "Wrong number of arguments"); + + napi_value number; + NAPI_CALL(env, napi_create_double(env, value_, &number)); + + return number; +} + +napi_value SetValue(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_ASSERT(env, argc == 1, "Wrong number of arguments"); + + NAPI_CALL(env, napi_get_value_double(env, args[0], &value_)); + + return NULL; +} + +napi_value Echo(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_ASSERT(env, argc == 1, "Wrong number of arguments"); + + return args[0]; +} + +napi_value HasNamedProperty(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value args[2]; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NAPI_ASSERT(env, argc == 2, "Wrong number of arguments"); + + // Extract the name of the property to check + char buffer[128]; + size_t copied; + NAPI_CALL(env, + napi_get_value_string_utf8(env, args[1], buffer, sizeof(buffer), &copied)); + + // do the check and create the boolean return value + bool value; + napi_value result; + NAPI_CALL(env, napi_has_named_property(env, args[0], buffer, &value)); + NAPI_CALL(env, napi_get_boolean(env, value, &result)); + + return result; +} + +napi_value Init(napi_env env, napi_value exports) { + napi_value number; + NAPI_CALL(env, napi_create_double(env, value_, &number)); + + napi_value name_value; + NAPI_CALL(env, napi_create_string_utf8(env, + "NameKeyValue", + NAPI_AUTO_LENGTH, + &name_value)); + + napi_value symbol_description; + napi_value name_symbol; + NAPI_CALL(env, napi_create_string_utf8(env, + "NameKeySymbol", + NAPI_AUTO_LENGTH, + &symbol_description)); + NAPI_CALL(env, napi_create_symbol(env, + symbol_description, + &name_symbol)); + + napi_property_descriptor properties[] = { + { "echo", 0, Echo, 0, 0, 0, napi_enumerable, 0 }, + { "readwriteValue", 0, 0, 0, 0, number, napi_enumerable | napi_writable, 0 }, + { "readonlyValue", 0, 0, 0, 0, number, napi_enumerable, 0}, + { "hiddenValue", 0, 0, 0, 0, number, napi_default, 0}, + { NULL, name_value, 0, 0, 0, number, napi_enumerable, 0}, + { NULL, name_symbol, 0, 0, 0, number, napi_enumerable, 0}, + { "readwriteAccessor1", 0, 0, GetValue, SetValue, 0, napi_default, 0}, + { "readwriteAccessor2", 0, 0, GetValue, SetValue, 0, napi_writable, 0}, + { "readonlyAccessor1", 0, 0, GetValue, NULL, 0, napi_default, 0}, + { "readonlyAccessor2", 0, 0, GetValue, NULL, 0, napi_writable, 0}, + { "hasNamedProperty", 0, HasNamedProperty, 0, 0, 0, napi_default, 0 }, + }; + + NAPI_CALL(env, napi_define_properties( + env, exports, sizeof(properties) / sizeof(*properties), properties)); + + return exports; +} + +NAPI_MODULE(addon, Init) diff --git a/test/addons-napi/test_reference/binding.gyp b/test/addons-napi/test_reference/binding.gyp new file mode 100644 index 00000000000000..3a6d69a65c81f5 --- /dev/null +++ b/test/addons-napi/test_reference/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "test_reference", + "sources": [ "test_reference.c" ] + } + ] +} diff --git a/test/addons-napi/test_reference/test.js b/test/addons-napi/test_reference/test.js new file mode 100644 index 00000000000000..14932a74ca70b0 --- /dev/null +++ b/test/addons-napi/test_reference/test.js @@ -0,0 +1,120 @@ +'use strict'; +// Flags: --expose-gc + +const common = require('../../common'); +const assert = require('assert'); + +const test_reference = require(`./build/${common.buildType}/test_reference`); + +// This test script uses external values with finalizer callbacks +// in order to track when values get garbage-collected. Each invocation +// of a finalizer callback increments the finalizeCount property. +assert.strictEqual(test_reference.finalizeCount, 0); + +// Run each test function in sequence, +// with an async delay and GC call between each. +function runTests(i, title, tests) { + if (tests[i]) { + if (typeof tests[i] === 'string') { + title = tests[i]; + runTests(i + 1, title, tests); + } else { + try { + tests[i](); + } catch (e) { + console.error(`Test failed: ${title}`); + throw e; + } + setImmediate(() => { + global.gc(); + runTests(i + 1, title, tests); + }); + } + } +} +runTests(0, undefined, [ + + 'External value without a finalizer', + () => { + const value = test_reference.createExternal(); + assert.strictEqual(test_reference.finalizeCount, 0); + assert.strictEqual(typeof value, 'object'); + test_reference.checkExternal(value); + }, + () => { + assert.strictEqual(test_reference.finalizeCount, 0); + }, + + 'External value with a finalizer', + () => { + const value = test_reference.createExternalWithFinalize(); + assert.strictEqual(test_reference.finalizeCount, 0); + assert.strictEqual(typeof value, 'object'); + test_reference.checkExternal(value); + }, + () => { + assert.strictEqual(test_reference.finalizeCount, 1); + }, + + 'Weak reference', + () => { + const value = test_reference.createExternalWithFinalize(); + assert.strictEqual(test_reference.finalizeCount, 0); + test_reference.createReference(value, 0); + assert.strictEqual(test_reference.referenceValue, value); + }, + () => { + // Value should be GC'd because there is only a weak ref + assert.strictEqual(test_reference.referenceValue, undefined); + assert.strictEqual(test_reference.finalizeCount, 1); + test_reference.deleteReference(); + }, + + 'Strong reference', + () => { + const value = test_reference.createExternalWithFinalize(); + assert.strictEqual(test_reference.finalizeCount, 0); + test_reference.createReference(value, 1); + assert.strictEqual(test_reference.referenceValue, value); + }, + () => { + // Value should NOT be GC'd because there is a strong ref + assert.strictEqual(test_reference.finalizeCount, 0); + test_reference.deleteReference(); + }, + () => { + // Value should be GC'd because the strong ref was deleted + assert.strictEqual(test_reference.finalizeCount, 1); + }, + + 'Strong reference, increment then decrement to weak reference', + () => { + const value = test_reference.createExternalWithFinalize(); + assert.strictEqual(test_reference.finalizeCount, 0); + test_reference.createReference(value, 1); + }, + () => { + // Value should NOT be GC'd because there is a strong ref + assert.strictEqual(test_reference.finalizeCount, 0); + assert.strictEqual(test_reference.incrementRefcount(), 2); + }, + () => { + // Value should NOT be GC'd because there is a strong ref + assert.strictEqual(test_reference.finalizeCount, 0); + assert.strictEqual(test_reference.decrementRefcount(), 1); + }, + () => { + // Value should NOT be GC'd because there is a strong ref + assert.strictEqual(test_reference.finalizeCount, 0); + assert.strictEqual(test_reference.decrementRefcount(), 0); + }, + () => { + // Value should be GC'd because the ref is now weak! + assert.strictEqual(test_reference.finalizeCount, 1); + test_reference.deleteReference(); + }, + () => { + // Value was already GC'd + assert.strictEqual(test_reference.finalizeCount, 1); + }, +]); diff --git a/test/addons-napi/test_reference/test_reference.c b/test/addons-napi/test_reference/test_reference.c new file mode 100644 index 00000000000000..329d1bb18b6a09 --- /dev/null +++ b/test/addons-napi/test_reference/test_reference.c @@ -0,0 +1,153 @@ +#include +#include "../common.h" + +static int test_value = 1; +static int finalize_count = 0; +static napi_ref test_reference = NULL; + +napi_value GetFinalizeCount(napi_env env, napi_callback_info info) { + napi_value result; + NAPI_CALL(env, napi_create_int32(env, finalize_count, &result)); + return result; +} + +void FinalizeExternal(napi_env env, void* data, void* hint) { + int *actual_value = data; + NAPI_ASSERT_RETURN_VOID(env, actual_value == &test_value, + "The correct pointer was passed to the finalizer"); + finalize_count++; +} + +napi_value CreateExternal(napi_env env, napi_callback_info info) { + int* data = &test_value; + + napi_value result; + NAPI_CALL(env, + napi_create_external(env, + data, + NULL, /* finalize_cb */ + NULL, /* finalize_hint */ + &result)); + + finalize_count = 0; + return result; +} + +napi_value CreateExternalWithFinalize(napi_env env, napi_callback_info info) { + napi_value result; + NAPI_CALL(env, + napi_create_external(env, + &test_value, + FinalizeExternal, + NULL, /* finalize_hint */ + &result)); + + finalize_count = 0; + return result; +} + +napi_value CheckExternal(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value arg; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, &arg, NULL, NULL)); + + NAPI_ASSERT(env, argc == 1, "Expected one argument."); + + napi_valuetype argtype; + NAPI_CALL(env, napi_typeof(env, arg, &argtype)); + + NAPI_ASSERT(env, argtype == napi_external, "Expected an external value."); + + void* data; + NAPI_CALL(env, napi_get_value_external(env, arg, &data)); + + NAPI_ASSERT(env, data != NULL && *(int*)data == test_value, + "An external data value of 1 was expected."); + + return NULL; +} + +napi_value CreateReference(napi_env env, napi_callback_info info) { + NAPI_ASSERT(env, test_reference == NULL, + "The test allows only one reference at a time."); + + size_t argc = 2; + napi_value args[2]; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + NAPI_ASSERT(env, argc == 2, "Expected two arguments."); + + uint32_t initial_refcount; + NAPI_CALL(env, napi_get_value_uint32(env, args[1], &initial_refcount)); + + NAPI_CALL(env, + napi_create_reference(env, args[0], initial_refcount, &test_reference)); + + NAPI_ASSERT(env, test_reference != NULL, + "A reference should have been created."); + + return NULL; +} + +napi_value DeleteReference(napi_env env, napi_callback_info info) { + NAPI_ASSERT(env, test_reference != NULL, + "A reference must have been created."); + + NAPI_CALL(env, napi_delete_reference(env, test_reference)); + test_reference = NULL; + return NULL; +} + +napi_value IncrementRefcount(napi_env env, napi_callback_info info) { + NAPI_ASSERT(env, test_reference != NULL, + "A reference must have been created."); + + uint32_t refcount; + NAPI_CALL(env, napi_reference_ref(env, test_reference, &refcount)); + + napi_value result; + NAPI_CALL(env, napi_create_uint32(env, refcount, &result)); + return result; +} + +napi_value DecrementRefcount(napi_env env, napi_callback_info info) { + NAPI_ASSERT(env, test_reference != NULL, + "A reference must have been created."); + + uint32_t refcount; + NAPI_CALL(env, napi_reference_unref(env, test_reference, &refcount)); + + napi_value result; + NAPI_CALL(env, napi_create_uint32(env, refcount, &result)); + return result; +} + +napi_value GetReferenceValue(napi_env env, napi_callback_info info) { + NAPI_ASSERT(env, test_reference != NULL, + "A reference must have been created."); + + napi_value result; + NAPI_CALL(env, napi_get_reference_value(env, test_reference, &result)); + return result; +} + +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor descriptors[] = { + DECLARE_NAPI_GETTER("finalizeCount", GetFinalizeCount), + DECLARE_NAPI_PROPERTY("createExternal", CreateExternal), + DECLARE_NAPI_PROPERTY("createExternalWithFinalize", + CreateExternalWithFinalize), + DECLARE_NAPI_PROPERTY("checkExternal", CheckExternal), + DECLARE_NAPI_PROPERTY("createReference", CreateReference), + DECLARE_NAPI_PROPERTY("deleteReference", DeleteReference), + DECLARE_NAPI_PROPERTY("incrementRefcount", IncrementRefcount), + DECLARE_NAPI_PROPERTY("decrementRefcount", DecrementRefcount), + DECLARE_NAPI_GETTER("referenceValue", GetReferenceValue), + }; + + NAPI_CALL(env, napi_define_properties( + env, exports, sizeof(descriptors) / sizeof(*descriptors), descriptors)); + + return exports; +} + +NAPI_MODULE(addon, Init) diff --git a/test/addons-napi/test_string/binding.gyp b/test/addons-napi/test_string/binding.gyp new file mode 100644 index 00000000000000..d4825de933985f --- /dev/null +++ b/test/addons-napi/test_string/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "test_string", + "sources": [ "test_string.c" ] + } + ] +} diff --git a/test/addons-napi/test_string/test.js b/test/addons-napi/test_string/test.js new file mode 100644 index 00000000000000..5ce3d739c7a941 --- /dev/null +++ b/test/addons-napi/test_string/test.js @@ -0,0 +1,75 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); + +// testing api calls for string +const test_string = require(`./build/${common.buildType}/test_string`); + +const empty = ''; +assert.strictEqual(test_string.TestLatin1(empty), empty); +assert.strictEqual(test_string.TestUtf8(empty), empty); +assert.strictEqual(test_string.TestUtf16(empty), empty); +assert.strictEqual(test_string.Utf16Length(empty), 0); +assert.strictEqual(test_string.Utf8Length(empty), 0); + +const str1 = 'hello world'; +assert.strictEqual(test_string.TestLatin1(str1), str1); +assert.strictEqual(test_string.TestUtf8(str1), str1); +assert.strictEqual(test_string.TestUtf16(str1), str1); +assert.strictEqual(test_string.TestLatin1Insufficient(str1), str1.slice(0, 3)); +assert.strictEqual(test_string.TestUtf8Insufficient(str1), str1.slice(0, 3)); +assert.strictEqual(test_string.TestUtf16Insufficient(str1), str1.slice(0, 3)); +assert.strictEqual(test_string.Utf16Length(str1), 11); +assert.strictEqual(test_string.Utf8Length(str1), 11); + +const str2 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; +assert.strictEqual(test_string.TestLatin1(str2), str2); +assert.strictEqual(test_string.TestUtf8(str2), str2); +assert.strictEqual(test_string.TestUtf16(str2), str2); +assert.strictEqual(test_string.TestLatin1Insufficient(str2), str2.slice(0, 3)); +assert.strictEqual(test_string.TestUtf8Insufficient(str2), str2.slice(0, 3)); +assert.strictEqual(test_string.TestUtf16Insufficient(str2), str2.slice(0, 3)); +assert.strictEqual(test_string.Utf16Length(str2), 62); +assert.strictEqual(test_string.Utf8Length(str2), 62); + +const str3 = '?!@#$%^&*()_+-=[]{}/.,<>\'"\\'; +assert.strictEqual(test_string.TestLatin1(str3), str3); +assert.strictEqual(test_string.TestUtf8(str3), str3); +assert.strictEqual(test_string.TestUtf16(str3), str3); +assert.strictEqual(test_string.TestLatin1Insufficient(str3), str3.slice(0, 3)); +assert.strictEqual(test_string.TestUtf8Insufficient(str3), str3.slice(0, 3)); +assert.strictEqual(test_string.TestUtf16Insufficient(str3), str3.slice(0, 3)); +assert.strictEqual(test_string.Utf16Length(str3), 27); +assert.strictEqual(test_string.Utf8Length(str3), 27); + +const str4 = '¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿'; +assert.strictEqual(test_string.TestLatin1(str4), str4); +assert.strictEqual(test_string.TestUtf8(str4), str4); +assert.strictEqual(test_string.TestUtf16(str4), str4); +assert.strictEqual(test_string.TestLatin1Insufficient(str4), str4.slice(0, 3)); +assert.strictEqual(test_string.TestUtf8Insufficient(str4), str4.slice(0, 1)); +assert.strictEqual(test_string.TestUtf16Insufficient(str4), str4.slice(0, 3)); +assert.strictEqual(test_string.Utf16Length(str4), 31); +assert.strictEqual(test_string.Utf8Length(str4), 62); + +const str5 = 'ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþ'; +assert.strictEqual(test_string.TestLatin1(str5), str5); +assert.strictEqual(test_string.TestUtf8(str5), str5); +assert.strictEqual(test_string.TestUtf16(str5), str5); +assert.strictEqual(test_string.TestLatin1Insufficient(str5), str5.slice(0, 3)); +assert.strictEqual(test_string.TestUtf8Insufficient(str5), str5.slice(0, 1)); +assert.strictEqual(test_string.TestUtf16Insufficient(str5), str5.slice(0, 3)); +assert.strictEqual(test_string.Utf16Length(str5), 63); +assert.strictEqual(test_string.Utf8Length(str5), 126); + +const str6 = '\u{2003}\u{2101}\u{2001}\u{202}\u{2011}'; +assert.strictEqual(test_string.TestUtf8(str6), str6); +assert.strictEqual(test_string.TestUtf16(str6), str6); +assert.strictEqual(test_string.TestUtf8Insufficient(str6), str6.slice(0, 1)); +assert.strictEqual(test_string.TestUtf16Insufficient(str6), str6.slice(0, 3)); +assert.strictEqual(test_string.Utf16Length(str6), 5); +assert.strictEqual(test_string.Utf8Length(str6), 14); + +assert.throws(() => { + test_string.TestLargeUtf8(); +}, /^Error: Invalid argument$/); diff --git a/test/addons-napi/test_string/test_string.c b/test/addons-napi/test_string/test_string.c new file mode 100644 index 00000000000000..01845e428054bc --- /dev/null +++ b/test/addons-napi/test_string/test_string.c @@ -0,0 +1,237 @@ +#include // INT_MAX +#include +#include "../common.h" + +napi_value TestLatin1(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_ASSERT(env, argc >= 1, "Wrong number of arguments"); + + napi_valuetype valuetype; + NAPI_CALL(env, napi_typeof(env, args[0], &valuetype)); + + NAPI_ASSERT(env, valuetype == napi_string, + "Wrong type of argment. Expects a string."); + + char buffer[128]; + size_t buffer_size = 128; + size_t copied; + + NAPI_CALL(env, + napi_get_value_string_latin1(env, args[0], buffer, buffer_size, &copied)); + + napi_value output; + NAPI_CALL(env, napi_create_string_latin1(env, buffer, copied, &output)); + + return output; +} + +napi_value TestUtf8(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_ASSERT(env, argc >= 1, "Wrong number of arguments"); + + napi_valuetype valuetype; + NAPI_CALL(env, napi_typeof(env, args[0], &valuetype)); + + NAPI_ASSERT(env, valuetype == napi_string, + "Wrong type of argment. Expects a string."); + + char buffer[128]; + size_t buffer_size = 128; + size_t copied; + + NAPI_CALL(env, + napi_get_value_string_utf8(env, args[0], buffer, buffer_size, &copied)); + + napi_value output; + NAPI_CALL(env, napi_create_string_utf8(env, buffer, copied, &output)); + + return output; +} + +napi_value TestUtf16(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_ASSERT(env, argc >= 1, "Wrong number of arguments"); + + napi_valuetype valuetype; + NAPI_CALL(env, napi_typeof(env, args[0], &valuetype)); + + NAPI_ASSERT(env, valuetype == napi_string, + "Wrong type of argment. Expects a string."); + + char16_t buffer[128]; + size_t buffer_size = 128; + size_t copied; + + NAPI_CALL(env, + napi_get_value_string_utf16(env, args[0], buffer, buffer_size, &copied)); + + napi_value output; + NAPI_CALL(env, napi_create_string_utf16(env, buffer, copied, &output)); + + return output; +} + +napi_value TestLatin1Insufficient(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_ASSERT(env, argc >= 1, "Wrong number of arguments"); + + napi_valuetype valuetype; + NAPI_CALL(env, napi_typeof(env, args[0], &valuetype)); + + NAPI_ASSERT(env, valuetype == napi_string, + "Wrong type of argment. Expects a string."); + + char buffer[4]; + size_t buffer_size = 4; + size_t copied; + + NAPI_CALL(env, + napi_get_value_string_latin1(env, args[0], buffer, buffer_size, &copied)); + + napi_value output; + NAPI_CALL(env, napi_create_string_latin1(env, buffer, copied, &output)); + + return output; +} + +napi_value TestUtf8Insufficient(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_ASSERT(env, argc >= 1, "Wrong number of arguments"); + + napi_valuetype valuetype; + NAPI_CALL(env, napi_typeof(env, args[0], &valuetype)); + + NAPI_ASSERT(env, valuetype == napi_string, + "Wrong type of argment. Expects a string."); + + char buffer[4]; + size_t buffer_size = 4; + size_t copied; + + NAPI_CALL(env, + napi_get_value_string_utf8(env, args[0], buffer, buffer_size, &copied)); + + napi_value output; + NAPI_CALL(env, napi_create_string_utf8(env, buffer, copied, &output)); + + return output; +} + +napi_value TestUtf16Insufficient(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_ASSERT(env, argc >= 1, "Wrong number of arguments"); + + napi_valuetype valuetype; + NAPI_CALL(env, napi_typeof(env, args[0], &valuetype)); + + NAPI_ASSERT(env, valuetype == napi_string, + "Wrong type of argment. Expects a string."); + + char16_t buffer[4]; + size_t buffer_size = 4; + size_t copied; + + NAPI_CALL(env, + napi_get_value_string_utf16(env, args[0], buffer, buffer_size, &copied)); + + napi_value output; + NAPI_CALL(env, napi_create_string_utf16(env, buffer, copied, &output)); + + return output; +} + +napi_value Utf16Length(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_ASSERT(env, argc >= 1, "Wrong number of arguments"); + + napi_valuetype valuetype; + NAPI_CALL(env, napi_typeof(env, args[0], &valuetype)); + + NAPI_ASSERT(env, valuetype == napi_string, + "Wrong type of argment. Expects a string."); + + size_t length; + NAPI_CALL(env, napi_get_value_string_utf16(env, args[0], NULL, 0, &length)); + + napi_value output; + NAPI_CALL(env, napi_create_uint32(env, (uint32_t)length, &output)); + + return output; +} + +napi_value Utf8Length(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_ASSERT(env, argc >= 1, "Wrong number of arguments"); + + napi_valuetype valuetype; + NAPI_CALL(env, napi_typeof(env, args[0], &valuetype)); + + NAPI_ASSERT(env, valuetype == napi_string, + "Wrong type of argment. Expects a string."); + + size_t length; + NAPI_CALL(env, napi_get_value_string_utf8(env, args[0], NULL, 0, &length)); + + napi_value output; + NAPI_CALL(env, napi_create_uint32(env, (uint32_t)length, &output)); + + return output; +} + +napi_value TestLargeUtf8(napi_env env, napi_callback_info info) { + napi_value output; + if (SIZE_MAX > INT_MAX) { + NAPI_CALL(env, napi_create_string_utf8(env, "", ((size_t)INT_MAX) + 1, &output)); + } else { + // just throw the expected error as there is nothing to test + // in this case since we can't overflow + NAPI_CALL(env, napi_throw_error(env, NULL, "Invalid argument")); + } + + return output; +} + +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor properties[] = { + DECLARE_NAPI_PROPERTY("TestLatin1", TestLatin1), + DECLARE_NAPI_PROPERTY("TestLatin1Insufficient", TestLatin1Insufficient), + DECLARE_NAPI_PROPERTY("TestUtf8", TestUtf8), + DECLARE_NAPI_PROPERTY("TestUtf8Insufficient", TestUtf8Insufficient), + DECLARE_NAPI_PROPERTY("TestUtf16", TestUtf16), + DECLARE_NAPI_PROPERTY("TestUtf16Insufficient", TestUtf16Insufficient), + DECLARE_NAPI_PROPERTY("Utf16Length", Utf16Length), + DECLARE_NAPI_PROPERTY("Utf8Length", Utf8Length), + DECLARE_NAPI_PROPERTY("TestLargeUtf8", TestLargeUtf8), + }; + + NAPI_CALL(env, napi_define_properties( + env, exports, sizeof(properties) / sizeof(*properties), properties)); + + return exports; +} + +NAPI_MODULE(addon, Init) diff --git a/test/addons-napi/test_symbol/binding.gyp b/test/addons-napi/test_symbol/binding.gyp new file mode 100644 index 00000000000000..6ef3407968db94 --- /dev/null +++ b/test/addons-napi/test_symbol/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "test_symbol", + "sources": [ "test_symbol.c" ] + } + ] +} diff --git a/test/addons-napi/test_symbol/test1.js b/test/addons-napi/test_symbol/test1.js new file mode 100644 index 00000000000000..25eb473c4b1b9d --- /dev/null +++ b/test/addons-napi/test_symbol/test1.js @@ -0,0 +1,20 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); + +// testing api calls for symbol +const test_symbol = require(`./build/${common.buildType}/test_symbol`); + +const sym = test_symbol.New('test'); +assert.strictEqual(sym.toString(), 'Symbol(test)'); + + +const myObj = {}; +const fooSym = test_symbol.New('foo'); +const otherSym = test_symbol.New('bar'); +myObj['foo'] = 'bar'; +myObj[fooSym] = 'baz'; +myObj[otherSym] = 'bing'; +assert.strictEqual(myObj.foo, 'bar'); +assert.strictEqual(myObj[fooSym], 'baz'); +assert.strictEqual(myObj[otherSym], 'bing'); diff --git a/test/addons-napi/test_symbol/test2.js b/test/addons-napi/test_symbol/test2.js new file mode 100644 index 00000000000000..60512431110a5b --- /dev/null +++ b/test/addons-napi/test_symbol/test2.js @@ -0,0 +1,15 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); + +// testing api calls for symbol +const test_symbol = require(`./build/${common.buildType}/test_symbol`); + +const fooSym = test_symbol.New('foo'); +const myObj = {}; +myObj['foo'] = 'bar'; +myObj[fooSym] = 'baz'; +Object.keys(myObj); // -> [ 'foo' ] +Object.getOwnPropertyNames(myObj); // -> [ 'foo' ] +Object.getOwnPropertySymbols(myObj); // -> [ Symbol(foo) ] +assert.strictEqual(Object.getOwnPropertySymbols(myObj)[0], fooSym); diff --git a/test/addons-napi/test_symbol/test3.js b/test/addons-napi/test_symbol/test3.js new file mode 100644 index 00000000000000..a7c6c18c025480 --- /dev/null +++ b/test/addons-napi/test_symbol/test3.js @@ -0,0 +1,19 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); + +// testing api calls for symbol +const test_symbol = require(`./build/${common.buildType}/test_symbol`); + +assert.notStrictEqual(test_symbol.New(), test_symbol.New()); +assert.notStrictEqual(test_symbol.New('foo'), test_symbol.New('foo')); +assert.notStrictEqual(test_symbol.New('foo'), test_symbol.New('bar')); + +const foo1 = test_symbol.New('foo'); +const foo2 = test_symbol.New('foo'); +const object = { + [foo1]: 1, + [foo2]: 2, +}; +assert.strictEqual(object[foo1], 1); +assert.strictEqual(object[foo2], 2); diff --git a/test/addons-napi/test_symbol/test_symbol.c b/test/addons-napi/test_symbol/test_symbol.c new file mode 100644 index 00000000000000..87f6cdde671600 --- /dev/null +++ b/test/addons-napi/test_symbol/test_symbol.c @@ -0,0 +1,62 @@ +#include +#include "../common.h" + +napi_value Test(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_ASSERT(env, argc >= 1, "Wrong number of arguments"); + + napi_valuetype valuetype; + NAPI_CALL(env, napi_typeof(env, args[0], &valuetype)); + + NAPI_ASSERT(env, valuetype == napi_symbol, + "Wrong type of arguments. Expects a symbol."); + + char buffer[128]; + size_t buffer_size = 128; + + NAPI_CALL(env, napi_get_value_string_utf8( + env, args[0], buffer, buffer_size, NULL)); + + napi_value output; + NAPI_CALL(env, napi_create_string_utf8(env, buffer, buffer_size, &output)); + + return output; +} + +napi_value New(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 description = NULL; + if (argc >= 1) { + napi_valuetype valuetype; + NAPI_CALL(env, napi_typeof(env, args[0], &valuetype)); + + NAPI_ASSERT(env, valuetype == napi_string, + "Wrong type of arguments. Expects a string."); + + description = args[0]; + } + + napi_value symbol; + NAPI_CALL(env, napi_create_symbol(env, description, &symbol)); + + return symbol; +} + +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor properties[] = { + DECLARE_NAPI_PROPERTY("New", New), + }; + + NAPI_CALL(env, napi_define_properties( + env, exports, sizeof(properties) / sizeof(*properties), properties)); + + return exports; +} + +NAPI_MODULE(addon, Init) diff --git a/test/addons-napi/test_typedarray/binding.gyp b/test/addons-napi/test_typedarray/binding.gyp new file mode 100644 index 00000000000000..8b4a4dc622b2ef --- /dev/null +++ b/test/addons-napi/test_typedarray/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "test_typedarray", + "sources": [ "test_typedarray.c" ] + } + ] +} diff --git a/test/addons-napi/test_typedarray/test.js b/test/addons-napi/test_typedarray/test.js new file mode 100644 index 00000000000000..4c139d92200fe3 --- /dev/null +++ b/test/addons-napi/test_typedarray/test.js @@ -0,0 +1,75 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); + +// Testing api calls for arrays +const test_typedarray = require(`./build/${common.buildType}/test_typedarray`); + +const byteArray = new Uint8Array(3); +byteArray[0] = 0; +byteArray[1] = 1; +byteArray[2] = 2; +assert.strictEqual(byteArray.length, 3); + +const doubleArray = new Float64Array(3); +doubleArray[0] = 0.0; +doubleArray[1] = 1.1; +doubleArray[2] = 2.2; +assert.strictEqual(doubleArray.length, 3); + +const byteResult = test_typedarray.Multiply(byteArray, 3); +assert.ok(byteResult instanceof Uint8Array); +assert.strictEqual(byteResult.length, 3); +assert.strictEqual(byteResult[0], 0); +assert.strictEqual(byteResult[1], 3); +assert.strictEqual(byteResult[2], 6); + +const doubleResult = test_typedarray.Multiply(doubleArray, -3); +assert.ok(doubleResult instanceof Float64Array); +assert.strictEqual(doubleResult.length, 3); +assert.strictEqual(doubleResult[0], 0); +assert.strictEqual(Math.round(10 * doubleResult[1]) / 10, -3.3); +assert.strictEqual(Math.round(10 * doubleResult[2]) / 10, -6.6); + +const externalResult = test_typedarray.External(); +assert.ok(externalResult instanceof Int8Array); +assert.strictEqual(externalResult.length, 3); +assert.strictEqual(externalResult[0], 0); +assert.strictEqual(externalResult[1], 1); +assert.strictEqual(externalResult[2], 2); + +// validate creation of all kinds of TypedArrays +const buffer = new ArrayBuffer(128); +const arrayTypes = [ Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, + Uint16Array, Int32Array, Uint32Array, Float32Array, + Float64Array ]; + +arrayTypes.forEach((currentType) => { + const template = Reflect.construct(currentType, buffer); + const theArray = test_typedarray.CreateTypedArray(template, buffer); + + assert.ok(theArray instanceof currentType, + 'Type of new array should match that of the template. ' + + `Expected type: ${currentType.name}, ` + + `actual type: ${template.constructor.name}`); + assert.notStrictEqual(theArray, template); + assert.strictEqual(theArray.buffer, buffer); +}); + +arrayTypes.forEach((currentType) => { + const template = Reflect.construct(currentType, buffer); + assert.throws(() => { + test_typedarray.CreateTypedArray(template, buffer, 0, 136); + }, RangeError); +}); + +const nonByteArrayTypes = [ Int16Array, Uint16Array, Int32Array, Uint32Array, + Float32Array, Float64Array ]; +nonByteArrayTypes.forEach((currentType) => { + const template = Reflect.construct(currentType, buffer); + assert.throws(() => { + test_typedarray.CreateTypedArray(template, buffer, + currentType.BYTES_PER_ELEMENT + 1, 1); + console.log(`start of offset ${currentType}`); + }, RangeError); +}); diff --git a/test/addons-napi/test_typedarray/test_typedarray.c b/test/addons-napi/test_typedarray/test_typedarray.c new file mode 100644 index 00000000000000..278812f98d5971 --- /dev/null +++ b/test/addons-napi/test_typedarray/test_typedarray.c @@ -0,0 +1,181 @@ +#include +#include +#include "../common.h" + +napi_value Multiply(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value args[2]; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NAPI_ASSERT(env, argc == 2, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NAPI_CALL(env, napi_typeof(env, args[0], &valuetype0)); + + NAPI_ASSERT(env, valuetype0 == napi_object, + "Wrong type of arguments. Expects a typed array as first argument."); + + napi_value input_array = args[0]; + bool is_typedarray; + NAPI_CALL(env, napi_is_typedarray(env, input_array, &is_typedarray)); + + NAPI_ASSERT(env, is_typedarray, + "Wrong type of arguments. Expects a typed array as first argument."); + + napi_valuetype valuetype1; + NAPI_CALL(env, napi_typeof(env, args[1], &valuetype1)); + + NAPI_ASSERT(env, valuetype1 == napi_number, + "Wrong type of arguments. Expects a number as second argument."); + + double multiplier; + NAPI_CALL(env, napi_get_value_double(env, args[1], &multiplier)); + + napi_typedarray_type type; + napi_value input_buffer; + size_t byte_offset; + size_t i, length; + NAPI_CALL(env, napi_get_typedarray_info( + env, input_array, &type, &length, NULL, &input_buffer, &byte_offset)); + + void* data; + size_t byte_length; + NAPI_CALL(env, napi_get_arraybuffer_info( + env, input_buffer, &data, &byte_length)); + + napi_value output_buffer; + void* output_ptr = NULL; + NAPI_CALL(env, napi_create_arraybuffer( + env, byte_length, &output_ptr, &output_buffer)); + + napi_value output_array; + NAPI_CALL(env, napi_create_typedarray( + env, type, length, output_buffer, byte_offset, &output_array)); + + if (type == napi_uint8_array) { + uint8_t* input_bytes = (uint8_t*)(data) + byte_offset; + uint8_t* output_bytes = (uint8_t*)(output_ptr); + for (i = 0; i < length; i++) { + output_bytes[i] = (uint8_t)(input_bytes[i] * multiplier); + } + } else if (type == napi_float64_array) { + double* input_doubles = (double*)((uint8_t*)(data) + byte_offset); + double* output_doubles = (double*)(output_ptr); + for (i = 0; i < length; i++) { + output_doubles[i] = input_doubles[i] * multiplier; + } + } else { + napi_throw_error(env, NULL, + "Typed array was of a type not expected by test."); + return NULL; + } + + return output_array; +} + +napi_value External(napi_env env, napi_callback_info info) { + static int8_t externalData[] = {0, 1, 2}; + + napi_value output_buffer; + NAPI_CALL(env, napi_create_external_arraybuffer( + env, + externalData, + sizeof(externalData), + NULL, // finalize_callback + NULL, // finalize_hint + &output_buffer)); + + napi_value output_array; + NAPI_CALL(env, napi_create_typedarray(env, + napi_int8_array, + sizeof(externalData) / sizeof(int8_t), + output_buffer, + 0, + &output_array)); + + return output_array; +} + +napi_value CreateTypedArray(napi_env env, napi_callback_info info) { + size_t argc = 4; + napi_value args[4]; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NAPI_ASSERT(env, argc == 2 || argc == 4, "Wrong number of arguments"); + + napi_value input_array = args[0]; + napi_valuetype valuetype0; + NAPI_CALL(env, napi_typeof(env, input_array, &valuetype0)); + + NAPI_ASSERT(env, valuetype0 == napi_object, + "Wrong type of arguments. Expects a typed array as first argument."); + + bool is_typedarray; + NAPI_CALL(env, napi_is_typedarray(env, input_array, &is_typedarray)); + + NAPI_ASSERT(env, is_typedarray, + "Wrong type of arguments. Expects a typed array as first argument."); + + napi_valuetype valuetype1; + napi_value input_buffer = args[1]; + NAPI_CALL(env, napi_typeof(env, input_buffer, &valuetype1)); + + NAPI_ASSERT(env, valuetype1 == napi_object, + "Wrong type of arguments. Expects an array buffer as second argument."); + + bool is_arraybuffer; + NAPI_CALL(env, napi_is_arraybuffer(env, input_buffer, &is_arraybuffer)); + + NAPI_ASSERT(env, is_arraybuffer, + "Wrong type of arguments. Expects an array buffer as second argument."); + + napi_typedarray_type type; + napi_value in_array_buffer; + size_t byte_offset; + size_t length; + NAPI_CALL(env, napi_get_typedarray_info( + env, input_array, &type, &length, NULL, &in_array_buffer, &byte_offset)); + + if (argc == 4) { + napi_valuetype valuetype2; + NAPI_CALL(env, napi_typeof(env, args[2], &valuetype2)); + + NAPI_ASSERT(env, valuetype2 == napi_number, + "Wrong type of arguments. Expects a number as third argument."); + + uint32_t uint32_length; + NAPI_CALL(env, napi_get_value_uint32(env, args[2], &uint32_length)); + length = uint32_length; + + napi_valuetype valuetype3; + NAPI_CALL(env, napi_typeof(env, args[3], &valuetype3)); + + NAPI_ASSERT(env, valuetype3 == napi_number, + "Wrong type of arguments. Expects a number as third argument."); + + uint32_t uint32_byte_offset; + NAPI_CALL(env, napi_get_value_uint32(env, args[3], &uint32_byte_offset)); + byte_offset = uint32_byte_offset; + } + + napi_value output_array; + NAPI_CALL(env, napi_create_typedarray( + env, type, length, input_buffer, byte_offset, &output_array)); + + return output_array; +} + +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor descriptors[] = { + DECLARE_NAPI_PROPERTY("Multiply", Multiply), + DECLARE_NAPI_PROPERTY("External", External), + DECLARE_NAPI_PROPERTY("CreateTypedArray", CreateTypedArray), + }; + + NAPI_CALL(env, napi_define_properties( + env, exports, sizeof(descriptors) / sizeof(*descriptors), descriptors)); + + return exports; +} + +NAPI_MODULE(addon, Init) diff --git a/test/addons-napi/testcfg.py b/test/addons-napi/testcfg.py new file mode 100644 index 00000000000000..b112eb009924fc --- /dev/null +++ b/test/addons-napi/testcfg.py @@ -0,0 +1,6 @@ +import sys, os +sys.path.append(os.path.join(os.path.dirname(__file__), '..')) +import testpy + +def GetConfiguration(context, root): + return testpy.AddonTestConfiguration(context, root, 'addons-napi') diff --git a/test/addons/openssl-binding/binding.cc b/test/addons/openssl-binding/binding.cc index 582a94478d26ca..fa40b3346a7a39 100644 --- a/test/addons/openssl-binding/binding.cc +++ b/test/addons/openssl-binding/binding.cc @@ -29,4 +29,4 @@ inline void Initialize(v8::Local exports, } // anonymous namespace -NODE_MODULE_CONTEXT_AWARE(binding, Initialize) +NODE_MODULE_CONTEXT_AWARE(NODE_GYP_MODULE_NAME, Initialize) diff --git a/test/addons/repl-domain-abort/test.js b/test/addons/repl-domain-abort/test.js index 8e5d8785164fb9..9b9a63abfb685c 100644 --- a/test/addons/repl-domain-abort/test.js +++ b/test/addons/repl-domain-abort/test.js @@ -25,7 +25,7 @@ const lines = [ const dInput = new stream.Readable(); const dOutput = new stream.Writable(); -dInput._read = function _read(size) { +dInput._read = function _read() { while (lines.length > 0 && this.push(lines.shift())); if (lines.length === 0) this.push(null); diff --git a/test/addons/zlib-binding/binding.cc b/test/addons/zlib-binding/binding.cc index 262f90bf0236e5..a9a8c14306c486 100644 --- a/test/addons/zlib-binding/binding.cc +++ b/test/addons/zlib-binding/binding.cc @@ -52,4 +52,4 @@ inline void Initialize(v8::Local exports, } // anonymous namespace -NODE_MODULE_CONTEXT_AWARE(binding, Initialize) +NODE_MODULE_CONTEXT_AWARE(NODE_GYP_MODULE_NAME, Initialize) diff --git a/test/common/index.js b/test/common/index.js index d762f4e0aa8498..a5328e29ec998f 100644 --- a/test/common/index.js +++ b/test/common/index.js @@ -625,6 +625,59 @@ Object.defineProperty(exports, 'hasIntl', { } }); +// Useful for testing expected internal/error objects +exports.expectsError = function expectsError(fn, settings, exact) { + if (typeof fn !== 'function') { + exact = settings; + settings = fn; + fn = undefined; + } + function innerFn(error) { + assert.strictEqual(error.code, settings.code); + if ('type' in settings) { + const type = settings.type; + if (type !== Error && !Error.isPrototypeOf(type)) { + throw new TypeError('`settings.type` must inherit from `Error`'); + } + assert(error instanceof type, + `${error.name} is not instance of ${type.name}`); + let typeName = error.constructor.name; + if (typeName === 'NodeError' && type.name !== 'NodeError') { + typeName = Object.getPrototypeOf(error.constructor).name; + } + assert.strictEqual(typeName, type.name); + } + if ('message' in settings) { + const message = settings.message; + if (typeof message === 'string') { + assert.strictEqual(error.message, message); + } else { + assert(message.test(error.message), + `${error.message} does not match ${message}`); + } + } + if ('name' in settings) { + assert.strictEqual(error.name, settings.name); + } + if (error.constructor.name === 'AssertionError') { + ['generatedMessage', 'actual', 'expected', 'operator'].forEach((key) => { + if (key in settings) { + const actual = error[key]; + const expected = settings[key]; + assert.strictEqual(actual, expected, + `${key}: expected ${expected}, not ${actual}`); + } + }); + } + return true; + } + if (fn) { + assert.throws(fn, innerFn); + return; + } + return exports.mustCall(innerFn, exact); +}; + // Crash the process on unhandled rejections. exports.crashOnUnhandledRejection = function() { process.on('unhandledRejection', diff --git a/test/parallel/test-assert.js b/test/parallel/test-assert.js index 55376328b026f2..9b82c3ff4cccf9 100644 --- a/test/parallel/test-assert.js +++ b/test/parallel/test-assert.js @@ -522,15 +522,19 @@ testAssertionMessage({a: undefined, b: null}, '{ a: undefined, b: null }'); testAssertionMessage({a: NaN, b: Infinity, c: -Infinity}, '{ a: NaN, b: Infinity, c: -Infinity }'); -// https://github.com/nodejs/node-v0.x-archive/issues/2893 -try { - // eslint-disable-next-line no-restricted-syntax - assert.throws(function() { - assert.ifError(null); - }); -} catch (e) { - threw = true; - assert.strictEqual(e.message, 'Missing expected exception..'); +// #2893 +{ + let threw = false; + try { + // eslint-disable-next-line no-restricted-syntax + assert.throws(function() { + assert.ifError(null); + }); + } catch (e) { + threw = true; + assert.strictEqual(e.message, 'Missing expected exception..'); + } + assert.ok(threw); } assert.ok(threw); diff --git a/test/parallel/test-process-versions.js b/test/parallel/test-process-versions.js index b0377afd4a1899..d701a06a46ec10 100644 --- a/test/parallel/test-process-versions.js +++ b/test/parallel/test-process-versions.js @@ -3,7 +3,7 @@ const common = require('../common'); const assert = require('assert'); const expected_keys = ['ares', 'http_parser', 'modules', 'node', - 'uv', 'v8', 'zlib']; + 'uv', 'v8', 'zlib', 'napi']; if (common.hasCrypto) { expected_keys.push('openssl'); diff --git a/test/testpy/__init__.py b/test/testpy/__init__.py index 4e6fb4e660d9d0..7502d990023451 100644 --- a/test/testpy/__init__.py +++ b/test/testpy/__init__.py @@ -149,5 +149,5 @@ def ListTests(self, current_path, path, arch, mode): if self.Contains(path, test): file_path = join(self.root, reduce(join, test[1:], "") + ".js") result.append( - SimpleTestCase(test, file_path, arch, mode, self.context, self)) + SimpleTestCase(test, file_path, arch, mode, self.context, self, self.additional_flags)) return result diff --git a/tools/install.py b/tools/install.py index 5abcde49c16a2d..bb7d52818892a5 100755 --- a/tools/install.py +++ b/tools/install.py @@ -154,6 +154,8 @@ def headers(action): 'common.gypi', 'config.gypi', 'src/node.h', + 'src/node_api.h', + 'src/node_api_types.h', 'src/node_buffer.h', 'src/node_object_wrap.h', 'src/node_version.h', diff --git a/tools/test.py b/tools/test.py index 561473f246629b..24a27ac18a95cc 100755 --- a/tools/test.py +++ b/tools/test.py @@ -1469,6 +1469,7 @@ def ExpandCommand(args): 'message', 'internet', 'addons', + 'addons-napi', 'gc', 'debugger', 'doctool', diff --git a/vcbuild.bat b/vcbuild.bat index a46c99d75916e6..0f95fb3a2a2690 100644 --- a/vcbuild.bat +++ b/vcbuild.bat @@ -41,6 +41,7 @@ set configure_flags= set build_addons= set dll= set enable_static= +set build_addons_napi= :next-arg if "%1"=="" goto args-done @@ -58,9 +59,10 @@ if /i "%1"=="nosnapshot" set nosnapshot=1&goto arg-ok if /i "%1"=="noetw" set noetw=1&goto arg-ok if /i "%1"=="noperfctr" set noperfctr=1&goto arg-ok if /i "%1"=="licensertf" set licensertf=1&goto arg-ok -if /i "%1"=="test" set test_args=%test_args% abort doctool known_issues message parallel sequential addons -J&set lint_cpp=1&set lint_js=1&set build_addons=1&goto arg-ok -if /i "%1"=="test-ci" set test_args=%test_args% %test_ci_args% -p tap --logfile test.tap abort doctool inspector known_issues message sequential parallel addons&set cctest_args=%cctest_args% --gtest_output=tap:cctest.tap&set build_addons=1&goto arg-ok +if /i "%1"=="test" set test_args=%test_args% abort doctool known_issues message parallel sequential addons addons-napi -J&set lint_cpp=1&set lint_js=1&set build_addons=1&set build_addons_napi=1&goto arg-ok +if /i "%1"=="test-ci" set test_args=%test_args% %test_ci_args% -p tap --logfile test.tap abort doctool inspector known_issues message sequential parallel addons addons-napi &set cctest_args=%cctest_args% --gtest_output=tap:cctest.tap&set build_addons=1&set build_addons_napi=1&goto arg-ok if /i "%1"=="test-addons" set test_args=%test_args% addons&set build_addons=1&goto arg-ok +if /i "%1"=="test-addons-napi" set test_args=%test_args% addons-napi&set build_addons_napi=1&goto arg-ok if /i "%1"=="test-simple" set test_args=%test_args% sequential parallel -J&goto arg-ok if /i "%1"=="test-message" set test_args=%test_args% message&goto arg-ok if /i "%1"=="test-gc" set test_args=%test_args% gc&set buildnodeweak=1&goto arg-ok @@ -318,12 +320,12 @@ echo Failed to build node-weak. goto exit :build-addons -if not defined build_addons goto run-tests +if not defined build_addons goto build-addons-napi if not exist "%node_exe%" ( echo Failed to find node.exe - goto run-tests + goto build-addons-napi ) -echo Building add-ons +echo Building addons :: clear for /d %%F in (test\addons\??_*) do ( rd /s /q %%F @@ -339,6 +341,24 @@ for /d %%F in (test\addons\*) do ( --nodedir="%cd%" if !errorlevel! neq 0 exit /b !errorlevel! ) + +:build-addons-napi +if not defined build_addons_napi goto run-tests +if not exist "%node_exe%" ( + echo Failed to find node.exe + goto run-tests +) +echo Building addons-napi +:: clear +for /d %%F in (test\addons-napi\??_*) do ( + rd /s /q %%F +) +:: building addons-napi +for /d %%F in (test\addons-napi\*) do ( + "%node_exe%" deps\npm\node_modules\node-gyp\bin\node-gyp rebuild ^ + --directory="%%F" ^ + --nodedir="%cd%" +) endlocal goto run-tests