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

Commit 302433a

Browse files
committed
domains: port fix abort on uncaught to v0.12
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
1 parent 162d0db commit 302433a

9 files changed

+443
-41
lines changed

deps/v8/include/v8.h

+11
Original file line numberDiff line numberDiff line change
@@ -4186,6 +4186,17 @@ class V8_EXPORT Isolate {
41864186
*/
41874187
static Isolate* GetCurrent();
41884188

4189+
/**
4190+
* Custom callback used by embedders to help V8 determine if it should abort
4191+
* when it throws and no internal handler can catch the exception.
4192+
* If FLAG_abort_on_uncaught_exception is true, then V8 will abort if either:
4193+
* - no custom callback is set.
4194+
* - the custom callback set returns true.
4195+
* Otherwise it won't abort.
4196+
*/
4197+
typedef bool (*abort_on_uncaught_exception_t)(Isolate*);
4198+
void SetAbortOnUncaughtException(abort_on_uncaught_exception_t callback);
4199+
41894200
/**
41904201
* Methods below this point require holding a lock (using Locker) in
41914202
* a multi-threaded environment.

deps/v8/src/api.cc

+7
Original file line numberDiff line numberDiff line change
@@ -6556,6 +6556,13 @@ void Isolate::Enter() {
65566556
}
65576557

65586558

6559+
void Isolate::SetAbortOnUncaughtException(
6560+
abort_on_uncaught_exception_t callback) {
6561+
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(this);
6562+
isolate->SetAbortOnUncaughtException(callback);
6563+
}
6564+
6565+
65596566
void Isolate::Exit() {
65606567
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(this);
65616568
isolate->Exit();

deps/v8/src/isolate.cc

+30-12
Original file line numberDiff line numberDiff line change
@@ -1090,19 +1090,30 @@ void Isolate::DoThrow(Object* exception, MessageLocation* location) {
10901090
thread_local_top()->pending_message_end_pos_ = location->end_pos();
10911091
}
10921092

1093-
// If the abort-on-uncaught-exception flag is specified, abort on any
1094-
// exception not caught by JavaScript, even when an external handler is
1095-
// present. This flag is intended for use by JavaScript developers, so
1096-
// print a user-friendly stack trace (not an internal one).
1093+
// If the abort-on-uncaught-exception flag is specified, and if the
1094+
// exception is not caught by JavaScript (even when an external handler is
1095+
// present).
10971096
if (fatal_exception_depth == 0 &&
1098-
FLAG_abort_on_uncaught_exception &&
1097+
(FLAG_abort_on_uncaught_exception) &&
10991098
(report_exception || can_be_caught_externally)) {
1100-
fatal_exception_depth++;
1101-
PrintF(stderr,
1102-
"%s\n\nFROM\n",
1103-
MessageHandler::GetLocalizedMessage(this, message_obj).get());
1104-
PrintCurrentStackTrace(stderr);
1105-
base::OS::Abort();
1099+
// If the embedder didn't specify a custom uncaught exception callback,
1100+
// or if the custom callback determined that V8 should abort, then
1101+
// abort
1102+
bool should_abort = !abort_on_uncaught_exception_callback_ ||
1103+
abort_on_uncaught_exception_callback_(
1104+
reinterpret_cast<v8::Isolate*>(this)
1105+
);
1106+
1107+
if (should_abort) {
1108+
fatal_exception_depth++;
1109+
// This flag is intended for use by JavaScript developers, so
1110+
// print a user-friendly stack trace (not an internal one).
1111+
PrintF(stderr,
1112+
"%s\n\nFROM\n",
1113+
MessageHandler::GetLocalizedMessage(this, message_obj).get());
1114+
PrintCurrentStackTrace(stderr);
1115+
base::OS::Abort();
1116+
}
11061117
}
11071118
} else if (location != NULL && !location->script().is_null()) {
11081119
// We are bootstrapping and caught an error where the location is set
@@ -1299,6 +1310,12 @@ void Isolate::SetCaptureStackTraceForUncaughtExceptions(
12991310
}
13001311

13011312

1313+
void Isolate::SetAbortOnUncaughtException(
1314+
v8::Isolate::abort_on_uncaught_exception_t callback) {
1315+
abort_on_uncaught_exception_callback_ = callback;
1316+
}
1317+
1318+
13021319
Handle<Context> Isolate::native_context() {
13031320
return handle(context()->native_context());
13041321
}
@@ -1474,7 +1491,8 @@ Isolate::Isolate()
14741491
num_sweeper_threads_(0),
14751492
stress_deopt_count_(0),
14761493
next_optimization_id_(0),
1477-
use_counter_callback_(NULL) {
1494+
use_counter_callback_(NULL),
1495+
abort_on_uncaught_exception_callback_(NULL) {
14781496
id_ = base::NoBarrier_AtomicIncrement(&isolate_counter_, 1);
14791497
TRACE_ISOLATE(constructor);
14801498

deps/v8/src/isolate.h

+5
Original file line numberDiff line numberDiff line change
@@ -707,6 +707,9 @@ class Isolate {
707707
int frame_limit,
708708
StackTrace::StackTraceOptions options);
709709

710+
typedef bool (*abort_on_uncaught_exception_t)(v8::Isolate*);
711+
void SetAbortOnUncaughtException(abort_on_uncaught_exception_t callback);
712+
710713
void PrintCurrentStackTrace(FILE* out);
711714
void PrintStack(StringStream* accumulator);
712715
void PrintStack(FILE* out);
@@ -1331,6 +1334,8 @@ class Isolate {
13311334

13321335
v8::Isolate::UseCounterCallback use_counter_callback_;
13331336

1337+
abort_on_uncaught_exception_t abort_on_uncaught_exception_callback_;
1338+
13341339
friend class ExecutionAccess;
13351340
friend class HandleScopeImplementer;
13361341
friend class IsolateInitializer;

lib/domain.js

+59-29
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,20 @@ Domain.prototype._disposed = undefined;
7777
// Called by process._fatalException in case an error was thrown.
7878
Domain.prototype._errorHandler = function errorHandler(er) {
7979
var caught = false;
80+
var self = this;
81+
82+
function emitError() {
83+
var handled = self.emit('error', er);
84+
85+
// Exit all domains on the stack. Uncaught exceptions end the
86+
// current tick and no domains should be left on the stack
87+
// between ticks.
88+
stack.length = 0;
89+
exports.active = process.domain = null;
90+
91+
return handled;
92+
}
93+
8094
// ignore errors on disposed domains.
8195
//
8296
// XXX This is a bit stupid. We should probably get rid of
@@ -89,38 +103,54 @@ Domain.prototype._errorHandler = function errorHandler(er) {
89103
er.domain = this;
90104
er.domainThrown = true;
91105
}
92-
// wrap this in a try/catch so we don't get infinite throwing
93-
try {
94-
// One of three things will happen here.
95-
//
96-
// 1. There is a handler, caught = true
97-
// 2. There is no handler, caught = false
98-
// 3. It throws, caught = false
99-
//
100-
// If caught is false after this, then there's no need to exit()
101-
// the domain, because we're going to crash the process anyway.
102-
caught = this.emit('error', er);
103106

104-
// Exit all domains on the stack. Uncaught exceptions end the
105-
// current tick and no domains should be left on the stack
106-
// between ticks.
107-
stack.length = 0;
108-
exports.active = process.domain = null;
109-
} catch (er2) {
110-
// The domain error handler threw! oh no!
111-
// See if another domain can catch THIS error,
112-
// or else crash on the original one.
113-
// If the user already exited it, then don't double-exit.
114-
if (this === exports.active) {
115-
stack.pop();
107+
// The top-level domain-handler is handled separately.
108+
//
109+
// The reason is that if V8 was passed a command line option
110+
// asking it to abort on an uncaught exception (currently
111+
// "--abort-on-uncaught-exception"), we want an uncaught exception
112+
// in the top-level domain error handler to make the
113+
// process abort. Using try/catch here would always make V8 think
114+
// that these exceptions are caught, and thus would prevent it from
115+
// aborting in these cases.
116+
if (stack.length === 1) {
117+
try {
118+
// Set the _emittingTopLevelDomainError so that we know that, even
119+
// if technically the top-level domain is still active, it would
120+
// be ok to abort on an uncaught exception at this point
121+
process._emittingTopLevelDomainError = true;
122+
caught = emitError();
123+
} finally {
124+
process._emittingTopLevelDomainError = false;
116125
}
117-
if (stack.length) {
118-
exports.active = process.domain = stack[stack.length - 1];
119-
caught = process._fatalException(er2);
120-
} else {
121-
caught = false;
126+
} else {
127+
// wrap this in a try/catch so we don't get infinite throwing
128+
try {
129+
// One of three things will happen here.
130+
//
131+
// 1. There is a handler, caught = true
132+
// 2. There is no handler, caught = false
133+
// 3. It throws, caught = false
134+
//
135+
// If caught is false after this, then there's no need to exit()
136+
// the domain, because we're going to crash the process anyway.
137+
caught = emitError();
138+
} catch (er2) {
139+
// The domain error handler threw! oh no!
140+
// See if another domain can catch THIS error,
141+
// or else crash on the original one.
142+
// If the user already exited it, then don't double-exit.
143+
if (this === exports.active) {
144+
stack.pop();
145+
}
146+
if (stack.length) {
147+
exports.active = process.domain = stack[stack.length - 1];
148+
caught = process._fatalException(er2);
149+
} else {
150+
caught = false;
151+
}
152+
return caught;
122153
}
123-
return caught;
124154
}
125155
return caught;
126156
};

src/env.h

+1
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ namespace node {
8686
V(dev_string, "dev") \
8787
V(disposed_string, "_disposed") \
8888
V(domain_string, "domain") \
89+
V(emitting_top_level_domain_error_string, "_emittingTopLevelDomainError") \
8990
V(exchange_string, "exchange") \
9091
V(idle_string, "idle") \
9192
V(irq_string, "irq") \

src/node.cc

+32
Original file line numberDiff line numberDiff line change
@@ -909,6 +909,33 @@ Local<Value> WinapiErrnoException(Isolate* isolate,
909909
#endif
910910

911911

912+
static bool IsDomainActive(const Environment* env) {
913+
if (!env->using_domains()) {
914+
return false;
915+
}
916+
917+
Local<Array> domain_array = env->domain_array().As<Array>();
918+
uint32_t domainsArrayLength = domain_array->Length();
919+
if (domainsArrayLength == 0)
920+
return false;
921+
922+
Local<Value> domain_v = domain_array->Get(0);
923+
return !domain_v->IsNull();
924+
}
925+
926+
927+
bool ShouldAbortOnUncaughtException(v8::Isolate* isolate) {
928+
Environment* env = Environment::GetCurrent(isolate);
929+
Local<Object> process_object = env->process_object();
930+
Local<String> emitting_top_level_domain_error_key =
931+
env->emitting_top_level_domain_error_string();
932+
bool isEmittingTopLevelDomainError =
933+
process_object->Get(emitting_top_level_domain_error_key)->BooleanValue();
934+
935+
return !IsDomainActive(env) || isEmittingTopLevelDomainError;
936+
}
937+
938+
912939
void SetupDomainUse(const FunctionCallbackInfo<Value>& args) {
913940
Environment* env = Environment::GetCurrent(args.GetIsolate());
914941

@@ -2772,6 +2799,9 @@ void SetupProcessObject(Environment* env,
27722799

27732800
// pre-set _events object for faster emit checks
27742801
process->Set(env->events_string(), Object::New(env->isolate()));
2802+
2803+
process->Set(env->emitting_top_level_domain_error_string(),
2804+
False(env->isolate()));
27752805
}
27762806

27772807

@@ -3453,6 +3483,8 @@ void Init(int* argc,
34533483
node_isolate = Isolate::New();
34543484
Isolate::Scope isolate_scope(node_isolate);
34553485

3486+
node_isolate->SetAbortOnUncaughtException(ShouldAbortOnUncaughtException);
3487+
34563488
#ifdef __POSIX__
34573489
// Raise the open file descriptor limit.
34583490
{ // NOLINT (whitespace/braces)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Copyright Joyent, Inc. and other Node contributors.
2+
//
3+
// Permission is hereby granted, free of charge, to any person obtaining a
4+
// copy of this software and associated documentation files (the
5+
// "Software"), to deal in the Software without restriction, including
6+
// without limitation the rights to use, copy, modify, merge, publish,
7+
// distribute, sublicense, and/or sell copies of the Software, and to permit
8+
// persons to whom the Software is furnished to do so, subject to the
9+
// following conditions:
10+
//
11+
// The above copyright notice and this permission notice shall be included
12+
// in all copies or substantial portions of the Software.
13+
//
14+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15+
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16+
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
17+
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18+
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19+
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
20+
// USE OR OTHER DEALINGS IN THE SOFTWARE.
21+
22+
/*
23+
* The goal of this test is to make sure that when a top-level error
24+
* handler throws an error following the handling of a previous error,
25+
* the process reports the error message from the error thrown in the
26+
* top-level error handler, not the one from the previous error.
27+
*/
28+
29+
var domainErrHandlerExMessage = 'exception from domain error handler';
30+
var internalExMessage = 'You should NOT see me';
31+
32+
if (process.argv[2] === 'child') {
33+
var domain = require('domain');
34+
var d = domain.create();
35+
36+
d.on('error', function() {
37+
throw new Error(domainErrHandlerExMessage);
38+
});
39+
40+
d.run(function doStuff() {
41+
process.nextTick(function () {
42+
throw new Error(internalExMessage);
43+
});
44+
});
45+
} else {
46+
var fork = require('child_process').fork;
47+
var assert = require('assert');
48+
49+
function test() {
50+
var child = fork(process.argv[1], ['child'], {silent:true});
51+
var gotDataFromStderr = false;
52+
var stderrOutput = '';
53+
if (child) {
54+
child.stderr.on('data', function onStderrData(data) {
55+
gotDataFromStderr = true;
56+
stderrOutput += data.toString();
57+
});
58+
59+
child.on('exit', function onChildExited(exitCode, signal) {
60+
assert(gotDataFromStderr);
61+
assert(stderrOutput.indexOf(domainErrHandlerExMessage) !== -1);
62+
assert(stderrOutput.indexOf(internalExMessage) === -1);
63+
64+
var expectedExitCode = 7;
65+
var expectedSignal = null;
66+
67+
assert.equal(exitCode, expectedExitCode);
68+
assert.equal(signal, expectedSignal);
69+
});
70+
}
71+
}
72+
73+
test();
74+
}

0 commit comments

Comments
 (0)