1 // Copyright 2016 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/inspector/v8-inspector-session-impl.h"
6
7 #include "../../third_party/inspector_protocol/crdtp/cbor.h"
8 #include "../../third_party/inspector_protocol/crdtp/dispatch.h"
9 #include "../../third_party/inspector_protocol/crdtp/json.h"
10 #include "src/base/logging.h"
11 #include "src/base/macros.h"
12 #include "src/inspector/injected-script.h"
13 #include "src/inspector/inspected-context.h"
14 #include "src/inspector/protocol/Protocol.h"
15 #include "src/inspector/remote-object-id.h"
16 #include "src/inspector/search-util.h"
17 #include "src/inspector/string-util.h"
18 #include "src/inspector/v8-console-agent-impl.h"
19 #include "src/inspector/v8-debugger-agent-impl.h"
20 #include "src/inspector/v8-debugger.h"
21 #include "src/inspector/v8-heap-profiler-agent-impl.h"
22 #include "src/inspector/v8-inspector-impl.h"
23 #include "src/inspector/v8-profiler-agent-impl.h"
24 #include "src/inspector/v8-runtime-agent-impl.h"
25 #include "src/inspector/v8-schema-agent-impl.h"
26
27 namespace v8_inspector {
28 namespace {
29 using v8_crdtp::span;
30 using v8_crdtp::SpanFrom;
31 using v8_crdtp::Status;
32 using v8_crdtp::cbor::CheckCBORMessage;
33 using v8_crdtp::json::ConvertCBORToJSON;
34 using v8_crdtp::json::ConvertJSONToCBOR;
35
IsCBORMessage(StringView msg)36 bool IsCBORMessage(StringView msg) {
37 return msg.is8Bit() && msg.length() >= 2 && msg.characters8()[0] == 0xd8 &&
38 msg.characters8()[1] == 0x5a;
39 }
40
ConvertToCBOR(StringView state,std::vector<uint8_t> * cbor)41 Status ConvertToCBOR(StringView state, std::vector<uint8_t>* cbor) {
42 return state.is8Bit()
43 ? ConvertJSONToCBOR(
44 span<uint8_t>(state.characters8(), state.length()), cbor)
45 : ConvertJSONToCBOR(
46 span<uint16_t>(state.characters16(), state.length()), cbor);
47 }
48
ParseState(StringView state)49 std::unique_ptr<protocol::DictionaryValue> ParseState(StringView state) {
50 std::vector<uint8_t> converted;
51 span<uint8_t> cbor;
52 if (IsCBORMessage(state))
53 cbor = span<uint8_t>(state.characters8(), state.length());
54 else if (ConvertToCBOR(state, &converted).ok())
55 cbor = SpanFrom(converted);
56 if (!cbor.empty()) {
57 std::unique_ptr<protocol::Value> value =
58 protocol::Value::parseBinary(cbor.data(), cbor.size());
59 if (value) return protocol::DictionaryValue::cast(std::move(value));
60 }
61 return protocol::DictionaryValue::create();
62 }
63 } // namespace
64
65 // static
canDispatchMethod(StringView method)66 bool V8InspectorSession::canDispatchMethod(StringView method) {
67 return stringViewStartsWith(method,
68 protocol::Runtime::Metainfo::commandPrefix) ||
69 stringViewStartsWith(method,
70 protocol::Debugger::Metainfo::commandPrefix) ||
71 stringViewStartsWith(method,
72 protocol::Profiler::Metainfo::commandPrefix) ||
73 stringViewStartsWith(
74 method, protocol::HeapProfiler::Metainfo::commandPrefix) ||
75 stringViewStartsWith(method,
76 protocol::Console::Metainfo::commandPrefix) ||
77 stringViewStartsWith(method,
78 protocol::Schema::Metainfo::commandPrefix);
79 }
80
81 // static
executionContextId(v8::Local<v8::Context> context)82 int V8ContextInfo::executionContextId(v8::Local<v8::Context> context) {
83 return InspectedContext::contextId(context);
84 }
85
create(V8InspectorImpl * inspector,int contextGroupId,int sessionId,V8Inspector::Channel * channel,StringView state)86 std::unique_ptr<V8InspectorSessionImpl> V8InspectorSessionImpl::create(
87 V8InspectorImpl* inspector, int contextGroupId, int sessionId,
88 V8Inspector::Channel* channel, StringView state) {
89 return std::unique_ptr<V8InspectorSessionImpl>(new V8InspectorSessionImpl(
90 inspector, contextGroupId, sessionId, channel, state));
91 }
92
V8InspectorSessionImpl(V8InspectorImpl * inspector,int contextGroupId,int sessionId,V8Inspector::Channel * channel,StringView savedState)93 V8InspectorSessionImpl::V8InspectorSessionImpl(V8InspectorImpl* inspector,
94 int contextGroupId,
95 int sessionId,
96 V8Inspector::Channel* channel,
97 StringView savedState)
98 : m_contextGroupId(contextGroupId),
99 m_sessionId(sessionId),
100 m_inspector(inspector),
101 m_channel(channel),
102 m_customObjectFormatterEnabled(false),
103 m_dispatcher(this),
104 m_state(ParseState(savedState)),
105 m_runtimeAgent(nullptr),
106 m_debuggerAgent(nullptr),
107 m_heapProfilerAgent(nullptr),
108 m_profilerAgent(nullptr),
109 m_consoleAgent(nullptr),
110 m_schemaAgent(nullptr) {
111 m_state->getBoolean("use_binary_protocol", &use_binary_protocol_);
112
113 m_runtimeAgent.reset(new V8RuntimeAgentImpl(
114 this, this, agentState(protocol::Runtime::Metainfo::domainName)));
115 protocol::Runtime::Dispatcher::wire(&m_dispatcher, m_runtimeAgent.get());
116
117 m_debuggerAgent.reset(new V8DebuggerAgentImpl(
118 this, this, agentState(protocol::Debugger::Metainfo::domainName)));
119 protocol::Debugger::Dispatcher::wire(&m_dispatcher, m_debuggerAgent.get());
120
121 m_profilerAgent.reset(new V8ProfilerAgentImpl(
122 this, this, agentState(protocol::Profiler::Metainfo::domainName)));
123 protocol::Profiler::Dispatcher::wire(&m_dispatcher, m_profilerAgent.get());
124
125 m_heapProfilerAgent.reset(new V8HeapProfilerAgentImpl(
126 this, this, agentState(protocol::HeapProfiler::Metainfo::domainName)));
127 protocol::HeapProfiler::Dispatcher::wire(&m_dispatcher,
128 m_heapProfilerAgent.get());
129
130 m_consoleAgent.reset(new V8ConsoleAgentImpl(
131 this, this, agentState(protocol::Console::Metainfo::domainName)));
132 protocol::Console::Dispatcher::wire(&m_dispatcher, m_consoleAgent.get());
133
134 m_schemaAgent.reset(new V8SchemaAgentImpl(
135 this, this, agentState(protocol::Schema::Metainfo::domainName)));
136 protocol::Schema::Dispatcher::wire(&m_dispatcher, m_schemaAgent.get());
137
138 if (savedState.length()) {
139 m_runtimeAgent->restore();
140 m_debuggerAgent->restore();
141 m_heapProfilerAgent->restore();
142 m_profilerAgent->restore();
143 m_consoleAgent->restore();
144 }
145 }
146
~V8InspectorSessionImpl()147 V8InspectorSessionImpl::~V8InspectorSessionImpl() {
148 discardInjectedScripts();
149 m_consoleAgent->disable();
150 m_profilerAgent->disable();
151 m_heapProfilerAgent->disable();
152 m_debuggerAgent->disable();
153 m_runtimeAgent->disable();
154 m_inspector->disconnect(this);
155 }
156
agentState(const String16 & name)157 protocol::DictionaryValue* V8InspectorSessionImpl::agentState(
158 const String16& name) {
159 protocol::DictionaryValue* state = m_state->getObject(name);
160 if (!state) {
161 std::unique_ptr<protocol::DictionaryValue> newState =
162 protocol::DictionaryValue::create();
163 state = newState.get();
164 m_state->setObject(name, std::move(newState));
165 }
166 return state;
167 }
168
serializeForFrontend(std::unique_ptr<protocol::Serializable> message)169 std::unique_ptr<StringBuffer> V8InspectorSessionImpl::serializeForFrontend(
170 std::unique_ptr<protocol::Serializable> message) {
171 std::vector<uint8_t> cbor = message->Serialize();
172 DCHECK(CheckCBORMessage(SpanFrom(cbor)).ok());
173 if (use_binary_protocol_) return StringBufferFrom(std::move(cbor));
174 std::vector<uint8_t> json;
175 Status status = ConvertCBORToJSON(SpanFrom(cbor), &json);
176 DCHECK(status.ok());
177 USE(status);
178 // TODO(johannes): It should be OK to make a StringBuffer from |json|
179 // directly, since it's 7 Bit US-ASCII with anything else escaped.
180 // However it appears that the Node.js tests (or perhaps even production)
181 // assume that the StringBuffer is 16 Bit. It probably accesses
182 // characters16() somehwere without checking is8Bit. Until it's fixed
183 // we take a detour via String16 which makes the StringBuffer 16 bit.
184 String16 string16(reinterpret_cast<const char*>(json.data()), json.size());
185 return StringBufferFrom(std::move(string16));
186 }
187
SendProtocolResponse(int callId,std::unique_ptr<protocol::Serializable> message)188 void V8InspectorSessionImpl::SendProtocolResponse(
189 int callId, std::unique_ptr<protocol::Serializable> message) {
190 m_channel->sendResponse(callId, serializeForFrontend(std::move(message)));
191 }
192
SendProtocolNotification(std::unique_ptr<protocol::Serializable> message)193 void V8InspectorSessionImpl::SendProtocolNotification(
194 std::unique_ptr<protocol::Serializable> message) {
195 m_channel->sendNotification(serializeForFrontend(std::move(message)));
196 }
197
FallThrough(int callId,const v8_crdtp::span<uint8_t> method,v8_crdtp::span<uint8_t> message)198 void V8InspectorSessionImpl::FallThrough(int callId,
199 const v8_crdtp::span<uint8_t> method,
200 v8_crdtp::span<uint8_t> message) {
201 // There's no other layer to handle the command.
202 UNREACHABLE();
203 }
204
FlushProtocolNotifications()205 void V8InspectorSessionImpl::FlushProtocolNotifications() {
206 m_channel->flushProtocolNotifications();
207 }
208
reset()209 void V8InspectorSessionImpl::reset() {
210 m_debuggerAgent->reset();
211 m_runtimeAgent->reset();
212 discardInjectedScripts();
213 }
214
discardInjectedScripts()215 void V8InspectorSessionImpl::discardInjectedScripts() {
216 m_inspectedObjects.clear();
217 int sessionId = m_sessionId;
218 m_inspector->forEachContext(m_contextGroupId,
219 [&sessionId](InspectedContext* context) {
220 context->discardInjectedScript(sessionId);
221 });
222 }
223
findInjectedScript(int contextId,InjectedScript * & injectedScript)224 Response V8InspectorSessionImpl::findInjectedScript(
225 int contextId, InjectedScript*& injectedScript) {
226 injectedScript = nullptr;
227 InspectedContext* context =
228 m_inspector->getContext(m_contextGroupId, contextId);
229 if (!context)
230 return Response::ServerError("Cannot find context with specified id");
231 injectedScript = context->getInjectedScript(m_sessionId);
232 if (!injectedScript) {
233 injectedScript = context->createInjectedScript(m_sessionId);
234 if (m_customObjectFormatterEnabled)
235 injectedScript->setCustomObjectFormatterEnabled(true);
236 }
237 return Response::Success();
238 }
239
findInjectedScript(RemoteObjectIdBase * objectId,InjectedScript * & injectedScript)240 Response V8InspectorSessionImpl::findInjectedScript(
241 RemoteObjectIdBase* objectId, InjectedScript*& injectedScript) {
242 if (objectId->isolateId() != m_inspector->isolateId())
243 return Response::ServerError("Cannot find context with specified id");
244 return findInjectedScript(objectId->contextId(), injectedScript);
245 }
246
releaseObjectGroup(StringView objectGroup)247 void V8InspectorSessionImpl::releaseObjectGroup(StringView objectGroup) {
248 releaseObjectGroup(toString16(objectGroup));
249 }
250
releaseObjectGroup(const String16 & objectGroup)251 void V8InspectorSessionImpl::releaseObjectGroup(const String16& objectGroup) {
252 int sessionId = m_sessionId;
253 m_inspector->forEachContext(
254 m_contextGroupId, [&objectGroup, &sessionId](InspectedContext* context) {
255 InjectedScript* injectedScript = context->getInjectedScript(sessionId);
256 if (injectedScript) injectedScript->releaseObjectGroup(objectGroup);
257 });
258 }
259
unwrapObject(std::unique_ptr<StringBuffer> * error,StringView objectId,v8::Local<v8::Value> * object,v8::Local<v8::Context> * context,std::unique_ptr<StringBuffer> * objectGroup)260 bool V8InspectorSessionImpl::unwrapObject(
261 std::unique_ptr<StringBuffer>* error, StringView objectId,
262 v8::Local<v8::Value>* object, v8::Local<v8::Context>* context,
263 std::unique_ptr<StringBuffer>* objectGroup) {
264 String16 objectGroupString;
265 Response response = unwrapObject(toString16(objectId), object, context,
266 objectGroup ? &objectGroupString : nullptr);
267 if (response.IsError()) {
268 if (error) {
269 const std::string& msg = response.Message();
270 *error = StringBufferFrom(String16::fromUTF8(msg.data(), msg.size()));
271 }
272 return false;
273 }
274 if (objectGroup)
275 *objectGroup = StringBufferFrom(std::move(objectGroupString));
276 return true;
277 }
278
unwrapObject(const String16 & objectId,v8::Local<v8::Value> * object,v8::Local<v8::Context> * context,String16 * objectGroup)279 Response V8InspectorSessionImpl::unwrapObject(const String16& objectId,
280 v8::Local<v8::Value>* object,
281 v8::Local<v8::Context>* context,
282 String16* objectGroup) {
283 std::unique_ptr<RemoteObjectId> remoteId;
284 Response response = RemoteObjectId::parse(objectId, &remoteId);
285 if (!response.IsSuccess()) return response;
286 InjectedScript* injectedScript = nullptr;
287 response = findInjectedScript(remoteId.get(), injectedScript);
288 if (!response.IsSuccess()) return response;
289 response = injectedScript->findObject(*remoteId, object);
290 if (!response.IsSuccess()) return response;
291 *context = injectedScript->context()->context();
292 if (objectGroup) *objectGroup = injectedScript->objectGroupName(*remoteId);
293 return Response::Success();
294 }
295
296 std::unique_ptr<protocol::Runtime::API::RemoteObject>
wrapObject(v8::Local<v8::Context> context,v8::Local<v8::Value> value,StringView groupName,bool generatePreview)297 V8InspectorSessionImpl::wrapObject(v8::Local<v8::Context> context,
298 v8::Local<v8::Value> value,
299 StringView groupName, bool generatePreview) {
300 return wrapObject(context, value, toString16(groupName), generatePreview);
301 }
302
303 std::unique_ptr<protocol::Runtime::RemoteObject>
wrapObject(v8::Local<v8::Context> context,v8::Local<v8::Value> value,const String16 & groupName,bool generatePreview)304 V8InspectorSessionImpl::wrapObject(v8::Local<v8::Context> context,
305 v8::Local<v8::Value> value,
306 const String16& groupName,
307 bool generatePreview) {
308 InjectedScript* injectedScript = nullptr;
309 findInjectedScript(InspectedContext::contextId(context), injectedScript);
310 if (!injectedScript) return nullptr;
311 std::unique_ptr<protocol::Runtime::RemoteObject> result;
312 injectedScript->wrapObject(
313 value, groupName,
314 generatePreview ? WrapMode::kWithPreview : WrapMode::kNoPreview, &result);
315 return result;
316 }
317
318 std::unique_ptr<protocol::Runtime::RemoteObject>
wrapTable(v8::Local<v8::Context> context,v8::Local<v8::Object> table,v8::MaybeLocal<v8::Array> columns)319 V8InspectorSessionImpl::wrapTable(v8::Local<v8::Context> context,
320 v8::Local<v8::Object> table,
321 v8::MaybeLocal<v8::Array> columns) {
322 InjectedScript* injectedScript = nullptr;
323 findInjectedScript(InspectedContext::contextId(context), injectedScript);
324 if (!injectedScript) return nullptr;
325 return injectedScript->wrapTable(table, columns);
326 }
327
setCustomObjectFormatterEnabled(bool enabled)328 void V8InspectorSessionImpl::setCustomObjectFormatterEnabled(bool enabled) {
329 m_customObjectFormatterEnabled = enabled;
330 int sessionId = m_sessionId;
331 m_inspector->forEachContext(
332 m_contextGroupId, [&enabled, &sessionId](InspectedContext* context) {
333 InjectedScript* injectedScript = context->getInjectedScript(sessionId);
334 if (injectedScript)
335 injectedScript->setCustomObjectFormatterEnabled(enabled);
336 });
337 }
338
reportAllContexts(V8RuntimeAgentImpl * agent)339 void V8InspectorSessionImpl::reportAllContexts(V8RuntimeAgentImpl* agent) {
340 m_inspector->forEachContext(m_contextGroupId,
341 [&agent](InspectedContext* context) {
342 agent->reportExecutionContextCreated(context);
343 });
344 }
345
dispatchProtocolMessage(StringView message)346 void V8InspectorSessionImpl::dispatchProtocolMessage(StringView message) {
347 using v8_crdtp::span;
348 using v8_crdtp::SpanFrom;
349 span<uint8_t> cbor;
350 std::vector<uint8_t> converted_cbor;
351 if (IsCBORMessage(message)) {
352 use_binary_protocol_ = true;
353 m_state->setBoolean("use_binary_protocol", true);
354 cbor = span<uint8_t>(message.characters8(), message.length());
355 } else {
356 // We're ignoring the return value of the conversion function
357 // intentionally. It means the |parsed_message| below will be nullptr.
358 auto status = ConvertToCBOR(message, &converted_cbor);
359 if (!status.ok()) {
360 m_channel->sendNotification(
361 serializeForFrontend(v8_crdtp::CreateErrorNotification(
362 v8_crdtp::DispatchResponse::ParseError(status.ToASCIIString()))));
363 return;
364 }
365 cbor = SpanFrom(converted_cbor);
366 }
367 v8_crdtp::Dispatchable dispatchable(cbor);
368 if (!dispatchable.ok()) {
369 if (dispatchable.HasCallId()) {
370 m_channel->sendNotification(serializeForFrontend(
371 v8_crdtp::CreateErrorNotification(dispatchable.DispatchError())));
372 } else {
373 m_channel->sendResponse(
374 dispatchable.CallId(),
375 serializeForFrontend(v8_crdtp::CreateErrorResponse(
376 dispatchable.CallId(), dispatchable.DispatchError())));
377 }
378 return;
379 }
380 m_dispatcher.Dispatch(dispatchable).Run();
381 }
382
state()383 std::vector<uint8_t> V8InspectorSessionImpl::state() {
384 return m_state->Serialize();
385 }
386
387 std::vector<std::unique_ptr<protocol::Schema::API::Domain>>
supportedDomains()388 V8InspectorSessionImpl::supportedDomains() {
389 std::vector<std::unique_ptr<protocol::Schema::Domain>> domains =
390 supportedDomainsImpl();
391 std::vector<std::unique_ptr<protocol::Schema::API::Domain>> result;
392 for (size_t i = 0; i < domains.size(); ++i)
393 result.push_back(std::move(domains[i]));
394 return result;
395 }
396
397 std::vector<std::unique_ptr<protocol::Schema::Domain>>
supportedDomainsImpl()398 V8InspectorSessionImpl::supportedDomainsImpl() {
399 std::vector<std::unique_ptr<protocol::Schema::Domain>> result;
400 result.push_back(protocol::Schema::Domain::create()
401 .setName(protocol::Runtime::Metainfo::domainName)
402 .setVersion(protocol::Runtime::Metainfo::version)
403 .build());
404 result.push_back(protocol::Schema::Domain::create()
405 .setName(protocol::Debugger::Metainfo::domainName)
406 .setVersion(protocol::Debugger::Metainfo::version)
407 .build());
408 result.push_back(protocol::Schema::Domain::create()
409 .setName(protocol::Profiler::Metainfo::domainName)
410 .setVersion(protocol::Profiler::Metainfo::version)
411 .build());
412 result.push_back(protocol::Schema::Domain::create()
413 .setName(protocol::HeapProfiler::Metainfo::domainName)
414 .setVersion(protocol::HeapProfiler::Metainfo::version)
415 .build());
416 result.push_back(protocol::Schema::Domain::create()
417 .setName(protocol::Schema::Metainfo::domainName)
418 .setVersion(protocol::Schema::Metainfo::version)
419 .build());
420 return result;
421 }
422
addInspectedObject(std::unique_ptr<V8InspectorSession::Inspectable> inspectable)423 void V8InspectorSessionImpl::addInspectedObject(
424 std::unique_ptr<V8InspectorSession::Inspectable> inspectable) {
425 m_inspectedObjects.insert(m_inspectedObjects.begin(), std::move(inspectable));
426 if (m_inspectedObjects.size() > kInspectedObjectBufferSize)
427 m_inspectedObjects.resize(kInspectedObjectBufferSize);
428 }
429
inspectedObject(unsigned num)430 V8InspectorSession::Inspectable* V8InspectorSessionImpl::inspectedObject(
431 unsigned num) {
432 if (num >= m_inspectedObjects.size()) return nullptr;
433 return m_inspectedObjects[num].get();
434 }
435
schedulePauseOnNextStatement(StringView breakReason,StringView breakDetails)436 void V8InspectorSessionImpl::schedulePauseOnNextStatement(
437 StringView breakReason, StringView breakDetails) {
438 std::vector<uint8_t> cbor;
439 ConvertToCBOR(breakDetails, &cbor);
440 m_debuggerAgent->schedulePauseOnNextStatement(
441 toString16(breakReason),
442 protocol::DictionaryValue::cast(
443 protocol::Value::parseBinary(cbor.data(), cbor.size())));
444 }
445
cancelPauseOnNextStatement()446 void V8InspectorSessionImpl::cancelPauseOnNextStatement() {
447 m_debuggerAgent->cancelPauseOnNextStatement();
448 }
449
breakProgram(StringView breakReason,StringView breakDetails)450 void V8InspectorSessionImpl::breakProgram(StringView breakReason,
451 StringView breakDetails) {
452 std::vector<uint8_t> cbor;
453 ConvertToCBOR(breakDetails, &cbor);
454 m_debuggerAgent->breakProgram(
455 toString16(breakReason),
456 protocol::DictionaryValue::cast(
457 protocol::Value::parseBinary(cbor.data(), cbor.size())));
458 }
459
setSkipAllPauses(bool skip)460 void V8InspectorSessionImpl::setSkipAllPauses(bool skip) {
461 m_debuggerAgent->setSkipAllPauses(skip);
462 }
463
resume(bool terminateOnResume)464 void V8InspectorSessionImpl::resume(bool terminateOnResume) {
465 m_debuggerAgent->resume(terminateOnResume);
466 }
467
stepOver()468 void V8InspectorSessionImpl::stepOver() { m_debuggerAgent->stepOver({}); }
469
470 std::vector<std::unique_ptr<protocol::Debugger::API::SearchMatch>>
searchInTextByLines(StringView text,StringView query,bool caseSensitive,bool isRegex)471 V8InspectorSessionImpl::searchInTextByLines(StringView text, StringView query,
472 bool caseSensitive, bool isRegex) {
473 // TODO(dgozman): search may operate on StringView and avoid copying |text|.
474 std::vector<std::unique_ptr<protocol::Debugger::SearchMatch>> matches =
475 searchInTextByLinesImpl(this, toString16(text), toString16(query),
476 caseSensitive, isRegex);
477 std::vector<std::unique_ptr<protocol::Debugger::API::SearchMatch>> result;
478 for (size_t i = 0; i < matches.size(); ++i)
479 result.push_back(std::move(matches[i]));
480 return result;
481 }
482
triggerPreciseCoverageDeltaUpdate(StringView occassion)483 void V8InspectorSessionImpl::triggerPreciseCoverageDeltaUpdate(
484 StringView occassion) {
485 m_profilerAgent->triggerPreciseCoverageDeltaUpdate(toString16(occassion));
486 }
487
488 } // namespace v8_inspector
489