From 43ab5a8b03d05c93054bbdb264a56bae0881523b Mon Sep 17 00:00:00 2001 From: Jason Ginchereau Date: Mon, 20 Mar 2017 14:55:26 -0700 Subject: [PATCH 01/22] module: add support for abi stable module API Add support for abi stable module API (N-API) as "Experimental feature". The goal of this API is to provide a stable Node API for native module developers. N-API aims to provide ABI compatibility guarantees across different Node versions and also across different Node VMs - allowing N-API enabled native modules to just work across different versions and flavors of Node.js without recompilation. A more detailed introduction is provided in: https://github.com/nodejs/node-eps/blob/master/005-ABI-Stable-Module-API.md and https://github.com/nodejs/abi-stable-node/blob/doc/VM%20Summit.pdf. The feature, during its experimental state, will be guarded by a runtime flag "--napi-modules". Only when this flag is added to the command line will N-API modules along with regular non N-API modules be supported. The API is defined by the methods in "src/node_api.h", "src/node_api_types.h" and "src/node_api_async.h". This is the best starting point to review the API surface. More documentation will follow. In addition to the implementation of the API using V8, which is included in this PR, the API has also been validated against chakracore and that port is available in https://github.com/nodejs/abi-stable-node/tree/api-prototype-chakracore-8.x. The current plan is to provide N-API support in versions 8.X and 6.X directly. For older versions, such as 4.X or pre N-API versions of 6.X, we plan to create an external npm module to provide a migration path that will allow modules targeting older Node.js versions to use the API, albeit without getting the advantage of not having to recompile. In addition, we also plan an external npm package with C++ sugar to simplify the use of the API. The sugar will be in-line only and will only use the exported N-API methods but is not part of the N-API itself. The current version is in: https://github.com/nodejs/node-api. This PR is a result of work in the abi-stable-node repo: https://github.com/nodejs/abi-stable-node/tree/doc, with this PR being the cumulative work on the api-prototype-8.x branch with the following contributors in alphabetical order: Arunesh Chandra (@aruneshchandra) Gabriel Schulhof (@gabrielschulhof) Hitesh Kanwathirtha (@digitalInfinity) Ian Halliday (@ianwjhalliday) Jason Ginchereau (@jasongin) Michael Dawson (@mhdawson) Sampson Gao (@sampsongao) Taylor Woll (@boingoing) --- Makefile | 60 +- node.gyp | 8 + src/node.cc | 42 +- src/node_api.cc | 2430 +++++++++++++++++ src/node_api.h | 483 ++++ src/node_api_async.cc | 60 + src/node_api_async.h | 18 + src/node_api_async_internal.h | 20 + src/node_api_async_types.h | 8 + src/node_api_internal.h | 16 + src/node_api_types.h | 93 + test/addons-napi/.gitignore | 7 + test/addons-napi/1_hello_world/binding.cc | 23 + test/addons-napi/1_hello_world/binding.gyp | 8 + test/addons-napi/1_hello_world/test.js | 6 + .../2_function_arguments/binding.cc | 58 + .../2_function_arguments/binding.gyp | 8 + test/addons-napi/2_function_arguments/test.js | 6 + test/addons-napi/3_callbacks/binding.cc | 51 + test/addons-napi/3_callbacks/binding.gyp | 8 + test/addons-napi/3_callbacks/test.js | 27 + test/addons-napi/4_object_factory/binding.cc | 31 + test/addons-napi/4_object_factory/binding.gyp | 8 + test/addons-napi/4_object_factory/test.js | 8 + .../addons-napi/5_function_factory/binding.cc | 37 + .../5_function_factory/binding.gyp | 8 + test/addons-napi/5_function_factory/test.js | 7 + test/addons-napi/6_object_wrap/binding.cc | 7 + test/addons-napi/6_object_wrap/binding.gyp | 8 + test/addons-napi/6_object_wrap/myobject.cc | 199 ++ test/addons-napi/6_object_wrap/myobject.h | 26 + test/addons-napi/6_object_wrap/test.js | 19 + test/addons-napi/7_factory_wrap/binding.cc | 32 + test/addons-napi/7_factory_wrap/binding.gyp | 8 + test/addons-napi/7_factory_wrap/myobject.cc | 107 + test/addons-napi/7_factory_wrap/myobject.h | 26 + test/addons-napi/7_factory_wrap/test.js | 14 + test/addons-napi/8_passing_wrapped/binding.cc | 57 + .../addons-napi/8_passing_wrapped/binding.gyp | 8 + .../addons-napi/8_passing_wrapped/myobject.cc | 79 + test/addons-napi/8_passing_wrapped/myobject.h | 26 + test/addons-napi/8_passing_wrapped/test.js | 9 + test/addons-napi/test_array/binding.gyp | 8 + test/addons-napi/test_array/test.js | 36 + test/addons-napi/test_array/test_array.cc | 137 + test/addons-napi/test_buffer/binding.gyp | 8 + test/addons-napi/test_buffer/test.js | 25 + test/addons-napi/test_buffer/test_buffer.cc | 155 ++ test/addons-napi/test_constructor/binding.gyp | 8 + test/addons-napi/test_constructor/test.js | 28 + .../test_constructor/test_constructor.cc | 105 + test/addons-napi/test_error/binding.gyp | 8 + test/addons-napi/test_error/test.js | 57 + test/addons-napi/test_error/test_error.cc | 41 + test/addons-napi/test_exception/binding.gyp | 8 + test/addons-napi/test_exception/test.js | 52 + .../test_exception/test_exception.cc | 75 + test/addons-napi/test_function/binding.gyp | 8 + test/addons-napi/test_function/test.js | 28 + .../test_function/test_function.cc | 55 + test/addons-napi/test_instanceof/binding.gyp | 8 + test/addons-napi/test_instanceof/test.js | 49 + .../test_instanceof/test_instanceof.cc | 39 + test/addons-napi/test_number/binding.gyp | 8 + test/addons-napi/test_number/test.js | 39 + test/addons-napi/test_number/test_number.cc | 55 + test/addons-napi/test_object/binding.gyp | 8 + test/addons-napi/test_object/test.js | 65 + test/addons-napi/test_object/test_object.cc | 246 ++ test/addons-napi/test_properties/binding.gyp | 8 + test/addons-napi/test_properties/test.js | 27 + .../test_properties/test_properties.cc | 86 + test/addons-napi/test_string/binding.gyp | 8 + test/addons-napi/test_string/test.js | 26 + test/addons-napi/test_string/test_string.cc | 134 + test/addons-napi/test_symbol/binding.gyp | 8 + test/addons-napi/test_symbol/test1.js | 20 + test/addons-napi/test_symbol/test2.js | 15 + test/addons-napi/test_symbol/test3.js | 19 + test/addons-napi/test_symbol/test_symbol.cc | 100 + test/addons-napi/test_typedarray/binding.gyp | 8 + test/addons-napi/test_typedarray/test.js | 39 + .../test_typedarray/test_typedarray.cc | 145 + test/addons-napi/testcfg.py | 6 + test/testpy/__init__.py | 2 +- tools/install.py | 4 + tools/test.py | 1 + vcbuild.bat | 30 +- 88 files changed, 6186 insertions(+), 23 deletions(-) create mode 100644 src/node_api.cc create mode 100644 src/node_api.h create mode 100644 src/node_api_async.cc create mode 100644 src/node_api_async.h create mode 100644 src/node_api_async_internal.h create mode 100644 src/node_api_async_types.h create mode 100644 src/node_api_internal.h create mode 100644 src/node_api_types.h create mode 100644 test/addons-napi/.gitignore create mode 100644 test/addons-napi/1_hello_world/binding.cc create mode 100644 test/addons-napi/1_hello_world/binding.gyp create mode 100644 test/addons-napi/1_hello_world/test.js create mode 100644 test/addons-napi/2_function_arguments/binding.cc create mode 100644 test/addons-napi/2_function_arguments/binding.gyp create mode 100644 test/addons-napi/2_function_arguments/test.js create mode 100644 test/addons-napi/3_callbacks/binding.cc create mode 100644 test/addons-napi/3_callbacks/binding.gyp create mode 100644 test/addons-napi/3_callbacks/test.js create mode 100644 test/addons-napi/4_object_factory/binding.cc create mode 100644 test/addons-napi/4_object_factory/binding.gyp create mode 100644 test/addons-napi/4_object_factory/test.js create mode 100644 test/addons-napi/5_function_factory/binding.cc create mode 100644 test/addons-napi/5_function_factory/binding.gyp create mode 100644 test/addons-napi/5_function_factory/test.js create mode 100644 test/addons-napi/6_object_wrap/binding.cc create mode 100644 test/addons-napi/6_object_wrap/binding.gyp create mode 100644 test/addons-napi/6_object_wrap/myobject.cc create mode 100644 test/addons-napi/6_object_wrap/myobject.h create mode 100644 test/addons-napi/6_object_wrap/test.js create mode 100644 test/addons-napi/7_factory_wrap/binding.cc create mode 100644 test/addons-napi/7_factory_wrap/binding.gyp create mode 100644 test/addons-napi/7_factory_wrap/myobject.cc create mode 100644 test/addons-napi/7_factory_wrap/myobject.h create mode 100644 test/addons-napi/7_factory_wrap/test.js create mode 100644 test/addons-napi/8_passing_wrapped/binding.cc create mode 100644 test/addons-napi/8_passing_wrapped/binding.gyp create mode 100644 test/addons-napi/8_passing_wrapped/myobject.cc create mode 100644 test/addons-napi/8_passing_wrapped/myobject.h create mode 100644 test/addons-napi/8_passing_wrapped/test.js create mode 100644 test/addons-napi/test_array/binding.gyp create mode 100644 test/addons-napi/test_array/test.js create mode 100644 test/addons-napi/test_array/test_array.cc create mode 100644 test/addons-napi/test_buffer/binding.gyp create mode 100644 test/addons-napi/test_buffer/test.js create mode 100644 test/addons-napi/test_buffer/test_buffer.cc create mode 100644 test/addons-napi/test_constructor/binding.gyp create mode 100644 test/addons-napi/test_constructor/test.js create mode 100644 test/addons-napi/test_constructor/test_constructor.cc create mode 100644 test/addons-napi/test_error/binding.gyp create mode 100644 test/addons-napi/test_error/test.js create mode 100644 test/addons-napi/test_error/test_error.cc create mode 100644 test/addons-napi/test_exception/binding.gyp create mode 100644 test/addons-napi/test_exception/test.js create mode 100644 test/addons-napi/test_exception/test_exception.cc create mode 100644 test/addons-napi/test_function/binding.gyp create mode 100644 test/addons-napi/test_function/test.js create mode 100644 test/addons-napi/test_function/test_function.cc create mode 100644 test/addons-napi/test_instanceof/binding.gyp create mode 100644 test/addons-napi/test_instanceof/test.js create mode 100644 test/addons-napi/test_instanceof/test_instanceof.cc create mode 100644 test/addons-napi/test_number/binding.gyp create mode 100644 test/addons-napi/test_number/test.js create mode 100644 test/addons-napi/test_number/test_number.cc create mode 100644 test/addons-napi/test_object/binding.gyp create mode 100644 test/addons-napi/test_object/test.js create mode 100644 test/addons-napi/test_object/test_object.cc create mode 100644 test/addons-napi/test_properties/binding.gyp create mode 100644 test/addons-napi/test_properties/test.js create mode 100644 test/addons-napi/test_properties/test_properties.cc create mode 100644 test/addons-napi/test_string/binding.gyp create mode 100644 test/addons-napi/test_string/test.js create mode 100644 test/addons-napi/test_string/test_string.cc create mode 100644 test/addons-napi/test_symbol/binding.gyp create mode 100644 test/addons-napi/test_symbol/test1.js create mode 100644 test/addons-napi/test_symbol/test2.js create mode 100644 test/addons-napi/test_symbol/test3.js create mode 100644 test/addons-napi/test_symbol/test_symbol.cc create mode 100644 test/addons-napi/test_typedarray/binding.gyp create mode 100644 test/addons-napi/test_typedarray/test.js create mode 100644 test/addons-napi/test_typedarray/test_typedarray.cc create mode 100644 test/addons-napi/testcfg.py diff --git a/Makefile b/Makefile index 410f643c91bd59..7e14e2bf139ddf 100644 --- a/Makefile +++ b/Makefile @@ -193,9 +193,10 @@ v8: test: all $(MAKE) build-addons + $(MAKE) build-addons-napi $(MAKE) cctest $(PYTHON) tools/test.py --mode=release -J \ - addons doctool inspector known_issues message pseudo-tty parallel sequential + addons addons-napi doctool inspector known_issues message pseudo-tty parallel sequential $(MAKE) lint test-parallel: all @@ -262,6 +263,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_async.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 + ifeq ($(OSTYPE),$(filter $(OSTYPE),darwin aix)) XARGS = xargs else @@ -274,7 +310,9 @@ clear-stalled: test-gc: all test/gc/build/Release/binding.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/build/Release/binding.node $(PYTHON) tools/test.py --mode=debug,release @@ -282,12 +320,12 @@ test-all: test-build test/gc/build/Release/binding.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) @@ -304,11 +342,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_NATIVE_SUITES) $(CI_JS_SUITES) + $(TEST_CI_ARGS) $(CI_NATIVE_SUITES) addons-napi $(CI_JS_SUITES) # Clean up any leftover processes PS_OUT=`ps awwx | grep Release/node | grep -v grep | awk '{print $$1}'`; \ if [ "$${PS_OUT}" ]; then \ @@ -355,7 +393,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: @@ -821,6 +862,7 @@ CPPLINT_EXCLUDE += src/node_root_certs.h CPPLINT_EXCLUDE += src/queue.h CPPLINT_EXCLUDE += src/tree.h CPPLINT_EXCLUDE += $(wildcard test/addons/??_*/*.cc test/addons/??_*/*.h) +CPPLINT_EXCLUDE += $(wildcard test/addons-napi/??_*/*.cc test/addons-napi/??_*/*.h) CPPLINT_FILES = $(filter-out $(CPPLINT_EXCLUDE), $(wildcard \ src/*.c \ @@ -830,6 +872,8 @@ CPPLINT_FILES = $(filter-out $(CPPLINT_EXCLUDE), $(wildcard \ test/addons/*/*.h \ test/cctest/*.cc \ test/cctest/*.h \ + test/addons-napi/*/*.cc \ + test/addons-napi/*/*.h \ test/gc/binding.cc \ tools/icu/*.cc \ tools/icu/*.h \ @@ -869,4 +913,4 @@ endif test-v8-intl test-v8-benchmarks test-v8-all v8 lint-ci bench-ci jslint-ci \ doc-only $(TARBALL)-headers test-ci test-ci-native test-ci-js build-ci \ clear-stalled coverage-clean coverage-build coverage-test coverage \ - list-gtests + list-gtests test-addons-napi build-addons-napi diff --git a/node.gyp b/node.gyp index 637a1934287841..81289658360be9 100644 --- a/node.gyp +++ b/node.gyp @@ -163,6 +163,14 @@ 'src/handle_wrap.cc', 'src/js_stream.cc', 'src/node.cc', + 'src/node_api.cc', + 'src/node_api.h', + 'src/node_api_internal.h', + 'src/node_api_types.h', + 'src/node_api_async.cc', + 'src/node_api_async.h', + 'src/node_api_async_internal.h', + 'src/node_api_async_types.h', 'src/node_buffer.cc', 'src/node_config.cc', 'src/node_constants.cc', diff --git a/src/node.cc b/src/node.cc index d79b8ee30edd2b..6d6974db538617 100644 --- a/src/node.cc +++ b/src/node.cc @@ -180,6 +180,9 @@ static const char* trace_enabled_categories = nullptr; std::string icu_data_dir; // NOLINT(runtime/string) #endif +// N-API is in experimental state, disabled by default. +bool load_napi_modules = false; + // used by C++ modules as well bool no_deprecation = false; @@ -2463,15 +2466,24 @@ void DLOpen(const FunctionCallbackInfo& args) { } if (mp->nm_version != NODE_MODULE_VERSION) { char errmsg[1024]; - snprintf(errmsg, - sizeof(errmsg), - "The module '%s'" - "\nwas compiled against a different Node.js version using" - "\nNODE_MODULE_VERSION %d. This version of Node.js requires" - "\nNODE_MODULE_VERSION %d. Please try re-compiling or " - "re-installing\nthe module (for instance, using `npm rebuild` or " - "`npm install`).", - *filename, mp->nm_version, NODE_MODULE_VERSION); + if (mp->nm_version == -1) { + snprintf(errmsg, + sizeof(errmsg), + "The module '%s'" + "\nwas compiled against the Node.js API. This feature is " + "\nexperimental and must be enabled on the command-line.", + *filename); + } else { + snprintf(errmsg, + sizeof(errmsg), + "The module '%s'" + "\nwas compiled against a different Node.js version using" + "\nNODE_MODULE_VERSION %d. This version of Node.js requires" + "\nNODE_MODULE_VERSION %d. Please try re-compiling or " + "re-installing\nthe module (for instance, using `npm rebuild` " + "or `npm install`).", + *filename, mp->nm_version, NODE_MODULE_VERSION); + } // NOTE: `mp` is allocated inside of the shared library's memory, calling // `uv_dlclose` will deallocate it @@ -3535,6 +3547,7 @@ static void PrintHelp() { " --trace-deprecation show stack traces on deprecations\n" " --throw-deprecation throw an exception on deprecations\n" " --no-warnings silence all process warnings\n" + " --napi-modules[=yes|no] load N-API modules (default no)\n" " --trace-warnings show stack traces on process warnings\n" " --redirect-warnings=path\n" " write warnings to path instead of\n" @@ -3705,6 +3718,12 @@ 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) { + load_napi_modules = true; + } else if (strcmp(arg, "--napi-modules=yes") == 0) { + load_napi_modules = true; + } else if (strcmp(arg, "--napi-modules=no") == 0) { + load_napi_modules = false; } else if (strcmp(arg, "--no-warnings") == 0) { no_process_warnings = true; } else if (strcmp(arg, "--trace-warnings") == 0) { @@ -4473,6 +4492,11 @@ inline int Start(Isolate* isolate, IsolateData* isolate_data, if (debug_enabled) EnableDebug(&env); + if (load_napi_modules) { + ProcessEmitWarning(&env, "N-API is an experimental feature " + "and could change at any time."); + } + { SealHandleScope seal(isolate); bool more; diff --git a/src/node_api.cc b/src/node_api.cc new file mode 100644 index 00000000000000..3b10f32e80b060 --- /dev/null +++ b/src/node_api.cc @@ -0,0 +1,2430 @@ +/****************************************************************************** + * Experimental prototype for demonstrating VM agnostic and ABI stable API + * for native modules to use instead of using Nan and V8 APIs directly. + * + * The current status is "Experimental" and should not be used for + * production applications. The API is still subject to change + * and as an experimental feature is NOT subject to semver. + * + ******************************************************************************/ + + +#include +#include +#include +#include +#include +#include "node_api_internal.h" + +namespace v8impl { + +//=== Conversion between V8 Isolate and napi_env ========================== + +napi_env JsEnvFromV8Isolate(v8::Isolate* isolate) { + return reinterpret_cast(isolate); +} + +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) {} + template + v8::Local Escape(v8::Local handle) { + return scope.Escape(handle); + } + + private: + v8::EscapableHandleScope scope; +}; + +napi_handle_scope JsHandleScopeFromV8HandleScope(HandleScopeWrapper* s) { + return reinterpret_cast(s); +} + +HandleScopeWrapper* V8HandleScopeFromJsHandleScope(napi_handle_scope s) { + return reinterpret_cast(s); +} + +napi_escapable_handle_scope JsEscapableHandleScopeFromV8EscapableHandleScope( + EscapableHandleScopeWrapper* s) { + return reinterpret_cast(s); +} + +EscapableHandleScopeWrapper* +V8EscapableHandleScopeFromJsEscapableHandleScope( + napi_escapable_handle_scope s) { + return reinterpret_cast(s); +} + +//=== Conversion between V8 Handles and napi_value ======================== + +// This is assuming v8::Local<> will always be implemented with a single +// pointer field so that we can pass it around as a void* (maybe we should +// use intptr_t instead of void*) + +napi_value JsValueFromV8LocalValue(v8::Local local) { + // This is awkward, better way? memcpy but don't want that dependency? + union U { + napi_value v; + v8::Local l; + U(v8::Local _l) : l(_l) {} + } u(local); + assert(sizeof(u.v) == sizeof(u.l)); + return u.v; +} + +v8::Local V8LocalValueFromJsValue(napi_value v) { + // Likewise awkward + union U { + napi_value v; + v8::Local l; + U(napi_value _v) : v(_v) {} + } u(v); + assert(sizeof(u.v) == sizeof(u.l)); + return u.l; +} + +static v8::Local V8LocalFunctionFromJsValue(napi_value v) { + // Likewise awkward + union U { + napi_value v; + v8::Local f; + U(napi_value _v) : v(_v) {} + } u(v); + assert(sizeof(u.v) == sizeof(u.f)); + return u.f; +} + +// Wrapper around v8::Persistent that implements reference counting. +class Reference { + public: + Reference(v8::Isolate* isolate, + v8::Local value, + int initialRefcount, + bool deleteSelf, + napi_finalize finalizeCallback = nullptr, + void* finalizeData = nullptr, + void* finalizeHint = nullptr) + : _isolate(isolate), + _persistent(isolate, value), + _refcount(initialRefcount), + _deleteSelf(deleteSelf), + _finalizeCallback(finalizeCallback), + _finalizeData(finalizeData), + _finalizeHint(finalizeHint) { + if (initialRefcount == 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(); + } + + int AddRef() { + if (++_refcount == 1) { + _persistent.ClearWeak(); + } + + return _refcount; + } + + int Release() { + if (--_refcount == 0) { + _persistent.SetWeak( + this, FinalizeCallback, v8::WeakCallbackType::kParameter); + _persistent.MarkIndependent(); + } + + return _refcount; + } + + v8::Local Get() { + if (_persistent.IsEmpty()) { + return v8::Local(); + } else { + return v8::Local::New(_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 deleteSelf = reference->_deleteSelf; + + if (reference->_finalizeCallback != nullptr) { + reference->_finalizeCallback(reference->_finalizeData, + reference->_finalizeHint); + } + + if (deleteSelf) { + delete reference; + } + } + + v8::Isolate* _isolate; + v8::Persistent _persistent; + int _refcount; + bool _deleteSelf; + napi_finalize _finalizeCallback; + void* _finalizeData; + void* _finalizeHint; +}; + +class TryCatch : public v8::TryCatch { + public: + explicit TryCatch(v8::Isolate* isolate) + : v8::TryCatch(isolate), _isolate(isolate) {} + + ~TryCatch() { + if (HasCaught()) { + _theException.Reset(_isolate, Exception()); + } + } + + static v8::Persistent& lastException() { return _theException; } + + private: + static v8::Persistent _theException; + v8::Isolate* _isolate; +}; + +v8::Persistent TryCatch::_theException; + +//=== Function napi_callback wrapper ================================= + +static const int kDataIndex = 0; + +static const int kFunctionIndex = 1; +static const int kFunctionFieldCount = 2; + +static const int kGetterIndex = 1; +static const int kSetterIndex = 2; +static const int kAccessorFieldCount = 3; + +// Base class extended by classes that wrap V8 function and property callback +// info. +class CallbackWrapper { + public: + CallbackWrapper(napi_value thisArg, int argsLength, void* data) + : _this(thisArg), _argsLength(argsLength), _data(data) {} + + virtual napi_value Holder() = 0; + virtual bool IsConstructCall() = 0; + virtual void Args(napi_value* buffer, int bufferlength) = 0; + virtual void SetReturnValue(napi_value v) = 0; + + napi_value This() { return _this; } + + int ArgsLength() { return _argsLength; } + + void* Data() { return _data; } + + protected: + const napi_value _this; + const int _argsLength; + void* _data; +}; + +template +class CallbackWrapperBase : public CallbackWrapper { + public: + CallbackWrapperBase(const T& cbinfo, const int argsLength) + : CallbackWrapper(JsValueFromV8LocalValue(cbinfo.This()), + argsLength, + nullptr), + _cbinfo(cbinfo), + _cbdata(v8::Local::Cast(cbinfo.Data())) { + _data = v8::Local::Cast(_cbdata->GetInternalField(kDataIndex)) + ->Value(); + } + + /*virtual*/ + napi_value Holder() override { + return JsValueFromV8LocalValue(_cbinfo.Holder()); + } + + /*virtual*/ + bool IsConstructCall() override { return false; } + + protected: + void InvokeCallback() { + napi_callback_info cbinfo_wrapper = reinterpret_cast( + static_cast(this)); + napi_callback cb = reinterpret_cast( + v8::Local::Cast(_cbdata->GetInternalField(I))->Value()); + v8::Isolate* isolate = _cbinfo.GetIsolate(); + cb(v8impl::JsEnvFromV8Isolate(isolate), cbinfo_wrapper); + + if (!TryCatch::lastException().IsEmpty()) { + isolate->ThrowException( + v8::Local::New(isolate, TryCatch::lastException())); + TryCatch::lastException().Reset(); + } + } + + const T& _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()) {} + + /*virtual*/ + bool IsConstructCall() override { return _cbinfo.IsConstructCall(); } + + /*virtual*/ + void Args(napi_value* buffer, int bufferlength) override { + int i = 0; + int min = std::min(bufferlength, _argsLength); + + for (; i < min; i += 1) { + buffer[i] = v8impl::JsValueFromV8LocalValue(_cbinfo[i]); + } + + if (i < bufferlength) { + napi_value undefined = + v8impl::JsValueFromV8LocalValue(v8::Undefined(_cbinfo.GetIsolate())); + for (; i < bufferlength; i += 1) { + buffer[i] = undefined; + } + } + } + + /*virtual*/ + void SetReturnValue(napi_value v) override { + v8::Local val = v8impl::V8LocalValueFromJsValue(v); + _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, int bufferlength) override { + if (bufferlength > 0) { + napi_value undefined = + v8impl::JsValueFromV8LocalValue(v8::Undefined(_cbinfo.GetIsolate())); + for (int i = 0; i < bufferlength; i += 1) { + buffer[i] = undefined; + } + } + } + + /*virtual*/ + void SetReturnValue(napi_value v) override { + v8::Local val = v8impl::V8LocalValueFromJsValue(v); + _cbinfo.GetReturnValue().Set(val); + } +}; + +class SetterCallbackWrapper + : public CallbackWrapperBase, kSetterIndex> { + public: + static void Invoke(v8::Local property, + v8::Local v, + const v8::PropertyCallbackInfo& info) { + SetterCallbackWrapper cbwrapper(info, v); + cbwrapper.InvokeCallback(); + } + + SetterCallbackWrapper(const v8::PropertyCallbackInfo& cbinfo, + const v8::Local& value) + : CallbackWrapperBase(cbinfo, 1), _value(value) {} + + /*virtual*/ + void Args(napi_value* buffer, int bufferlength) override { + if (bufferlength > 0) { + buffer[0] = v8impl::JsValueFromV8LocalValue(_value); + + if (bufferlength > 1) { + napi_value undefined = v8impl::JsValueFromV8LocalValue( + v8::Undefined(_cbinfo.GetIsolate())); + for (int i = 1; i < bufferlength; i += 1) { + buffer[i] = undefined; + } + } + } + } + + /*virtual*/ + void SetReturnValue(napi_value v) override { + // Cannot set the return value of a setter. + assert(false); + } + + 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. +v8::Local CreateFunctionCallbackData(napi_env e, + napi_callback cb, + void* data) { + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(e); + v8::Local context = isolate->GetCurrentContext(); + + v8::Local otpl = v8::ObjectTemplate::New(isolate); + otpl->SetInternalFieldCount(v8impl::kFunctionFieldCount); + v8::Local cbdata = otpl->NewInstance(context).ToLocalChecked(); + + cbdata->SetInternalField( + v8impl::kFunctionIndex, + v8::External::New(isolate, reinterpret_cast(cb))); + + if (data) { + cbdata->SetInternalField( + v8impl::kDataIndex, + v8::External::New(isolate, reinterpret_cast(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. +v8::Local CreateAccessorCallbackData(napi_env e, + napi_callback getter, + napi_callback setter, + void* data) { + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(e); + v8::Local context = isolate->GetCurrentContext(); + + v8::Local otpl = v8::ObjectTemplate::New(isolate); + otpl->SetInternalFieldCount(v8impl::kAccessorFieldCount); + v8::Local cbdata = otpl->NewInstance(context).ToLocalChecked(); + + if (getter) { + cbdata->SetInternalField( + v8impl::kGetterIndex, + v8::External::New(isolate, reinterpret_cast(getter))); + } + + if (setter) { + cbdata->SetInternalField( + v8impl::kSetterIndex, + v8::External::New(isolate, reinterpret_cast(setter))); + } + + if (data) { + cbdata->SetInternalField( + v8impl::kDataIndex, + v8::External::New(isolate, reinterpret_cast(data))); + } + + return cbdata; +} + +} // 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); + mod->nm_register_func( + v8impl::JsEnvFromV8Isolate(context->GetIsolate()), + v8impl::JsValueFromV8LocalValue(exports), + v8impl::JsValueFromV8LocalValue(module), + mod->nm_priv); +} + +#ifndef EXTERNAL_NAPI +namespace node { + // Indicates whether NAPI was enabled/disabled via the node command-line. + extern bool load_napi_modules; +} +#endif // EXTERNAL_NAPI + +// Registers a NAPI module. +void napi_module_register(napi_module* mod) { + // NAPI modules always work with the current node version. + int moduleVersion = NODE_MODULE_VERSION; + +#ifndef EXTERNAL_NAPI + if (!node::load_napi_modules) { + // NAPI is disabled, so set the module version to -1 to cause the module + // to be unloaded. + moduleVersion = -1; + } +#endif // EXTERNAL_NAPI + + node::node_module* nm = new node::node_module { + moduleVersion, + mod->nm_flags, + nullptr, + mod->nm_filename, + nullptr, + napi_module_register_cb, + mod->nm_modname, + mod, // priv + nullptr, + }; + node::node_module_register(nm); +} + +#define RETURN_STATUS_IF_FALSE(condition, status) \ + do { \ + if (!(condition)) { \ + return napi_set_last_error((status)); \ + } \ + } while (0) + +#define CHECK_ARG(arg) RETURN_STATUS_IF_FALSE((arg), napi_invalid_arg) + +#define CHECK_MAYBE_EMPTY(maybe, status) \ + RETURN_STATUS_IF_FALSE(!((maybe).IsEmpty()), (status)) + +#define CHECK_MAYBE_NOTHING(maybe, status) \ + RETURN_STATUS_IF_FALSE(!((maybe).IsNothing()), (status)) + +// NAPI_PREAMBLE is not wrapped in do..while: tryCatch must have function scope. +#define NAPI_PREAMBLE(e) \ + CHECK_ARG(e); \ + RETURN_STATUS_IF_FALSE(v8impl::TryCatch::lastException().IsEmpty(), \ + napi_pending_exception); \ + napi_clear_last_error(); \ + v8impl::TryCatch tryCatch(v8impl::V8IsolateFromJsEnv((e))) + +#define CHECK_TO_TYPE(type, context, result, src, status) \ + do { \ + auto maybe = v8impl::V8LocalValueFromJsValue((src))->To##type((context)); \ + CHECK_MAYBE_EMPTY(maybe, (status)); \ + result = maybe.ToLocalChecked(); \ + } while (0) + +#define CHECK_TO_OBJECT(context, result, src) \ + CHECK_TO_TYPE(Object, context, result, src, napi_object_expected) + +#define CHECK_TO_STRING(context, result, src) \ + CHECK_TO_TYPE(String, context, result, src, napi_string_expected) + +#define CHECK_TO_NUMBER(context, result, src) \ + CHECK_TO_TYPE(Number, context, result, src, napi_number_expected) + +#define CHECK_TO_BOOL(context, result, src) \ + CHECK_TO_TYPE(Boolean, context, result, src, napi_boolean_expected) + +#define CHECK_NEW_FROM_UTF8_LEN(isolate, result, str, len) \ + do { \ + auto str_maybe = v8::String::NewFromUtf8( \ + (isolate), (str), v8::NewStringType::kInternalized, (len)); \ + CHECK_MAYBE_EMPTY(str_maybe, napi_generic_failure); \ + result = str_maybe.ToLocalChecked(); \ + } while (0) + +#define CHECK_NEW_FROM_UTF8(isolate, result, str) \ + CHECK_NEW_FROM_UTF8_LEN((isolate), (result), (str), -1) + +#define GET_RETURN_STATUS() \ + (!tryCatch.HasCaught() ? napi_ok \ + : napi_set_last_error(napi_pending_exception)) + +// Static last error returned from napi_get_last_error_info +napi_extended_error_info static_last_error = {}; + +// Warning: Keep in-sync with napi_status enum +const char* error_messages[] = {nullptr, + "Invalid pointer passed as argument", + "An object was expected", + "A string was expected", + "A function was expected", + "A number was expected", + "A boolean was expected", + "Unknown failure", + "An exception is pending"}; + +void napi_clear_last_error() { + static_last_error.error_code = napi_ok; + + // TODO(boingoing): Should this be a callback? + static_last_error.engine_error_code = 0; + static_last_error.engine_reserved = nullptr; +} + +const napi_extended_error_info* napi_get_last_error_info() { + static_assert( + sizeof(error_messages) / sizeof(*error_messages) == napi_status_last, + "Count of error messages must match count of error values"); + assert(static_last_error.error_code < napi_status_last); + + // Wait until someone requests the last error information to fetch the error + // message string + static_last_error.error_message = + error_messages[static_last_error.error_code]; + + return &static_last_error; +} + +napi_status napi_set_last_error(napi_status error_code, + uint32_t engine_error_code = 0, + void* engine_reserved = nullptr) { + static_last_error.error_code = error_code; + static_last_error.engine_error_code = engine_error_code; + static_last_error.engine_reserved = engine_reserved; + + return error_code; +} + +napi_status napi_create_function(napi_env e, + const char* utf8name, + napi_callback cb, + void* data, + napi_value* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(result); + + v8::Isolate *isolate = v8impl::V8IsolateFromJsEnv(e); + v8::Local retval; + + v8::EscapableHandleScope scope(isolate); + + v8::Local cbdata = + v8impl::CreateFunctionCallbackData(e, cb, data); + + RETURN_STATUS_IF_FALSE(!cbdata.IsEmpty(), napi_generic_failure); + + v8::Local tpl = v8::FunctionTemplate::New( + isolate, v8impl::FunctionCallbackWrapper::Invoke, cbdata); + + retval = scope.Escape(tpl->GetFunction()); + + if (utf8name) { + v8::Local namestring; + CHECK_NEW_FROM_UTF8(isolate, namestring, utf8name); + retval->SetName(namestring); + } + + *result = v8impl::JsValueFromV8LocalValue(retval); + + return GET_RETURN_STATUS(); +} + +napi_status napi_define_class(napi_env e, + const char* utf8name, + napi_callback constructor, + void* data, + int property_count, + const napi_property_descriptor* properties, + napi_value* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(result); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(e); + + v8::EscapableHandleScope scope(isolate); + v8::Local cbdata = + v8impl::CreateFunctionCallbackData(e, constructor, data); + + RETURN_STATUS_IF_FALSE(!cbdata.IsEmpty(), napi_generic_failure); + + v8::Local tpl = v8::FunctionTemplate::New( + isolate, v8impl::FunctionCallbackWrapper::Invoke, cbdata); + + // we need an internal field to stash the wrapped object + tpl->InstanceTemplate()->SetInternalFieldCount(1); + + v8::Local namestring; + CHECK_NEW_FROM_UTF8(isolate, namestring, utf8name); + tpl->SetClassName(namestring); + + int staticPropertyCount = 0; + for (int i = 0; i < property_count; i++) { + const napi_property_descriptor* p = properties + i; + + if ((p->attributes & napi_static_property) != 0) { + // Static properties are handled separately below. + staticPropertyCount++; + continue; + } + + v8::Local propertyname; + CHECK_NEW_FROM_UTF8(isolate, propertyname, p->utf8name); + + v8::PropertyAttribute attributes = + static_cast(p->attributes); + + // This code is similar to that in napi_define_property(); the + // difference is it applies to a template instead of an object. + if (p->method) { + v8::Local cbdata = + v8impl::CreateFunctionCallbackData(e, p->method, p->data); + + RETURN_STATUS_IF_FALSE(!cbdata.IsEmpty(), napi_generic_failure); + + v8::Local t = + v8::FunctionTemplate::New(isolate, + v8impl::FunctionCallbackWrapper::Invoke, + cbdata, + v8::Signature::New(isolate, tpl)); + t->SetClassName(propertyname); + + tpl->PrototypeTemplate()->Set(propertyname, t, attributes); + } else if (p->getter || p->setter) { + v8::Local cbdata = + v8impl::CreateAccessorCallbackData(e, p->getter, p->setter, p->data); + + tpl->PrototypeTemplate()->SetAccessor( + propertyname, + v8impl::GetterCallbackWrapper::Invoke, + p->setter ? v8impl::SetterCallbackWrapper::Invoke : nullptr, + cbdata, + v8::AccessControl::DEFAULT, + attributes); + } else { + v8::Local value = v8impl::V8LocalValueFromJsValue(p->value); + tpl->PrototypeTemplate()->Set(propertyname, value, attributes); + } + } + + *result = v8impl::JsValueFromV8LocalValue(scope.Escape(tpl->GetFunction())); + + if (staticPropertyCount > 0) { + std::vector staticDescriptors; + staticDescriptors.reserve(staticPropertyCount); + + for (int i = 0; i < property_count; i++) { + const napi_property_descriptor* p = properties + i; + if ((p->attributes & napi_static_property) != 0) { + staticDescriptors.push_back(*p); + } + } + + napi_status status = + napi_define_properties(e, + *result, + static_cast(staticDescriptors.size()), + staticDescriptors.data()); + if (status != napi_ok) return status; + } + + return GET_RETURN_STATUS(); +} + +napi_status napi_set_return_value(napi_env e, + napi_callback_info cbinfo, + napi_value value) { + NAPI_PREAMBLE(e); + + v8impl::CallbackWrapper* info = + reinterpret_cast(cbinfo); + + info->SetReturnValue(value); + return GET_RETURN_STATUS(); +} + +napi_status napi_get_propertynames(napi_env e, + napi_value object, + napi_value* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(result); + + v8::Isolate *isolate = v8impl::V8IsolateFromJsEnv(e); + v8::Local context = isolate->GetCurrentContext(); + v8::Local obj; + CHECK_TO_OBJECT(context, obj, object); + + auto maybe_propertynames = obj->GetPropertyNames(context); + + CHECK_MAYBE_EMPTY(maybe_propertynames, napi_generic_failure); + + *result = v8impl::JsValueFromV8LocalValue( + maybe_propertynames.ToLocalChecked()); + return GET_RETURN_STATUS(); +} + +napi_status napi_set_property(napi_env e, + napi_value object, + napi_value key, + napi_value value) { + NAPI_PREAMBLE(e); + + v8::Isolate *isolate = v8impl::V8IsolateFromJsEnv(e); + v8::Local context = isolate->GetCurrentContext(); + v8::Local obj; + + CHECK_TO_OBJECT(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(set_maybe.FromMaybe(false), napi_generic_failure); + return GET_RETURN_STATUS(); +} + +napi_status napi_has_property(napi_env e, + napi_value object, + napi_value key, + bool* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(result); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(e); + v8::Local context = isolate->GetCurrentContext(); + v8::Local obj; + + CHECK_TO_OBJECT(context, obj, object); + + v8::Local k = v8impl::V8LocalValueFromJsValue(key); + v8::Maybe has_maybe = obj->Has(context, k); + + CHECK_MAYBE_NOTHING(has_maybe, napi_generic_failure); + + *result = has_maybe.FromMaybe(false); + return GET_RETURN_STATUS(); +} + +napi_status napi_get_property(napi_env e, + napi_value object, + napi_value key, + napi_value* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(result); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(e); + v8::Local context = isolate->GetCurrentContext(); + v8::Local k = v8impl::V8LocalValueFromJsValue(key); + v8::Local obj; + + CHECK_TO_OBJECT(context, obj, object); + + auto get_maybe = obj->Get(context, k); + + CHECK_MAYBE_EMPTY(get_maybe, napi_generic_failure); + + v8::Local val = get_maybe.ToLocalChecked(); + *result = v8impl::JsValueFromV8LocalValue(val); + return GET_RETURN_STATUS(); +} + +napi_status napi_set_named_property(napi_env e, + napi_value object, + const char* utf8name, + napi_value value) { + NAPI_PREAMBLE(e); + + v8::Isolate *isolate = v8impl::V8IsolateFromJsEnv(e); + v8::Local context = isolate->GetCurrentContext(); + v8::Local obj; + + CHECK_TO_OBJECT(context, obj, object); + + v8::Local key; + CHECK_NEW_FROM_UTF8(isolate, key, utf8name); + + v8::Local val = v8impl::V8LocalValueFromJsValue(value); + + v8::Maybe set_maybe = obj->Set(context, key, val); + + RETURN_STATUS_IF_FALSE(set_maybe.FromMaybe(false), napi_generic_failure); + return GET_RETURN_STATUS(); +} + +napi_status napi_has_named_property(napi_env e, + napi_value object, + const char* utf8name, + bool* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(result); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(e); + v8::Local context = isolate->GetCurrentContext(); + v8::Local obj; + + CHECK_TO_OBJECT(context, obj, object); + + v8::Local key; + CHECK_NEW_FROM_UTF8(isolate, key, utf8name); + + v8::Maybe has_maybe = obj->Has(context, key); + + CHECK_MAYBE_NOTHING(has_maybe, napi_generic_failure); + + *result = has_maybe.FromMaybe(false); + return GET_RETURN_STATUS(); +} + +napi_status napi_get_named_property(napi_env e, + napi_value object, + const char* utf8name, + napi_value* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(result); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(e); + v8::Local context = isolate->GetCurrentContext(); + + v8::Local key; + CHECK_NEW_FROM_UTF8(isolate, key, utf8name); + + v8::Local obj; + + CHECK_TO_OBJECT(context, obj, object); + + auto get_maybe = obj->Get(context, key); + + CHECK_MAYBE_EMPTY(get_maybe, napi_generic_failure); + + v8::Local val = get_maybe.ToLocalChecked(); + *result = v8impl::JsValueFromV8LocalValue(val); + return GET_RETURN_STATUS(); +} + +napi_status napi_set_element(napi_env e, + napi_value object, + uint32_t index, + napi_value value) { + NAPI_PREAMBLE(e); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(e); + v8::Local context = isolate->GetCurrentContext(); + v8::Local obj; + + CHECK_TO_OBJECT(context, obj, object); + + v8::Local val = v8impl::V8LocalValueFromJsValue(value); + auto set_maybe = obj->Set(context, index, val); + + RETURN_STATUS_IF_FALSE(set_maybe.FromMaybe(false), napi_generic_failure); + + return GET_RETURN_STATUS(); +} + +napi_status napi_has_element(napi_env e, + napi_value object, + uint32_t index, + bool* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(result); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(e); + v8::Local context = isolate->GetCurrentContext(); + v8::Local obj; + + CHECK_TO_OBJECT(context, obj, object); + + v8::Maybe has_maybe = obj->Has(context, index); + + CHECK_MAYBE_NOTHING(has_maybe, napi_generic_failure); + + *result = has_maybe.FromMaybe(false); + return GET_RETURN_STATUS(); +} + +napi_status napi_get_element(napi_env e, + napi_value object, + uint32_t index, + napi_value* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(result); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(e); + v8::Local context = isolate->GetCurrentContext(); + v8::Local obj; + + CHECK_TO_OBJECT(context, obj, object); + + auto get_maybe = obj->Get(context, index); + + CHECK_MAYBE_EMPTY(get_maybe, napi_generic_failure); + + *result = v8impl::JsValueFromV8LocalValue(get_maybe.ToLocalChecked()); + return GET_RETURN_STATUS(); +} + +napi_status napi_define_properties(napi_env e, + napi_value object, + int property_count, + const napi_property_descriptor* properties) { + NAPI_PREAMBLE(e); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(e); + v8::Local context = isolate->GetCurrentContext(); + v8::Local obj = + v8impl::V8LocalValueFromJsValue(object).As(); + + for (int i = 0; i < property_count; i++) { + const napi_property_descriptor* p = &properties[i]; + + v8::Local name; + CHECK_NEW_FROM_UTF8(isolate, name, p->utf8name); + + v8::PropertyAttribute attributes = static_cast( + p->attributes & ~napi_static_property); + + if (p->method) { + v8::Local cbdata = + v8impl::CreateFunctionCallbackData(e, p->method, p->data); + + RETURN_STATUS_IF_FALSE(!cbdata.IsEmpty(), napi_generic_failure); + + v8::Local t = v8::FunctionTemplate::New( + isolate, v8impl::FunctionCallbackWrapper::Invoke, cbdata); + + auto define_maybe = + obj->DefineOwnProperty(context, name, t->GetFunction(), attributes); + + // IsNothing seems like a serious failure, + // should we return a different error code if the define failed? + if (define_maybe.IsNothing() || !define_maybe.FromMaybe(false)) { + return napi_set_last_error(napi_generic_failure); + } + } else if (p->getter || p->setter) { + v8::Local cbdata = + v8impl::CreateAccessorCallbackData(e, p->getter, p->setter, p->data); + + auto set_maybe = obj->SetAccessor( + context, + name, + v8impl::GetterCallbackWrapper::Invoke, + p->setter ? v8impl::SetterCallbackWrapper::Invoke : nullptr, + cbdata, + v8::AccessControl::DEFAULT, + attributes); + + // IsNothing seems like a serious failure, + // should we return a different error code if the set failed? + if (set_maybe.IsNothing() || !set_maybe.FromMaybe(false)) { + return napi_set_last_error(napi_generic_failure); + } + } else { + v8::Local value = v8impl::V8LocalValueFromJsValue(p->value); + + auto define_maybe = + obj->DefineOwnProperty(context, name, value, attributes); + + // IsNothing seems like a serious failure, + // should we return a different error code if the define failed? + if (define_maybe.IsNothing() || !define_maybe.FromMaybe(false)) { + return napi_set_last_error(napi_generic_failure); + } + } + } + + return GET_RETURN_STATUS(); +} + +napi_status napi_is_array(napi_env e, napi_value value, bool* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(result); + + v8::Local val = v8impl::V8LocalValueFromJsValue(value); + + *result = val->IsArray(); + return GET_RETURN_STATUS(); +} + +napi_status napi_get_array_length(napi_env e, + napi_value value, + uint32_t* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(result); + + // TODO(boingoing): Should this also check to see if v is an array before + // blindly casting it? + v8::Local arr = + v8impl::V8LocalValueFromJsValue(value).As(); + + *result = arr->Length(); + return GET_RETURN_STATUS(); +} + +napi_status napi_strict_equals(napi_env e, + napi_value lhs, + napi_value rhs, + bool* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(result); + + v8::Local a = v8impl::V8LocalValueFromJsValue(lhs); + v8::Local b = v8impl::V8LocalValueFromJsValue(rhs); + + *result = a->StrictEquals(b); + return GET_RETURN_STATUS(); +} + +napi_status napi_get_prototype(napi_env e, + napi_value object, + napi_value* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(result); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(e); + v8::Local context = isolate->GetCurrentContext(); + + v8::Local obj; + CHECK_TO_OBJECT(context, obj, object); + + v8::Local val = obj->GetPrototype(); + *result = v8impl::JsValueFromV8LocalValue(val); + return GET_RETURN_STATUS(); +} + +napi_status napi_create_object(napi_env e, napi_value* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(result); + + *result = v8impl::JsValueFromV8LocalValue( + v8::Object::New(v8impl::V8IsolateFromJsEnv(e))); + + return GET_RETURN_STATUS(); +} + +napi_status napi_create_array(napi_env e, napi_value* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(result); + + *result = v8impl::JsValueFromV8LocalValue( + v8::Array::New(v8impl::V8IsolateFromJsEnv(e))); + + return GET_RETURN_STATUS(); +} + +napi_status napi_create_array_with_length(napi_env e, + int length, + napi_value* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(result); + + *result = v8impl::JsValueFromV8LocalValue( + v8::Array::New(v8impl::V8IsolateFromJsEnv(e), length)); + + return GET_RETURN_STATUS(); +} + +napi_status napi_create_string_utf8(napi_env e, + const char* s, + int length, + napi_value* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(result); + + auto isolate = v8impl::V8IsolateFromJsEnv(e); + v8::Local str; + CHECK_NEW_FROM_UTF8_LEN(isolate, str, s, length); + + *result = v8impl::JsValueFromV8LocalValue(str); + return GET_RETURN_STATUS(); +} + +napi_status napi_create_string_utf16(napi_env e, + const char16_t* s, + int length, + napi_value* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(result); + + auto isolate = v8impl::V8IsolateFromJsEnv(e); + auto str_maybe = + v8::String::NewFromTwoByte(isolate, + reinterpret_cast(s), + v8::NewStringType::kInternalized, + length); + CHECK_MAYBE_EMPTY(str_maybe, napi_generic_failure); + v8::Local str = str_maybe.ToLocalChecked(); + + *result = v8impl::JsValueFromV8LocalValue(str); + return GET_RETURN_STATUS(); +} + +napi_status napi_create_number(napi_env e, double v, napi_value* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(result); + + *result = v8impl::JsValueFromV8LocalValue( + v8::Number::New(v8impl::V8IsolateFromJsEnv(e), v)); + + return GET_RETURN_STATUS(); +} + +napi_status napi_create_boolean(napi_env e, bool b, napi_value* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(result); + + *result = v8impl::JsValueFromV8LocalValue( + v8::Boolean::New(v8impl::V8IsolateFromJsEnv(e), b)); + + return GET_RETURN_STATUS(); +} + +napi_status napi_create_symbol(napi_env e, const char* s, napi_value* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(result); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(e); + if (s == nullptr) { + *result = v8impl::JsValueFromV8LocalValue(v8::Symbol::New(isolate)); + } else { + v8::Local string; + CHECK_NEW_FROM_UTF8(isolate, string, s); + + *result = v8impl::JsValueFromV8LocalValue(v8::Symbol::New(isolate, string)); + } + + return GET_RETURN_STATUS(); +} + +napi_status napi_create_error(napi_env e, napi_value msg, napi_value* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(result); + + *result = v8impl::JsValueFromV8LocalValue(v8::Exception::Error( + v8impl::V8LocalValueFromJsValue(msg).As())); + + return GET_RETURN_STATUS(); +} + +napi_status napi_create_type_error(napi_env e, + napi_value msg, + napi_value* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(result); + + *result = v8impl::JsValueFromV8LocalValue(v8::Exception::TypeError( + v8impl::V8LocalValueFromJsValue(msg).As())); + + return GET_RETURN_STATUS(); +} + +napi_status napi_create_range_error(napi_env e, + napi_value msg, + napi_value* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(result); + + *result = v8impl::JsValueFromV8LocalValue(v8::Exception::RangeError( + v8impl::V8LocalValueFromJsValue(msg).As())); + + return GET_RETURN_STATUS(); +} + +napi_status napi_get_type_of_value(napi_env e, + napi_value value, + napi_valuetype* result) { + // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw + // JS exceptions. + CHECK_ARG(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->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 if (v->IsExternal()) { + *result = napi_external; + } else { + *result = napi_object; // Is this correct? + } + + return napi_ok; +} + +napi_status napi_get_undefined(napi_env e, napi_value* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(result); + + *result = v8impl::JsValueFromV8LocalValue( + v8::Undefined(v8impl::V8IsolateFromJsEnv(e))); + + return GET_RETURN_STATUS(); +} + +napi_status napi_get_null(napi_env e, napi_value* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(result); + + *result = + v8impl::JsValueFromV8LocalValue(v8::Null(v8impl::V8IsolateFromJsEnv(e))); + + return GET_RETURN_STATUS(); +} + +napi_status napi_get_false(napi_env e, napi_value* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(result); + + *result = + v8impl::JsValueFromV8LocalValue(v8::False(v8impl::V8IsolateFromJsEnv(e))); + + return GET_RETURN_STATUS(); +} + +napi_status napi_get_true(napi_env e, napi_value* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(result); + + *result = + v8impl::JsValueFromV8LocalValue(v8::True(v8impl::V8IsolateFromJsEnv(e))); + + return GET_RETURN_STATUS(); +} + +// Gets all callback info in a single call. (Ugly, but faster.) +napi_status napi_get_cb_info( + napi_env e, // [in] NAPI environment handle + napi_callback_info cbinfo, // [in] Opaque callback-info handle + int* 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* thisArg, // [out] Receives the JS 'this' arg for the call + void** data) { // [out] Receives the data pointer for the callback. + CHECK_ARG(argc); + CHECK_ARG(argv); + CHECK_ARG(thisArg); + CHECK_ARG(data); + + v8impl::CallbackWrapper* info = + reinterpret_cast(cbinfo); + + info->Args(argv, std::min(*argc, info->ArgsLength())); + *argc = info->ArgsLength(); + *thisArg = info->This(); + *data = info->Data(); + + return napi_ok; +} + +napi_status napi_get_cb_args_length(napi_env e, + napi_callback_info cbinfo, + int* result) { + // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because no V8 APIs are called. + CHECK_ARG(result); + + v8impl::CallbackWrapper* info = + reinterpret_cast(cbinfo); + + *result = info->ArgsLength(); + return napi_ok; +} + +napi_status napi_is_construct_call(napi_env e, + napi_callback_info cbinfo, + bool* result) { + // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because no V8 APIs are called. + CHECK_ARG(result); + + v8impl::CallbackWrapper* info = + reinterpret_cast(cbinfo); + + *result = info->IsConstructCall(); + return napi_ok; +} + +// copy encoded arguments into provided buffer or return direct pointer to +// encoded arguments array? +napi_status napi_get_cb_args(napi_env e, + napi_callback_info cbinfo, + napi_value* buffer, + int bufferlength) { + // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because no V8 APIs are called. + CHECK_ARG(buffer); + + v8impl::CallbackWrapper* info = + reinterpret_cast(cbinfo); + + info->Args(buffer, bufferlength); + return napi_ok; +} + +napi_status napi_get_cb_this(napi_env e, + napi_callback_info cbinfo, + napi_value* result) { + // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because no V8 APIs are called. + CHECK_ARG(result); + + v8impl::CallbackWrapper* info = + reinterpret_cast(cbinfo); + + *result = info->This(); + return napi_ok; +} + +napi_status napi_get_cb_data(napi_env e, + napi_callback_info cbinfo, + void** result) { + // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because no V8 APIs are called. + CHECK_ARG(result); + + v8impl::CallbackWrapper* info = + reinterpret_cast(cbinfo); + + *result = info->Data(); + return napi_ok; +} + +napi_status napi_call_function(napi_env e, + napi_value recv, + napi_value func, + int argc, + const napi_value* argv, + napi_value* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(result); + + std::vector> args(argc); + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(e); + v8::Local context = isolate->GetCurrentContext(); + + v8::Handle v8recv = v8impl::V8LocalValueFromJsValue(recv); + + for (int i = 0; i < argc; i++) { + args[i] = v8impl::V8LocalValueFromJsValue(argv[i]); + } + + v8::Local v8func = v8impl::V8LocalFunctionFromJsValue(func); + auto maybe = v8func->Call(context, v8recv, argc, args.data()); + + if (tryCatch.HasCaught()) { + return napi_set_last_error(napi_pending_exception); + } else { + CHECK_MAYBE_EMPTY(maybe, napi_generic_failure); + *result = v8impl::JsValueFromV8LocalValue(maybe.ToLocalChecked()); + return napi_ok; + } +} + +napi_status napi_get_global(napi_env e, napi_value* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(result); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(e); + // 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 GET_RETURN_STATUS(); +} + +napi_status napi_throw(napi_env e, napi_value error) { + NAPI_PREAMBLE(e); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(e); + + isolate->ThrowException(v8impl::V8LocalValueFromJsValue(error)); + // any VM calls after this point and before returning + // to the javascript invoker will fail + return napi_ok; +} + +napi_status napi_throw_error(napi_env e, const char* msg) { + NAPI_PREAMBLE(e); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(e); + v8::Local str; + CHECK_NEW_FROM_UTF8(isolate, str, msg); + + isolate->ThrowException(v8::Exception::Error(str)); + // any VM calls after this point and before returning + // to the javascript invoker will fail + return napi_ok; +} + +napi_status napi_throw_type_error(napi_env e, const char* msg) { + NAPI_PREAMBLE(e); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(e); + v8::Local str; + CHECK_NEW_FROM_UTF8(isolate, str, msg); + + isolate->ThrowException(v8::Exception::TypeError(str)); + // any VM calls after this point and before returning + // to the javascript invoker will fail + return napi_ok; +} + +napi_status napi_throw_range_error(napi_env e, const char* msg) { + NAPI_PREAMBLE(e); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(e); + v8::Local str; + CHECK_NEW_FROM_UTF8(isolate, str, msg); + + isolate->ThrowException(v8::Exception::RangeError(str)); + // any VM calls after this point and before returning + // to the javascript invoker will fail + return napi_ok; +} + +napi_status napi_is_error(napi_env e, napi_value value, bool* result) { + // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw + // JS exceptions. + CHECK_ARG(result); + + v8::Local val = v8impl::V8LocalValueFromJsValue(value); + *result = val->IsNativeError(); + + return napi_ok; +} + +napi_status napi_get_value_double(napi_env e, + napi_value value, + double* result) { + // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw + // JS exceptions. + CHECK_ARG(result); + + v8::Local val = v8impl::V8LocalValueFromJsValue(value); + RETURN_STATUS_IF_FALSE(val->IsNumber(), napi_number_expected); + + *result = val.As()->Value(); + + return napi_ok; +} + +napi_status napi_get_value_int32(napi_env e, + napi_value value, + int32_t* result) { + // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw + // JS exceptions. + CHECK_ARG(result); + + v8::Local val = v8impl::V8LocalValueFromJsValue(value); + RETURN_STATUS_IF_FALSE(val->IsNumber(), napi_number_expected); + + *result = val.As()->Value(); + + return napi_ok; +} + +napi_status napi_get_value_uint32(napi_env e, + napi_value value, + uint32_t* result) { + // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw + // JS exceptions. + CHECK_ARG(result); + + v8::Local val = v8impl::V8LocalValueFromJsValue(value); + RETURN_STATUS_IF_FALSE(val->IsNumber(), napi_number_expected); + + *result = val.As()->Value(); + + return napi_ok; +} + +napi_status napi_get_value_int64(napi_env e, + napi_value value, + int64_t* result) { + // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw + // JS exceptions. + CHECK_ARG(result); + + v8::Local val = v8impl::V8LocalValueFromJsValue(value); + RETURN_STATUS_IF_FALSE(val->IsNumber(), napi_number_expected); + + *result = val.As()->Value(); + + return napi_ok; +} + +napi_status napi_get_value_bool(napi_env e, napi_value value, bool* result) { + // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw + // JS exceptions. + CHECK_ARG(result); + + v8::Local val = v8impl::V8LocalValueFromJsValue(value); + RETURN_STATUS_IF_FALSE(val->IsBoolean(), napi_boolean_expected); + + *result = val.As()->Value(); + + return napi_ok; +} + +// Gets the number of CHARACTERS in the string. +napi_status napi_get_value_string_length(napi_env e, + napi_value value, + int* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(result); + + v8::Local val = v8impl::V8LocalValueFromJsValue(value); + RETURN_STATUS_IF_FALSE(val->IsString(), napi_string_expected); + + *result = val.As()->Length(); + + return GET_RETURN_STATUS(); +} + +// Gets the number of BYTES in the UTF-8 encoded representation of the string. +napi_status napi_get_value_string_utf8_length(napi_env e, + napi_value value, + int* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(result); + + v8::Local val = v8impl::V8LocalValueFromJsValue(value); + RETURN_STATUS_IF_FALSE(val->IsString(), napi_string_expected); + + *result = val.As()->Utf8Length(); + + return GET_RETURN_STATUS(); +} + +// Copies a JavaScript string into a UTF-8 string buffer. The result is the +// number +// of bytes copied into buf, including the null terminator. If the buf size is +// insufficient, the string will be truncated, including a null terminator. +napi_status napi_get_value_string_utf8(napi_env e, + napi_value value, + char* buf, + int bufsize, + int* result) { + NAPI_PREAMBLE(e); + + v8::Local val = v8impl::V8LocalValueFromJsValue(value); + RETURN_STATUS_IF_FALSE(val->IsString(), napi_string_expected); + + int copied = val.As()->WriteUtf8( + buf, bufsize, nullptr, v8::String::REPLACE_INVALID_UTF8); + + if (result != nullptr) { + *result = copied; + } + + return GET_RETURN_STATUS(); +} + +// Gets the number of 2-byte code units in the UTF-16 encoded representation of +// the string. +napi_status napi_get_value_string_utf16_length(napi_env e, + napi_value value, + int* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(result); + + v8::Local val = v8impl::V8LocalValueFromJsValue(value); + RETURN_STATUS_IF_FALSE(val->IsString(), napi_string_expected); + + // V8 assumes UTF-16 length is the same as the number of characters. + *result = val.As()->Length(); + + return GET_RETURN_STATUS(); +} + +// Copies a JavaScript string into a UTF-16 string buffer. The result is the +// number +// of 2-byte code units copied into buf, including the null terminator. If the +// buf +// size is insufficient, the string will be truncated, including a null +// terminator. +napi_status napi_get_value_string_utf16(napi_env e, + napi_value value, + char16_t* buf, + int bufsize, + int* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(result); + + v8::Local val = v8impl::V8LocalValueFromJsValue(value); + RETURN_STATUS_IF_FALSE(val->IsString(), napi_string_expected); + + int copied = val.As()->Write( + reinterpret_cast(buf), 0, bufsize, v8::String::NO_OPTIONS); + + if (result != nullptr) { + *result = copied; + } + + return GET_RETURN_STATUS(); +} + +napi_status napi_coerce_to_object(napi_env e, + napi_value value, + napi_value* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(result); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(e); + v8::Local context = isolate->GetCurrentContext(); + v8::Local obj; + CHECK_TO_OBJECT(context, obj, value); + + *result = v8impl::JsValueFromV8LocalValue(obj); + return GET_RETURN_STATUS(); +} + +napi_status napi_coerce_to_bool(napi_env e, + napi_value value, + napi_value* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(result); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(e); + v8::Local context = isolate->GetCurrentContext(); + v8::Local b; + + CHECK_TO_BOOL(context, b, value); + + *result = v8impl::JsValueFromV8LocalValue(b); + return GET_RETURN_STATUS(); +} + +napi_status napi_coerce_to_number(napi_env e, + napi_value value, + napi_value* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(result); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(e); + v8::Local context = isolate->GetCurrentContext(); + v8::Local num; + + CHECK_TO_NUMBER(context, num, value); + + *result = v8impl::JsValueFromV8LocalValue(num); + return GET_RETURN_STATUS(); +} + +napi_status napi_coerce_to_string(napi_env e, + napi_value value, + napi_value* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(result); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(e); + v8::Local context = isolate->GetCurrentContext(); + v8::Local str; + + CHECK_TO_STRING(context, str, value); + + *result = v8impl::JsValueFromV8LocalValue(str); + return GET_RETURN_STATUS(); +} + +napi_status napi_wrap(napi_env e, + napi_value jsObject, + void* nativeObj, + napi_finalize finalize_cb, + void* finalize_hint, + napi_ref* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(jsObject); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(e); + v8::Local obj = + v8impl::V8LocalValueFromJsValue(jsObject).As(); + + // Only objects that were created from a NAPI constructor's prototype + // via napi_define_class() can be (un)wrapped. + RETURN_STATUS_IF_FALSE(obj->InternalFieldCount() > 0, napi_invalid_arg); + + obj->SetAlignedPointerInInternalField(0, nativeObj); + + 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(finalize_cb); + v8impl::Reference* reference = new v8impl::Reference( + isolate, obj, 0, false, finalize_cb, nativeObj, finalize_hint); + *result = reinterpret_cast(reference); + } else if (finalize_cb != nullptr) { + // Create a self-deleting reference just for the finalize callback. + new v8impl::Reference( + isolate, obj, 0, true, finalize_cb, nativeObj, finalize_hint); + } + + return GET_RETURN_STATUS(); +} + +napi_status napi_unwrap(napi_env e, napi_value jsObject, void** result) { + // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw + // JS exceptions. + CHECK_ARG(jsObject); + CHECK_ARG(result); + + v8::Local obj = + v8impl::V8LocalValueFromJsValue(jsObject).As(); + + // Only objects that were created from a NAPI constructor's prototype + // via napi_define_class() can be (un)wrapped. + RETURN_STATUS_IF_FALSE(obj->InternalFieldCount() > 0, napi_invalid_arg); + + *result = obj->GetAlignedPointerFromInternalField(0); + + return napi_ok; +} + +napi_status napi_create_external(napi_env e, + void* data, + napi_finalize finalize_cb, + void* finalize_hint, + napi_value* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(result); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(e); + + v8::Local externalValue = v8::External::New(isolate, data); + + // The Reference object will delete itself after invoking the finalizer + // callback. + new v8impl::Reference(isolate, + externalValue, + 0, + true, + finalize_cb, + data, + finalize_hint); + + *result = v8impl::JsValueFromV8LocalValue(externalValue); + + return GET_RETURN_STATUS(); +} + +napi_status napi_get_value_external(napi_env e, + napi_value value, + void** result) { + NAPI_PREAMBLE(e); + CHECK_ARG(value); + CHECK_ARG(result); + + v8::Local val = v8impl::V8LocalValueFromJsValue(value); + RETURN_STATUS_IF_FALSE(val->IsExternal(), napi_invalid_arg); + + v8::Local externalValue = val.As(); + *result = externalValue->Value(); + + return GET_RETURN_STATUS(); +} + +// Set initial_refcount to 0 for a weak reference, >0 for a strong reference. +napi_status napi_create_reference(napi_env e, + napi_value value, + int initial_refcount, + napi_ref* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(result); + RETURN_STATUS_IF_FALSE(initial_refcount >= 0, napi_invalid_arg); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(e); + + v8impl::Reference* reference = new v8impl::Reference( + isolate, v8impl::V8LocalValueFromJsValue(value), initial_refcount, false); + + *result = reinterpret_cast(reference); + return GET_RETURN_STATUS(); +} + +// 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 e, napi_ref ref) { + // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw + // JS exceptions. + CHECK_ARG(ref); + + v8impl::Reference* reference = reinterpret_cast(ref); + delete reference; + + return napi_ok; +} + +// 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_addref(napi_env e, napi_ref ref, int* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(ref); + + v8impl::Reference* reference = reinterpret_cast(ref); + int count = reference->AddRef(); + + if (result != nullptr) { + *result = count; + } + + return GET_RETURN_STATUS(); +} + +// 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_release(napi_env e, napi_ref ref, int* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(ref); + + v8impl::Reference* reference = reinterpret_cast(ref); + int count = reference->Release(); + if (count < 0) { + return napi_set_last_error(napi_generic_failure); + } + + if (result != nullptr) { + *result = count; + } + + return GET_RETURN_STATUS(); +} + +// 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 e, + napi_ref ref, + napi_value* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(ref); + CHECK_ARG(result); + + v8impl::Reference* reference = reinterpret_cast(ref); + *result = v8impl::JsValueFromV8LocalValue(reference->Get()); + + return GET_RETURN_STATUS(); +} + +napi_status napi_open_handle_scope(napi_env e, napi_handle_scope* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(result); + + *result = v8impl::JsHandleScopeFromV8HandleScope( + new v8impl::HandleScopeWrapper(v8impl::V8IsolateFromJsEnv(e))); + return GET_RETURN_STATUS(); +} + +napi_status napi_close_handle_scope(napi_env e, napi_handle_scope scope) { + NAPI_PREAMBLE(e); + CHECK_ARG(scope); + + delete v8impl::V8HandleScopeFromJsHandleScope(scope); + return GET_RETURN_STATUS(); +} + +napi_status napi_open_escapable_handle_scope( + napi_env e, + napi_escapable_handle_scope* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(result); + + *result = v8impl::JsEscapableHandleScopeFromV8EscapableHandleScope( + new v8impl::EscapableHandleScopeWrapper(v8impl::V8IsolateFromJsEnv(e))); + return GET_RETURN_STATUS(); +} + +napi_status napi_close_escapable_handle_scope( + napi_env e, + napi_escapable_handle_scope scope) { + NAPI_PREAMBLE(e); + CHECK_ARG(scope); + + delete v8impl::V8EscapableHandleScopeFromJsEscapableHandleScope(scope); + return GET_RETURN_STATUS(); +} + +napi_status napi_escape_handle(napi_env e, + napi_escapable_handle_scope scope, + napi_value escapee, + napi_value* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(scope); + CHECK_ARG(result); + + v8impl::EscapableHandleScopeWrapper* s = + v8impl::V8EscapableHandleScopeFromJsEscapableHandleScope(scope); + *result = v8impl::JsValueFromV8LocalValue( + s->Escape(v8impl::V8LocalValueFromJsValue(escapee))); + return GET_RETURN_STATUS(); +} + +napi_status napi_new_instance(napi_env e, + napi_value constructor, + int argc, + const napi_value* argv, + napi_value* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(result); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(e); + v8::Local context = isolate->GetCurrentContext(); + + std::vector> args(argc); + for (int i = 0; i < argc; i++) { + args[i] = v8impl::V8LocalValueFromJsValue(argv[i]); + } + + v8::Local v8cons = + v8impl::V8LocalFunctionFromJsValue(constructor); + + auto maybe = v8cons->NewInstance(context, argc, args.data()); + CHECK_MAYBE_EMPTY(maybe, napi_generic_failure); + + *result = v8impl::JsValueFromV8LocalValue(maybe.ToLocalChecked()); + return GET_RETURN_STATUS(); +} + +napi_status napi_instanceof(napi_env e, + napi_value object, + napi_value constructor, + bool* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(result); + + *result = false; + + v8::Local v8Cons; + v8::Local prototypeString; + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(e); + v8::Local context = isolate->GetCurrentContext(); + + CHECK_TO_OBJECT(context, v8Cons, constructor); + + if (!v8Cons->IsFunction()) { + napi_throw_type_error(e, "constructor must be a function"); + + return napi_set_last_error(napi_function_expected); + } + + CHECK_NEW_FROM_UTF8(isolate, prototypeString, "prototype"); + + auto maybe = v8Cons->Get(context, prototypeString); + + CHECK_MAYBE_EMPTY(maybe, napi_generic_failure); + + v8::Local prototypeProperty = maybe.ToLocalChecked(); + + if (!prototypeProperty->IsObject()) { + napi_throw_type_error(e, "constructor prototype must be an object"); + + return napi_set_last_error(napi_object_expected); + } + + v8Cons = prototypeProperty->ToObject(); + + v8::Local v8Obj = v8impl::V8LocalValueFromJsValue(object); + if (!v8Obj->StrictEquals(v8Cons)) { + for (v8::Local originalObj = v8Obj; + !(v8Obj->IsNull() || v8Obj->IsUndefined());) { + if (v8Obj->StrictEquals(v8Cons)) { + *result = !(originalObj->IsNumber() || originalObj->IsBoolean() || + originalObj->IsString()); + break; + } + v8::Local obj; + CHECK_TO_OBJECT(context, obj, v8impl::JsValueFromV8LocalValue(v8Obj)); + v8Obj = obj->GetPrototype(); + } + } + + return GET_RETURN_STATUS(); +} + +napi_status napi_make_callback(napi_env e, + napi_value recv, + napi_value func, + int argc, + const napi_value* argv, + napi_value* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(result); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(e); + v8::Local v8recv = + v8impl::V8LocalValueFromJsValue(recv).As(); + v8::Local v8func = + v8impl::V8LocalValueFromJsValue(func).As(); + std::vector> args(argc); + for (int i = 0; i < argc; i++) { + args[i] = v8impl::V8LocalValueFromJsValue(argv[i]); + } + + v8::Handle retval = + node::MakeCallback(isolate, v8recv, v8func, argc, args.data()); + *result = v8impl::JsValueFromV8LocalValue(retval); + + return GET_RETURN_STATUS(); +} + +// Methods to support catching exceptions +napi_status napi_is_exception_pending(napi_env e, bool* result) { + // NAPI_PREAMBLE is not used here: this function must execute when there is a + // pending exception. + CHECK_ARG(e); + CHECK_ARG(result); + + *result = !v8impl::TryCatch::lastException().IsEmpty(); + return napi_ok; +} + +napi_status napi_get_and_clear_last_exception(napi_env e, napi_value* result) { + // NAPI_PREAMBLE is not used here: this function must execute when there is a + // pending exception. + CHECK_ARG(e); + CHECK_ARG(result); + + // TODO(boingoing): Is there a chance that an exception will be thrown in + // the process of attempting to retrieve the global static exception? + if (v8impl::TryCatch::lastException().IsEmpty()) { + return napi_get_undefined(e, result); + } else { + *result = v8impl::JsValueFromV8LocalValue(v8::Local::New( + v8impl::V8IsolateFromJsEnv(e), v8impl::TryCatch::lastException())); + v8impl::TryCatch::lastException().Reset(); + } + + return napi_ok; +} + +napi_status napi_create_buffer(napi_env e, + size_t size, + void** data, + napi_value* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(data); + CHECK_ARG(result); + + auto maybe = node::Buffer::New(v8impl::V8IsolateFromJsEnv(e), size); + + CHECK_MAYBE_EMPTY(maybe, napi_generic_failure); + + v8::Local jsBuffer = maybe.ToLocalChecked(); + + *result = v8impl::JsValueFromV8LocalValue(jsBuffer); + *data = node::Buffer::Data(jsBuffer); + + return GET_RETURN_STATUS(); +} + +napi_status napi_create_external_buffer(napi_env e, + size_t size, + void* data, + napi_finalize finalize_cb, + void* finalize_hint, + napi_value* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(result); + + auto maybe = node::Buffer::New(v8impl::V8IsolateFromJsEnv(e), + static_cast(data), + size, + (node::Buffer::FreeCallback)finalize_cb, + finalize_hint); + + CHECK_MAYBE_EMPTY(maybe, napi_generic_failure); + + *result = v8impl::JsValueFromV8LocalValue(maybe.ToLocalChecked()); + return GET_RETURN_STATUS(); +} + +napi_status napi_create_buffer_copy(napi_env e, + const void* data, + size_t size, + napi_value* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(result); + + auto maybe = node::Buffer::Copy(v8impl::V8IsolateFromJsEnv(e), + static_cast(data), size); + + CHECK_MAYBE_EMPTY(maybe, napi_generic_failure); + + *result = v8impl::JsValueFromV8LocalValue(maybe.ToLocalChecked()); + return GET_RETURN_STATUS(); +} + +napi_status napi_is_buffer(napi_env e, napi_value value, bool* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(result); + + *result = node::Buffer::HasInstance(v8impl::V8LocalValueFromJsValue(value)); + return GET_RETURN_STATUS(); +} + +napi_status napi_get_buffer_info(napi_env e, + napi_value value, + void** data, + size_t* length) { + NAPI_PREAMBLE(e); + + v8::Local buffer = + v8impl::V8LocalValueFromJsValue(value).As(); + + if (data) { + *data = node::Buffer::Data(buffer); + } + if (length) { + *length = node::Buffer::Length(buffer); + } + + return GET_RETURN_STATUS(); +} + +napi_status napi_is_arraybuffer(napi_env e, napi_value value, bool* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(result); + + v8::Local v8value = v8impl::V8LocalValueFromJsValue(value); + *result = v8value->IsArrayBuffer(); + + return GET_RETURN_STATUS(); +} + +napi_status napi_create_arraybuffer(napi_env e, + size_t byte_length, + void** data, + napi_value* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(result); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(e); + 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(); +} + +napi_status napi_create_external_arraybuffer(napi_env e, + void* external_data, + size_t byte_length, + napi_finalize finalize_cb, + void* finalize_hint, + napi_value* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(result); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(e); + 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. + new v8impl::Reference(isolate, + buffer, + 0, + true, + finalize_cb, + external_data, + finalize_hint); + } + + *result = v8impl::JsValueFromV8LocalValue(buffer); + return GET_RETURN_STATUS(); +} + +napi_status napi_get_arraybuffer_info(napi_env e, + napi_value arraybuffer, + void** data, + size_t* byte_length) { + NAPI_PREAMBLE(e); + + v8::Local value = v8impl::V8LocalValueFromJsValue(arraybuffer); + RETURN_STATUS_IF_FALSE(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 GET_RETURN_STATUS(); +} + +napi_status napi_is_typedarray(napi_env e, napi_value value, bool* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(result); + + v8::Local v8value = v8impl::V8LocalValueFromJsValue(value); + *result = v8value->IsTypedArray(); + + return GET_RETURN_STATUS(); +} + +napi_status napi_create_typedarray(napi_env e, + napi_typedarray_type type, + size_t length, + napi_value arraybuffer, + size_t byte_offset, + napi_value* result) { + NAPI_PREAMBLE(e); + CHECK_ARG(result); + + v8::Local value = v8impl::V8LocalValueFromJsValue(arraybuffer); + RETURN_STATUS_IF_FALSE(value->IsArrayBuffer(), napi_invalid_arg); + + v8::Local buffer = value.As(); + v8::Local typedArray; + + switch (type) { + case napi_int8: + typedArray = v8::Int8Array::New(buffer, byte_offset, length); + break; + case napi_uint8: + typedArray = v8::Uint8Array::New(buffer, byte_offset, length); + break; + case napi_uint8_clamped: + typedArray = v8::Uint8ClampedArray::New(buffer, byte_offset, length); + break; + case napi_int16: + typedArray = v8::Int16Array::New(buffer, byte_offset, length); + break; + case napi_uint16: + typedArray = v8::Uint16Array::New(buffer, byte_offset, length); + break; + case napi_int32: + typedArray = v8::Int32Array::New(buffer, byte_offset, length); + break; + case napi_uint32: + typedArray = v8::Uint32Array::New(buffer, byte_offset, length); + break; + case napi_float32: + typedArray = v8::Float32Array::New(buffer, byte_offset, length); + break; + case napi_float64: + typedArray = v8::Float64Array::New(buffer, byte_offset, length); + break; + default: + return napi_set_last_error(napi_invalid_arg); + } + + *result = v8impl::JsValueFromV8LocalValue(typedArray); + return GET_RETURN_STATUS(); +} + +napi_status napi_get_typedarray_info(napi_env e, + napi_value typedarray, + napi_typedarray_type* type, + size_t* length, + void** data, + napi_value* arraybuffer, + size_t* byte_offset) { + NAPI_PREAMBLE(e); + + v8::Local value = v8impl::V8LocalValueFromJsValue(typedarray); + RETURN_STATUS_IF_FALSE(value->IsTypedArray(), napi_invalid_arg); + + v8::Local array = value.As(); + + if (type != nullptr) { + if (value->IsInt8Array()) { + *type = napi_int8; + } else if (value->IsUint8Array()) { + *type = napi_uint8; + } else if (value->IsUint8ClampedArray()) { + *type = napi_uint8_clamped; + } else if (value->IsInt16Array()) { + *type = napi_int16; + } else if (value->IsUint16Array()) { + *type = napi_uint16; + } else if (value->IsInt32Array()) { + *type = napi_int32; + } else if (value->IsUint32Array()) { + *type = napi_uint32; + } else if (value->IsFloat32Array()) { + *type = napi_float32; + } else if (value->IsFloat64Array()) { + *type = napi_float64; + } + } + + 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 GET_RETURN_STATUS(); +} diff --git a/src/node_api.h b/src/node_api.h new file mode 100644 index 00000000000000..847b5a0a8f6f84 --- /dev/null +++ b/src/node_api.h @@ -0,0 +1,483 @@ +/****************************************************************************** + * Experimental prototype for demonstrating VM agnostic and ABI stable API + * for native modules to use instead of using Nan and V8 APIs directly. + * + * The current status is "Experimental" and should not be used for + * production applications. The API is still subject to change + * and as an experimental feature is NOT subject to semver. + * + ******************************************************************************/ +#ifndef SRC_NODE_API_H_ +#define SRC_NODE_API_H_ + +#include +#include "node_api_types.h" + +#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 + + +namespace node { + +NAPI_EXTERN typedef void (*napi_addon_register_func)(napi_env env, + napi_value exports, + napi_value module, + void* priv); +} // namespace node + +struct napi_module { + int nm_version; + unsigned int nm_flags; + const char* nm_filename; + node::napi_addon_register_func nm_register_func; + const char* nm_modname; + void* nm_priv; + void* reserved[4]; +}; + +#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 + +#define NAPI_MODULE_X(modname, regfunc, priv, flags) \ + extern "C" { \ + static napi_module _module = \ + { \ + NAPI_MODULE_VERSION, \ + flags, \ + __FILE__, \ + regfunc, \ + #modname, \ + priv, \ + {0}, \ + }; \ + NAPI_C_CTOR(_register_ ## modname) { \ + napi_module_register(&_module); \ + } \ + } + +#define NAPI_MODULE(modname, regfunc) \ + NAPI_MODULE_X(modname, regfunc, NULL, 0) + +extern "C" { + +NAPI_EXTERN void napi_module_register(napi_module* mod); + +NAPI_EXTERN const napi_extended_error_info* napi_get_last_error_info(); + +// 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_false(napi_env env, napi_value* result); +NAPI_EXTERN napi_status napi_get_true(napi_env env, napi_value* result); +NAPI_EXTERN napi_status napi_get_global(napi_env env, 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, + int length, + napi_value* result); +NAPI_EXTERN napi_status napi_create_number(napi_env env, + double val, + napi_value* result); +NAPI_EXTERN napi_status napi_create_string_utf8(napi_env env, + const char* s, + int length, + napi_value* result); +NAPI_EXTERN napi_status napi_create_string_utf16(napi_env env, + const char16_t* s, + int length, + napi_value* result); +NAPI_EXTERN napi_status napi_create_boolean(napi_env env, + bool b, + napi_value* result); +NAPI_EXTERN napi_status napi_create_symbol(napi_env env, + const char* s, + napi_value* result); +NAPI_EXTERN napi_status napi_create_function(napi_env env, + const char* utf8name, + napi_callback cb, + void* data, + napi_value* result); +NAPI_EXTERN napi_status napi_create_error(napi_env env, + napi_value msg, + napi_value* result); +NAPI_EXTERN napi_status napi_create_type_error(napi_env env, + napi_value msg, + napi_value* result); +NAPI_EXTERN napi_status napi_create_range_error(napi_env env, + napi_value msg, + napi_value* result); + +// Methods to get the the native napi_value from Primitive type +NAPI_EXTERN napi_status napi_get_type_of_value(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); + +// Gets the number of CHARACTERS in the string. +NAPI_EXTERN napi_status napi_get_value_string_length(napi_env env, + napi_value value, + int* result); + +// Gets the number of BYTES in the UTF-8 encoded representation of the string. +NAPI_EXTERN napi_status napi_get_value_string_utf8_length(napi_env env, + napi_value value, + int* 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, + int bufsize, + int* result); + +// Gets the number of 2-byte code units in the UTF-16 encoded +// representation of the string. +NAPI_EXTERN napi_status napi_get_value_string_utf16_length(napi_env env, + napi_value value, + int* 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, + int bufsize, + int* result); + +// Methods to coerce values +// These APIs may execute user script +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_propertynames(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_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_define_properties(napi_env env, + napi_value object, + int 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, + int argc, + const napi_value* argv, + napi_value* result); +NAPI_EXTERN napi_status napi_new_instance(napi_env env, + napi_value constructor, + int 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); + +// Napi version of node::MakeCallback(...) +NAPI_EXTERN napi_status napi_make_callback(napi_env env, + napi_value recv, + napi_value func, + int argc, + const napi_value* argv, + napi_value* 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 + int* 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* thisArg, // [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_cb_args_length(napi_env env, + napi_callback_info cbinfo, + int* result); +NAPI_EXTERN napi_status napi_get_cb_args(napi_env env, + napi_callback_info cbinfo, + napi_value* buffer, + int bufferlength); +NAPI_EXTERN napi_status napi_get_cb_this(napi_env env, + napi_callback_info cbinfo, + napi_value* result); + +NAPI_EXTERN napi_status napi_get_cb_data(napi_env env, + napi_callback_info cbinfo, + void** result); +NAPI_EXTERN napi_status napi_is_construct_call(napi_env env, + napi_callback_info cbinfo, + bool* result); +NAPI_EXTERN napi_status napi_set_return_value(napi_env env, + napi_callback_info cbinfo, + napi_value value); +NAPI_EXTERN napi_status +napi_define_class(napi_env env, + const char* utf8name, + napi_callback constructor, + void* data, + int 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 jsObject, + void* nativeObj, + napi_finalize finalize_cb, + void* finalize_hint, + napi_ref* result); +NAPI_EXTERN napi_status napi_unwrap(napi_env env, + napi_value jsObject, + 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, + int 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 isunavailable +// results in an error. +NAPI_EXTERN napi_status napi_reference_addref(napi_env env, + napi_ref ref, + int* 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_release(napi_env env, + napi_ref ref, + int* 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); + +// 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* msg); +NAPI_EXTERN napi_status napi_throw_type_error(napi_env env, const char* msg); +NAPI_EXTERN napi_status napi_throw_range_error(napi_env env, 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 size, + void** data, + napi_value* result); +NAPI_EXTERN napi_status napi_create_external_buffer(napi_env env, + size_t size, + void* data, + napi_finalize finalize_cb, + void* finalize_hint, + napi_value* result); +NAPI_EXTERN napi_status napi_create_buffer_copy(napi_env env, + const void* data, + size_t size, + 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); +} // extern "C" + +#endif // SRC_NODE_API_H__ diff --git a/src/node_api_async.cc b/src/node_api_async.cc new file mode 100644 index 00000000000000..baef75358afdd3 --- /dev/null +++ b/src/node_api_async.cc @@ -0,0 +1,60 @@ +#include "node_api_async_internal.h" + +napi_work napi_create_async_work() { + napi_work_impl* worker = + reinterpret_cast(malloc(sizeof(napi_work_impl))); + uv_work_t* req = reinterpret_cast(malloc(sizeof(uv_work_t))); + req->data = worker; + worker->work = req; + return reinterpret_cast(worker); +} + +void napi_delete_async_work(napi_work w) { + napi_work_impl* worker = reinterpret_cast(w); + if (worker != NULL) { + if (worker->work != NULL) { + delete reinterpret_cast(worker->work); + } + delete worker; + worker = NULL; + } +} + +void napi_async_set_data(napi_work w, void* data) { + napi_work_impl* worker = reinterpret_cast(w); + worker->data = data; +} + +void napi_async_set_execute(napi_work w, void (*execute)(void* data)) { + napi_work_impl* worker = reinterpret_cast(w); + worker->execute = execute; +} + +void napi_async_set_complete(napi_work w, void (*complete)(void* data)) { + napi_work_impl* worker = reinterpret_cast(w); + worker->complete = complete; +} + +void napi_async_set_destroy(napi_work w, void (*destroy)(void* data)) { + napi_work_impl* worker = reinterpret_cast(w); + worker->destroy = destroy; +} + +void napi_async_execute(uv_work_t* req) { + napi_work_impl* worker = static_cast(req->data); + worker->execute(worker->data); +} + +void napi_async_complete(uv_work_t* req) { + napi_work_impl* worker = static_cast(req->data); + worker->complete(worker->data); + worker->destroy(worker->data); +} + +void napi_async_queue_worker(napi_work w) { + napi_work_impl* worker = reinterpret_cast(w); + uv_queue_work(uv_default_loop(), + worker->work, + napi_async_execute, + reinterpret_cast(napi_async_complete)); +} diff --git a/src/node_api_async.h b/src/node_api_async.h new file mode 100644 index 00000000000000..c754d348c19c62 --- /dev/null +++ b/src/node_api_async.h @@ -0,0 +1,18 @@ +#ifndef SRC_NODE_API_ASYNC_H_ +#define SRC_NODE_API_ASYNC_H_ + +#include +#include "node_api.h" +#include "node_api_async_types.h" + +extern "C" { +NAPI_EXTERN napi_work napi_create_async_work(); +NAPI_EXTERN void napi_delete_async_work(napi_work w); +NAPI_EXTERN void napi_async_set_data(napi_work w, void* data); +NAPI_EXTERN void napi_async_set_execute(napi_work w, void (*execute)(void*)); +NAPI_EXTERN void napi_async_set_complete(napi_work w, void (*complete)(void*)); +NAPI_EXTERN void napi_async_set_destroy(napi_work w, void (*destroy)(void*)); +NAPI_EXTERN void napi_async_queue_worker(napi_work w); +} // extern "C" + +#endif // SRC_NODE_API_ASYNC_H_ diff --git a/src/node_api_async_internal.h b/src/node_api_async_internal.h new file mode 100644 index 00000000000000..3022a311ed7e4b --- /dev/null +++ b/src/node_api_async_internal.h @@ -0,0 +1,20 @@ +#ifndef SRC_NODE_API_ASYNC_INTERNAL_H_ +#define SRC_NODE_API_ASYNC_INTERNAL_H_ + +#include "node_api_async.h" +#include "uv.h" + +typedef struct napi_work_impl__ { + uv_work_t* work; + void* data; + void (*execute)(void* data); + void (*complete)(void* data); + void (*destroy)(void* data); +} napi_work_impl; + + +void napi_async_execute(uv_work_t* req); +void napi_async_complete(uv_work_t* req); + + +#endif // SRC_NODE_API_ASYNC_INTERNAL_H_ diff --git a/src/node_api_async_types.h b/src/node_api_async_types.h new file mode 100644 index 00000000000000..44e0671c3aed30 --- /dev/null +++ b/src/node_api_async_types.h @@ -0,0 +1,8 @@ +#ifndef SRC_NODE_API_ASYNC_TYPES_H_ +#define SRC_NODE_API_ASYNC_TYPES_H_ + +// LIBUV API types are all opaque pointers for ABI stability +// typedef undefined structs instead of void* for compile time type safety +typedef struct napi_uv_work_t__ *napi_work; + +#endif // SRC_NODE_API_ASYNC_TYPES_H_ diff --git a/src/node_api_internal.h b/src/node_api_internal.h new file mode 100644 index 00000000000000..fa6d808c8040b6 --- /dev/null +++ b/src/node_api_internal.h @@ -0,0 +1,16 @@ +#ifndef SRC_NODE_API_INTERNAL_H_ +#define SRC_NODE_API_INTERNAL_H_ + +#include "node_api.h" +#include "node.h" + +namespace v8impl { + napi_env JsEnvFromV8Isolate(v8::Isolate* isolate); + v8::Isolate* V8IsolateFromJsEnv(napi_env e); + + napi_value JsValueFromV8LocalValue(v8::Local local); + v8::Local V8LocalValueFromJsValue(napi_value v); +} + + +#endif // SRC_NODE_API_INTERNAL_H_ diff --git a/src/node_api_types.h b/src/node_api_types.h new file mode 100644 index 00000000000000..86fd5767c341ce --- /dev/null +++ b/src/node_api_types.h @@ -0,0 +1,93 @@ +#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_info__ *napi_callback_info; + +typedef void (*napi_callback)(napi_env, napi_callback_info); +typedef void (*napi_finalize)(void* finalize_data, void* finalize_hint); + +enum napi_property_attributes { + napi_default = 0, + napi_read_only = 1 << 0, + napi_dont_enum = 1 << 1, + napi_dont_delete = 1 << 2, + + // Used with napi_define_class to distinguish static properties + // from instance properties. Ignored by napi_define_properties. + napi_static_property = 1 << 10, +}; + +struct napi_property_descriptor { + const char* utf8name; + + napi_callback method; + napi_callback getter; + napi_callback setter; + napi_value value; + + napi_property_attributes attributes; + void* data; +}; + +#define DEFAULT_ATTR 0, 0, 0, napi_default, 0 + +enum napi_valuetype { + // ES6 types (corresponds to typeof) + napi_undefined, + napi_null, + napi_boolean, + napi_number, + napi_string, + napi_symbol, + napi_object, + napi_function, + napi_external, +}; + +enum napi_typedarray_type { + napi_int8, + napi_uint8, + napi_uint8_clamped, + napi_int16, + napi_uint16, + napi_int32, + napi_uint32, + napi_float32, + napi_float64, +}; + +enum napi_status { + napi_ok, + napi_invalid_arg, + napi_object_expected, + napi_string_expected, + napi_function_expected, + napi_number_expected, + napi_boolean_expected, + napi_generic_failure, + napi_pending_exception, + napi_status_last +}; + +struct napi_extended_error_info { + const char* error_message; + void* engine_reserved; + uint32_t engine_error_code; + napi_status error_code; +}; + +#endif // SRC_NODE_API_TYPES_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.cc b/test/addons-napi/1_hello_world/binding.cc new file mode 100644 index 00000000000000..6ff76038b8cc70 --- /dev/null +++ b/test/addons-napi/1_hello_world/binding.cc @@ -0,0 +1,23 @@ +#include + +void Method(napi_env env, napi_callback_info info) { + napi_status status; + napi_value world; + status = napi_create_string_utf8(env, "world", -1, &world); + if (status != napi_ok) return; + status = napi_set_return_value(env, info, world); + if (status != napi_ok) return; +} + +#define DECLARE_NAPI_METHOD(name, func) \ + { name, func, 0, 0, 0, napi_default, 0 } + +void Init(napi_env env, napi_value exports, napi_value module, void* priv) { + napi_status status; + napi_property_descriptor desc = DECLARE_NAPI_METHOD("hello", Method); + status = napi_define_properties(env, exports, 1, &desc); + if (status != napi_ok) return; +} + +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..2656c2651a03d1 --- /dev/null +++ b/test/addons-napi/1_hello_world/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "binding", + "sources": [ "binding.cc" ] + } + ] +} 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.cc b/test/addons-napi/2_function_arguments/binding.cc new file mode 100644 index 00000000000000..0b0b2b0c04d171 --- /dev/null +++ b/test/addons-napi/2_function_arguments/binding.cc @@ -0,0 +1,58 @@ +#include + +void Add(napi_env env, napi_callback_info info) { + napi_status status; + + int argc; + status = napi_get_cb_args_length(env, info, &argc); + if (status != napi_ok) return; + + if (argc < 2) { + napi_throw_type_error(env, "Wrong number of arguments"); + return; + } + + napi_value args[2]; + status = napi_get_cb_args(env, info, args, 2); + if (status != napi_ok) return; + + napi_valuetype valuetype0; + status = napi_get_type_of_value(env, args[0], &valuetype0); + if (status != napi_ok) return; + + napi_valuetype valuetype1; + status = napi_get_type_of_value(env, args[1], &valuetype1); + if (status != napi_ok) return; + + if (valuetype0 != napi_number || valuetype1 != napi_number) { + napi_throw_type_error(env, "Wrong arguments"); + return; + } + + double value0; + status = napi_get_value_double(env, args[0], &value0); + if (status != napi_ok) return; + + double value1; + status = napi_get_value_double(env, args[1], &value1); + if (status != napi_ok) return; + + napi_value sum; + status = napi_create_number(env, value0 + value1, &sum); + if (status != napi_ok) return; + + status = napi_set_return_value(env, info, sum); + if (status != napi_ok) return; +} + +#define DECLARE_NAPI_METHOD(name, func) \ + { name, func, 0, 0, 0, napi_default, 0 } + +void Init(napi_env env, napi_value exports, napi_value module, void* priv) { + napi_status status; + napi_property_descriptor addDescriptor = DECLARE_NAPI_METHOD("add", Add); + status = napi_define_properties(env, exports, 1, &addDescriptor); + if (status != napi_ok) return; +} + +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..2656c2651a03d1 --- /dev/null +++ b/test/addons-napi/2_function_arguments/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "binding", + "sources": [ "binding.cc" ] + } + ] +} 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.cc b/test/addons-napi/3_callbacks/binding.cc new file mode 100644 index 00000000000000..8434403a52439b --- /dev/null +++ b/test/addons-napi/3_callbacks/binding.cc @@ -0,0 +1,51 @@ +#include + +void RunCallback(napi_env env, const napi_callback_info info) { + napi_status status; + + napi_value args[1]; + status = napi_get_cb_args(env, info, args, 1); + if (status != napi_ok) return; + + napi_value cb = args[0]; + + napi_value argv[1]; + status = napi_create_string_utf8(env, "hello world", -1, argv); + if (status != napi_ok) return; + + napi_value global; + status = napi_get_global(env, &global); + if (status != napi_ok) return; + + status = napi_call_function(env, global, cb, 1, argv, nullptr); + if (status != napi_ok) return; +} + +void RunCallbackWithRecv(napi_env env, const napi_callback_info info) { + napi_status status; + + napi_value args[2]; + status = napi_get_cb_args(env, info, args, 2); + if (status != napi_ok) return; + + napi_value cb = args[0]; + napi_value recv = args[1]; + + status = napi_call_function(env, recv, cb, 0, nullptr, nullptr); + if (status != napi_ok) return; +} + +#define DECLARE_NAPI_METHOD(name, func) \ + { name, func, 0, 0, 0, napi_default, 0 } + +void Init(napi_env env, napi_value exports, napi_value module, void* priv) { + napi_status status; + napi_property_descriptor desc[2] = { + DECLARE_NAPI_METHOD("RunCallback", RunCallback), + DECLARE_NAPI_METHOD("RunCallbackWithRecv", RunCallbackWithRecv), + }; + status = napi_define_properties(env, exports, 2, desc); + if (status != napi_ok) return; +} + +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..2656c2651a03d1 --- /dev/null +++ b/test/addons-napi/3_callbacks/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "binding", + "sources": [ "binding.cc" ] + } + ] +} diff --git a/test/addons-napi/3_callbacks/test.js b/test/addons-napi/3_callbacks/test.js new file mode 100644 index 00000000000000..e7dca7cbd9b907 --- /dev/null +++ b/test/addons-napi/3_callbacks/test.js @@ -0,0 +1,27 @@ +'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'); +}); + +const global = function() { return this; }.apply(); + +function testRecv(desiredRecv) { + addon.RunCallbackWithRecv(function() { + assert.strictEqual(this, + (desiredRecv === undefined || + desiredRecv === null) ? + global : desiredRecv); + }, desiredRecv); +} + +testRecv(undefined); +testRecv(null); +testRecv(5); +testRecv(true); +testRecv('Hello'); +testRecv([]); +testRecv({}); diff --git a/test/addons-napi/4_object_factory/binding.cc b/test/addons-napi/4_object_factory/binding.cc new file mode 100644 index 00000000000000..75ed56a15c5c2d --- /dev/null +++ b/test/addons-napi/4_object_factory/binding.cc @@ -0,0 +1,31 @@ +#include + +void CreateObject(napi_env env, const napi_callback_info info) { + napi_status status; + + napi_value args[1]; + status = napi_get_cb_args(env, info, args, 1); + if (status != napi_ok) return; + + napi_value obj; + status = napi_create_object(env, &obj); + if (status != napi_ok) return; + + status = napi_set_named_property(env, obj, "msg", args[0]); + if (status != napi_ok) return; + + status = napi_set_return_value(env, info, obj); + if (status != napi_ok) return; +} + +#define DECLARE_NAPI_METHOD(name, func) \ + { name, func, 0, 0, 0, napi_default, 0 } + +void Init(napi_env env, napi_value exports, napi_value module, void* priv) { + napi_status status; + napi_property_descriptor desc = DECLARE_NAPI_METHOD("exports", CreateObject); + status = napi_define_properties(env, module, 1, &desc); + if (status != napi_ok) return; +} + +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..2656c2651a03d1 --- /dev/null +++ b/test/addons-napi/4_object_factory/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "binding", + "sources": [ "binding.cc" ] + } + ] +} 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..db666ff7c9122a --- /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.cc b/test/addons-napi/5_function_factory/binding.cc new file mode 100644 index 00000000000000..bfdd1504759760 --- /dev/null +++ b/test/addons-napi/5_function_factory/binding.cc @@ -0,0 +1,37 @@ +#include + +void MyFunction(napi_env env, napi_callback_info info) { + napi_status status; + + napi_value str; + status = napi_create_string_utf8(env, "hello world", -1, &str); + if (status != napi_ok) return; + + status = napi_set_return_value(env, info, str); + if (status != napi_ok) return; +} + +void CreateFunction(napi_env env, napi_callback_info info) { + napi_status status; + + napi_value fn; + status = napi_create_function(env, "theFunction", MyFunction, nullptr, &fn); + if (status != napi_ok) return; + + status = napi_set_return_value(env, info, fn); + if (status != napi_ok) return; +} + +#define DECLARE_NAPI_METHOD(name, func) \ + { name, func, 0, 0, 0, napi_default, 0 } + +void Init(napi_env env, napi_value exports, napi_value module, void* priv) { + napi_status status; + napi_property_descriptor desc = + DECLARE_NAPI_METHOD("exports", CreateFunction); + status = napi_define_properties(env, module, 1, &desc); + if (status != napi_ok) return; +} + +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..2656c2651a03d1 --- /dev/null +++ b/test/addons-napi/5_function_factory/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "binding", + "sources": [ "binding.cc" ] + } + ] +} 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..99d8339bd09ef3 --- /dev/null +++ b/test/addons-napi/6_object_wrap/binding.cc @@ -0,0 +1,7 @@ +#include "myobject.h" + +void Init(napi_env env, napi_value exports, napi_value module, void* priv) { + MyObject::Init(env, 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..a995df712d69c2 --- /dev/null +++ b/test/addons-napi/6_object_wrap/myobject.cc @@ -0,0 +1,199 @@ +#include "myobject.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(void* nativeObject, void* /*finalize_hint*/) { + reinterpret_cast(nativeObject)->~MyObject(); +} + +#define DECLARE_NAPI_METHOD(name, func) \ + { name, func, 0, 0, 0, napi_default, 0 } + +void MyObject::Init(napi_env env, napi_value exports) { + napi_status status; + napi_property_descriptor properties[] = { + { "value", nullptr, 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, nullptr, 3, properties, &cons); + if (status != napi_ok) return; + + status = napi_create_reference(env, cons, 1, &constructor); + if (status != napi_ok) return; + + status = napi_set_named_property(env, exports, "MyObject", cons); + if (status != napi_ok) return; +} + +void MyObject::New(napi_env env, napi_callback_info info) { + napi_status status; + + bool is_constructor; + status = napi_is_construct_call(env, info, &is_constructor); + if (status != napi_ok) return; + + if (is_constructor) { + // Invoked as constructor: `new MyObject(...)` + napi_value args[1]; + status = napi_get_cb_args(env, info, args, 1); + if (status != napi_ok) return; + + double value = 0; + + napi_valuetype valuetype; + status = napi_get_type_of_value(env, args[0], &valuetype); + if (status != napi_ok) return; + + if (valuetype != napi_undefined) { + status = napi_get_value_double(env, args[0], &value); + if (status != napi_ok) return; + } + + MyObject* obj = new MyObject(value); + + napi_value jsthis; + status = napi_get_cb_this(env, info, &jsthis); + if (status != napi_ok) return; + + obj->env_ = env; + status = napi_wrap(env, + jsthis, + reinterpret_cast(obj), + MyObject::Destructor, + nullptr, // finalize_hint + &obj->wrapper_); + if (status != napi_ok) return; + + status = napi_set_return_value(env, info, jsthis); + if (status != napi_ok) return; + } else { + // Invoked as plain function `MyObject(...)`, turn into construct call. + napi_value args[1]; + status = napi_get_cb_args(env, info, args, 1); + if (status != napi_ok) return; + + const int argc = 1; + napi_value argv[argc] = {args[0]}; + + napi_value cons; + status = napi_get_reference_value(env, constructor, &cons); + if (status != napi_ok) return; + + napi_value instance; + status = napi_new_instance(env, cons, argc, argv, &instance); + if (status != napi_ok) return; + + status = napi_set_return_value(env, info, instance); + if (status != napi_ok) return; + } +} + +void MyObject::GetValue(napi_env env, napi_callback_info info) { + napi_status status; + + napi_value jsthis; + status = napi_get_cb_this(env, info, &jsthis); + if (status != napi_ok) return; + + MyObject* obj; + status = napi_unwrap(env, jsthis, reinterpret_cast(&obj)); + if (status != napi_ok) return; + + napi_value num; + status = napi_create_number(env, obj->value_, &num); + if (status != napi_ok) return; + + status = napi_set_return_value(env, info, num); + if (status != napi_ok) return; +} + +void MyObject::SetValue(napi_env env, napi_callback_info info) { + napi_status status; + + napi_value value; + status = napi_get_cb_args(env, info, &value, 1); + if (status != napi_ok) return; + + napi_value jsthis; + status = napi_get_cb_this(env, info, &jsthis); + if (status != napi_ok) return; + + MyObject* obj; + status = napi_unwrap(env, jsthis, reinterpret_cast(&obj)); + if (status != napi_ok) return; + + status = napi_get_value_double(env, value, &obj->value_); + if (status != napi_ok) return; +} + +void MyObject::PlusOne(napi_env env, napi_callback_info info) { + napi_status status; + + napi_value jsthis; + status = napi_get_cb_this(env, info, &jsthis); + if (status != napi_ok) return; + + MyObject* obj; + status = napi_unwrap(env, jsthis, reinterpret_cast(&obj)); + if (status != napi_ok) return; + + obj->value_ += 1; + + napi_value num; + status = napi_create_number(env, obj->value_, &num); + if (status != napi_ok) return; + + status = napi_set_return_value(env, info, num); + if (status != napi_ok) return; +} + +void MyObject::Multiply(napi_env env, napi_callback_info info) { + napi_status status; + + napi_value args[1]; + status = napi_get_cb_args(env, info, args, 1); + if (status != napi_ok) return; + + napi_valuetype valuetype; + status = napi_get_type_of_value(env, args[0], &valuetype); + if (status != napi_ok) return; + + double multiple = 1; + if (valuetype != napi_undefined) { + status = napi_get_value_double(env, args[0], &multiple); + if (status != napi_ok) return; + } + + napi_value jsthis; + status = napi_get_cb_this(env, info, &jsthis); + if (status != napi_ok) return; + + MyObject* obj; + status = napi_unwrap(env, jsthis, reinterpret_cast(&obj)); + if (status != napi_ok) return; + + napi_value cons; + status = napi_get_reference_value(env, constructor, &cons); + if (status != napi_ok) return; + + const int kArgCount = 1; + napi_value argv[kArgCount]; + status = napi_create_number(env, obj->value_ * multiple, argv); + if (status != napi_ok) return; + + napi_value instance; + status = napi_new_instance(env, cons, kArgCount, argv, &instance); + if (status != napi_ok) return; + + status = napi_set_return_value(env, info, instance); + if (status != napi_ok) return; +} 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..b548594dd95788 --- /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(void* nativeObject, void* finalize_hint); + + private: + explicit MyObject(double value_ = 0); + ~MyObject(); + + static void New(napi_env env, napi_callback_info info); + static void GetValue(napi_env env, napi_callback_info info); + static void SetValue(napi_env env, napi_callback_info info); + static void PlusOne(napi_env env, napi_callback_info info); + static void 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..0aede8cbb8c201 --- /dev/null +++ b/test/addons-napi/7_factory_wrap/binding.cc @@ -0,0 +1,32 @@ +#include "myobject.h" + +void CreateObject(napi_env env, napi_callback_info info) { + napi_status status; + + napi_value args[1]; + status = napi_get_cb_args(env, info, args, 1); + if (status != napi_ok) return; + + napi_value instance; + status = MyObject::NewInstance(env, args[0], &instance); + if (status != napi_ok) return; + + status = napi_set_return_value(env, info, instance); + if (status != napi_ok) return; +} + +#define DECLARE_NAPI_METHOD(name, func) \ + { name, func, 0, 0, 0, napi_default, 0 } + +void Init(napi_env env, napi_value exports, napi_value module, void* priv) { + napi_status status; + + status = MyObject::Init(env); + if (status != napi_ok) return; + + napi_property_descriptor desc = DECLARE_NAPI_METHOD("exports", CreateObject); + status = napi_define_properties(env, module, 1, &desc); + if (status != napi_ok) return; +} + +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..46c8e49b8e22d5 --- /dev/null +++ b/test/addons-napi/7_factory_wrap/myobject.cc @@ -0,0 +1,107 @@ +#include "myobject.h" + +MyObject::MyObject() : env_(nullptr), wrapper_(nullptr) {} + +MyObject::~MyObject() { napi_delete_reference(env_, wrapper_); } + +void MyObject::Destructor(void* nativeObject, void* /*finalize_hint*/) { + reinterpret_cast(nativeObject)->~MyObject(); +} + +#define DECLARE_NAPI_METHOD(name, func) \ + { name, func, 0, 0, 0, napi_default, 0 } + +napi_ref MyObject::constructor; + +napi_status MyObject::Init(napi_env env) { + napi_status status; + napi_property_descriptor properties[] = { + DECLARE_NAPI_METHOD("plusOne", PlusOne), + }; + + napi_value cons; + status = + napi_define_class(env, "MyObject", 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; +} + +void MyObject::New(napi_env env, napi_callback_info info) { + napi_status status; + + napi_value args[1]; + status = napi_get_cb_args(env, info, args, 1); + if (status != napi_ok) return; + + napi_valuetype valuetype; + status = napi_get_type_of_value(env, args[0], &valuetype); + if (status != napi_ok) return; + + MyObject* obj = new MyObject(); + + if (valuetype == napi_undefined) { + obj->counter_ = 0; + } else { + status = napi_get_value_double(env, args[0], &obj->counter_); + if (status != napi_ok) return; + } + + napi_value jsthis; + status = napi_get_cb_this(env, info, &jsthis); + if (status != napi_ok) return; + + obj->env_ = env; + status = napi_wrap(env, + jsthis, + reinterpret_cast(obj), + MyObject::Destructor, + nullptr, /* finalize_hint */ + &obj->wrapper_); + if (status != napi_ok) return; + + status = napi_set_return_value(env, info, jsthis); + if (status != napi_ok) return; +} + +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; +} + +void MyObject::PlusOne(napi_env env, napi_callback_info info) { + napi_status status; + + napi_value jsthis; + status = napi_get_cb_this(env, info, &jsthis); + if (status != napi_ok) return; + + MyObject* obj; + status = napi_unwrap(env, jsthis, reinterpret_cast(&obj)); + if (status != napi_ok) return; + + obj->counter_ += 1; + + napi_value num; + status = napi_create_number(env, obj->counter_, &num); + if (status != napi_ok) return; + + status = napi_set_return_value(env, info, num); + if (status != napi_ok) return; +} 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..6406b1999faca8 --- /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(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 void New(napi_env env, napi_callback_info info); + static void PlusOne(napi_env env, napi_callback_info info); + double 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..d6a4eafd883a45 --- /dev/null +++ b/test/addons-napi/8_passing_wrapped/binding.cc @@ -0,0 +1,57 @@ +#include "myobject.h" + +void CreateObject(napi_env env, napi_callback_info info) { + napi_status status; + + napi_value args[1]; + status = napi_get_cb_args(env, info, args, 1); + if (status != napi_ok) return; + + napi_value instance; + status = MyObject::NewInstance(env, args[0], &instance); + + status = napi_set_return_value(env, info, instance); + if (status != napi_ok) return; +} + +void Add(napi_env env, napi_callback_info info) { + napi_status status; + + napi_value args[2]; + status = napi_get_cb_args(env, info, args, 2); + if (status != napi_ok) return; + + MyObject* obj1; + status = napi_unwrap(env, args[0], reinterpret_cast(&obj1)); + if (status != napi_ok) return; + + MyObject* obj2; + status = napi_unwrap(env, args[1], reinterpret_cast(&obj2)); + if (status != napi_ok) return; + + napi_value sum; + status = napi_create_number(env, obj1->Val() + obj2->Val(), &sum); + if (status != napi_ok) return; + + status = napi_set_return_value(env, info, sum); + if (status != napi_ok) return; +} + +#define DECLARE_NAPI_METHOD(name, func) \ + { name, func, 0, 0, 0, napi_default, 0 } + +void Init(napi_env env, napi_value exports, napi_value module, void* priv) { + napi_status status; + + MyObject::Init(env); + + napi_property_descriptor desc[] = { + DECLARE_NAPI_METHOD("createObject", CreateObject), + DECLARE_NAPI_METHOD("add", Add), + }; + status = + napi_define_properties(env, exports, sizeof(desc) / sizeof(*desc), desc); + if (status != napi_ok) return; +} + +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..126f92c36e350b --- /dev/null +++ b/test/addons-napi/8_passing_wrapped/myobject.cc @@ -0,0 +1,79 @@ +#include "myobject.h" + +MyObject::MyObject() : env_(nullptr), wrapper_(nullptr) {} + +MyObject::~MyObject() { napi_delete_reference(env_, wrapper_); } + +void MyObject::Destructor(void* nativeObject, void* /*finalize_hint*/) { + reinterpret_cast(nativeObject)->~MyObject(); +} + +napi_ref MyObject::constructor; + +napi_status MyObject::Init(napi_env env) { + napi_status status; + + napi_value cons; + status = napi_define_class(env, "MyObject", 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; +} + +void MyObject::New(napi_env env, napi_callback_info info) { + napi_status status; + + napi_value args[1]; + status = napi_get_cb_args(env, info, args, 1); + if (status != napi_ok) return; + + MyObject* obj = new MyObject(); + + napi_valuetype valuetype; + status = napi_get_type_of_value(env, args[0], &valuetype); + if (status != napi_ok) return; + + if (valuetype == napi_undefined) { + obj->val_ = 0; + } else { + status = napi_get_value_double(env, args[0], &obj->val_); + if (status != napi_ok) return; + } + + napi_value jsthis; + status = napi_get_cb_this(env, info, &jsthis); + if (status != napi_ok) return; + + obj->env_ = env; + status = napi_wrap(env, + jsthis, + reinterpret_cast(obj), + MyObject::Destructor, + nullptr, // finalize_hint + &obj->wrapper_); + if (status != napi_ok) return; + + status = napi_set_return_value(env, info, jsthis); + if (status != napi_ok) return; +} + +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..601b5bbccfbd59 --- /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(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 void 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..3d24fa5d9fdaa7 --- /dev/null +++ b/test/addons-napi/8_passing_wrapped/test.js @@ -0,0 +1,9 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); +const addon = require(`./build/${common.buildType}/binding`); + +const obj1 = addon.createObject(10); +const obj2 = addon.createObject(20); +const result = addon.add(obj1, obj2); +assert.strictEqual(result, 30); diff --git a/test/addons-napi/test_array/binding.gyp b/test/addons-napi/test_array/binding.gyp new file mode 100644 index 00000000000000..f277e2893d8197 --- /dev/null +++ b/test/addons-napi/test_array/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "test_array", + "sources": [ "test_array.cc" ] + } + ] +} diff --git a/test/addons-napi/test_array/test.js b/test/addons-napi/test_array/test.js new file mode 100644 index 00000000000000..14e6f2895c0807 --- /dev/null +++ b/test/addons-napi/test_array/test.js @@ -0,0 +1,36 @@ +'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.strictEqual(test_array.Test(array, array.length + 1), + 'Index out of bound!'); + +try { + test_array.Test(array, -2); +} catch (err) { + assert.strictEqual(err.message, 'Invalid index. Expects a positive integer.'); +} + +array.forEach(function(element, index) { + assert.strictEqual(test_array.Test(array, index), element); +}); + + +assert.deepStrictEqual(test_array.New(array), array); diff --git a/test/addons-napi/test_array/test_array.cc b/test/addons-napi/test_array/test_array.cc new file mode 100644 index 00000000000000..7d38abd0e800bd --- /dev/null +++ b/test/addons-napi/test_array/test_array.cc @@ -0,0 +1,137 @@ +#include +#include + +void Test(napi_env env, napi_callback_info info) { + napi_status status; + + int argc; + status = napi_get_cb_args_length(env, info, &argc); + if (status != napi_ok) return; + + if (argc < 2) { + napi_throw_type_error(env, "Wrong number of arguments"); + return; + } + + napi_value args[2]; + status = napi_get_cb_args(env, info, args, 2); + if (status != napi_ok) return; + + napi_valuetype valuetype0; + status = napi_get_type_of_value(env, args[0], &valuetype0); + if (status != napi_ok) return; + + if (valuetype0 != napi_object) { + napi_throw_type_error( + env, "Wrong type of argments. Expects an array as first argument."); + return; + } + + napi_valuetype valuetype1; + status = napi_get_type_of_value(env, args[1], &valuetype1); + if (status != napi_ok) return; + + if (valuetype1 != napi_number) { + napi_throw_type_error( + env, "Wrong type of argments. Expects an integer as second argument."); + return; + } + + napi_value array = args[0]; + int index; + status = napi_get_value_int32(env, args[1], &index); + if (status != napi_ok) return; + + bool isarray; + status = napi_is_array(env, array, &isarray); + if (status != napi_ok) return; + + if (isarray) { + uint32_t size; + status = napi_get_array_length(env, array, &size); + if (status != napi_ok) return; + + if (index >= static_cast(size)) { + napi_value str; + status = napi_create_string_utf8(env, "Index out of bound!", -1, &str); + if (status != napi_ok) return; + + status = napi_set_return_value(env, info, str); + if (status != napi_ok) return; + } else if (index < 0) { + napi_throw_type_error(env, "Invalid index. Expects a positive integer."); + } else { + napi_value ret; + status = napi_get_element(env, array, index, &ret); + if (status != napi_ok) return; + + status = napi_set_return_value(env, info, ret); + if (status != napi_ok) return; + } + } +} + +void New(napi_env env, napi_callback_info info) { + napi_status status; + + int argc; + status = napi_get_cb_args_length(env, info, &argc); + if (status != napi_ok) return; + + if (argc < 1) { + napi_throw_type_error(env, "Wrong number of arguments"); + return; + } + + napi_value args[1]; + status = napi_get_cb_args(env, info, args, 1); + if (status != napi_ok) return; + + napi_valuetype valuetype; + status = napi_get_type_of_value(env, args[0], &valuetype); + if (status != napi_ok) return; + + if (valuetype != napi_object) { + napi_throw_type_error( + env, "Wrong type of argments. Expects an array as first argument."); + return; + } + + napi_value ret; + status = napi_create_array(env, &ret); + if (status != napi_ok) return; + + uint32_t length; + status = napi_get_array_length(env, args[0], &length); + if (status != napi_ok) return; + + for (uint32_t i = 0; i < length; i++) { + napi_value e; + status = napi_get_element(env, args[0], i, &e); + if (status != napi_ok) return; + + status = napi_set_element(env, ret, i, e); + if (status != napi_ok) return; + } + + status = napi_set_return_value(env, info, ret); + if (status != napi_ok) return; +} + +#define DECLARE_NAPI_METHOD(name, func) \ + { name, func, 0, 0, 0, napi_default, 0 } + +void Init(napi_env env, napi_value exports, napi_value module, void* priv) { + napi_status status; + + napi_property_descriptor descriptors[] = { + DECLARE_NAPI_METHOD("Test", Test), + DECLARE_NAPI_METHOD("New", New), + }; + + status = napi_define_properties( + env, exports, sizeof(descriptors) / sizeof(*descriptors), descriptors); + if (status != napi_ok) return; +} + +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..711edd4f792168 --- /dev/null +++ b/test/addons-napi/test_buffer/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "test_buffer", + "sources": [ "test_buffer.cc" ] + } + ] +} diff --git a/test/addons-napi/test_buffer/test.js b/test/addons-napi/test_buffer/test.js new file mode 100644 index 00000000000000..6fb80b02053f9f --- /dev/null +++ b/test/addons-napi/test_buffer/test.js @@ -0,0 +1,25 @@ +'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, + 'buffer returned by newBuffer() has wrong contents'); +assert.strictEqual(binding.newExternalBuffer().toString(), binding.theText, + 'buffer returned by newExternalBuffer() has wrong contents'); +console.log('gc1'); +global.gc(); +assert.strictEqual(binding.getDeleterCallCount(), 1, 'deleter was not called'); +assert.strictEqual(binding.copyBuffer().toString(), binding.theText, + 'buffer returned by copyBuffer() has wrong contents'); + +let buffer = binding.staticBuffer(); +assert.strictEqual(binding.bufferHasInstance(buffer), true, + 'buffer type checking fails'); +assert.strictEqual(binding.bufferInfo(buffer), true, 'buffer data is accurate'); +buffer = null; +global.gc(); +console.log('gc2'); +assert.strictEqual(binding.getDeleterCallCount(), 2, 'deleter was not called'); diff --git a/test/addons-napi/test_buffer/test_buffer.cc b/test/addons-napi/test_buffer/test_buffer.cc new file mode 100644 index 00000000000000..4b781b85ded0bd --- /dev/null +++ b/test/addons-napi/test_buffer/test_buffer.cc @@ -0,0 +1,155 @@ +#include +#include +#include + +#define JS_ASSERT(env, assertion, message) \ + if (!(assertion)) { \ + napi_throw_error( \ + (env), \ + (std::string("assertion (" #assertion ") failed: ") + message) \ + .c_str()); \ + return; \ + } + +#define NAPI_CALL(env, theCall) \ + if (theCall != napi_ok) { \ + const char *errorMessage = napi_get_last_error_info()->error_message; \ + errorMessage = errorMessage ? errorMessage : "empty error message"; \ + napi_throw_error((env), errorMessage); \ + return; \ + } + +static const char theText[] = + "Lorem ipsum dolor sit amet, consectetur adipiscing elit."; + +static int deleterCallCount = 0; +static void deleteTheText(void *data, void* /*finalize_hint*/) { + delete reinterpret_cast(data); + deleterCallCount++; +} + +static void noopDeleter(void *data, void* /*finalize_hint*/) { + deleterCallCount++; +} + +void 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), + reinterpret_cast(&theCopy), + &theBuffer)); + JS_ASSERT(env, theCopy, "Failed to copy static text for newBuffer"); + snprintf(theCopy, kBufferSize, "%s", theText); + NAPI_CALL(env, napi_set_return_value(env, info, theBuffer)); +} + +void newExternalBuffer(napi_env env, napi_callback_info info) { + napi_value theBuffer; + char *theCopy = strdup(theText); + JS_ASSERT(env, theCopy, "Failed to copy static text for newExternalBuffer"); + NAPI_CALL(env, + napi_create_external_buffer( + env, + sizeof(theText), + theCopy, + deleteTheText, + nullptr, // finalize_hint + &theBuffer)); + NAPI_CALL(env, napi_set_return_value(env, info, theBuffer)); +} + +void getDeleterCallCount(napi_env env, napi_callback_info info) { + napi_value callCount; + NAPI_CALL(env, napi_create_number(env, deleterCallCount, &callCount)); + NAPI_CALL(env, napi_set_return_value(env, info, callCount)); +} + +void copyBuffer(napi_env env, napi_callback_info info) { + napi_value theBuffer; + NAPI_CALL(env, + napi_create_buffer_copy(env, theText, sizeof(theText), &theBuffer)); + NAPI_CALL(env, napi_set_return_value(env, info, theBuffer)); +} + +void bufferHasInstance(napi_env env, napi_callback_info info) { + int argc; + NAPI_CALL(env, napi_get_cb_args_length(env, info, &argc)); + JS_ASSERT(env, argc == 1, "Wrong number of arguments"); + napi_value theBuffer; + NAPI_CALL(env, napi_get_cb_args(env, info, &theBuffer, 1)); + bool hasInstance; + napi_valuetype theType; + NAPI_CALL(env, napi_get_type_of_value(env, theBuffer, &theType)); + JS_ASSERT(env, + theType == napi_object, + "bufferHasInstance: instance is not an object"); + NAPI_CALL(env, napi_is_buffer(env, theBuffer, &hasInstance)); + JS_ASSERT(env, hasInstance, "bufferHasInstance: instance is not a buffer"); + napi_value returnValue; + NAPI_CALL(env, napi_create_boolean(env, hasInstance, &returnValue)); + NAPI_CALL(env, napi_set_return_value(env, info, returnValue)); +} + +void bufferInfo(napi_env env, napi_callback_info info) { + int argc; + NAPI_CALL(env, napi_get_cb_args_length(env, info, &argc)); + JS_ASSERT(env, argc == 1, "Wrong number of arguments"); + napi_value theBuffer, returnValue; + NAPI_CALL(env, napi_get_cb_args(env, info, &theBuffer, 1)); + char *bufferData; + size_t bufferLength; + NAPI_CALL(env, + napi_get_buffer_info( + env, + theBuffer, + reinterpret_cast(&bufferData), + &bufferLength)); + NAPI_CALL(env, napi_create_boolean(env, + !strcmp(bufferData, theText) && bufferLength == sizeof(theText), + &returnValue)); + NAPI_CALL(env, napi_set_return_value(env, info, returnValue)); +} + +void staticBuffer(napi_env env, napi_callback_info info) { + napi_value theBuffer; + NAPI_CALL( + env, + napi_create_external_buffer(env, + sizeof(theText), + const_cast(theText), + noopDeleter, + nullptr, // finalize_hint + &theBuffer)); + NAPI_CALL(env, napi_set_return_value(env, info, theBuffer)); +} + +#define DECLARE_NAPI_METHOD(name, func) \ + { name, func, 0, 0, 0, napi_default, 0 } + +void Init(napi_env env, napi_value exports, napi_value module, void* priv) { + 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_METHOD("newBuffer", newBuffer), + DECLARE_NAPI_METHOD("newExternalBuffer", newExternalBuffer), + DECLARE_NAPI_METHOD("getDeleterCallCount", getDeleterCallCount), + DECLARE_NAPI_METHOD("copyBuffer", copyBuffer), + DECLARE_NAPI_METHOD("bufferHasInstance", bufferHasInstance), + DECLARE_NAPI_METHOD("bufferInfo", bufferInfo), + DECLARE_NAPI_METHOD("staticBuffer", staticBuffer), + }; + NAPI_CALL(env, + napi_define_properties( + env, exports, sizeof(methods) / sizeof(methods[0]), methods)); +} + +NAPI_MODULE(addon, Init) diff --git a/test/addons-napi/test_constructor/binding.gyp b/test/addons-napi/test_constructor/binding.gyp new file mode 100644 index 00000000000000..0ea8f877380721 --- /dev/null +++ b/test/addons-napi/test_constructor/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "test_constructor", + "sources": [ "test_constructor.cc" ] + } + ] +} diff --git a/test/addons-napi/test_constructor/test.js b/test/addons-napi/test_constructor/test.js new file mode 100644 index 00000000000000..9feae5a1360b2c --- /dev/null +++ b/test/addons-napi/test_constructor/test.js @@ -0,0 +1,28 @@ +'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; }); + +assert.ok(test_object.hiddenValue); + +// All properties except 'hiddenValue' should be enumerable. +const propertyNames = []; +for (const name in test_object) { + propertyNames.push(name); +} +assert.ok(propertyNames.indexOf('echo') >= 0); +assert.ok(propertyNames.indexOf('readwriteValue') >= 0); +assert.ok(propertyNames.indexOf('readonlyValue') >= 0); +assert.ok(propertyNames.indexOf('hiddenValue') < 0); diff --git a/test/addons-napi/test_constructor/test_constructor.cc b/test/addons-napi/test_constructor/test_constructor.cc new file mode 100644 index 00000000000000..900f74b4bedfce --- /dev/null +++ b/test/addons-napi/test_constructor/test_constructor.cc @@ -0,0 +1,105 @@ +#include + +static double value_ = 1; +napi_ref constructor_; + +void GetValue(napi_env env, napi_callback_info info) { + napi_status status; + + int argc; + status = napi_get_cb_args_length(env, info, &argc); + if (status != napi_ok) return; + + if (argc != 0) { + napi_throw_type_error(env, "Wrong number of arguments"); + return; + } + + napi_value number; + status = napi_create_number(env, value_, &number); + if (status != napi_ok) return; + + status = napi_set_return_value(env, info, number); + if (status != napi_ok) return; +} + +void SetValue(napi_env env, napi_callback_info info) { + napi_status status; + + int argc; + status = napi_get_cb_args_length(env, info, &argc); + if (status != napi_ok) return; + + if (argc != 1) { + napi_throw_type_error(env, "Wrong number of arguments"); + return; + } + + napi_value arg; + status = napi_get_cb_args(env, info, &arg, 1); + if (status != napi_ok) return; + + status = napi_get_value_double(env, arg, &value_); + if (status != napi_ok) return; +} + +void Echo(napi_env env, napi_callback_info info) { + napi_status status; + + int argc; + status = napi_get_cb_args_length(env, info, &argc); + if (status != napi_ok) return; + + if (argc != 1) { + napi_throw_type_error(env, "Wrong number of arguments"); + return; + } + + napi_value arg; + status = napi_get_cb_args(env, info, &arg, 1); + if (status != napi_ok) return; + + status = napi_set_return_value(env, info, arg); + if (status != napi_ok) return; +} + +void New(napi_env env, napi_callback_info info) { + napi_status status; + + napi_value jsthis; + status = napi_get_cb_this(env, info, &jsthis); + if (status != napi_ok) return; + + status = napi_set_return_value(env, info, jsthis); + if (status != napi_ok) return; +} + +void Init(napi_env env, napi_value exports, napi_value module, void* priv) { + napi_status status; + + napi_value number; + status = napi_create_number(env, value_, &number); + if (status != napi_ok) return; + + napi_property_descriptor properties[] = { + { "echo", Echo, 0, 0, 0, napi_default, 0 }, + { "accessorValue", 0, GetValue, SetValue, 0, napi_default, 0}, + { "readwriteValue", 0, 0, 0, number, napi_default, 0 }, + { "readonlyValue", 0, 0, 0, number, napi_read_only, 0}, + { "hiddenValue", 0, 0, 0, number, static_cast( + napi_read_only | napi_dont_enum), 0}, + }; + + napi_value cons; + status = napi_define_class(env, "MyObject", New, + nullptr, sizeof(properties)/sizeof(*properties), properties, &cons); + if (status != napi_ok) return; + + status = napi_set_named_property(env, module, "exports", cons); + if (status != napi_ok) return; + + status = napi_create_reference(env, cons, 1, &constructor_); + if (status != napi_ok) return; +} + +NAPI_MODULE(addon, Init) 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..521c29250d5ffa --- /dev/null +++ b/test/addons-napi/test_error/test.js @@ -0,0 +1,57 @@ +'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, + 'Error object correctly classed by napi_is_error'); + +// Test that native type error object is correctly classed +assert.strictEqual(test_error.checkError(theTypeError), true, + 'Type error object correctly classed by napi_is_error'); + +// Test that native syntax error object is correctly classed +assert.strictEqual(test_error.checkError(theSyntaxError), true, + 'Syntax error object correctly classed by napi_is_error'); + +// Test that native range error object is correctly classed +assert.strictEqual(test_error.checkError(theRangeError), true, + 'Range error object correctly classed by napi_is_error'); + +// Test that native reference error object is correctly classed +assert.strictEqual(test_error.checkError(theReferenceError), true, + 'Reference error object correctly classed by' + + ' napi_is_error'); + +// Test that native URI error object is correctly classed +assert.strictEqual(test_error.checkError(theURIError), true, + 'URI error object correctly classed by napi_is_error'); + +// Test that native eval error object is correctly classed +assert.strictEqual(test_error.checkError(theEvalError), true, + 'Eval error object correctly classed by napi_is_error'); + +// Test that class derived from native error is correctly classed +assert.strictEqual(test_error.checkError(myError), true, + 'Class derived from native error correctly classed by' + + ' napi_is_error'); + +// Test that non-error object is correctly classed +assert.strictEqual(test_error.checkError({}), false, + 'Non-error object correctly classed by napi_is_error'); + +// Test that non-error primitive is correctly classed +assert.strictEqual(test_error.checkError('non-object'), false, + 'Non-error primitive correctly classed by napi_is_error'); 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..c63e60d0a6589f --- /dev/null +++ b/test/addons-napi/test_error/test_error.cc @@ -0,0 +1,41 @@ +#include + +void checkError(napi_env e, napi_callback_info info) { + napi_status status; + napi_value jsError; + + status = napi_get_cb_args(e, info, &jsError, 1); + if (status != napi_ok) return; + + bool r; + status = napi_is_error(e, jsError, &r); + if (status != napi_ok) return; + + napi_value result; + if (r) { + status = napi_get_true(e, &result); + } else { + status = napi_get_false(e, &result); + } + if (status != napi_ok) return; + + status = napi_set_return_value(e, info, result); + if (status != napi_ok) return; +} + +#define DECLARE_NAPI_METHOD(name, func) \ + { name, func, 0, 0, 0, napi_default, 0 } + +void Init(napi_env env, napi_value exports, napi_value module, void* priv) { + napi_status status; + + napi_property_descriptor descriptors[] = { + DECLARE_NAPI_METHOD("checkError", checkError), + }; + + status = napi_define_properties( + env, exports, sizeof(descriptors) / sizeof(*descriptors), descriptors); + if (status != napi_ok) return; +} + +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..3024b2bff951e6 --- /dev/null +++ b/test/addons-napi/test_exception/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "test_exception", + "sources": [ "test_exception.cc" ] + } + ] +} diff --git a/test/addons-napi/test_exception/test.js b/test/addons-napi/test_exception/test.js new file mode 100644 index 00000000000000..f8fc05c71f1c42 --- /dev/null +++ b/test/addons-napi/test_exception/test.js @@ -0,0 +1,52 @@ +'use strict'; + +const common = require('../../common'); +const test_exception = require(`./build/${common.buildType}/test_exception`); +const assert = require('assert'); +const theError = new Error('Some error'); +const throwTheError = function() { + throw theError; +}; +let caughtError; + +const throwNoError = function() {}; + +// Test that the native side successfully captures the exception +let returnedError = test_exception.returnException(throwTheError); +assert.strictEqual(theError, returnedError, + 'Returned error is strictly equal to the thrown error'); + +// Test that the native side passes the exception through +try { + test_exception.allowException(throwTheError); +} catch (anError) { + caughtError = anError; +} +assert.strictEqual(caughtError, theError, + 'Thrown exception was allowed to pass through unhindered'); +caughtError = undefined; + +// Test that the exception thrown above was marked as pending +// before it was handled on the JS side +assert.strictEqual(test_exception.wasPending(), true, + 'VM was marked as having an exception pending' + + ' when it was allowed through'); + +// Test that the native side does not capture a non-existing exception +returnedError = test_exception.returnException(throwNoError); +assert.strictEqual(undefined, returnedError, + 'Returned error is undefined when no exception is thrown'); + +// Test that no exception appears that was not thrown by us +try { + test_exception.allowException(throwNoError); +} catch (anError) { + caughtError = anError; +} +assert.strictEqual(undefined, caughtError, + 'No exception originated on the native side'); + +// Test that the exception state remains clear when no exception is thrown +assert.strictEqual(test_exception.wasPending(), false, + 'VM was not marked as having an exception pending' + + ' when none was allowed through'); diff --git a/test/addons-napi/test_exception/test_exception.cc b/test/addons-napi/test_exception/test_exception.cc new file mode 100644 index 00000000000000..cd14988622f1ba --- /dev/null +++ b/test/addons-napi/test_exception/test_exception.cc @@ -0,0 +1,75 @@ +#include + +static bool exceptionWasPending = false; + +void returnException(napi_env env, napi_callback_info info) { + napi_status status; + napi_value jsFunction; + + status = napi_get_cb_args(env, info, &jsFunction, 1); + if (status != napi_ok) return; + + napi_value global; + status = napi_get_global(env, &global); + if (status != napi_ok) return; + + napi_value result; + status = napi_call_function(env, global, jsFunction, 0, 0, &result); + if (status == napi_pending_exception) { + napi_value ex; + status = napi_get_and_clear_last_exception(env, &ex); + if (status != napi_ok) return; + + status = napi_set_return_value(env, info, ex); + if (status != napi_ok) return; + } +} + +void allowException(napi_env env, napi_callback_info info) { + napi_status status; + napi_value jsFunction; + + status = napi_get_cb_args(env, info, &jsFunction, 1); + if (status != napi_ok) return; + + napi_value global; + status = napi_get_global(env, &global); + if (status != napi_ok) return; + + napi_value result; + status = napi_call_function(env, global, jsFunction, 0, 0, &result); + // Ignore status and check napi_is_exception_pending() instead. + + status = napi_is_exception_pending(env, &exceptionWasPending); + if (status != napi_ok) return; +} + +void wasPending(napi_env env, napi_callback_info info) { + napi_status status; + + napi_value result; + status = napi_create_boolean(env, exceptionWasPending, &result); + if (status != napi_ok) return; + + status = napi_set_return_value(env, info, result); + if (status != napi_ok) return; +} + +#define DECLARE_NAPI_METHOD(name, func) \ + { name, func, 0, 0, 0, napi_default, 0 } + +void Init(napi_env env, napi_value exports, napi_value module, void* priv) { + napi_status status; + + napi_property_descriptor descriptors[] = { + DECLARE_NAPI_METHOD("returnException", returnException), + DECLARE_NAPI_METHOD("allowException", allowException), + DECLARE_NAPI_METHOD("wasPending", wasPending), + }; + + status = napi_define_properties( + env, exports, sizeof(descriptors) / sizeof(*descriptors), descriptors); + if (status != napi_ok) return; +} + +NAPI_MODULE(addon, 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..e58e6e098530d0 --- /dev/null +++ b/test/addons-napi/test_function/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "test_function", + "sources": [ "test_function.cc" ] + } + ] +} diff --git a/test/addons-napi/test_function/test.js b/test/addons-napi/test_function/test.js new file mode 100644 index 00000000000000..bdb9133adf9346 --- /dev/null +++ b/test/addons-napi/test_function/test.js @@ -0,0 +1,28 @@ +'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.Test(func1), 1); + +function func2() { + console.log('hello world!'); + return null; +} +assert.strictEqual(test_function.Test(func2), null); + +function func3(input) { + return input + 1; +} +assert.strictEqual(test_function.Test(func3, 1), 2); + +function func4(input) { + return func3(input); +} +assert.strictEqual(test_function.Test(func4, 1), 2); diff --git a/test/addons-napi/test_function/test_function.cc b/test/addons-napi/test_function/test_function.cc new file mode 100644 index 00000000000000..b450afe2c55f02 --- /dev/null +++ b/test/addons-napi/test_function/test_function.cc @@ -0,0 +1,55 @@ +#include + +void Test(napi_env env, napi_callback_info info) { + napi_status status; + + int argc; + status = napi_get_cb_args_length(env, info, &argc); + if (status != napi_ok) return; + + if (argc < 1) { + napi_throw_type_error(env, "Wrong number of arguments"); + return; + } + + napi_value args[10]; + status = napi_get_cb_args(env, info, args, 10); + if (status != napi_ok) return; + + napi_valuetype valuetype; + status = napi_get_type_of_value(env, args[0], &valuetype); + if (status != napi_ok) return; + + if (valuetype != napi_function) { + napi_throw_type_error(env, "Wrong type of argments. Expects a function."); + return; + } + + napi_value function = args[0]; + napi_value* argv = args + 1; + argc = argc - 1; + + napi_value global; + status = napi_get_global(env, &global); + if (status != napi_ok) return; + + napi_value result; + status = napi_call_function(env, global, function, argc, argv, &result); + if (status != napi_ok) return; + + status = napi_set_return_value(env, info, result); + if (status != napi_ok) return; +} + +void Init(napi_env env, napi_value exports, napi_value module, void* priv) { + napi_status status; + + napi_value fn; + status = napi_create_function(env, nullptr, Test, nullptr, &fn); + if (status != napi_ok) return; + + status = napi_set_named_property(env, exports, "Test", fn); + if (status != napi_ok) return; +} + +NAPI_MODULE(addon, Init) diff --git a/test/addons-napi/test_instanceof/binding.gyp b/test/addons-napi/test_instanceof/binding.gyp new file mode 100644 index 00000000000000..0519df52e91ae2 --- /dev/null +++ b/test/addons-napi/test_instanceof/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "test_instanceof", + "sources": [ "test_instanceof.cc" ] + } + ] +} diff --git a/test/addons-napi/test_instanceof/test.js b/test/addons-napi/test_instanceof/test.js new file mode 100644 index 00000000000000..9500d864dd8df6 --- /dev/null +++ b/test/addons-napi/test_instanceof/test.js @@ -0,0 +1,49 @@ +'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_instanceof`); +const path = require('path'); + +// 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) { + const contents = fs.readFileSync(fileName, { encoding: 'utf8' }); + eval(contents.replace(/[(]([^\s(]+)\s+instanceof\s+([^)]+)[)]/g, + '(addon.doInstanceOf($1, $2))')); +} + +testFile( + path.join(path.resolve(__dirname, '..', '..', '..', + 'deps', 'v8', 'test', 'mjsunit'), + 'instanceof.js')); +testFile( + path.join(path.resolve(__dirname, '..', '..', '..', + 'deps', 'v8', 'test', 'mjsunit'), + 'instanceof-2.js')); diff --git a/test/addons-napi/test_instanceof/test_instanceof.cc b/test/addons-napi/test_instanceof/test_instanceof.cc new file mode 100644 index 00000000000000..8873f5df87b872 --- /dev/null +++ b/test/addons-napi/test_instanceof/test_instanceof.cc @@ -0,0 +1,39 @@ +#include +#include + +void doInstanceOf(napi_env env, napi_callback_info info) { + napi_status status; + + napi_value arguments[2]; + + status = napi_get_cb_args(env, info, arguments, 2); + if (status != napi_ok) return; + + bool instanceof; + status = napi_instanceof(env, arguments[0], arguments[1], &instanceof); + if (status != napi_ok) return; + + napi_value result; + status = napi_create_boolean(env, instanceof, &result); + if (status != napi_ok) return; + + status = napi_set_return_value(env, info, result); + if (status != napi_ok) return; +} + +#define DECLARE_NAPI_METHOD(name, func) \ + { name, func, 0, 0, 0, napi_default, 0 } + +void Init(napi_env env, napi_value exports, napi_value module, void* priv) { + napi_status status; + + napi_property_descriptor descriptors[] = { + DECLARE_NAPI_METHOD("doInstanceOf", doInstanceOf), + }; + + status = napi_define_properties( + env, exports, sizeof(descriptors) / sizeof(*descriptors), descriptors); + if (status != napi_ok) return; +} + +NAPI_MODULE(addon, Init) diff --git a/test/addons-napi/test_number/binding.gyp b/test/addons-napi/test_number/binding.gyp new file mode 100644 index 00000000000000..b04e027b5d7d88 --- /dev/null +++ b/test/addons-napi/test_number/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "test_number", + "sources": [ "test_number.cc" ] + } + ] +} diff --git a/test/addons-napi/test_number/test.js b/test/addons-napi/test_number/test.js new file mode 100644 index 00000000000000..885b9b599d9f25 --- /dev/null +++ b/test/addons-napi/test_number/test.js @@ -0,0 +1,39 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); +const test_number = require(`./build/${common.buildType}/test_number`); + + +// testing api calls for number +assert.strictEqual(0, test_number.Test(0)); +assert.strictEqual(1, test_number.Test(1)); +assert.strictEqual(-1, test_number.Test(-1)); +assert.strictEqual(100, test_number.Test(100)); +assert.strictEqual(2121, test_number.Test(2121)); +assert.strictEqual(-1233, test_number.Test(-1233)); +assert.strictEqual(986583, test_number.Test(986583)); +assert.strictEqual(-976675, test_number.Test(-976675)); + +const num1 = 98765432213456789876546896323445679887645323232436587988766545658; +assert.strictEqual(num1, test_number.Test(num1)); + +const num2 = -4350987086545760976737453646576078997096876957864353245245769809; +assert.strictEqual(num2, test_number.Test(num2)); + +const num3 = Number.MAX_SAFE_INTEGER; +assert.strictEqual(num3, test_number.Test(num3)); + +const num4 = Number.MAX_SAFE_INTEGER + 10; +assert.strictEqual(num4, test_number.Test(num4)); + +const num5 = Number.MAX_VALUE; +assert.strictEqual(num5, test_number.Test(num5)); + +const num6 = Number.MAX_VALUE + 10; +assert.strictEqual(num6, test_number.Test(num6)); + +const num7 = Number.POSITIVE_INFINITY; +assert.strictEqual(num7, test_number.Test(num7)); + +const num8 = Number.NEGATIVE_INFINITY; +assert.strictEqual(num8, test_number.Test(num8)); diff --git a/test/addons-napi/test_number/test_number.cc b/test/addons-napi/test_number/test_number.cc new file mode 100644 index 00000000000000..6b7cb0f7c9a5e1 --- /dev/null +++ b/test/addons-napi/test_number/test_number.cc @@ -0,0 +1,55 @@ +#include + +void Test(napi_env env, napi_callback_info info) { + napi_status status; + + int argc; + status = napi_get_cb_args_length(env, info, &argc); + if (status != napi_ok) return; + + if (argc < 1) { + napi_throw_type_error(env, "Wrong number of arguments"); + return; + } + + napi_value args[1]; + status = napi_get_cb_args(env, info, args, 1); + if (status != napi_ok) return; + + napi_valuetype valuetype; + status = napi_get_type_of_value(env, args[0], &valuetype); + if (status != napi_ok) return; + + if (valuetype != napi_number) { + napi_throw_type_error(env, "Wrong type of argments. Expects a number."); + return; + } + + double input; + status = napi_get_value_double(env, args[0], &input); + if (status != napi_ok) return; + + napi_value output; + status = napi_create_number(env, input, &output); + if (status != napi_ok) return; + + status = napi_set_return_value(env, info, output); + if (status != napi_ok) return; +} + +#define DECLARE_NAPI_METHOD(name, func) \ + { name, func, 0, 0, 0, napi_default, 0 } + +void Init(napi_env env, napi_value exports, napi_value module, void* priv) { + napi_status status; + + napi_property_descriptor descriptors[] = { + DECLARE_NAPI_METHOD("Test", Test), + }; + + status = napi_define_properties( + env, exports, sizeof(descriptors) / sizeof(*descriptors), descriptors); + if (status != napi_ok) return; +} + +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..961735aad1ddb3 --- /dev/null +++ b/test/addons-napi/test_object/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "test_object", + "sources": [ "test_object.cc" ] + } + ] +} diff --git a/test/addons-napi/test_object/test.js b/test/addons-napi/test_object/test.js new file mode 100644 index 00000000000000..d14a15421feef6 --- /dev/null +++ b/test/addons-napi/test_object/test.js @@ -0,0 +1,65 @@ +'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'); + +// 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); diff --git a/test/addons-napi/test_object/test_object.cc b/test/addons-napi/test_object/test_object.cc new file mode 100644 index 00000000000000..ce0a95e4b4b74b --- /dev/null +++ b/test/addons-napi/test_object/test_object.cc @@ -0,0 +1,246 @@ +#include + +void Get(napi_env env, napi_callback_info info) { + napi_status status; + + int argc; + status = napi_get_cb_args_length(env, info, &argc); + if (status != napi_ok) return; + + if (argc < 2) { + napi_throw_type_error(env, "Wrong number of arguments"); + return; + } + + napi_value args[2]; + status = napi_get_cb_args(env, info, args, 2); + if (status != napi_ok) return; + + napi_valuetype valuetype0; + status = napi_get_type_of_value(env, args[0], &valuetype0); + if (status != napi_ok) return; + + if (valuetype0 != napi_object) { + napi_throw_type_error( + env, "Wrong type of argments. Expects an object as first argument."); + return; + } + + napi_valuetype valuetype1; + status = napi_get_type_of_value(env, args[1], &valuetype1); + if (status != napi_ok) return; + + if (valuetype1 != napi_string && valuetype1 != napi_symbol) { + napi_throw_type_error(env, + "Wrong type of argments. Expects a string or symbol as second."); + return; + } + + napi_value object = args[0]; + napi_value output; + status = napi_get_property(env, object, args[1], &output); + if (status != napi_ok) return; + + status = napi_set_return_value(env, info, output); + if (status != napi_ok) return; +} + +void Set(napi_env env, napi_callback_info info) { + napi_status status; + + int argc; + status = napi_get_cb_args_length(env, info, &argc); + if (status != napi_ok) return; + + if (argc < 3) { + napi_throw_type_error(env, "Wrong number of arguments"); + return; + } + + napi_value args[3]; + status = napi_get_cb_args(env, info, args, 3); + if (status != napi_ok) return; + + napi_valuetype valuetype0; + status = napi_get_type_of_value(env, args[0], &valuetype0); + if (status != napi_ok) return; + + if (valuetype0 != napi_object) { + napi_throw_type_error(env, + "Wrong type of argments. Expects an object as first argument."); + return; + } + + napi_valuetype valuetype1; + status = napi_get_type_of_value(env, args[1], &valuetype1); + if (status != napi_ok) return; + + if (valuetype1 != napi_string && valuetype1 != napi_symbol) { + napi_throw_type_error(env, + "Wrong type of argments. Expects a string or symbol as second."); + return; + } + + napi_value object = args[0]; + status = napi_set_property(env, object, args[1], args[2]); + if (status != napi_ok) return; + + napi_value valuetrue; + status = napi_get_true(env, &valuetrue); + if (status != napi_ok) return; + + status = napi_set_return_value(env, info, valuetrue); + if (status != napi_ok) return; +} + +void Has(napi_env env, napi_callback_info info) { + napi_status status; + + int argc; + status = napi_get_cb_args_length(env, info, &argc); + if (status != napi_ok) return; + + if (argc < 2) { + napi_throw_type_error(env, "Wrong number of arguments"); + return; + } + + napi_value args[2]; + status = napi_get_cb_args(env, info, args, 2); + if (status != napi_ok) return; + + napi_valuetype valuetype0; + status = napi_get_type_of_value(env, args[0], &valuetype0); + if (status != napi_ok) return; + + if (valuetype0 != napi_object) { + napi_throw_type_error( + env, "Wrong type of argments. Expects an object as first argument."); + return; + } + + napi_valuetype valuetype1; + status = napi_get_type_of_value(env, args[1], &valuetype1); + if (status != napi_ok) return; + + if (valuetype1 != napi_string && valuetype1 != napi_symbol) { + napi_throw_type_error(env, + "Wrong type of argments. Expects a string or symbol as second."); + return; + } + + napi_value obj = args[0]; + bool has_property; + status = napi_has_property(env, obj, args[1], &has_property); + if (status != napi_ok) return; + + napi_value ret; + status = napi_create_boolean(env, has_property, &ret); + if (status != napi_ok) return; + + status = napi_set_return_value(env, info, ret); + if (status != napi_ok) return; +} + +void New(napi_env env, napi_callback_info info) { + napi_status status; + + napi_value ret; + status = napi_create_object(env, &ret); + + napi_value num; + status = napi_create_number(env, 987654321, &num); + if (status != napi_ok) return; + + status = napi_set_named_property(env, ret, "test_number", num); + if (status != napi_ok) return; + + napi_value str; + status = napi_create_string_utf8(env, "test string", -1, &str); + if (status != napi_ok) return; + + status = napi_set_named_property(env, ret, "test_string", str); + if (status != napi_ok) return; + + status = napi_set_return_value(env, info, ret); + if (status != napi_ok) return; +} + +void Inflate(napi_env env, napi_callback_info info) { + napi_status status; + + int argc; + status = napi_get_cb_args_length(env, info, &argc); + if (status != napi_ok) return; + + if (argc < 1) { + napi_throw_type_error(env, "Wrong number of arguments"); + return; + } + + napi_value args[1]; + status = napi_get_cb_args(env, info, args, 1); + if (status != napi_ok) return; + + napi_valuetype valuetype; + status = napi_get_type_of_value(env, args[0], &valuetype); + if (status != napi_ok) return; + + if (valuetype != napi_object) { + napi_throw_type_error( + env, "Wrong type of argments. Expects an object as first argument."); + return; + } + + napi_value obj = args[0]; + + napi_value propertynames; + status = napi_get_propertynames(env, obj, &propertynames); + if (status != napi_ok) return; + + uint32_t length; + status = napi_get_array_length(env, propertynames, &length); + if (status != napi_ok) return; + + for (uint32_t i = 0; i < length; i++) { + napi_value property_str; + status = napi_get_element(env, propertynames, i, &property_str); + if (status != napi_ok) return; + + napi_value value; + status = napi_get_property(env, obj, property_str, &value); + if (status != napi_ok) return; + + double double_val; + status = napi_get_value_double(env, value, &double_val); + if (status != napi_ok) return; + + status = napi_create_number(env, double_val + 1, &value); + if (status != napi_ok) return; + + status = napi_set_property(env, obj, property_str, value); + if (status != napi_ok) return; + } + status = napi_set_return_value(env, info, obj); +} + +#define DECLARE_NAPI_METHOD(name, func) \ + { name, func, 0, 0, 0, napi_default, 0 } + +void Init(napi_env env, napi_value exports, napi_value module, void* priv) { + napi_status status; + + napi_property_descriptor descriptors[] = { + DECLARE_NAPI_METHOD("Get", Get), + DECLARE_NAPI_METHOD("Set", Set), + DECLARE_NAPI_METHOD("Has", Has), + DECLARE_NAPI_METHOD("New", New), + DECLARE_NAPI_METHOD("Inflate", Inflate), + }; + + status = napi_define_properties( + env, exports, sizeof(descriptors) / sizeof(*descriptors), descriptors); + if (status != napi_ok) return; +} + +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..ddcfb41da06e56 --- /dev/null +++ b/test/addons-napi/test_properties/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "test_properties", + "sources": [ "test_properties.cc" ] + } + ] +} diff --git a/test/addons-napi/test_properties/test.js b/test/addons-napi/test_properties/test.js new file mode 100644 index 00000000000000..8e19903dcfe6e6 --- /dev/null +++ b/test/addons-napi/test_properties/test.js @@ -0,0 +1,27 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); + +// 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; }); + +assert.ok(test_object.hiddenValue); + +// All properties except 'hiddenValue' should be enumerable. +const propertyNames = []; +for (const name in test_object) { + propertyNames.push(name); +} +assert.ok(propertyNames.indexOf('echo') >= 0); +assert.ok(propertyNames.indexOf('readwriteValue') >= 0); +assert.ok(propertyNames.indexOf('readonlyValue') >= 0); +assert.ok(propertyNames.indexOf('hiddenValue') < 0); diff --git a/test/addons-napi/test_properties/test_properties.cc b/test/addons-napi/test_properties/test_properties.cc new file mode 100644 index 00000000000000..0d055c9ecea616 --- /dev/null +++ b/test/addons-napi/test_properties/test_properties.cc @@ -0,0 +1,86 @@ +#include + +static double value_ = 1; + +void GetValue(napi_env env, napi_callback_info info) { + napi_status status; + + int argc; + status = napi_get_cb_args_length(env, info, &argc); + if (status != napi_ok) return; + + if (argc != 0) { + napi_throw_type_error(env, "Wrong number of arguments"); + return; + } + + napi_value number; + status = napi_create_number(env, value_, &number); + if (status != napi_ok) return; + + status = napi_set_return_value(env, info, number); + if (status != napi_ok) return; +} + +void SetValue(napi_env env, napi_callback_info info) { + napi_status status; + + int argc; + status = napi_get_cb_args_length(env, info, &argc); + if (status != napi_ok) return; + + if (argc != 1) { + napi_throw_type_error(env, "Wrong number of arguments"); + return; + } + + napi_value arg; + status = napi_get_cb_args(env, info, &arg, 1); + if (status != napi_ok) return; + + status = napi_get_value_double(env, arg, &value_); + if (status != napi_ok) return; +} + +void Echo(napi_env env, napi_callback_info info) { + napi_status status; + + int argc; + status = napi_get_cb_args_length(env, info, &argc); + if (status != napi_ok) return; + + if (argc != 1) { + napi_throw_type_error(env, "Wrong number of arguments"); + return; + } + + napi_value arg; + status = napi_get_cb_args(env, info, &arg, 1); + if (status != napi_ok) return; + + status = napi_set_return_value(env, info, arg); + if (status != napi_ok) return; +} + +void Init(napi_env env, napi_value exports, napi_value module, void* priv) { + napi_status status; + + napi_value number; + status = napi_create_number(env, value_, &number); + if (status != napi_ok) return; + + napi_property_descriptor properties[] = { + { "echo", Echo, 0, 0, 0, napi_default, 0 }, + { "accessorValue", 0, GetValue, SetValue, 0, napi_default, 0 }, + { "readwriteValue", 0, 0, 0, number, napi_default, 0 }, + { "readonlyValue", 0, 0, 0, number, napi_read_only, 0 }, + { "hiddenValue", 0, 0, 0, number, static_cast( + napi_read_only | napi_dont_enum), 0 }, + }; + + status = napi_define_properties( + env, exports, sizeof(properties) / sizeof(*properties), properties); + if (status != napi_ok) return; +} + +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..dd7ea0fa776d7e --- /dev/null +++ b/test/addons-napi/test_string/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "test_string", + "sources": [ "test_string.cc" ] + } + ] +} diff --git a/test/addons-napi/test_string/test.js b/test/addons-napi/test_string/test.js new file mode 100644 index 00000000000000..3c9334a5616523 --- /dev/null +++ b/test/addons-napi/test_string/test.js @@ -0,0 +1,26 @@ +'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 str1 = 'hello world'; +assert.strictEqual(test_string.Copy(str1), str1); +assert.strictEqual(test_string.Length(str1), 11); +assert.strictEqual(test_string.Utf8Length(str1), 11); + +const str2 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; +assert.strictEqual(test_string.Copy(str2), str2); +assert.strictEqual(test_string.Length(str2), 62); +assert.strictEqual(test_string.Utf8Length(str2), 62); + +const str3 = '?!@#$%^&*()_+-=[]{}/.,<>\'"\\'; +assert.strictEqual(test_string.Copy(str3), str3); +assert.strictEqual(test_string.Length(str3), 27); +assert.strictEqual(test_string.Utf8Length(str3), 27); + +const str4 = '\u{2003}\u{2101}\u{2001}'; +assert.strictEqual(test_string.Copy(str4), str4); +assert.strictEqual(test_string.Length(str4), 3); +assert.strictEqual(test_string.Utf8Length(str4), 9); diff --git a/test/addons-napi/test_string/test_string.cc b/test/addons-napi/test_string/test_string.cc new file mode 100644 index 00000000000000..1da30fe77ef2d1 --- /dev/null +++ b/test/addons-napi/test_string/test_string.cc @@ -0,0 +1,134 @@ +#include + +void Copy(napi_env env, napi_callback_info info) { + napi_status status; + + int argc; + status = napi_get_cb_args_length(env, info, &argc); + if (status != napi_ok) return; + + if (argc < 1) { + napi_throw_type_error(env, "Wrong number of arguments"); + return; + } + + napi_value args[1]; + status = napi_get_cb_args(env, info, args, 1); + if (status != napi_ok) return; + + napi_valuetype valuetype; + status = napi_get_type_of_value(env, args[0], &valuetype); + if (status != napi_ok) return; + + if (valuetype != napi_string) { + napi_throw_type_error(env, "Wrong type of argments. Expects a string."); + return; + } + + char buffer[128]; + int buffer_size = 128; + + status = + napi_get_value_string_utf8(env, args[0], buffer, buffer_size, nullptr); + if (status != napi_ok) return; + + napi_value output; + status = napi_create_string_utf8(env, buffer, -1, &output); + if (status != napi_ok) return; + + status = napi_set_return_value(env, info, output); + if (status != napi_ok) return; +} + +void Length(napi_env env, napi_callback_info info) { + napi_status status; + + int argc; + status = napi_get_cb_args_length(env, info, &argc); + if (status != napi_ok) return; + + if (argc < 1) { + napi_throw_type_error(env, "Wrong number of arguments"); + return; + } + + napi_value args[1]; + status = napi_get_cb_args(env, info, args, 1); + if (status != napi_ok) return; + + napi_valuetype valuetype; + status = napi_get_type_of_value(env, args[0], &valuetype); + if (status != napi_ok) return; + + if (valuetype != napi_string) { + napi_throw_type_error(env, "Wrong type of argments. Expects a string."); + return; + } + + int length; + status = napi_get_value_string_length(env, args[0], &length); + if (status != napi_ok) return; + + napi_value output; + status = napi_create_number(env, length, &output); + if (status != napi_ok) return; + + status = napi_set_return_value(env, info, output); + if (status != napi_ok) return; +} + +void Utf8Length(napi_env env, napi_callback_info info) { + napi_status status; + + int argc; + status = napi_get_cb_args_length(env, info, &argc); + if (status != napi_ok) return; + + if (argc < 1) { + napi_throw_type_error(env, "Wrong number of arguments"); + return; + } + + napi_value args[1]; + status = napi_get_cb_args(env, info, args, 1); + if (status != napi_ok) return; + + napi_valuetype valuetype; + status = napi_get_type_of_value(env, args[0], &valuetype); + if (status != napi_ok) return; + + if (valuetype != napi_string) { + napi_throw_type_error(env, "Wrong type of argments. Expects a string."); + return; + } + + int length; + status = napi_get_value_string_utf8_length(env, args[0], &length); + if (status != napi_ok) return; + + napi_value output; + status = napi_create_number(env, length, &output); + if (status != napi_ok) return; + + status = napi_set_return_value(env, info, output); + if (status != napi_ok) return; +} + +#define DECLARE_NAPI_METHOD(name, func) \ + { name, func, 0, 0, 0, napi_default, 0 } + +void Init(napi_env env, napi_value exports, napi_value module, void* priv) { + napi_status status; + + napi_property_descriptor properties[] = { + DECLARE_NAPI_METHOD("Copy", Copy), + DECLARE_NAPI_METHOD("Length", Length), + DECLARE_NAPI_METHOD("Utf8Length", Utf8Length), + }; + + status = napi_define_properties( + env, exports, sizeof(properties) / sizeof(*properties), properties); + if (status != napi_ok) return; +} + +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..9b8ec0e89dd9fe --- /dev/null +++ b/test/addons-napi/test_symbol/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "test_symbol", + "sources": [ "test_symbol.cc" ] + } + ] +} 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.cc b/test/addons-napi/test_symbol/test_symbol.cc new file mode 100644 index 00000000000000..0338d59c0904bd --- /dev/null +++ b/test/addons-napi/test_symbol/test_symbol.cc @@ -0,0 +1,100 @@ +#include + +void Test(napi_env env, napi_callback_info info) { + napi_status status; + + int argc; + status = napi_get_cb_args_length(env, info, &argc); + if (status != napi_ok) return; + + if (argc < 1) { + napi_throw_type_error(env, "Wrong number of arguments"); + return; + } + + napi_value args[1]; + status = napi_get_cb_args(env, info, args, 1); + if (status != napi_ok) return; + + napi_valuetype valuetype; + status = napi_get_type_of_value(env, args[0], &valuetype); + if (status != napi_ok) return; + + if (valuetype != napi_symbol) { + napi_throw_type_error(env, "Wrong type of argments. Expects a symbol."); + return; + } + + char buffer[128]; + int buffer_size = 128; + + status = + napi_get_value_string_utf8(env, args[0], buffer, buffer_size, nullptr); + if (status != napi_ok) return; + + napi_value output; + status = napi_create_string_utf8(env, buffer, -1, &output); + if (status != napi_ok) return; + + status = napi_set_return_value(env, info, output); + if (status != napi_ok) return; +} + +void New(napi_env env, napi_callback_info info) { + napi_status status; + + int argc; + status = napi_get_cb_args_length(env, info, &argc); + if (status != napi_ok) return; + + if (argc >= 1) { + napi_value args[1]; + napi_get_cb_args(env, info, args, 1); + + napi_valuetype valuetype; + status = napi_get_type_of_value(env, args[0], &valuetype); + if (status != napi_ok) return; + + if (valuetype != napi_string) { + napi_throw_type_error(env, "Wrong type of argments. Expects a string."); + return; + } + + char buffer[128]; + int buffer_size = 128; + status = + napi_get_value_string_utf8(env, args[0], buffer, buffer_size, nullptr); + if (status != napi_ok) return; + + napi_value symbol; + status = napi_create_symbol(env, buffer, &symbol); + if (status != napi_ok) return; + + status = napi_set_return_value(env, info, symbol); + if (status != napi_ok) return; + } else { + napi_value symbol; + status = napi_create_symbol(env, NULL, &symbol); + if (status != napi_ok) return; + + status = napi_set_return_value(env, info, symbol); + if (status != napi_ok) return; + } +} + +#define DECLARE_NAPI_METHOD(name, func) \ + { name, func, 0, 0, 0, napi_default, 0 } + +void Init(napi_env env, napi_value exports, napi_value module, void* priv) { + napi_status status; + + napi_property_descriptor properties[] = { + DECLARE_NAPI_METHOD("New", New), + }; + + status = napi_define_properties( + env, exports, sizeof(properties) / sizeof(*properties), properties); + if (status != napi_ok) return; +} + +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..e512fbdcdb6f38 --- /dev/null +++ b/test/addons-napi/test_typedarray/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "test_typedarray", + "sources": [ "test_typedarray.cc" ] + } + ] +} diff --git a/test/addons-napi/test_typedarray/test.js b/test/addons-napi/test_typedarray/test.js new file mode 100644 index 00000000000000..cc1fcbe3566da3 --- /dev/null +++ b/test/addons-napi/test_typedarray/test.js @@ -0,0 +1,39 @@ +'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); diff --git a/test/addons-napi/test_typedarray/test_typedarray.cc b/test/addons-napi/test_typedarray/test_typedarray.cc new file mode 100644 index 00000000000000..55ac5ae137e2c1 --- /dev/null +++ b/test/addons-napi/test_typedarray/test_typedarray.cc @@ -0,0 +1,145 @@ +#include +#include + +void Multiply(napi_env env, napi_callback_info info) { + napi_status status; + + int argc; + status = napi_get_cb_args_length(env, info, &argc); + if (status != napi_ok) return; + + if (argc != 2) { + napi_throw_type_error(env, "Wrong number of arguments"); + return; + } + + napi_value args[2]; + status = napi_get_cb_args(env, info, args, 2); + if (status != napi_ok) return; + + napi_valuetype valuetype0; + status = napi_get_type_of_value(env, args[0], &valuetype0); + if (status != napi_ok) return; + + if (valuetype0 != napi_object) { + napi_throw_type_error( + env, + "Wrong type of argments. Expects a typed array as first argument."); + return; + } + + napi_value input_array = args[0]; + bool istypedarray; + status = napi_is_typedarray(env, input_array, &istypedarray); + if (status != napi_ok) return; + + if (!istypedarray) { + napi_throw_type_error( + env, + "Wrong type of argments. Expects a typed array as first argument."); + return; + } + + napi_valuetype valuetype1; + status = napi_get_type_of_value(env, args[1], &valuetype1); + if (status != napi_ok) return; + + if (valuetype1 != napi_number) { + napi_throw_type_error( + env, "Wrong type of argments. Expects a number as second argument."); + return; + } + + double multiplier; + status = napi_get_value_double(env, args[1], &multiplier); + if (status != napi_ok) return; + + napi_typedarray_type type; + napi_value input_buffer; + size_t byte_offset; + size_t length; + status = napi_get_typedarray_info( + env, input_array, &type, &length, nullptr, &input_buffer, &byte_offset); + if (status != napi_ok) return; + + void* data; + size_t byte_length; + status = napi_get_arraybuffer_info(env, input_buffer, &data, &byte_length); + if (status != napi_ok) return; + + napi_value output_buffer; + void* output_ptr = nullptr; + status = + napi_create_arraybuffer(env, byte_length, &output_ptr, &output_buffer); + if (status != napi_ok) return; + + napi_value output_array; + status = napi_create_typedarray( + env, type, length, output_buffer, byte_offset, &output_array); + if (status != napi_ok) return; + + if (type == napi_uint8) { + uint8_t* input_bytes = reinterpret_cast(data) + byte_offset; + uint8_t* output_bytes = reinterpret_cast(output_ptr); + for (size_t i = 0; i < length; i++) { + output_bytes[i] = static_cast(input_bytes[i] * multiplier); + } + } else if (type == napi_float64) { + double* input_doubles = reinterpret_cast( + reinterpret_cast(data) + byte_offset); + double* output_doubles = reinterpret_cast(output_ptr); + for (size_t i = 0; i < length; i++) { + output_doubles[i] = input_doubles[i] * multiplier; + } + } else { + napi_throw_error(env, "Typed array was of a type not expected by test."); + return; + } + + status = napi_set_return_value(env, info, output_array); + if (status != napi_ok) return; +} + +void External(napi_env env, napi_callback_info info) { + static int8_t externalData[] = {0, 1, 2}; + + napi_value output_buffer; + napi_status status = napi_create_external_arraybuffer( + env, + externalData, + sizeof(externalData), + nullptr, // finalize_callback + nullptr, // finalize_hint + &output_buffer); + if (status != napi_ok) return; + + napi_value output_array; + status = napi_create_typedarray(env, + napi_int8, + sizeof(externalData) / sizeof(uint8_t), + output_buffer, + 0, + &output_array); + if (status != napi_ok) return; + + status = napi_set_return_value(env, info, output_array); + if (status != napi_ok) return; +} + +#define DECLARE_NAPI_METHOD(name, func) \ + { name, func, 0, 0, 0, napi_default, 0 } + +void Init(napi_env env, napi_value exports, napi_value module, void* priv) { + napi_status status; + + napi_property_descriptor descriptors[] = { + DECLARE_NAPI_METHOD("Multiply", Multiply), + DECLARE_NAPI_METHOD("External", External), + }; + + status = napi_define_properties( + env, exports, sizeof(descriptors) / sizeof(*descriptors), descriptors); + if (status != napi_ok) return; +} + +NAPI_MODULE(addon, Init) diff --git a/test/addons-napi/testcfg.py b/test/addons-napi/testcfg.py new file mode 100644 index 00000000000000..07037ff6a0b0c0 --- /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', ['--napi-modules=yes']) diff --git a/test/testpy/__init__.py b/test/testpy/__init__.py index d7ec88992e2366..f999b6a6baafaf 100644 --- a/test/testpy/__init__.py +++ b/test/testpy/__init__.py @@ -162,5 +162,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 d1100352c6a413..145ceb4c9c51c4 100755 --- a/tools/install.py +++ b/tools/install.py @@ -148,6 +148,10 @@ def headers(action): 'common.gypi', 'config.gypi', 'src/node.h', + 'src/node_api.h', + 'src/node_api_types.h', + 'src/node_api_async.h', + 'src/node_api_async_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 deeb7aeffe2e02..6e1262931ff93d 100755 --- a/tools/test.py +++ b/tools/test.py @@ -1529,6 +1529,7 @@ def ExpandCommand(args): 'message', 'internet', 'addons', + 'addons-napi', 'gc', 'debugger', 'doctool', diff --git a/vcbuild.bat b/vcbuild.bat index e4c5276bf2af6e..05a319a08159ca 100644 --- a/vcbuild.bat +++ b/vcbuild.bat @@ -40,6 +40,7 @@ set enable_vtune_arg= set configure_flags= set build_addons= set dll= +set build_addons_napi= set test_node_inspect= :next-arg @@ -59,9 +60,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% addons doctool known_issues message parallel sequential -J&set cpplint=1&set jslint=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 addons doctool inspector known_issues message sequential parallel&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% addons addons-napi doctool known_issues message parallel sequential -J&set cpplint=1&set jslint=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 addons addons-napi doctool inspector known_issues message sequential parallel&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 build_testgc_addon=1&goto arg-ok @@ -312,12 +314,12 @@ echo Failed to build test/gc add-on." 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 @@ -333,6 +335,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 From a5d43d1f5fe1a15d63b6ae792e436982978ebf33 Mon Sep 17 00:00:00 2001 From: Jason Ginchereau Date: Tue, 21 Mar 2017 17:27:10 -0700 Subject: [PATCH 02/22] Fix undefined snprintf use for older compiler The snprintf API is not available with older versions of MSVC. Found by a CI run. --- test/addons-napi/test_buffer/test_buffer.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/addons-napi/test_buffer/test_buffer.cc b/test/addons-napi/test_buffer/test_buffer.cc index 4b781b85ded0bd..6477f25441897e 100644 --- a/test/addons-napi/test_buffer/test_buffer.cc +++ b/test/addons-napi/test_buffer/test_buffer.cc @@ -44,7 +44,7 @@ void newBuffer(napi_env env, napi_callback_info info) { reinterpret_cast(&theCopy), &theBuffer)); JS_ASSERT(env, theCopy, "Failed to copy static text for newBuffer"); - snprintf(theCopy, kBufferSize, "%s", theText); + memcpy(theCopy, theText, kBufferSize); NAPI_CALL(env, napi_set_return_value(env, info, theBuffer)); } From 87c42e70fcdb4247c79f1a07c6cc335c9ff64287 Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Wed, 22 Mar 2017 10:03:08 +0200 Subject: [PATCH 03/22] all: update --napi-modules flag to not have a yes/no This updates the documentation, the error message upon module load failure, the command line option parsing of the flag, and the way the N-API addon tests pass the flag to node. Re https://github.com/nodejs/node/pull/11975#discussion_r107257386 Re https://github.com/nodejs/node/pull/11975#discussion_r107325207 Fixes https://github.com/nodejs/abi-stable-node/issues/184 Closes https://github.com/nodejs/abi-stable-node/pull/186 --- doc/api/cli.md | 7 +++++++ doc/node.1 | 4 ++++ src/node.cc | 9 +++------ test/addons-napi/testcfg.py | 2 +- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/doc/api/cli.md b/doc/api/cli.md index 5e6390335bfef1..31087367da70f3 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -144,6 +144,13 @@ added: v6.0.0 Silence all process warnings (including deprecations). +### `--napi-modules` + + +Load N-API modules. + ### `--trace-warnings` -Load N-API modules. +Load N-API modules (experimental, opt-in by adding this flag). ### `--trace-warnings` -Load N-API modules (experimental, opt-in by adding this flag). +Enable loading native modules compiled with the ABI-stable Node.js API (N-API) +(experimental). ### `--trace-warnings`