From 059a67413f66841e042322e17a802243854b0959 Mon Sep 17 00:00:00 2001 From: Michael Bianco Date: Sun, 11 Jun 2023 06:42:02 -0600 Subject: [PATCH 1/2] feat: allow repl pause on exception state to be set from env Right now, it's not possible to configure the debugger to pause on uncaught exceptions without manually configuring the pause state on each run. This change would allow the pause state to be configured via a shell env variable to allow for faster CLI-based node debugging. --- lib/internal/debugger/inspect_repl.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/internal/debugger/inspect_repl.js b/lib/internal/debugger/inspect_repl.js index b4f454152dc438..d37bebfbbda1b9 100644 --- a/lib/internal/debugger/inspect_repl.js +++ b/lib/internal/debugger/inspect_repl.js @@ -370,7 +370,7 @@ function createRepl(inspector) { const watchedExpressions = []; const knownBreakpoints = []; let heapSnapshotPromise = null; - let pauseOnExceptionState = 'none'; + let pauseOnExceptionState = process.env.NODE_INSPECT_PAUSE_ON_EXCEPTION_STATE || 'none'; let lastCommand; // Things we need to reset when the app restarts From 836df085e4c2741fbd7ff1872cdae527e7b7bf7c Mon Sep 17 00:00:00 2001 From: Michael Bianco Date: Fri, 28 Jul 2023 16:56:11 -0600 Subject: [PATCH 2/2] feat: node inspect argv parser, allow exception state to be set from command line --- lib/internal/debugger/inspect.js | 49 +------- lib/internal/debugger/inspect_repl.js | 6 +- lib/internal/debugger/util/argument_parser.js | 111 ++++++++++++++++++ 3 files changed, 117 insertions(+), 49 deletions(-) create mode 100644 lib/internal/debugger/util/argument_parser.js diff --git a/lib/internal/debugger/inspect.js b/lib/internal/debugger/inspect.js index 24e5c0bd401f01..d219570c4c9b03 100644 --- a/lib/internal/debugger/inspect.js +++ b/lib/internal/debugger/inspect.js @@ -32,10 +32,11 @@ const { AbortController, } = require('internal/abort_controller'); -const { 0: InspectClient, 1: createRepl } = +const { 0: InspectClient, 1: createRepl, 2: parseArguments } = [ require('internal/debugger/inspect_client'), require('internal/debugger/inspect_repl'), + require('internal/debugger/util/argument_parser') ]; const debuglog = util.debuglog('inspect'); @@ -286,50 +287,6 @@ class NodeInspector { } } -function parseArgv(args) { - const target = ArrayPrototypeShift(args); - let host = '127.0.0.1'; - let port = 9229; - let isRemote = false; - let script = target; - let scriptArgs = args; - - const hostMatch = RegExpPrototypeExec(/^([^:]+):(\d+)$/, target); - const portMatch = RegExpPrototypeExec(/^--port=(\d+)$/, target); - - if (hostMatch) { - // Connecting to remote debugger - host = hostMatch[1]; - port = Number(hostMatch[2]); - isRemote = true; - script = null; - } else if (portMatch) { - // Start on custom port - port = Number(portMatch[1]); - script = args[0]; - scriptArgs = ArrayPrototypeSlice(args, 1); - } else if (args.length === 1 && RegExpPrototypeExec(/^\d+$/, args[0]) !== null && - target === '-p') { - // Start debugger against a given pid - const pid = Number(args[0]); - try { - process._debugProcess(pid); - } catch (e) { - if (e.code === 'ESRCH') { - process.stderr.write(`Target process: ${pid} doesn't exist.\n`); - process.exit(kGenericUserError); - } - throw e; - } - script = null; - isRemote = true; - } - - return { - host, port, isRemote, script, scriptArgs, - }; -} - function startInspect(argv = ArrayPrototypeSlice(process.argv, 2), stdin = process.stdin, stdout = process.stdout) { @@ -343,7 +300,7 @@ function startInspect(argv = ArrayPrototypeSlice(process.argv, 2), process.exit(kInvalidCommandLineArgument); } - const options = parseArgv(argv); + const options = parseArguments(argv); const inspector = new NodeInspector(options, stdin, stdout); stdin.resume(); diff --git a/lib/internal/debugger/inspect_repl.js b/lib/internal/debugger/inspect_repl.js index d37bebfbbda1b9..4b2519337e6bc9 100644 --- a/lib/internal/debugger/inspect_repl.js +++ b/lib/internal/debugger/inspect_repl.js @@ -361,7 +361,7 @@ function aliasProperties(target, mapping) { } function createRepl(inspector) { - const { Debugger, HeapProfiler, Profiler, Runtime } = inspector; + const { Debugger, HeapProfiler, Profiler, Runtime, options: commandLineOptions } = inspector; let repl; @@ -370,7 +370,7 @@ function createRepl(inspector) { const watchedExpressions = []; const knownBreakpoints = []; let heapSnapshotPromise = null; - let pauseOnExceptionState = process.env.NODE_INSPECT_PAUSE_ON_EXCEPTION_STATE || 'none'; + let pauseOnExceptionState = commandLineOptions.pauseOnExceptionState || 'none'; let lastCommand; // Things we need to reset when the app restarts @@ -883,7 +883,7 @@ function createRepl(inspector) { } Debugger.on('paused', ({ callFrames, reason /* , hitBreakpoints */ }) => { - if (process.env.NODE_INSPECT_RESUME_ON_START === '1' && + if ((process.env.NODE_INSPECT_RESUME_ON_START === '1' || commandLineOptions.inspectResumeOnStart === true) && reason === 'Break on start') { debuglog('Paused on start, but NODE_INSPECT_RESUME_ON_START' + ' environment variable is set to 1, resuming'); diff --git a/lib/internal/debugger/util/argument_parser.js b/lib/internal/debugger/util/argument_parser.js new file mode 100644 index 00000000000000..0512d0e42cfbc3 --- /dev/null +++ b/lib/internal/debugger/util/argument_parser.js @@ -0,0 +1,111 @@ +const { + ArrayPrototypeShift, + ArrayPrototypeSplice, + ArrayPrototypeIncludes, + StringPrototypeSplit, + RegExpPrototypeExec, +} = primordials; + +function parseBoolean(value) { + return value === 'true' || value === '1' || value === 'yes'; +} + +function validatePauseOnExceptionState(value) { + const validStates = ['uncaught', 'none', 'all']; + if (!ArrayPrototypeIncludes(validStates, value)) { + throw new Error(`Invalid state passed for pauseOnExceptionState: ${value}. Must be one of 'uncaught', 'none', or 'all'.`); + } + return value; +} + +function parseArguments(argv) { + const legacyArguments = processLegacyArgs(argv) + + let options = { + pauseOnExceptionState: undefined, + inspectResumeOnStart: undefined + } + + // `NODE_INSPECT_OPTIONS` is parsed first and can be overwritten by command line arguments + + if (process.env.NODE_INSPECT_OPTIONS) { + const envOptions = StringPrototypeSplit(process.env.NODE_INSPECT_OPTIONS, ' '); + for (let i = 0; i < envOptions.length; i++) { + switch (envOptions[i]) { + case '--pause-on-exception-state': + options.pauseOnExceptionState = validatePauseOnExceptionState(envOptions[++i]); + break; + case '--inspect-resume-on-start': + options.inspectResumeOnStart = parseBoolean(envOptions[++i]); + break; + } + } + } + + for (let i = 0; i < argv.length;) { + switch (argv[i]) { + case '--pause-on-exception-state': + options.pauseOnExceptionState = validatePauseOnExceptionState(argv[i+1]); + ArrayPrototypeSplice(argv, i, 2); + break; + case '--inspect-resume-on-start': + options.inspectResumeOnStart = parseBoolean(argv[i+1]); + ArrayPrototypeSplice(argv, i, 2); + break; + default: + i++; + break; + } + } + + return {...options, ...legacyArguments}; +} + +// the legacy `node inspect` options assumed the first argument was the target +// to avoid breaking existing scripts, we maintain this behavior + +function processLegacyArgs(args) { + const target = ArrayPrototypeShift(args); + let host = '127.0.0.1'; + let port = 9229; + let isRemote = false; + let script = target; + let scriptArgs = args; + + const hostMatch = RegExpPrototypeExec(/^([^:]+):(\d+)$/, target); + const portMatch = RegExpPrototypeExec(/^--port=(\d+)$/, target); + + if (hostMatch) { + // Connecting to remote debugger + host = hostMatch[1]; + port = Number(hostMatch[2]); + isRemote = true; + script = null; + } else if (portMatch) { + // Start on custom port + port = Number(portMatch[1]); + script = args[0]; + scriptArgs = ArrayPrototypeSlice(args, 1); + } else if (args.length === 1 && RegExpPrototypeExec(/^\d+$/, args[0]) !== null && + target === '-p') { + // Start debugger against a given pid + const pid = Number(args[0]); + try { + process._debugProcess(pid); + } catch (e) { + if (e.code === 'ESRCH') { + process.stderr.write(`Target process: ${pid} doesn't exist.\n`); + process.exit(kGenericUserError); + } + throw e; + } + script = null; + isRemote = true; + } + + return { + host, port, isRemote, script, scriptArgs, + }; +} + +module.exports = parseArguments;