Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

POC: src: add addon ABI declaration option #25539

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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" \
Expand Down
77 changes: 75 additions & 2 deletions doc/api/addons.md
Original file line number Diff line number Diff line change
Expand Up @@ -323,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 <v8.h>`) and
`node-gyp` will locate the appropriate headers automatically. However, there
are a few caveats to be aware of:
Expand All @@ -339,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
Expand Down Expand Up @@ -1377,5 +1449,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/
101 changes: 1 addition & 100 deletions src/node.h
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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<v8::Object> exports, \
v8::Local<v8::Value> module, \
v8::Local<v8::Context> context); \
NODE_MODULE_CONTEXT_AWARE(NODE_GYP_MODULE_NAME, \
NODE_MODULE_INITIALIZER) \
void NODE_MODULE_INITIALIZER(v8::Local<v8::Object> exports, \
v8::Local<v8::Value> module, \
v8::Local<v8::Context> 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.
*/
Expand Down
26 changes: 26 additions & 0 deletions src/node_abi_versions.h
Original file line number Diff line number Diff line change
@@ -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_
145 changes: 145 additions & 0 deletions src/node_addon_macros.h
Original file line number Diff line number Diff line change
@@ -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<v8::Object> exports, \
v8::Local<v8::Value> module, \
v8::Local<v8::Context> context); \
NODE_MODULE_CONTEXT_AWARE(NODE_GYP_MODULE_NAME, \
NODE_MODULE_INITIALIZER) \
void NODE_MODULE_INITIALIZER(v8::Local<v8::Object> exports, \
v8::Local<v8::Value> module, \
v8::Local<v8::Context> 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_
Loading