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

lib: add api to enable source-maps programmatically #39085

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
19 changes: 19 additions & 0 deletions doc/api/process.md
Expand Up @@ -2379,6 +2379,24 @@ This function is only available on POSIX platforms (i.e. not Windows or
Android).
This feature is not available in [`Worker`][] threads.

## `process.setSourceMapsEnabled(val)`
<!-- YAML
added: REPLACEME
-->

> Stability: 1 - Experimental

* `val` {boolean}

This function enables or disables the [Source Map v3][Source Map] support for
stack traces.

It provides same features as launching Node.js process with commandline options
`--enable-source-maps`.

Only source maps in JavaScript files that are loaded after source maps has been
enabled will be parsed and loaded.

## `process.setUncaughtExceptionCaptureCallback(fn)`
<!-- YAML
added: v9.3.0
Expand Down Expand Up @@ -2763,6 +2781,7 @@ cases:
[LTS]: https://github.com/nodejs/Release
[Readable]: stream.md#stream_readable_streams
[Signal Events]: #process_signal_events
[Source Map]: https://sourcemaps.info/spec.html
[Stream compatibility]: stream.md#stream_compatibility_with_older_node_js_versions
[TTY]: tty.md#tty_tty
[Writable]: stream.md#stream_writable_streams
Expand Down
7 changes: 7 additions & 0 deletions lib/internal/bootstrap/pre_execution.js
Expand Up @@ -65,6 +65,7 @@ function prepareMainThreadExecution(expandArgv1 = false) {
// (including preload modules).
initializeClusterIPC();

initializeSourceMapsHandlers();
initializeDeprecations();
initializeWASI();
initializeCJSLoader();
Expand Down Expand Up @@ -448,6 +449,12 @@ function initializeESMLoader() {
}
}

function initializeSourceMapsHandlers() {
const { setSourceMapsEnabled } =
require('internal/source_map/source_map_cache');
process.setSourceMapsEnabled = setSourceMapsEnabled;
}

