You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

936 lines
29 KiB

#include "inspector_agent.h"
#include "inspector_socket.h"
#include "env.h"
#include "env-inl.h"
#include "node.h"
#include "node_crypto.h"
#include "node_mutex.h"
#include "node_version.h"
#include "v8-inspector.h"
#include "v8-platform.h"
#include "util.h"
#include "zlib.h"
#include "libplatform/libplatform.h"
#include <map>
#include <sstream>
#include <unicode/unistr.h>
#include <string.h>
#include <utility>
#include <vector>
namespace node {
namespace inspector {
namespace {
using v8_inspector::StringBuffer;
using v8_inspector::StringView;
const char TAG_CONNECT[] = "#connect";
const char TAG_DISCONNECT[] = "#disconnect";
static const uint8_t PROTOCOL_JSON[] = {
#include "v8_inspector_protocol_json.h" // NOLINT(build/include_order)
};
std::string GetWsUrl(int port, const std::string& id) {
char buf[1024];
snprintf(buf, sizeof(buf), "127.0.0.1:%d/%s", port, id.c_str());
return buf;
}
void PrintDebuggerReadyMessage(int port, const std::string& id) {
fprintf(stderr, "Debugger listening on port %d.\n"
"Warning: This is an experimental feature and could change at any time.\n"
"To start debugging, open the following URL in Chrome:\n"
" chrome-devtools://devtools/bundled/inspector.html?"
"experiments=true&v8only=true&ws=%s\n",
port, GetWsUrl(port, id).c_str());
fflush(stderr);
}
std::string MapToString(const std::map<std::string, std::string> object) {
std::ostringstream json;
json << "[ {\n";
bool first = true;
for (const auto& name_value : object) {
if (!first)
json << ",\n";
json << " \"" << name_value.first << "\": \"";
json << name_value.second << "\"";
first = false;
}
json << "\n} ]\n\n";
return json.str();
}
void Escape(std::string* string) {
for (char& c : *string) {
c = (c == '\"' || c == '\\') ? '_' : c;
}
}
void DisposeInspector(InspectorSocket* socket, int status) {
delete socket;
}
void DisconnectAndDisposeIO(InspectorSocket* socket) {
if (socket) {
inspector_close(socket, DisposeInspector);
}
}
void OnBufferAlloc(uv_handle_t* handle, size_t len, uv_buf_t* buf) {
buf->base = new char[len];
buf->len = len;
}
void SendHttpResponse(InspectorSocket* socket, const char* response,
size_t size) {
const char HEADERS[] = "HTTP/1.0 200 OK\r\n"
"Content-Type: application/json; charset=UTF-8\r\n"
"Cache-Control: no-cache\r\n"
"Content-Length: %zu\r\n"
"\r\n";
char header[sizeof(HEADERS) + 20];
int header_len = snprintf(header, sizeof(header), HEADERS, size);
inspector_write(socket, header, header_len);
inspector_write(socket, response, size);
}
void SendHttpResponse(InspectorSocket* socket, const std::string& response) {
SendHttpResponse(socket, response.data(), response.size());
}
void SendVersionResponse(InspectorSocket* socket) {
static const char response[] =
"{\n"
" \"Browser\": \"node.js/" NODE_VERSION "\",\n"
" \"Protocol-Version\": \"1.1\"\n"
"}\n";
SendHttpResponse(socket, response, sizeof(response) - 1);
}
std::string GetProcessTitle() {
// uv_get_process_title will trim the title if it is too long.
char title[2048];
int err = uv_get_process_title(title, sizeof(title));
if (err == 0) {
return title;
} else {
return "Node.js";
}
}
void SendProtocolJson(InspectorSocket* socket) {
z_stream strm;
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
CHECK_EQ(Z_OK, inflateInit(&strm));
static const size_t kDecompressedSize =
PROTOCOL_JSON[0] * 0x10000u +
PROTOCOL_JSON[1] * 0x100u +
PROTOCOL_JSON[2];
strm.next_in = const_cast<uint8_t*>(PROTOCOL_JSON + 3);
strm.avail_in = sizeof(PROTOCOL_JSON) - 3;
std::string data(kDecompressedSize, '\0');
strm.next_out = reinterpret_cast<Byte*>(&data[0]);
strm.avail_out = data.size();
CHECK_EQ(Z_STREAM_END, inflate(&strm, Z_FINISH));
CHECK_EQ(0, strm.avail_out);
CHECK_EQ(Z_OK, inflateEnd(&strm));
SendHttpResponse(socket, data);
}
const char* match_path_segment(const char* path, const char* expected) {
size_t len = strlen(expected);
if (StringEqualNoCaseN(path, expected, len)) {
if (path[len] == '/') return path + len + 1;
if (path[len] == '\0') return path + len;
}
return nullptr;
}
// UUID RFC: https://www.ietf.org/rfc/rfc4122.txt
// Used ver 4 - with numbers
std::string GenerateID() {
uint16_t buffer[8];
CHECK(crypto::EntropySource(reinterpret_cast<unsigned char*>(buffer),
sizeof(buffer)));
char uuid[256];
snprintf(uuid, sizeof(uuid), "%04x%04x-%04x-%04x-%04x-%04x%04x%04x",
buffer[0], // time_low
buffer[1], // time_mid
buffer[2], // time_low
(buffer[3] & 0x0fff) | 0x4000, // time_hi_and_version
(buffer[4] & 0x3fff) | 0x8000, // clk_seq_hi clk_seq_low
buffer[5], // node
buffer[6],
buffer[7]);
return uuid;
}
std::string StringViewToUtf8(const StringView& view) {
if (view.is8Bit()) {
return std::string(reinterpret_cast<const char*>(view.characters8()),
view.length());
}
const uint16_t* source = view.characters16();
const UChar* unicodeSource = reinterpret_cast<const UChar*>(source);
static_assert(sizeof(*source) == sizeof(*unicodeSource),
"sizeof(*source) == sizeof(*unicodeSource)");
size_t result_length = view.length() * sizeof(*source);
std::string result(result_length, '\0');
UnicodeString utf16(unicodeSource, view.length());
// ICU components for std::string compatibility are not enabled in build...
bool done = false;
while (!done) {
CheckedArrayByteSink sink(&result[0], result_length);
utf16.toUTF8(sink);
result_length = sink.NumberOfBytesAppended();
result.resize(result_length);
done = !sink.Overflowed();
}
return result;
}
std::unique_ptr<StringBuffer> Utf8ToStringView(const char* source,
size_t length) {
UnicodeString utf16 = UnicodeString::fromUTF8(StringPiece(source, length));
StringView view(reinterpret_cast<const uint16_t*>(utf16.getBuffer()),
utf16.length());
return StringBuffer::create(view);
}
} // namespace
class V8NodeInspector;
class AgentImpl {
public:
explicit AgentImpl(node::Environment* env);
~AgentImpl();
// Start the inspector agent thread
bool Start(v8::Platform* platform, const char* path, int port, bool wait);
// Stop the inspector agent
void Stop();
bool IsStarted();
bool IsConnected() { return state_ == State::kConnected; }
void WaitForDisconnect();
void FatalException(v8::Local<v8::Value> error,
v8::Local<v8::Message> message);
private:
using MessageQueue =
std::vector<std::pair<int, std::unique_ptr<StringBuffer>>>;
enum class State { kNew, kAccepting, kConnected, kDone, kError };
static void ThreadCbIO(void* agent);
static void OnSocketConnectionIO(uv_stream_t* server, int status);
static bool OnInspectorHandshakeIO(InspectorSocket* socket,
enum inspector_handshake_event state,
const std::string& path);
static void WriteCbIO(uv_async_t* async);
void InstallInspectorOnProcess();
void WorkerRunIO();
void OnInspectorConnectionIO(InspectorSocket* socket);
void OnRemoteDataIO(InspectorSocket* stream, ssize_t read,
const uv_buf_t* b);
void SetConnected(bool connected);
void DispatchMessages();
void Write(int session_id, const StringView& message);
bool AppendMessage(MessageQueue* vector, int session_id,
std::unique_ptr<StringBuffer> buffer);
void SwapBehindLock(MessageQueue* vector1, MessageQueue* vector2);
void PostIncomingMessage(const char* message, size_t len);
void WaitForFrontendMessage();
void NotifyMessageReceived();
State ToState(State state);
void SendListResponse(InspectorSocket* socket);
bool RespondToGet(InspectorSocket* socket, const std::string& path);
uv_sem_t start_sem_;
ConditionVariable incoming_message_cond_;
Mutex state_lock_;
uv_thread_t thread_;
uv_loop_t child_loop_;
int port_;
bool wait_;
bool shutting_down_;
State state_;
node::Environment* parent_env_;
uv_async_t* data_written_;
uv_async_t io_thread_req_;
InspectorSocket* client_socket_;
V8NodeInspector* inspector_;
v8::Platform* platform_;
MessageQueue incoming_message_queue_;
MessageQueue outgoing_message_queue_;
bool dispatching_messages_;
int frontend_session_id_;
int backend_session_id_;
std::string script_name_;
std::string script_path_;
const std::string id_;
friend class ChannelImpl;
friend class DispatchOnInspectorBackendTask;
friend class SetConnectedTask;
friend class V8NodeInspector;
friend void InterruptCallback(v8::Isolate*, void* agent);
friend void DataCallback(uv_stream_t* stream, ssize_t read,
const uv_buf_t* buf);
};
void InterruptCallback(v8::Isolate*, void* agent) {
static_cast<AgentImpl*>(agent)->DispatchMessages();
}
void DataCallback(uv_stream_t* stream, ssize_t read, const uv_buf_t* buf) {
InspectorSocket* socket = inspector_from_stream(stream);
static_cast<AgentImpl*>(socket->data)->OnRemoteDataIO(socket, read, buf);
}
class DispatchOnInspectorBackendTask : public v8::Task {
public:
explicit DispatchOnInspectorBackendTask(AgentImpl* agent) : agent_(agent) {}
void Run() override {
agent_->DispatchMessages();
}
private:
AgentImpl* agent_;
};
class ChannelImpl final : public v8_inspector::V8Inspector::Channel {
public:
explicit ChannelImpl(AgentImpl* agent): agent_(agent) {}
virtual ~ChannelImpl() {}
private:
void sendProtocolResponse(int callId, const StringView& message) override {
sendMessageToFrontend(message);
}
void sendProtocolNotification(const StringView& message) override {
sendMessageToFrontend(message);
}
void flushProtocolNotifications() override { }
void sendMessageToFrontend(const StringView& message) {
agent_->Write(agent_->frontend_session_id_, message);
}
AgentImpl* const agent_;
};
// Used in V8NodeInspector::currentTimeMS() below.
#define NANOS_PER_MSEC 1000000
using V8Inspector = v8_inspector::V8Inspector;
class V8NodeInspector : public v8_inspector::V8InspectorClient {
public:
V8NodeInspector(AgentImpl* agent, node::Environment* env,
v8::Platform* platform)
: agent_(agent),
env_(env),
platform_(platform),
terminated_(false),
running_nested_loop_(false),
inspector_(V8Inspector::create(env->isolate(), this)) {
const uint8_t CONTEXT_NAME[] = "Node.js Main Context";
StringView context_name(CONTEXT_NAME, sizeof(CONTEXT_NAME) - 1);
v8_inspector::V8ContextInfo info(env->context(), 1, context_name);
inspector_->contextCreated(info);
}
void runMessageLoopOnPause(int context_group_id) override {
if (running_nested_loop_)
return;
terminated_ = false;
running_nested_loop_ = true;
while (!terminated_) {
agent_->WaitForFrontendMessage();
while (v8::platform::PumpMessageLoop(platform_, env_->isolate()))
{}
}
terminated_ = false;
running_nested_loop_ = false;
}
double currentTimeMS() override {
return uv_hrtime() * 1.0 / NANOS_PER_MSEC;
}
void quitMessageLoopOnPause() override {
terminated_ = true;
}
void connectFrontend() {
session_ = inspector_->connect(1, new ChannelImpl(agent_), StringView());
}
void disconnectFrontend() {
session_.reset();
}
void dispatchMessageFromFrontend(const StringView& message) {
CHECK(session_);
session_->dispatchProtocolMessage(message);
}
v8::Local<v8::Context> ensureDefaultContextInGroup(int contextGroupId)
override {
return env_->context();
}
V8Inspector* inspector() {
return inspector_.get();
}
private:
AgentImpl* agent_;
node::Environment* env_;
v8::Platform* platform_;
bool terminated_;
bool running_nested_loop_;
std::unique_ptr<V8Inspector> inspector_;
std::unique_ptr<v8_inspector::V8InspectorSession> session_;
};
AgentImpl::AgentImpl(Environment* env) : port_(0),
wait_(false),
shutting_down_(false),
state_(State::kNew),
parent_env_(env),
data_written_(new uv_async_t()),
client_socket_(nullptr),
inspector_(nullptr),
platform_(nullptr),
dispatching_messages_(false),
frontend_session_id_(0),
backend_session_id_(0),
id_(GenerateID()) {
CHECK_EQ(0, uv_sem_init(&start_sem_, 0));
memset(&io_thread_req_, 0, sizeof(io_thread_req_));
CHECK_EQ(0, uv_async_init(env->event_loop(), data_written_, nullptr));
uv_unref(reinterpret_cast<uv_handle_t*>(data_written_));
}
AgentImpl::~AgentImpl() {
auto close_cb = [](uv_handle_t* handle) {
delete reinterpret_cast<uv_async_t*>(handle);
};
uv_close(reinterpret_cast<uv_handle_t*>(data_written_), close_cb);
data_written_ = nullptr;
}
void InspectorConsoleCall(const v8::FunctionCallbackInfo<v8::Value>& info) {
v8::Isolate* isolate = info.GetIsolate();
v8::Local<v8::Context> context = isolate->GetCurrentContext();
CHECK(info.Data()->IsArray());
v8::Local<v8::Array> args = info.Data().As<v8::Array>();
CHECK_EQ(args->Length(), 3);
v8::Local<v8::Value> inspector_method =
args->Get(context, 0).ToLocalChecked();
CHECK(inspector_method->IsFunction());
v8::Local<v8::Value> node_method =
args->Get(context, 1).ToLocalChecked();
CHECK(node_method->IsFunction());
v8::Local<v8::Value> config_value =
args->Get(context, 2).ToLocalChecked();
CHECK(config_value->IsObject());
v8::Local<v8::Object> config_object = config_value.As<v8::Object>();
std::vector<v8::Local<v8::Value>> call_args(info.Length());
for (int i = 0; i < info.Length(); ++i) {
call_args[i] = info[i];
}
v8::Local<v8::String> in_call_key = OneByteString(isolate, "in_call");
bool in_call = config_object->Has(context, in_call_key).FromMaybe(false);
if (!in_call) {
CHECK(config_object->Set(context,
in_call_key,
v8::True(isolate)).FromJust());
CHECK(!inspector_method.As<v8::Function>()->Call(
context,
info.Holder(),
call_args.size(),
call_args.data()).IsEmpty());
}
v8::TryCatch try_catch(info.GetIsolate());
static_cast<void>(node_method.As<v8::Function>()->Call(context,
info.Holder(),
call_args.size(),
call_args.data()));
CHECK(config_object->Delete(context, in_call_key).FromJust());
if (try_catch.HasCaught())
try_catch.ReThrow();
}
void InspectorWrapConsoleCall(const v8::FunctionCallbackInfo<v8::Value>& args) {
Environment* env = Environment::GetCurrent(args);
if (args.Length() != 3 || !args[0]->IsFunction() ||
!args[1]->IsFunction() || !args[2]->IsObject()) {
return env->ThrowError("inspector.wrapConsoleCall takes exactly 3 "
"arguments: two functions and an object.");
}
v8::Local<v8::Array> array = v8::Array::New(env->isolate(), args.Length());
CHECK(array->Set(env->context(), 0, args[0]).FromJust());
CHECK(array->Set(env->context(), 1, args[1]).FromJust());
CHECK(array->Set(env->context(), 2, args[2]).FromJust());
args.GetReturnValue().Set(v8::Function::New(env->context(),
InspectorConsoleCall,
array).ToLocalChecked());
}
bool AgentImpl::Start(v8::Platform* platform, const char* path,
int port, bool wait) {
auto env = parent_env_;
inspector_ = new V8NodeInspector(this, env, platform);
platform_ = platform;
if (path != nullptr)
script_name_ = path;
InstallInspectorOnProcess();
int err = uv_loop_init(&child_loop_);
CHECK_EQ(err, 0);
port_ = port;
wait_ = wait;
err = uv_thread_create(&thread_, AgentImpl::ThreadCbIO, this);
CHECK_EQ(err, 0);
uv_sem_wait(&start_sem_);
if (state_ == State::kError) {
Stop();
return false;
}
state_ = State::kAccepting;
if (wait) {
DispatchMessages();
}
return true;
}
void AgentImpl::Stop() {
int err = uv_thread_join(&thread_);
CHECK_EQ(err, 0);
delete inspector_;
}
bool AgentImpl::IsStarted() {
return !!platform_;
}
void AgentImpl::WaitForDisconnect() {
if (state_ == State::kConnected) {
shutting_down_ = true;
fprintf(stderr, "Waiting for the debugger to disconnect...\n");
fflush(stderr);
inspector_->runMessageLoopOnPause(0);
}
}
#define READONLY_PROPERTY(obj, str, var) \
do { \
obj->DefineOwnProperty(env->context(), \
OneByteString(env->isolate(), str), \
var, \
v8::ReadOnly).FromJust(); \
} while (0)
void AgentImpl::InstallInspectorOnProcess() {
auto env = parent_env_;
v8::Local<v8::Object> process = env->process_object();
v8::Local<v8::Object> inspector = v8::Object::New(env->isolate());
READONLY_PROPERTY(process, "inspector", inspector);
env->SetMethod(inspector, "wrapConsoleCall", InspectorWrapConsoleCall);
}
std::unique_ptr<StringBuffer> ToProtocolString(v8::Local<v8::Value> value) {
if (value.IsEmpty() || value->IsNull() || value->IsUndefined() ||
!value->IsString()) {
return StringBuffer::create(StringView());
}
v8::Local<v8::String> string_value = v8::Local<v8::String>::Cast(value);
size_t len = string_value->Length();
std::basic_string<uint16_t> buffer(len, '\0');
string_value->Write(&buffer[0], 0, len);
return StringBuffer::create(StringView(buffer.data(), len));
}
void AgentImpl::FatalException(v8::Local<v8::Value> error,
v8::Local<v8::Message> message) {
if (!IsStarted())
return;
auto env = parent_env_;
v8::Local<v8::Context> context = env->context();
int script_id = message->GetScriptOrigin().ScriptID()->Value();
v8::Local<v8::StackTrace> stack_trace = message->GetStackTrace();
if (!stack_trace.IsEmpty() &&
stack_trace->GetFrameCount() > 0 &&
script_id == stack_trace->GetFrame(0)->GetScriptId()) {
script_id = 0;
}
const uint8_t DETAILS[] = "Uncaught";
inspector_->inspector()->exceptionThrown(
context,
StringView(DETAILS, sizeof(DETAILS) - 1),
error,
ToProtocolString(message->Get())->string(),
ToProtocolString(message->GetScriptResourceName())->string(),
message->GetLineNumber(context).FromMaybe(0),
message->GetStartColumn(context).FromMaybe(0),
inspector_->inspector()->createStackTrace(stack_trace),
script_id);
WaitForDisconnect();
}
// static
void AgentImpl::ThreadCbIO(void* agent) {
static_cast<AgentImpl*>(agent)->WorkerRunIO();
}
// static
void AgentImpl::OnSocketConnectionIO(uv_stream_t* server, int status) {
if (status == 0) {
InspectorSocket* socket = new InspectorSocket();
socket->data = server->data;
if (inspector_accept(server, socket,
AgentImpl::OnInspectorHandshakeIO) != 0) {
delete socket;
}
}
}
// static
bool AgentImpl::OnInspectorHandshakeIO(InspectorSocket* socket,
enum inspector_handshake_event state,
const std::string& path) {
AgentImpl* agent = static_cast<AgentImpl*>(socket->data);
switch (state) {
case kInspectorHandshakeHttpGet:
return agent->RespondToGet(socket, path);
case kInspectorHandshakeUpgrading:
return path.length() == agent->id_.length() + 1 &&
path.find(agent->id_) == 1;
case kInspectorHandshakeUpgraded:
agent->OnInspectorConnectionIO(socket);
return true;
case kInspectorHandshakeFailed:
delete socket;
return false;
default:
UNREACHABLE();
return false;
}
}
void AgentImpl::OnRemoteDataIO(InspectorSocket* socket,
ssize_t read,
const uv_buf_t* buf) {
if (read > 0) {
// TODO(pfeldman): Instead of blocking execution while debugger
// engages, node should wait for the run callback from the remote client
// and initiate its startup. This is a change to node.cc that should be
// upstreamed separately.
if (wait_) {
std::string message(buf->base, read);
if (message.find("\"Runtime.runIfWaitingForDebugger\"") !=
std::string::npos) {
wait_ = false;
uv_sem_post(&start_sem_);
}
}
PostIncomingMessage(buf->base, read);
} else {
// EOF
if (client_socket_ == socket) {
client_socket_ = nullptr;
PostIncomingMessage(TAG_DISCONNECT, sizeof(TAG_DISCONNECT) - 1);
}
DisconnectAndDisposeIO(socket);
}
if (buf) {
delete[] buf->base;
}
}
void AgentImpl::SendListResponse(InspectorSocket* socket) {
std::map<std::string, std::string> response;
response["description"] = "node.js instance";
response["faviconUrl"] = "https://nodejs.org/static/favicon.ico";
response["id"] = id_;
response["title"] = script_name_.empty() ? GetProcessTitle() : script_name_;
Escape(&response["title"]);
response["type"] = "node";
// This attribute value is a "best effort" URL that is passed as a JSON
// string. It is not guaranteed to resolve to a valid resource.
response["url"] = "file://" + script_path_;
Escape(&response["url"]);
if (!client_socket_) {
std::string address = GetWsUrl(port_, id_);
std::ostringstream frontend_url;
frontend_url << "chrome-devtools://devtools/bundled";
frontend_url << "/inspector.html?experiments=true&v8only=true&ws=";
frontend_url << address;
response["devtoolsFrontendUrl"] += frontend_url.str();
response["webSocketDebuggerUrl"] = "ws://" + address;
}
SendHttpResponse(socket, MapToString(response));
}
bool AgentImpl::RespondToGet(InspectorSocket* socket, const std::string& path) {
const char* command = match_path_segment(path.c_str(), "/json");
if (command == nullptr)
return false;
if (match_path_segment(command, "list") || command[0] == '\0') {
SendListResponse(socket);
return true;
} else if (match_path_segment(command, "protocol")) {
SendProtocolJson(socket);
return true;
} else if (match_path_segment(command, "version")) {
SendVersionResponse(socket);
return true;
} else if (const char* pid = match_path_segment(command, "activate")) {
if (pid != id_)
return false;
SendHttpResponse(socket, "Target activated");
return true;
}
return false;
}
// static
void AgentImpl::WriteCbIO(uv_async_t* async) {
AgentImpl* agent = static_cast<AgentImpl*>(async->data);
InspectorSocket* socket = agent->client_socket_;
if (socket) {
MessageQueue outgoing_messages;
agent->SwapBehindLock(&agent->outgoing_message_queue_, &outgoing_messages);
for (const MessageQueue::value_type& outgoing : outgoing_messages) {
if (outgoing.first == agent->frontend_session_id_) {
StringView message = outgoing.second->string();
std::string utf8Message = StringViewToUtf8(message);
inspector_write(socket, utf8Message.c_str(), utf8Message.length());
}
}
}
}
void AgentImpl::WorkerRunIO() {
sockaddr_in addr;
uv_tcp_t server;
int err = uv_loop_init(&child_loop_);
CHECK_EQ(err, 0);
err = uv_async_init(&child_loop_, &io_thread_req_, AgentImpl::WriteCbIO);
CHECK_EQ(err, 0);
io_thread_req_.data = this;
if (!script_name_.empty()) {
uv_fs_t req;
if (0 == uv_fs_realpath(&child_loop_, &req, script_name_.c_str(), nullptr))
script_path_ = std::string(reinterpret_cast<char*>(req.ptr));
uv_fs_req_cleanup(&req);
}
uv_tcp_init(&child_loop_, &server);
uv_ip4_addr("0.0.0.0", port_, &addr);
server.data = this;
err = uv_tcp_bind(&server,
reinterpret_cast<const struct sockaddr*>(&addr), 0);
if (err == 0) {
err = uv_listen(reinterpret_cast<uv_stream_t*>(&server), 1,
OnSocketConnectionIO);
}
if (err != 0) {
fprintf(stderr, "Unable to open devtools socket: %s\n", uv_strerror(err));
state_ = State::kError; // Safe, main thread is waiting on semaphore
uv_close(reinterpret_cast<uv_handle_t*>(&io_thread_req_), nullptr);
uv_close(reinterpret_cast<uv_handle_t*>(&server), nullptr);
uv_loop_close(&child_loop_);
uv_sem_post(&start_sem_);
return;
}
PrintDebuggerReadyMessage(port_, id_);
if (!wait_) {
uv_sem_post(&start_sem_);
}
uv_run(&child_loop_, UV_RUN_DEFAULT);
uv_close(reinterpret_cast<uv_handle_t*>(&io_thread_req_), nullptr);
uv_close(reinterpret_cast<uv_handle_t*>(&server), nullptr);
DisconnectAndDisposeIO(client_socket_);
uv_run(&child_loop_, UV_RUN_NOWAIT);
err = uv_loop_close(&child_loop_);
CHECK_EQ(err, 0);
}
bool AgentImpl::AppendMessage(MessageQueue* queue, int session_id,
std::unique_ptr<StringBuffer> buffer) {
Mutex::ScopedLock scoped_lock(state_lock_);
bool trigger_pumping = queue->empty();
queue->push_back(std::make_pair(session_id, std::move(buffer)));
return trigger_pumping;
}
void AgentImpl::SwapBehindLock(MessageQueue* vector1, MessageQueue* vector2) {
Mutex::ScopedLock scoped_lock(state_lock_);
vector1->swap(*vector2);
}
void AgentImpl::PostIncomingMessage(const char* message, size_t len) {
if (AppendMessage(&incoming_message_queue_, frontend_session_id_,
Utf8ToStringView(message, len))) {
v8::Isolate* isolate = parent_env_->isolate();
platform_->CallOnForegroundThread(isolate,
new DispatchOnInspectorBackendTask(this));
isolate->RequestInterrupt(InterruptCallback, this);
uv_async_send(data_written_);
}
NotifyMessageReceived();
}
void AgentImpl::WaitForFrontendMessage() {
Mutex::ScopedLock scoped_lock(state_lock_);
if (incoming_message_queue_.empty())
incoming_message_cond_.Wait(scoped_lock);
}
void AgentImpl::NotifyMessageReceived() {
Mutex::ScopedLock scoped_lock(state_lock_);
incoming_message_cond_.Broadcast(scoped_lock);
}
void AgentImpl::OnInspectorConnectionIO(InspectorSocket* socket) {
if (client_socket_) {
DisconnectAndDisposeIO(socket);
return;
}
client_socket_ = socket;
inspector_read_start(socket, OnBufferAlloc, DataCallback);
frontend_session_id_++;
PostIncomingMessage(TAG_CONNECT, sizeof(TAG_CONNECT) - 1);
}
void AgentImpl::DispatchMessages() {
// This function can be reentered if there was an incoming message while
// V8 was processing another inspector request (e.g. if the user is
// evaluating a long-running JS code snippet). This can happen only at
// specific points (e.g. the lines that call inspector_ methods)
if (dispatching_messages_)
return;
dispatching_messages_ = true;
MessageQueue tasks;
do {
tasks.clear();
SwapBehindLock(&incoming_message_queue_, &tasks);
for (const MessageQueue::value_type& pair : tasks) {
StringView message = pair.second->string();
std::string tag;
if (message.length() == sizeof(TAG_CONNECT) - 1 ||
message.length() == sizeof(TAG_DISCONNECT) - 1) {
tag = StringViewToUtf8(message);
}
if (tag == TAG_CONNECT) {
CHECK_EQ(State::kAccepting, state_);
backend_session_id_++;
state_ = State::kConnected;
fprintf(stderr, "Debugger attached.\n");
inspector_->connectFrontend();
} else if (tag == TAG_DISCONNECT) {
CHECK_EQ(State::kConnected, state_);
if (shutting_down_) {
state_ = State::kDone;
} else {
PrintDebuggerReadyMessage(port_, id_);
state_ = State::kAccepting;
}
inspector_->quitMessageLoopOnPause();
inspector_->disconnectFrontend();
} else {
inspector_->dispatchMessageFromFrontend(message);
}
}
} while (!tasks.empty());
uv_async_send(data_written_);
dispatching_messages_ = false;
}
void AgentImpl::Write(int session_id, const StringView& inspector_message) {
AppendMessage(&outgoing_message_queue_, session_id,
StringBuffer::create(inspector_message));
int err = uv_async_send(&io_thread_req_);
CHECK_EQ(0, err);
}
// Exported class Agent
Agent::Agent(node::Environment* env) : impl(new AgentImpl(env)) {}
Agent::~Agent() {
delete impl;
}
bool Agent::Start(v8::Platform* platform, const char* path,
int port, bool wait) {
return impl->Start(platform, path, port, wait);
}
void Agent::Stop() {
impl->Stop();
}
bool Agent::IsStarted() {
return impl->IsStarted();
}
bool Agent::IsConnected() {
return impl->IsConnected();
}
void Agent::WaitForDisconnect() {
impl->WaitForDisconnect();
}
void Agent::FatalException(v8::Local<v8::Value> error,
v8::Local<v8::Message> message) {
impl->FatalException(error, message);
}
} // namespace inspector
} // namespace node