-
Notifications
You must be signed in to change notification settings - Fork 29.1k
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
repl: fix freeze if util.inspect throws & clean up error handling a bit #1968
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -157,12 +157,6 @@ function REPLServer(prompt, | |
} | ||
} catch (e) { | ||
err = e; | ||
if (err && process.domain) { | ||
debug('not recoverable, send to domain'); | ||
process.domain.emit('error', err); | ||
process.domain.exit(); | ||
return; | ||
} | ||
} | ||
} | ||
|
||
|
@@ -314,40 +308,42 @@ function REPLServer(prompt, | |
debug('finish', e, ret); | ||
self.memory(cmd); | ||
|
||
if (e && !self.bufferedCommand && cmd.trim().match(/^npm /)) { | ||
self.outputStream.write('npm should be run outside of the ' + | ||
'node repl, in your normal shell.\n' + | ||
'(Press Control-D to exit.)\n'); | ||
self.bufferedCommand = ''; | ||
self.displayPrompt(); | ||
return; | ||
} | ||
|
||
// If error was SyntaxError and not JSON.parse error | ||
if (e) { | ||
if (e instanceof Recoverable) { | ||
if (!self.bufferedCommand && cmd.trim().match(/^npm /)) { | ||
self.outputStream.write('npm should be run outside of the ' + | ||
'node repl, in your normal shell.\n' + | ||
'(Press Control-D to exit.)\n'); | ||
self.bufferedCommand = ''; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We always shortcircuit if there is an error; group with the rest of the error handling. |
||
self.displayPrompt(); | ||
} else if (e instanceof Recoverable) { | ||
// Start buffering data like that: | ||
// { | ||
// ... x: 1 | ||
// ... } | ||
self.bufferedCommand += cmd + '\n'; | ||
self.displayPrompt(); | ||
return; | ||
} else { | ||
self._domain.emit('error', e); | ||
debug('not recoverable, send to domain'); | ||
process.domain.emit('error', e); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of sometimes delegating to |
||
} | ||
return; | ||
} | ||
|
||
// Clear buffer if no SyntaxErrors | ||
self.bufferedCommand = ''; | ||
|
||
// If we got any output - print it (if no error) | ||
if (!e && (!self.ignoreUndefined || ret !== undefined)) { | ||
// If we got any output - print it | ||
if (!self.ignoreUndefined || ret !== undefined) { | ||
self.context._ = ret; | ||
self.outputStream.write(self.writer(ret) + '\n'); | ||
try { | ||
var s = self.writer(ret); | ||
} catch(e) { | ||
process.domain.emit('error', e); | ||
return; | ||
} | ||
self.outputStream.write(s + '\n'); | ||
} | ||
|
||
// Display prompt again | ||
self.bufferedCommand = ''; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In every other case modifying |
||
self.displayPrompt(); | ||
} | ||
}); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
'use strict'; | ||
var assert = require('assert'); | ||
var common = require('../common'); | ||
var util = require('util'); | ||
var repl = require('repl'); | ||
|
||
// A stream to push an array into a REPL | ||
function ArrayStream() { | ||
this.run = function(data) { | ||
var self = this; | ||
data.forEach(function(line) { | ||
self.emit('data', line + '\n'); | ||
}); | ||
}; | ||
} | ||
util.inherits(ArrayStream, require('stream').Stream); | ||
ArrayStream.prototype.readable = true; | ||
ArrayStream.prototype.writable = true; | ||
ArrayStream.prototype.resume = function() {}; | ||
ArrayStream.prototype.write = function() {}; | ||
|
||
var putIn = new ArrayStream(); | ||
var testMe = repl.start({ | ||
input: putIn, | ||
output: putIn, | ||
terminal: true | ||
}); | ||
|
||
// Regression test: an exception raised during the 'keypress' event in readline | ||
// would corrupt the state of the escape sequence decoder, rendering the REPL | ||
// (and possibly your terminal) completely unusable. | ||
// | ||
// We can trigger this by triggering a throw inside util.inspect, which is used | ||
// to print a string representation of what you evaluate in the REPL. | ||
testMe.context.throwObj = { | ||
inspect: function() { | ||
throw new Error('inspect error'); | ||
} | ||
}; | ||
testMe.context.reached = false; | ||
|
||
// Exceptions for whatever reason do not reach the normal error handler after | ||
// the inspect throw, but will work if run in another tick. | ||
process.nextTick(function() { | ||
assert(testMe.context.reached); | ||
}); | ||
|
||
putIn.run([ | ||
'throwObj', | ||
'reached = true' | ||
]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This logic is moved to
finish
so all of the error handling can be in one place. It doesn't seem safe to do this here anyway since the evaluation function can be overriden. The explicitprocess.domain.exit
is not carried over as 1) I don't understand what use it serves & I don't like to blindly c/p, 2) the other existing domain emit didn't come with an exit and 3) the repl+domain test passes without it. @isaacs you added this 2 years ago, do you remember?