Skip to content

debugger: fix behavior of plain object exec in debugger repl #57498

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

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion lib/internal/debugger/inspect_repl.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ const { fileURLToPath } = require('internal/url');

const { customInspectSymbol, SideEffectFreeRegExpPrototypeSymbolReplace } = require('internal/util');
const { inspect: utilInspect } = require('internal/util/inspect');
const { isObjectLiteral } = require('internal/repl/utils');
const debuglog = require('internal/util/debuglog').debuglog('inspect');

const SHORTCUTS = {
Expand Down Expand Up @@ -573,8 +574,15 @@ function createRepl(inspector) {
if (input === '\n') return lastCommand;
// Add parentheses: exec process.title => exec("process.title");
const match = RegExpPrototypeExec(/^\s*(?:exec|p)\s+([^\n]*)/, input);
input = match ? match[1] : input;

if (isObjectLiteral(input)) {
// Add parentheses to make sure `input` is parsed as an expression
input = `(${StringPrototypeTrim(input)})\n`;
}

if (match) {
lastCommand = `exec(${JSONStringify(match[1])})`;
lastCommand = `exec(${JSONStringify(input)})`;
} else {
lastCommand = input;
}
Expand Down
17 changes: 17 additions & 0 deletions lib/internal/repl/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -739,11 +739,28 @@ function setupReverseSearch(repl) {
return { reverseSearch };
}

const startsWithBraceRegExp = /^\s*{/;
const endsWithSemicolonRegExp = /;\s*$/;

/**
* Checks if some provided code represents an object literal.
* This is helpful to prevent confusing repl code evaluations where
* strings such as `{ a : 1 }` would get interpreted as block statements
* rather than object literals.
* @param {string} code the code to check
* @returns {boolean} true if the code represents an object literal, false otherwise
*/
function isObjectLiteral(code) {
return RegExpPrototypeExec(startsWithBraceRegExp, code) !== null &&
RegExpPrototypeExec(endsWithSemicolonRegExp, code) === null;
}

module.exports = {
REPL_MODE_SLOPPY: Symbol('repl-sloppy'),
REPL_MODE_STRICT,
isRecoverableError,
kStandaloneREPL: Symbol('kStandaloneREPL'),
setupPreview,
setupReverseSearch,
isObjectLiteral,
};
10 changes: 3 additions & 7 deletions lib/repl.js
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ const {
kStandaloneREPL,
setupPreview,
setupReverseSearch,
isObjectLiteral,
} = require('internal/repl/utils');
const {
constants: {
Expand Down Expand Up @@ -436,13 +437,8 @@ function REPLServer(prompt,
let awaitPromise = false;
const input = code;

// It's confusing for `{ a : 1 }` to be interpreted as a block
// statement rather than an object literal. So, we first try
// to wrap it in parentheses, so that it will be interpreted as
// an expression. Note that if the above condition changes,
// lib/internal/repl/utils.js needs to be changed to match.
if (RegExpPrototypeExec(/^\s*{/, code) !== null &&
RegExpPrototypeExec(/;\s*$/, code) === null) {
if (isObjectLiteral(code)) {
// Add parentheses to make sure `code` is parsed as an expression
code = `(${StringPrototypeTrim(code)})\n`;
wrappedCmd = true;
}
Expand Down
5 changes: 5 additions & 0 deletions test/parallel/test-debugger-exec.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ async function waitInitialBreak() {
/\[ 'undefined', 'function' \]/,
'non-paused exec can see global but not module-scope values'
);

// Ref: https://github.com/nodejs/node/issues/46808
await cli.waitForPrompt();
await cli.command('exec { a: 1 }');
assert.match(cli.output, /\{ a: 1 \}/);
} finally {
await cli.quit();
}
Expand Down
Loading