1// Copyright 2018 the V8 project authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "src/d8/async-hooks-wrapper.h" 6 7#include "include/v8-function.h" 8#include "include/v8-local-handle.h" 9#include "include/v8-primitive.h" 10#include "include/v8-template.h" 11#include "src/api/api-inl.h" 12#include "src/api/api.h" 13#include "src/d8/d8.h" 14#include "src/execution/isolate-inl.h" 15#include "src/objects/managed-inl.h" 16 17namespace v8 { 18 19namespace { 20std::shared_ptr<AsyncHooksWrap> UnwrapHook( 21 const v8::FunctionCallbackInfo<v8::Value>& args) { 22 Isolate* isolate = args.GetIsolate(); 23 HandleScope scope(isolate); 24 Local<Object> hook = args.This(); 25 26 AsyncHooks* hooks = PerIsolateData::Get(isolate)->GetAsyncHooks(); 27 28 if (!hooks->async_hook_ctor.Get(isolate)->HasInstance(hook)) { 29 isolate->ThrowError("Invalid 'this' passed instead of AsyncHooks instance"); 30 return nullptr; 31 } 32 33 i::Handle<i::Object> handle = Utils::OpenHandle(*hook->GetInternalField(0)); 34 return i::Handle<i::Managed<AsyncHooksWrap>>::cast(handle)->get(); 35} 36 37void EnableHook(const v8::FunctionCallbackInfo<v8::Value>& args) { 38 auto wrap = UnwrapHook(args); 39 if (wrap) wrap->Enable(); 40} 41 42void DisableHook(const v8::FunctionCallbackInfo<v8::Value>& args) { 43 auto wrap = UnwrapHook(args); 44 if (wrap) wrap->Disable(); 45} 46 47} // namespace 48 49AsyncHooks::AsyncHooks(Isolate* isolate) : isolate_(isolate) { 50 AsyncContext ctx; 51 ctx.execution_async_id = 1; 52 ctx.trigger_async_id = 0; 53 asyncContexts.push(ctx); 54 current_async_id = 1; 55 56 HandleScope handle_scope(isolate_); 57 58 async_hook_ctor.Reset(isolate_, FunctionTemplate::New(isolate_)); 59 async_hook_ctor.Get(isolate_)->SetClassName( 60 String::NewFromUtf8Literal(isolate_, "AsyncHook")); 61 62 async_hooks_templ.Reset(isolate_, 63 async_hook_ctor.Get(isolate_)->InstanceTemplate()); 64 async_hooks_templ.Get(isolate_)->SetInternalFieldCount(1); 65 async_hooks_templ.Get(isolate_)->Set( 66 isolate_, "enable", FunctionTemplate::New(isolate_, EnableHook)); 67 async_hooks_templ.Get(isolate_)->Set( 68 isolate_, "disable", FunctionTemplate::New(isolate_, DisableHook)); 69 70 async_id_smb.Reset(isolate_, Private::New(isolate_)); 71 trigger_id_smb.Reset(isolate_, Private::New(isolate_)); 72 73 isolate_->SetPromiseHook(ShellPromiseHook); 74} 75 76AsyncHooks::~AsyncHooks() { 77 isolate_->SetPromiseHook(nullptr); 78 base::RecursiveMutexGuard lock_guard(&async_wraps_mutex_); 79 async_wraps_.clear(); 80} 81 82void AsyncHooksWrap::Enable() { enabled_ = true; } 83 84void AsyncHooksWrap::Disable() { enabled_ = false; } 85 86v8::Local<v8::Function> AsyncHooksWrap::init_function() const { 87 return init_function_.Get(isolate_); 88} 89void AsyncHooksWrap::set_init_function(v8::Local<v8::Function> value) { 90 init_function_.Reset(isolate_, value); 91} 92v8::Local<v8::Function> AsyncHooksWrap::before_function() const { 93 return before_function_.Get(isolate_); 94} 95void AsyncHooksWrap::set_before_function(v8::Local<v8::Function> value) { 96 before_function_.Reset(isolate_, value); 97} 98v8::Local<v8::Function> AsyncHooksWrap::after_function() const { 99 return after_function_.Get(isolate_); 100} 101void AsyncHooksWrap::set_after_function(v8::Local<v8::Function> value) { 102 after_function_.Reset(isolate_, value); 103} 104v8::Local<v8::Function> AsyncHooksWrap::promiseResolve_function() const { 105 return promiseResolve_function_.Get(isolate_); 106} 107void AsyncHooksWrap::set_promiseResolve_function( 108 v8::Local<v8::Function> value) { 109 promiseResolve_function_.Reset(isolate_, value); 110} 111 112async_id_t AsyncHooks::GetExecutionAsyncId() const { 113 return asyncContexts.top().execution_async_id; 114} 115 116async_id_t AsyncHooks::GetTriggerAsyncId() const { 117 return asyncContexts.top().trigger_async_id; 118} 119 120Local<Object> AsyncHooks::CreateHook( 121 const v8::FunctionCallbackInfo<v8::Value>& args) { 122 Isolate* isolate = args.GetIsolate(); 123 EscapableHandleScope handle_scope(isolate); 124 125 Local<Context> currentContext = isolate->GetCurrentContext(); 126 127 if (args.Length() != 1 || !args[0]->IsObject()) { 128 isolate->ThrowError("Invalid arguments passed to createHook"); 129 return Local<Object>(); 130 } 131 132 std::shared_ptr<AsyncHooksWrap> wrap = 133 std::make_shared<AsyncHooksWrap>(isolate); 134 135 Local<Object> fn_obj = args[0].As<Object>(); 136 137#define SET_HOOK_FN(name) \ 138 Local<Value> name##_v = \ 139 fn_obj->Get(currentContext, String::NewFromUtf8Literal(isolate, #name)) \ 140 .ToLocalChecked(); \ 141 if (name##_v->IsFunction()) { \ 142 wrap->set_##name##_function(name##_v.As<Function>()); \ 143 } 144 145 SET_HOOK_FN(init); 146 SET_HOOK_FN(before); 147 SET_HOOK_FN(after); 148 SET_HOOK_FN(promiseResolve); 149#undef SET_HOOK_FN 150 151 Local<Object> obj = async_hooks_templ.Get(isolate) 152 ->NewInstance(currentContext) 153 .ToLocalChecked(); 154 i::Handle<i::Object> managed = i::Managed<AsyncHooksWrap>::FromSharedPtr( 155 reinterpret_cast<i::Isolate*>(isolate), sizeof(AsyncHooksWrap), wrap); 156 obj->SetInternalField(0, Utils::ToLocal(managed)); 157 158 { 159 base::RecursiveMutexGuard lock_guard(&async_wraps_mutex_); 160 async_wraps_.push_back(std::move(wrap)); 161 } 162 163 return handle_scope.Escape(obj); 164} 165 166void AsyncHooks::ShellPromiseHook(PromiseHookType type, Local<Promise> promise, 167 Local<Value> parent) { 168 v8::Isolate* isolate = promise->GetIsolate(); 169 i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate); 170 171 AsyncHooks* hooks = PerIsolateData::Get(isolate)->GetAsyncHooks(); 172 HandleScope handle_scope(isolate); 173 // Temporarily clear any scheduled_exception to allow evaluating JS that can 174 // throw. 175 i::Handle<i::Object> scheduled_exception; 176 if (i_isolate->has_scheduled_exception()) { 177 scheduled_exception = handle(i_isolate->scheduled_exception(), i_isolate); 178 i_isolate->clear_scheduled_exception(); 179 } 180 { 181 TryCatch try_catch(isolate); 182 try_catch.SetVerbose(true); 183 184 Local<Context> currentContext = isolate->GetCurrentContext(); 185 DCHECK(!currentContext.IsEmpty()); 186 187 if (type == PromiseHookType::kInit) { 188 ++hooks->current_async_id; 189 Local<Integer> async_id = Integer::New(isolate, hooks->current_async_id); 190 CHECK( 191 !promise->HasPrivate(currentContext, hooks->async_id_smb.Get(isolate)) 192 .ToChecked()); 193 promise->SetPrivate(currentContext, hooks->async_id_smb.Get(isolate), 194 async_id); 195 196 if (parent->IsPromise()) { 197 Local<Promise> parent_promise = parent.As<Promise>(); 198 Local<Value> parent_async_id = 199 parent_promise 200 ->GetPrivate(currentContext, hooks->async_id_smb.Get(isolate)) 201 .ToLocalChecked(); 202 promise->SetPrivate(currentContext, hooks->trigger_id_smb.Get(isolate), 203 parent_async_id); 204 } else { 205 CHECK(parent->IsUndefined()); 206 promise->SetPrivate(currentContext, hooks->trigger_id_smb.Get(isolate), 207 Integer::New(isolate, 0)); 208 } 209 } else if (type == PromiseHookType::kBefore) { 210 AsyncContext ctx; 211 ctx.execution_async_id = 212 promise->GetPrivate(currentContext, hooks->async_id_smb.Get(isolate)) 213 .ToLocalChecked() 214 .As<Integer>() 215 ->Value(); 216 ctx.trigger_async_id = 217 promise 218 ->GetPrivate(currentContext, hooks->trigger_id_smb.Get(isolate)) 219 .ToLocalChecked() 220 .As<Integer>() 221 ->Value(); 222 hooks->asyncContexts.push(ctx); 223 } else if (type == PromiseHookType::kAfter) { 224 hooks->asyncContexts.pop(); 225 } 226 if (!i::StackLimitCheck{i_isolate}.HasOverflowed()) { 227 base::RecursiveMutexGuard lock_guard(&hooks->async_wraps_mutex_); 228 for (const auto& wrap : hooks->async_wraps_) { 229 PromiseHookDispatch(type, promise, parent, *wrap, hooks); 230 if (try_catch.HasCaught()) break; 231 } 232 if (try_catch.HasCaught()) Shell::ReportException(isolate, &try_catch); 233 } 234 } 235 if (!scheduled_exception.is_null()) { 236 i_isolate->set_scheduled_exception(*scheduled_exception); 237 } 238} 239 240void AsyncHooks::PromiseHookDispatch(PromiseHookType type, 241 Local<Promise> promise, 242 Local<Value> parent, 243 const AsyncHooksWrap& wrap, 244 AsyncHooks* hooks) { 245 if (!wrap.IsEnabled()) return; 246 v8::Isolate* v8_isolate = hooks->isolate_; 247 HandleScope handle_scope(v8_isolate); 248 249 Local<Value> rcv = Undefined(v8_isolate); 250 Local<Context> context = v8_isolate->GetCurrentContext(); 251 Local<Value> async_id = 252 promise->GetPrivate(context, hooks->async_id_smb.Get(v8_isolate)) 253 .ToLocalChecked(); 254 Local<Value> args[1] = {async_id}; 255 256 switch (type) { 257 case PromiseHookType::kInit: 258 if (!wrap.init_function().IsEmpty()) { 259 Local<Value> initArgs[4] = { 260 async_id, String::NewFromUtf8Literal(v8_isolate, "PROMISE"), 261 promise->GetPrivate(context, hooks->trigger_id_smb.Get(v8_isolate)) 262 .ToLocalChecked(), 263 promise}; 264 USE(wrap.init_function()->Call(context, rcv, 4, initArgs)); 265 } 266 break; 267 case PromiseHookType::kBefore: 268 if (!wrap.before_function().IsEmpty()) { 269 USE(wrap.before_function()->Call(context, rcv, 1, args)); 270 } 271 break; 272 case PromiseHookType::kAfter: 273 if (!wrap.after_function().IsEmpty()) { 274 USE(wrap.after_function()->Call(context, rcv, 1, args)); 275 } 276 break; 277 case PromiseHookType::kResolve: 278 if (!wrap.promiseResolve_function().IsEmpty()) { 279 USE(wrap.promiseResolve_function()->Call(context, rcv, 1, args)); 280 } 281 } 282} 283 284} // namespace v8 285