From 2dfaa456fbd66b8551062e458a75e45317d7517a Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Mon, 14 Jan 2019 18:13:00 -0800 Subject: [PATCH 1/3] src: add addon ABI declaration option Add macro `NODE_MODULE_DECLARE_ABI` to give addon authors the option of providing a list of versions for various parts of the ABI against which to check the addon at load time. Re: https://github.com/nodejs/TSC/issues/651 --- Makefile | 3 +- doc/api/addons.md | 69 +++++++++++++ src/node.h | 101 +------------------ src/node_abi_versions.h | 26 +++++ src/node_addon_macros.h | 145 +++++++++++++++++++++++++++ src/node_api.h | 28 ++---- src/node_binding.cc | 59 +++++++++++ src/node_binding.h | 2 - src/node_version.h | 2 + test/addons/abi-mismatch/binding.cc | 5 + test/addons/abi-mismatch/binding.gyp | 9 ++ test/addons/abi-mismatch/test.js | 4 + tools/install.py | 2 + 13 files changed, 331 insertions(+), 124 deletions(-) create mode 100644 src/node_abi_versions.h create mode 100644 src/node_addon_macros.h create mode 100644 test/addons/abi-mismatch/binding.cc create mode 100644 test/addons/abi-mismatch/binding.gyp create mode 100644 test/addons/abi-mismatch/test.js diff --git a/Makefile b/Makefile index 9dd9b198491eb3..a872303fb7f863 100644 --- a/Makefile +++ b/Makefile @@ -357,7 +357,8 @@ ADDONS_BINDING_SOURCES := \ ADDONS_PREREQS := config.gypi \ deps/npm/node_modules/node-gyp/package.json tools/build-addons.js \ 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.h src/node_buffer.h src/node_object_wrap.h src/node_version.h \ + src/node_addon_macros.h src/node_abi_versions.h define run_build_addons env npm_config_loglevel=$(LOGLEVEL) npm_config_nodedir="$$PWD" \ diff --git a/doc/api/addons.md b/doc/api/addons.md index 4941d3de9add8f..d94432f61ddf13 100644 --- a/doc/api/addons.md +++ b/doc/api/addons.md @@ -251,6 +251,74 @@ down. If necessary, such hooks can be removed using `RemoveEnvironmentCleanupHook()` before they are run, which has the same signature. +#### ABI declaration + +Node.js is available from a number of sources besides the [official +distribution][]. Since the various versions of Node.js are configured +differently at build time, the resulting runtime ABI may be different. For +example, version 10 of Node.js as shipped by Debian GNU/Linux may have a +different ABI than version 10 of Node.js as available from the official +distribution. + +The Node.js ABI consists of various different, independent parts, such as V8, +openssl, libuv, and others. Native addons may use some, all, or even just one of +these independent parts of the Node.js ABI. Thus, when Node.js is tasked with +loading an addon, at which time it needs to determine whether the addon is ABI- +compatible, it needs ABI information provided by the addon. The addon normally +provides this information as a single number (`NODE_MODULE_VERSION`) which is +stored inside the addon and which is compared against the value present in the +running Node.js process at addon load time. + +Since `NODE_MODULE_VERSION` reflects only the Node.js major version against +which the addon was built, it may match the running Node.js process even though +some of the independent parts of the ABI are mismatched. To address this problem +the addon may optionally declare which portions of the Node.js ABI it uses by +invoking the `NODE_MODULE_DECLARE_ABI` macro. Any portions of the ABI included +as a parameter to the macro will be checked during addon load in addition to +`NODE_MODULE_VERSION` in order to ensure that all ABIs declared by the addon +have the version as requested by the addon. Node.js assumes that ABIs not +included in the invocation of the `NODE_MODULE_DECLARE_ABI` macro are not used +by the addon. + +The `NODE_MODULE_DECLARE_ABI` macro may be invoked as follows: +```C++ +NODE_MODULE_DECLARE_ABI( + NODE_MODULE_ABI_VENDOR_VERSION, + NODE_MODULE_ABI_ENGINE_VERSION, + NODE_MODULE_ABI_OPENSSL_VERSION) +``` +Note that there must be no semicolon at the end of the declaration. + +The following parameters can be passed to `NODE_MODULE_DECLARE_ABI`: +* `NODE_MODULE_ABI_VERSION_TERMINATOR` - this is a sentinel indicating the end +of the list of ABI declarations. It need not normally be used by addons. + +* `NODE_MODULE_ABI_VENDOR_VERSION` - this declaration ties the addon to a +specific vendor's version of Node.js. For example, if the addon is built against +the official disitrbution of Node.js, it will not load on a version of Node.js +provided by the Debian GNU/Linux project nor will it load on a version of +Electron. + +* `NODE_MODULE_ABI_ENGINE_VERSION` - this declaration ties the addon to a +specific JavaScript engine version. It will fail to load on a version of Node.js +that provides a different JavaScript engine version. + +* `NODE_MODULE_ABI_OPENSSL_VERSION` - this declaration ties the addon to a +specific version of the OpenSSL library. It will not load on a version of +Node.js that provides a different version of the OpenSSL library. + +* `NODE_MODULE_ABI_LIBUV_VERSION` - this declaration ties the addon to a +specific version of the libuv library. It will fail to load on a version of +Node.js that provides a different version of libuv. + +* `NODE_MODULE_ABI_ICU_VERSION` - this declaration ties the addon to a +specific version of the ICU library. It will fail to load on a version of +Node.js that provides a different version of the ICU library. + +* `NODE_MODULE_ABI_CARES_VERSION` - this declaration ties the addon to a +specific version of the c-ares library. It will fail to load on a version of +Node.js that provides a different version of the c-ares library. + ### Building Once the source code has been written, it must be compiled into the binary @@ -1377,5 +1445,6 @@ require('./build/Release/addon'); [installation instructions]: https://github.com/nodejs/node-gyp#installation [libuv]: https://github.com/libuv/libuv [node-gyp]: https://github.com/nodejs/node-gyp +[official distribution]: https://nodejs.org/ [require]: modules.html#modules_require_id [v8-docs]: https://v8docs.nodesource.com/ diff --git a/src/node.h b/src/node.h index fbf9128be42429..9d7db5c53190df 100644 --- a/src/node.h +++ b/src/node.h @@ -63,6 +63,7 @@ #include "v8.h" // NOLINT(build/include_order) #include "v8-platform.h" // NOLINT(build/include_order) #include "node_version.h" // NODE_MODULE_VERSION +#include "node_addon_macros.h" #define NODE_MAKE_VERSION(major, minor, patch) \ ((major) * 0x1000 + (minor) * 0x100 + (patch)) @@ -459,106 +460,6 @@ struct node_module { extern "C" NODE_EXTERN void node_module_register(void* mod); -#ifdef _WIN32 -# define NODE_MODULE_EXPORT __declspec(dllexport) -#else -# define NODE_MODULE_EXPORT __attribute__((visibility("default"))) -#endif - -#ifdef NODE_SHARED_MODE -# define NODE_CTOR_PREFIX -#else -# define NODE_CTOR_PREFIX static -#endif - -#if defined(_MSC_VER) -#pragma section(".CRT$XCU", read) -#define NODE_C_CTOR(fn) \ - NODE_CTOR_PREFIX void __cdecl fn(void); \ - __declspec(dllexport, allocate(".CRT$XCU")) \ - void (__cdecl*fn ## _)(void) = fn; \ - NODE_CTOR_PREFIX void __cdecl fn(void) -#else -#define NODE_C_CTOR(fn) \ - NODE_CTOR_PREFIX void fn(void) __attribute__((constructor)); \ - NODE_CTOR_PREFIX void fn(void) -#endif - -#define NODE_MODULE_X(modname, regfunc, priv, flags) \ - extern "C" { \ - static node::node_module _module = \ - { \ - NODE_MODULE_VERSION, \ - flags, \ - NULL, /* NOLINT (readability/null_usage) */ \ - __FILE__, \ - (node::addon_register_func) (regfunc), \ - NULL, /* NOLINT (readability/null_usage) */ \ - NODE_STRINGIFY(modname), \ - priv, \ - NULL /* NOLINT (readability/null_usage) */ \ - }; \ - NODE_C_CTOR(_register_ ## modname) { \ - node_module_register(&_module); \ - } \ - } - -#define NODE_MODULE_CONTEXT_AWARE_X(modname, regfunc, priv, flags) \ - extern "C" { \ - static node::node_module _module = \ - { \ - NODE_MODULE_VERSION, \ - flags, \ - NULL, /* NOLINT (readability/null_usage) */ \ - __FILE__, \ - NULL, /* NOLINT (readability/null_usage) */ \ - (node::addon_context_register_func) (regfunc), \ - NODE_STRINGIFY(modname), \ - priv, \ - NULL /* NOLINT (readability/null_usage) */ \ - }; \ - NODE_C_CTOR(_register_ ## modname) { \ - node_module_register(&_module); \ - } \ - } - -// Usage: `NODE_MODULE(NODE_GYP_MODULE_NAME, InitializerFunction)` -// If no NODE_MODULE is declared, Node.js looks for the well-known -// symbol `node_register_module_v${NODE_MODULE_VERSION}`. -#define NODE_MODULE(modname, regfunc) \ - NODE_MODULE_X(modname, regfunc, NULL, 0) // NOLINT (readability/null_usage) - -#define NODE_MODULE_CONTEXT_AWARE(modname, regfunc) \ - /* NOLINTNEXTLINE (readability/null_usage) */ \ - NODE_MODULE_CONTEXT_AWARE_X(modname, regfunc, NULL, 0) - -/* - * For backward compatibility in add-on modules. - */ -#define NODE_MODULE_DECL /* nothing */ - -#define NODE_MODULE_INITIALIZER_BASE node_register_module_v - -#define NODE_MODULE_INITIALIZER_X(base, version) \ - NODE_MODULE_INITIALIZER_X_HELPER(base, version) - -#define NODE_MODULE_INITIALIZER_X_HELPER(base, version) base##version - -#define NODE_MODULE_INITIALIZER \ - NODE_MODULE_INITIALIZER_X(NODE_MODULE_INITIALIZER_BASE, \ - NODE_MODULE_VERSION) - -#define NODE_MODULE_INIT() \ - extern "C" NODE_MODULE_EXPORT void \ - NODE_MODULE_INITIALIZER(v8::Local exports, \ - v8::Local module, \ - v8::Local context); \ - NODE_MODULE_CONTEXT_AWARE(NODE_GYP_MODULE_NAME, \ - NODE_MODULE_INITIALIZER) \ - void NODE_MODULE_INITIALIZER(v8::Local exports, \ - v8::Local module, \ - v8::Local context) - /* Called after the event loop exits but before the VM is disposed. * Callbacks are run in reverse order of registration, i.e. newest first. */ diff --git a/src/node_abi_versions.h b/src/node_abi_versions.h new file mode 100644 index 00000000000000..9c6ae5c0b1747f --- /dev/null +++ b/src/node_abi_versions.h @@ -0,0 +1,26 @@ +#ifndef SRC_NODE_ABI_VERSIONS_H_ +#define SRC_NODE_ABI_VERSIONS_H_ + +typedef enum { + node_abi_version_terminator, + node_abi_vendor_version, + node_abi_engine_version, + node_abi_openssl_version, + node_abi_libuv_version, + node_abi_icu_version, + node_abi_cares_version +} node_abi_version_item; + +typedef struct { + node_abi_version_item item; + int version; +} node_abi_version_entry; + +#define NODE_ABI_VENDOR_VERSION 1 +#define NODE_ABI_ENGINE_VERSION 1 +#define NODE_ABI_OPENSSL_VERSION 1 +#define NODE_ABI_LIBUV_VERSION 1 +#define NODE_ABI_ICU_VERSION 1 +#define NODE_ABI_CARES_VERSION 1 + +#endif // SRC_NODE_ABI_VERSIONS_H_ diff --git a/src/node_addon_macros.h b/src/node_addon_macros.h new file mode 100644 index 00000000000000..6d77ebfcddc1a5 --- /dev/null +++ b/src/node_addon_macros.h @@ -0,0 +1,145 @@ +#ifndef SRC_NODE_ADDON_MACROS_H_ +#define SRC_NODE_ADDON_MACROS_H_ + +#include "node_abi_versions.h" + +#ifdef _WIN32 +# define NODE_MODULE_EXPORT __declspec(dllexport) +#else +# define NODE_MODULE_EXPORT __attribute__((visibility("default"))) +#endif + +#ifdef NODE_SHARED_MODE +# define NODE_CTOR_PREFIX +#else +# define NODE_CTOR_PREFIX static +#endif + +#if defined(_MSC_VER) +#pragma section(".CRT$XCU", read) +#define NODE_C_CTOR(fn) \ + NODE_CTOR_PREFIX void __cdecl fn(void); \ + __declspec(dllexport, allocate(".CRT$XCU")) \ + void (__cdecl*fn ## _)(void) = fn; \ + NODE_CTOR_PREFIX void __cdecl fn(void) +#else +#define NODE_C_CTOR(fn) \ + NODE_CTOR_PREFIX void fn(void) __attribute__((constructor)); \ + NODE_CTOR_PREFIX 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 NODE_MODULE_X(modname, regfunc, priv, flags) \ + extern "C" { \ + static node::node_module _module = \ + { \ + NODE_MODULE_VERSION, \ + flags, \ + NULL, /* NOLINT (readability/null_usage) */ \ + __FILE__, \ + (node::addon_register_func) (regfunc), \ + NULL, /* NOLINT (readability/null_usage) */ \ + NODE_STRINGIFY(modname), \ + priv, \ + NULL /* NOLINT (readability/null_usage) */ \ + }; \ + NODE_C_CTOR(_register_ ## modname) { \ + node_module_register(&_module); \ + } \ + } + +#define NODE_MODULE_CONTEXT_AWARE_X(modname, regfunc, priv, flags) \ + extern "C" { \ + static node::node_module _module = \ + { \ + NODE_MODULE_VERSION, \ + flags, \ + NULL, /* NOLINT (readability/null_usage) */ \ + __FILE__, \ + NULL, /* NOLINT (readability/null_usage) */ \ + (node::addon_context_register_func) (regfunc), \ + NODE_STRINGIFY(modname), \ + priv, \ + NULL /* NOLINT (readability/null_usage) */ \ + }; \ + NODE_C_CTOR(_register_ ## modname) { \ + node_module_register(&_module); \ + } \ + } + +// Usage: `NODE_MODULE(NODE_GYP_MODULE_NAME, InitializerFunction)` +// If no NODE_MODULE is declared, Node.js looks for the well-known +// symbol `node_register_module_v${NODE_MODULE_VERSION}`. +#define NODE_MODULE(modname, regfunc) \ + NODE_MODULE_X(modname, regfunc, NULL, 0) // NOLINT (readability/null_usage) + +#define NODE_MODULE_CONTEXT_AWARE(modname, regfunc) \ + /* NOLINTNEXTLINE (readability/null_usage) */ \ + NODE_MODULE_CONTEXT_AWARE_X(modname, regfunc, NULL, 0) + +/* + * For backward compatibility in add-on modules. + */ +#define NODE_MODULE_DECL /* nothing */ + +#define NODE_MODULE_INITIALIZER_BASE node_register_module_v + +#define NODE_MODULE_INITIALIZER_X(base, version) \ + NODE_MODULE_INITIALIZER_X_HELPER(base, version) + +#define NODE_MODULE_INITIALIZER_X_HELPER(base, version) base##version + +#define NODE_MODULE_INITIALIZER \ + NODE_MODULE_INITIALIZER_X(NODE_MODULE_INITIALIZER_BASE, \ + NODE_MODULE_VERSION) + +#define NODE_MODULE_INIT() \ + extern "C" NODE_MODULE_EXPORT void \ + NODE_MODULE_INITIALIZER(v8::Local exports, \ + v8::Local module, \ + v8::Local context); \ + NODE_MODULE_CONTEXT_AWARE(NODE_GYP_MODULE_NAME, \ + NODE_MODULE_INITIALIZER) \ + void NODE_MODULE_INITIALIZER(v8::Local exports, \ + v8::Local module, \ + v8::Local context) + +#define NODE_MODULE_ABI_VERSION_TERMINATOR \ + { node_abi_version_terminator, 0 } +#define NODE_MODULE_ABI_VENDOR_VERSION \ + { node_abi_vendor_version, NODE_ABI_VENDOR_VERSION } +#define NODE_MODULE_ABI_ENGINE_VERSION \ + { node_abi_engine_version, NODE_ABI_ENGINE_VERSION } +#define NODE_MODULE_ABI_OPENSSL_VERSION \ + { node_abi_openssl_version, NODE_ABI_OPENSSL_VERSION } +#define NODE_MODULE_ABI_LIBUV_VERSION \ + { node_abi_libuv_version, NODE_ABI_LIBUV_VERSION } +#define NODE_MODULE_ABI_ICU_VERSION \ + { node_abi_icu_version, NODE_ABI_ICU_VERSION } +#define NODE_MODULE_ABI_CARES_VERSION \ + { node_abi_cares_version, NODE_ABI_CARES_VERSION } + +#define NODE_MODULE_ABI_DECLARATION_BASE node_module_declare_abi_v + +#define NODE_MODULE_ABI_DECLARATION \ + NODE_MODULE_INITIALIZER_X(NODE_MODULE_ABI_DECLARATION_BASE, \ + NODE_MODULE_VERSION) + +#define NODE_MODULE_DECLARE_ABI(...) \ +EXTERN_C_START \ +NODE_MODULE_EXPORT node_abi_version_entry* NODE_MODULE_ABI_DECLARATION() { \ + static node_abi_version_entry versions[] = { \ + __VA_ARGS__, NODE_MODULE_ABI_VERSION_TERMINATOR \ + }; \ + return versions; \ +} \ +EXTERN_C_END + +#endif // SRC_NODE_ADDON_MACROS_H_ diff --git a/src/node_api.h b/src/node_api.h index b36f6d4b7a4b9e..9be6b0d09bb7e7 100644 --- a/src/node_api.h +++ b/src/node_api.h @@ -12,11 +12,9 @@ struct uv_loop_s; // Forward declaration. -#ifdef _WIN32 -# define NAPI_MODULE_EXPORT __declspec(dllexport) -#else -# define NAPI_MODULE_EXPORT __attribute__((visibility("default"))) -#endif +#include "node_addon_macros.h" + +#define NAPI_MODULE_EXPORT NODE_MODULE_EXPORT #ifdef __GNUC__ #define NAPI_NO_RETURN __attribute__((noreturn)) @@ -39,18 +37,7 @@ typedef struct { #define NAPI_MODULE_VERSION 1 -#if defined(_MSC_VER) -#pragma section(".CRT$XCU", read) -#define NAPI_C_CTOR(fn) \ - static void __cdecl fn(void); \ - __declspec(dllexport, allocate(".CRT$XCU")) void(__cdecl * fn##_)(void) = \ - fn; \ - static void __cdecl fn(void) -#else -#define NAPI_C_CTOR(fn) \ - static void fn(void) __attribute__((constructor)); \ - static void fn(void) -#endif +#define NAPI_C_CTOR(fn) NODE_C_CTOR(fn) #define NAPI_MODULE_X(modname, regfunc, priv, flags) \ EXTERN_C_START \ @@ -74,12 +61,11 @@ typedef struct { #define NAPI_MODULE_INITIALIZER_BASE napi_register_module_v -#define NAPI_MODULE_INITIALIZER_X(base, version) \ - NAPI_MODULE_INITIALIZER_X_HELPER(base, version) -#define NAPI_MODULE_INITIALIZER_X_HELPER(base, version) base##version +#define NAPI_MODULE_INITIALIZER_X(base, version) \ + NODE_MODULE_INITIALIZER_X(base, version) #define NAPI_MODULE_INITIALIZER \ - NAPI_MODULE_INITIALIZER_X(NAPI_MODULE_INITIALIZER_BASE, \ + NODE_MODULE_INITIALIZER_X(NAPI_MODULE_INITIALIZER_BASE, \ NAPI_MODULE_VERSION) #define NAPI_MODULE_INIT() \ diff --git a/src/node_binding.cc b/src/node_binding.cc index cf47b3058de538..ec210413cf6a9a 100644 --- a/src/node_binding.cc +++ b/src/node_binding.cc @@ -190,6 +190,52 @@ void InitModpendingOnce() { CHECK_EQ(0, uv_key_create(&thread_local_modpending)); } +#define ABI_VERSION_CASE(entry, key, KEY) \ + case node_abi_ ## key ## _version: \ + if ((entry)->version != NODE_ABI_ ## KEY ## _VERSION) { \ + char int_str[32]; \ + result += std::string("\n") + \ + NODE_STRINGIFY(node_abi_ ## key ## _version) + \ + ": module: "; \ + snprintf(int_str, sizeof(int_str), "%d", (entry)->version); \ + result += int_str; \ + result += " vs. Node.js: "; \ + snprintf(int_str, sizeof(int_str), "%d", NODE_ABI_ ## KEY ## _VERSION); \ + result += int_str; \ + } \ + break \ + +using ABICallback = const node_abi_version_entry* (*)(); + +std::string ABICheck(DLib* dlib) { + std::string result; + const char* name = "node_module_declare_abi_v" STRINGIFY(NODE_MODULE_VERSION); + ABICallback abi_lister = + reinterpret_cast(dlib->GetSymbolAddress(name)); + if (abi_lister != nullptr) { + const node_abi_version_entry* abi_entry = abi_lister(); + if (abi_entry != nullptr) { + for (; abi_entry->item != node_abi_version_terminator; abi_entry++) { + switch (abi_entry->item) { + ABI_VERSION_CASE(abi_entry, vendor, VENDOR); + ABI_VERSION_CASE(abi_entry, engine, ENGINE); + ABI_VERSION_CASE(abi_entry, openssl, OPENSSL); + ABI_VERSION_CASE(abi_entry, libuv, LIBUV); + ABI_VERSION_CASE(abi_entry, icu, ICU); + ABI_VERSION_CASE(abi_entry, cares, CARES); + default: { + char int_string[32]; + snprintf(int_string, sizeof(int_string), "%d", abi_entry->item); + result += std::string("Unknown ABI: ") + int_string; + break; + } + } + } + } + } + return result; +} + // DLOpen is process.dlopen(module, filename, flags). // Used to load 'module.node' dynamically shared objects. // @@ -246,6 +292,19 @@ void DLOpen(const FunctionCallbackInfo& args) { return false; } + std::string abi_result = ABICheck(dlib); + if (!abi_result.empty()) { + abi_result = std::string("The module '") + (*filename) + "'" + + "\nwas compiled against a different Node.js version. The following" + "\nABI mismatches were dectected:" + + abi_result + + "\nPlease try re-compiling or reinstalling the module (for instance," + "\nusing `npm rebuild` or `npm install`)."; + dlib->Close(); + env->ThrowError(abi_result.c_str()); + return false; + } + if (mp == nullptr) { if (auto callback = GetInitializerCallback(dlib)) { callback(exports, module, context); diff --git a/src/node_binding.h b/src/node_binding.h index 7d79dae80d8e39..ab2c13a1a47b89 100644 --- a/src/node_binding.h +++ b/src/node_binding.h @@ -92,7 +92,5 @@ void DLOpen(const v8::FunctionCallbackInfo& args); } // namespace node -#include "node_binding.h" - #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS #endif // SRC_NODE_BINDING_H_ diff --git a/src/node_version.h b/src/node_version.h index b3f4808bf17127..b215aed1621fa7 100644 --- a/src/node_version.h +++ b/src/node_version.h @@ -22,6 +22,8 @@ #ifndef SRC_NODE_VERSION_H_ #define SRC_NODE_VERSION_H_ +#include "node_abi_versions.h" + #define NODE_MAJOR_VERSION 12 #define NODE_MINOR_VERSION 0 #define NODE_PATCH_VERSION 0 diff --git a/test/addons/abi-mismatch/binding.cc b/test/addons/abi-mismatch/binding.cc new file mode 100644 index 00000000000000..08a5f2bc9c8c75 --- /dev/null +++ b/test/addons/abi-mismatch/binding.cc @@ -0,0 +1,5 @@ +#include + +NODE_MODULE_INIT(/*exports, module, context*/) {} + +NODE_MODULE_DECLARE_ABI({node_abi_icu_version, NODE_ABI_ICU_VERSION + 1}) diff --git a/test/addons/abi-mismatch/binding.gyp b/test/addons/abi-mismatch/binding.gyp new file mode 100644 index 00000000000000..7ede63d94a0d77 --- /dev/null +++ b/test/addons/abi-mismatch/binding.gyp @@ -0,0 +1,9 @@ +{ + 'targets': [ + { + 'target_name': 'binding', + 'defines': [ 'V8_DEPRECATION_WARNINGS=1' ], + 'sources': [ 'binding.cc' ] + } + ] +} diff --git a/test/addons/abi-mismatch/test.js b/test/addons/abi-mismatch/test.js new file mode 100644 index 00000000000000..698df33c6a2c19 --- /dev/null +++ b/test/addons/abi-mismatch/test.js @@ -0,0 +1,4 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); +assert.throws(() => require(`./build/${common.buildType}/binding`), /node_abi_icu_version/); diff --git a/tools/install.py b/tools/install.py index 659b3b23a95ac4..489540652cd93d 100755 --- a/tools/install.py +++ b/tools/install.py @@ -169,6 +169,8 @@ def ignore_inspector_headers(files, dest): 'common.gypi', 'config.gypi', 'src/node.h', + 'src/node_abi_versions.h', + 'src/node_addon_macros.h', 'src/node_api.h', 'src/js_native_api.h', 'src/js_native_api_types.h', From 2f62e74006ac8a0f794d93a381c0c3eac70ec14e Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Thu, 17 Jan 2019 22:27:43 -0800 Subject: [PATCH 2/3] use std::to_string --- src/node_binding.cc | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/src/node_binding.cc b/src/node_binding.cc index ec210413cf6a9a..0435ee046f654f 100644 --- a/src/node_binding.cc +++ b/src/node_binding.cc @@ -190,20 +190,15 @@ void InitModpendingOnce() { CHECK_EQ(0, uv_key_create(&thread_local_modpending)); } -#define ABI_VERSION_CASE(entry, key, KEY) \ - case node_abi_ ## key ## _version: \ - if ((entry)->version != NODE_ABI_ ## KEY ## _VERSION) { \ - char int_str[32]; \ - result += std::string("\n") + \ - NODE_STRINGIFY(node_abi_ ## key ## _version) + \ - ": module: "; \ - snprintf(int_str, sizeof(int_str), "%d", (entry)->version); \ - result += int_str; \ - result += " vs. Node.js: "; \ - snprintf(int_str, sizeof(int_str), "%d", NODE_ABI_ ## KEY ## _VERSION); \ - result += int_str; \ - } \ - break \ +#define ABI_VERSION_CASE(entry, key, KEY) \ + case node_abi_ ## key ## _version: \ + if ((entry)->version != NODE_ABI_ ## KEY ## _VERSION) { \ + result += std::string() + \ + "\n" NODE_STRINGIFY(node_abi_ ## key ## _version) ": " + \ + "module: " + std::to_string((entry)->version) + \ + " vs. Node.js: " + std::to_string(NODE_ABI_ ## KEY ## _VERSION); \ + } \ + break \ using ABICallback = const node_abi_version_entry* (*)(); From 06d96f7d3fdb8b36cfc633fcec39f1f9a3c052c8 Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Tue, 22 Jan 2019 09:21:35 -0800 Subject: [PATCH 3/3] Move doc to own-dependencies section --- doc/api/addons.md | 144 ++++++++++++++++++++++++---------------------- 1 file changed, 74 insertions(+), 70 deletions(-) diff --git a/doc/api/addons.md b/doc/api/addons.md index d94432f61ddf13..1929c9abade81e 100644 --- a/doc/api/addons.md +++ b/doc/api/addons.md @@ -251,74 +251,6 @@ down. If necessary, such hooks can be removed using `RemoveEnvironmentCleanupHook()` before they are run, which has the same signature. -#### ABI declaration - -Node.js is available from a number of sources besides the [official -distribution][]. Since the various versions of Node.js are configured -differently at build time, the resulting runtime ABI may be different. For -example, version 10 of Node.js as shipped by Debian GNU/Linux may have a -different ABI than version 10 of Node.js as available from the official -distribution. - -The Node.js ABI consists of various different, independent parts, such as V8, -openssl, libuv, and others. Native addons may use some, all, or even just one of -these independent parts of the Node.js ABI. Thus, when Node.js is tasked with -loading an addon, at which time it needs to determine whether the addon is ABI- -compatible, it needs ABI information provided by the addon. The addon normally -provides this information as a single number (`NODE_MODULE_VERSION`) which is -stored inside the addon and which is compared against the value present in the -running Node.js process at addon load time. - -Since `NODE_MODULE_VERSION` reflects only the Node.js major version against -which the addon was built, it may match the running Node.js process even though -some of the independent parts of the ABI are mismatched. To address this problem -the addon may optionally declare which portions of the Node.js ABI it uses by -invoking the `NODE_MODULE_DECLARE_ABI` macro. Any portions of the ABI included -as a parameter to the macro will be checked during addon load in addition to -`NODE_MODULE_VERSION` in order to ensure that all ABIs declared by the addon -have the version as requested by the addon. Node.js assumes that ABIs not -included in the invocation of the `NODE_MODULE_DECLARE_ABI` macro are not used -by the addon. - -The `NODE_MODULE_DECLARE_ABI` macro may be invoked as follows: -```C++ -NODE_MODULE_DECLARE_ABI( - NODE_MODULE_ABI_VENDOR_VERSION, - NODE_MODULE_ABI_ENGINE_VERSION, - NODE_MODULE_ABI_OPENSSL_VERSION) -``` -Note that there must be no semicolon at the end of the declaration. - -The following parameters can be passed to `NODE_MODULE_DECLARE_ABI`: -* `NODE_MODULE_ABI_VERSION_TERMINATOR` - this is a sentinel indicating the end -of the list of ABI declarations. It need not normally be used by addons. - -* `NODE_MODULE_ABI_VENDOR_VERSION` - this declaration ties the addon to a -specific vendor's version of Node.js. For example, if the addon is built against -the official disitrbution of Node.js, it will not load on a version of Node.js -provided by the Debian GNU/Linux project nor will it load on a version of -Electron. - -* `NODE_MODULE_ABI_ENGINE_VERSION` - this declaration ties the addon to a -specific JavaScript engine version. It will fail to load on a version of Node.js -that provides a different JavaScript engine version. - -* `NODE_MODULE_ABI_OPENSSL_VERSION` - this declaration ties the addon to a -specific version of the OpenSSL library. It will not load on a version of -Node.js that provides a different version of the OpenSSL library. - -* `NODE_MODULE_ABI_LIBUV_VERSION` - this declaration ties the addon to a -specific version of the libuv library. It will fail to load on a version of -Node.js that provides a different version of libuv. - -* `NODE_MODULE_ABI_ICU_VERSION` - this declaration ties the addon to a -specific version of the ICU library. It will fail to load on a version of -Node.js that provides a different version of the ICU library. - -* `NODE_MODULE_ABI_CARES_VERSION` - this declaration ties the addon to a -specific version of the c-ares library. It will fail to load on a version of -Node.js that provides a different version of the c-ares library. - ### Building Once the source code has been written, it must be compiled into the binary @@ -391,8 +323,8 @@ try { ### Linking to Node.js' own dependencies Node.js uses a number of statically linked libraries such as V8, libuv and -OpenSSL. All Addons are required to link to V8 and may link to any of the -other dependencies as well. Typically, this is as simple as including +OpenSSL. All addons are required to link to either V8 or N-API and may link to +any of the other dependencies as well. Typically, this is as simple as including the appropriate `#include <...>` statements (e.g. `#include `) and `node-gyp` will locate the appropriate headers automatically. However, there are a few caveats to be aware of: @@ -407,6 +339,78 @@ only the symbols exported by Node.js will be available. source image. Using this option, the Addon will have access to the full set of dependencies. +Node.js is available from a number of sources besides the [official +distribution][]. Since the various versions of Node.js are configured +differently at build time, the resulting runtime ABI of the statically linked +libraries may be different. For example, version 10 of Node.js as shipped by +Debian GNU/Linux may have a different ABI than version 10 of Node.js as +available from the official distribution. + +The Node.js ABI consists of various different, independent parts, such as V8, +OpenSSL, libuv, and others. Native addons may use some, all, or even just one of +these independent parts of the Node.js ABI. Thus, when Node.js is tasked with +loading an addon, at which time it needs to determine whether the addon is ABI- +compatible, it needs ABI information provided by the addon. The addon normally +provides this information as a single number (`NODE_MODULE_VERSION`) which is +stored inside the addon and which is compared against the value present in the +running Node.js process at addon load time. + +Since `NODE_MODULE_VERSION` reflects only the Node.js major version against +which the addon was built, it may match the running Node.js process even though +some of the independent parts of the ABI are mismatched. To address this problem +the addon may optionally declare which portions of the Node.js ABI it uses by +invoking the `NODE_MODULE_DECLARE_ABI` macro. Any portions of the ABI included +as a parameter to the macro will be checked during addon load in addition to +`NODE_MODULE_VERSION` in order to ensure that all ABIs declared by the addon +have the version as requested by the addon. Node.js assumes that ABIs not +included in the invocation of the `NODE_MODULE_DECLARE_ABI` macro are not used +by the addon. + +If there is a mismatch between any ABI version number declared by the addon and +the corresponding ABI version number as present in the Node.js process +attempting to load the addon, Node.js will refuse to load the addon and throw an +exception in which it indicates which ABIs were mismatched. + +The `NODE_MODULE_DECLARE_ABI` macro may be invoked as follows: +```C++ +NODE_MODULE_DECLARE_ABI( + NODE_MODULE_ABI_VENDOR_VERSION, + NODE_MODULE_ABI_ENGINE_VERSION, + NODE_MODULE_ABI_OPENSSL_VERSION) +``` +Note that there must be no semicolon at the end of the declaration. + +One or more of the following parameters may be passed to +`NODE_MODULE_DECLARE_ABI`: +* `NODE_MODULE_ABI_VERSION_TERMINATOR` - this is a sentinel indicating the end +of the list of ABI declarations. It need not normally be used by addons. + +* `NODE_MODULE_ABI_VENDOR_VERSION` - this declaration ties the addon to a +specific vendor's version of Node.js. For example, if the addon is built against +the official disitrbution of Node.js, it will not load on a version of Node.js +provided by the Debian GNU/Linux project nor will it load on a version of +Electron. + +* `NODE_MODULE_ABI_ENGINE_VERSION` - this declaration ties the addon to a +specific JavaScript engine version. It will fail to load on a version of Node.js +that provides a different JavaScript engine version. + +* `NODE_MODULE_ABI_OPENSSL_VERSION` - this declaration ties the addon to a +specific version of the OpenSSL library. It will not load on a version of +Node.js that provides a different version of the OpenSSL library. + +* `NODE_MODULE_ABI_LIBUV_VERSION` - this declaration ties the addon to a +specific version of the libuv library. It will fail to load on a version of +Node.js that provides a different version of libuv. + +* `NODE_MODULE_ABI_ICU_VERSION` - this declaration ties the addon to a +specific version of the ICU library. It will fail to load on a version of +Node.js that provides a different version of the ICU library. + +* `NODE_MODULE_ABI_CARES_VERSION` - this declaration ties the addon to a +specific version of the c-ares library. It will fail to load on a version of +Node.js that provides a different version of the c-ares library. + ### Loading Addons using require() The filename extension of the compiled Addon binary is `.node` (as opposed