Skip to content

Commit 6740cdf

Browse files
author
Gabriel Schulhof
committed
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.
1 parent 95fafc0 commit 6740cdf

File tree

9 files changed

+128
-3
lines changed

9 files changed

+128
-3
lines changed

src/node.cc

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2225,6 +2225,13 @@ inline InitializerCallback GetInitializerCallback(DLib* dlib) {
22252225
return reinterpret_cast<InitializerCallback>(dlib->GetSymbolAddress(name));
22262226
}
22272227

2228+
using KickstartCallback = void (*)();
2229+
2230+
inline KickstartCallback GetKickstartCallback(DLib* dlib) {
2231+
const char* name = "node_kickstart_module";
2232+
return reinterpret_cast<KickstartCallback>(dlib->GetSymbolAddress(name));
2233+
}
2234+
22282235
// DLOpen is process.dlopen(module, filename, flags).
22292236
// Used to load 'module.node' dynamically shared objects.
22302237
//
@@ -2263,7 +2270,7 @@ static void DLOpen(const FunctionCallbackInfo<Value>& args) {
22632270
// Objects containing v14 or later modules will have registered themselves
22642271
// on the pending list. Activate all of them now. At present, only one
22652272
// module per object is supported.
2266-
node_module* const mp = modpending;
2273+
node_module* mp = modpending;
22672274
modpending = nullptr;
22682275

22692276
if (!is_opened) {
@@ -2281,15 +2288,26 @@ static void DLOpen(const FunctionCallbackInfo<Value>& args) {
22812288
if (mp == nullptr) {
22822289
if (auto callback = GetInitializerCallback(&dlib)) {
22832290
callback(exports, module, context);
2284-
} else {
2291+
return;
2292+
} else if (auto kickstart = GetKickstartCallback(&dlib)) {
2293+
kickstart();
2294+
mp = modpending;
2295+
modpending = nullptr;
2296+
}
2297+
if (mp == nullptr) {
22852298
dlib.Close();
22862299
env->ThrowError("Module did not self-register.");
2300+
return;
22872301
}
2288-
return;
22892302
}
22902303

22912304
// -1 is used for N-API modules
22922305
if ((mp->nm_version != -1) && (mp->nm_version != NODE_MODULE_VERSION)) {
2306+
if (auto callback = GetInitializerCallback(&dlib)) {
2307+
callback(exports, module, context);
2308+
return;
2309+
}
2310+
22932311
char errmsg[1024];
22942312
snprintf(errmsg,
22952313
sizeof(errmsg),

src/node.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,16 @@ extern "C" NODE_EXTERN void node_module_register(void* mod);
545545
/* NOLINTNEXTLINE (readability/null_usage) */ \
546546
NODE_MODULE_CONTEXT_AWARE_X(modname, regfunc, NULL, 0)
547547

548+
#define NODE_MODULE_FALLBACK_X(modname) \
549+
extern "C" { \
550+
void node_kickstart_module() { \
551+
_register_ ## modname(); \
552+
} \
553+
}
554+
555+
#define NODE_MODULE_FALLBACK(modname) \
556+
NODE_MODULE_FALLBACK_X(modname)
557+
548558
/*
549559
* For backward compatibility in add-on modules.
550560
*/

src/node_api.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,16 @@ typedef struct {
9393
#define NAPI_MODULE(modname, regfunc) \
9494
NAPI_MODULE_X(modname, regfunc, NULL, 0) // NOLINT (readability/null_usage)
9595

96+
#define NAPI_MODULE_FALLBACK_X(modname) \
97+
EXTERN_C_START \
98+
void node_kickstart_module() { \
99+
_register_ ## modname(); \
100+
} \
101+
EXTERN_C_END
102+
103+
#define NAPI_MODULE_FALLBACK(modname) \
104+
NAPI_MODULE_FALLBACK_X(modname)
105+
96106
#define NAPI_AUTO_LENGTH SIZE_MAX
97107

98108
EXTERN_C_START
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#include "node_api.h"
2+
#include "../common.h"
3+
4+
static napi_value Init(napi_env env, napi_value exports) {
5+
napi_value result, answer;
6+
7+
NAPI_CALL(env, napi_create_object(env, &result));
8+
NAPI_CALL(env, napi_create_int64(env, 42, &answer));
9+
NAPI_CALL(env, napi_set_named_property(env, result, "answer", answer));
10+
return result;
11+
}
12+
13+
NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
14+
NAPI_MODULE_FALLBACK(NODE_GYP_MODULE_NAME)
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
'targets': [
3+
{
4+
'target_name': 'binding',
5+
'sources': [ 'binding.c' ]
6+
}
7+
]
8+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
'use strict';
2+
const common = require('../../common');
3+
const assert = require('assert');
4+
const bindingPath = require.resolve(`./build/${common.buildType}/binding`);
5+
const binding = require(bindingPath);
6+
assert.strictEqual(binding.answer, 42);
7+
8+
// Test multiple loading of the same module.
9+
delete require.cache[bindingPath];
10+
const rebinding = require(bindingPath);
11+
assert.strictEqual(rebinding.answer, 42);
12+
assert.notStrictEqual(binding, rebinding);

test/addons/addon-fallback/binding.cc

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#include "node.h"
2+
3+
void Init(v8::Local<v8::Object> exports,
4+
v8::Local<v8::Value> module,
5+
v8::Local<v8::Context> context) {
6+
const char *error = nullptr;
7+
8+
v8::Isolate *isolate = context->GetIsolate();
9+
v8::Local<v8::Object> result = v8::Object::New(isolate);
10+
v8::Local<v8::String> prop_name;
11+
auto answer = v8::Number::New(isolate, 42.0).As<v8::Value>();
12+
13+
prop_name = v8::String::NewFromUtf8(isolate, "answer",
14+
v8::NewStringType::kNormal).ToLocalChecked();
15+
if (result->Set(context, prop_name, answer).FromJust()) {
16+
prop_name = v8::String::NewFromUtf8(isolate, "exports",
17+
v8::NewStringType::kNormal).ToLocalChecked();
18+
if (module.As<v8::Object>()->Set(context, prop_name, result).FromJust()) {
19+
return;
20+
} else {
21+
error = "Failed to set exports";
22+
}
23+
} else {
24+
error = "Failed to set property";
25+
}
26+
27+
isolate->ThrowException(
28+
v8::String::NewFromUtf8(isolate, error,
29+
v8::NewStringType::kNormal).ToLocalChecked());
30+
}
31+
32+
NODE_MODULE_CONTEXT_AWARE(NODE_GYP_MODULE_NAME, Init)
33+
NODE_MODULE_FALLBACK(NODE_GYP_MODULE_NAME)
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
'targets': [
3+
{
4+
'target_name': 'binding',
5+
'sources': [ 'binding.cc' ]
6+
}
7+
]
8+
}

test/addons/addon-fallback/test.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
'use strict';
2+
const common = require('../../common');
3+
const assert = require('assert');
4+
const bindingPath = require.resolve(`./build/${common.buildType}/binding`);
5+
const binding = require(bindingPath);
6+
assert.strictEqual(binding.answer, 42);
7+
8+
// Test multiple loading of the same module.
9+
delete require.cache[bindingPath];
10+
const rebinding = require(bindingPath);
11+
assert.strictEqual(rebinding.answer, 42);
12+
assert.notStrictEqual(binding, rebinding);

0 commit comments

Comments
 (0)