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: https://github.com/nodejs/node/pull/17394 Fixes: https://github.com/nodejs/node/issues/13258 Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
parent
aeddc3676b
commit
b73e66e949
15
lib/vm.js
15
lib/vm.js
@ -22,7 +22,7 @@
|
||||
'use strict';
|
||||
|
||||
const {
|
||||
ContextifyScript: Script,
|
||||
ContextifyScript,
|
||||
kParsingContext,
|
||||
|
||||
makeContext,
|
||||
@ -39,6 +39,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;
|
||||
|
||||
|
@ -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_;
|
||||
}
|
||||
|
14
src/env.h
14
src/env.h
@ -699,6 +699,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);
|
||||
@ -724,6 +736,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_;
|
||||
|
||||
|
@ -782,7 +782,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();
|
||||
}
|
||||
|
||||
|
||||
@ -1311,6 +1312,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, ...) {"
|
||||
|
@ -621,6 +621,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>());
|
||||
|
||||
@ -633,6 +634,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;
|
||||
}
|
||||
@ -669,6 +671,7 @@ class ContextifyScript : public BaseObject {
|
||||
|
||||
if (v8_script.IsEmpty()) {
|
||||
DecorateErrorStack(env, try_catch);
|
||||
no_abort_scope.Close();
|
||||
try_catch.ReThrow();
|
||||
return;
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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:*:*)
|
||||
@ -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:*:*)
|
||||
@ -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:*:*)
|
||||
@ -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:*:*)
|
||||
|
@ -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:*:*)
|
||||
@ -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:*:*)
|
||||
@ -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:*:*)
|
||||
@ -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:*:*)
|
||||
|
@ -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:*)
|
||||
|
@ -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:*)
|
||||
@ -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:*)
|
||||
|
@ -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:*)
|
||||
@ -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:*)
|
||||
@ -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:*:*)
|
||||
|
@ -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:*)
|
||||
|
@ -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:*)
|
||||
@ -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
test/parallel/test-vm-parse-abort-on-uncaught-exception.js
Normal file
14
test/parallel/test-vm-parse-abort-on-uncaught-exception.js
Normal file
@ -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) {}
|
Loading…
x
Reference in New Issue
Block a user