Skip to content

Commit

Permalink
vm: never abort on caught syntax error
Browse files Browse the repository at this point in the history
Keep track of C++ `TryCatch` state to avoid aborting when
an exception is thrown inside one, and re-throw in JS
to make sure the exception is being picked up a second time by
a second uncaught exception handler, if necessary.

Add a bit of a hack to `AppendExceptionLine` to avoid overriding
the line responsible for re-throwing the exception.

PR-URL: #17394
Fixes: #13258
Reviewed-By: James M Snell <jasnell@gmail.com>
  • Loading branch information
addaleax authored and MylesBorins committed Dec 21, 2017
1 parent df6acf9 commit e276711
Show file tree
Hide file tree
Showing 14 changed files with 97 additions and 20 deletions.
15 changes: 14 additions & 1 deletion lib/vm.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
'use strict';

const {
ContextifyScript: Script,
ContextifyScript,
kParsingContext,

makeContext,
Expand All @@ -40,6 +40,19 @@ const {
// - isContext(sandbox)
// From this we build the entire documented API.

class Script extends ContextifyScript {
constructor(code, options) {
// Calling `ReThrow()` on a native TryCatch does not generate a new
// abort-on-uncaught-exception check. A dummy try/catch in JS land
// protects against that.
try {
super(code, options);
} catch (e) {
throw e; /* node-do-not-add-exception-line */
}
}
}

const realRunInThisContext = Script.prototype.runInThisContext;
const realRunInContext = Script.prototype.runInContext;

Expand Down
21 changes: 21 additions & 0 deletions src/env-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,27 @@ Environment::should_abort_on_uncaught_toggle() {
return should_abort_on_uncaught_toggle_;
}

Environment::ShouldNotAbortOnUncaughtScope::ShouldNotAbortOnUncaughtScope(
Environment* env)
: env_(env) {
env_->should_not_abort_scope_counter_++;
}

Environment::ShouldNotAbortOnUncaughtScope::~ShouldNotAbortOnUncaughtScope() {
Close();
}

void Environment::ShouldNotAbortOnUncaughtScope::Close() {
if (env_ != nullptr) {
env_->should_not_abort_scope_counter_--;
env_ = nullptr;
}
}

bool Environment::inside_should_not_abort_on_uncaught_scope() const {
return should_not_abort_scope_counter_ > 0;
}

inline std::vector<double>* Environment::destroy_async_id_list() {
return &destroy_async_id_list_;
}
Expand Down
14 changes: 14 additions & 0 deletions src/env.h
Original file line number Diff line number Diff line change
Expand Up @@ -681,6 +681,18 @@ class Environment {
// This needs to be available for the JS-land setImmediate().
void ActivateImmediateCheck();

class ShouldNotAbortOnUncaughtScope {
public:
explicit inline ShouldNotAbortOnUncaughtScope(Environment* env);
inline void Close();
inline ~ShouldNotAbortOnUncaughtScope();

private:
Environment* env_;
};

inline bool inside_should_not_abort_on_uncaught_scope() const;

private:
inline void ThrowError(v8::Local<v8::Value> (*fun)(v8::Local<v8::String>),
const char* errmsg);
Expand All @@ -706,6 +718,8 @@ class Environment {
AliasedBuffer<uint32_t, v8::Uint32Array> scheduled_immediate_count_;
AliasedBuffer<uint32_t, v8::Uint32Array> should_abort_on_uncaught_toggle_;

int should_not_abort_scope_counter_ = 0;

performance::performance_state* performance_state_ = nullptr;
std::map<std::string, uint64_t> performance_marks_;

Expand Down
5 changes: 4 additions & 1 deletion src/node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1110,7 +1110,8 @@ namespace {
bool ShouldAbortOnUncaughtException(Isolate* isolate) {
HandleScope scope(isolate);
Environment* env = Environment::GetCurrent(isolate);
return env->should_abort_on_uncaught_toggle()[0];
return env->should_abort_on_uncaught_toggle()[0] &&
!env->inside_should_not_abort_on_uncaught_scope();
}


Expand Down Expand Up @@ -1639,6 +1640,8 @@ void AppendExceptionLine(Environment* env,
// Print line of source code.
node::Utf8Value sourceline(env->isolate(), message->GetSourceLine());
const char* sourceline_string = *sourceline;
if (strstr(sourceline_string, "node-do-not-add-exception-line") != nullptr)
return;

// Because of how node modules work, all scripts are wrapped with a
// "function (module, exports, __filename, ...) {"
Expand Down
3 changes: 3 additions & 0 deletions src/node_contextify.cc
Original file line number Diff line number Diff line change
Expand Up @@ -654,6 +654,7 @@ class ContextifyScript : public BaseObject {
new ContextifyScript(env, args.This());

TryCatch try_catch(env->isolate());
Environment::ShouldNotAbortOnUncaughtScope no_abort_scope(env);
Local<String> code =
args[0]->ToString(env->context()).FromMaybe(Local<String>());

Expand All @@ -666,6 +667,7 @@ class ContextifyScript : public BaseObject {
Maybe<bool> maybe_produce_cached_data = GetProduceCachedData(env, options);
MaybeLocal<Context> maybe_context = GetContext(env, options);
if (try_catch.HasCaught()) {
no_abort_scope.Close();
try_catch.ReThrow();
return;
}
Expand Down Expand Up @@ -702,6 +704,7 @@ class ContextifyScript : public BaseObject {

if (v8_script.IsEmpty()) {
DecorateErrorStack(env, try_catch);
no_abort_scope.Close();
try_catch.ReThrow();
return;
}
Expand Down
15 changes: 11 additions & 4 deletions test/abort/test-abort-uncaught-exception.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,24 @@
const common = require('../common');
const assert = require('assert');
const spawn = require('child_process').spawn;
const vm = require('vm');
const node = process.execPath;

if (process.argv[2] === 'child') {
throw new Error('child error');
} else if (process.argv[2] === 'vm') {
// Refs: https://github.com/nodejs/node/issues/13258
// This *should* still crash.
new vm.Script('[', {});
} else {
run('', null);
run('--abort-on-uncaught-exception', ['SIGABRT', 'SIGTRAP', 'SIGILL']);
run('', 'child', null);
run('--abort-on-uncaught-exception', 'child',
['SIGABRT', 'SIGTRAP', 'SIGILL']);
run('--abort-on-uncaught-exception', 'vm', ['SIGABRT', 'SIGTRAP', 'SIGILL']);
}

function run(flags, signals) {
const args = [__filename, 'child'];
function run(flags, argv2, signals) {
const args = [__filename, argv2];
if (flags)
args.unshift(flags);

Expand Down
7 changes: 4 additions & 3 deletions test/message/eval_messages.out
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
with(this){__filename}
^^^^
SyntaxError: Strict mode code may not include a with statement
at new Script (vm.js:*:*)
at createScript (vm.js:*:*)
at Object.runInThisContext (vm.js:*:*)
at Object.<anonymous> ([eval]-wrapper:*:*)
Expand All @@ -18,7 +19,7 @@ throw new Error("hello")

Error: hello
at [eval]:1:7
at ContextifyScript.Script.runInThisContext (vm.js:*:*)
at Script.runInThisContext (vm.js:*:*)
at Object.runInThisContext (vm.js:*:*)
at Object.<anonymous> ([eval]-wrapper:*:*)
at Module._compile (module.js:*:*)
Expand All @@ -32,7 +33,7 @@ throw new Error("hello")

Error: hello
at [eval]:1:7
at ContextifyScript.Script.runInThisContext (vm.js:*:*)
at Script.runInThisContext (vm.js:*:*)
at Object.runInThisContext (vm.js:*:*)
at Object.<anonymous> ([eval]-wrapper:*:*)
at Module._compile (module.js:*:*)
Expand All @@ -46,7 +47,7 @@ var x = 100; y = x;

ReferenceError: y is not defined
at [eval]:1:16
at ContextifyScript.Script.runInThisContext (vm.js:*:*)
at Script.runInThisContext (vm.js:*:*)
at Object.runInThisContext (vm.js:*:*)
at Object.<anonymous> ([eval]-wrapper:*:*)
at Module._compile (module.js:*:*)
Expand Down
7 changes: 4 additions & 3 deletions test/message/stdin_messages.out
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
with(this){__filename}
^^^^
SyntaxError: Strict mode code may not include a with statement
at new Script (vm.js:*)
at createScript (vm.js:*)
at Object.runInThisContext (vm.js:*)
at Object.<anonymous> ([stdin]-wrapper:*:*)
Expand All @@ -20,7 +21,7 @@ throw new Error("hello")

Error: hello
at [stdin]:1:7
at ContextifyScript.Script.runInThisContext (vm.js:*)
at Script.runInThisContext (vm.js:*)
at Object.runInThisContext (vm.js:*)
at Object.<anonymous> ([stdin]-wrapper:*:*)
at Module._compile (module.js:*:*)
Expand All @@ -35,7 +36,7 @@ throw new Error("hello")

Error: hello
at [stdin]:1:*
at ContextifyScript.Script.runInThisContext (vm.js:*)
at Script.runInThisContext (vm.js:*)
at Object.runInThisContext (vm.js:*)
at Object.<anonymous> ([stdin]-wrapper:*:*)
at Module._compile (module.js:*:*)
Expand All @@ -51,7 +52,7 @@ var x = 100; y = x;

ReferenceError: y is not defined
at [stdin]:1:16
at ContextifyScript.Script.runInThisContext (vm.js:*)
at Script.runInThisContext (vm.js:*)
at Object.runInThisContext (vm.js:*)
at Object.<anonymous> ([stdin]-wrapper:*:*)
at Module._compile (module.js:*:*)
Expand Down
4 changes: 2 additions & 2 deletions test/message/undefined_reference_in_new_context.out
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ foo.bar = 5;

ReferenceError: foo is not defined
at evalmachine.<anonymous>:1:1
at ContextifyScript.Script.runInContext (vm.js:*)
at ContextifyScript.Script.runInNewContext (vm.js:*)
at Script.runInContext (vm.js:*)
at Script.runInNewContext (vm.js:*)
at Object.runInNewContext (vm.js:*)
at Object.<anonymous> (*test*message*undefined_reference_in_new_context.js:*)
at Module._compile (module.js:*)
Expand Down
4 changes: 2 additions & 2 deletions test/message/vm_display_runtime_error.out
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ throw new Error("boo!")

Error: boo!
at test.vm:1:7
at ContextifyScript.Script.runInThisContext (vm.js:*)
at Script.runInThisContext (vm.js:*)
at Object.runInThisContext (vm.js:*)
at Object.<anonymous> (*test*message*vm_display_runtime_error.js:*)
at Module._compile (module.js:*)
Expand All @@ -20,7 +20,7 @@ throw new Error("spooky!")

Error: spooky!
at test.vm:1:7
at ContextifyScript.Script.runInThisContext (vm.js:*)
at Script.runInThisContext (vm.js:*)
at Object.runInThisContext (vm.js:*)
at Object.<anonymous> (*test*message*vm_display_runtime_error.js:*)
at Module._compile (module.js:*)
Expand Down
4 changes: 2 additions & 2 deletions test/message/vm_display_syntax_error.out
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ foo.vm:1
var 4;
^
SyntaxError: Unexpected number
at new Script (vm.js:*)
at createScript (vm.js:*)
at Object.runInThisContext (vm.js:*)
at Object.<anonymous> (*test*message*vm_display_syntax_error.js:*)
Expand All @@ -12,11 +13,11 @@ SyntaxError: Unexpected number
at tryModuleLoad (module.js:*:*)
at Function.Module._load (module.js:*)
at Function.Module.runMain (module.js:*)
at startup (bootstrap_node.js:*:*)
test.vm:1
var 5;
^
SyntaxError: Unexpected number
at new Script (vm.js:*)
at createScript (vm.js:*)
at Object.runInThisContext (vm.js:*)
at Object.<anonymous> (*test*message*vm_display_syntax_error.js:*)
Expand All @@ -26,4 +27,3 @@ SyntaxError: Unexpected number
at tryModuleLoad (module.js:*:*)
at Function.Module._load (module.js:*)
at Function.Module.runMain (module.js:*)
at startup (bootstrap_node.js:*:*)
2 changes: 1 addition & 1 deletion test/message/vm_dont_display_runtime_error.out
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ throw new Error("boo!")

Error: boo!
at test.vm:1:7
at ContextifyScript.Script.runInThisContext (vm.js:*)
at Script.runInThisContext (vm.js:*)
at Object.runInThisContext (vm.js:*)
at Object.<anonymous> (*test*message*vm_dont_display_runtime_error.js:*)
at Module._compile (module.js:*)
Expand Down
2 changes: 1 addition & 1 deletion test/message/vm_dont_display_syntax_error.out
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ var 5;
^

SyntaxError: Unexpected number
at new Script (vm.js:*)
at createScript (vm.js:*)
at Object.runInThisContext (vm.js:*)
at Object.<anonymous> (*test*message*vm_dont_display_syntax_error.js:*)
Expand All @@ -14,4 +15,3 @@ SyntaxError: Unexpected number
at tryModuleLoad (module.js:*:*)
at Function.Module._load (module.js:*)
at Function.Module.runMain (module.js:*)
at startup (bootstrap_node.js:*:*)
14 changes: 14 additions & 0 deletions test/parallel/test-vm-parse-abort-on-uncaught-exception.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Flags: --abort-on-uncaught-exception
'use strict';
require('../common');
const vm = require('vm');

// Regression test for https://github.com/nodejs/node/issues/13258

try {
new vm.Script({ toString() { throw new Error('foo'); } }, {});
} catch (err) {}

try {
new vm.Script('[', {});
} catch (err) {}

0 comments on commit e276711

Please sign in to comment.