Skip to content

Commit

Permalink
src: add addon ABI declaration option
Browse files Browse the repository at this point in the history
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: nodejs/TSC#651
  • Loading branch information
Gabriel Schulhof committed Jan 16, 2019
1 parent 1c7b5db commit 2dfaa45
Show file tree
Hide file tree
Showing 13 changed files with 331 additions and 124 deletions.
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
69 changes: 69 additions & 0 deletions doc/api/addons.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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/
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

0 comments on commit 2dfaa45

Please sign in to comment.