Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

vm: never abort on caught syntax error #17394

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -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.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nodejs/v8 Is this a bug in V8?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah a ReThrow is not actually a new throw. It just "re-activates" the exception.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hashseed Does that mean one could get the right behavior by exiting the TryCatch scope, then using isolate->ThrowException()?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. Though the message you get with this kind of "rethrow" will show the stack trace of the new throw location.

try {
super(code, options);
} catch (e) {
throw e; /* node-do-not-add-exception-line */
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any idea for how to avoid this? Or: Is this even actually a bad thing?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would it be a bad thing?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure - it seems like something that people might find out, start to use, and be unhappy when they find out we changed/removed/etc. it

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what "it" refers to. The line in the (decorated) stack trace? What info could they glean from that?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, sorry, I should have been clearer - I was talking about the node-do-not-add-exception-line magic string and its effect of not appending the filename + arrow string in that case

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hah, okay. I read your question as "is it a bad thing for the throw to show up." I wouldn't say so, and yes, I'd say it's only a matter of time before people find out about the magic comment. :-)

If you want to exclude stack frames, you can filter based on the script id. Probably overkill for this, though.

}
}
}

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
15 changes: 14 additions & 1 deletion src/env.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,6 @@ class ModuleWrap;
V(contextify_global_private_symbol, "node:contextify:global") \
V(decorated_private_symbol, "node:decorated") \
V(npn_buffer_private_symbol, "node:npnBuffer") \
V(processed_private_symbol, "node:processed") \
V(selected_npn_buffer_private_symbol, "node:selectedNpnBuffer") \
V(domain_private_symbol, "node:domain") \

Expand Down Expand Up @@ -700,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);
Expand All @@ -725,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_;

Expand Down
15 changes: 4 additions & 11 deletions src/node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}


Expand Down Expand Up @@ -1301,16 +1302,6 @@ void AppendExceptionLine(Environment* env,
Local<Object> err_obj;
if (!er.IsEmpty() && er->IsObject()) {
err_obj = er.As<Object>();

auto context = env->context();
auto processed_private_symbol = env->processed_private_symbol();
// Do it only once per message
if (err_obj->HasPrivate(context, processed_private_symbol).FromJust())
return;
err_obj->SetPrivate(
context,
processed_private_symbol,
True(env->isolate()));
}

// Print (filename):(line number): (message).
Expand All @@ -1321,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, ...) {"
Expand Down
7 changes: 4 additions & 3 deletions src/node_contextify.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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>());

Expand All @@ -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;
}
Expand Down Expand Up @@ -668,9 +670,8 @@ class ContextifyScript : public BaseObject {
compile_options);

if (v8_script.IsEmpty()) {
if (display_errors) {
DecorateErrorStack(env, try_catch);
}
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) {}