mirror of
https://github.com/nodejs/node.git
synced 2024-11-21 10:59:27 +00:00
src: add option to disable loading native addons
PR-URL: https://github.com/nodejs/node/pull/39977 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Bradley Farias <bradley.meck@gmail.com> Reviewed-By: Guy Bedford <guybedford@gmail.com> Reviewed-By: Michael Dawson <midawson@redhat.com>
This commit is contained in:
parent
a42bd7e944
commit
a9dd03b1ec
@ -598,6 +598,15 @@ added: v7.10.0
|
||||
|
||||
This option is a no-op. It is kept for compatibility.
|
||||
|
||||
### `--no-addons`
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
Disable the `node-addons` exports condition as well as disable loading
|
||||
native addons. When `--no-addons` is specified, calling `process.dlopen` or
|
||||
requiring a native C++ addon will fail and throw an exception.
|
||||
|
||||
### `--no-deprecation`
|
||||
<!-- YAML
|
||||
added: v0.8.0
|
||||
@ -1428,6 +1437,7 @@ Node.js options that are allowed are:
|
||||
* `--inspect`
|
||||
* `--max-http-header-size`
|
||||
* `--napi-modules`
|
||||
* `--no-addons`
|
||||
* `--no-deprecation`
|
||||
* `--no-experimental-repl-await`
|
||||
* `--no-extra-info-on-fatal-exception`
|
||||
|
@ -1027,6 +1027,14 @@ added:
|
||||
|
||||
The [debugger][] timed out waiting for the required host/port to be free.
|
||||
|
||||
<a id="ERR_DLOPEN_DISABLED"></a>
|
||||
### `ERR_DLOPEN_DISABLED`
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
Loading native addons has been disabled using [`--no-addons`][].
|
||||
|
||||
<a id="ERR_DLOPEN_FAILED"></a>
|
||||
### `ERR_DLOPEN_FAILED`
|
||||
<!-- YAML
|
||||
@ -2879,6 +2887,7 @@ The native call from `process.cpuUsage` could not be processed.
|
||||
[`'uncaughtException'`]: process.md#event-uncaughtexception
|
||||
[`--disable-proto=throw`]: cli.md#--disable-protomode
|
||||
[`--force-fips`]: cli.md#--force-fips
|
||||
[`--no-addons`]: cli.md#--no-addons
|
||||
[`Class: assert.AssertionError`]: assert.md#class-assertassertionerror
|
||||
[`ERR_INVALID_ARG_TYPE`]: #err_invalid_arg_type
|
||||
[`ERR_MISSING_MESSAGE_PORT_IN_TRANSFER_LIST`]: #err_missing_message_port_in_transfer_list
|
||||
|
@ -537,6 +537,11 @@ Node.js implements the following conditions:
|
||||
* `"node"` - matches for any Node.js environment. Can be a CommonJS or ES
|
||||
module file. _This condition should always come after `"import"` or
|
||||
`"require"`._
|
||||
* `"node-addons"` - similar to `"node"` and matches for any Node.js environment.
|
||||
This condition can be used to provide an entry point which uses native C++
|
||||
addons as opposed to an entry point which is more universal and doesn't rely
|
||||
on native addons. This condition can be disabled via the
|
||||
[`--no-addons` flag][].
|
||||
* `"default"` - the generic fallback that always matches. Can be a CommonJS
|
||||
or ES module file. _This condition should always come last._
|
||||
|
||||
@ -615,17 +620,23 @@ node --conditions=development main.js
|
||||
```
|
||||
|
||||
which would then resolve the `"development"` condition in package imports and
|
||||
exports, while resolving the existing `"node"`, `"default"`, `"import"`, and
|
||||
`"require"` conditions as appropriate.
|
||||
exports, while resolving the existing `"node"`, `"node-addons"`, `"default"`,
|
||||
`"import"`, and `"require"` conditions as appropriate.
|
||||
|
||||
Any number of custom conditions can be set with repeat flags.
|
||||
|
||||
### Conditions Definitions
|
||||
|
||||
The `"import"`, `"require"`, `"node"` and `"default"` conditions are defined
|
||||
and implemented in Node.js core,
|
||||
The `"import"`, `"require"`, `"node"`, `"node-addons"` and `"default"`
|
||||
conditions are defined and implemented in Node.js core,
|
||||
[as specified above](#conditional-exports).
|
||||
|
||||
The `"node-addons"` condition can be used to provide an entry point which
|
||||
uses native C++ addons. However, this condition can be disabled via the
|
||||
[`--no-addons` flag][]. When using `"node-addons"`, it's recommended to treat
|
||||
`"default"` as an enhancement that provides a more universal entry point, e.g.
|
||||
using WebAssembly instead of a native addon.
|
||||
|
||||
Other condition strings are unknown to Node.js and thus ignored by default.
|
||||
Runtimes or tools other than Node.js can use them at their discretion.
|
||||
|
||||
@ -1249,6 +1260,7 @@ This field defines [subpath imports][] for the current package.
|
||||
[`"packageManager"`]: #packagemanager
|
||||
[`"type"`]: #type
|
||||
[`--conditions` flag]: #resolving-user-conditions
|
||||
[`--no-addons` flag]: cli.md#--no-addons
|
||||
[`ERR_PACKAGE_PATH_NOT_EXPORTED`]: errors.md#err_package_path_not_exported
|
||||
[`esm`]: https://github.com/standard-things/esm#readme
|
||||
[`package.json`]: #nodejs-packagejson-field-definitions
|
||||
|
@ -280,6 +280,11 @@ Hide extra information on fatal exception that causes exit.
|
||||
Disable runtime checks for `async_hooks`.
|
||||
These will still be enabled dynamically when `async_hooks` is enabled.
|
||||
.
|
||||
.It Fl -no-addons
|
||||
Disable the `node-addons` exports condition as well as disable loading native
|
||||
addons. When `--no-addons` is specified, calling `process.dlopen` or requiring
|
||||
a native C++ addon will fail and throw an exception.
|
||||
.
|
||||
.It Fl -no-warnings
|
||||
Silence all process warnings (including deprecations).
|
||||
.
|
||||
|
@ -30,8 +30,16 @@ let debug = require('internal/util/debuglog').debuglog('module', (fn) => {
|
||||
debug = fn;
|
||||
});
|
||||
|
||||
const noAddons = getOptionValue('--no-addons');
|
||||
const addonConditions = noAddons ? [] : ['node-addons'];
|
||||
|
||||
// TODO: Use this set when resolving pkg#exports conditions in loader.js.
|
||||
const cjsConditions = new SafeSet(['require', 'node', ...userConditions]);
|
||||
const cjsConditions = new SafeSet([
|
||||
'require',
|
||||
'node',
|
||||
...addonConditions,
|
||||
...userConditions,
|
||||
]);
|
||||
|
||||
function loadNativeModule(filename, request) {
|
||||
const mod = NativeModule.map.get(filename);
|
||||
|
@ -58,7 +58,16 @@ const { Module: CJSModule } = require('internal/modules/cjs/loader');
|
||||
|
||||
const packageJsonReader = require('internal/modules/package_json_reader');
|
||||
const userConditions = getOptionValue('--conditions');
|
||||
const DEFAULT_CONDITIONS = ObjectFreeze(['node', 'import', ...userConditions]);
|
||||
const noAddons = getOptionValue('--no-addons');
|
||||
const addonConditions = noAddons ? [] : ['node-addons'];
|
||||
|
||||
const DEFAULT_CONDITIONS = ObjectFreeze([
|
||||
'node',
|
||||
'import',
|
||||
...addonConditions,
|
||||
...userConditions,
|
||||
]);
|
||||
|
||||
const DEFAULT_CONDITIONS_SET = new SafeSet(DEFAULT_CONDITIONS);
|
||||
|
||||
/**
|
||||
|
@ -861,6 +861,11 @@ inline bool Environment::is_main_thread() const {
|
||||
return worker_context() == nullptr;
|
||||
}
|
||||
|
||||
inline bool Environment::no_native_addons() const {
|
||||
return (flags_ & EnvironmentFlags::kNoNativeAddons) ||
|
||||
!options_->allow_native_addons;
|
||||
}
|
||||
|
||||
inline bool Environment::should_not_register_esm_loader() const {
|
||||
return flags_ & EnvironmentFlags::kNoRegisterESMLoader;
|
||||
}
|
||||
|
@ -1197,6 +1197,7 @@ class Environment : public MemoryRetainer {
|
||||
inline void set_has_serialized_options(bool has_serialized_options);
|
||||
|
||||
inline bool is_main_thread() const;
|
||||
inline bool no_native_addons() const;
|
||||
inline bool should_not_register_esm_loader() const;
|
||||
inline bool owns_process_state() const;
|
||||
inline bool owns_inspector() const;
|
||||
|
@ -407,7 +407,13 @@ enum Flags : uint64_t {
|
||||
// Set this flag to force hiding console windows when spawning child
|
||||
// processes. This is usually used when embedding Node.js in GUI programs on
|
||||
// Windows.
|
||||
kHideConsoleWindows = 1 << 5
|
||||
kHideConsoleWindows = 1 << 5,
|
||||
// Set this flag to disable loading native addons via `process.dlopen`.
|
||||
// This environment flag is especially important for worker threads
|
||||
// so that a worker thread can't load a native addon even if `execArgv`
|
||||
// is overwritten and `--no-addons` is not specified but was specified
|
||||
// for this Environment instance.
|
||||
kNoNativeAddons = 1 << 6
|
||||
};
|
||||
} // namespace EnvironmentFlags
|
||||
|
||||
|
@ -415,6 +415,12 @@ inline napi_addon_register_func GetNapiInitializerCallback(DLib* dlib) {
|
||||
// cache that's a plain C list or hash table that's shared across contexts?
|
||||
void DLOpen(const FunctionCallbackInfo<Value>& args) {
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
|
||||
if (env->no_native_addons()) {
|
||||
return THROW_ERR_DLOPEN_DISABLED(
|
||||
env, "Cannot load native addon because loading addons is disabled.");
|
||||
}
|
||||
|
||||
auto context = env->context();
|
||||
|
||||
CHECK_NULL(thread_local_modpending);
|
||||
|
@ -57,6 +57,7 @@ void OnFatalError(const char* location, const char* message);
|
||||
V(ERR_CRYPTO_UNKNOWN_DH_GROUP, Error) \
|
||||
V(ERR_CRYPTO_UNSUPPORTED_OPERATION, Error) \
|
||||
V(ERR_CRYPTO_JOB_INIT_FAILED, Error) \
|
||||
V(ERR_DLOPEN_DISABLED, Error) \
|
||||
V(ERR_DLOPEN_FAILED, Error) \
|
||||
V(ERR_EXECUTION_ENVIRONMENT_NOT_AVAILABLE, Error) \
|
||||
V(ERR_INVALID_ADDRESS, Error) \
|
||||
|
@ -402,6 +402,11 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
|
||||
&EnvironmentOptions::force_async_hooks_checks,
|
||||
kAllowedInEnvironment,
|
||||
true);
|
||||
AddOption("--addons",
|
||||
"disable loading native addons",
|
||||
&EnvironmentOptions::allow_native_addons,
|
||||
kAllowedInEnvironment,
|
||||
true);
|
||||
AddOption("--warnings",
|
||||
"silence all process warnings",
|
||||
&EnvironmentOptions::warnings,
|
||||
|
@ -121,6 +121,7 @@ class EnvironmentOptions : public Options {
|
||||
uint64_t max_http_header_size = 16 * 1024;
|
||||
bool deprecation = true;
|
||||
bool force_async_hooks_checks = true;
|
||||
bool allow_native_addons = true;
|
||||
bool warnings = true;
|
||||
bool force_context_aware = false;
|
||||
bool pending_deprecation = false;
|
||||
|
@ -560,6 +560,8 @@ void Worker::New(const FunctionCallbackInfo<Value>& args) {
|
||||
worker->environment_flags_ |= EnvironmentFlags::kTrackUnmanagedFds;
|
||||
if (env->hide_console_windows())
|
||||
worker->environment_flags_ |= EnvironmentFlags::kHideConsoleWindows;
|
||||
if (env->no_native_addons())
|
||||
worker->environment_flags_ |= EnvironmentFlags::kNoNativeAddons;
|
||||
}
|
||||
|
||||
void Worker::StartThread(const FunctionCallbackInfo<Value>& args) {
|
||||
|
9
test/addons/no-addons/binding.gyp
Normal file
9
test/addons/no-addons/binding.gyp
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'binding',
|
||||
'sources': [ '../hello-world/binding.cc' ],
|
||||
'includes': ['../common.gypi'],
|
||||
}
|
||||
]
|
||||
}
|
59
test/addons/no-addons/test-worker.js
Normal file
59
test/addons/no-addons/test-worker.js
Normal file
@ -0,0 +1,59 @@
|
||||
// Flags: --no-addons
|
||||
|
||||
'use strict';
|
||||
|
||||
const common = require('../../common');
|
||||
const assert = require('assert');
|
||||
const path = require('path');
|
||||
const { Worker } = require('worker_threads');
|
||||
|
||||
const binding = path.resolve(__dirname, `./build/${common.buildType}/binding`);
|
||||
|
||||
const assertError = (error) => {
|
||||
assert.strictEqual(error.code, 'ERR_DLOPEN_DISABLED');
|
||||
assert.strictEqual(
|
||||
error.message,
|
||||
'Cannot load native addon because loading addons is disabled.'
|
||||
);
|
||||
};
|
||||
|
||||
{
|
||||
// Flags should be inherited
|
||||
const worker = new Worker(`require(${JSON.stringify(binding)})`, {
|
||||
eval: true,
|
||||
});
|
||||
|
||||
worker.on('error', common.mustCall(assertError));
|
||||
}
|
||||
|
||||
{
|
||||
// Should throw when using `process.dlopen` directly
|
||||
const worker = new Worker(
|
||||
`process.dlopen({ exports: {} }, ${JSON.stringify(binding)});`,
|
||||
{
|
||||
eval: true,
|
||||
}
|
||||
);
|
||||
|
||||
worker.on('error', common.mustCall(assertError));
|
||||
}
|
||||
|
||||
{
|
||||
// Explicitly pass `--no-addons`
|
||||
const worker = new Worker(`require(${JSON.stringify(binding)})`, {
|
||||
eval: true,
|
||||
execArgv: ['--no-addons'],
|
||||
});
|
||||
|
||||
worker.on('error', common.mustCall(assertError));
|
||||
}
|
||||
|
||||
{
|
||||
// If `execArgv` is overwritten it should still fail to load addons
|
||||
const worker = new Worker(`require(${JSON.stringify(binding)})`, {
|
||||
eval: true,
|
||||
execArgv: [],
|
||||
});
|
||||
|
||||
worker.on('error', common.mustCall(assertError));
|
||||
}
|
43
test/addons/no-addons/test.js
Normal file
43
test/addons/no-addons/test.js
Normal file
@ -0,0 +1,43 @@
|
||||
// Flags: --no-addons
|
||||
|
||||
'use strict';
|
||||
|
||||
const common = require('../../common');
|
||||
const assert = require('assert');
|
||||
|
||||
const bindingPath = require.resolve(`./build/${common.buildType}/binding`);
|
||||
|
||||
const assertError = (error) => {
|
||||
assert(error instanceof Error);
|
||||
assert.strictEqual(error.code, 'ERR_DLOPEN_DISABLED');
|
||||
assert.strictEqual(
|
||||
error.message,
|
||||
'Cannot load native addon because loading addons is disabled.'
|
||||
);
|
||||
};
|
||||
|
||||
{
|
||||
let threw = false;
|
||||
|
||||
try {
|
||||
require(bindingPath);
|
||||
} catch (error) {
|
||||
assertError(error);
|
||||
threw = true;
|
||||
}
|
||||
|
||||
assert(threw);
|
||||
}
|
||||
|
||||
{
|
||||
let threw = false;
|
||||
|
||||
try {
|
||||
process.dlopen({ exports: {} }, bindingPath);
|
||||
} catch (error) {
|
||||
assertError(error);
|
||||
threw = true;
|
||||
}
|
||||
|
||||
assert(threw);
|
||||
}
|
27
test/es-module/test-esm-no-addons.mjs
Normal file
27
test/es-module/test-esm-no-addons.mjs
Normal file
@ -0,0 +1,27 @@
|
||||
import { mustCall } from '../common/index.mjs';
|
||||
import { Worker, isMainThread } from 'worker_threads';
|
||||
import assert from 'assert';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { requireFixture, importFixture } from '../fixtures/pkgexports.mjs';
|
||||
|
||||
if (isMainThread) {
|
||||
const tests = [[], ['--no-addons']];
|
||||
|
||||
for (const execArgv of tests) {
|
||||
new Worker(fileURLToPath(import.meta.url), { execArgv });
|
||||
}
|
||||
} else {
|
||||
[requireFixture, importFixture].forEach((loadFixture) => {
|
||||
loadFixture('pkgexports/no-addons').then(
|
||||
mustCall((module) => {
|
||||
const message = module.default;
|
||||
|
||||
if (process.execArgv.length === 0) {
|
||||
assert.strictEqual(message, 'using native addons');
|
||||
} else {
|
||||
assert.strictEqual(message, 'not using native addons');
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
@ -1,8 +1,14 @@
|
||||
import {ok, deepStrictEqual} from 'assert';
|
||||
import { ok, deepStrictEqual } from 'assert';
|
||||
|
||||
export async function resolve(specifier, context, defaultResolve) {
|
||||
ok(Array.isArray(context.conditions), 'loader receives conditions array');
|
||||
deepStrictEqual([...context.conditions].sort(), ['import', 'node']);
|
||||
|
||||
deepStrictEqual([...context.conditions].sort(), [
|
||||
'import',
|
||||
'node',
|
||||
'node-addons',
|
||||
]);
|
||||
|
||||
return defaultResolve(specifier, {
|
||||
...context,
|
||||
conditions: ['custom-condition', ...context.conditions],
|
||||
|
3
test/fixtures/node_modules/pkgexports/addons-entry.js
generated
vendored
Normal file
3
test/fixtures/node_modules/pkgexports/addons-entry.js
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = 'using native addons';
|
3
test/fixtures/node_modules/pkgexports/no-addons-entry.js
generated
vendored
Normal file
3
test/fixtures/node_modules/pkgexports/no-addons-entry.js
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = 'not using native addons';
|
4
test/fixtures/node_modules/pkgexports/package.json
generated
vendored
4
test/fixtures/node_modules/pkgexports/package.json
generated
vendored
@ -20,6 +20,10 @@
|
||||
"./nofallback1": [],
|
||||
"./nofallback2": [null, {}, "builtin:x"],
|
||||
"./nodemodules": "./node_modules/internalpkg/x.js",
|
||||
"./no-addons": {
|
||||
"node-addons": "./addons-entry.js",
|
||||
"default": "./no-addons-entry.js"
|
||||
},
|
||||
"./condition": [{
|
||||
"custom-condition": {
|
||||
"import": "./custom-condition.mjs",
|
||||
|
29
test/parallel/test-no-addons-resolution-condition.js
Normal file
29
test/parallel/test-no-addons-resolution-condition.js
Normal file
@ -0,0 +1,29 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const fixtures = require('../common/fixtures');
|
||||
const { Worker, isMainThread, parentPort } = require('worker_threads');
|
||||
const assert = require('assert');
|
||||
const { createRequire } = require('module');
|
||||
|
||||
const loadFixture = createRequire(fixtures.path('node_modules'));
|
||||
|
||||
if (isMainThread) {
|
||||
const tests = [[], ['--no-addons']];
|
||||
|
||||
for (const execArgv of tests) {
|
||||
const worker = new Worker(__filename, { execArgv });
|
||||
|
||||
worker.on('message', common.mustCall((message) => {
|
||||
if (execArgv.length === 0) {
|
||||
assert.strictEqual(message, 'using native addons');
|
||||
} else {
|
||||
assert.strictEqual(message, 'not using native addons');
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
} else {
|
||||
const message = loadFixture('pkgexports/no-addons');
|
||||
parentPort.postMessage(message);
|
||||
}
|
Loading…
Reference in New Issue
Block a user