Skip to content
This repository has been archived by the owner on Apr 22, 2023. It is now read-only.

Commit

Permalink
domains: port fix abort on uncaught to v0.12
Browse files Browse the repository at this point in the history
Port fbff705 and caeb677 from v0.10 to v0.12, original commit messages:

  fbff705
  Add v8::Isolate::SetAbortOnUncaughtException() so the user can be
  notified when an uncaught exception has bubbled.

  caeb677
  Do not abort the process if an error is thrown from within a domain,
  an error handler is setup for the domain and
  --abort-on-uncaught-exception was passed on the command line.

  However, if an error is thrown from within the top-level domain's
  error handler and --abort-on-uncaught-exception was passed on the
  command line, make the process abort.

Fixes: #8877
  • Loading branch information
whitlockjc committed Aug 10, 2015
1 parent 162d0db commit 5d695f9
Show file tree
Hide file tree
Showing 9 changed files with 440 additions and 41 deletions.
11 changes: 11 additions & 0 deletions deps/v8/include/v8.h
Original file line number Diff line number Diff line change
Expand Up @@ -4186,6 +4186,17 @@ class V8_EXPORT Isolate {
*/
static Isolate* GetCurrent();

/**
* Custom callback used by embedders to help V8 determine if it should abort
* when it throws and no internal handler can catch the exception.
* If FLAG_abort_on_uncaught_exception is true, then V8 will abort if either:
* - no custom callback is set.
* - the custom callback set returns true.
* Otherwise it won't abort.
*/
typedef bool (*abort_on_uncaught_exception_t)(Isolate*);
void SetAbortOnUncaughtException(abort_on_uncaught_exception_t callback);

/**
* Methods below this point require holding a lock (using Locker) in
* a multi-threaded environment.
Expand Down
7 changes: 7 additions & 0 deletions deps/v8/src/api.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6556,6 +6556,13 @@ void Isolate::Enter() {
}


void Isolate::SetAbortOnUncaughtException(
abort_on_uncaught_exception_t callback) {
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(this);
isolate->SetAbortOnUncaughtException(callback);
}


void Isolate::Exit() {
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(this);
isolate->Exit();
Expand Down
42 changes: 30 additions & 12 deletions deps/v8/src/isolate.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1090,19 +1090,30 @@ void Isolate::DoThrow(Object* exception, MessageLocation* location) {
thread_local_top()->pending_message_end_pos_ = location->end_pos();
}

// If the abort-on-uncaught-exception flag is specified, abort on any
// exception not caught by JavaScript, even when an external handler is
// present. This flag is intended for use by JavaScript developers, so
// print a user-friendly stack trace (not an internal one).
// If the abort-on-uncaught-exception flag is specified, and if the
// exception is not caught by JavaScript (even when an external handler is
// present).
if (fatal_exception_depth == 0 &&
FLAG_abort_on_uncaught_exception &&
(FLAG_abort_on_uncaught_exception) &&
(report_exception || can_be_caught_externally)) {
fatal_exception_depth++;
PrintF(stderr,
"%s\n\nFROM\n",
MessageHandler::GetLocalizedMessage(this, message_obj).get());
PrintCurrentStackTrace(stderr);
base::OS::Abort();
// If the embedder didn't specify a custom uncaught exception callback,
// or if the custom callback determined that V8 should abort, then
// abort
bool should_abort = !abort_on_uncaught_exception_callback_ ||
abort_on_uncaught_exception_callback_(
reinterpret_cast<v8::Isolate*>(this)
);

if (should_abort) {
fatal_exception_depth++;
// This flag is intended for use by JavaScript developers, so
// print a user-friendly stack trace (not an internal one).
PrintF(stderr,
"%s\n\nFROM\n",
MessageHandler::GetLocalizedMessage(this, message_obj).get());
PrintCurrentStackTrace(stderr);
base::OS::Abort();
}
}
} else if (location != NULL && !location->script().is_null()) {
// We are bootstrapping and caught an error where the location is set
Expand Down Expand Up @@ -1299,6 +1310,12 @@ void Isolate::SetCaptureStackTraceForUncaughtExceptions(
}


void Isolate::SetAbortOnUncaughtException(
v8::Isolate::abort_on_uncaught_exception_t callback) {
abort_on_uncaught_exception_callback_ = callback;
}


Handle<Context> Isolate::native_context() {
return handle(context()->native_context());
}
Expand Down Expand Up @@ -1474,7 +1491,8 @@ Isolate::Isolate()
num_sweeper_threads_(0),
stress_deopt_count_(0),
next_optimization_id_(0),
use_counter_callback_(NULL) {
use_counter_callback_(NULL),
abort_on_uncaught_exception_callback_(NULL) {
id_ = base::NoBarrier_AtomicIncrement(&isolate_counter_, 1);
TRACE_ISOLATE(constructor);

Expand Down
5 changes: 5 additions & 0 deletions deps/v8/src/isolate.h
Original file line number Diff line number Diff line change
Expand Up @@ -707,6 +707,9 @@ class Isolate {
int frame_limit,
StackTrace::StackTraceOptions options);

typedef bool (*abort_on_uncaught_exception_t)(v8::Isolate*);
void SetAbortOnUncaughtException(abort_on_uncaught_exception_t callback);

void PrintCurrentStackTrace(FILE* out);
void PrintStack(StringStream* accumulator);
void PrintStack(FILE* out);
Expand Down Expand Up @@ -1331,6 +1334,8 @@ class Isolate {

v8::Isolate::UseCounterCallback use_counter_callback_;

abort_on_uncaught_exception_t abort_on_uncaught_exception_callback_;

friend class ExecutionAccess;
friend class HandleScopeImplementer;
friend class IsolateInitializer;
Expand Down
88 changes: 59 additions & 29 deletions lib/domain.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,20 @@ Domain.prototype._disposed = undefined;
// Called by process._fatalException in case an error was thrown.
Domain.prototype._errorHandler = function errorHandler(er) {
var caught = false;
var self = this;

function emitError() {
var handled = self.emit('error', er);

// Exit all domains on the stack. Uncaught exceptions end the
// current tick and no domains should be left on the stack
// between ticks.
stack.length = 0;
exports.active = process.domain = null;

return handled;
}

// ignore errors on disposed domains.
//
// XXX This is a bit stupid. We should probably get rid of
Expand All @@ -89,38 +103,54 @@ Domain.prototype._errorHandler = function errorHandler(er) {
er.domain = this;
er.domainThrown = true;
}
// wrap this in a try/catch so we don't get infinite throwing
try {
// One of three things will happen here.
//
// 1. There is a handler, caught = true
// 2. There is no handler, caught = false
// 3. It throws, caught = false
//
// If caught is false after this, then there's no need to exit()
// the domain, because we're going to crash the process anyway.
caught = this.emit('error', er);

// Exit all domains on the stack. Uncaught exceptions end the
// current tick and no domains should be left on the stack
// between ticks.
stack.length = 0;
exports.active = process.domain = null;
} catch (er2) {
// The domain error handler threw! oh no!
// See if another domain can catch THIS error,
// or else crash on the original one.
// If the user already exited it, then don't double-exit.
if (this === exports.active) {
stack.pop();
// The top-level domain-handler is handled separately.
//
// The reason is that if V8 was passed a command line option
// asking it to abort on an uncaught exception (currently
// "--abort-on-uncaught-exception"), we want an uncaught exception
// in the top-level domain error handler to make the
// process abort. Using try/catch here would always make V8 think
// that these exceptions are caught, and thus would prevent it from
// aborting in these cases.
if (stack.length === 1) {
try {
// Set the _emittingTopLevelDomainError so that we know that, even
// if technically the top-level domain is still active, it would
// be ok to abort on an uncaught exception at this point
process._emittingTopLevelDomainError = true;
caught = emitError();
} finally {
process._emittingTopLevelDomainError = false;
}
if (stack.length) {
exports.active = process.domain = stack[stack.length - 1];
caught = process._fatalException(er2);
} else {
caught = false;
} else {
// wrap this in a try/catch so we don't get infinite throwing
try {
// One of three things will happen here.
//
// 1. There is a handler, caught = true
// 2. There is no handler, caught = false
// 3. It throws, caught = false
//
// If caught is false after this, then there's no need to exit()
// the domain, because we're going to crash the process anyway.
caught = emitError();
} catch (er2) {
// The domain error handler threw! oh no!
// See if another domain can catch THIS error,
// or else crash on the original one.
// If the user already exited it, then don't double-exit.
if (this === exports.active) {
stack.pop();
}
if (stack.length) {
exports.active = process.domain = stack[stack.length - 1];
caught = process._fatalException(er2);
} else {
caught = false;
}
return caught;
}
return caught;
}
return caught;
};
Expand Down
1 change: 1 addition & 0 deletions src/env.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ namespace node {
V(dev_string, "dev") \
V(disposed_string, "_disposed") \
V(domain_string, "domain") \
V(emitting_top_level_domain_error_string, "_emittingTopLevelDomainError") \
V(exchange_string, "exchange") \
V(idle_string, "idle") \
V(irq_string, "irq") \
Expand Down
32 changes: 32 additions & 0 deletions src/node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -909,6 +909,33 @@ Local<Value> WinapiErrnoException(Isolate* isolate,
#endif


static bool IsDomainActive(const Environment* env) {
if (!env->using_domains()) {
return false;
}

Local<Array> domain_array = env->domain_array().As<Array>();
uint32_t domainsArrayLength = domain_array->Length();
if (domainsArrayLength == 0)
return false;

Local<Value> domain_v = domain_array->Get(0);
return !domain_v->IsNull();
}


bool ShouldAbortOnUncaughtException(v8::Isolate* isolate) {
Environment* env = Environment::GetCurrent(isolate);
Local<Object> process_object = env->process_object();
Local<String> emitting_top_level_domain_error_key =
env->emitting_top_level_domain_error_string();
bool isEmittingTopLevelDomainError =
process_object->Get(emitting_top_level_domain_error_key)->BooleanValue();

return !IsDomainActive(env) || isEmittingTopLevelDomainError;
}


void SetupDomainUse(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args.GetIsolate());

Expand Down Expand Up @@ -2772,6 +2799,9 @@ void SetupProcessObject(Environment* env,

// pre-set _events object for faster emit checks
process->Set(env->events_string(), Object::New(env->isolate()));

process->Set(env->emitting_top_level_domain_error_string(),
False(env->isolate()));
}


Expand Down Expand Up @@ -3453,6 +3483,8 @@ void Init(int* argc,
node_isolate = Isolate::New();
Isolate::Scope isolate_scope(node_isolate);

node_isolate->SetAbortOnUncaughtException(ShouldAbortOnUncaughtException);

#ifdef __POSIX__
// Raise the open file descriptor limit.
{ // NOLINT (whitespace/braces)
Expand Down
74 changes: 74 additions & 0 deletions test/simple/test-domain-top-level-error-handler-throw.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// 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.

/*
* The goal of this test is to make sure that when a top-level error
* handler throws an error following the handling of a previous error,
* the process reports the error message from the error thrown in the
* top-level error handler, not the one from the previous error.
*/

var domainErrHandlerExMessage = 'exception from domain error handler';
var internalExMessage = 'You should NOT see me';

if (process.argv[2] === 'child') {
var domain = require('domain');
var d = domain.create();

d.on('error', function() {
throw new Error(domainErrHandlerExMessage);
});

d.run(function doStuff() {
process.nextTick(function () {
throw new Error(internalExMessage);
});
});
} else {
var fork = require('child_process').fork;
var assert = require('assert');

function test() {
var child = fork(process.argv[1], ['child'], {silent:true});
var gotDataFromStderr = false;
var stderrOutput = '';
if (child) {
child.stderr.on('data', function onStderrData(data) {
gotDataFromStderr = true;
stderrOutput += data.toString();
});

child.on('exit', function onChildExited(exitCode, signal) {
assert(gotDataFromStderr);
assert(stderrOutput.indexOf(domainErrHandlerExMessage) !== -1);
assert(stderrOutput.indexOf(internalExMessage) === -1);

var expectedExitCode = 7;
var expectedSignal = null;

assert.equal(exitCode, expectedExitCode);
assert.equal(signal, expectedSignal);
});
}
}

test();
}
Loading

0 comments on commit 5d695f9

Please sign in to comment.