diff --git a/doc/api/errors.md b/doc/api/errors.md index a6aaa57d924f26..96c002e7099e4b 100644 --- a/doc/api/errors.md +++ b/doc/api/errors.md @@ -2021,13 +2021,6 @@ An invalid `options.protocol` was passed to `http.request()`. Both `breakEvalOnSigint` and `eval` options were set in the [`REPL`][] config, which is not supported. - - -### `ERR_INVALID_REPL_INPUT` - -The input may not be used in the [`REPL`][]. The conditions under which this -error is used are described in the [`REPL`][] documentation. - ### `ERR_INVALID_RETURN_PROPERTY` diff --git a/doc/api/repl.md b/doc/api/repl.md index 8d00cdeed3916a..6b8693e74bfaa7 100644 --- a/doc/api/repl.md +++ b/doc/api/repl.md @@ -151,34 +151,18 @@ global or scoped variable, the input `fs` will be evaluated on-demand as -The REPL uses the [`domain`][] module to catch all uncaught exceptions for that -REPL session. - -This use of the [`domain`][] module in the REPL has these side effects: - -* Uncaught exceptions only emit the [`'uncaughtException'`][] event in the - standalone REPL. Adding a listener for this event in a REPL within - another Node.js program results in [`ERR_INVALID_REPL_INPUT`][]. - - ```js - const r = repl.start(); - - r.write('process.on("uncaughtException", () => console.log("Foobar"));\n'); - // Output stream includes: - // TypeError [ERR_INVALID_REPL_INPUT]: Listeners for `uncaughtException` - // cannot be used in the REPL - - r.close(); - ``` - -* Trying to use [`process.setUncaughtExceptionCaptureCallback()`][] throws - an [`ERR_DOMAIN_CANNOT_SET_UNCAUGHT_EXCEPTION_CAPTURE`][] error. +The REPL uses the [`process.setUncaughtExceptionCaptureCallback()`][] to catch all uncaught exceptions for that +REPL session. For more information on potential side effects, refer to it's documentation. #### Assignment of the `_` (underscore) variable @@ -768,12 +752,8 @@ avoiding open network interfaces. [TTY keybindings]: readline.md#tty-keybindings [ZSH]: https://en.wikipedia.org/wiki/Z_shell -[`'uncaughtException'`]: process.md#event-uncaughtexception [`--no-experimental-repl-await`]: cli.md#--no-experimental-repl-await -[`ERR_DOMAIN_CANNOT_SET_UNCAUGHT_EXCEPTION_CAPTURE`]: errors.md#err_domain_cannot_set_uncaught_exception_capture -[`ERR_INVALID_REPL_INPUT`]: errors.md#err_invalid_repl_input [`curl(1)`]: https://curl.haxx.se/docs/manpage.html -[`domain`]: domain.md [`process.setUncaughtExceptionCaptureCallback()`]: process.md#processsetuncaughtexceptioncapturecallbackfn [`readline.InterfaceCompleter`]: readline.md#use-of-the-completer-function [`repl.ReplServer`]: #class-replserver diff --git a/lib/internal/errors.js b/lib/internal/errors.js index c7cb7715dbcaef..ecf5d748ab81ee 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -1502,7 +1502,6 @@ E('ERR_INVALID_PROTOCOL', TypeError); E('ERR_INVALID_REPL_EVAL_CONFIG', 'Cannot specify both "breakEvalOnSigint" and "eval" for REPL', TypeError); -E('ERR_INVALID_REPL_INPUT', '%s', TypeError); E('ERR_INVALID_RETURN_PROPERTY', (input, name, prop, value) => { return `Expected a valid ${input} to be returned for the "${prop}" from the` + ` "${name}" hook but got ${determineSpecificType(value)}.`; diff --git a/lib/repl.js b/lib/repl.js index 005da4ee915625..8d39bda9f2462f 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -60,6 +60,7 @@ const { ArrayPrototypeUnshift, Boolean, Error: MainContextError, + FunctionPrototypeApply, FunctionPrototypeBind, JSONStringify, MathMaxApply, @@ -78,7 +79,6 @@ const { RegExpPrototypeExec, SafePromiseRace, SafeSet, - SafeWeakSet, StringPrototypeCharAt, StringPrototypeCodePointAt, StringPrototypeEndsWith, @@ -138,7 +138,6 @@ ArrayPrototypeForEach( BuiltinModule.getSchemeOnlyModuleNames(), (lib) => ArrayPrototypePush(nodeSchemeBuiltinLibs, `node:${lib}`), ); -const domain = require('domain'); let debug = require('internal/util/debuglog').debuglog('repl', (fn) => { debug = fn; }); @@ -147,7 +146,6 @@ const { codes: { ERR_CANNOT_WATCH_SIGINT, ERR_INVALID_REPL_EVAL_CONFIG, - ERR_INVALID_REPL_INPUT, ERR_MISSING_ARGS, ERR_SCRIPT_EXECUTION_INTERRUPTED, }, @@ -205,14 +203,11 @@ const globalBuiltins = new SafeSet(vm.runInNewContext('Object.getOwnPropertyNames(globalThis)')); const parentModule = module; -const domainSet = new SafeWeakSet(); const kBufferedCommandSymbol = Symbol('bufferedCommand'); const kContextId = Symbol('contextId'); const kLoadingSymbol = Symbol('loading'); -let addedNewListener = false; - try { // Hack for require.resolve("./relative") to work properly. module.filename = path.resolve('repl'); @@ -347,7 +342,6 @@ function REPLServer(prompt, this.allowBlockingCompletions = !!options.allowBlockingCompletions; this.useColors = !!options.useColors; - this._domain = options.domain || domain.create(); this.useGlobal = !!useGlobal; this.ignoreUndefined = !!ignoreUndefined; this.replMode = replMode || module.exports.REPL_MODE_SLOPPY; @@ -370,27 +364,10 @@ function REPLServer(prompt, // It is possible to introspect the running REPL accessing this variable // from inside the REPL. This is useful for anyone working on the REPL. module.exports.repl = this; - } else if (!addedNewListener) { - // Add this listener only once and use a WeakSet that contains the REPLs - // domains. Otherwise we'd have to add a single listener to each REPL - // instance and that could trigger the `MaxListenersExceededWarning`. - process.prependListener('newListener', (event, listener) => { - if (event === 'uncaughtException' && - process.domain && - listener.name !== 'domainUncaughtExceptionClear' && - domainSet.has(process.domain)) { - // Throw an error so that the event will not be added and the current - // domain takes over. That way the user is notified about the error - // and the current code evaluation is stopped, just as any other code - // that contains an error. - throw new ERR_INVALID_REPL_INPUT( - 'Listeners for `uncaughtException` cannot be used in the REPL'); - } - }); - addedNewListener = true; } - domainSet.add(this._domain); + process.setUncaughtExceptionCaptureCallback(null); + process.setUncaughtExceptionCaptureCallback(handleEvaluationError); const savedRegExMatches = ['', '', '', '', '', '', '', '', '', '']; const sep = '\u0000\u0000\u0000'; @@ -613,13 +590,8 @@ function REPLServer(prompt, } } catch (e) { err = e; - - if (process.domain) { - debug('not recoverable, send to domain'); - process.domain.emit('error', err); - process.domain.exit(); - return; - } + handleEvaluationError(e); + return; } if (awaitPromise && !err) { @@ -645,10 +617,8 @@ function REPLServer(prompt, const result = (await promise)?.value; finishExecution(null, result); } catch (err) { - if (err && process.domain) { - debug('not recoverable, send to domain'); - process.domain.emit('error', err); - process.domain.exit(); + if (err) { + handleEvaluationError(err); return; } finishExecution(err); @@ -666,10 +636,16 @@ function REPLServer(prompt, } } - self.eval = self._domain.bind(eval_); + self.eval = function(...args) { + try { + FunctionPrototypeApply(eval_, this, args); + } catch (e) { + handleEvaluationError(e); + } + }; - self._domain.on('error', function debugDomainError(e) { - debug('domain error'); + function handleEvaluationError(e) { + debug('eval error'); let errStack = ''; if (typeof e === 'object' && e !== null) { @@ -697,11 +673,6 @@ function REPLServer(prompt, }); decorateErrorStack(e); - if (e.domainThrown) { - delete e.domain; - delete e.domainThrown; - } - if (isError(e)) { if (e.stack) { if (e.name === 'SyntaxError') { @@ -779,7 +750,7 @@ function REPLServer(prompt, self.lines.level = []; self.displayPrompt(); } - }); + }; self.clearBufferedCommand(); @@ -952,7 +923,7 @@ function REPLServer(prompt, self.displayPrompt(); return; } - self._domain.emit('error', e.err || e); + handleEvaluationError(e.err || e); } // Clear buffer if no SyntaxErrors @@ -972,8 +943,7 @@ function REPLServer(prompt, self.output.write(self.writer(ret) + '\n'); } - // Display prompt again (unless we already did by emitting the 'error' - // event on the domain instance). + // Display prompt again if (!e) { self.displayPrompt(); } @@ -1083,15 +1053,17 @@ REPLServer.prototype.clearBufferedCommand = function clearBufferedCommand() { REPLServer.prototype.close = function close() { if (this.terminal && this._flushing && !this._closingOnFlush) { this._closingOnFlush = true; - this.once('flushHistory', () => - ReflectApply(Interface.prototype.close, this, []), - ); + this.once('flushHistory', () => { + process.setUncaughtExceptionCaptureCallback(null); + ReflectApply(Interface.prototype.close, this, []); + }); return; } - process.nextTick(() => - ReflectApply(Interface.prototype.close, this, []), - ); + process.nextTick(() => { + process.setUncaughtExceptionCaptureCallback(null); + ReflectApply(Interface.prototype.close, this, []); + }); }; REPLServer.prototype.createContext = function() { diff --git a/test/fixtures/repl-tab-completion-nested-repls.js b/test/fixtures/repl-tab-completion-nested-repls.js index 1d2b154f2b3341..61577320c31d95 100644 --- a/test/fixtures/repl-tab-completion-nested-repls.js +++ b/test/fixtures/repl-tab-completion-nested-repls.js @@ -29,11 +29,11 @@ ArrayStream.prototype.write = noop; const repl = require('repl'); const putIn = new ArrayStream(); -const testMe = repl.start('', putIn); -// Some errors are passed to the domain, but do not callback. -testMe._domain.on('error', function(err) { - throw err; +const testMe = repl.start({ + prompt: '', + input: putIn, + output: process.stdout, }); // Nesting of structures causes REPL to use a nested REPL for completion. diff --git a/test/parallel/test-repl-domain.js b/test/parallel/test-repl-domain.js deleted file mode 100644 index 462677d114c13b..00000000000000 --- a/test/parallel/test-repl-domain.js +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright Joyent, Inc. and other Node contributors. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to permit -// persons to whom the Software is furnished to do so, subject to the -// following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -// USE OR OTHER DEALINGS IN THE SOFTWARE. - -'use strict'; -require('../common'); -const ArrayStream = require('../common/arraystream'); - -const repl = require('repl'); - -const putIn = new ArrayStream(); -repl.start('', putIn); - -putIn.write = function(data) { - // Don't use assert for this because the domain might catch it, and - // give a false negative. Don't throw, just print and exit. - if (data === 'OK\n') { - console.log('ok'); - } else { - console.error(data); - process.exit(1); - } -}; - -putIn.run([ - 'require("domain").create().on("error", function() { console.log("OK") })' + - '.run(function() { throw new Error("threw") })', -]); diff --git a/test/parallel/test-repl-save-load.js b/test/parallel/test-repl-save-load.js index bb5130d1d71bbf..aa44039e0d0183 100644 --- a/test/parallel/test-repl-save-load.js +++ b/test/parallel/test-repl-save-load.js @@ -34,13 +34,6 @@ const works = [['inner.one'], 'inner.o']; const putIn = new ArrayStream(); const testMe = repl.start('', putIn); -// Some errors might be passed to the domain. -testMe._domain.on('error', function(reason) { - const err = new Error('Test failed'); - err.reason = reason; - throw err; -}); - const testFile = [ 'let inner = (function() {', ' return {one:1};', diff --git a/test/parallel/test-repl-tab-complete-import.js b/test/parallel/test-repl-tab-complete-import.js index e328d95db5986c..e16f921b9b9552 100644 --- a/test/parallel/test-repl-tab-complete-import.js +++ b/test/parallel/test-repl-tab-complete-import.js @@ -25,9 +25,6 @@ const testMe = repl.start({ allowBlockingCompletions: true }); -// Some errors are passed to the domain, but do not callback -testMe._domain.on('error', assert.ifError); - // Tab complete provides built in libs for import() testMe.complete('import(\'', common.mustCall((error, data) => { assert.strictEqual(error, null); diff --git a/test/parallel/test-repl-tab-complete-nested-repls.js b/test/parallel/test-repl-tab-complete-nested-repls.js index 3cac02f20562bc..3d090d327f4bf2 100644 --- a/test/parallel/test-repl-tab-complete-nested-repls.js +++ b/test/parallel/test-repl-tab-complete-nested-repls.js @@ -18,6 +18,4 @@ const result = spawnSync(process.execPath, [testFile]); // The spawned process will fail. In Node.js 10.11.0, it will fail silently. The // test here is to make sure that the error information bubbles up to the // calling process. -assert.ok(result.status, 'testFile swallowed its error'); -const err = result.stderr.toString(); -assert.ok(err.includes('fhqwhgads'), err); +assert.ok(result.stdout.toString().includes('fhqwhgads')); diff --git a/test/parallel/test-repl-tab-complete.js b/test/parallel/test-repl-tab-complete.js index 57278f52ccf2c6..0d5d35b3f3f04d 100644 --- a/test/parallel/test-repl-tab-complete.js +++ b/test/parallel/test-repl-tab-complete.js @@ -60,9 +60,6 @@ const testMe = repl.start({ allowBlockingCompletions: true }); -// Some errors are passed to the domain, but do not callback -testMe._domain.on('error', assert.ifError); - // Tab Complete will not break in an object literal putIn.run([ 'var inner = {', diff --git a/test/parallel/test-repl-tab.js b/test/parallel/test-repl-tab.js index f64a00d8bca99e..ac13a71969526f 100644 --- a/test/parallel/test-repl-tab.js +++ b/test/parallel/test-repl-tab.js @@ -1,5 +1,5 @@ 'use strict'; -const common = require('../common'); +require('../common'); const assert = require('assert'); const repl = require('repl'); const zlib = require('zlib'); @@ -11,8 +11,6 @@ const testMe = repl.start('', putIn, function(cmd, context, filename, callback(null, cmd); }); -testMe._domain.on('error', common.mustNotCall()); - testMe.complete('', function(err, results) { assert.strictEqual(err, null); }); diff --git a/test/parallel/test-repl-top-level-await.js b/test/parallel/test-repl-top-level-await.js index c8bc26fad62e5c..ab2d4153de8ba3 100644 --- a/test/parallel/test-repl-top-level-await.js +++ b/test/parallel/test-repl-top-level-await.js @@ -174,7 +174,7 @@ async function ordinaryTests() { 'undefined', ]], // Regression test for https://github.com/nodejs/node/issues/43777. - ['await Promise.resolve(123), Promise.resolve(456)', 'Promise {', { line: 0 }], + ['await Promise.resolve(123), Promise.resolve(456)', 'Promise { 456 }', { line: 0 }], ['await Promise.resolve(123), await Promise.resolve(456)', '456'], ['await (Promise.resolve(123), Promise.resolve(456))', '456'], ]; diff --git a/test/parallel/test-repl-uncaught-exception-async.js b/test/parallel/test-repl-uncaught-exception-async.js deleted file mode 100644 index 366a4e6f2968af..00000000000000 --- a/test/parallel/test-repl-uncaught-exception-async.js +++ /dev/null @@ -1,44 +0,0 @@ -'use strict'; - -// This verifies that adding an `uncaughtException` listener in an REPL instance -// does not suppress errors in the whole application. Adding such listener -// should throw. - -require('../common'); -const ArrayStream = require('../common/arraystream'); -const repl = require('repl'); -const assert = require('assert'); - -let accum = ''; - -const output = new ArrayStream(); -output.write = (data) => accum += data.replace('\r', ''); - -const r = repl.start({ - prompt: '', - input: new ArrayStream(), - output, - terminal: false, - useColors: false, - global: false -}); - -r.write( - 'process.nextTick(() => {\n' + - ' process.on("uncaughtException", () => console.log("Foo"));\n' + - ' throw new TypeError("foobar");\n' + - '});\n' -); -r.write( - 'setTimeout(() => {\n' + - ' throw new RangeError("abc");\n' + - '}, 1);console.log()\n' -); -r.close(); - -setTimeout(() => { - const len = process.listenerCount('uncaughtException'); - process.removeAllListeners('uncaughtException'); - assert.strictEqual(len, 0); - assert.match(accum, /ERR_INVALID_REPL_INPUT.*(?!Type)RangeError: abc/s); -}, 2); diff --git a/test/parallel/test-repl-uncaught-exception.js b/test/parallel/test-repl-uncaught-exception.js index d3dbe0acb0106d..93c0f4f4a4d05f 100644 --- a/test/parallel/test-repl-uncaught-exception.js +++ b/test/parallel/test-repl-uncaught-exception.js @@ -47,24 +47,10 @@ const tests = [ command: 'throw { foo: "test" }', expected: "Uncaught { foo: \x1B[32m'test'\x1B[39m }\n" }, - { - command: 'process.on("uncaughtException", () => console.log("Foobar"));\n', - expected: /^Uncaught:\nTypeError \[ERR_INVALID_REPL_INPUT]: Listeners for `/ - }, { command: 'x;\n', expected: 'Uncaught ReferenceError: x is not defined\n' }, - { - command: 'process.on("uncaughtException", () => console.log("Foobar"));' + - 'console.log("Baz");\n', - expected: /^Uncaught:\nTypeError \[ERR_INVALID_REPL_INPUT]: Listeners for `/ - }, - { - command: 'console.log("Baz");' + - 'process.on("uncaughtException", () => console.log("Foobar"));\n', - expected: /^Baz\nUncaught:\nTypeError \[ERR_INVALID_REPL_INPUT]:.*uncaughtException/ - }, ]; process.on('exit', () => {