function initializeFrozenIntrinsics() {
if (getOptionValue('--frozen-intrinsics')) {
process.emitWarning('The --frozen-intrinsics flag is experimental',
Expand Down
39 changes: 27 additions & 12 deletions lib/internal/source_map/source_map_cache.js
Expand Up @@ -29,6 +29,7 @@ const { IterableWeakMap } = require('internal/util/iterable_weak_map');
const {
normalizeReferrerURL,
} = require('internal/modules/cjs/helpers');
const { validateBoolean } = require('internal/validators');
// Since the CJS module cache is mutable, which leads to memory leaks when
// modules are deleted, we use a WeakMap so that the source map cache will
// be purged automatically:
Expand All @@ -41,22 +42,35 @@ let SourceMap;
let sourceMapsEnabled;
function getSourceMapsEnabled() {
if (sourceMapsEnabled === undefined) {
sourceMapsEnabled = getOptionValue('--enable-source-maps');
if (sourceMapsEnabled) {
const {
enableSourceMaps,
setPrepareStackTraceCallback
} = internalBinding('errors');
const {
prepareStackTrace
} = require('internal/source_map/prepare_stack_trace');
setPrepareStackTraceCallback(prepareStackTrace);
enableSourceMaps();
}
setSourceMapsEnabled(getOptionValue('--enable-source-maps'));
}
return sourceMapsEnabled;
}

function setSourceMapsEnabled(val) {
validateBoolean(val, 'val');

const {
setSourceMapsEnabled,
setPrepareStackTraceCallback
} = internalBinding('errors');
setSourceMapsEnabled(val);
if (val) {
const {
prepareStackTrace
} = require('internal/source_map/prepare_stack_trace');
setPrepareStackTraceCallback(prepareStackTrace);
} else if (sourceMapsEnabled !== undefined) {
// Reset prepare stack trace callback only when disabling source maps.
const {
prepareStackTrace,
} = require('internal/errors');
setPrepareStackTraceCallback(prepareStackTrace);
}

sourceMapsEnabled = val;
}

function maybeCacheSourceMap(filename, content, cjsModuleInstance) {
const sourceMapsEnabled = getSourceMapsEnabled();
if (!(process.env.NODE_V8_COVERAGE || sourceMapsEnabled)) return;
Expand Down Expand Up @@ -239,6 +253,7 @@ function findSourceMap(sourceURL) {
module.exports = {
findSourceMap,
getSourceMapsEnabled,
setSourceMapsEnabled,
maybeCacheSourceMap,
rekeySourceMap,
sourceMapCacheToObject,
Expand Down
10 changes: 6 additions & 4 deletions src/node_errors.cc
Expand Up @@ -820,9 +820,11 @@ void SetPrepareStackTraceCallback(const FunctionCallbackInfo<Value>& args) {
env->set_prepare_stack_trace_callback(args[0].As<Function>());
}

static void EnableSourceMaps(const FunctionCallbackInfo<Value>& args) {
static void SetSourceMapsEnabled(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
env->set_source_maps_enabled(true);
CHECK(args[0]->IsBoolean());
env->set_source_maps_enabled(
args[0].As<Boolean>()->BooleanValue(env->isolate()));
addaleax marked this conversation as resolved.
Show resolved Hide resolved
}

static void SetEnhanceStackForFatalException(
Expand Down Expand Up @@ -858,7 +860,7 @@ static void TriggerUncaughtException(const FunctionCallbackInfo<Value>& args) {

void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
registry->Register(SetPrepareStackTraceCallback);
registry->Register(EnableSourceMaps);
registry->Register(SetSourceMapsEnabled);
registry->Register(SetEnhanceStackForFatalException);
registry->Register(NoSideEffectsToString);
registry->Register(TriggerUncaughtException);
Expand All @@ -871,7 +873,7 @@ void Initialize(Local<Object> target,
Environment* env = Environment::GetCurrent(context);
env->SetMethod(
target, "setPrepareStackTraceCallback", SetPrepareStackTraceCallback);
env->SetMethod(target, "enableSourceMaps", EnableSourceMaps);
env->SetMethod(target, "setSourceMapsEnabled", SetSourceMapsEnabled);
env->SetMethod(target,
"setEnhanceStackForFatalException",
SetEnhanceStackForFatalException);
Expand Down
20 changes: 20 additions & 0 deletions test/message/source_map_disabled_by_api.js
@@ -0,0 +1,20 @@
// Flags: --enable-source-maps

'use strict';
require('../common');

process.setSourceMapsEnabled(false);

try {
require('../fixtures/source-map/enclosing-call-site-min.js');
} catch (e) {
console.log(e);
}

delete require.cache[require
.resolve('../fixtures/source-map/enclosing-call-site-min.js')];

// Re-enable.
process.setSourceMapsEnabled(true);

require('../fixtures/source-map/enclosing-call-site-min.js');
26 changes: 26 additions & 0 deletions test/message/source_map_disabled_by_api.out
@@ -0,0 +1,26 @@
Error: an error!
at functionD (*enclosing-call-site-min.js:1:156)
at functionC (*enclosing-call-site-min.js:1:97)
at functionB (*enclosing-call-site-min.js:1:60)
at functionA (*enclosing-call-site-min.js:1:26)
at Object.<anonymous> (*enclosing-call-site-min.js:1:199)
at Module._compile (node:internal/modules/cjs/loader:*)
at Object.Module._extensions..js (node:internal/modules/cjs/loader:*)
at Module.load (node:internal/modules/cjs/loader:*)
at Function.Module._load (node:internal/modules/cjs/loader:*)
at Module.require (node:internal/modules/cjs/loader:*)
*enclosing-call-site.js:16
throw new Error('an error!')
^

Error: an error!
at functionD (*enclosing-call-site.js:16:17)
at functionC (*enclosing-call-site.js:10:3)
at functionB (*enclosing-call-site.js:6:3)
at functionA (*enclosing-call-site.js:2:3)
at Object.<anonymous> (*enclosing-call-site.js:24:3)
at Module._compile (node:internal/modules/cjs/loader:*)
at Object.Module._extensions..js (node:internal/modules/cjs/loader:*)
at Module.load (node:internal/modules/cjs/loader:*)
at Function.Module._load (node:internal/modules/cjs/loader:*)
at Module.require (node:internal/modules/cjs/loader:*)
17 changes: 17 additions & 0 deletions test/message/source_map_enabled_by_api.js
@@ -0,0 +1,17 @@
'use strict';
require('../common');

process.setSourceMapsEnabled(true);

try {
require('../fixtures/source-map/enclosing-call-site-min.js');
} catch (e) {
console.log(e);
}

delete require.cache[require
.resolve('../fixtures/source-map/enclosing-call-site-min.js')];

process.setSourceMapsEnabled(false);

require('../fixtures/source-map/enclosing-call-site-min.js');
30 changes: 30 additions & 0 deletions test/message/source_map_enabled_by_api.out
@@ -0,0 +1,30 @@
*enclosing-call-site.js:16
throw new Error('an error!')
^

Error: an error!
at functionD (*enclosing-call-site.js:16:17)
at functionC (*enclosing-call-site.js:10:3)
at functionB (*enclosing-call-site.js:6:3)
at functionA (*enclosing-call-site.js:2:3)
at Object.<anonymous> (*enclosing-call-site.js:24:3)
at Module._compile (node:internal/modules/cjs/loader:*)
at Object.Module._extensions..js (node:internal/modules/cjs/loader:*)
at Module.load (node:internal/modules/cjs/loader:*)
at Function.Module._load (node:internal/modules/cjs/loader:*)
at Module.require (node:internal/modules/cjs/loader:*)
*enclosing-call-site-min.js:1
var functionA=function(){functionB()};function functionB(){functionC()}var functionC=function(){functionD()},functionD=function(){if(0<Math.random())throw Error("an error!");},thrower=functionA;try{functionA()}catch(a){throw a;};
^

Error: an error!
at functionD (*enclosing-call-site-min.js:1:156)
at functionC (*enclosing-call-site-min.js:1:97)
at functionB (*enclosing-call-site-min.js:1:60)
at functionA (*enclosing-call-site-min.js:1:26)
at Object.<anonymous> (*enclosing-call-site-min.js:1:199)
at Module._compile (node:internal/modules/cjs/loader:*)
at Object.Module._extensions..js (node:internal/modules/cjs/loader:*)
at Module.load (node:internal/modules/cjs/loader:*)
at Function.Module._load (node:internal/modules/cjs/loader:*)
at Module.require (node:internal/modules/cjs/loader:*)
16 changes: 16 additions & 0 deletions test/parallel/test-process-setsourcemapsenabled.js
@@ -0,0 +1,16 @@
'use strict';
require('../common');
const assert = require('assert');

const unexpectedValues = [
undefined,
null,
1,
{},
() => {},
];
for (const it of unexpectedValues) {
assert.throws(() => {
process.setSourceMapsEnabled(it);
}, /ERR_INVALID_ARG_TYPE/);
}