src: add can_call_into_js flag

This prevents calls back into JS from the shutdown phase.

Many thanks for Stephen Belanger for reviewing the original version of
this commit in the Ayo.js project.

Refs: https://github.com/ayojs/ayo/pull/82
PR-URL: https://github.com/nodejs/node/pull/19377
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
Anna Henningsen 2017-09-20 14:43:19 +02:00
parent 61fd027096
commit bcb324c3ff
No known key found for this signature in database
GPG Key ID: 9C63F3A6CD2AD8F9
5 changed files with 31 additions and 2 deletions

View File

@ -141,6 +141,7 @@ static void DestroyAsyncIdsCallback(Environment* env, void* data) {
do {
std::vector<double> destroy_async_id_list;
destroy_async_id_list.swap(*env->destroy_async_id_list());
if (!env->can_call_into_js()) return;
for (auto async_id : destroy_async_id_list) {
// Want each callback to be cleaned up after itself, instead of cleaning
// them all up after the while() loop completes.
@ -166,7 +167,7 @@ void Emit(Environment* env, double async_id, AsyncHooks::Fields type,
Local<Function> fn) {
AsyncHooks* async_hooks = env->async_hooks();
if (async_hooks->fields()[type] == 0)
if (async_hooks->fields()[type] == 0 || !env->can_call_into_js())
return;
v8::HandleScope handle_scope(env->isolate());
@ -625,8 +626,10 @@ void AsyncWrap::EmitTraceEventDestroy() {
}
void AsyncWrap::EmitDestroy(Environment* env, double async_id) {
if (env->async_hooks()->fields()[AsyncHooks::kDestroy] == 0)
if (env->async_hooks()->fields()[AsyncHooks::kDestroy] == 0 ||
!env->can_call_into_js()) {
return;
}
if (env->destroy_async_id_list()->empty()) {
env->SetUnrefImmediate(DestroyAsyncIdsCallback, nullptr);

View File

@ -559,6 +559,14 @@ void Environment::SetUnrefImmediate(native_immediate_callback cb,
CreateImmediate(cb, data, obj, false);
}
inline bool Environment::can_call_into_js() const {
return can_call_into_js_;
}
inline void Environment::set_can_call_into_js(bool can_call_into_js) {
can_call_into_js_ = can_call_into_js;
}
inline performance::performance_state* Environment::performance_state() {
return performance_state_.get();
}

View File

@ -679,6 +679,12 @@ class Environment {
const char* path = nullptr,
const char* dest = nullptr);
// If this flag is set, calls into JS (if they would be observable
// from userland) must be avoided. This flag does not indicate whether
// calling into JS is allowed from a VM perspective at this point.
inline bool can_call_into_js() const;
inline void set_can_call_into_js(bool can_call_into_js);
inline void ThrowError(const char* errmsg);
inline void ThrowTypeError(const char* errmsg);
inline void ThrowRangeError(const char* errmsg);
@ -821,6 +827,7 @@ class Environment {
std::unique_ptr<performance::performance_state> performance_state_;
std::unordered_map<std::string, uint64_t> performance_marks_;
bool can_call_into_js_ = true;
#if HAVE_INSPECTOR
std::unique_ptr<inspector::Agent> inspector_agent_;

View File

@ -954,6 +954,11 @@ InternalCallbackScope::InternalCallbackScope(Environment* env,
CHECK(!object.IsEmpty());
}
if (!env->can_call_into_js()) {
failed_ = true;
return;
}
HandleScope handle_scope(env->isolate());
// If you hit this assertion, you forgot to enter the v8::Context first.
CHECK_EQ(Environment::GetCurrent(env->isolate()), env);
@ -997,6 +1002,7 @@ void InternalCallbackScope::Close() {
Environment::TickInfo* tick_info = env_->tick_info();
if (!env_->can_call_into_js()) return;
if (!tick_info->has_scheduled()) {
env_->isolate()->RunMicrotasks();
}
@ -1014,6 +1020,8 @@ void InternalCallbackScope::Close() {
Local<Object> process = env_->process_object();
if (!env_->can_call_into_js()) return;
if (env_->tick_callback_function()->Call(process, 0, nullptr).IsEmpty()) {
env_->tick_info()->set_has_thrown(true);
failed_ = true;
@ -4552,6 +4560,7 @@ inline int Start(Isolate* isolate, IsolateData* isolate_data,
WaitForInspectorDisconnect(&env);
env.set_can_call_into_js(false);
env.RunCleanup();
RunAtExit(&env);

View File

@ -820,6 +820,8 @@ class ContextifyScript : public BaseObject {
const bool display_errors,
const bool break_on_sigint,
const FunctionCallbackInfo<Value>& args) {
if (!env->can_call_into_js())
return false;
if (!ContextifyScript::InstanceOf(env, args.Holder())) {
env->ThrowTypeError(
"Script methods can only be called on script instances.");