diff --git a/lib/console.js b/lib/console.js index b77832b9876735..d70a6b30b7298c 100644 --- a/lib/console.js +++ b/lib/console.js @@ -21,15 +21,16 @@ 'use strict'; -const { ERR_CONSOLE_WRITABLE_STREAM } = require('internal/errors').codes; +const { + isStackOverflowError, + codes: { ERR_CONSOLE_WRITABLE_STREAM }, +} = require('internal/errors'); const util = require('util'); const kCounts = Symbol('counts'); // Track amount of indentation required via `console.group()`. const kGroupIndent = Symbol('groupIndent'); -let MAX_STACK_MESSAGE; - function Console(stdout, stderr, ignoreErrors = true) { if (!(this instanceof Console)) { return new Console(stdout, stderr, ignoreErrors); @@ -113,17 +114,9 @@ function write(ignoreErrors, stream, string, errorhandler, groupIndent) { stream.write(string, errorhandler); } catch (e) { - if (MAX_STACK_MESSAGE === undefined) { - try { - // eslint-disable-next-line no-unused-vars - function a() { a(); } - } catch (err) { - MAX_STACK_MESSAGE = err.message; - } - } // console is a debugging utility, so it swallowing errors is not desirable // even in edge cases such as low stack space. - if (e.message === MAX_STACK_MESSAGE && e.name === 'RangeError') + if (isStackOverflowError(e)) throw e; // Sorry, there's no proper way to pass along the error here. } finally { diff --git a/lib/internal/errors.js b/lib/internal/errors.js index a8c4bfb3c33b9e..16b4cd2416f37f 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -569,11 +569,33 @@ function dnsException(err, syscall, hostname) { return ex; } +let MAX_STACK_MESSAGE; +/** + * Returns true if `err` is a `RangeError` with an engine-specific message. + * "Maximum call stack size exceeded" in V8. + * + * @param {Error} err + * @returns {boolean} + */ +function isStackOverflowError(err) { + if (MAX_STACK_MESSAGE === undefined) { + try { + function overflowStack() { overflowStack(); } + overflowStack(); + } catch (err) { + MAX_STACK_MESSAGE = err.message; + } + } + + return err.name === 'RangeError' && err.message === MAX_STACK_MESSAGE; +} + module.exports = exports = { dnsException, errnoException, exceptionWithHostPort, uvException, + isStackOverflowError, message, AssertionError, SystemError, diff --git a/test/parallel/test-console-no-swallow-stack-overflow.js b/test/parallel/test-console-no-swallow-stack-overflow.js new file mode 100644 index 00000000000000..f36ba4857e363b --- /dev/null +++ b/test/parallel/test-console-no-swallow-stack-overflow.js @@ -0,0 +1,18 @@ +'use strict'; +const common = require('../common'); +const { Console } = require('console'); +const { Writable } = require('stream'); + +for (const method of ['dir', 'log', 'warn']) { + common.expectsError(() => { + const out = new Writable({ + write: common.mustCall(function write(...args) { + // Exceeds call stack. + return write(...args); + }), + }); + const c = new Console(out, out, true); + + c[method]('Hello, world!'); + }, { name: 'RangeError' }); +}