diff options
88 files changed, 6580 insertions, 23 deletions
@@ -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_types.h +# Cannot use $(wildcard test/addons-napi/*/) here, it's evaluated before +# embedded addons have been generated from the documentation. + @for dirname in test/addons-napi/*/; do \ + printf "\nBuilding addon $$PWD/$$dirname\n" ; \ + env MAKEFLAGS="-j1" $(NODE) deps/npm/node_modules/node-gyp/bin/node-gyp \ + --loglevel=$(LOGLEVEL) rebuild \ + --python="$(PYTHON)" \ + --directory="$$PWD/$$dirname" \ + --nodedir="$$PWD" || exit 1 ; \ + done + touch $@ + +# .buildstamp and .docbuildstamp need $(NODE_EXE) but cannot depend on it +# directly because it calls make recursively. The parent make cannot know +# if the subprocess touched anything so it pessimistically assumes that +# .buildstamp and .docbuildstamp are out of date and need a rebuild. +# Just goes to show that recursive make really is harmful... +# TODO(bnoordhuis) Force rebuild after gyp or node-gyp update. +build-addons-napi: $(NODE_EXE) test/addons-napi/.buildstamp + 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/doc/api/cli.md b/doc/api/cli.md index a24aa5ee80..5f5e577895 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -144,6 +144,14 @@ added: v6.0.0 Silence all process warnings (including deprecations). +### `--napi-modules` +<!-- YAML +added: REPLACEME +--> + +Enable loading native modules compiled with the ABI-stable Node.js API (N-API) +(experimental). + ### `--trace-warnings` <!-- YAML added: v6.0.0 diff --git a/doc/node.1 b/doc/node.1 index 287b4e868a..0905b1f944 100644 --- a/doc/node.1 +++ b/doc/node.1 @@ -120,6 +120,11 @@ Throw errors for deprecations. Silence all process warnings (including deprecations). .TP +.BR \-\-napi\-modules +Enable loading native modules compiled with the ABI-stable Node.js API (N-API) +(experimental). + +.TP .BR \-\-trace\-warnings Print stack traces for process warnings (including deprecations). @@ -167,6 +167,9 @@ 'src/handle_wrap.cc', 'src/js_stream.cc', 'src/node.cc', + 'src/node_api.cc', + 'src/node_api.h', + 'src/node_api_types.h', 'src/node_buffer.cc', 'src/node_config.cc', 'src/node_constants.cc', diff --git a/src/node.cc b/src/node.cc index 77e6a5826e..853e0aa796 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,25 @@ void DLOpen(const FunctionCallbackInfo<Value>& 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 ABI-stable Node.js API (N-API)." + "\nThis feature is experimental and must be enabled on the " + "\ncommand-line by adding --napi-modules.", + *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 @@ -3537,6 +3550,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 load N-API modules\n" " --trace-warnings show stack traces on process warnings\n" " --redirect-warnings=path\n" " write warnings to path instead of\n" @@ -3709,6 +3723,8 @@ static void ParseArgs(int* argc, force_repl = true; } else if (strcmp(arg, "--no-deprecation") == 0) { no_deprecation = true; + } else if (strcmp(arg, "--napi-modules") == 0) { + load_napi_modules = true; } else if (strcmp(arg, "--no-warnings") == 0) { no_process_warnings = true; } else if (strcmp(arg, "--trace-warnings") == 0) { @@ -4489,6 +4505,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 0000000000..a94ee6af4f --- /dev/null +++ b/src/node_api.cc @@ -0,0 +1,2528 @@ +/****************************************************************************** + * 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 <node_buffer.h> +#include <node_object_wrap.h> +#include <string.h> +#include <algorithm> +#include <cmath> +#include <vector> +#include "node_api.h" + +namespace v8impl { + +//=== Conversion between V8 Isolate and napi_env ========================== + +napi_env JsEnvFromV8Isolate(v8::Isolate* isolate) { + return reinterpret_cast<napi_env>(isolate); +} + +v8::Isolate* V8IsolateFromJsEnv(napi_env e) { + return reinterpret_cast<v8::Isolate*>(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<T> v) to mimic the behavior +// of a EscapableHandleScope::Escape(Local<T> 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 <typename T> + v8::Local<T> Escape(v8::Local<T> handle) { + return scope.Escape(handle); + } + + private: + v8::EscapableHandleScope scope; +}; + +napi_handle_scope JsHandleScopeFromV8HandleScope(HandleScopeWrapper* s) { + return reinterpret_cast<napi_handle_scope>(s); +} + +HandleScopeWrapper* V8HandleScopeFromJsHandleScope(napi_handle_scope s) { + return reinterpret_cast<HandleScopeWrapper*>(s); +} + +napi_escapable_handle_scope JsEscapableHandleScopeFromV8EscapableHandleScope( + EscapableHandleScopeWrapper* s) { + return reinterpret_cast<napi_escapable_handle_scope>(s); +} + +EscapableHandleScopeWrapper* +V8EscapableHandleScopeFromJsEscapableHandleScope( + napi_escapable_handle_scope s) { + return reinterpret_cast<EscapableHandleScopeWrapper*>(s); +} + +//=== Conversion between V8 Handles and napi_value ======================== + +// This asserts v8::Local<> will always be implemented with a single +// pointer field so that we can pass it around as a void*. +static_assert(sizeof(v8::Local<v8::Value>) == sizeof(napi_value), + "Cannot convert between v8::Local<v8::Value> and napi_value"); + +napi_value JsValueFromV8LocalValue(v8::Local<v8::Value> local) { + return reinterpret_cast<napi_value>(*local); +} + +v8::Local<v8::Value> V8LocalValueFromJsValue(napi_value v) { + v8::Local<v8::Value> local; + memcpy(&local, &v, sizeof(v)); + return local; +} + +// Adapter for napi_finalize callbacks. +class Finalizer { + protected: + Finalizer(v8::Isolate* isolate, + napi_finalize finalize_callback, + void* finalize_data, + void* finalize_hint) + : _isolate(isolate), + _finalize_callback(finalize_callback), + _finalize_data(finalize_data), + _finalize_hint(finalize_hint) { + } + + ~Finalizer() { + } + + public: + static Finalizer* New(v8::Isolate* isolate, + napi_finalize finalize_callback = nullptr, + void* finalize_data = nullptr, + void* finalize_hint = nullptr) { + return new Finalizer( + isolate, finalize_callback, finalize_data, finalize_hint); + } + + static void Delete(Finalizer* finalizer) { + delete finalizer; + } + + // node::Buffer::FreeCallback + static void FinalizeBufferCallback(char* data, void* hint) { + Finalizer* finalizer = static_cast<Finalizer*>(hint); + if (finalizer->_finalize_callback != nullptr) { + finalizer->_finalize_callback( + v8impl::JsEnvFromV8Isolate(finalizer->_isolate), + data, + finalizer->_finalize_hint); + } + + Delete(finalizer); + } + + protected: + v8::Isolate* _isolate; + napi_finalize _finalize_callback; + void* _finalize_data; + void* _finalize_hint; +}; + +// Wrapper around v8::Persistent that implements reference counting. +class Reference : private Finalizer { + private: + Reference(v8::Isolate* isolate, + v8::Local<v8::Value> value, + uint32_t initial_refcount, + bool delete_self, + napi_finalize finalize_callback, + void* finalize_data, + void* finalize_hint) + : Finalizer(isolate, finalize_callback, finalize_data, finalize_hint), + _persistent(isolate, value), + _refcount(initial_refcount), + _delete_self(delete_self) { + if (initial_refcount == 0) { + _persistent.SetWeak( + this, FinalizeCallback, v8::WeakCallbackType::kParameter); + _persistent.MarkIndependent(); + } + } + + ~Reference() { + // The V8 Persistent class currently does not reset in its destructor: + // see NonCopyablePersistentTraits::kResetInDestructor = false. + // (Comments there claim that might change in the future.) + // To avoid memory leaks, it is better to reset at this time, however + // care must be taken to avoid attempting this after the Isolate has + // shut down, for example via a static (atexit) destructor. + _persistent.Reset(); + } + + public: + static Reference* New(v8::Isolate* isolate, + v8::Local<v8::Value> value, + uint32_t initial_refcount, + bool delete_self, + napi_finalize finalize_callback = nullptr, + void* finalize_data = nullptr, + void* finalize_hint = nullptr) { + return new Reference(isolate, + value, + initial_refcount, + delete_self, + finalize_callback, + finalize_data, + finalize_hint); + } + + static void Delete(Reference* reference) { + delete reference; + } + + uint32_t Ref() { + if (++_refcount == 1) { + _persistent.ClearWeak(); + } + + return _refcount; + } + + uint32_t Unref() { + if (_refcount == 0) { + return 0; + } + if (--_refcount == 0) { + _persistent.SetWeak( + this, FinalizeCallback, v8::WeakCallbackType::kParameter); + _persistent.MarkIndependent(); + } + + return _refcount; + } + + uint32_t RefCount() { + return _refcount; + } + + v8::Local<v8::Value> Get() { + if (_persistent.IsEmpty()) { + return v8::Local<v8::Value>(); + } else { + return v8::Local<v8::Value>::New(_isolate, _persistent); + } + } + + private: + static void FinalizeCallback(const v8::WeakCallbackInfo<Reference>& data) { + Reference* reference = data.GetParameter(); + reference->_persistent.Reset(); + + // Check before calling the finalize callback, because the callback might + // delete it. + bool delete_self = reference->_delete_self; + + if (reference->_finalize_callback != nullptr) { + reference->_finalize_callback( + v8impl::JsEnvFromV8Isolate(reference->_isolate), + reference->_finalize_data, + reference->_finalize_hint); + } + + if (delete_self) { + Delete(reference); + } + } + + v8::Persistent<v8::Value> _persistent; + uint32_t _refcount; + bool _delete_self; +}; + +class TryCatch : public v8::TryCatch { + public: + explicit TryCatch(v8::Isolate* isolate) + : v8::TryCatch(isolate), _isolate(isolate) {} + + ~TryCatch() { + if (HasCaught()) { + _the_exception.Reset(_isolate, Exception()); + } + } + + static v8::Persistent<v8::Value>& LastException() { return _the_exception; } + + private: + static v8::Persistent<v8::Value> _the_exception; + v8::Isolate* _isolate; +}; + +v8::Persistent<v8::Value> TryCatch::_the_exception; + +//=== 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 this_arg, size_t args_length, void* data) + : _this(this_arg), _args_length(args_length), _data(data) {} + + virtual bool IsConstructCall() = 0; + virtual void Args(napi_value* buffer, size_t bufferlength) = 0; + virtual void SetReturnValue(napi_value value) = 0; + + napi_value This() { return _this; } + + size_t ArgsLength() { return _args_length; } + + void* Data() { return _data; } + + protected: + const napi_value _this; + const size_t _args_length; + void* _data; +}; + +template <typename Info, int kInternalFieldIndex> +class CallbackWrapperBase : public CallbackWrapper { + public: + CallbackWrapperBase(const Info& cbinfo, const size_t args_length) + : CallbackWrapper(JsValueFromV8LocalValue(cbinfo.This()), + args_length, + nullptr), + _cbinfo(cbinfo), + _cbdata(v8::Local<v8::Object>::Cast(cbinfo.Data())) { + _data = v8::Local<v8::External>::Cast(_cbdata->GetInternalField(kDataIndex)) + ->Value(); + } + + /*virtual*/ + bool IsConstructCall() override { return false; } + + protected: + void InvokeCallback() { + napi_callback_info cbinfo_wrapper = reinterpret_cast<napi_callback_info>( + static_cast<CallbackWrapper*>(this)); + napi_callback cb = reinterpret_cast<napi_callback>( + v8::Local<v8::External>::Cast( + _cbdata->GetInternalField(kInternalFieldIndex))->Value()); + v8::Isolate* isolate = _cbinfo.GetIsolate(); + cb(v8impl::JsEnvFromV8Isolate(isolate), cbinfo_wrapper); + + if (!TryCatch::LastException().IsEmpty()) { + isolate->ThrowException( + v8::Local<v8::Value>::New(isolate, TryCatch::LastException())); + TryCatch::LastException().Reset(); + } + } + + const Info& _cbinfo; + const v8::Local<v8::Object> _cbdata; +}; + +class FunctionCallbackWrapper + : public CallbackWrapperBase<v8::FunctionCallbackInfo<v8::Value>, + kFunctionIndex> { + public: + static void Invoke(const v8::FunctionCallbackInfo<v8::Value>& info) { + FunctionCallbackWrapper cbwrapper(info); + cbwrapper.InvokeCallback(); + } + + explicit FunctionCallbackWrapper( + const v8::FunctionCallbackInfo<v8::Value>& cbinfo) + : CallbackWrapperBase(cbinfo, cbinfo.Length()) {} + + /*virtual*/ + bool IsConstructCall() override { return _cbinfo.IsConstructCall(); } + + /*virtual*/ + void Args(napi_value* buffer, size_t buffer_length) override { + size_t i = 0; + size_t min = std::min(buffer_length, _args_length); + + for (; i < min; i += 1) { + buffer[i] = v8impl::JsValueFromV8LocalValue(_cbinfo[i]); + } + + if (i < buffer_length) { + napi_value undefined = + v8impl::JsValueFromV8LocalValue(v8::Undefined(_cbinfo.GetIsolate())); + for (; i < buffer_length; i += 1) { + buffer[i] = undefined; + } + } + } + + /*virtual*/ + void SetReturnValue(napi_value value) override { + v8::Local<v8::Value> val = v8impl::V8LocalValueFromJsValue(value); + _cbinfo.GetReturnValue().Set(val); + } +}; + +class GetterCallbackWrapper + : public CallbackWrapperBase<v8::PropertyCallbackInfo<v8::Value>, + kGetterIndex> { + public: + static void Invoke(v8::Local<v8::Name> property, + const v8::PropertyCallbackInfo<v8::Value>& info) { + GetterCallbackWrapper cbwrapper(info); + cbwrapper.InvokeCallback(); + } + + explicit GetterCallbackWrapper( + const v8::PropertyCallbackInfo<v8::Value>& cbinfo) + : CallbackWrapperBase(cbinfo, 0) {} + + /*virtual*/ + void Args(napi_value* buffer, size_t buffer_length) override { + if (buffer_length > 0) { + napi_value undefined = + v8impl::JsValueFromV8LocalValue(v8::Undefined(_cbinfo.GetIsolate())); + for (size_t i = 0; i < buffer_length; i += 1) { + buffer[i] = undefined; + } + } + } + + /*virtual*/ + void SetReturnValue(napi_value value) override { + v8::Local<v8::Value> val = v8impl::V8LocalValueFromJsValue(value); + _cbinfo.GetReturnValue().Set(val); + } +}; + +class SetterCallbackWrapper + : public CallbackWrapperBase<v8::PropertyCallbackInfo<void>, kSetterIndex> { + public: + static void Invoke(v8::Local<v8::Name> property, + v8::Local<v8::Value> value, + const v8::PropertyCallbackInfo<void>& info) { + SetterCallbackWrapper cbwrapper(info, value); + cbwrapper.InvokeCallback(); + } + + SetterCallbackWrapper(const v8::PropertyCallbackInfo<void>& cbinfo, + const v8::Local<v8::Value>& value) + : CallbackWrapperBase(cbinfo, 1), _value(value) {} + + /*virtual*/ + void Args(napi_value* buffer, size_t buffer_length) override { + if (buffer_length > 0) { + buffer[0] = v8impl::JsValueFromV8LocalValue(_value); + + if (buffer_length > 1) { + napi_value undefined = v8impl::JsValueFromV8LocalValue( + v8::Undefined(_cbinfo.GetIsolate())); + for (size_t i = 1; i < buffer_length; i += 1) { + buffer[i] = undefined; + } + } + } + } + + /*virtual*/ + void SetReturnValue(napi_value value) override { + node::FatalError("napi_set_return_value", + "Cannot return a value from a setter callback."); + } + + private: + const v8::Local<v8::Value>& _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<v8::Object> CreateFunctionCallbackData(napi_env env, + napi_callback cb, + void* data) { + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(env); + v8::Local<v8::Context> context = isolate->GetCurrentContext(); + + v8::Local<v8::ObjectTemplate> otpl = v8::ObjectTemplate::New(isolate); + otpl->SetInternalFieldCount(v8impl::kFunctionFieldCount); + v8::Local<v8::Object> cbdata = otpl->NewInstance(context).ToLocalChecked(); + + cbdata->SetInternalField( + v8impl::kFunctionIndex, + v8::External::New(isolate, reinterpret_cast<void*>(cb))); + cbdata->SetInternalField( + v8impl::kDataIndex, + v8::External::New(isolate, data)); + return cbdata; +} + +// Creates an object to be made available to the static getter/setter +// callback wrapper, used to retrieve the native getter/setter callback +// function and data pointer. +v8::Local<v8::Object> CreateAccessorCallbackData(napi_env env, + napi_callback getter, + napi_callback setter, + void* data) { + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(env); + v8::Local<v8::Context> context = isolate->GetCurrentContext(); + + v8::Local<v8::ObjectTemplate> otpl = v8::ObjectTemplate::New(isolate); + otpl->SetInternalFieldCount(v8impl::kAccessorFieldCount); + v8::Local<v8::Object> cbdata = otpl->NewInstance(context).ToLocalChecked(); + + if (getter != nullptr) { + cbdata->SetInternalField( + v8impl::kGetterIndex, + v8::External::New(isolate, reinterpret_cast<void*>(getter))); + } + + if (setter != nullptr) { + cbdata->SetInternalField( + v8impl::kSetterIndex, + v8::External::New(isolate, reinterpret_cast<void*>(setter))); + } + + cbdata->SetInternalField( + v8impl::kDataIndex, + v8::External::New(isolate, 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<v8::Object> exports, + v8::Local<v8::Value> module, + v8::Local<v8::Context> context, + void* priv) { + napi_module* mod = static_cast<napi_module*>(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 module_version = 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. + module_version = -1; + } +#endif // EXTERNAL_NAPI + + node::node_module* nm = new node::node_module { + module_version, + 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: try_catch must have function scope +#define NAPI_PREAMBLE(env) \ + CHECK_ARG(env); \ + RETURN_STATUS_IF_FALSE(v8impl::TryCatch::LastException().IsEmpty(), \ + napi_pending_exception); \ + napi_clear_last_error(); \ + v8impl::TryCatch try_catch(v8impl::V8IsolateFromJsEnv((env))) + +#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() \ + (!try_catch.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 = { nullptr, nullptr, 0, napi_ok }; + +// 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", + "An array 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; +} + +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_get_last_error_info(napi_env env, + const napi_extended_error_info** result) { + CHECK_ARG(env); + + static_assert(node::arraysize(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]; + + *result = &static_last_error; + return napi_ok; +} + +napi_status napi_create_function(napi_env env, + const char* utf8name, + napi_callback cb, + void* callback_data, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(result); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(env); + v8::Local<v8::Function> return_value; + v8::EscapableHandleScope scope(isolate); + v8::Local<v8::Object> cbdata = + v8impl::CreateFunctionCallbackData(env, cb, callback_data); + + RETURN_STATUS_IF_FALSE(!cbdata.IsEmpty(), napi_generic_failure); + + v8::Local<v8::FunctionTemplate> tpl = v8::FunctionTemplate::New( + isolate, v8impl::FunctionCallbackWrapper::Invoke, cbdata); + + v8::Local<v8::Context> context = isolate->GetCurrentContext(); + v8::MaybeLocal<v8::Function> maybe_function = tpl->GetFunction(context); + CHECK_MAYBE_EMPTY(maybe_function, napi_generic_failure); + + return_value = scope.Escape(maybe_function.ToLocalChecked()); + + if (utf8name != nullptr) { + v8::Local<v8::String> name_string; + CHECK_NEW_FROM_UTF8(isolate, name_string, utf8name); + return_value->SetName(name_string); + } + + *result = v8impl::JsValueFromV8LocalValue(return_value); + + return GET_RETURN_STATUS(); +} + +napi_status napi_define_class(napi_env env, + const char* utf8name, + napi_callback constructor, + void* callback_data, + size_t property_count, + const napi_property_descriptor* properties, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(result); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(env); + + v8::EscapableHandleScope scope(isolate); + v8::Local<v8::Object> cbdata = + v8impl::CreateFunctionCallbackData(env, constructor, callback_data); + + RETURN_STATUS_IF_FALSE(!cbdata.IsEmpty(), napi_generic_failure); + + v8::Local<v8::FunctionTemplate> 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<v8::String> name_string; + CHECK_NEW_FROM_UTF8(isolate, name_string, utf8name); + tpl->SetClassName(name_string); + + size_t static_property_count = 0; + for (size_t i = 0; i < property_count; i++) { + const napi_property_descriptor* p = properties + i; + + if ((p->attributes & napi_static_property) != 0) { + // Static properties are handled separately below. + static_property_count++; + continue; + } + + v8::Local<v8::String> property_name; + CHECK_NEW_FROM_UTF8(isolate, property_name, p->utf8name); + + v8::PropertyAttribute attributes = + static_cast<v8::PropertyAttribute>(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<v8::Object> cbdata = + v8impl::CreateFunctionCallbackData(env, p->method, p->data); + + RETURN_STATUS_IF_FALSE(!cbdata.IsEmpty(), napi_generic_failure); + + v8::Local<v8::FunctionTemplate> t = + v8::FunctionTemplate::New(isolate, + v8impl::FunctionCallbackWrapper::Invoke, + cbdata, + v8::Signature::New(isolate, tpl)); + t->SetClassName(property_name); + + tpl->PrototypeTemplate()->Set(property_name, t, attributes); + } else if (p->getter || p->setter) { + v8::Local<v8::Object> cbdata = v8impl::CreateAccessorCallbackData( + env, p->getter, p->setter, p->data); + + tpl->PrototypeTemplate()->SetAccessor( + property_name, + p->getter ? v8impl::GetterCallbackWrapper::Invoke : nullptr, + p->setter ? v8impl::SetterCallbackWrapper::Invoke : nullptr, + cbdata, + v8::AccessControl::DEFAULT, + attributes); + } else { + v8::Local<v8::Value> value = v8impl::V8LocalValueFromJsValue(p->value); + tpl->PrototypeTemplate()->Set(property_name, value, attributes); + } + } + + *result = v8impl::JsValueFromV8LocalValue(scope.Escape(tpl->GetFunction())); + + if (static_property_count > 0) { + std::vector<napi_property_descriptor> static_descriptors; + static_descriptors.reserve(static_property_count); + + for (size_t i = 0; i < property_count; i++) { + const napi_property_descriptor* p = properties + i; + if ((p->attributes & napi_static_property) != 0) { + static_descriptors.push_back(*p); + } + } + + napi_status status = + napi_define_properties(env, + *result, + static_descriptors.size(), + static_descriptors.data()); + if (status != napi_ok) return status; + } + + return GET_RETURN_STATUS(); +} + +napi_status napi_set_return_value(napi_env env, + napi_callback_info cbinfo, + napi_value value) { + NAPI_PREAMBLE(env); + + v8impl::CallbackWrapper* info = + reinterpret_cast<v8impl::CallbackWrapper*>(cbinfo); + + info->SetReturnValue(value); + return GET_RETURN_STATUS(); +} + +napi_status napi_get_property_names(napi_env env, + napi_value object, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(result); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(env); + v8::Local<v8::Context> context = isolate->GetCurrentContext(); + v8::Local<v8::Object> 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 env, + napi_value object, + napi_value key, + napi_value value) { + NAPI_PREAMBLE(env); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(env); + v8::Local<v8::Context> context = isolate->GetCurrentContext(); + v8::Local<v8::Object> obj; + + CHECK_TO_OBJECT(context, obj, object); + + v8::Local<v8::Value> k = v8impl::V8LocalValueFromJsValue(key); + v8::Local<v8::Value> val = v8impl::V8LocalValueFromJsValue(value); + + v8::Maybe<bool> 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 env, + napi_value object, + napi_value key, + bool* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(result); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(env); + v8::Local<v8::Context> context = isolate->GetCurrentContext(); + v8::Local<v8::Object> obj; + + CHECK_TO_OBJECT(context, obj, object); + + v8::Local<v8::Value> k = v8impl::V8LocalValueFromJsValue(key); + v8::Maybe<bool> 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 env, + napi_value object, + napi_value key, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(result); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(env); + v8::Local<v8::Context> context = isolate->GetCurrentContext(); + v8::Local<v8::Value> k = v8impl::V8LocalValueFromJsValue(key); + v8::Local<v8::Object> obj; + + CHECK_TO_OBJECT(context, obj, object); + + auto get_maybe = obj->Get(context, k); + + CHECK_MAYBE_EMPTY(get_maybe, napi_generic_failure); + + v8::Local<v8::Value> val = get_maybe.ToLocalChecked(); + *result = v8impl::JsValueFromV8LocalValue(val); + return GET_RETURN_STATUS(); +} + +napi_status napi_set_named_property(napi_env env, + napi_value object, + const char* utf8name, + napi_value value) { + NAPI_PREAMBLE(env); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(env); + v8::Local<v8::Context> context = isolate->GetCurrentContext(); + v8::Local<v8::Object> obj; + + CHECK_TO_OBJECT(context, obj, object); + + v8::Local<v8::Name> key; + CHECK_NEW_FROM_UTF8(isolate, key, utf8name); + + v8::Local<v8::Value> val = v8impl::V8LocalValueFromJsValue(value); + + v8::Maybe<bool> 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 env, + napi_value object, + const char* utf8name, + bool* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(result); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(env); + v8::Local<v8::Context> context = isolate->GetCurrentContext(); + v8::Local<v8::Object> obj; + + CHECK_TO_OBJECT(context, obj, object); + + v8::Local<v8::Name> key; + CHECK_NEW_FROM_UTF8(isolate, key, utf8name); + + v8::Maybe<bool> 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 env, + napi_value object, + const char* utf8name, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(result); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(env); + v8::Local<v8::Context> context = isolate->GetCurrentContext(); + + v8::Local<v8::Name> key; + CHECK_NEW_FROM_UTF8(isolate, key, utf8name); + + v8::Local<v8::Object> obj; + + CHECK_TO_OBJECT(context, obj, object); + + auto get_maybe = obj->Get(context, key); + + CHECK_MAYBE_EMPTY(get_maybe, napi_generic_failure); + + v8::Local<v8::Value> val = get_maybe.ToLocalChecked(); + *result = v8impl::JsValueFromV8LocalValue(val); + return GET_RETURN_STATUS(); +} + +napi_status napi_set_element(napi_env env, + napi_value object, + uint32_t index, + napi_value value) { + NAPI_PREAMBLE(env); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(env); + v8::Local<v8::Context> context = isolate->GetCurrentContext(); + v8::Local<v8::Object> obj; + + CHECK_TO_OBJECT(context, obj, object); + + v8::Local<v8::Value> 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 env, + napi_value object, + uint32_t index, + bool* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(result); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(env); + v8::Local<v8::Context> context = isolate->GetCurrentContext(); + v8::Local<v8::Object> obj; + + CHECK_TO_OBJECT(context, obj, object); + + v8::Maybe<bool> 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 env, + napi_value object, + uint32_t index, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(result); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(env); + v8::Local<v8::Context> context = isolate->GetCurrentContext(); + v8::Local<v8::Object> 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 env, + napi_value object, + size_t property_count, + const napi_property_descriptor* properties) { + NAPI_PREAMBLE(env); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(env); + v8::Local<v8::Context> context = isolate->GetCurrentContext(); + v8::Local<v8::Object> obj = + v8impl::V8LocalValueFromJsValue(object).As<v8::Object>(); + + for (size_t i = 0; i < property_count; i++) { + const napi_property_descriptor* p = &properties[i]; + + v8::Local<v8::Name> name; + CHECK_NEW_FROM_UTF8(isolate, name, p->utf8name); + + v8::PropertyAttribute attributes = static_cast<v8::PropertyAttribute>( + p->attributes & ~napi_static_property); + + if (p->method) { + v8::Local<v8::Object> cbdata = + v8impl::CreateFunctionCallbackData(env, p->method, p->data); + + RETURN_STATUS_IF_FALSE(!cbdata.IsEmpty(), napi_generic_failure); + + v8::Local<v8::FunctionTemplate> t = v8::FunctionTemplate::New( + isolate, v8impl::FunctionCallbackWrapper::Invoke, cbdata); + + auto define_maybe = + obj->DefineOwnProperty(context, name, t->GetFunction(), attributes); + + if (!define_maybe.FromMaybe(false)) { + return napi_set_last_error(napi_generic_failure); + } + } else if (p->getter || p->setter) { + v8::Local<v8::Object> cbdata = v8impl::CreateAccessorCallbackData( + env, + p->getter, + p->setter, + p->data); + + auto set_maybe = obj->SetAccessor( + context, + name, + p->getter ? v8impl::GetterCallbackWrapper::Invoke : nullptr, + p->setter ? v8impl::SetterCallbackWrapper::Invoke : nullptr, + cbdata, + v8::AccessControl::DEFAULT, + attributes); + + if (!set_maybe.FromMaybe(false)) { + return napi_set_last_error(napi_invalid_arg); + } + } else { + v8::Local<v8::Value> value = v8impl::V8LocalValueFromJsValue(p->value); + + auto define_maybe = + obj->DefineOwnProperty(context, name, value, attributes); + + if (!define_maybe.FromMaybe(false)) { + return napi_set_last_error(napi_invalid_arg); + } + } + } + + return GET_RETURN_STATUS(); +} + +napi_status napi_is_array(napi_env env, napi_value value, bool* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(result); + + v8::Local<v8::Value> val = v8impl::V8LocalValueFromJsValue(value); + + *result = val->IsArray(); + return GET_RETURN_STATUS(); +} + +napi_status napi_get_array_length(napi_env env, + napi_value value, + uint32_t* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(result); + + v8::Local<v8::Value> val = v8impl::V8LocalValueFromJsValue(value); + RETURN_STATUS_IF_FALSE(val->IsArray(), napi_array_expected); + + v8::Local<v8::Array> arr = val.As<v8::Array>(); + *result = arr->Length(); + + return GET_RETURN_STATUS(); +} + +napi_status napi_strict_equals(napi_env env, + napi_value lhs, + napi_value rhs, + bool* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(result); + + v8::Local<v8::Value> a = v8impl::V8LocalValueFromJsValue(lhs); + v8::Local<v8::Value> b = v8impl::V8LocalValueFromJsValue(rhs); + + *result = a->StrictEquals(b); + return GET_RETURN_STATUS(); +} + +napi_status napi_get_prototype(napi_env env, + napi_value object, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(result); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(env); + v8::Local<v8::Context> context = isolate->GetCurrentContext(); + + v8::Local<v8::Object> obj; + CHECK_TO_OBJECT(context, obj, object); + + v8::Local<v8::Value> val = obj->GetPrototype(); + *result = v8impl::JsValueFromV8LocalValue(val); + return GET_RETURN_STATUS(); +} + +napi_status napi_create_object(napi_env env, napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(result); + + *result = v8impl::JsValueFromV8LocalValue( + v8::Object::New(v8impl::V8IsolateFromJsEnv(env))); + + return GET_RETURN_STATUS(); +} + +napi_status napi_create_array(napi_env env, napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(result); + + *result = v8impl::JsValueFromV8LocalValue( + v8::Array::New(v8impl::V8IsolateFromJsEnv(env))); + + return GET_RETURN_STATUS(); +} + +napi_status napi_create_array_with_length(napi_env env, + size_t length, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(result); + + *result = v8impl::JsValueFromV8LocalValue( + v8::Array::New(v8impl::V8IsolateFromJsEnv(env), length)); + + return GET_RETURN_STATUS(); +} + +napi_status napi_create_string_utf8(napi_env env, + const char* str, + size_t length, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(result); + + auto isolate = v8impl::V8IsolateFromJsEnv(env); + v8::Local<v8::String> s; + CHECK_NEW_FROM_UTF8_LEN(isolate, s, str, length); + + *result = v8impl::JsValueFromV8LocalValue(s); + return GET_RETURN_STATUS(); +} + +napi_status napi_create_string_utf16(napi_env env, + const char16_t* str, + size_t length, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(result); + + auto isolate = v8impl::V8IsolateFromJsEnv(env); + auto str_maybe = + v8::String::NewFromTwoByte(isolate, + reinterpret_cast<const uint16_t*>(str), + v8::NewStringType::kInternalized, + length); + CHECK_MAYBE_EMPTY(str_maybe, napi_generic_failure); + + *result = v8impl::JsValueFromV8LocalValue(str_maybe.ToLocalChecked()); + return GET_RETURN_STATUS(); +} + +napi_status napi_create_number(napi_env env, + double value, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(result); + + *result = v8impl::JsValueFromV8LocalValue( + v8::Number::New(v8impl::V8IsolateFromJsEnv(env), value)); + + return GET_RETURN_STATUS(); +} + +napi_status napi_get_boolean(napi_env env, bool value, napi_value* result) { + CHECK_ARG(env); + CHECK_ARG(result); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(env); + + if (value) { + *result = v8impl::JsValueFromV8LocalValue(v8::True(isolate)); + } else { + *result = v8impl::JsValueFromV8LocalValue(v8::False(isolate)); + } + + return napi_ok; +} + +napi_status napi_create_symbol(napi_env env, + napi_value description, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(result); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(env); + + if (description == nullptr) { + *result = v8impl::JsValueFromV8LocalValue(v8::Symbol::New(isolate)); + } else { + v8::Local<v8::Value> desc = v8impl::V8LocalValueFromJsValue(description); + RETURN_STATUS_IF_FALSE(desc->IsString(), napi_string_expected); + + *result = v8impl::JsValueFromV8LocalValue( + v8::Symbol::New(isolate, desc.As<v8::String>())); + } + + return GET_RETURN_STATUS(); +} + +napi_status napi_create_error(napi_env env, + napi_value msg, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(result); + + v8::Local<v8::Value> message_value = v8impl::V8LocalValueFromJsValue(msg); + RETURN_STATUS_IF_FALSE(message_value->IsString(), napi_string_expected); + + *result = v8impl::JsValueFromV8LocalValue(v8::Exception::Error( + message_value.As<v8::String>())); + + return GET_RETURN_STATUS(); +} + +napi_status napi_create_type_error(napi_env env, + napi_value msg, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(result); + + v8::Local<v8::Value> message_value = v8impl::V8LocalValueFromJsValue(msg); + RETURN_STATUS_IF_FALSE(message_value->IsString(), napi_string_expected); + + *result = v8impl::JsValueFromV8LocalValue(v8::Exception::TypeError( + message_value.As<v8::String>())); + + return GET_RETURN_STATUS(); +} + +napi_status napi_create_range_error(napi_env env, + napi_value msg, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(result); + + v8::Local<v8::Value> message_value = v8impl::V8LocalValueFromJsValue(msg); + RETURN_STATUS_IF_FALSE(message_value->IsString(), napi_string_expected); + + *result = v8impl::JsValueFromV8LocalValue(v8::Exception::RangeError( + message_value.As<v8::String>())); + + return GET_RETURN_STATUS(); +} + +napi_status napi_typeof(napi_env env, + napi_value value, + napi_valuetype* result) { + // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw + // JS exceptions. + CHECK_ARG(result); + + v8::Local<v8::Value> 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 { + // Should not get here unless V8 has added some new kind of value. + return napi_set_last_error(napi_invalid_arg); + } + + return napi_ok; +} + +napi_status napi_get_undefined(napi_env env, napi_value* result) { + CHECK_ARG(env); + CHECK_ARG(result); + + *result = v8impl::JsValueFromV8LocalValue( + v8::Undefined(v8impl::V8IsolateFromJsEnv(env))); + + return napi_ok; +} + +napi_status napi_get_null(napi_env env, napi_value* result) { + CHECK_ARG(env); + CHECK_ARG(result); + + *result = v8impl::JsValueFromV8LocalValue( + v8::Null(v8impl::V8IsolateFromJsEnv(env))); + + return napi_ok; +} + +// Gets all callback info in a single call. (Ugly, but faster.) +napi_status napi_get_cb_info( + napi_env env, // [in] NAPI environment handle + napi_callback_info cbinfo, // [in] Opaque callback-info handle + size_t* argc, // [in-out] Specifies the size of the provided argv array + // and receives the actual count of args. + napi_value* argv, // [out] Array of values + napi_value* this_arg, // [out] Receives the JS 'this' arg for the call + void** data) { // [out] Receives the data pointer for the callback. + CHECK_ARG(argc); + CHECK_ARG(argv); + CHECK_ARG(this_arg); + CHECK_ARG(data); + + v8impl::CallbackWrapper* info = + reinterpret_cast<v8impl::CallbackWrapper*>(cbinfo); + + info->Args(argv, std::min(*argc, info->ArgsLength())); + *argc = info->ArgsLength(); + *this_arg = info->This(); + *data = info->Data(); + + return napi_ok; +} + +napi_status napi_get_cb_args_length(napi_env env, + napi_callback_info cbinfo, + size_t* result) { + // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because no V8 APIs are called. + CHECK_ARG(result); + + v8impl::CallbackWrapper* info = + reinterpret_cast<v8impl::CallbackWrapper*>(cbinfo); + + *result = info->ArgsLength(); + return napi_ok; +} + +napi_status napi_is_construct_call(napi_env env, + 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<v8impl::CallbackWrapper*>(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 env, + napi_callback_info cbinfo, + napi_value* buf, + size_t bufsize) { + // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because no V8 APIs are called. + CHECK_ARG(buf); + + v8impl::CallbackWrapper* info = + reinterpret_cast<v8impl::CallbackWrapper*>(cbinfo); + + info->Args(buf, bufsize); + return napi_ok; +} + +napi_status napi_get_cb_this(napi_env env, + 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<v8impl::CallbackWrapper*>(cbinfo); + + *result = info->This(); + return napi_ok; +} + +napi_status napi_get_cb_data(napi_env env, + 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<v8impl::CallbackWrapper*>(cbinfo); + + *result = info->Data(); + return napi_ok; +} + +napi_status napi_call_function(napi_env env, + napi_value recv, + napi_value func, + size_t argc, + const napi_value* argv, + napi_value* result) { + NAPI_PREAMBLE(env); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(env); + v8::Local<v8::Context> context = isolate->GetCurrentContext(); + + v8::Local<v8::Value> v8recv = v8impl::V8LocalValueFromJsValue(recv); + + v8::Local<v8::Value> v8value = v8impl::V8LocalValueFromJsValue(func); + RETURN_STATUS_IF_FALSE(v8value->IsFunction(), napi_invalid_arg); + + v8::Local<v8::Function> v8func = v8value.As<v8::Function>(); + auto maybe = v8func->Call(context, v8recv, argc, + reinterpret_cast<v8::Local<v8::Value>*>(const_cast<napi_value*>(argv))); + + if (try_catch.HasCaught()) { + return napi_set_last_error(napi_pending_exception); + } else { + if (result != nullptr) { + CHECK_MAYBE_EMPTY(maybe, napi_generic_failure); + *result = v8impl::JsValueFromV8LocalValue(maybe.ToLocalChecked()); + } + return napi_ok; + } +} + +napi_status napi_get_global(napi_env env, napi_value* result) { + CHECK_ARG(env); + CHECK_ARG(result); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(env); + // 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<v8::Context> context = isolate->GetCurrentContext(); + *result = v8impl::JsValueFromV8LocalValue(context->Global()); + + return napi_ok; +} + +napi_status napi_throw(napi_env env, napi_value error) { + NAPI_PREAMBLE(env); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(env); + + 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 env, const char* msg) { + NAPI_PREAMBLE(env); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(env); + v8::Local<v8::String> 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 env, const char* msg) { + NAPI_PREAMBLE(env); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(env); + v8::Local<v8::String> 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 env, const char* msg) { + NAPI_PREAMBLE(env); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(env); + v8::Local<v8::String> 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 env, 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<v8::Value> val = v8impl::V8LocalValueFromJsValue(value); + *result = val->IsNativeError(); + + return napi_ok; +} + +napi_status napi_get_value_double(napi_env env, + napi_value value, + double* result) { + // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw + // JS exceptions. + CHECK_ARG(env); + CHECK_ARG(value); + CHECK_ARG(result); + + v8::Local<v8::Value> val = v8impl::V8LocalValueFromJsValue(value); + RETURN_STATUS_IF_FALSE(val->IsNumber(), napi_number_expected); + + *result = val.As<v8::Number>()->Value(); + + return napi_ok; +} + +napi_status napi_get_value_int32(napi_env env, + napi_value value, + int32_t* result) { + // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw + // JS exceptions. + CHECK_ARG(env); + CHECK_ARG(value); + CHECK_ARG(result); + + v8::Local<v8::Value> val = v8impl::V8LocalValueFromJsValue(value); + RETURN_STATUS_IF_FALSE(val->IsNumber(), napi_number_expected); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(env); + v8::Local<v8::Context> context = isolate->GetCurrentContext(); + *result = val->Int32Value(context).ToChecked(); + + return napi_ok; +} + +napi_status napi_get_value_uint32(napi_env env, + napi_value value, + uint32_t* result) { + // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw + // JS exceptions. + CHECK_ARG(env); + CHECK_ARG(value); + CHECK_ARG(result); + + v8::Local<v8::Value> val = v8impl::V8LocalValueFromJsValue(value); + RETURN_STATUS_IF_FALSE(val->IsNumber(), napi_number_expected); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(env); + v8::Local<v8::Context> context = isolate->GetCurrentContext(); + *result = val->Uint32Value(context).ToChecked(); + + return napi_ok; +} + +napi_status napi_get_value_int64(napi_env env, + napi_value value, + int64_t* result) { + // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw + // JS exceptions. + CHECK_ARG(env); + CHECK_ARG(value); + CHECK_ARG(result); + + v8::Local<v8::Value> val = v8impl::V8LocalValueFromJsValue(value); + RETURN_STATUS_IF_FALSE(val->IsNumber(), napi_number_expected); + + // v8::Value::IntegerValue() converts NaN to INT64_MIN, inconsistent with + // v8::Value::Int32Value() that converts NaN to 0. So special-case NaN here. + double doubleValue = val.As<v8::Number>()->Value(); + if (std::isnan(doubleValue)) { + *result = 0; + } else { + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(env); + v8::Local<v8::Context> context = isolate->GetCurrentContext(); + *result = val->IntegerValue(context).ToChecked(); + } + + return napi_ok; +} + +napi_status napi_get_value_bool(napi_env env, napi_value value, bool* result) { + // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw + // JS exceptions. + CHECK_ARG(value); + CHECK_ARG(result); + + v8::Local<v8::Value> val = v8impl::V8LocalValueFromJsValue(value); + RETURN_STATUS_IF_FALSE(val->IsBoolean(), napi_boolean_expected); + + *result = val.As<v8::Boolean>()->Value(); + + return napi_ok; +} + +// Gets the number of CHARACTERS in the string. +napi_status napi_get_value_string_length(napi_env env, + napi_value value, + size_t* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(result); + + v8::Local<v8::Value> val = v8impl::V8LocalValueFromJsValue(value); + RETURN_STATUS_IF_FALSE(val->IsString(), napi_string_expected); + + *result = val.As<v8::String>()->Length(); + + 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 bufsize +// is insufficient, the string will be truncated, including a null terminator. +// If buf is NULL, this method returns the length of the string (in bytes) +// via the result parameter. +// The result argument is optional unless buf is NULL. +napi_status napi_get_value_string_utf8(napi_env env, + napi_value value, + char* buf, + size_t bufsize, + size_t* result) { + NAPI_PREAMBLE(env); + + v8::Local<v8::Value> val = v8impl::V8LocalValueFromJsValue(value); + RETURN_STATUS_IF_FALSE(val->IsString(), napi_string_expected); + + if (!buf) { + CHECK_ARG(result); + *result = val.As<v8::String>()->Utf8Length(); + } else { + int copied = val.As<v8::String>()->WriteUtf8( + buf, bufsize, nullptr, v8::String::REPLACE_INVALID_UTF8); + + if (result != nullptr) { + *result = copied; + } + } + + 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 bufsize is insufficient, the string will be truncated, including a null +// terminator. If buf is NULL, this method returns the length of the string +// (in 2-byte code units) via the result parameter. +// The result argument is optional unless buf is NULL. +napi_status napi_get_value_string_utf16(napi_env env, + napi_value value, + char16_t* buf, + size_t bufsize, + size_t* result) { + NAPI_PREAMBLE(env); + + v8::Local<v8::Value> val = v8impl::V8LocalValueFromJsValue(value); + RETURN_STATUS_IF_FALSE(val->IsString(), napi_string_expected); + + if (!buf) { + CHECK_ARG(result); + // V8 assumes UTF-16 length is the same as the number of characters. + *result = val.As<v8::String>()->Length(); + } else { + int copied = val.As<v8::String>()->Write( + reinterpret_cast<uint16_t*>(buf), 0, bufsize, v8::String::NO_OPTIONS); + + if (result != nullptr) { + *result = copied; + } + } + + return GET_RETURN_STATUS(); +} + +napi_status napi_coerce_to_object(napi_env env, + napi_value value, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(result); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(env); + v8::Local<v8::Context> context = isolate->GetCurrentContext(); + v8::Local<v8::Object> obj; + CHECK_TO_OBJECT(context, obj, value); + + *result = v8impl::JsValueFromV8LocalValue(obj); + return GET_RETURN_STATUS(); +} + +napi_status napi_coerce_to_bool(napi_env env, + napi_value value, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(result); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(env); + v8::Local<v8::Context> context = isolate->GetCurrentContext(); + v8::Local<v8::Boolean> b; + + CHECK_TO_BOOL(context, b, value); + + *result = v8impl::JsValueFromV8LocalValue(b); + return GET_RETURN_STATUS(); +} + +napi_status napi_coerce_to_number(napi_env env, + napi_value value, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(result); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(env); + v8::Local<v8::Context> context = isolate->GetCurrentContext(); + v8::Local<v8::Number> num; + + CHECK_TO_NUMBER(context, num, value); + + *result = v8impl::JsValueFromV8LocalValue(num); + return GET_RETURN_STATUS(); +} + +napi_status napi_coerce_to_string(napi_env env, + napi_value value, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(result); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(env); + v8::Local<v8::Context> context = isolate->GetCurrentContext(); + v8::Local<v8::String> str; + + CHECK_TO_STRING(context, str, value); + + *result = v8impl::JsValueFromV8LocalValue(str); + return GET_RETURN_STATUS(); +} + +napi_status napi_wrap(napi_env env, + napi_value js_object, + void* native_object, + napi_finalize finalize_cb, + void* finalize_hint, + napi_ref* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(js_object); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(env); + v8::Local<v8::Object> obj = + v8impl::V8LocalValueFromJsValue(js_object).As<v8::Object>(); + + // 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->SetInternalField(0, v8::External::New(isolate, native_object)); + + 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 = v8impl::Reference::New( + isolate, obj, 0, false, finalize_cb, native_object, finalize_hint); + *result = reinterpret_cast<napi_ref>(reference); + } else if (finalize_cb != nullptr) { + // Create a self-deleting reference just for the finalize callback. + v8impl::Reference::New( + isolate, obj, 0, true, finalize_cb, native_object, finalize_hint); + } + + return GET_RETURN_STATUS(); +} + +napi_status napi_unwrap(napi_env env, napi_value js_object, void** result) { + // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw + // JS exceptions. + CHECK_ARG(js_object); + CHECK_ARG(result); + + v8::Local<v8::Value> value = v8impl::V8LocalValueFromJsValue(js_object); + RETURN_STATUS_IF_FALSE(value->IsObject(), napi_invalid_arg); + v8::Local<v8::Object> obj = value.As<v8::Object>(); + + // 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); + + v8::Local<v8::Value> unwrappedValue = obj->GetInternalField(0); + RETURN_STATUS_IF_FALSE(unwrappedValue->IsExternal(), napi_invalid_arg); + + *result = unwrappedValue.As<v8::External>()->Value(); + + return napi_ok; +} + +napi_status napi_create_external(napi_env env, + void* data, + napi_finalize finalize_cb, + void* finalize_hint, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(result); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(env); + + v8::Local<v8::Value> external_value = v8::External::New(isolate, data); + + // The Reference object will delete itself after invoking the finalizer + // callback. + v8impl::Reference::New(isolate, + external_value, + 0, + true, + finalize_cb, + data, + finalize_hint); + + *result = v8impl::JsValueFromV8LocalValue(external_value); + + return GET_RETURN_STATUS(); +} + +napi_status napi_get_value_external(napi_env env, + napi_value value, + void** result) { + NAPI_PREAMBLE(env); + CHECK_ARG(value); + CHECK_ARG(result); + + v8::Local<v8::Value> val = v8impl::V8LocalValueFromJsValue(value); + RETURN_STATUS_IF_FALSE(val->IsExternal(), napi_invalid_arg); + + v8::Local<v8::External> external_value = val.As<v8::External>(); + *result = external_value->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 env, + napi_value value, + uint32_t initial_refcount, + napi_ref* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(result); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(env); + + v8impl::Reference* reference = v8impl::Reference::New( + isolate, v8impl::V8LocalValueFromJsValue(value), initial_refcount, false); + + *result = reinterpret_cast<napi_ref>(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 env, napi_ref ref) { + // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw + // JS exceptions. + CHECK_ARG(ref); + + v8impl::Reference::Delete(reinterpret_cast<v8impl::Reference*>(ref)); + + 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_ref(napi_env env, napi_ref ref, uint32_t* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(ref); + + v8impl::Reference* reference = reinterpret_cast<v8impl::Reference*>(ref); + uint32_t count = reference->Ref(); + + 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_unref(napi_env env, napi_ref ref, uint32_t* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(ref); + + v8impl::Reference* reference = reinterpret_cast<v8impl::Reference*>(ref); + + if (reference->RefCount() == 0) { + return napi_set_last_error(napi_generic_failure); + } + + uint32_t count = reference->Unref(); + + 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 env, + napi_ref ref, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(ref); + CHECK_ARG(result); + + v8impl::Reference* reference = reinterpret_cast<v8impl::Reference*>(ref); + *result = v8impl::JsValueFromV8LocalValue(reference->Get()); + + return GET_RETURN_STATUS(); +} + +napi_status napi_open_handle_scope(napi_env env, napi_handle_scope* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(result); + + *result = v8impl::JsHandleScopeFromV8HandleScope( + new v8impl::HandleScopeWrapper(v8impl::V8IsolateFromJsEnv(env))); + return GET_RETURN_STATUS(); +} + +napi_status napi_close_handle_scope(napi_env env, napi_handle_scope scope) { + NAPI_PREAMBLE(env); + CHECK_ARG(scope); + + delete v8impl::V8HandleScopeFromJsHandleScope(scope); + return GET_RETURN_STATUS(); +} + +napi_status napi_open_escapable_handle_scope( + napi_env env, + napi_escapable_handle_scope* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(result); + + *result = v8impl::JsEscapableHandleScopeFromV8EscapableHandleScope( + new v8impl::EscapableHandleScopeWrapper(v8impl::V8IsolateFromJsEnv(env))); + return GET_RETURN_STATUS(); +} + +napi_status napi_close_escapable_handle_scope( + napi_env env, + napi_escapable_handle_scope scope) { + NAPI_PREAMBLE(env); + CHECK_ARG(scope); + + delete v8impl::V8EscapableHandleScopeFromJsEscapableHandleScope(scope); + return GET_RETURN_STATUS(); +} + +napi_status napi_escape_handle(napi_env env, + napi_escapable_handle_scope scope, + napi_value escapee, + napi_value* result) { + NAPI_PREAMBLE(env); + 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 env, + napi_value constructor, + size_t argc, + const napi_value* argv, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(result); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(env); + v8::Local<v8::Context> context = isolate->GetCurrentContext(); + + v8::Local<v8::Value> v8value = v8impl::V8LocalValueFromJsValue(constructor); + RETURN_STATUS_IF_FALSE(v8value->IsFunction(), napi_invalid_arg); + + v8::Local<v8::Function> ctor = v8value.As<v8::Function>(); + + auto maybe = ctor->NewInstance(context, argc, + reinterpret_cast<v8::Local<v8::Value>*>(const_cast<napi_value*>(argv))); + + CHECK_MAYBE_EMPTY(maybe, napi_generic_failure); + + *result = v8impl::JsValueFromV8LocalValue(maybe.ToLocalChecked()); + return GET_RETURN_STATUS(); +} + +napi_status napi_instanceof(napi_env env, + napi_value object, + napi_value constructor, + bool* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(result); + + *result = false; + + v8::Local<v8::Object> ctor; + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(env); + v8::Local<v8::Context> context = isolate->GetCurrentContext(); + + CHECK_TO_OBJECT(context, ctor, constructor); + + if (!ctor->IsFunction()) { + napi_throw_type_error(env, "constructor must be a function"); + + return napi_set_last_error(napi_function_expected); + } + + napi_value value, js_result; + napi_status status; + napi_valuetype value_type; + + // Get "Symbol" from the global object + status = napi_get_global(env, &value); + if (status != napi_ok) return status; + status = napi_get_named_property(env, value, "Symbol", &value); + if (status != napi_ok) return status; + status = napi_typeof(env, value, &value_type); + if (status != napi_ok) return status; + + // Get "hasInstance" from Symbol + if (value_type == napi_function) { + status = napi_get_named_property(env, value, "hasInstance", &value); + if (status != napi_ok) return status; + status = napi_typeof(env, value, &value_type); + if (status != napi_ok) return status; + + // Retrieve the function at the Symbol(hasInstance) key of the constructor + if (value_type == napi_symbol) { + status = napi_get_property(env, constructor, value, &value); + if (status != napi_ok) return status; + status = napi_typeof(env, value, &value_type); + if (status != napi_ok) return status; + + // Call the function to determine whether the object is an instance of the + // constructor + if (value_type == napi_function) { + status = napi_call_function(env, constructor, value, 1, &object, + &js_result); + if (status != napi_ok) return status; + return napi_get_value_bool(env, js_result, result); + } + } + } + + // If running constructor[Symbol.hasInstance](object) did not work, we perform + // a traditional instanceof (early Node.js 6.x). + + v8::Local<v8::String> prototype_string; + CHECK_NEW_FROM_UTF8(isolate, prototype_string, "prototype"); + + auto maybe_prototype = ctor->Get(context, prototype_string); + CHECK_MAYBE_EMPTY(maybe_prototype, napi_generic_failure); + + v8::Local<v8::Value> prototype_property = maybe_prototype.ToLocalChecked(); + if (!prototype_property->IsObject()) { + napi_throw_type_error(env, "constructor.prototype must be an object"); + + return napi_set_last_error(napi_object_expected); + } + + auto maybe_ctor = prototype_property->ToObject(context); + CHECK_MAYBE_EMPTY(maybe_ctor, napi_generic_failure); + ctor = maybe_ctor.ToLocalChecked(); + + v8::Local<v8::Value> current_obj = v8impl::V8LocalValueFromJsValue(object); + if (!current_obj->StrictEquals(ctor)) { + for (v8::Local<v8::Value> original_obj = current_obj; + !(current_obj->IsNull() || current_obj->IsUndefined());) { + if (current_obj->StrictEquals(ctor)) { + *result = !(original_obj->IsNumber() || + original_obj->IsBoolean() || + original_obj->IsString()); + break; + } + v8::Local<v8::Object> obj; + CHECK_TO_OBJECT(context, obj, v8impl::JsValueFromV8LocalValue( + current_obj)); + current_obj = obj->GetPrototype(); + } + } + + return GET_RETURN_STATUS(); +} + +napi_status napi_make_callback(napi_env env, + napi_value recv, + napi_value func, + size_t argc, + const napi_value* argv, + napi_value* result) { + NAPI_PREAMBLE(env); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(env); + v8::Local<v8::Object> v8recv = + v8impl::V8LocalValueFromJsValue(recv).As<v8::Object>(); + v8::Local<v8::Function> v8func = + v8impl::V8LocalValueFromJsValue(func).As<v8::Function>(); + + v8::Local<v8::Value> callback_result = node::MakeCallback( + isolate, v8recv, v8func, argc, + reinterpret_cast<v8::Local<v8::Value>*>(const_cast<napi_value*>(argv))); + + if (result != nullptr) { + *result = v8impl::JsValueFromV8LocalValue(callback_result); + } + + return GET_RETURN_STATUS(); +} + +// Methods to support catching exceptions +napi_status napi_is_exception_pending(napi_env env, bool* result) { + // NAPI_PREAMBLE is not used here: this function must execute when there is a + // pending exception. + CHECK_ARG(env); + CHECK_ARG(result); + + *result = !v8impl::TryCatch::LastException().IsEmpty(); + return napi_ok; +} + +napi_status napi_get_and_clear_last_exception(napi_env env, + napi_value* result) { + // NAPI_PREAMBLE is not used here: this function must execute when there is a + // pending exception. + CHECK_ARG(env); + CHECK_ARG(result); + + if (v8impl::TryCatch::LastException().IsEmpty()) { + return napi_get_undefined(env, result); + } else { + *result = v8impl::JsValueFromV8LocalValue(v8::Local<v8::Value>::New( + v8impl::V8IsolateFromJsEnv(env), v8impl::TryCatch::LastException())); + v8impl::TryCatch::LastException().Reset(); + } + + return napi_ok; +} + +napi_status napi_create_buffer(napi_env env, + size_t length, + void** data, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(data); + CHECK_ARG(result); + + auto maybe = node::Buffer::New(v8impl::V8IsolateFromJsEnv(env), length); + + CHECK_MAYBE_EMPTY(maybe, napi_generic_failure); + + v8::Local<v8::Object> buffer = maybe.ToLocalChecked(); + + *result = v8impl::JsValueFromV8LocalValue(buffer); + *data = node::Buffer::Data(buffer); + + return GET_RETURN_STATUS(); +} + +napi_status napi_create_external_buffer(napi_env env, + size_t length, + void* data, + napi_finalize finalize_cb, + void* finalize_hint, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(result); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(env); + + // The finalizer object will delete itself after invoking the callback. + v8impl::Finalizer* finalizer = v8impl::Finalizer::New( + isolate, finalize_cb, nullptr, finalize_hint); + + auto maybe = node::Buffer::New(isolate, + static_cast<char*>(data), + length, + v8impl::Finalizer::FinalizeBufferCallback, + finalizer); + + CHECK_MAYBE_EMPTY(maybe, napi_generic_failure); + + *result = v8impl::JsValueFromV8LocalValue(maybe.ToLocalChecked()); + return GET_RETURN_STATUS(); +} + +napi_status napi_create_buffer_copy(napi_env env, + size_t length, + const void* data, + void** result_data, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(result); + + auto maybe = node::Buffer::Copy(v8impl::V8IsolateFromJsEnv(env), + static_cast<const char*>(data), length); + + CHECK_MAYBE_EMPTY(maybe, napi_generic_failure); + + v8::Local<v8::Object> buffer = maybe.ToLocalChecked(); + *result = v8impl::JsValueFromV8LocalValue(buffer); + + if (result_data != nullptr) { + *result_data = node::Buffer::Data(buffer); + } + + return GET_RETURN_STATUS(); +} + +napi_status napi_is_buffer(napi_env env, napi_value value, bool* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(result); + + *result = node::Buffer::HasInstance(v8impl::V8LocalValueFromJsValue(value)); + return GET_RETURN_STATUS(); +} + +napi_status napi_get_buffer_info(napi_env env, + napi_value value, + void** data, + size_t* length) { + NAPI_PREAMBLE(env); + + v8::Local<v8::Object> buffer = + v8impl::V8LocalValueFromJsValue(value).As<v8::Object>(); + + if (data != nullptr) { + *data = node::Buffer::Data(buffer); + } + if (length != nullptr) { + *length = node::Buffer::Length(buffer); + } + + return GET_RETURN_STATUS(); +} + +napi_status napi_is_arraybuffer(napi_env env, napi_value value, bool* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(result); + + v8::Local<v8::Value> val = v8impl::V8LocalValueFromJsValue(value); + *result = val->IsArrayBuffer(); + + return GET_RETURN_STATUS(); +} + +napi_status napi_create_arraybuffer(napi_env env, + size_t byte_length, + void** data, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(result); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(env); + v8::Local<v8::ArrayBuffer> 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 env, + void* external_data, + size_t byte_length, + napi_finalize finalize_cb, + void* finalize_hint, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(result); + + v8::Isolate* isolate = v8impl::V8IsolateFromJsEnv(env); + v8::Local<v8::ArrayBuffer> buffer = + v8::ArrayBuffer::New(isolate, external_data, byte_length); + + if (finalize_cb != nullptr) { + // Create a self-deleting weak reference that invokes the finalizer + // callback. + v8impl::Reference::New(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 env, + napi_value arraybuffer, + void** data, + size_t* byte_length) { + NAPI_PREAMBLE(env); + + v8::Local<v8::Value> value = v8impl::V8LocalValueFromJsValue(arraybuffer); + RETURN_STATUS_IF_FALSE(value->IsArrayBuffer(), napi_invalid_arg); + + v8::ArrayBuffer::Contents contents = + value.As<v8::ArrayBuffer>()->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 env, napi_value value, bool* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(result); + + v8::Local<v8::Value> val = v8impl::V8LocalValueFromJsValue(value); + *result = val->IsTypedArray(); + + return GET_RETURN_STATUS(); +} + +napi_status napi_create_typedarray(napi_env env, + napi_typedarray_type type, + size_t length, + napi_value arraybuffer, + size_t byte_offset, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(result); + + v8::Local<v8::Value> value = v8impl::V8LocalValueFromJsValue(arraybuffer); + RETURN_STATUS_IF_FALSE(value->IsArrayBuffer(), napi_invalid_arg); + + v8::Local<v8::ArrayBuffer> buffer = value.As<v8::ArrayBuffer>(); + v8::Local<v8::TypedArray> typedArray; + + switch (type) { + case napi_int8_array: + typedArray = v8::Int8Array::New(buffer, byte_offset, length); + break; + case napi_uint8_array: + typedArray = v8::Uint8Array::New(buffer, byte_offset, length); + break; + case napi_uint8_clamped_array: + typedArray = v8::Uint8ClampedArray::New(buffer, byte_offset, length); + break; + case napi_int16_array: + typedArray = v8::Int16Array::New(buffer, byte_offset, length); + break; + case napi_uint16_array: + typedArray = v8::Uint16Array::New(buffer, byte_offset, length); + break; + case napi_int32_array: + typedArray = v8::Int32Array::New(buffer, byte_offset, length); + break; + case napi_uint32_array: + typedArray = v8::Uint32Array::New(buffer, byte_offset, length); + break; + case napi_float32_array: + typedArray = v8::Float32Array::New(buffer, byte_offset, length); + break; + case napi_float64_array: + 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 env, + napi_value typedarray, + napi_typedarray_type* type, + size_t* length, + void** data, + napi_value* arraybuffer, + size_t* byte_offset) { + NAPI_PREAMBLE(env); + + v8::Local<v8::Value> value = v8impl::V8LocalValueFromJsValue(typedarray); + RETURN_STATUS_IF_FALSE(value->IsTypedArray(), napi_invalid_arg); + + v8::Local<v8::TypedArray> array = value.As<v8::TypedArray>(); + + if (type != nullptr) { + if (value->IsInt8Array()) { + *type = napi_int8_array; + } else if (value->IsUint8Array()) { + *type = napi_uint8_array; + } else if (value->IsUint8ClampedArray()) { + *type = napi_uint8_clamped_array; + } else if (value->IsInt16Array()) { + *type = napi_int16_array; + } else if (value->IsUint16Array()) { + *type = napi_uint16_array; + } else if (value->IsInt32Array()) { + *type = napi_int32_array; + } else if (value->IsUint32Array()) { + *type = napi_uint32_array; + } else if (value->IsFloat32Array()) { + *type = napi_float32_array; + } else if (value->IsFloat64Array()) { + *type = napi_float64_array; + } + } + + if (length != nullptr) { + *length = array->Length(); + } + + v8::Local<v8::ArrayBuffer> buffer = array->Buffer(); + if (data != nullptr) { + *data = static_cast<uint8_t*>(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 0000000000..da14a01654 --- /dev/null +++ b/src/node_api.h @@ -0,0 +1,479 @@ +/****************************************************************************** + * 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 <stddef.h> +#include <stdbool.h> +#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 + + +typedef void (*napi_addon_register_func)(napi_env env, + napi_value exports, + napi_value module, + void* priv); + +typedef struct { + int nm_version; + unsigned int nm_flags; + const char* nm_filename; + napi_addon_register_func nm_register_func; + const char* nm_modname; + void* nm_priv; + void* reserved[4]; +} napi_module; + +#define NAPI_MODULE_VERSION 1 + +#if defined(_MSC_VER) +#pragma section(".CRT$XCU", read) +#define NAPI_C_CTOR(fn) \ + static void __cdecl fn(void); \ + __declspec(dllexport, allocate(".CRT$XCU")) void(__cdecl * fn##_)(void) = \ + fn; \ + static void __cdecl fn(void) +#else +#define NAPI_C_CTOR(fn) \ + static void fn(void) __attribute__((constructor)); \ + static void fn(void) +#endif + +#ifdef __cplusplus +#define EXTERN_C_START extern "C" { +#define EXTERN_C_END } +#else +#define EXTERN_C_START +#define EXTERN_C_END +#endif + +#define NAPI_MODULE_X(modname, regfunc, priv, flags) \ + EXTERN_C_START \ + static napi_module _module = \ + { \ + NAPI_MODULE_VERSION, \ + flags, \ + __FILE__, \ + regfunc, \ + #modname, \ + priv, \ + {0}, \ + }; \ + NAPI_C_CTOR(_register_ ## modname) { \ + napi_module_register(&_module); \ + } \ + EXTERN_C_END + +#define NAPI_MODULE(modname, regfunc) \ + NAPI_MODULE_X(modname, regfunc, NULL, 0) + +EXTERN_C_START + +NAPI_EXTERN void napi_module_register(napi_module* mod); + +NAPI_EXTERN napi_status +napi_get_last_error_info(napi_env env, + const napi_extended_error_info** result); + +// Getters for defined singletons +NAPI_EXTERN napi_status napi_get_undefined(napi_env env, napi_value* result); +NAPI_EXTERN napi_status napi_get_null(napi_env env, napi_value* result); +NAPI_EXTERN napi_status napi_get_global(napi_env env, napi_value* result); +NAPI_EXTERN napi_status napi_get_boolean(napi_env env, + bool value, + napi_value* result); + +// Methods to create Primitive types/Objects +NAPI_EXTERN napi_status napi_create_object(napi_env env, napi_value* result); +NAPI_EXTERN napi_status napi_create_array(napi_env env, napi_value* result); +NAPI_EXTERN napi_status napi_create_array_with_length(napi_env env, + size_t length, + napi_value* result); +NAPI_EXTERN napi_status napi_create_number(napi_env env, + double value, + napi_value* result); +NAPI_EXTERN napi_status napi_create_string_utf8(napi_env env, + const char* str, + size_t length, + napi_value* result); +NAPI_EXTERN napi_status napi_create_string_utf16(napi_env env, + const char16_t* str, + size_t length, + napi_value* result); +NAPI_EXTERN napi_status napi_create_symbol(napi_env env, + napi_value description, + napi_value* result); +NAPI_EXTERN napi_status napi_create_function(napi_env env, + const char* utf8name, + 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_typeof(napi_env env, + napi_value value, + napi_valuetype* result); +NAPI_EXTERN napi_status napi_get_value_double(napi_env env, + napi_value value, + double* result); +NAPI_EXTERN napi_status napi_get_value_int32(napi_env env, + napi_value value, + int32_t* result); +NAPI_EXTERN napi_status napi_get_value_uint32(napi_env env, + napi_value value, + uint32_t* result); +NAPI_EXTERN napi_status napi_get_value_int64(napi_env env, + napi_value value, + int64_t* result); +NAPI_EXTERN napi_status napi_get_value_bool(napi_env env, + napi_value value, + bool* result); + +// Gets the number of CHARACTERS in the string. +NAPI_EXTERN napi_status napi_get_value_string_length(napi_env env, + napi_value value, + size_t* result); + +// Copies UTF-8 encoded bytes from a string into a buffer. +NAPI_EXTERN napi_status napi_get_value_string_utf8(napi_env env, + napi_value value, + char* buf, + size_t bufsize, + size_t* result); + +// Copies UTF-16 encoded bytes from a string into a buffer. +NAPI_EXTERN napi_status napi_get_value_string_utf16(napi_env env, + napi_value value, + char16_t* buf, + size_t bufsize, + size_t* result); + +// Methods to coerce values +// These APIs may execute user scripts +NAPI_EXTERN napi_status napi_coerce_to_bool(napi_env env, + napi_value value, + napi_value* result); +NAPI_EXTERN napi_status napi_coerce_to_number(napi_env env, + napi_value value, + napi_value* result); +NAPI_EXTERN napi_status napi_coerce_to_object(napi_env env, + napi_value value, + napi_value* result); +NAPI_EXTERN napi_status napi_coerce_to_string(napi_env env, + napi_value value, + napi_value* result); + +// Methods to work with Objects +NAPI_EXTERN napi_status napi_get_prototype(napi_env env, + napi_value object, + napi_value* result); +NAPI_EXTERN napi_status napi_get_property_names(napi_env env, + napi_value object, + napi_value* result); +NAPI_EXTERN napi_status napi_set_property(napi_env env, + napi_value object, + napi_value key, + napi_value value); +NAPI_EXTERN napi_status napi_has_property(napi_env env, + napi_value object, + napi_value key, + bool* result); +NAPI_EXTERN napi_status napi_get_property(napi_env env, + napi_value object, + napi_value key, + napi_value* result); +NAPI_EXTERN napi_status napi_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, + size_t property_count, + const napi_property_descriptor* properties); + +// Methods to work with Arrays +NAPI_EXTERN napi_status napi_is_array(napi_env env, + napi_value value, + bool* result); +NAPI_EXTERN napi_status napi_get_array_length(napi_env env, + napi_value value, + uint32_t* result); + +// Methods to compare values +NAPI_EXTERN napi_status napi_strict_equals(napi_env env, + napi_value lhs, + napi_value rhs, + bool* result); + +// Methods to work with Functions +NAPI_EXTERN napi_status napi_call_function(napi_env env, + napi_value recv, + napi_value func, + size_t argc, + const napi_value* argv, + napi_value* result); +NAPI_EXTERN napi_status napi_new_instance(napi_env env, + napi_value constructor, + size_t argc, + const napi_value* argv, + napi_value* result); +NAPI_EXTERN napi_status napi_instanceof(napi_env env, + napi_value object, + napi_value constructor, + bool* result); + +// Napi version of node::MakeCallback(...) +NAPI_EXTERN napi_status napi_make_callback(napi_env env, + napi_value recv, + napi_value func, + size_t 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 + size_t* argc, // [in-out] Specifies the size of the provided argv array + // and receives the actual count of args. + napi_value* argv, // [out] Array of values + napi_value* 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, + size_t* result); +NAPI_EXTERN napi_status napi_get_cb_args(napi_env env, + napi_callback_info cbinfo, + napi_value* buf, + size_t bufsize); +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, + size_t property_count, + const napi_property_descriptor* properties, + napi_value* result); + +// Methods to work with external data objects +NAPI_EXTERN napi_status napi_wrap(napi_env env, + napi_value js_object, + void* native_object, + napi_finalize finalize_cb, + void* finalize_hint, + napi_ref* result); +NAPI_EXTERN napi_status napi_unwrap(napi_env env, + napi_value js_object, + void** result); +NAPI_EXTERN napi_status napi_create_external(napi_env env, + void* data, + napi_finalize finalize_cb, + void* finalize_hint, + napi_value* result); +NAPI_EXTERN napi_status napi_get_value_external(napi_env env, + napi_value value, + void** result); + +// Methods to control object lifespan + +// Set initial_refcount to 0 for a weak reference, >0 for a strong reference. +NAPI_EXTERN napi_status napi_create_reference(napi_env env, + napi_value value, + uint32_t initial_refcount, + napi_ref* result); + +// Deletes a reference. The referenced value is released, and may +// be GC'd unless there are other references to it. +NAPI_EXTERN napi_status napi_delete_reference(napi_env env, napi_ref ref); + +// Increments the reference count, optionally returning the resulting count. +// After this call the reference will be a strong reference because its +// refcount is >0, and the referenced object is effectively "pinned". +// Calling this when the refcount is 0 and the object is unavailable +// results in an error. +NAPI_EXTERN napi_status napi_reference_ref(napi_env env, + napi_ref ref, + uint32_t* result); + +// Decrements the reference count, optionally returning the resulting count. +// If the result is 0 the reference is now weak and the object may be GC'd +// at any time if there are no other references. Calling this when the +// refcount is already 0 results in an error. +NAPI_EXTERN napi_status napi_reference_unref(napi_env env, + napi_ref ref, + uint32_t* result); + +// Attempts to get a referenced value. If the reference is weak, +// the value might no longer be available, in that case the call +// is still successful but the result is NULL. +NAPI_EXTERN napi_status napi_get_reference_value(napi_env env, + napi_ref ref, + napi_value* result); + +NAPI_EXTERN napi_status napi_open_handle_scope(napi_env env, + napi_handle_scope* result); +NAPI_EXTERN napi_status napi_close_handle_scope(napi_env env, + napi_handle_scope scope); +NAPI_EXTERN napi_status +napi_open_escapable_handle_scope(napi_env env, + napi_escapable_handle_scope* result); +NAPI_EXTERN napi_status +napi_close_escapable_handle_scope(napi_env env, + napi_escapable_handle_scope scope); + +NAPI_EXTERN napi_status napi_escape_handle(napi_env env, + napi_escapable_handle_scope scope, + napi_value escapee, + napi_value* result); + +// 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 length, + void** data, + napi_value* result); +NAPI_EXTERN napi_status napi_create_external_buffer(napi_env env, + size_t length, + void* data, + napi_finalize finalize_cb, + void* finalize_hint, + napi_value* result); +NAPI_EXTERN napi_status napi_create_buffer_copy(napi_env env, + size_t length, + const void* data, + void** result_data, + napi_value* result); +NAPI_EXTERN napi_status napi_is_buffer(napi_env env, + napi_value value, + bool* result); +NAPI_EXTERN napi_status napi_get_buffer_info(napi_env env, + napi_value value, + void** data, + size_t* length); + +// Methods to work with array buffers and typed arrays +NAPI_EXTERN napi_status napi_is_arraybuffer(napi_env env, + napi_value value, + bool* result); +NAPI_EXTERN napi_status napi_create_arraybuffer(napi_env env, + size_t byte_length, + void** data, + napi_value* result); +NAPI_EXTERN napi_status +napi_create_external_arraybuffer(napi_env env, + void* external_data, + size_t byte_length, + napi_finalize finalize_cb, + void* finalize_hint, + napi_value* result); +NAPI_EXTERN napi_status napi_get_arraybuffer_info(napi_env env, + napi_value arraybuffer, + void** data, + size_t* byte_length); +NAPI_EXTERN napi_status napi_is_typedarray(napi_env env, + napi_value value, + bool* result); +NAPI_EXTERN napi_status napi_create_typedarray(napi_env env, + napi_typedarray_type type, + size_t length, + napi_value arraybuffer, + size_t byte_offset, + napi_value* result); +NAPI_EXTERN napi_status napi_get_typedarray_info(napi_env env, + napi_value typedarray, + napi_typedarray_type* type, + size_t* length, + void** data, + napi_value* arraybuffer, + size_t* byte_offset); +EXTERN_C_END + +#endif // SRC_NODE_API_H__ diff --git a/src/node_api_types.h b/src/node_api_types.h new file mode 100644 index 0000000000..7cb242368a --- /dev/null +++ b/src/node_api_types.h @@ -0,0 +1,95 @@ +#ifndef SRC_NODE_API_TYPES_H_ +#define SRC_NODE_API_TYPES_H_ + +#include <stddef.h> +#include <stdint.h> + +#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 env, + napi_callback_info info); +typedef void (*napi_finalize)(napi_env env, + void* finalize_data, + void* finalize_hint); + +typedef enum { + 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, +} napi_property_attributes; + +typedef struct { + const char* utf8name; + + napi_callback method; + napi_callback getter; + napi_callback setter; + napi_value value; + + napi_property_attributes attributes; + void* data; +} napi_property_descriptor; + +typedef enum { + // ES6 types (corresponds to typeof) + napi_undefined, + napi_null, + napi_boolean, + napi_number, + napi_string, + napi_symbol, + napi_object, + napi_function, + napi_external, +} napi_valuetype; + +typedef enum { + napi_int8_array, + napi_uint8_array, + napi_uint8_clamped_array, + napi_int16_array, + napi_uint16_array, + napi_int32_array, + napi_uint32_array, + napi_float32_array, + napi_float64_array, +} napi_typedarray_type; + +typedef enum { + napi_ok, + napi_invalid_arg, + napi_object_expected, + napi_string_expected, + napi_function_expected, + napi_number_expected, + napi_boolean_expected, + napi_array_expected, + napi_generic_failure, + napi_pending_exception, + napi_status_last +} napi_status; + +typedef struct { + const char* error_message; + void* engine_reserved; + uint32_t engine_error_code; + napi_status error_code; +} napi_extended_error_info; + +#endif // SRC_NODE_API_TYPES_H_ diff --git a/test/addons-napi/.gitignore b/test/addons-napi/.gitignore new file mode 100644 index 0000000000..bde1cf3ab9 --- /dev/null +++ b/test/addons-napi/.gitignore @@ -0,0 +1,7 @@ +.buildstamp +.docbuildstamp +Makefile +*.Makefile +*.mk +gyp-mac-tool +/*/build diff --git a/test/addons-napi/1_hello_world/binding.c b/test/addons-napi/1_hello_world/binding.c new file mode 100644 index 0000000000..882508b954 --- /dev/null +++ b/test/addons-napi/1_hello_world/binding.c @@ -0,0 +1,22 @@ +#include <node_api.h> + +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 0000000000..62381d5e54 --- /dev/null +++ b/test/addons-napi/1_hello_world/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "binding", + "sources": [ "binding.c" ] + } + ] +} diff --git a/test/addons-napi/1_hello_world/test.js b/test/addons-napi/1_hello_world/test.js new file mode 100644 index 0000000000..c975c48a73 --- /dev/null +++ b/test/addons-napi/1_hello_world/test.js @@ -0,0 +1,6 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); +const addon = require(`./build/${common.buildType}/binding`); + +assert.strictEqual(addon.hello(), 'world'); diff --git a/test/addons-napi/2_function_arguments/binding.c b/test/addons-napi/2_function_arguments/binding.c new file mode 100644 index 0000000000..bdc25a235c --- /dev/null +++ b/test/addons-napi/2_function_arguments/binding.c @@ -0,0 +1,58 @@ +#include <node_api.h> + +void Add(napi_env env, napi_callback_info info) { + napi_status status; + + size_t 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_typeof(env, args[0], &valuetype0); + if (status != napi_ok) return; + + napi_valuetype valuetype1; + status = napi_typeof(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 0000000000..62381d5e54 --- /dev/null +++ b/test/addons-napi/2_function_arguments/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "binding", + "sources": [ "binding.c" ] + } + ] +} diff --git a/test/addons-napi/2_function_arguments/test.js b/test/addons-napi/2_function_arguments/test.js new file mode 100644 index 0000000000..e70f76b718 --- /dev/null +++ b/test/addons-napi/2_function_arguments/test.js @@ -0,0 +1,6 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); +const addon = require(`./build/${common.buildType}/binding`); + +assert.strictEqual(addon.add(3, 5), 8); diff --git a/test/addons-napi/3_callbacks/binding.c b/test/addons-napi/3_callbacks/binding.c new file mode 100644 index 0000000000..10e7318bce --- /dev/null +++ b/test/addons-napi/3_callbacks/binding.c @@ -0,0 +1,51 @@ +#include <node_api.h> + +#define NAPI_CALL(env, theCall) \ + if ((theCall) != napi_ok) { \ + const napi_extended_error_info* error; \ + napi_get_last_error_info((env), &error); \ + const char* errorMessage = error->error_message; \ + errorMessage = errorMessage ? errorMessage : "empty error message"; \ + napi_throw_error((env), errorMessage); \ + return; \ + } + +void RunCallback(napi_env env, napi_callback_info info) { + napi_value args[1]; + NAPI_CALL(env, napi_get_cb_args(env, info, args, 1)); + + napi_value cb = args[0]; + + napi_value argv[1]; + NAPI_CALL(env, napi_create_string_utf8(env, "hello world", -1, argv)); + + napi_value global; + NAPI_CALL(env, napi_get_global(env, &global)); + + NAPI_CALL(env, napi_call_function(env, global, cb, 1, argv, NULL)); +} + +void RunCallbackWithRecv(napi_env env, napi_callback_info info) { + napi_value args[2]; + NAPI_CALL(env, napi_get_cb_args(env, info, args, 2)); + + napi_value cb = args[0]; + napi_value recv = args[1]; + + NAPI_CALL(env, napi_call_function(env, recv, cb, 0, NULL, NULL)); +} + +#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 0000000000..62381d5e54 --- /dev/null +++ b/test/addons-napi/3_callbacks/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "binding", + "sources": [ "binding.c" ] + } + ] +} diff --git a/test/addons-napi/3_callbacks/test.js b/test/addons-napi/3_callbacks/test.js new file mode 100644 index 0000000000..25e070d974 --- /dev/null +++ b/test/addons-napi/3_callbacks/test.js @@ -0,0 +1,22 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); +const addon = require(`./build/${common.buildType}/binding`); + +addon.RunCallback(function(msg) { + assert.strictEqual(msg, 'hello world'); +}); + +function testRecv(desiredRecv) { + addon.RunCallbackWithRecv(function() { + assert.strictEqual(this, desiredRecv); + }, desiredRecv); +} + +testRecv(undefined); +testRecv(null); +testRecv(5); +testRecv(true); +testRecv('Hello'); +testRecv([]); +testRecv({}); diff --git a/test/addons-napi/4_object_factory/binding.c b/test/addons-napi/4_object_factory/binding.c new file mode 100644 index 0000000000..0d9902259f --- /dev/null +++ b/test/addons-napi/4_object_factory/binding.c @@ -0,0 +1,31 @@ +#include <node_api.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 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 0000000000..62381d5e54 --- /dev/null +++ b/test/addons-napi/4_object_factory/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "binding", + "sources": [ "binding.c" ] + } + ] +} diff --git a/test/addons-napi/4_object_factory/test.js b/test/addons-napi/4_object_factory/test.js new file mode 100644 index 0000000000..15313c1547 --- /dev/null +++ b/test/addons-napi/4_object_factory/test.js @@ -0,0 +1,8 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); +const addon = require(`./build/${common.buildType}/binding`); + +const obj1 = addon('hello'); +const obj2 = addon('world'); +assert.strictEqual(`${obj1.msg} ${obj2.msg}`, 'hello world'); diff --git a/test/addons-napi/5_function_factory/binding.c b/test/addons-napi/5_function_factory/binding.c new file mode 100644 index 0000000000..867a527ee9 --- /dev/null +++ b/test/addons-napi/5_function_factory/binding.c @@ -0,0 +1,36 @@ +#include <node_api.h> + +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, NULL, &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 0000000000..62381d5e54 --- /dev/null +++ b/test/addons-napi/5_function_factory/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "binding", + "sources": [ "binding.c" ] + } + ] +} diff --git a/test/addons-napi/5_function_factory/test.js b/test/addons-napi/5_function_factory/test.js new file mode 100644 index 0000000000..7521824e1e --- /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 0000000000..99d8339bd0 --- /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 0000000000..d8f91601e9 --- /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 0000000000..67521ae313 --- /dev/null +++ b/test/addons-napi/6_object_wrap/myobject.cc @@ -0,0 +1,201 @@ +#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( + napi_env env, void* nativeObject, void* /*finalize_hint*/) { + MyObject* obj = static_cast<MyObject*>(nativeObject); + delete obj; +} + +#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_typeof(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, + 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<void**>(&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<void**>(&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<void**>(&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_typeof(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<void**>(&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 0000000000..2c8f477537 --- /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 <node_api.h> + +class MyObject { + public: + static void Init(napi_env env, napi_value exports); + static void Destructor(napi_env env, void* nativeObject, void* finalize_hint); + + private: + explicit MyObject(double value_ = 0); + ~MyObject(); + + static 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 0000000000..4d89da6a43 --- /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 0000000000..0aede8cbb8 --- /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 0000000000..d8f91601e9 --- /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 0000000000..a3fbf6f9ec --- /dev/null +++ b/test/addons-napi/7_factory_wrap/myobject.cc @@ -0,0 +1,110 @@ +#include "myobject.h" + +MyObject::MyObject() : env_(nullptr), wrapper_(nullptr) {} + +MyObject::~MyObject() { napi_delete_reference(env_, wrapper_); } + +void MyObject::Destructor(napi_env env, + void* nativeObject, + void* /*finalize_hint*/) { + MyObject* obj = static_cast<MyObject*>(nativeObject); + delete obj; +} + +#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_typeof(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, + 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<void**>(&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 0000000000..323079951e --- /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 <node_api.h> + +class MyObject { + public: + static napi_status Init(napi_env env); + static void Destructor(napi_env env, void* nativeObject, void* finalize_hint); + static napi_status NewInstance(napi_env env, + napi_value arg, + napi_value* instance); + + private: + MyObject(); + ~MyObject(); + + static napi_ref constructor; + static 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 0000000000..20108e14b6 --- /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 0000000000..d6a4eafd88 --- /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<void**>(&obj1)); + if (status != napi_ok) return; + + MyObject* obj2; + status = napi_unwrap(env, args[1], reinterpret_cast<void**>(&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 0000000000..d8f91601e9 --- /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 0000000000..8bca17a3be --- /dev/null +++ b/test/addons-napi/8_passing_wrapped/myobject.cc @@ -0,0 +1,81 @@ +#include "myobject.h" + +MyObject::MyObject() : env_(nullptr), wrapper_(nullptr) {} + +MyObject::~MyObject() { napi_delete_reference(env_, wrapper_); } + +void MyObject::Destructor( + napi_env env, void* nativeObject, void* /*finalize_hint*/) { + MyObject* obj = static_cast<MyObject*>(nativeObject); + delete obj; +} + +napi_ref MyObject::constructor; + +napi_status MyObject::Init(napi_env env) { + napi_status status; + + napi_value cons; + status = napi_define_class(env, "MyObject", 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_typeof(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, + 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 0000000000..773c86e04b --- /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 <node_api.h> + +class MyObject { + public: + static napi_status Init(napi_env env); + static void Destructor(napi_env env, void* nativeObject, void* finalize_hint); + static napi_status NewInstance(napi_env env, + napi_value arg, + napi_value* instance); + double Val() const { return val_; } + + private: + MyObject(); + ~MyObject(); + + static napi_ref constructor; + static 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 0000000000..3d24fa5d9f --- /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 0000000000..44920de098 --- /dev/null +++ b/test/addons-napi/test_array/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "test_array", + "sources": [ "test_array.c" ] + } + ] +} diff --git a/test/addons-napi/test_array/test.js b/test/addons-napi/test_array/test.js new file mode 100644 index 0000000000..c2759b0072 --- /dev/null +++ b/test/addons-napi/test_array/test.js @@ -0,0 +1,37 @@ +'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!'); + +assert.throws( + () => { + test_array.Test(array, -2); + }, + /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.c b/test/addons-napi/test_array/test_array.c new file mode 100644 index 0000000000..e5c4be4e8f --- /dev/null +++ b/test/addons-napi/test_array/test_array.c @@ -0,0 +1,137 @@ +#include <node_api.h> +#include <string.h> + +void Test(napi_env env, napi_callback_info info) { + napi_status status; + + size_t 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_typeof(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_typeof(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 >= (int)(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; + + size_t 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_typeof(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 i, length; + status = napi_get_array_length(env, args[0], &length); + if (status != napi_ok) return; + + for (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 0000000000..e41a3993cd --- /dev/null +++ b/test/addons-napi/test_buffer/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "test_buffer", + "sources": [ "test_buffer.c" ] + } + ] +} diff --git a/test/addons-napi/test_buffer/test.js b/test/addons-napi/test_buffer/test.js new file mode 100644 index 0000000000..6fb80b0205 --- /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.c b/test/addons-napi/test_buffer/test_buffer.c new file mode 100644 index 0000000000..cfb8f44cc7 --- /dev/null +++ b/test/addons-napi/test_buffer/test_buffer.c @@ -0,0 +1,160 @@ +#include <stdlib.h> +#include <string.h> +#include <node_api.h> + +#define JS_ASSERT(env, assertion, message) \ + if (!(assertion)) { \ + napi_throw_error( \ + (env), \ + "assertion (" #assertion ") failed: " message); \ + return; \ + } + +#define NAPI_CALL(env, theCall) \ + if ((theCall) != napi_ok) { \ + const napi_extended_error_info* error; \ + napi_get_last_error_info((env), &error); \ + const char* errorMessage = error->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(napi_env env, void* data, void* finalize_hint) { + JS_ASSERT(env, data != NULL && strcmp(data, theText) == 0, "invalid data"); + (void)finalize_hint; + free(data); + deleterCallCount++; +} + +static void noopDeleter(napi_env env, void* data, void* finalize_hint) { + JS_ASSERT(env, data != NULL && strcmp(data, theText) == 0, "invalid 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), + (void**)(&theCopy), + &theBuffer)); + JS_ASSERT(env, theCopy, "Failed to copy static text for newBuffer"); + memcpy(theCopy, theText, kBufferSize); + 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, + NULL, // 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, sizeof(theText), theText, NULL, &theBuffer)); + NAPI_CALL(env, napi_set_return_value(env, info, theBuffer)); +} + +void bufferHasInstance(napi_env env, napi_callback_info info) { + size_t 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_typeof(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_get_boolean(env, hasInstance, &returnValue)); + NAPI_CALL(env, napi_set_return_value(env, info, returnValue)); +} + +void bufferInfo(napi_env env, napi_callback_info info) { + size_t 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, + (void**)(&bufferData), + &bufferLength)); + NAPI_CALL(env, napi_get_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), + (void*)theText, + noopDeleter, + NULL, // 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 0000000000..55140e7c37 --- /dev/null +++ b/test/addons-napi/test_constructor/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "test_constructor", + "sources": [ "test_constructor.c" ] + } + ] +} diff --git a/test/addons-napi/test_constructor/test.js b/test/addons-napi/test_constructor/test.js new file mode 100644 index 0000000000..9feae5a136 --- /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.c b/test/addons-napi/test_constructor/test_constructor.c new file mode 100644 index 0000000000..5a45da3073 --- /dev/null +++ b/test/addons-napi/test_constructor/test_constructor.c @@ -0,0 +1,104 @@ +#include <node_api.h> + +static double value_ = 1; +napi_ref constructor_; + +void GetValue(napi_env env, napi_callback_info info) { + napi_status status; + + size_t 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; + + size_t 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; + + size_t 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, napi_read_only | napi_dont_enum, 0}, + }; + + napi_value cons; + status = napi_define_class(env, "MyObject", New, + NULL, 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_conversions/binding.gyp b/test/addons-napi/test_conversions/binding.gyp new file mode 100644 index 0000000000..8d8d6fc012 --- /dev/null +++ b/test/addons-napi/test_conversions/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "test_conversions", + "sources": [ "test_conversions.c" ] + } + ] +} diff --git a/test/addons-napi/test_conversions/test.js b/test/addons-napi/test_conversions/test.js new file mode 100644 index 0000000000..73d2c3314f --- /dev/null +++ b/test/addons-napi/test_conversions/test.js @@ -0,0 +1,140 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); +const test = require(`./build/${common.buildType}/test_conversions`); + +const boolExpected = /boolean was expected/; +const numberExpected = /number was expected/; +const stringExpected = /string was expected/; + +const testSym = Symbol('test'); + +assert.strictEqual(false, test.asBool(false)); +assert.strictEqual(true, test.asBool(true)); +assert.throws(() => test.asBool(undefined), boolExpected); +assert.throws(() => test.asBool(null), boolExpected); +assert.throws(() => test.asBool(Number.NaN), boolExpected); +assert.throws(() => test.asBool(0), boolExpected); +assert.throws(() => test.asBool(''), boolExpected); +assert.throws(() => test.asBool('0'), boolExpected); +assert.throws(() => test.asBool(1), boolExpected); +assert.throws(() => test.asBool('1'), boolExpected); +assert.throws(() => test.asBool('true'), boolExpected); +assert.throws(() => test.asBool({}), boolExpected); +assert.throws(() => test.asBool([]), boolExpected); +assert.throws(() => test.asBool(testSym), boolExpected); + +[test.asInt32, test.asUInt32, test.asInt64].forEach((asInt) => { + assert.strictEqual(0, asInt(0)); + assert.strictEqual(1, asInt(1)); + assert.strictEqual(1, asInt(1.0)); + assert.strictEqual(1, asInt(1.1)); + assert.strictEqual(1, asInt(1.9)); + assert.strictEqual(0, asInt(0.9)); + assert.strictEqual(999, asInt(999.9)); + assert.strictEqual(0, asInt(Number.NaN)); + assert.throws(() => asInt(undefined), numberExpected); + assert.throws(() => asInt(null), numberExpected); + assert.throws(() => asInt(false), numberExpected); + assert.throws(() => asInt(''), numberExpected); + assert.throws(() => asInt('1'), numberExpected); + assert.throws(() => asInt({}), numberExpected); + assert.throws(() => asInt([]), numberExpected); + assert.throws(() => asInt(testSym), numberExpected); +}); + +assert.strictEqual(-1, test.asInt32(-1)); +assert.strictEqual(-1, test.asInt64(-1)); +assert.strictEqual(Math.pow(2, 32) - 1, test.asUInt32(-1)); + +assert.strictEqual(0, test.asDouble(0)); +assert.strictEqual(1, test.asDouble(1)); +assert.strictEqual(1.0, test.asDouble(1.0)); +assert.strictEqual(1.1, test.asDouble(1.1)); +assert.strictEqual(1.9, test.asDouble(1.9)); +assert.strictEqual(0.9, test.asDouble(0.9)); +assert.strictEqual(999.9, test.asDouble(999.9)); +assert.strictEqual(-1, test.asDouble(-1)); +assert.ok(Number.isNaN(test.asDouble(Number.NaN))); +assert.throws(() => test.asDouble(undefined), numberExpected); +assert.throws(() => test.asDouble(null), numberExpected); +assert.throws(() => test.asDouble(false), numberExpected); +assert.throws(() => test.asDouble(''), numberExpected); +assert.throws(() => test.asDouble('1'), numberExpected); +assert.throws(() => test.asDouble({}), numberExpected); +assert.throws(() => test.asDouble([]), numberExpected); +assert.throws(() => test.asDouble(testSym), numberExpected); + +assert.strictEqual('', test.asString('')); +assert.strictEqual('test', test.asString('test')); +assert.throws(() => test.asString(undefined), stringExpected); +assert.throws(() => test.asString(null), stringExpected); +assert.throws(() => test.asString(false), stringExpected); +assert.throws(() => test.asString(1), stringExpected); +assert.throws(() => test.asString(1.1), stringExpected); +assert.throws(() => test.asString(Number.NaN), stringExpected); +assert.throws(() => test.asString({}), stringExpected); +assert.throws(() => test.asString([]), stringExpected); +assert.throws(() => test.asString(testSym), stringExpected); + +assert.strictEqual(true, test.toBool(true)); +assert.strictEqual(true, test.toBool(1)); +assert.strictEqual(true, test.toBool(-1)); +assert.strictEqual(true, test.toBool('true')); +assert.strictEqual(true, test.toBool('false')); +assert.strictEqual(true, test.toBool({})); +assert.strictEqual(true, test.toBool([])); +assert.strictEqual(true, test.toBool(testSym)); +assert.strictEqual(false, test.toBool(false)); +assert.strictEqual(false, test.toBool(undefined)); +assert.strictEqual(false, test.toBool(null)); +assert.strictEqual(false, test.toBool(0)); +assert.strictEqual(false, test.toBool(Number.NaN)); +assert.strictEqual(false, test.toBool('')); + +assert.strictEqual(0, test.toNumber(0)); +assert.strictEqual(1, test.toNumber(1)); +assert.strictEqual(1.1, test.toNumber(1.1)); +assert.strictEqual(-1, test.toNumber(-1)); +assert.strictEqual(0, test.toNumber('0')); +assert.strictEqual(1, test.toNumber('1')); +assert.strictEqual(1.1, test.toNumber('1.1')); +assert.strictEqual(0, test.toNumber([])); +assert.strictEqual(0, test.toNumber(false)); +assert.strictEqual(0, test.toNumber(null)); +assert.strictEqual(0, test.toNumber('')); +assert.ok(Number.isNaN(test.toNumber(Number.NaN))); +assert.ok(Number.isNaN(test.toNumber({}))); +assert.ok(Number.isNaN(test.toNumber(undefined))); +assert.throws(() => test.toNumber(testSym), TypeError); + +assert.deepStrictEqual({}, test.toObject({})); +assert.deepStrictEqual({ 'test': 1 }, test.toObject({ 'test': 1 })); +assert.deepStrictEqual([], test.toObject([])); +assert.deepStrictEqual([ 1, 2, 3 ], test.toObject([ 1, 2, 3 ])); +assert.deepStrictEqual(new Boolean(false), test.toObject(false)); +assert.deepStrictEqual(new Boolean(true), test.toObject(true)); +assert.deepStrictEqual(new String(''), test.toObject('')); +assert.deepStrictEqual(new Number(0), test.toObject(0)); +assert.deepStrictEqual(new Number(Number.NaN), test.toObject(Number.NaN)); +assert.deepStrictEqual(new Object(testSym), test.toObject(testSym)); +assert.notDeepStrictEqual(false, test.toObject(false)); +assert.notDeepStrictEqual(true, test.toObject(true)); +assert.notDeepStrictEqual('', test.toObject('')); +assert.notDeepStrictEqual(0, test.toObject(0)); +assert.ok(!Number.isNaN(test.toObject(Number.NaN))); + +assert.strictEqual('', test.toString('')); +assert.strictEqual('test', test.toString('test')); +assert.strictEqual('undefined', test.toString(undefined)); +assert.strictEqual('null', test.toString(null)); +assert.strictEqual('false', test.toString(false)); +assert.strictEqual('true', test.toString(true)); +assert.strictEqual('0', test.toString(0)); +assert.strictEqual('1.1', test.toString(1.1)); +assert.strictEqual('NaN', test.toString(Number.NaN)); +assert.strictEqual('[object Object]', test.toString({})); +assert.strictEqual('test', test.toString({ toString: () => 'test' })); +assert.strictEqual('', test.toString([])); +assert.strictEqual('1,2,3', test.toString([ 1, 2, 3 ])); +assert.throws(() => test.toString(testSym), TypeError); diff --git a/test/addons-napi/test_conversions/test_conversions.c b/test/addons-napi/test_conversions/test_conversions.c new file mode 100644 index 0000000000..5fc8b0f263 --- /dev/null +++ b/test/addons-napi/test_conversions/test_conversions.c @@ -0,0 +1,237 @@ +#include <node_api.h> + +void ThrowLastError(napi_env env) { + const napi_extended_error_info* error_info; + napi_get_last_error_info(env, &error_info); + if (error_info->error_code != napi_ok) { + napi_throw_error(env, error_info->error_message); + } +} + +void AsBool(napi_env env, napi_callback_info info) { + napi_status status; + + napi_value input; + status = napi_get_cb_args(env, info, &input, 1); + if (status != napi_ok) goto done; + + bool value; + status = napi_get_value_bool(env, input, &value); + if (status != napi_ok) goto done; + + napi_value output; + status = napi_get_boolean(env, value, &output); + if (status != napi_ok) goto done; + + status = napi_set_return_value(env, info, output); + if (status != napi_ok) goto done; + +done: + if (status != napi_ok) ThrowLastError(env); +} + +void AsInt32(napi_env env, napi_callback_info info) { + napi_status status; + + napi_value input; + status = napi_get_cb_args(env, info, &input, 1); + if (status != napi_ok) goto done; + + int32_t value; + status = napi_get_value_int32(env, input, &value); + if (status != napi_ok) goto done; + + napi_value output; + status = napi_create_number(env, value, &output); + if (status != napi_ok) goto done; + + status = napi_set_return_value(env, info, output); + if (status != napi_ok) goto done; + +done: + if (status != napi_ok) ThrowLastError(env); +} + +void AsUInt32(napi_env env, napi_callback_info info) { + napi_status status; + + napi_value input; + status = napi_get_cb_args(env, info, &input, 1); + if (status != napi_ok) goto done; + + uint32_t value; + status = napi_get_value_uint32(env, input, &value); + if (status != napi_ok) goto done; + + napi_value output; + status = napi_create_number(env, value, &output); + if (status != napi_ok) goto done; + + status = napi_set_return_value(env, info, output); + if (status != napi_ok) goto done; + +done: + if (status != napi_ok) ThrowLastError(env); +} + +void AsInt64(napi_env env, napi_callback_info info) { + napi_status status; + + napi_value input; + status = napi_get_cb_args(env, info, &input, 1); + if (status != napi_ok) goto done; + + int64_t value; + status = napi_get_value_int64(env, input, &value); + if (status != napi_ok) goto done; + + napi_value output; + status = napi_create_number(env, (double)value, &output); + if (status != napi_ok) goto done; + + status = napi_set_return_value(env, info, output); + if (status != napi_ok) goto done; + +done: + if (status != napi_ok) ThrowLastError(env); +} + +void AsDouble(napi_env env, napi_callback_info info) { + napi_status status; + + napi_value input; + status = napi_get_cb_args(env, info, &input, 1); + if (status != napi_ok) goto done; + + double value; + status = napi_get_value_double(env, input, &value); + if (status != napi_ok) goto done; + + napi_value output; + status = napi_create_number(env, value, &output); + if (status != napi_ok) goto done; + + status = napi_set_return_value(env, info, output); + if (status != napi_ok) goto done; + +done: + if (status != napi_ok) ThrowLastError(env); +} + +void AsString(napi_env env, napi_callback_info info) { + napi_status status; + + napi_value input; + status = napi_get_cb_args(env, info, &input, 1); + if (status != napi_ok) goto done; + + char value[100]; + status = napi_get_value_string_utf8(env, input, value, sizeof(value), NULL); + if (status != napi_ok) goto done; + + napi_value output; + status = napi_create_string_utf8(env, value, -1, &output); + if (status != napi_ok) goto done; + + status = napi_set_return_value(env, info, output); + if (status != napi_ok) goto done; + +done: + if (status != napi_ok) ThrowLastError(env); +} + +void ToBool(napi_env env, napi_callback_info info) { + napi_status status; + + napi_value input; + status = napi_get_cb_args(env, info, &input, 1); + if (status != napi_ok) goto done; + + napi_value output; + status = napi_coerce_to_bool(env, input, &output); + if (status != napi_ok) goto done; + + status = napi_set_return_value(env, info, output); + if (status != napi_ok) goto done; + +done: + if (status != napi_ok) ThrowLastError(env); +} + +void ToNumber(napi_env env, napi_callback_info info) { + napi_status status; + + napi_value input; + status = napi_get_cb_args(env, info, &input, 1); + if (status != napi_ok) goto done; + + napi_value output; + status = napi_coerce_to_number(env, input, &output); + if (status != napi_ok) goto done; + + status = napi_set_return_value(env, info, output); + if (status != napi_ok) goto done; + +done: + if (status != napi_ok) ThrowLastError(env); +} + +void ToObject(napi_env env, napi_callback_info info) { + napi_status status; + + napi_value input; + status = napi_get_cb_args(env, info, &input, 1); + if (status != napi_ok) goto done; + + napi_value output; + status = napi_coerce_to_object(env, input, &output); + if (status != napi_ok) goto done; + + status = napi_set_return_value(env, info, output); + if (status != napi_ok) goto done; + +done: + if (status != napi_ok) ThrowLastError(env); +} + +void ToString(napi_env env, napi_callback_info info) { + napi_status status; + + napi_value input; + status = napi_get_cb_args(env, info, &input, 1); + if (status != napi_ok) goto done; + + napi_value output; + status = napi_coerce_to_string(env, input, &output); + if (status != napi_ok) goto done; + + status = napi_set_return_value(env, info, output); + if (status != napi_ok) goto done; + +done: + if (status != napi_ok) ThrowLastError(env); +} + +#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_property_descriptor descriptors[] = { + DECLARE_NAPI_METHOD("asBool", AsBool), + DECLARE_NAPI_METHOD("asInt32", AsInt32), + DECLARE_NAPI_METHOD("asUInt32", AsUInt32), + DECLARE_NAPI_METHOD("asInt64", AsInt64), + DECLARE_NAPI_METHOD("asDouble", AsDouble), + DECLARE_NAPI_METHOD("asString", AsString), + DECLARE_NAPI_METHOD("toBool", ToBool), + DECLARE_NAPI_METHOD("toNumber", ToNumber), + DECLARE_NAPI_METHOD("toObject", ToObject), + DECLARE_NAPI_METHOD("toString", ToString), + }; + + napi_status 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_error/binding.gyp b/test/addons-napi/test_error/binding.gyp new file mode 100644 index 0000000000..c2defd9551 --- /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 0000000000..521c29250d --- /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 0000000000..1395cb5c4e --- /dev/null +++ b/test/addons-napi/test_error/test_error.cc @@ -0,0 +1,37 @@ +#include <node_api.h> + +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; + status = napi_get_boolean(e, r, &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 0000000000..d2e4586e46 --- /dev/null +++ b/test/addons-napi/test_exception/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "test_exception", + "sources": [ "test_exception.c" ] + } + ] +} diff --git a/test/addons-napi/test_exception/test.js b/test/addons-napi/test_exception/test.js new file mode 100644 index 0000000000..83d2b5000e --- /dev/null +++ b/test/addons-napi/test_exception/test.js @@ -0,0 +1,53 @@ +'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 +assert.throws( + () => { + test_exception.allowException(throwTheError); + }, + function(err) { + return err === theError; + }, + 'Thrown exception was allowed to pass through unhindered' +); + +// 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.c b/test/addons-napi/test_exception/test_exception.c new file mode 100644 index 0000000000..f92bae323d --- /dev/null +++ b/test/addons-napi/test_exception/test_exception.c @@ -0,0 +1,75 @@ +#include <node_api.h> + +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_get_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 0000000000..2b015bddd7 --- /dev/null +++ b/test/addons-napi/test_function/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "test_function", + "sources": [ "test_function.c" ] + } + ] +} diff --git a/test/addons-napi/test_function/test.js b/test/addons-napi/test_function/test.js new file mode 100644 index 0000000000..bdb9133adf --- /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.c b/test/addons-napi/test_function/test_function.c new file mode 100644 index 0000000000..1bd421e7bc --- /dev/null +++ b/test/addons-napi/test_function/test_function.c @@ -0,0 +1,55 @@ +#include <node_api.h> + +void Test(napi_env env, napi_callback_info info) { + napi_status status; + + size_t 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_typeof(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, NULL, Test, NULL, &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 0000000000..7fca7e0736 --- /dev/null +++ b/test/addons-napi/test_instanceof/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "test_instanceof", + "sources": [ "test_instanceof.c" ] + } + ] +} diff --git a/test/addons-napi/test_instanceof/test.js b/test/addons-napi/test_instanceof/test.js new file mode 100644 index 0000000000..38d17031e9 --- /dev/null +++ b/test/addons-napi/test_instanceof/test.js @@ -0,0 +1,87 @@ +'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')); + +// We can only perform this test if we have a working Symbol.hasInstance +if (typeof Symbol !== 'undefined' && 'hasInstance' in Symbol && + typeof Symbol.hasInstance === 'symbol') { + + function compareToNative(theObject, theConstructor) { + assert.strictEqual(addon.doInstanceOf(theObject, theConstructor), + (theObject instanceof theConstructor)); + } + + const MyClass = function MyClass() {}; + Object.defineProperty(MyClass, Symbol.hasInstance, { + value: function(candidate) { + return 'mark' in candidate; + } + }); + + const MySubClass = function MySubClass() {}; + MySubClass.prototype = new MyClass(); + + let x = new MySubClass(); + let y = new MySubClass(); + x.mark = true; + + compareToNative(x, MySubClass); + compareToNative(y, MySubClass); + compareToNative(x, MyClass); + compareToNative(y, MyClass); + + x = new MyClass(); + y = new MyClass(); + x.mark = true; + + compareToNative(x, MySubClass); + compareToNative(y, MySubClass); + compareToNative(x, MyClass); + compareToNative(y, MyClass); +} diff --git a/test/addons-napi/test_instanceof/test_instanceof.c b/test/addons-napi/test_instanceof/test_instanceof.c new file mode 100644 index 0000000000..76df14eb39 --- /dev/null +++ b/test/addons-napi/test_instanceof/test_instanceof.c @@ -0,0 +1,39 @@ +#include <node_api.h> +#include <stdio.h> + +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_get_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 0000000000..c934d5ef03 --- /dev/null +++ b/test/addons-napi/test_number/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "test_number", + "sources": [ "test_number.c" ] + } + ] +} diff --git a/test/addons-napi/test_number/test.js b/test/addons-napi/test_number/test.js new file mode 100644 index 0000000000..885b9b599d --- /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.c b/test/addons-napi/test_number/test_number.c new file mode 100644 index 0000000000..f38c09d681 --- /dev/null +++ b/test/addons-napi/test_number/test_number.c @@ -0,0 +1,55 @@ +#include <node_api.h> + +void Test(napi_env env, napi_callback_info info) { + napi_status status; + + size_t 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_typeof(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 0000000000..be225ace77 --- /dev/null +++ b/test/addons-napi/test_object/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "test_object", + "sources": [ "test_object.c" ] + } + ] +} diff --git a/test/addons-napi/test_object/test.js b/test/addons-napi/test_object/test.js new file mode 100644 index 0000000000..d14a15421f --- /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.c b/test/addons-napi/test_object/test_object.c new file mode 100644 index 0000000000..6080201877 --- /dev/null +++ b/test/addons-napi/test_object/test_object.c @@ -0,0 +1,246 @@ +#include <node_api.h> + +void Get(napi_env env, napi_callback_info info) { + napi_status status; + + size_t 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_typeof(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_typeof(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; + + size_t 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_typeof(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_typeof(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_boolean(env, true, &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; + + size_t 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_typeof(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_typeof(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_get_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; + + size_t 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_typeof(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_property_names(env, obj, &propertynames); + if (status != napi_ok) return; + + uint32_t i, length; + status = napi_get_array_length(env, propertynames, &length); + if (status != napi_ok) return; + + for (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 0000000000..345e5c88d7 --- /dev/null +++ b/test/addons-napi/test_properties/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "test_properties", + "sources": [ "test_properties.c" ] + } + ] +} diff --git a/test/addons-napi/test_properties/test.js b/test/addons-napi/test_properties/test.js new file mode 100644 index 0000000000..8e19903dcf --- /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.c b/test/addons-napi/test_properties/test_properties.c new file mode 100644 index 0000000000..9474e97266 --- /dev/null +++ b/test/addons-napi/test_properties/test_properties.c @@ -0,0 +1,85 @@ +#include <node_api.h> + +static double value_ = 1; + +void GetValue(napi_env env, napi_callback_info info) { + napi_status status; + + size_t 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; + + size_t 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; + + size_t 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, 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 0000000000..d4825de933 --- /dev/null +++ b/test/addons-napi/test_string/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "test_string", + "sources": [ "test_string.c" ] + } + ] +} diff --git a/test/addons-napi/test_string/test.js b/test/addons-napi/test_string/test.js new file mode 100644 index 0000000000..3c9334a561 --- /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.c b/test/addons-napi/test_string/test_string.c new file mode 100644 index 0000000000..1e9c8109d9 --- /dev/null +++ b/test/addons-napi/test_string/test_string.c @@ -0,0 +1,134 @@ +#include <node_api.h> + +void Copy(napi_env env, napi_callback_info info) { + napi_status status; + + size_t 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_typeof(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, NULL); + 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; + + size_t 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_typeof(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; + } + + size_t length; + status = napi_get_value_string_length(env, args[0], &length); + if (status != napi_ok) return; + + napi_value output; + status = napi_create_number(env, (double)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; + + size_t 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_typeof(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; + } + + size_t length; + status = napi_get_value_string_utf8(env, args[0], NULL, 0, &length); + if (status != napi_ok) return; + + napi_value output; + status = napi_create_number(env, (double)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 0000000000..6ef3407968 --- /dev/null +++ b/test/addons-napi/test_symbol/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "test_symbol", + "sources": [ "test_symbol.c" ] + } + ] +} diff --git a/test/addons-napi/test_symbol/test1.js b/test/addons-napi/test_symbol/test1.js new file mode 100644 index 0000000000..25eb473c4b --- /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 0000000000..6051243111 --- /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 0000000000..a7c6c18c02 --- /dev/null +++ b/test/addons-napi/test_symbol/test3.js @@ -0,0 +1,19 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); + +// testing api calls for symbol +const test_symbol = require(`./build/${common.buildType}/test_symbol`); + +assert.notStrictEqual(test_symbol.New(), test_symbol.New()); +assert.notStrictEqual(test_symbol.New('foo'), test_symbol.New('foo')); +assert.notStrictEqual(test_symbol.New('foo'), test_symbol.New('bar')); + +const foo1 = test_symbol.New('foo'); +const foo2 = test_symbol.New('foo'); +const object = { + [foo1]: 1, + [foo2]: 2, +}; +assert.strictEqual(object[foo1], 1); +assert.strictEqual(object[foo2], 2); diff --git a/test/addons-napi/test_symbol/test_symbol.c b/test/addons-napi/test_symbol/test_symbol.c new file mode 100644 index 0000000000..af694b1dd3 --- /dev/null +++ b/test/addons-napi/test_symbol/test_symbol.c @@ -0,0 +1,94 @@ +#include <node_api.h> + +void Test(napi_env env, napi_callback_info info) { + napi_status status; + + size_t 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_typeof(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, NULL); + 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; + + size_t 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_typeof(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; + } + + napi_value symbol; + status = napi_create_symbol(env, args[0], &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 0000000000..8b4a4dc622 --- /dev/null +++ b/test/addons-napi/test_typedarray/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "test_typedarray", + "sources": [ "test_typedarray.c" ] + } + ] +} diff --git a/test/addons-napi/test_typedarray/test.js b/test/addons-napi/test_typedarray/test.js new file mode 100644 index 0000000000..cc1fcbe356 --- /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.c b/test/addons-napi/test_typedarray/test_typedarray.c new file mode 100644 index 0000000000..00217a8cc0 --- /dev/null +++ b/test/addons-napi/test_typedarray/test_typedarray.c @@ -0,0 +1,144 @@ +#include <node_api.h> +#include <string.h> + +void Multiply(napi_env env, napi_callback_info info) { + napi_status status; + + size_t 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_typeof(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_typeof(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 i, length; + status = napi_get_typedarray_info( + env, input_array, &type, &length, NULL, &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 = NULL; + 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_array) { + uint8_t* input_bytes = (uint8_t*)(data) + byte_offset; + uint8_t* output_bytes = (uint8_t*)(output_ptr); + for (i = 0; i < length; i++) { + output_bytes[i] = (uint8_t)(input_bytes[i] * multiplier); + } + } else if (type == napi_float64_array) { + double* input_doubles = (double*)((uint8_t*)(data) + byte_offset); + double* output_doubles = (double*)(output_ptr); + for (i = 0; i < length; i++) { + output_doubles[i] = input_doubles[i] * multiplier; + } + } else { + napi_throw_error(env, "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), + NULL, // finalize_callback + NULL, // finalize_hint + &output_buffer); + if (status != napi_ok) return; + + napi_value output_array; + status = napi_create_typedarray(env, + napi_int8_array, + 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 0000000000..ef7c18fdae --- /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']) diff --git a/test/testpy/__init__.py b/test/testpy/__init__.py index d7ec88992e..f999b6a6ba 100644 --- a/test/testpy/__init__.py +++ b/test/testpy/__init__.py @@ -162,5 +162,5 @@ class AddonTestConfiguration(SimpleTestConfiguration): 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 fdaf2b2e7a..2235cb7350 100755 --- a/tools/install.py +++ b/tools/install.py @@ -148,6 +148,8 @@ def headers(action): 'common.gypi', 'config.gypi', 'src/node.h', + 'src/node_api.h', + 'src/node_api_types.h', 'src/node_buffer.h', 'src/node_object_wrap.h', 'src/node_version.h', diff --git a/tools/test.py b/tools/test.py index deeb7aeffe..6e1262931f 100755 --- a/tools/test.py +++ b/tools/test.py @@ -1529,6 +1529,7 @@ BUILT_IN_TESTS = [ 'message', 'internet', 'addons', + 'addons-napi', 'gc', 'debugger', 'doctool', diff --git a/vcbuild.bat b/vcbuild.bat index 9cbac9088f..985b7f1fcc 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 |