Skip to content

Commit

Permalink
process: add api to enable source-maps programmatically
Browse files Browse the repository at this point in the history
Add `process.setSourceMapsEnabled` to enable
source-maps programmatically.

PR-URL: #39085
Reviewed-By: Ben Coe <bencoe@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
  • Loading branch information
legendecas authored and targos committed Sep 4, 2021
1 parent baaa397 commit 1a6bf1c
Show file tree
Hide file tree
Showing 10 changed files with 169 additions and 15 deletions.
19 changes: 19 additions & 0 deletions doc/api/process.md
Expand Up @@ -2348,6 +2348,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 @@ -2732,6 +2750,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
8 changes: 8 additions & 0 deletions lib/internal/bootstrap/pre_execution.js
Expand Up @@ -61,6 +61,7 @@ function prepareMainThreadExecution(expandArgv1 = false) {
initializeClusterIPC();

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

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 Expand Up @@ -479,6 +486,7 @@ module.exports = {
initializeDeprecations,
initializeESMLoader,
initializeFrozenIntrinsics,
initializeSourceMapsHandlers,
loadPreloadModules,
setupTraceCategoryState,
setupInspectorHooks,
Expand Down
2 changes: 2 additions & 0 deletions lib/internal/main/worker_thread.js
Expand Up @@ -25,6 +25,7 @@ const {
initializeESMLoader,
initializeFrozenIntrinsics,
initializeReport,
initializeSourceMapsHandlers,
loadPreloadModules,
setupTraceCategoryState
} = require('internal/bootstrap/pre_execution');
Expand Down Expand Up @@ -66,6 +67,7 @@ setupInspectorHooks();
setupDebugEnv();

setupWarningHandler();
initializeSourceMapsHandlers();

// Since worker threads cannot switch cwd, we do not need to
// overwrite the process.env.NODE_V8_COVERAGE variable.
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 @@ -231,6 +245,7 @@ function findSourceMap(sourceURL) {
module.exports = {
findSourceMap,
getSourceMapsEnabled,
setSourceMapsEnabled,
maybeCacheSourceMap,
sourceMapCacheToObject,
};
7 changes: 4 additions & 3 deletions src/node_errors.cc
Expand Up @@ -819,9 +819,10 @@ 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>()->Value());
}

static void SetEnhanceStackForFatalException(
Expand Down Expand Up @@ -862,7 +863,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 (internal/modules/cjs/loader.js:*)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:*)
at Module.load (internal/modules/cjs/loader.js:*)
at Function.Module._load (internal/modules/cjs/loader.js:*)
at Module.require (internal/modules/cjs/loader.js:*)
*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 (internal/modules/cjs/loader.js:*)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:*)
at Module.load (internal/modules/cjs/loader.js:*)
at Function.Module._load (internal/modules/cjs/loader.js:*)
at Module.require (internal/modules/cjs/loader.js:*)
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 (internal/modules/cjs/loader.js:*)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:*)
at Module.load (internal/modules/cjs/loader.js:*)
at Function.Module._load (internal/modules/cjs/loader.js:*)
at Module.require (internal/modules/cjs/loader.js:*)
*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 (internal/modules/cjs/loader.js:*)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:*)
at Module.load (internal/modules/cjs/loader.js:*)
at Function.Module._load (internal/modules/cjs/loader.js:*)
at Module.require (internal/modules/cjs/loader.js:*)
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/);
}

0 comments on commit 1a6bf1c

Please sign in to comment.