Skip to content

Commit e276711

Browse files
addaleaxMylesBorins
authored andcommitted
vm: never abort on caught syntax error
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 <[email protected]>
1 parent df6acf9 commit e276711

14 files changed

+97
-20
lines changed

lib/vm.js

+14-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
'use strict';
2323

2424
const {
25-
ContextifyScript: Script,
25+
ContextifyScript,
2626
kParsingContext,
2727

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

43+
class Script extends ContextifyScript {
44+
constructor(code, options) {
45+
// Calling `ReThrow()` on a native TryCatch does not generate a new
46+
// abort-on-uncaught-exception check. A dummy try/catch in JS land
47+
// protects against that.
48+
try {
49+
super(code, options);
50+
} catch (e) {
51+
throw e; /* node-do-not-add-exception-line */
52+
}
53+
}
54+
}
55+
4356
const realRunInThisContext = Script.prototype.runInThisContext;
4457
const realRunInContext = Script.prototype.runInContext;
4558

src/env-inl.h

+21
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,27 @@ Environment::should_abort_on_uncaught_toggle() {
408408
return should_abort_on_uncaught_toggle_;
409409
}
410410

411+
Environment::ShouldNotAbortOnUncaughtScope::ShouldNotAbortOnUncaughtScope(
412+
Environment* env)
413+
: env_(env) {
414+
env_->should_not_abort_scope_counter_++;
415+
}
416+
417+
Environment::ShouldNotAbortOnUncaughtScope::~ShouldNotAbortOnUncaughtScope() {
418+
Close();
419+
}
420+
421+
void Environment::ShouldNotAbortOnUncaughtScope::Close() {
422+
if (env_ != nullptr) {
423+
env_->should_not_abort_scope_counter_--;
424+
env_ = nullptr;
425+
}
426+
}
427+
428+
bool Environment::inside_should_not_abort_on_uncaught_scope() const {
429+
return should_not_abort_scope_counter_ > 0;
430+
}
431+
411432
inline std::vector<double>* Environment::destroy_async_id_list() {
412433
return &destroy_async_id_list_;
413434
}

src/env.h

+14
Original file line numberDiff line numberDiff line change
@@ -681,6 +681,18 @@ class Environment {
681681
// This needs to be available for the JS-land setImmediate().
682682
void ActivateImmediateCheck();
683683

684+
class ShouldNotAbortOnUncaughtScope {
685+
public:
686+
explicit inline ShouldNotAbortOnUncaughtScope(Environment* env);
687+
inline void Close();
688+
inline ~ShouldNotAbortOnUncaughtScope();
689+
690+
private:
691+
Environment* env_;
692+
};
693+
694+
inline bool inside_should_not_abort_on_uncaught_scope() const;
695+
684696
private:
685697
inline void ThrowError(v8::Local<v8::Value> (*fun)(v8::Local<v8::String>),
686698
const char* errmsg);
@@ -706,6 +718,8 @@ class Environment {
706718
AliasedBuffer<uint32_t, v8::Uint32Array> scheduled_immediate_count_;
707719
AliasedBuffer<uint32_t, v8::Uint32Array> should_abort_on_uncaught_toggle_;
708720

721+
int should_not_abort_scope_counter_ = 0;
722+
709723
performance::performance_state* performance_state_ = nullptr;
710724
std::map<std::string, uint64_t> performance_marks_;
711725

src/node.cc

+4-1
Original file line numberDiff line numberDiff line change
@@ -1110,7 +1110,8 @@ namespace {
11101110
bool ShouldAbortOnUncaughtException(Isolate* isolate) {
11111111
HandleScope scope(isolate);
11121112
Environment* env = Environment::GetCurrent(isolate);
1113-
return env->should_abort_on_uncaught_toggle()[0];
1113+
return env->should_abort_on_uncaught_toggle()[0] &&
1114+
!env->inside_should_not_abort_on_uncaught_scope();
11141115
}
11151116

11161117

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

16431646
// Because of how node modules work, all scripts are wrapped with a
16441647
// "function (module, exports, __filename, ...) {"

src/node_contextify.cc

+3
Original file line numberDiff line numberDiff line change
@@ -654,6 +654,7 @@ class ContextifyScript : public BaseObject {
654654
new ContextifyScript(env, args.This());
655655

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

@@ -666,6 +667,7 @@ class ContextifyScript : public BaseObject {
666667
Maybe<bool> maybe_produce_cached_data = GetProduceCachedData(env, options);
667668
MaybeLocal<Context> maybe_context = GetContext(env, options);
668669
if (try_catch.HasCaught()) {
670+
no_abort_scope.Close();
669671
try_catch.ReThrow();
670672
return;
671673
}
@@ -702,6 +704,7 @@ class ContextifyScript : public BaseObject {
702704

703705
if (v8_script.IsEmpty()) {
704706
DecorateErrorStack(env, try_catch);
707+
no_abort_scope.Close();
705708
try_catch.ReThrow();
706709
return;
707710
}

test/abort/test-abort-uncaught-exception.js

+11-4
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,24 @@
33
const common = require('../common');
44
const assert = require('assert');
55
const spawn = require('child_process').spawn;
6+
const vm = require('vm');
67
const node = process.execPath;
78

89
if (process.argv[2] === 'child') {
910
throw new Error('child error');
11+
} else if (process.argv[2] === 'vm') {
12+
// Refs: https://github.com/nodejs/node/issues/13258
13+
// This *should* still crash.
14+
new vm.Script('[', {});
1015
} else {
11-
run('', null);
12-
run('--abort-on-uncaught-exception', ['SIGABRT', 'SIGTRAP', 'SIGILL']);
16+
run('', 'child', null);
17+
run('--abort-on-uncaught-exception', 'child',
18+
['SIGABRT', 'SIGTRAP', 'SIGILL']);
19+
run('--abort-on-uncaught-exception', 'vm', ['SIGABRT', 'SIGTRAP', 'SIGILL']);
1320
}
1421

15-
function run(flags, signals) {
16-
const args = [__filename, 'child'];
22+
function run(flags, argv2, signals) {
23+
const args = [__filename, argv2];
1724
if (flags)
1825
args.unshift(flags);
1926

test/message/eval_messages.out

+4-3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
with(this){__filename}
44
^^^^
55
SyntaxError: Strict mode code may not include a with statement
6+
at new Script (vm.js:*:*)
67
at createScript (vm.js:*:*)
78
at Object.runInThisContext (vm.js:*:*)
89
at Object.<anonymous> ([eval]-wrapper:*:*)
@@ -18,7 +19,7 @@ throw new Error("hello")
1819

1920
Error: hello
2021
at [eval]:1:7
21-
at ContextifyScript.Script.runInThisContext (vm.js:*:*)
22+
at Script.runInThisContext (vm.js:*:*)
2223
at Object.runInThisContext (vm.js:*:*)
2324
at Object.<anonymous> ([eval]-wrapper:*:*)
2425
at Module._compile (module.js:*:*)
@@ -32,7 +33,7 @@ throw new Error("hello")
3233

3334
Error: hello
3435
at [eval]:1:7
35-
at ContextifyScript.Script.runInThisContext (vm.js:*:*)
36+
at Script.runInThisContext (vm.js:*:*)
3637
at Object.runInThisContext (vm.js:*:*)
3738
at Object.<anonymous> ([eval]-wrapper:*:*)
3839
at Module._compile (module.js:*:*)
@@ -46,7 +47,7 @@ var x = 100; y = x;
4647

4748
ReferenceError: y is not defined
4849
at [eval]:1:16
49-
at ContextifyScript.Script.runInThisContext (vm.js:*:*)
50+
at Script.runInThisContext (vm.js:*:*)
5051
at Object.runInThisContext (vm.js:*:*)
5152
at Object.<anonymous> ([eval]-wrapper:*:*)
5253
at Module._compile (module.js:*:*)

test/message/stdin_messages.out

+4-3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
with(this){__filename}
44
^^^^
55
SyntaxError: Strict mode code may not include a with statement
6+
at new Script (vm.js:*)
67
at createScript (vm.js:*)
78
at Object.runInThisContext (vm.js:*)
89
at Object.<anonymous> ([stdin]-wrapper:*:*)
@@ -20,7 +21,7 @@ throw new Error("hello")
2021

2122
Error: hello
2223
at [stdin]:1:7
23-
at ContextifyScript.Script.runInThisContext (vm.js:*)
24+
at Script.runInThisContext (vm.js:*)
2425
at Object.runInThisContext (vm.js:*)
2526
at Object.<anonymous> ([stdin]-wrapper:*:*)
2627
at Module._compile (module.js:*:*)
@@ -35,7 +36,7 @@ throw new Error("hello")
3536

3637
Error: hello
3738
at [stdin]:1:*
38-
at ContextifyScript.Script.runInThisContext (vm.js:*)
39+
at Script.runInThisContext (vm.js:*)
3940
at Object.runInThisContext (vm.js:*)
4041
at Object.<anonymous> ([stdin]-wrapper:*:*)
4142
at Module._compile (module.js:*:*)
@@ -51,7 +52,7 @@ var x = 100; y = x;
5152

5253
ReferenceError: y is not defined
5354
at [stdin]:1:16
54-
at ContextifyScript.Script.runInThisContext (vm.js:*)
55+
at Script.runInThisContext (vm.js:*)
5556
at Object.runInThisContext (vm.js:*)
5657
at Object.<anonymous> ([stdin]-wrapper:*:*)
5758
at Module._compile (module.js:*:*)

test/message/undefined_reference_in_new_context.out

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ foo.bar = 5;
55

66
ReferenceError: foo is not defined
77
at evalmachine.<anonymous>:1:1
8-
at ContextifyScript.Script.runInContext (vm.js:*)
9-
at ContextifyScript.Script.runInNewContext (vm.js:*)
8+
at Script.runInContext (vm.js:*)
9+
at Script.runInNewContext (vm.js:*)
1010
at Object.runInNewContext (vm.js:*)
1111
at Object.<anonymous> (*test*message*undefined_reference_in_new_context.js:*)
1212
at Module._compile (module.js:*)

test/message/vm_display_runtime_error.out

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ throw new Error("boo!")
55

66
Error: boo!
77
at test.vm:1:7
8-
at ContextifyScript.Script.runInThisContext (vm.js:*)
8+
at Script.runInThisContext (vm.js:*)
99
at Object.runInThisContext (vm.js:*)
1010
at Object.<anonymous> (*test*message*vm_display_runtime_error.js:*)
1111
at Module._compile (module.js:*)
@@ -20,7 +20,7 @@ throw new Error("spooky!")
2020

2121
Error: spooky!
2222
at test.vm:1:7
23-
at ContextifyScript.Script.runInThisContext (vm.js:*)
23+
at Script.runInThisContext (vm.js:*)
2424
at Object.runInThisContext (vm.js:*)
2525
at Object.<anonymous> (*test*message*vm_display_runtime_error.js:*)
2626
at Module._compile (module.js:*)

test/message/vm_display_syntax_error.out

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ foo.vm:1
33
var 4;
44
^
55
SyntaxError: Unexpected number
6+
at new Script (vm.js:*)
67
at createScript (vm.js:*)
78
at Object.runInThisContext (vm.js:*)
89
at Object.<anonymous> (*test*message*vm_display_syntax_error.js:*)
@@ -12,11 +13,11 @@ SyntaxError: Unexpected number
1213
at tryModuleLoad (module.js:*:*)
1314
at Function.Module._load (module.js:*)
1415
at Function.Module.runMain (module.js:*)
15-
at startup (bootstrap_node.js:*:*)
1616
test.vm:1
1717
var 5;
1818
^
1919
SyntaxError: Unexpected number
20+
at new Script (vm.js:*)
2021
at createScript (vm.js:*)
2122
at Object.runInThisContext (vm.js:*)
2223
at Object.<anonymous> (*test*message*vm_display_syntax_error.js:*)
@@ -26,4 +27,3 @@ SyntaxError: Unexpected number
2627
at tryModuleLoad (module.js:*:*)
2728
at Function.Module._load (module.js:*)
2829
at Function.Module.runMain (module.js:*)
29-
at startup (bootstrap_node.js:*:*)

test/message/vm_dont_display_runtime_error.out

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ throw new Error("boo!")
66

77
Error: boo!
88
at test.vm:1:7
9-
at ContextifyScript.Script.runInThisContext (vm.js:*)
9+
at Script.runInThisContext (vm.js:*)
1010
at Object.runInThisContext (vm.js:*)
1111
at Object.<anonymous> (*test*message*vm_dont_display_runtime_error.js:*)
1212
at Module._compile (module.js:*)

test/message/vm_dont_display_syntax_error.out

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ var 5;
55
^
66

77
SyntaxError: Unexpected number
8+
at new Script (vm.js:*)
89
at createScript (vm.js:*)
910
at Object.runInThisContext (vm.js:*)
1011
at Object.<anonymous> (*test*message*vm_dont_display_syntax_error.js:*)
@@ -14,4 +15,3 @@ SyntaxError: Unexpected number
1415
at tryModuleLoad (module.js:*:*)
1516
at Function.Module._load (module.js:*)
1617
at Function.Module.runMain (module.js:*)
17-
at startup (bootstrap_node.js:*:*)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Flags: --abort-on-uncaught-exception
2+
'use strict';
3+
require('../common');
4+
const vm = require('vm');
5+
6+
// Regression test for https://github.com/nodejs/node/issues/13258
7+
8+
try {
9+
new vm.Script({ toString() { throw new Error('foo'); } }, {});
10+
} catch (err) {}
11+
12+
try {
13+
new vm.Script('[', {});
14+
} catch (err) {}

0 commit comments

Comments
 (0)