0
0
mirror of https://github.com/nodejs/node.git synced 2024-12-01 16:10:02 +01:00
nodejs/src/inspector_js_api.cc
James M Snell 0fac393d26 src: improve handling of internal field counting
Change suggested by bnoordhuis.

Improve handing of internal field counting by using enums.
Helps protect against future possible breakage if field
indexes are ever changed or added to.

Signed-off-by: James M Snell <jasnell@gmail.com>

PR-URL: https://github.com/nodejs/node/pull/31960
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Franziska Hinkelmann <franziska.hinkelmann@gmail.com>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
2020-03-02 10:58:36 -08:00

354 lines
12 KiB
C++

#include "base_object-inl.h"
#include "inspector_agent.h"
#include "inspector_io.h"
#include "memory_tracker-inl.h"
#include "util-inl.h"
#include "v8.h"
#include "v8-inspector.h"
#include <memory>
namespace node {
namespace inspector {
namespace {
using v8::Boolean;
using v8::Context;
using v8::Function;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::Global;
using v8::HandleScope;
using v8::Isolate;
using v8::Local;
using v8::MaybeLocal;
using v8::NewStringType;
using v8::Object;
using v8::String;
using v8::Uint32;
using v8::Value;
using v8_inspector::StringBuffer;
using v8_inspector::StringView;
std::unique_ptr<StringBuffer> ToProtocolString(Isolate* isolate,
Local<Value> value) {
TwoByteValue buffer(isolate, value);
return StringBuffer::create(StringView(*buffer, buffer.length()));
}
struct LocalConnection {
static std::unique_ptr<InspectorSession> Connect(
Agent* inspector, std::unique_ptr<InspectorSessionDelegate> delegate) {
return inspector->Connect(std::move(delegate), false);
}
static Local<String> GetClassName(Environment* env) {
return FIXED_ONE_BYTE_STRING(env->isolate(), "Connection");
}
};
struct MainThreadConnection {
static std::unique_ptr<InspectorSession> Connect(
Agent* inspector, std::unique_ptr<InspectorSessionDelegate> delegate) {
return inspector->ConnectToMainThread(std::move(delegate), true);
}
static Local<String> GetClassName(Environment* env) {
return FIXED_ONE_BYTE_STRING(env->isolate(), "MainThreadConnection");
}
};
template <typename ConnectionType>
class JSBindingsConnection : public AsyncWrap {
public:
class JSBindingsSessionDelegate : public InspectorSessionDelegate {
public:
JSBindingsSessionDelegate(Environment* env,
JSBindingsConnection* connection)
: env_(env),
connection_(connection) {
}
void SendMessageToFrontend(const v8_inspector::StringView& message)
override {
Isolate* isolate = env_->isolate();
HandleScope handle_scope(isolate);
Context::Scope context_scope(env_->context());
MaybeLocal<String> v8string =
String::NewFromTwoByte(isolate, message.characters16(),
NewStringType::kNormal, message.length());
Local<Value> argument = v8string.ToLocalChecked().As<Value>();
connection_->OnMessage(argument);
}
private:
Environment* env_;
JSBindingsConnection* connection_;
};
JSBindingsConnection(Environment* env,
Local<Object> wrap,
Local<Function> callback)
: AsyncWrap(env, wrap, PROVIDER_INSPECTORJSBINDING),
callback_(env->isolate(), callback) {
Agent* inspector = env->inspector_agent();
session_ = ConnectionType::Connect(
inspector, std::make_unique<JSBindingsSessionDelegate>(env, this));
}
void OnMessage(Local<Value> value) {
MakeCallback(callback_.Get(env()->isolate()), 1, &value);
}
static void Bind(Environment* env, Local<Object> target) {
Local<String> class_name = ConnectionType::GetClassName(env);
Local<FunctionTemplate> tmpl =
env->NewFunctionTemplate(JSBindingsConnection::New);
tmpl->InstanceTemplate()->SetInternalFieldCount(
JSBindingsConnection::kInternalFieldCount);
tmpl->SetClassName(class_name);
tmpl->Inherit(AsyncWrap::GetConstructorTemplate(env));
env->SetProtoMethod(tmpl, "dispatch", JSBindingsConnection::Dispatch);
env->SetProtoMethod(tmpl, "disconnect", JSBindingsConnection::Disconnect);
target->Set(env->context(),
class_name,
tmpl->GetFunction(env->context()).ToLocalChecked())
.ToChecked();
}
static void New(const FunctionCallbackInfo<Value>& info) {
Environment* env = Environment::GetCurrent(info);
CHECK(info[0]->IsFunction());
Local<Function> callback = info[0].As<Function>();
new JSBindingsConnection(env, info.This(), callback);
}
void Disconnect() {
session_.reset();
delete this;
}
static void Disconnect(const FunctionCallbackInfo<Value>& info) {
JSBindingsConnection* session;
ASSIGN_OR_RETURN_UNWRAP(&session, info.Holder());
session->Disconnect();
}
static void Dispatch(const FunctionCallbackInfo<Value>& info) {
Environment* env = Environment::GetCurrent(info);
JSBindingsConnection* session;
ASSIGN_OR_RETURN_UNWRAP(&session, info.Holder());
CHECK(info[0]->IsString());
if (session->session_) {
session->session_->Dispatch(
ToProtocolString(env->isolate(), info[0])->string());
}
}
void MemoryInfo(MemoryTracker* tracker) const override {
tracker->TrackField("callback", callback_);
tracker->TrackFieldWithSize(
"session", sizeof(*session_), "InspectorSession");
}
SET_MEMORY_INFO_NAME(JSBindingsConnection)
SET_SELF_SIZE(JSBindingsConnection)
private:
std::unique_ptr<InspectorSession> session_;
Global<Function> callback_;
};
static bool InspectorEnabled(Environment* env) {
Agent* agent = env->inspector_agent();
return agent->IsActive();
}
void SetConsoleExtensionInstaller(const FunctionCallbackInfo<Value>& info) {
auto env = Environment::GetCurrent(info);
CHECK_EQ(info.Length(), 1);
CHECK(info[0]->IsFunction());
env->set_inspector_console_extension_installer(info[0].As<Function>());
}
void CallAndPauseOnStart(const FunctionCallbackInfo<v8::Value>& args) {
Environment* env = Environment::GetCurrent(args);
CHECK_GT(args.Length(), 1);
CHECK(args[0]->IsFunction());
SlicedArguments call_args(args, /* start */ 2);
env->inspector_agent()->PauseOnNextJavascriptStatement("Break on start");
v8::MaybeLocal<v8::Value> retval =
args[0].As<v8::Function>()->Call(env->context(), args[1],
call_args.length(), call_args.out());
if (!retval.IsEmpty()) {
args.GetReturnValue().Set(retval.ToLocalChecked());
}
}
void InspectorConsoleCall(const FunctionCallbackInfo<Value>& info) {
Environment* env = Environment::GetCurrent(info);
Isolate* isolate = env->isolate();
Local<Context> context = isolate->GetCurrentContext();
CHECK_GE(info.Length(), 2);
SlicedArguments call_args(info, /* start */ 2);
if (InspectorEnabled(env)) {
Local<Value> inspector_method = info[0];
CHECK(inspector_method->IsFunction());
if (!env->is_in_inspector_console_call()) {
env->set_is_in_inspector_console_call(true);
MaybeLocal<Value> ret =
inspector_method.As<Function>()->Call(context,
info.Holder(),
call_args.length(),
call_args.out());
env->set_is_in_inspector_console_call(false);
if (ret.IsEmpty())
return;
}
}
Local<Value> node_method = info[1];
CHECK(node_method->IsFunction());
node_method.As<Function>()->Call(context,
info.Holder(),
call_args.length(),
call_args.out()).FromMaybe(Local<Value>());
}
static void* GetAsyncTask(int64_t asyncId) {
// The inspector assumes that when other clients use its asyncTask* API,
// they use real pointers, or at least something aligned like real pointer.
// In general it means that our task_id should always be even.
//
// On 32bit platforms, the 64bit asyncId would get truncated when converted
// to a 32bit pointer. However, the javascript part will never enable
// the async_hook on 32bit platforms, therefore the truncation will never
// happen in practice.
return reinterpret_cast<void*>(asyncId << 1);
}
template <void (Agent::*asyncTaskFn)(void*)>
static void InvokeAsyncTaskFnWithId(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
CHECK(args[0]->IsNumber());
int64_t task_id = args[0]->IntegerValue(env->context()).FromJust();
(env->inspector_agent()->*asyncTaskFn)(GetAsyncTask(task_id));
}
static void AsyncTaskScheduledWrapper(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
CHECK(args[0]->IsString());
Local<String> task_name = args[0].As<String>();
String::Value task_name_value(args.GetIsolate(), task_name);
StringView task_name_view(*task_name_value, task_name_value.length());
CHECK(args[1]->IsNumber());
int64_t task_id = args[1]->IntegerValue(env->context()).FromJust();
void* task = GetAsyncTask(task_id);
CHECK(args[2]->IsBoolean());
bool recurring = args[2]->BooleanValue(args.GetIsolate());
env->inspector_agent()->AsyncTaskScheduled(task_name_view, task, recurring);
}
static void RegisterAsyncHookWrapper(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
CHECK(args[0]->IsFunction());
Local<Function> enable_function = args[0].As<Function>();
CHECK(args[1]->IsFunction());
Local<Function> disable_function = args[1].As<Function>();
env->inspector_agent()->RegisterAsyncHook(env->isolate(),
enable_function, disable_function);
}
void IsEnabled(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
args.GetReturnValue().Set(InspectorEnabled(env));
}
void Open(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Agent* agent = env->inspector_agent();
if (args.Length() > 0 && args[0]->IsUint32()) {
uint32_t port = args[0].As<Uint32>()->Value();
ExclusiveAccess<HostPort>::Scoped host_port(agent->host_port());
host_port->set_port(static_cast<int>(port));
}
if (args.Length() > 1 && args[1]->IsString()) {
Utf8Value host(env->isolate(), args[1].As<String>());
ExclusiveAccess<HostPort>::Scoped host_port(agent->host_port());
host_port->set_host(*host);
}
agent->StartIoThread();
}
void WaitForDebugger(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Agent* agent = env->inspector_agent();
if (agent->IsActive())
agent->WaitForConnect();
args.GetReturnValue().Set(agent->IsActive());
}
void Url(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
std::string url = env->inspector_agent()->GetWsUrl();
if (url.length() == 0) {
return;
}
args.GetReturnValue().Set(OneByteString(env->isolate(), url.c_str()));
}
void Initialize(Local<Object> target, Local<Value> unused,
Local<Context> context, void* priv) {
Environment* env = Environment::GetCurrent(context);
v8::Local<v8::Function> consoleCallFunc =
env->NewFunctionTemplate(InspectorConsoleCall, v8::Local<v8::Signature>(),
v8::ConstructorBehavior::kThrow,
v8::SideEffectType::kHasSideEffect)
->GetFunction(context)
.ToLocalChecked();
auto name_string = FIXED_ONE_BYTE_STRING(env->isolate(), "consoleCall");
target->Set(context, name_string, consoleCallFunc).Check();
consoleCallFunc->SetName(name_string);
env->SetMethod(
target, "setConsoleExtensionInstaller", SetConsoleExtensionInstaller);
env->SetMethod(target, "callAndPauseOnStart", CallAndPauseOnStart);
env->SetMethod(target, "open", Open);
env->SetMethodNoSideEffect(target, "url", Url);
env->SetMethod(target, "waitForDebugger", WaitForDebugger);
env->SetMethod(target, "asyncTaskScheduled", AsyncTaskScheduledWrapper);
env->SetMethod(target, "asyncTaskCanceled",
InvokeAsyncTaskFnWithId<&Agent::AsyncTaskCanceled>);
env->SetMethod(target, "asyncTaskStarted",
InvokeAsyncTaskFnWithId<&Agent::AsyncTaskStarted>);
env->SetMethod(target, "asyncTaskFinished",
InvokeAsyncTaskFnWithId<&Agent::AsyncTaskFinished>);
env->SetMethod(target, "registerAsyncHook", RegisterAsyncHookWrapper);
env->SetMethodNoSideEffect(target, "isEnabled", IsEnabled);
JSBindingsConnection<LocalConnection>::Bind(env, target);
JSBindingsConnection<MainThreadConnection>::Bind(env, target);
}
} // namespace
} // namespace inspector
} // namespace node
NODE_MODULE_CONTEXT_AWARE_INTERNAL(inspector,
node::inspector::Initialize)