From 6740cdfc5dab58844a21cec7d1019600485ace87 Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Tue, 17 Apr 2018 15:49:01 -0400 Subject: [PATCH] src: kickstart addon by calling its constructor If the addon does not self-register and a well-known Init symbol cannot be found, look for a version-agnostic symbol which calls the library constructor and call it if found. --- src/node.cc | 24 +++++++++++++-- src/node.h | 10 +++++++ src/node_api.h | 10 +++++++ test/addons-napi/addon-fallback/binding.c | 14 +++++++++ test/addons-napi/addon-fallback/binding.gyp | 8 +++++ test/addons-napi/addon-fallback/test.js | 12 ++++++++ test/addons/addon-fallback/binding.cc | 33 +++++++++++++++++++++ test/addons/addon-fallback/binding.gyp | 8 +++++ test/addons/addon-fallback/test.js | 12 ++++++++ 9 files changed, 128 insertions(+), 3 deletions(-) create mode 100644 test/addons-napi/addon-fallback/binding.c create mode 100644 test/addons-napi/addon-fallback/binding.gyp create mode 100644 test/addons-napi/addon-fallback/test.js create mode 100644 test/addons/addon-fallback/binding.cc create mode 100644 test/addons/addon-fallback/binding.gyp create mode 100644 test/addons/addon-fallback/test.js diff --git a/src/node.cc b/src/node.cc index 6f946ab3ad1cc7..e3c43d3b15fa58 100644 --- a/src/node.cc +++ b/src/node.cc @@ -2225,6 +2225,13 @@ inline InitializerCallback GetInitializerCallback(DLib* dlib) { return reinterpret_cast(dlib->GetSymbolAddress(name)); } +using KickstartCallback = void (*)(); + +inline KickstartCallback GetKickstartCallback(DLib* dlib) { + const char* name = "node_kickstart_module"; + return reinterpret_cast(dlib->GetSymbolAddress(name)); +} + // DLOpen is process.dlopen(module, filename, flags). // Used to load 'module.node' dynamically shared objects. // @@ -2263,7 +2270,7 @@ static void DLOpen(const FunctionCallbackInfo& args) { // Objects containing v14 or later modules will have registered themselves // on the pending list. Activate all of them now. At present, only one // module per object is supported. - node_module* const mp = modpending; + node_module* mp = modpending; modpending = nullptr; if (!is_opened) { @@ -2281,15 +2288,26 @@ static void DLOpen(const FunctionCallbackInfo& args) { if (mp == nullptr) { if (auto callback = GetInitializerCallback(&dlib)) { callback(exports, module, context); - } else { + return; + } else if (auto kickstart = GetKickstartCallback(&dlib)) { + kickstart(); + mp = modpending; + modpending = nullptr; + } + if (mp == nullptr) { dlib.Close(); env->ThrowError("Module did not self-register."); + return; } - return; } // -1 is used for N-API modules if ((mp->nm_version != -1) && (mp->nm_version != NODE_MODULE_VERSION)) { + if (auto callback = GetInitializerCallback(&dlib)) { + callback(exports, module, context); + return; + } + char errmsg[1024]; snprintf(errmsg, sizeof(errmsg), diff --git a/src/node.h b/src/node.h index ab5d1c120fa007..c4cee960e3d74b 100644 --- a/src/node.h +++ b/src/node.h @@ -545,6 +545,16 @@ extern "C" NODE_EXTERN void node_module_register(void* mod); /* NOLINTNEXTLINE (readability/null_usage) */ \ NODE_MODULE_CONTEXT_AWARE_X(modname, regfunc, NULL, 0) +#define NODE_MODULE_FALLBACK_X(modname) \ + extern "C" { \ + void node_kickstart_module() { \ + _register_ ## modname(); \ + } \ + } + +#define NODE_MODULE_FALLBACK(modname) \ + NODE_MODULE_FALLBACK_X(modname) + /* * For backward compatibility in add-on modules. */ diff --git a/src/node_api.h b/src/node_api.h index aaf002b7584449..c14ec3c4f8efb0 100644 --- a/src/node_api.h +++ b/src/node_api.h @@ -93,6 +93,16 @@ typedef struct { #define NAPI_MODULE(modname, regfunc) \ NAPI_MODULE_X(modname, regfunc, NULL, 0) // NOLINT (readability/null_usage) +#define NAPI_MODULE_FALLBACK_X(modname) \ + EXTERN_C_START \ + void node_kickstart_module() { \ + _register_ ## modname(); \ + } \ + EXTERN_C_END + +#define NAPI_MODULE_FALLBACK(modname) \ + NAPI_MODULE_FALLBACK_X(modname) + #define NAPI_AUTO_LENGTH SIZE_MAX EXTERN_C_START diff --git a/test/addons-napi/addon-fallback/binding.c b/test/addons-napi/addon-fallback/binding.c new file mode 100644 index 00000000000000..0c842666b6fa7f --- /dev/null +++ b/test/addons-napi/addon-fallback/binding.c @@ -0,0 +1,14 @@ +#include "node_api.h" +#include "../common.h" + +static napi_value Init(napi_env env, napi_value exports) { + napi_value result, answer; + + NAPI_CALL(env, napi_create_object(env, &result)); + NAPI_CALL(env, napi_create_int64(env, 42, &answer)); + NAPI_CALL(env, napi_set_named_property(env, result, "answer", answer)); + return result; +} + +NAPI_MODULE(NODE_GYP_MODULE_NAME, Init) +NAPI_MODULE_FALLBACK(NODE_GYP_MODULE_NAME) diff --git a/test/addons-napi/addon-fallback/binding.gyp b/test/addons-napi/addon-fallback/binding.gyp new file mode 100644 index 00000000000000..413621ade335a1 --- /dev/null +++ b/test/addons-napi/addon-fallback/binding.gyp @@ -0,0 +1,8 @@ +{ + 'targets': [ + { + 'target_name': 'binding', + 'sources': [ 'binding.c' ] + } + ] +} diff --git a/test/addons-napi/addon-fallback/test.js b/test/addons-napi/addon-fallback/test.js new file mode 100644 index 00000000000000..bde7fd787a5b41 --- /dev/null +++ b/test/addons-napi/addon-fallback/test.js @@ -0,0 +1,12 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); +const bindingPath = require.resolve(`./build/${common.buildType}/binding`); +const binding = require(bindingPath); +assert.strictEqual(binding.answer, 42); + +// Test multiple loading of the same module. +delete require.cache[bindingPath]; +const rebinding = require(bindingPath); +assert.strictEqual(rebinding.answer, 42); +assert.notStrictEqual(binding, rebinding); diff --git a/test/addons/addon-fallback/binding.cc b/test/addons/addon-fallback/binding.cc new file mode 100644 index 00000000000000..cc46a3b417209f --- /dev/null +++ b/test/addons/addon-fallback/binding.cc @@ -0,0 +1,33 @@ +#include "node.h" + +void Init(v8::Local exports, + v8::Local module, + v8::Local context) { + const char *error = nullptr; + + v8::Isolate *isolate = context->GetIsolate(); + v8::Local result = v8::Object::New(isolate); + v8::Local prop_name; + auto answer = v8::Number::New(isolate, 42.0).As(); + + prop_name = v8::String::NewFromUtf8(isolate, "answer", + v8::NewStringType::kNormal).ToLocalChecked(); + if (result->Set(context, prop_name, answer).FromJust()) { + prop_name = v8::String::NewFromUtf8(isolate, "exports", + v8::NewStringType::kNormal).ToLocalChecked(); + if (module.As()->Set(context, prop_name, result).FromJust()) { + return; + } else { + error = "Failed to set exports"; + } + } else { + error = "Failed to set property"; + } + + isolate->ThrowException( + v8::String::NewFromUtf8(isolate, error, + v8::NewStringType::kNormal).ToLocalChecked()); +} + +NODE_MODULE_CONTEXT_AWARE(NODE_GYP_MODULE_NAME, Init) +NODE_MODULE_FALLBACK(NODE_GYP_MODULE_NAME) diff --git a/test/addons/addon-fallback/binding.gyp b/test/addons/addon-fallback/binding.gyp new file mode 100644 index 00000000000000..3bfb84493f3e87 --- /dev/null +++ b/test/addons/addon-fallback/binding.gyp @@ -0,0 +1,8 @@ +{ + 'targets': [ + { + 'target_name': 'binding', + 'sources': [ 'binding.cc' ] + } + ] +} diff --git a/test/addons/addon-fallback/test.js b/test/addons/addon-fallback/test.js new file mode 100644 index 00000000000000..bde7fd787a5b41 --- /dev/null +++ b/test/addons/addon-fallback/test.js @@ -0,0 +1,12 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); +const bindingPath = require.resolve(`./build/${common.buildType}/binding`); +const binding = require(bindingPath); +assert.strictEqual(binding.answer, 42); + +// Test multiple loading of the same module. +delete require.cache[bindingPath]; +const rebinding = require(bindingPath); +assert.strictEqual(rebinding.answer, 42); +assert.notStrictEqual(binding, rebinding);