1 // Copyright 2015 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-debugger-agent-impl.h"
6
7 #include <algorithm>
8
9 #include "../../third_party/inspector_protocol/crdtp/json.h"
10 #include "include/v8-inspector.h"
11 #include "src/base/safe_conversions.h"
12 #include "src/debug/debug-interface.h"
13 #include "src/inspector/injected-script.h"
14 #include "src/inspector/inspected-context.h"
15 #include "src/inspector/protocol/Debugger.h"
16 #include "src/inspector/protocol/Protocol.h"
17 #include "src/inspector/remote-object-id.h"
18 #include "src/inspector/search-util.h"
19 #include "src/inspector/string-util.h"
20 #include "src/inspector/v8-debugger-script.h"
21 #include "src/inspector/v8-debugger.h"
22 #include "src/inspector/v8-inspector-impl.h"
23 #include "src/inspector/v8-inspector-session-impl.h"
24 #include "src/inspector/v8-regex.h"
25 #include "src/inspector/v8-runtime-agent-impl.h"
26 #include "src/inspector/v8-stack-trace-impl.h"
27 #include "src/inspector/v8-value-utils.h"
28
29 namespace v8_inspector {
30
31 using protocol::Array;
32 using protocol::Maybe;
33 using protocol::Debugger::BreakpointId;
34 using protocol::Debugger::CallFrame;
35 using protocol::Debugger::Scope;
36 using protocol::Runtime::ExceptionDetails;
37 using protocol::Runtime::RemoteObject;
38 using protocol::Runtime::ScriptId;
39
40 namespace InstrumentationEnum =
41 protocol::Debugger::SetInstrumentationBreakpoint::InstrumentationEnum;
42
43 namespace DebuggerAgentState {
44 static const char pauseOnExceptionsState[] = "pauseOnExceptionsState";
45 static const char asyncCallStackDepth[] = "asyncCallStackDepth";
46 static const char blackboxPattern[] = "blackboxPattern";
47 static const char debuggerEnabled[] = "debuggerEnabled";
48 static const char skipAllPauses[] = "skipAllPauses";
49
50 static const char breakpointsByRegex[] = "breakpointsByRegex";
51 static const char breakpointsByUrl[] = "breakpointsByUrl";
52 static const char breakpointsByScriptHash[] = "breakpointsByScriptHash";
53 static const char breakpointHints[] = "breakpointHints";
54 static const char instrumentationBreakpoints[] = "instrumentationBreakpoints";
55
56 } // namespace DebuggerAgentState
57
58 static const char kBacktraceObjectGroup[] = "backtrace";
59 static const char kDebuggerNotEnabled[] = "Debugger agent is not enabled";
60 static const char kDebuggerNotPaused[] =
61 "Can only perform operation while paused.";
62
63 static const size_t kBreakpointHintMaxLength = 128;
64 static const intptr_t kBreakpointHintMaxSearchOffset = 80 * 10;
65 // Limit the number of breakpoints returned, as we otherwise may exceed
66 // the maximum length of a message in mojo (see https://crbug.com/1105172).
67 static const size_t kMaxNumBreakpoints = 1000;
68
69 // TODO(1099680): getScriptSource and getWasmBytecode return Wasm wire bytes
70 // as protocol::Binary, which is encoded as JSON string in the communication
71 // to the DevTools front-end and hence leads to either crashing the renderer
72 // that is being debugged or the renderer that's running the front-end if we
73 // allow arbitrarily big Wasm byte sequences here. Ideally we would find a
74 // different way to transfer the wire bytes (middle- to long-term), but as a
75 // short-term solution, we should at least not crash.
76 static const size_t kWasmBytecodeMaxLength = (v8::String::kMaxLength / 4) * 3;
77 static const char kWasmBytecodeExceedsTransferLimit[] =
78 "WebAssembly bytecode exceeds the transfer limit";
79
80 namespace {
81
82 enum class BreakpointType {
83 kByUrl = 1,
84 kByUrlRegex,
85 kByScriptHash,
86 kByScriptId,
87 kDebugCommand,
88 kMonitorCommand,
89 kBreakpointAtEntry,
90 kInstrumentationBreakpoint
91 };
92
generateBreakpointId(BreakpointType type,const String16 & scriptSelector,int lineNumber,int columnNumber)93 String16 generateBreakpointId(BreakpointType type,
94 const String16& scriptSelector, int lineNumber,
95 int columnNumber) {
96 String16Builder builder;
97 builder.appendNumber(static_cast<int>(type));
98 builder.append(':');
99 builder.appendNumber(lineNumber);
100 builder.append(':');
101 builder.appendNumber(columnNumber);
102 builder.append(':');
103 builder.append(scriptSelector);
104 return builder.toString();
105 }
106
generateBreakpointId(BreakpointType type,v8::Local<v8::Function> function)107 String16 generateBreakpointId(BreakpointType type,
108 v8::Local<v8::Function> function) {
109 String16Builder builder;
110 builder.appendNumber(static_cast<int>(type));
111 builder.append(':');
112 builder.appendNumber(v8::debug::GetDebuggingId(function));
113 return builder.toString();
114 }
115
generateInstrumentationBreakpointId(const String16 & instrumentation)116 String16 generateInstrumentationBreakpointId(const String16& instrumentation) {
117 String16Builder builder;
118 builder.appendNumber(
119 static_cast<int>(BreakpointType::kInstrumentationBreakpoint));
120 builder.append(':');
121 builder.append(instrumentation);
122 return builder.toString();
123 }
124
parseBreakpointId(const String16 & breakpointId,BreakpointType * type,String16 * scriptSelector=nullptr,int * lineNumber=nullptr,int * columnNumber=nullptr)125 bool parseBreakpointId(const String16& breakpointId, BreakpointType* type,
126 String16* scriptSelector = nullptr,
127 int* lineNumber = nullptr, int* columnNumber = nullptr) {
128 size_t typeLineSeparator = breakpointId.find(':');
129 if (typeLineSeparator == String16::kNotFound) return false;
130
131 int rawType = breakpointId.substring(0, typeLineSeparator).toInteger();
132 if (rawType < static_cast<int>(BreakpointType::kByUrl) ||
133 rawType > static_cast<int>(BreakpointType::kInstrumentationBreakpoint)) {
134 return false;
135 }
136 if (type) *type = static_cast<BreakpointType>(rawType);
137 if (rawType == static_cast<int>(BreakpointType::kDebugCommand) ||
138 rawType == static_cast<int>(BreakpointType::kMonitorCommand) ||
139 rawType == static_cast<int>(BreakpointType::kBreakpointAtEntry) ||
140 rawType == static_cast<int>(BreakpointType::kInstrumentationBreakpoint)) {
141 // The script and source position are not encoded in this case.
142 return true;
143 }
144
145 size_t lineColumnSeparator = breakpointId.find(':', typeLineSeparator + 1);
146 if (lineColumnSeparator == String16::kNotFound) return false;
147 size_t columnSelectorSeparator =
148 breakpointId.find(':', lineColumnSeparator + 1);
149 if (columnSelectorSeparator == String16::kNotFound) return false;
150 if (scriptSelector) {
151 *scriptSelector = breakpointId.substring(columnSelectorSeparator + 1);
152 }
153 if (lineNumber) {
154 *lineNumber = breakpointId
155 .substring(typeLineSeparator + 1,
156 lineColumnSeparator - typeLineSeparator - 1)
157 .toInteger();
158 }
159 if (columnNumber) {
160 *columnNumber =
161 breakpointId
162 .substring(lineColumnSeparator + 1,
163 columnSelectorSeparator - lineColumnSeparator - 1)
164 .toInteger();
165 }
166 return true;
167 }
168
positionComparator(const std::pair<int,int> & a,const std::pair<int,int> & b)169 bool positionComparator(const std::pair<int, int>& a,
170 const std::pair<int, int>& b) {
171 if (a.first != b.first) return a.first < b.first;
172 return a.second < b.second;
173 }
174
breakpointHint(const V8DebuggerScript & script,int lineNumber,int columnNumber)175 String16 breakpointHint(const V8DebuggerScript& script, int lineNumber,
176 int columnNumber) {
177 int offset = script.offset(lineNumber, columnNumber);
178 if (offset == V8DebuggerScript::kNoOffset) return String16();
179 String16 hint =
180 script.source(offset, kBreakpointHintMaxLength).stripWhiteSpace();
181 for (size_t i = 0; i < hint.length(); ++i) {
182 if (hint[i] == '\r' || hint[i] == '\n' || hint[i] == ';') {
183 return hint.substring(0, i);
184 }
185 }
186 return hint;
187 }
188
adjustBreakpointLocation(const V8DebuggerScript & script,const String16 & hint,int * lineNumber,int * columnNumber)189 void adjustBreakpointLocation(const V8DebuggerScript& script,
190 const String16& hint, int* lineNumber,
191 int* columnNumber) {
192 if (*lineNumber < script.startLine() || *lineNumber > script.endLine())
193 return;
194 if (hint.isEmpty()) return;
195 intptr_t sourceOffset = script.offset(*lineNumber, *columnNumber);
196 if (sourceOffset == V8DebuggerScript::kNoOffset) return;
197
198 intptr_t searchRegionOffset = std::max(
199 sourceOffset - kBreakpointHintMaxSearchOffset, static_cast<intptr_t>(0));
200 size_t offset = sourceOffset - searchRegionOffset;
201 String16 searchArea = script.source(searchRegionOffset,
202 offset + kBreakpointHintMaxSearchOffset);
203
204 size_t nextMatch = searchArea.find(hint, offset);
205 size_t prevMatch = searchArea.reverseFind(hint, offset);
206 if (nextMatch == String16::kNotFound && prevMatch == String16::kNotFound) {
207 return;
208 }
209 size_t bestMatch;
210 if (nextMatch == String16::kNotFound) {
211 bestMatch = prevMatch;
212 } else if (prevMatch == String16::kNotFound) {
213 bestMatch = nextMatch;
214 } else {
215 bestMatch = nextMatch - offset < offset - prevMatch ? nextMatch : prevMatch;
216 }
217 bestMatch += searchRegionOffset;
218 v8::debug::Location hintPosition =
219 script.location(static_cast<int>(bestMatch));
220 if (hintPosition.IsEmpty()) return;
221 *lineNumber = hintPosition.GetLineNumber();
222 *columnNumber = hintPosition.GetColumnNumber();
223 }
224
breakLocationType(v8::debug::BreakLocationType type)225 String16 breakLocationType(v8::debug::BreakLocationType type) {
226 switch (type) {
227 case v8::debug::kCallBreakLocation:
228 return protocol::Debugger::BreakLocation::TypeEnum::Call;
229 case v8::debug::kReturnBreakLocation:
230 return protocol::Debugger::BreakLocation::TypeEnum::Return;
231 case v8::debug::kDebuggerStatementBreakLocation:
232 return protocol::Debugger::BreakLocation::TypeEnum::DebuggerStatement;
233 case v8::debug::kCommonBreakLocation:
234 return String16();
235 }
236 return String16();
237 }
238
scopeType(v8::debug::ScopeIterator::ScopeType type)239 String16 scopeType(v8::debug::ScopeIterator::ScopeType type) {
240 switch (type) {
241 case v8::debug::ScopeIterator::ScopeTypeGlobal:
242 return Scope::TypeEnum::Global;
243 case v8::debug::ScopeIterator::ScopeTypeLocal:
244 return Scope::TypeEnum::Local;
245 case v8::debug::ScopeIterator::ScopeTypeWith:
246 return Scope::TypeEnum::With;
247 case v8::debug::ScopeIterator::ScopeTypeClosure:
248 return Scope::TypeEnum::Closure;
249 case v8::debug::ScopeIterator::ScopeTypeCatch:
250 return Scope::TypeEnum::Catch;
251 case v8::debug::ScopeIterator::ScopeTypeBlock:
252 return Scope::TypeEnum::Block;
253 case v8::debug::ScopeIterator::ScopeTypeScript:
254 return Scope::TypeEnum::Script;
255 case v8::debug::ScopeIterator::ScopeTypeEval:
256 return Scope::TypeEnum::Eval;
257 case v8::debug::ScopeIterator::ScopeTypeModule:
258 return Scope::TypeEnum::Module;
259 case v8::debug::ScopeIterator::ScopeTypeWasmExpressionStack:
260 return Scope::TypeEnum::WasmExpressionStack;
261 }
262 UNREACHABLE();
263 return String16();
264 }
265
buildScopes(v8::Isolate * isolate,v8::debug::ScopeIterator * iterator,InjectedScript * injectedScript,std::unique_ptr<Array<Scope>> * scopes)266 Response buildScopes(v8::Isolate* isolate, v8::debug::ScopeIterator* iterator,
267 InjectedScript* injectedScript,
268 std::unique_ptr<Array<Scope>>* scopes) {
269 *scopes = std::make_unique<Array<Scope>>();
270 if (!injectedScript) return Response::Success();
271 if (iterator->Done()) return Response::Success();
272
273 String16 scriptId = String16::fromInteger(iterator->GetScriptId());
274
275 for (; !iterator->Done(); iterator->Advance()) {
276 std::unique_ptr<RemoteObject> object;
277 Response result =
278 injectedScript->wrapObject(iterator->GetObject(), kBacktraceObjectGroup,
279 WrapMode::kNoPreview, &object);
280 if (!result.IsSuccess()) return result;
281
282 auto scope = Scope::create()
283 .setType(scopeType(iterator->GetType()))
284 .setObject(std::move(object))
285 .build();
286
287 String16 name = toProtocolStringWithTypeCheck(
288 isolate, iterator->GetFunctionDebugName());
289 if (!name.isEmpty()) scope->setName(name);
290
291 if (iterator->HasLocationInfo()) {
292 v8::debug::Location start = iterator->GetStartLocation();
293 scope->setStartLocation(protocol::Debugger::Location::create()
294 .setScriptId(scriptId)
295 .setLineNumber(start.GetLineNumber())
296 .setColumnNumber(start.GetColumnNumber())
297 .build());
298
299 v8::debug::Location end = iterator->GetEndLocation();
300 scope->setEndLocation(protocol::Debugger::Location::create()
301 .setScriptId(scriptId)
302 .setLineNumber(end.GetLineNumber())
303 .setColumnNumber(end.GetColumnNumber())
304 .build());
305 }
306 (*scopes)->emplace_back(std::move(scope));
307 }
308 return Response::Success();
309 }
310
getOrCreateObject(protocol::DictionaryValue * object,const String16 & key)311 protocol::DictionaryValue* getOrCreateObject(protocol::DictionaryValue* object,
312 const String16& key) {
313 protocol::DictionaryValue* value = object->getObject(key);
314 if (value) return value;
315 std::unique_ptr<protocol::DictionaryValue> newDictionary =
316 protocol::DictionaryValue::create();
317 value = newDictionary.get();
318 object->setObject(key, std::move(newDictionary));
319 return value;
320 }
321
isValidPosition(protocol::Debugger::ScriptPosition * position)322 Response isValidPosition(protocol::Debugger::ScriptPosition* position) {
323 if (position->getLineNumber() < 0)
324 return Response::ServerError("Position missing 'line' or 'line' < 0.");
325 if (position->getColumnNumber() < 0)
326 return Response::ServerError("Position missing 'column' or 'column' < 0.");
327 return Response::Success();
328 }
329
isValidRangeOfPositions(std::vector<std::pair<int,int>> & positions)330 Response isValidRangeOfPositions(std::vector<std::pair<int, int>>& positions) {
331 for (size_t i = 1; i < positions.size(); ++i) {
332 if (positions[i - 1].first < positions[i].first) continue;
333 if (positions[i - 1].first == positions[i].first &&
334 positions[i - 1].second < positions[i].second)
335 continue;
336 return Response::ServerError(
337 "Input positions array is not sorted or contains duplicate values.");
338 }
339 return Response::Success();
340 }
341 } // namespace
342
V8DebuggerAgentImpl(V8InspectorSessionImpl * session,protocol::FrontendChannel * frontendChannel,protocol::DictionaryValue * state)343 V8DebuggerAgentImpl::V8DebuggerAgentImpl(
344 V8InspectorSessionImpl* session, protocol::FrontendChannel* frontendChannel,
345 protocol::DictionaryValue* state)
346 : m_inspector(session->inspector()),
347 m_debugger(m_inspector->debugger()),
348 m_session(session),
349 m_enabled(false),
350 m_state(state),
351 m_frontend(frontendChannel),
352 m_isolate(m_inspector->isolate()) {}
353
354 V8DebuggerAgentImpl::~V8DebuggerAgentImpl() = default;
355
enableImpl()356 void V8DebuggerAgentImpl::enableImpl() {
357 m_enabled = true;
358 m_state->setBoolean(DebuggerAgentState::debuggerEnabled, true);
359 m_debugger->enable();
360
361 std::vector<std::unique_ptr<V8DebuggerScript>> compiledScripts =
362 m_debugger->getCompiledScripts(m_session->contextGroupId(), this);
363 for (auto& script : compiledScripts) {
364 didParseSource(std::move(script), true);
365 }
366
367 m_breakpointsActive = true;
368 m_debugger->setBreakpointsActive(true);
369
370 if (isPaused()) {
371 didPause(0, v8::Local<v8::Value>(), std::vector<v8::debug::BreakpointId>(),
372 v8::debug::kException, false, false, false);
373 }
374 }
375
enable(Maybe<double> maxScriptsCacheSize,String16 * outDebuggerId)376 Response V8DebuggerAgentImpl::enable(Maybe<double> maxScriptsCacheSize,
377 String16* outDebuggerId) {
378 m_maxScriptCacheSize = v8::base::saturated_cast<size_t>(
379 maxScriptsCacheSize.fromMaybe(std::numeric_limits<double>::max()));
380 *outDebuggerId =
381 m_debugger->debuggerIdFor(m_session->contextGroupId()).toString();
382 if (enabled()) return Response::Success();
383
384 if (!m_inspector->client()->canExecuteScripts(m_session->contextGroupId()))
385 return Response::ServerError("Script execution is prohibited");
386
387 enableImpl();
388 return Response::Success();
389 }
390
disable()391 Response V8DebuggerAgentImpl::disable() {
392 if (!enabled()) return Response::Success();
393
394 m_state->remove(DebuggerAgentState::breakpointsByRegex);
395 m_state->remove(DebuggerAgentState::breakpointsByUrl);
396 m_state->remove(DebuggerAgentState::breakpointsByScriptHash);
397 m_state->remove(DebuggerAgentState::breakpointHints);
398 m_state->remove(DebuggerAgentState::instrumentationBreakpoints);
399
400 m_state->setInteger(DebuggerAgentState::pauseOnExceptionsState,
401 v8::debug::NoBreakOnException);
402 m_state->setInteger(DebuggerAgentState::asyncCallStackDepth, 0);
403
404 if (m_breakpointsActive) {
405 m_debugger->setBreakpointsActive(false);
406 m_breakpointsActive = false;
407 }
408 m_blackboxedPositions.clear();
409 m_blackboxPattern.reset();
410 resetBlackboxedStateCache();
411 m_skipList.clear();
412 m_scripts.clear();
413 m_cachedScriptIds.clear();
414 m_cachedScriptSize = 0;
415 for (const auto& it : m_debuggerBreakpointIdToBreakpointId) {
416 v8::debug::RemoveBreakpoint(m_isolate, it.first);
417 }
418 m_breakpointIdToDebuggerBreakpointIds.clear();
419 m_debuggerBreakpointIdToBreakpointId.clear();
420 m_debugger->setAsyncCallStackDepth(this, 0);
421 clearBreakDetails();
422 m_skipAllPauses = false;
423 m_state->setBoolean(DebuggerAgentState::skipAllPauses, false);
424 m_state->remove(DebuggerAgentState::blackboxPattern);
425 m_enabled = false;
426 m_state->setBoolean(DebuggerAgentState::debuggerEnabled, false);
427 m_debugger->disable();
428 return Response::Success();
429 }
430
restore()431 void V8DebuggerAgentImpl::restore() {
432 DCHECK(!m_enabled);
433 if (!m_state->booleanProperty(DebuggerAgentState::debuggerEnabled, false))
434 return;
435 if (!m_inspector->client()->canExecuteScripts(m_session->contextGroupId()))
436 return;
437
438 enableImpl();
439
440 int pauseState = v8::debug::NoBreakOnException;
441 m_state->getInteger(DebuggerAgentState::pauseOnExceptionsState, &pauseState);
442 setPauseOnExceptionsImpl(pauseState);
443
444 m_skipAllPauses =
445 m_state->booleanProperty(DebuggerAgentState::skipAllPauses, false);
446
447 int asyncCallStackDepth = 0;
448 m_state->getInteger(DebuggerAgentState::asyncCallStackDepth,
449 &asyncCallStackDepth);
450 m_debugger->setAsyncCallStackDepth(this, asyncCallStackDepth);
451
452 String16 blackboxPattern;
453 if (m_state->getString(DebuggerAgentState::blackboxPattern,
454 &blackboxPattern)) {
455 setBlackboxPattern(blackboxPattern);
456 }
457 }
458
setBreakpointsActive(bool active)459 Response V8DebuggerAgentImpl::setBreakpointsActive(bool active) {
460 if (!enabled()) return Response::ServerError(kDebuggerNotEnabled);
461 if (m_breakpointsActive == active) return Response::Success();
462 m_breakpointsActive = active;
463 m_debugger->setBreakpointsActive(active);
464 if (!active && !m_breakReason.empty()) {
465 clearBreakDetails();
466 m_debugger->setPauseOnNextCall(false, m_session->contextGroupId());
467 }
468 return Response::Success();
469 }
470
setSkipAllPauses(bool skip)471 Response V8DebuggerAgentImpl::setSkipAllPauses(bool skip) {
472 m_state->setBoolean(DebuggerAgentState::skipAllPauses, skip);
473 m_skipAllPauses = skip;
474 return Response::Success();
475 }
476
matches(V8InspectorImpl * inspector,const V8DebuggerScript & script,BreakpointType type,const String16 & selector)477 static bool matches(V8InspectorImpl* inspector, const V8DebuggerScript& script,
478 BreakpointType type, const String16& selector) {
479 switch (type) {
480 case BreakpointType::kByUrl:
481 return script.sourceURL() == selector;
482 case BreakpointType::kByScriptHash:
483 return script.hash() == selector;
484 case BreakpointType::kByUrlRegex: {
485 V8Regex regex(inspector, selector, true);
486 return regex.match(script.sourceURL()) != -1;
487 }
488 case BreakpointType::kByScriptId: {
489 return script.scriptId() == selector;
490 }
491 default:
492 return false;
493 }
494 }
495
setBreakpointByUrl(int lineNumber,Maybe<String16> optionalURL,Maybe<String16> optionalURLRegex,Maybe<String16> optionalScriptHash,Maybe<int> optionalColumnNumber,Maybe<String16> optionalCondition,String16 * outBreakpointId,std::unique_ptr<protocol::Array<protocol::Debugger::Location>> * locations)496 Response V8DebuggerAgentImpl::setBreakpointByUrl(
497 int lineNumber, Maybe<String16> optionalURL,
498 Maybe<String16> optionalURLRegex, Maybe<String16> optionalScriptHash,
499 Maybe<int> optionalColumnNumber, Maybe<String16> optionalCondition,
500 String16* outBreakpointId,
501 std::unique_ptr<protocol::Array<protocol::Debugger::Location>>* locations) {
502 *locations = std::make_unique<Array<protocol::Debugger::Location>>();
503
504 int specified = (optionalURL.isJust() ? 1 : 0) +
505 (optionalURLRegex.isJust() ? 1 : 0) +
506 (optionalScriptHash.isJust() ? 1 : 0);
507 if (specified != 1) {
508 return Response::ServerError(
509 "Either url or urlRegex or scriptHash must be specified.");
510 }
511 int columnNumber = 0;
512 if (optionalColumnNumber.isJust()) {
513 columnNumber = optionalColumnNumber.fromJust();
514 if (columnNumber < 0)
515 return Response::ServerError("Incorrect column number");
516 }
517
518 BreakpointType type = BreakpointType::kByUrl;
519 String16 selector;
520 if (optionalURLRegex.isJust()) {
521 selector = optionalURLRegex.fromJust();
522 type = BreakpointType::kByUrlRegex;
523 } else if (optionalURL.isJust()) {
524 selector = optionalURL.fromJust();
525 type = BreakpointType::kByUrl;
526 } else if (optionalScriptHash.isJust()) {
527 selector = optionalScriptHash.fromJust();
528 type = BreakpointType::kByScriptHash;
529 }
530
531 String16 condition = optionalCondition.fromMaybe(String16());
532 String16 breakpointId =
533 generateBreakpointId(type, selector, lineNumber, columnNumber);
534 protocol::DictionaryValue* breakpoints;
535 switch (type) {
536 case BreakpointType::kByUrlRegex:
537 breakpoints =
538 getOrCreateObject(m_state, DebuggerAgentState::breakpointsByRegex);
539 break;
540 case BreakpointType::kByUrl:
541 breakpoints = getOrCreateObject(
542 getOrCreateObject(m_state, DebuggerAgentState::breakpointsByUrl),
543 selector);
544 break;
545 case BreakpointType::kByScriptHash:
546 breakpoints = getOrCreateObject(
547 getOrCreateObject(m_state,
548 DebuggerAgentState::breakpointsByScriptHash),
549 selector);
550 break;
551 default:
552 UNREACHABLE();
553 }
554 if (breakpoints->get(breakpointId)) {
555 return Response::ServerError(
556 "Breakpoint at specified location already exists.");
557 }
558
559 String16 hint;
560 for (const auto& script : m_scripts) {
561 if (!matches(m_inspector, *script.second, type, selector)) continue;
562 if (!hint.isEmpty()) {
563 adjustBreakpointLocation(*script.second, hint, &lineNumber,
564 &columnNumber);
565 }
566 std::unique_ptr<protocol::Debugger::Location> location = setBreakpointImpl(
567 breakpointId, script.first, condition, lineNumber, columnNumber);
568 if (location && type != BreakpointType::kByUrlRegex) {
569 hint = breakpointHint(*script.second, lineNumber, columnNumber);
570 }
571 if (location) (*locations)->emplace_back(std::move(location));
572 }
573 breakpoints->setString(breakpointId, condition);
574 if (!hint.isEmpty()) {
575 protocol::DictionaryValue* breakpointHints =
576 getOrCreateObject(m_state, DebuggerAgentState::breakpointHints);
577 breakpointHints->setString(breakpointId, hint);
578 }
579 *outBreakpointId = breakpointId;
580 return Response::Success();
581 }
582
setBreakpoint(std::unique_ptr<protocol::Debugger::Location> location,Maybe<String16> optionalCondition,String16 * outBreakpointId,std::unique_ptr<protocol::Debugger::Location> * actualLocation)583 Response V8DebuggerAgentImpl::setBreakpoint(
584 std::unique_ptr<protocol::Debugger::Location> location,
585 Maybe<String16> optionalCondition, String16* outBreakpointId,
586 std::unique_ptr<protocol::Debugger::Location>* actualLocation) {
587 String16 breakpointId = generateBreakpointId(
588 BreakpointType::kByScriptId, location->getScriptId(),
589 location->getLineNumber(), location->getColumnNumber(0));
590 if (m_breakpointIdToDebuggerBreakpointIds.find(breakpointId) !=
591 m_breakpointIdToDebuggerBreakpointIds.end()) {
592 return Response::ServerError(
593 "Breakpoint at specified location already exists.");
594 }
595 *actualLocation = setBreakpointImpl(breakpointId, location->getScriptId(),
596 optionalCondition.fromMaybe(String16()),
597 location->getLineNumber(),
598 location->getColumnNumber(0));
599 if (!*actualLocation)
600 return Response::ServerError("Could not resolve breakpoint");
601 *outBreakpointId = breakpointId;
602 return Response::Success();
603 }
604
setBreakpointOnFunctionCall(const String16 & functionObjectId,Maybe<String16> optionalCondition,String16 * outBreakpointId)605 Response V8DebuggerAgentImpl::setBreakpointOnFunctionCall(
606 const String16& functionObjectId, Maybe<String16> optionalCondition,
607 String16* outBreakpointId) {
608 InjectedScript::ObjectScope scope(m_session, functionObjectId);
609 Response response = scope.initialize();
610 if (!response.IsSuccess()) return response;
611 if (!scope.object()->IsFunction()) {
612 return Response::ServerError("Could not find function with given id");
613 }
614 v8::Local<v8::Function> function =
615 v8::Local<v8::Function>::Cast(scope.object());
616 String16 breakpointId =
617 generateBreakpointId(BreakpointType::kBreakpointAtEntry, function);
618 if (m_breakpointIdToDebuggerBreakpointIds.find(breakpointId) !=
619 m_breakpointIdToDebuggerBreakpointIds.end()) {
620 return Response::ServerError(
621 "Breakpoint at specified location already exists.");
622 }
623 v8::Local<v8::String> condition =
624 toV8String(m_isolate, optionalCondition.fromMaybe(String16()));
625 setBreakpointImpl(breakpointId, function, condition);
626 *outBreakpointId = breakpointId;
627 return Response::Success();
628 }
629
setInstrumentationBreakpoint(const String16 & instrumentation,String16 * outBreakpointId)630 Response V8DebuggerAgentImpl::setInstrumentationBreakpoint(
631 const String16& instrumentation, String16* outBreakpointId) {
632 if (!enabled()) return Response::ServerError(kDebuggerNotEnabled);
633 String16 breakpointId = generateInstrumentationBreakpointId(instrumentation);
634 protocol::DictionaryValue* breakpoints = getOrCreateObject(
635 m_state, DebuggerAgentState::instrumentationBreakpoints);
636 if (breakpoints->get(breakpointId)) {
637 return Response::ServerError(
638 "Instrumentation breakpoint is already enabled.");
639 }
640 breakpoints->setBoolean(breakpointId, true);
641 *outBreakpointId = breakpointId;
642 return Response::Success();
643 }
644
removeBreakpoint(const String16 & breakpointId)645 Response V8DebuggerAgentImpl::removeBreakpoint(const String16& breakpointId) {
646 if (!enabled()) return Response::ServerError(kDebuggerNotEnabled);
647 BreakpointType type;
648 String16 selector;
649 if (!parseBreakpointId(breakpointId, &type, &selector)) {
650 return Response::Success();
651 }
652 protocol::DictionaryValue* breakpoints = nullptr;
653 switch (type) {
654 case BreakpointType::kByUrl: {
655 protocol::DictionaryValue* breakpointsByUrl =
656 m_state->getObject(DebuggerAgentState::breakpointsByUrl);
657 if (breakpointsByUrl) {
658 breakpoints = breakpointsByUrl->getObject(selector);
659 }
660 } break;
661 case BreakpointType::kByScriptHash: {
662 protocol::DictionaryValue* breakpointsByScriptHash =
663 m_state->getObject(DebuggerAgentState::breakpointsByScriptHash);
664 if (breakpointsByScriptHash) {
665 breakpoints = breakpointsByScriptHash->getObject(selector);
666 }
667 } break;
668 case BreakpointType::kByUrlRegex:
669 breakpoints = m_state->getObject(DebuggerAgentState::breakpointsByRegex);
670 break;
671 case BreakpointType::kInstrumentationBreakpoint:
672 breakpoints =
673 m_state->getObject(DebuggerAgentState::instrumentationBreakpoints);
674 break;
675 default:
676 break;
677 }
678 if (breakpoints) breakpoints->remove(breakpointId);
679 protocol::DictionaryValue* breakpointHints =
680 m_state->getObject(DebuggerAgentState::breakpointHints);
681 if (breakpointHints) breakpointHints->remove(breakpointId);
682
683 // Get a list of scripts to remove breakpoints.
684 // TODO(duongn): we can do better here if from breakpoint id we can tell it is
685 // not Wasm breakpoint.
686 std::vector<V8DebuggerScript*> scripts;
687 for (const auto& scriptIter : m_scripts) {
688 if (!matches(m_inspector, *scriptIter.second, type, selector)) continue;
689 V8DebuggerScript* script = scriptIter.second.get();
690 scripts.push_back(script);
691 }
692 removeBreakpointImpl(breakpointId, scripts);
693
694 return Response::Success();
695 }
696
removeBreakpointImpl(const String16 & breakpointId,const std::vector<V8DebuggerScript * > & scripts)697 void V8DebuggerAgentImpl::removeBreakpointImpl(
698 const String16& breakpointId,
699 const std::vector<V8DebuggerScript*>& scripts) {
700 DCHECK(enabled());
701 BreakpointIdToDebuggerBreakpointIdsMap::iterator
702 debuggerBreakpointIdsIterator =
703 m_breakpointIdToDebuggerBreakpointIds.find(breakpointId);
704 if (debuggerBreakpointIdsIterator ==
705 m_breakpointIdToDebuggerBreakpointIds.end()) {
706 return;
707 }
708 for (const auto& id : debuggerBreakpointIdsIterator->second) {
709 for (auto& script : scripts) {
710 script->removeWasmBreakpoint(id);
711 }
712 v8::debug::RemoveBreakpoint(m_isolate, id);
713 m_debuggerBreakpointIdToBreakpointId.erase(id);
714 }
715 m_breakpointIdToDebuggerBreakpointIds.erase(breakpointId);
716 }
717
getPossibleBreakpoints(std::unique_ptr<protocol::Debugger::Location> start,Maybe<protocol::Debugger::Location> end,Maybe<bool> restrictToFunction,std::unique_ptr<protocol::Array<protocol::Debugger::BreakLocation>> * locations)718 Response V8DebuggerAgentImpl::getPossibleBreakpoints(
719 std::unique_ptr<protocol::Debugger::Location> start,
720 Maybe<protocol::Debugger::Location> end, Maybe<bool> restrictToFunction,
721 std::unique_ptr<protocol::Array<protocol::Debugger::BreakLocation>>*
722 locations) {
723 String16 scriptId = start->getScriptId();
724
725 if (start->getLineNumber() < 0 || start->getColumnNumber(0) < 0)
726 return Response::ServerError(
727 "start.lineNumber and start.columnNumber should be >= 0");
728
729 v8::debug::Location v8Start(start->getLineNumber(),
730 start->getColumnNumber(0));
731 v8::debug::Location v8End;
732 if (end.isJust()) {
733 if (end.fromJust()->getScriptId() != scriptId)
734 return Response::ServerError(
735 "Locations should contain the same scriptId");
736 int line = end.fromJust()->getLineNumber();
737 int column = end.fromJust()->getColumnNumber(0);
738 if (line < 0 || column < 0)
739 return Response::ServerError(
740 "end.lineNumber and end.columnNumber should be >= 0");
741 v8End = v8::debug::Location(line, column);
742 }
743 auto it = m_scripts.find(scriptId);
744 if (it == m_scripts.end()) return Response::ServerError("Script not found");
745 std::vector<v8::debug::BreakLocation> v8Locations;
746 {
747 v8::HandleScope handleScope(m_isolate);
748 int contextId = it->second->executionContextId();
749 InspectedContext* inspected = m_inspector->getContext(contextId);
750 if (!inspected) {
751 return Response::ServerError("Cannot retrive script context");
752 }
753 v8::Context::Scope contextScope(inspected->context());
754 v8::MicrotasksScope microtasks(m_isolate,
755 v8::MicrotasksScope::kDoNotRunMicrotasks);
756 v8::TryCatch tryCatch(m_isolate);
757 it->second->getPossibleBreakpoints(
758 v8Start, v8End, restrictToFunction.fromMaybe(false), &v8Locations);
759 }
760
761 *locations =
762 std::make_unique<protocol::Array<protocol::Debugger::BreakLocation>>();
763
764 // TODO(1106269): Return an error instead of capping the number of
765 // breakpoints.
766 const size_t numBreakpointsToSend =
767 std::min(v8Locations.size(), kMaxNumBreakpoints);
768 for (size_t i = 0; i < numBreakpointsToSend; ++i) {
769 std::unique_ptr<protocol::Debugger::BreakLocation> breakLocation =
770 protocol::Debugger::BreakLocation::create()
771 .setScriptId(scriptId)
772 .setLineNumber(v8Locations[i].GetLineNumber())
773 .setColumnNumber(v8Locations[i].GetColumnNumber())
774 .build();
775 if (v8Locations[i].type() != v8::debug::kCommonBreakLocation) {
776 breakLocation->setType(breakLocationType(v8Locations[i].type()));
777 }
778 (*locations)->emplace_back(std::move(breakLocation));
779 }
780 return Response::Success();
781 }
782
continueToLocation(std::unique_ptr<protocol::Debugger::Location> location,Maybe<String16> targetCallFrames)783 Response V8DebuggerAgentImpl::continueToLocation(
784 std::unique_ptr<protocol::Debugger::Location> location,
785 Maybe<String16> targetCallFrames) {
786 if (!enabled()) return Response::ServerError(kDebuggerNotEnabled);
787 if (!isPaused()) return Response::ServerError(kDebuggerNotPaused);
788 ScriptsMap::iterator it = m_scripts.find(location->getScriptId());
789 if (it == m_scripts.end()) {
790 return Response::ServerError("Cannot continue to specified location");
791 }
792 V8DebuggerScript* script = it->second.get();
793 int contextId = script->executionContextId();
794 InspectedContext* inspected = m_inspector->getContext(contextId);
795 if (!inspected)
796 return Response::ServerError("Cannot continue to specified location");
797 v8::HandleScope handleScope(m_isolate);
798 v8::Context::Scope contextScope(inspected->context());
799 return m_debugger->continueToLocation(
800 m_session->contextGroupId(), script, std::move(location),
801 targetCallFrames.fromMaybe(
802 protocol::Debugger::ContinueToLocation::TargetCallFramesEnum::Any));
803 }
804
getStackTrace(std::unique_ptr<protocol::Runtime::StackTraceId> inStackTraceId,std::unique_ptr<protocol::Runtime::StackTrace> * outStackTrace)805 Response V8DebuggerAgentImpl::getStackTrace(
806 std::unique_ptr<protocol::Runtime::StackTraceId> inStackTraceId,
807 std::unique_ptr<protocol::Runtime::StackTrace>* outStackTrace) {
808 bool isOk = false;
809 int64_t id = inStackTraceId->getId().toInteger64(&isOk);
810 if (!isOk) return Response::ServerError("Invalid stack trace id");
811
812 V8DebuggerId debuggerId;
813 if (inStackTraceId->hasDebuggerId()) {
814 debuggerId = V8DebuggerId(inStackTraceId->getDebuggerId(String16()));
815 } else {
816 debuggerId = m_debugger->debuggerIdFor(m_session->contextGroupId());
817 }
818 if (!debuggerId.isValid())
819 return Response::ServerError("Invalid stack trace id");
820
821 V8StackTraceId v8StackTraceId(id, debuggerId.pair());
822 if (v8StackTraceId.IsInvalid())
823 return Response::ServerError("Invalid stack trace id");
824 auto stack =
825 m_debugger->stackTraceFor(m_session->contextGroupId(), v8StackTraceId);
826 if (!stack) {
827 return Response::ServerError("Stack trace with given id is not found");
828 }
829 *outStackTrace = stack->buildInspectorObject(
830 m_debugger, m_debugger->maxAsyncCallChainDepth());
831 return Response::Success();
832 }
833
isFunctionBlackboxed(const String16 & scriptId,const v8::debug::Location & start,const v8::debug::Location & end)834 bool V8DebuggerAgentImpl::isFunctionBlackboxed(const String16& scriptId,
835 const v8::debug::Location& start,
836 const v8::debug::Location& end) {
837 ScriptsMap::iterator it = m_scripts.find(scriptId);
838 if (it == m_scripts.end()) {
839 // Unknown scripts are blackboxed.
840 return true;
841 }
842 if (m_blackboxPattern) {
843 const String16& scriptSourceURL = it->second->sourceURL();
844 if (!scriptSourceURL.isEmpty() &&
845 m_blackboxPattern->match(scriptSourceURL) != -1)
846 return true;
847 }
848 auto itBlackboxedPositions = m_blackboxedPositions.find(scriptId);
849 if (itBlackboxedPositions == m_blackboxedPositions.end()) return false;
850
851 const std::vector<std::pair<int, int>>& ranges =
852 itBlackboxedPositions->second;
853 auto itStartRange = std::lower_bound(
854 ranges.begin(), ranges.end(),
855 std::make_pair(start.GetLineNumber(), start.GetColumnNumber()),
856 positionComparator);
857 auto itEndRange = std::lower_bound(
858 itStartRange, ranges.end(),
859 std::make_pair(end.GetLineNumber(), end.GetColumnNumber()),
860 positionComparator);
861 // Ranges array contains positions in script where blackbox state is changed.
862 // [(0,0) ... ranges[0]) isn't blackboxed, [ranges[0] ... ranges[1]) is
863 // blackboxed...
864 return itStartRange == itEndRange &&
865 std::distance(ranges.begin(), itStartRange) % 2;
866 }
867
shouldBeSkipped(const String16 & scriptId,int line,int column)868 bool V8DebuggerAgentImpl::shouldBeSkipped(const String16& scriptId, int line,
869 int column) {
870 if (m_skipList.empty()) return false;
871
872 auto it = m_skipList.find(scriptId);
873 if (it == m_skipList.end()) return false;
874
875 const std::vector<std::pair<int, int>>& ranges = it->second;
876 DCHECK(!ranges.empty());
877 const std::pair<int, int> location = std::make_pair(line, column);
878 auto itLowerBound = std::lower_bound(ranges.begin(), ranges.end(), location,
879 positionComparator);
880
881 bool shouldSkip = false;
882 if (itLowerBound != ranges.end()) {
883 // Skip lists are defined as pairs of locations that specify the
884 // start and the end of ranges to skip: [ranges[0], ranges[1], ..], where
885 // locations in [ranges[0], ranges[1]) should be skipped, i.e.
886 // [(lineStart, columnStart), (lineEnd, columnEnd)).
887 const bool isSameAsLowerBound = location == *itLowerBound;
888 const bool isUnevenIndex = (itLowerBound - ranges.begin()) % 2;
889 shouldSkip = isSameAsLowerBound ^ isUnevenIndex;
890 }
891
892 return shouldSkip;
893 }
894
acceptsPause(bool isOOMBreak) const895 bool V8DebuggerAgentImpl::acceptsPause(bool isOOMBreak) const {
896 return enabled() && (isOOMBreak || !m_skipAllPauses);
897 }
898
899 std::unique_ptr<protocol::Debugger::Location>
setBreakpointImpl(const String16 & breakpointId,const String16 & scriptId,const String16 & condition,int lineNumber,int columnNumber)900 V8DebuggerAgentImpl::setBreakpointImpl(const String16& breakpointId,
901 const String16& scriptId,
902 const String16& condition,
903 int lineNumber, int columnNumber) {
904 v8::HandleScope handles(m_isolate);
905 DCHECK(enabled());
906
907 ScriptsMap::iterator scriptIterator = m_scripts.find(scriptId);
908 if (scriptIterator == m_scripts.end()) return nullptr;
909 V8DebuggerScript* script = scriptIterator->second.get();
910 if (lineNumber < script->startLine() || script->endLine() < lineNumber) {
911 return nullptr;
912 }
913
914 v8::debug::BreakpointId debuggerBreakpointId;
915 v8::debug::Location location(lineNumber, columnNumber);
916 int contextId = script->executionContextId();
917 InspectedContext* inspected = m_inspector->getContext(contextId);
918 if (!inspected) return nullptr;
919
920 {
921 v8::Context::Scope contextScope(inspected->context());
922 if (!script->setBreakpoint(condition, &location, &debuggerBreakpointId)) {
923 return nullptr;
924 }
925 }
926
927 m_debuggerBreakpointIdToBreakpointId[debuggerBreakpointId] = breakpointId;
928 m_breakpointIdToDebuggerBreakpointIds[breakpointId].push_back(
929 debuggerBreakpointId);
930
931 return protocol::Debugger::Location::create()
932 .setScriptId(scriptId)
933 .setLineNumber(location.GetLineNumber())
934 .setColumnNumber(location.GetColumnNumber())
935 .build();
936 }
937
setBreakpointImpl(const String16 & breakpointId,v8::Local<v8::Function> function,v8::Local<v8::String> condition)938 void V8DebuggerAgentImpl::setBreakpointImpl(const String16& breakpointId,
939 v8::Local<v8::Function> function,
940 v8::Local<v8::String> condition) {
941 v8::debug::BreakpointId debuggerBreakpointId;
942 if (!v8::debug::SetFunctionBreakpoint(function, condition,
943 &debuggerBreakpointId)) {
944 return;
945 }
946 m_debuggerBreakpointIdToBreakpointId[debuggerBreakpointId] = breakpointId;
947 m_breakpointIdToDebuggerBreakpointIds[breakpointId].push_back(
948 debuggerBreakpointId);
949 }
950
searchInContent(const String16 & scriptId,const String16 & query,Maybe<bool> optionalCaseSensitive,Maybe<bool> optionalIsRegex,std::unique_ptr<Array<protocol::Debugger::SearchMatch>> * results)951 Response V8DebuggerAgentImpl::searchInContent(
952 const String16& scriptId, const String16& query,
953 Maybe<bool> optionalCaseSensitive, Maybe<bool> optionalIsRegex,
954 std::unique_ptr<Array<protocol::Debugger::SearchMatch>>* results) {
955 v8::HandleScope handles(m_isolate);
956 ScriptsMap::iterator it = m_scripts.find(scriptId);
957 if (it == m_scripts.end())
958 return Response::ServerError("No script for id: " + scriptId.utf8());
959
960 *results = std::make_unique<protocol::Array<protocol::Debugger::SearchMatch>>(
961 searchInTextByLinesImpl(m_session, it->second->source(0), query,
962 optionalCaseSensitive.fromMaybe(false),
963 optionalIsRegex.fromMaybe(false)));
964 return Response::Success();
965 }
966
setScriptSource(const String16 & scriptId,const String16 & newContent,Maybe<bool> dryRun,Maybe<protocol::Array<protocol::Debugger::CallFrame>> * newCallFrames,Maybe<bool> * stackChanged,Maybe<protocol::Runtime::StackTrace> * asyncStackTrace,Maybe<protocol::Runtime::StackTraceId> * asyncStackTraceId,Maybe<protocol::Runtime::ExceptionDetails> * optOutCompileError)967 Response V8DebuggerAgentImpl::setScriptSource(
968 const String16& scriptId, const String16& newContent, Maybe<bool> dryRun,
969 Maybe<protocol::Array<protocol::Debugger::CallFrame>>* newCallFrames,
970 Maybe<bool>* stackChanged,
971 Maybe<protocol::Runtime::StackTrace>* asyncStackTrace,
972 Maybe<protocol::Runtime::StackTraceId>* asyncStackTraceId,
973 Maybe<protocol::Runtime::ExceptionDetails>* optOutCompileError) {
974 if (!enabled()) return Response::ServerError(kDebuggerNotEnabled);
975
976 ScriptsMap::iterator it = m_scripts.find(scriptId);
977 if (it == m_scripts.end()) {
978 return Response::ServerError("No script with given id found");
979 }
980 int contextId = it->second->executionContextId();
981 InspectedContext* inspected = m_inspector->getContext(contextId);
982 if (!inspected) {
983 return Response::InternalError();
984 }
985 v8::HandleScope handleScope(m_isolate);
986 v8::Local<v8::Context> context = inspected->context();
987 v8::Context::Scope contextScope(context);
988
989 v8::debug::LiveEditResult result;
990 it->second->setSource(newContent, dryRun.fromMaybe(false), &result);
991 if (result.status != v8::debug::LiveEditResult::OK) {
992 *optOutCompileError =
993 protocol::Runtime::ExceptionDetails::create()
994 .setExceptionId(m_inspector->nextExceptionId())
995 .setText(toProtocolString(m_isolate, result.message))
996 .setLineNumber(result.line_number != -1 ? result.line_number - 1
997 : 0)
998 .setColumnNumber(result.column_number != -1 ? result.column_number
999 : 0)
1000 .build();
1001 return Response::Success();
1002 } else {
1003 *stackChanged = result.stack_changed;
1004 }
1005 std::unique_ptr<Array<CallFrame>> callFrames;
1006 Response response = currentCallFrames(&callFrames);
1007 if (!response.IsSuccess()) return response;
1008 *newCallFrames = std::move(callFrames);
1009 *asyncStackTrace = currentAsyncStackTrace();
1010 *asyncStackTraceId = currentExternalStackTrace();
1011 return Response::Success();
1012 }
1013
restartFrame(const String16 & callFrameId,std::unique_ptr<Array<CallFrame>> * newCallFrames,Maybe<protocol::Runtime::StackTrace> * asyncStackTrace,Maybe<protocol::Runtime::StackTraceId> * asyncStackTraceId)1014 Response V8DebuggerAgentImpl::restartFrame(
1015 const String16& callFrameId,
1016 std::unique_ptr<Array<CallFrame>>* newCallFrames,
1017 Maybe<protocol::Runtime::StackTrace>* asyncStackTrace,
1018 Maybe<protocol::Runtime::StackTraceId>* asyncStackTraceId) {
1019 if (!isPaused()) return Response::ServerError(kDebuggerNotPaused);
1020 InjectedScript::CallFrameScope scope(m_session, callFrameId);
1021 Response response = scope.initialize();
1022 if (!response.IsSuccess()) return response;
1023 int frameOrdinal = static_cast<int>(scope.frameOrdinal());
1024 auto it = v8::debug::StackTraceIterator::Create(m_isolate, frameOrdinal);
1025 if (it->Done()) {
1026 return Response::ServerError("Could not find call frame with given id");
1027 }
1028 if (!it->Restart()) {
1029 return Response::InternalError();
1030 }
1031 response = currentCallFrames(newCallFrames);
1032 if (!response.IsSuccess()) return response;
1033 *asyncStackTrace = currentAsyncStackTrace();
1034 *asyncStackTraceId = currentExternalStackTrace();
1035 return Response::Success();
1036 }
1037
getScriptSource(const String16 & scriptId,String16 * scriptSource,Maybe<protocol::Binary> * bytecode)1038 Response V8DebuggerAgentImpl::getScriptSource(
1039 const String16& scriptId, String16* scriptSource,
1040 Maybe<protocol::Binary>* bytecode) {
1041 if (!enabled()) return Response::ServerError(kDebuggerNotEnabled);
1042 ScriptsMap::iterator it = m_scripts.find(scriptId);
1043 if (it == m_scripts.end())
1044 return Response::ServerError("No script for id: " + scriptId.utf8());
1045 *scriptSource = it->second->source(0);
1046 v8::MemorySpan<const uint8_t> span;
1047 if (it->second->wasmBytecode().To(&span)) {
1048 if (span.size() > kWasmBytecodeMaxLength) {
1049 return Response::ServerError(kWasmBytecodeExceedsTransferLimit);
1050 }
1051 *bytecode = protocol::Binary::fromSpan(span.data(), span.size());
1052 }
1053 return Response::Success();
1054 }
1055
getWasmBytecode(const String16 & scriptId,protocol::Binary * bytecode)1056 Response V8DebuggerAgentImpl::getWasmBytecode(const String16& scriptId,
1057 protocol::Binary* bytecode) {
1058 if (!enabled()) return Response::ServerError(kDebuggerNotEnabled);
1059 ScriptsMap::iterator it = m_scripts.find(scriptId);
1060 if (it == m_scripts.end())
1061 return Response::ServerError("No script for id: " + scriptId.utf8());
1062 v8::MemorySpan<const uint8_t> span;
1063 if (!it->second->wasmBytecode().To(&span))
1064 return Response::ServerError("Script with id " + scriptId.utf8() +
1065 " is not WebAssembly");
1066 if (span.size() > kWasmBytecodeMaxLength) {
1067 return Response::ServerError(kWasmBytecodeExceedsTransferLimit);
1068 }
1069 *bytecode = protocol::Binary::fromSpan(span.data(), span.size());
1070 return Response::Success();
1071 }
1072
pushBreakDetails(const String16 & breakReason,std::unique_ptr<protocol::DictionaryValue> breakAuxData)1073 void V8DebuggerAgentImpl::pushBreakDetails(
1074 const String16& breakReason,
1075 std::unique_ptr<protocol::DictionaryValue> breakAuxData) {
1076 m_breakReason.push_back(std::make_pair(breakReason, std::move(breakAuxData)));
1077 }
1078
popBreakDetails()1079 void V8DebuggerAgentImpl::popBreakDetails() {
1080 if (m_breakReason.empty()) return;
1081 m_breakReason.pop_back();
1082 }
1083
clearBreakDetails()1084 void V8DebuggerAgentImpl::clearBreakDetails() {
1085 std::vector<BreakReason> emptyBreakReason;
1086 m_breakReason.swap(emptyBreakReason);
1087 }
1088
schedulePauseOnNextStatement(const String16 & breakReason,std::unique_ptr<protocol::DictionaryValue> data)1089 void V8DebuggerAgentImpl::schedulePauseOnNextStatement(
1090 const String16& breakReason,
1091 std::unique_ptr<protocol::DictionaryValue> data) {
1092 if (isPaused() || !acceptsPause(false) || !m_breakpointsActive) return;
1093 if (m_breakReason.empty()) {
1094 m_debugger->setPauseOnNextCall(true, m_session->contextGroupId());
1095 }
1096 pushBreakDetails(breakReason, std::move(data));
1097 }
1098
cancelPauseOnNextStatement()1099 void V8DebuggerAgentImpl::cancelPauseOnNextStatement() {
1100 if (isPaused() || !acceptsPause(false) || !m_breakpointsActive) return;
1101 if (m_breakReason.size() == 1) {
1102 m_debugger->setPauseOnNextCall(false, m_session->contextGroupId());
1103 }
1104 popBreakDetails();
1105 }
1106
pause()1107 Response V8DebuggerAgentImpl::pause() {
1108 if (!enabled()) return Response::ServerError(kDebuggerNotEnabled);
1109 if (isPaused()) return Response::Success();
1110 if (m_debugger->canBreakProgram()) {
1111 m_debugger->interruptAndBreak(m_session->contextGroupId());
1112 } else {
1113 if (m_breakReason.empty()) {
1114 m_debugger->setPauseOnNextCall(true, m_session->contextGroupId());
1115 }
1116 pushBreakDetails(protocol::Debugger::Paused::ReasonEnum::Other, nullptr);
1117 }
1118 return Response::Success();
1119 }
1120
resume(Maybe<bool> terminateOnResume)1121 Response V8DebuggerAgentImpl::resume(Maybe<bool> terminateOnResume) {
1122 if (!isPaused()) return Response::ServerError(kDebuggerNotPaused);
1123 m_session->releaseObjectGroup(kBacktraceObjectGroup);
1124 m_debugger->continueProgram(m_session->contextGroupId(),
1125 terminateOnResume.fromMaybe(false));
1126 return Response::Success();
1127 }
1128
stepOver(Maybe<protocol::Array<protocol::Debugger::LocationRange>> inSkipList)1129 Response V8DebuggerAgentImpl::stepOver(
1130 Maybe<protocol::Array<protocol::Debugger::LocationRange>> inSkipList) {
1131 if (!isPaused()) return Response::ServerError(kDebuggerNotPaused);
1132
1133 if (inSkipList.isJust()) {
1134 const Response res = processSkipList(inSkipList.fromJust());
1135 if (res.IsError()) return res;
1136 } else {
1137 m_skipList.clear();
1138 }
1139
1140 m_session->releaseObjectGroup(kBacktraceObjectGroup);
1141 m_debugger->stepOverStatement(m_session->contextGroupId());
1142 return Response::Success();
1143 }
1144
stepInto(Maybe<bool> inBreakOnAsyncCall,Maybe<protocol::Array<protocol::Debugger::LocationRange>> inSkipList)1145 Response V8DebuggerAgentImpl::stepInto(
1146 Maybe<bool> inBreakOnAsyncCall,
1147 Maybe<protocol::Array<protocol::Debugger::LocationRange>> inSkipList) {
1148 if (!isPaused()) return Response::ServerError(kDebuggerNotPaused);
1149
1150 if (inSkipList.isJust()) {
1151 const Response res = processSkipList(inSkipList.fromJust());
1152 if (res.IsError()) return res;
1153 } else {
1154 m_skipList.clear();
1155 }
1156
1157 m_session->releaseObjectGroup(kBacktraceObjectGroup);
1158 m_debugger->stepIntoStatement(m_session->contextGroupId(),
1159 inBreakOnAsyncCall.fromMaybe(false));
1160 return Response::Success();
1161 }
1162
stepOut()1163 Response V8DebuggerAgentImpl::stepOut() {
1164 if (!isPaused()) return Response::ServerError(kDebuggerNotPaused);
1165 m_session->releaseObjectGroup(kBacktraceObjectGroup);
1166 m_debugger->stepOutOfFunction(m_session->contextGroupId());
1167 return Response::Success();
1168 }
1169
pauseOnAsyncCall(std::unique_ptr<protocol::Runtime::StackTraceId> inParentStackTraceId)1170 Response V8DebuggerAgentImpl::pauseOnAsyncCall(
1171 std::unique_ptr<protocol::Runtime::StackTraceId> inParentStackTraceId) {
1172 // Deprecated, just return OK.
1173 return Response::Success();
1174 }
1175
setPauseOnExceptions(const String16 & stringPauseState)1176 Response V8DebuggerAgentImpl::setPauseOnExceptions(
1177 const String16& stringPauseState) {
1178 if (!enabled()) return Response::ServerError(kDebuggerNotEnabled);
1179 v8::debug::ExceptionBreakState pauseState;
1180 if (stringPauseState == "none") {
1181 pauseState = v8::debug::NoBreakOnException;
1182 } else if (stringPauseState == "all") {
1183 pauseState = v8::debug::BreakOnAnyException;
1184 } else if (stringPauseState == "uncaught") {
1185 pauseState = v8::debug::BreakOnUncaughtException;
1186 } else {
1187 return Response::ServerError("Unknown pause on exceptions mode: " +
1188 stringPauseState.utf8());
1189 }
1190 setPauseOnExceptionsImpl(pauseState);
1191 return Response::Success();
1192 }
1193
setPauseOnExceptionsImpl(int pauseState)1194 void V8DebuggerAgentImpl::setPauseOnExceptionsImpl(int pauseState) {
1195 // TODO(dgozman): this changes the global state and forces all context groups
1196 // to pause. We should make this flag be per-context-group.
1197 m_debugger->setPauseOnExceptionsState(
1198 static_cast<v8::debug::ExceptionBreakState>(pauseState));
1199 m_state->setInteger(DebuggerAgentState::pauseOnExceptionsState, pauseState);
1200 }
1201
evaluateOnCallFrame(const String16 & callFrameId,const String16 & expression,Maybe<String16> objectGroup,Maybe<bool> includeCommandLineAPI,Maybe<bool> silent,Maybe<bool> returnByValue,Maybe<bool> generatePreview,Maybe<bool> throwOnSideEffect,Maybe<double> timeout,std::unique_ptr<RemoteObject> * result,Maybe<protocol::Runtime::ExceptionDetails> * exceptionDetails)1202 Response V8DebuggerAgentImpl::evaluateOnCallFrame(
1203 const String16& callFrameId, const String16& expression,
1204 Maybe<String16> objectGroup, Maybe<bool> includeCommandLineAPI,
1205 Maybe<bool> silent, Maybe<bool> returnByValue, Maybe<bool> generatePreview,
1206 Maybe<bool> throwOnSideEffect, Maybe<double> timeout,
1207 std::unique_ptr<RemoteObject>* result,
1208 Maybe<protocol::Runtime::ExceptionDetails>* exceptionDetails) {
1209 if (!isPaused()) return Response::ServerError(kDebuggerNotPaused);
1210 InjectedScript::CallFrameScope scope(m_session, callFrameId);
1211 Response response = scope.initialize();
1212 if (!response.IsSuccess()) return response;
1213 if (includeCommandLineAPI.fromMaybe(false)) scope.installCommandLineAPI();
1214 if (silent.fromMaybe(false)) scope.ignoreExceptionsAndMuteConsole();
1215
1216 int frameOrdinal = static_cast<int>(scope.frameOrdinal());
1217 auto it = v8::debug::StackTraceIterator::Create(m_isolate, frameOrdinal);
1218 if (it->Done()) {
1219 return Response::ServerError("Could not find call frame with given id");
1220 }
1221
1222 v8::MaybeLocal<v8::Value> maybeResultValue;
1223 {
1224 V8InspectorImpl::EvaluateScope evaluateScope(scope);
1225 if (timeout.isJust()) {
1226 response = evaluateScope.setTimeout(timeout.fromJust() / 1000.0);
1227 if (!response.IsSuccess()) return response;
1228 }
1229 maybeResultValue = it->Evaluate(toV8String(m_isolate, expression),
1230 throwOnSideEffect.fromMaybe(false));
1231 }
1232 // Re-initialize after running client's code, as it could have destroyed
1233 // context or session.
1234 response = scope.initialize();
1235 if (!response.IsSuccess()) return response;
1236 WrapMode mode = generatePreview.fromMaybe(false) ? WrapMode::kWithPreview
1237 : WrapMode::kNoPreview;
1238 if (returnByValue.fromMaybe(false)) mode = WrapMode::kForceValue;
1239 return scope.injectedScript()->wrapEvaluateResult(
1240 maybeResultValue, scope.tryCatch(), objectGroup.fromMaybe(""), mode,
1241 result, exceptionDetails);
1242 }
1243
executeWasmEvaluator(const String16 & callFrameId,const protocol::Binary & evaluator,Maybe<double> timeout,std::unique_ptr<protocol::Runtime::RemoteObject> * result,Maybe<protocol::Runtime::ExceptionDetails> * exceptionDetails)1244 Response V8DebuggerAgentImpl::executeWasmEvaluator(
1245 const String16& callFrameId, const protocol::Binary& evaluator,
1246 Maybe<double> timeout,
1247 std::unique_ptr<protocol::Runtime::RemoteObject>* result,
1248 Maybe<protocol::Runtime::ExceptionDetails>* exceptionDetails) {
1249 if (!v8::debug::StackTraceIterator::SupportsWasmDebugEvaluate()) {
1250 return Response::ServerError(
1251 "--wasm-expose-debug-eval is required to execte evaluator modules");
1252 }
1253 if (!isPaused()) return Response::ServerError(kDebuggerNotPaused);
1254 InjectedScript::CallFrameScope scope(m_session, callFrameId);
1255 Response response = scope.initialize();
1256 if (!response.IsSuccess()) return response;
1257
1258 int frameOrdinal = static_cast<int>(scope.frameOrdinal());
1259 std::unique_ptr<v8::debug::StackTraceIterator> it =
1260 v8::debug::StackTraceIterator::Create(m_isolate, frameOrdinal);
1261 if (it->Done()) {
1262 return Response::ServerError("Could not find call frame with given id");
1263 }
1264 if (!it->GetScript()->IsWasm()) {
1265 return Response::ServerError(
1266 "executeWasmEvaluator can only be called on WebAssembly frames");
1267 }
1268
1269 v8::MaybeLocal<v8::Value> maybeResultValue;
1270 {
1271 V8InspectorImpl::EvaluateScope evaluateScope(scope);
1272 if (timeout.isJust()) {
1273 response = evaluateScope.setTimeout(timeout.fromJust() / 1000.0);
1274 if (!response.IsSuccess()) return response;
1275 }
1276 v8::MaybeLocal<v8::String> eval_result =
1277 it->EvaluateWasm({evaluator.data(), evaluator.size()}, frameOrdinal);
1278 if (!eval_result.IsEmpty()) maybeResultValue = eval_result.ToLocalChecked();
1279 }
1280
1281 // Re-initialize after running client's code, as it could have destroyed
1282 // context or session.
1283 response = scope.initialize();
1284 if (!response.IsSuccess()) return response;
1285
1286 String16 object_group = "";
1287 InjectedScript* injected_script = scope.injectedScript();
1288 return injected_script->wrapEvaluateResult(maybeResultValue, scope.tryCatch(),
1289 object_group, WrapMode::kNoPreview,
1290 result, exceptionDetails);
1291 }
1292
setVariableValue(int scopeNumber,const String16 & variableName,std::unique_ptr<protocol::Runtime::CallArgument> newValueArgument,const String16 & callFrameId)1293 Response V8DebuggerAgentImpl::setVariableValue(
1294 int scopeNumber, const String16& variableName,
1295 std::unique_ptr<protocol::Runtime::CallArgument> newValueArgument,
1296 const String16& callFrameId) {
1297 if (!enabled()) return Response::ServerError(kDebuggerNotEnabled);
1298 if (!isPaused()) return Response::ServerError(kDebuggerNotPaused);
1299 InjectedScript::CallFrameScope scope(m_session, callFrameId);
1300 Response response = scope.initialize();
1301 if (!response.IsSuccess()) return response;
1302 v8::Local<v8::Value> newValue;
1303 response = scope.injectedScript()->resolveCallArgument(newValueArgument.get(),
1304 &newValue);
1305 if (!response.IsSuccess()) return response;
1306
1307 int frameOrdinal = static_cast<int>(scope.frameOrdinal());
1308 auto it = v8::debug::StackTraceIterator::Create(m_isolate, frameOrdinal);
1309 if (it->Done()) {
1310 return Response::ServerError("Could not find call frame with given id");
1311 }
1312 auto scopeIterator = it->GetScopeIterator();
1313 while (!scopeIterator->Done() && scopeNumber > 0) {
1314 --scopeNumber;
1315 scopeIterator->Advance();
1316 }
1317 if (scopeNumber != 0) {
1318 return Response::ServerError("Could not find scope with given number");
1319 }
1320
1321 if (!scopeIterator->SetVariableValue(toV8String(m_isolate, variableName),
1322 newValue) ||
1323 scope.tryCatch().HasCaught()) {
1324 return Response::InternalError();
1325 }
1326 return Response::Success();
1327 }
1328
setReturnValue(std::unique_ptr<protocol::Runtime::CallArgument> protocolNewValue)1329 Response V8DebuggerAgentImpl::setReturnValue(
1330 std::unique_ptr<protocol::Runtime::CallArgument> protocolNewValue) {
1331 if (!enabled()) return Response::ServerError(kDebuggerNotEnabled);
1332 if (!isPaused()) return Response::ServerError(kDebuggerNotPaused);
1333 v8::HandleScope handleScope(m_isolate);
1334 auto iterator = v8::debug::StackTraceIterator::Create(m_isolate);
1335 if (iterator->Done()) {
1336 return Response::ServerError("Could not find top call frame");
1337 }
1338 if (iterator->GetReturnValue().IsEmpty()) {
1339 return Response::ServerError(
1340 "Could not update return value at non-return position");
1341 }
1342 InjectedScript::ContextScope scope(m_session, iterator->GetContextId());
1343 Response response = scope.initialize();
1344 if (!response.IsSuccess()) return response;
1345 v8::Local<v8::Value> newValue;
1346 response = scope.injectedScript()->resolveCallArgument(protocolNewValue.get(),
1347 &newValue);
1348 if (!response.IsSuccess()) return response;
1349 v8::debug::SetReturnValue(m_isolate, newValue);
1350 return Response::Success();
1351 }
1352
setAsyncCallStackDepth(int depth)1353 Response V8DebuggerAgentImpl::setAsyncCallStackDepth(int depth) {
1354 if (!enabled() && !m_session->runtimeAgent()->enabled()) {
1355 return Response::ServerError(kDebuggerNotEnabled);
1356 }
1357 m_state->setInteger(DebuggerAgentState::asyncCallStackDepth, depth);
1358 m_debugger->setAsyncCallStackDepth(this, depth);
1359 return Response::Success();
1360 }
1361
setBlackboxPatterns(std::unique_ptr<protocol::Array<String16>> patterns)1362 Response V8DebuggerAgentImpl::setBlackboxPatterns(
1363 std::unique_ptr<protocol::Array<String16>> patterns) {
1364 if (patterns->empty()) {
1365 m_blackboxPattern = nullptr;
1366 resetBlackboxedStateCache();
1367 m_state->remove(DebuggerAgentState::blackboxPattern);
1368 return Response::Success();
1369 }
1370
1371 String16Builder patternBuilder;
1372 patternBuilder.append('(');
1373 for (size_t i = 0; i < patterns->size() - 1; ++i) {
1374 patternBuilder.append((*patterns)[i]);
1375 patternBuilder.append("|");
1376 }
1377 patternBuilder.append(patterns->back());
1378 patternBuilder.append(')');
1379 String16 pattern = patternBuilder.toString();
1380 Response response = setBlackboxPattern(pattern);
1381 if (!response.IsSuccess()) return response;
1382 resetBlackboxedStateCache();
1383 m_state->setString(DebuggerAgentState::blackboxPattern, pattern);
1384 return Response::Success();
1385 }
1386
setBlackboxPattern(const String16 & pattern)1387 Response V8DebuggerAgentImpl::setBlackboxPattern(const String16& pattern) {
1388 std::unique_ptr<V8Regex> regex(new V8Regex(
1389 m_inspector, pattern, true /** caseSensitive */, false /** multiline */));
1390 if (!regex->isValid())
1391 return Response::ServerError("Pattern parser error: " +
1392 regex->errorMessage().utf8());
1393 m_blackboxPattern = std::move(regex);
1394 return Response::Success();
1395 }
1396
resetBlackboxedStateCache()1397 void V8DebuggerAgentImpl::resetBlackboxedStateCache() {
1398 for (const auto& it : m_scripts) {
1399 it.second->resetBlackboxedStateCache();
1400 }
1401 }
1402
setBlackboxedRanges(const String16 & scriptId,std::unique_ptr<protocol::Array<protocol::Debugger::ScriptPosition>> inPositions)1403 Response V8DebuggerAgentImpl::setBlackboxedRanges(
1404 const String16& scriptId,
1405 std::unique_ptr<protocol::Array<protocol::Debugger::ScriptPosition>>
1406 inPositions) {
1407 auto it = m_scripts.find(scriptId);
1408 if (it == m_scripts.end())
1409 return Response::ServerError("No script with passed id.");
1410
1411 if (inPositions->empty()) {
1412 m_blackboxedPositions.erase(scriptId);
1413 it->second->resetBlackboxedStateCache();
1414 return Response::Success();
1415 }
1416
1417 std::vector<std::pair<int, int>> positions;
1418 positions.reserve(inPositions->size());
1419 for (const std::unique_ptr<protocol::Debugger::ScriptPosition>& position :
1420 *inPositions) {
1421 Response res = isValidPosition(position.get());
1422 if (res.IsError()) return res;
1423
1424 positions.push_back(
1425 std::make_pair(position->getLineNumber(), position->getColumnNumber()));
1426 }
1427 Response res = isValidRangeOfPositions(positions);
1428 if (res.IsError()) return res;
1429
1430 m_blackboxedPositions[scriptId] = positions;
1431 it->second->resetBlackboxedStateCache();
1432 return Response::Success();
1433 }
1434
currentCallFrames(std::unique_ptr<Array<CallFrame>> * result)1435 Response V8DebuggerAgentImpl::currentCallFrames(
1436 std::unique_ptr<Array<CallFrame>>* result) {
1437 if (!isPaused()) {
1438 *result = std::make_unique<Array<CallFrame>>();
1439 return Response::Success();
1440 }
1441 v8::HandleScope handles(m_isolate);
1442 *result = std::make_unique<Array<CallFrame>>();
1443 auto iterator = v8::debug::StackTraceIterator::Create(m_isolate);
1444 int frameOrdinal = 0;
1445 for (; !iterator->Done(); iterator->Advance(), frameOrdinal++) {
1446 int contextId = iterator->GetContextId();
1447 InjectedScript* injectedScript = nullptr;
1448 if (contextId) m_session->findInjectedScript(contextId, injectedScript);
1449 String16 callFrameId = RemoteCallFrameId::serialize(
1450 m_inspector->isolateId(), contextId, frameOrdinal);
1451
1452 v8::debug::Location loc = iterator->GetSourceLocation();
1453
1454 std::unique_ptr<Array<Scope>> scopes;
1455 auto scopeIterator = iterator->GetScopeIterator();
1456 Response res =
1457 buildScopes(m_isolate, scopeIterator.get(), injectedScript, &scopes);
1458 if (!res.IsSuccess()) return res;
1459
1460 std::unique_ptr<RemoteObject> protocolReceiver;
1461 if (injectedScript) {
1462 v8::Local<v8::Value> receiver;
1463 if (iterator->GetReceiver().ToLocal(&receiver)) {
1464 res =
1465 injectedScript->wrapObject(receiver, kBacktraceObjectGroup,
1466 WrapMode::kNoPreview, &protocolReceiver);
1467 if (!res.IsSuccess()) return res;
1468 }
1469 }
1470 if (!protocolReceiver) {
1471 protocolReceiver = RemoteObject::create()
1472 .setType(RemoteObject::TypeEnum::Undefined)
1473 .build();
1474 }
1475
1476 v8::Local<v8::debug::Script> script = iterator->GetScript();
1477 DCHECK(!script.IsEmpty());
1478 std::unique_ptr<protocol::Debugger::Location> location =
1479 protocol::Debugger::Location::create()
1480 .setScriptId(String16::fromInteger(script->Id()))
1481 .setLineNumber(loc.GetLineNumber())
1482 .setColumnNumber(loc.GetColumnNumber())
1483 .build();
1484 String16 scriptId = String16::fromInteger(script->Id());
1485 ScriptsMap::iterator scriptIterator =
1486 m_scripts.find(location->getScriptId());
1487 String16 url;
1488 if (scriptIterator != m_scripts.end()) {
1489 url = scriptIterator->second->sourceURL();
1490 }
1491
1492 auto frame = CallFrame::create()
1493 .setCallFrameId(callFrameId)
1494 .setFunctionName(toProtocolString(
1495 m_isolate, iterator->GetFunctionDebugName()))
1496 .setLocation(std::move(location))
1497 .setUrl(url)
1498 .setScopeChain(std::move(scopes))
1499 .setThis(std::move(protocolReceiver))
1500 .build();
1501
1502 v8::Local<v8::Function> func = iterator->GetFunction();
1503 if (!func.IsEmpty()) {
1504 frame->setFunctionLocation(
1505 protocol::Debugger::Location::create()
1506 .setScriptId(String16::fromInteger(func->ScriptId()))
1507 .setLineNumber(func->GetScriptLineNumber())
1508 .setColumnNumber(func->GetScriptColumnNumber())
1509 .build());
1510 }
1511
1512 v8::Local<v8::Value> returnValue = iterator->GetReturnValue();
1513 if (!returnValue.IsEmpty() && injectedScript) {
1514 std::unique_ptr<RemoteObject> value;
1515 res = injectedScript->wrapObject(returnValue, kBacktraceObjectGroup,
1516 WrapMode::kNoPreview, &value);
1517 if (!res.IsSuccess()) return res;
1518 frame->setReturnValue(std::move(value));
1519 }
1520 (*result)->emplace_back(std::move(frame));
1521 }
1522 return Response::Success();
1523 }
1524
1525 std::unique_ptr<protocol::Runtime::StackTrace>
currentAsyncStackTrace()1526 V8DebuggerAgentImpl::currentAsyncStackTrace() {
1527 std::shared_ptr<AsyncStackTrace> asyncParent =
1528 m_debugger->currentAsyncParent();
1529 if (!asyncParent) return nullptr;
1530 return asyncParent->buildInspectorObject(
1531 m_debugger, m_debugger->maxAsyncCallChainDepth() - 1);
1532 }
1533
1534 std::unique_ptr<protocol::Runtime::StackTraceId>
currentExternalStackTrace()1535 V8DebuggerAgentImpl::currentExternalStackTrace() {
1536 V8StackTraceId externalParent = m_debugger->currentExternalParent();
1537 if (externalParent.IsInvalid()) return nullptr;
1538 return protocol::Runtime::StackTraceId::create()
1539 .setId(stackTraceIdToString(externalParent.id))
1540 .setDebuggerId(V8DebuggerId(externalParent.debugger_id).toString())
1541 .build();
1542 }
1543
isPaused() const1544 bool V8DebuggerAgentImpl::isPaused() const {
1545 return m_debugger->isPausedInContextGroup(m_session->contextGroupId());
1546 }
1547
getScriptLanguage(const V8DebuggerScript & script)1548 static String16 getScriptLanguage(const V8DebuggerScript& script) {
1549 switch (script.getLanguage()) {
1550 case V8DebuggerScript::Language::WebAssembly:
1551 return protocol::Debugger::ScriptLanguageEnum::WebAssembly;
1552 case V8DebuggerScript::Language::JavaScript:
1553 return protocol::Debugger::ScriptLanguageEnum::JavaScript;
1554 }
1555 }
1556
getDebugSymbolTypeName(v8::debug::WasmScript::DebugSymbolsType type)1557 static const char* getDebugSymbolTypeName(
1558 v8::debug::WasmScript::DebugSymbolsType type) {
1559 switch (type) {
1560 case v8::debug::WasmScript::DebugSymbolsType::None:
1561 return v8_inspector::protocol::Debugger::DebugSymbols::TypeEnum::None;
1562 case v8::debug::WasmScript::DebugSymbolsType::SourceMap:
1563 return v8_inspector::protocol::Debugger::DebugSymbols::TypeEnum::
1564 SourceMap;
1565 case v8::debug::WasmScript::DebugSymbolsType::EmbeddedDWARF:
1566 return v8_inspector::protocol::Debugger::DebugSymbols::TypeEnum::
1567 EmbeddedDWARF;
1568 case v8::debug::WasmScript::DebugSymbolsType::ExternalDWARF:
1569 return v8_inspector::protocol::Debugger::DebugSymbols::TypeEnum::
1570 ExternalDWARF;
1571 }
1572 }
1573
getDebugSymbols(const V8DebuggerScript & script)1574 static std::unique_ptr<protocol::Debugger::DebugSymbols> getDebugSymbols(
1575 const V8DebuggerScript& script) {
1576 v8::debug::WasmScript::DebugSymbolsType type;
1577 if (!script.getDebugSymbolsType().To(&type)) return {};
1578
1579 std::unique_ptr<protocol::Debugger::DebugSymbols> debugSymbols =
1580 v8_inspector::protocol::Debugger::DebugSymbols::create()
1581 .setType(getDebugSymbolTypeName(type))
1582 .build();
1583 String16 externalUrl;
1584 if (script.getExternalDebugSymbolsURL().To(&externalUrl)) {
1585 debugSymbols->setExternalURL(externalUrl);
1586 }
1587 return debugSymbols;
1588 }
1589
didParseSource(std::unique_ptr<V8DebuggerScript> script,bool success)1590 void V8DebuggerAgentImpl::didParseSource(
1591 std::unique_ptr<V8DebuggerScript> script, bool success) {
1592 v8::HandleScope handles(m_isolate);
1593 if (!success) {
1594 DCHECK(!script->isSourceLoadedLazily());
1595 String16 scriptSource = script->source(0);
1596 script->setSourceURL(findSourceURL(scriptSource, false));
1597 script->setSourceMappingURL(findSourceMapURL(scriptSource, false));
1598 }
1599
1600 int contextId = script->executionContextId();
1601 int contextGroupId = m_inspector->contextGroupId(contextId);
1602 InspectedContext* inspected =
1603 m_inspector->getContext(contextGroupId, contextId);
1604 std::unique_ptr<protocol::DictionaryValue> executionContextAuxData;
1605 if (inspected) {
1606 // Script reused between different groups/sessions can have a stale
1607 // execution context id.
1608 const String16& aux = inspected->auxData();
1609 std::vector<uint8_t> cbor;
1610 v8_crdtp::json::ConvertJSONToCBOR(
1611 v8_crdtp::span<uint16_t>(aux.characters16(), aux.length()), &cbor);
1612 executionContextAuxData = protocol::DictionaryValue::cast(
1613 protocol::Value::parseBinary(cbor.data(), cbor.size()));
1614 }
1615 bool isLiveEdit = script->isLiveEdit();
1616 bool hasSourceURLComment = script->hasSourceURLComment();
1617 bool isModule = script->isModule();
1618 String16 scriptId = script->scriptId();
1619 String16 scriptURL = script->sourceURL();
1620 String16 embedderName = script->embedderName();
1621 String16 scriptLanguage = getScriptLanguage(*script);
1622 Maybe<int> codeOffset;
1623 if (script->getLanguage() == V8DebuggerScript::Language::WebAssembly)
1624 codeOffset = script->codeOffset();
1625 std::unique_ptr<protocol::Debugger::DebugSymbols> debugSymbols =
1626 getDebugSymbols(*script);
1627
1628 m_scripts[scriptId] = std::move(script);
1629 // Release the strong reference to get notified when debugger is the only
1630 // one that holds the script. Has to be done after script added to m_scripts.
1631 m_scripts[scriptId]->MakeWeak();
1632
1633 ScriptsMap::iterator scriptIterator = m_scripts.find(scriptId);
1634 DCHECK(scriptIterator != m_scripts.end());
1635 V8DebuggerScript* scriptRef = scriptIterator->second.get();
1636 // V8 could create functions for parsed scripts before reporting and asks
1637 // inspector about blackboxed state, we should reset state each time when we
1638 // make any change that change isFunctionBlackboxed output - adding parsed
1639 // script is changing.
1640 scriptRef->resetBlackboxedStateCache();
1641
1642 Maybe<String16> sourceMapURLParam = scriptRef->sourceMappingURL();
1643 Maybe<protocol::DictionaryValue> executionContextAuxDataParam(
1644 std::move(executionContextAuxData));
1645 const bool* isLiveEditParam = isLiveEdit ? &isLiveEdit : nullptr;
1646 const bool* hasSourceURLParam =
1647 hasSourceURLComment ? &hasSourceURLComment : nullptr;
1648 const bool* isModuleParam = isModule ? &isModule : nullptr;
1649 std::unique_ptr<V8StackTraceImpl> stack =
1650 V8StackTraceImpl::capture(m_inspector->debugger(), contextGroupId, 1);
1651 std::unique_ptr<protocol::Runtime::StackTrace> stackTrace =
1652 stack && !stack->isEmpty()
1653 ? stack->buildInspectorObjectImpl(m_debugger, 0)
1654 : nullptr;
1655
1656 if (!success) {
1657 m_frontend.scriptFailedToParse(
1658 scriptId, scriptURL, scriptRef->startLine(), scriptRef->startColumn(),
1659 scriptRef->endLine(), scriptRef->endColumn(), contextId,
1660 scriptRef->hash(), std::move(executionContextAuxDataParam),
1661 std::move(sourceMapURLParam), hasSourceURLParam, isModuleParam,
1662 scriptRef->length(), std::move(stackTrace), std::move(codeOffset),
1663 std::move(scriptLanguage), embedderName);
1664 return;
1665 }
1666
1667 if (scriptRef->isSourceLoadedLazily()) {
1668 m_frontend.scriptParsed(
1669 scriptId, scriptURL, 0, 0, 0, 0, contextId, scriptRef->hash(),
1670 std::move(executionContextAuxDataParam), isLiveEditParam,
1671 std::move(sourceMapURLParam), hasSourceURLParam, isModuleParam, 0,
1672 std::move(stackTrace), std::move(codeOffset), std::move(scriptLanguage),
1673 std::move(debugSymbols), embedderName);
1674 } else {
1675 m_frontend.scriptParsed(
1676 scriptId, scriptURL, scriptRef->startLine(), scriptRef->startColumn(),
1677 scriptRef->endLine(), scriptRef->endColumn(), contextId,
1678 scriptRef->hash(), std::move(executionContextAuxDataParam),
1679 isLiveEditParam, std::move(sourceMapURLParam), hasSourceURLParam,
1680 isModuleParam, scriptRef->length(), std::move(stackTrace),
1681 std::move(codeOffset), std::move(scriptLanguage),
1682 std::move(debugSymbols), embedderName);
1683 }
1684
1685 std::vector<protocol::DictionaryValue*> potentialBreakpoints;
1686 if (!scriptURL.isEmpty()) {
1687 protocol::DictionaryValue* breakpointsByUrl =
1688 m_state->getObject(DebuggerAgentState::breakpointsByUrl);
1689 if (breakpointsByUrl) {
1690 potentialBreakpoints.push_back(breakpointsByUrl->getObject(scriptURL));
1691 }
1692 potentialBreakpoints.push_back(
1693 m_state->getObject(DebuggerAgentState::breakpointsByRegex));
1694 }
1695 protocol::DictionaryValue* breakpointsByScriptHash =
1696 m_state->getObject(DebuggerAgentState::breakpointsByScriptHash);
1697 if (breakpointsByScriptHash) {
1698 potentialBreakpoints.push_back(
1699 breakpointsByScriptHash->getObject(scriptRef->hash()));
1700 }
1701 protocol::DictionaryValue* breakpointHints =
1702 m_state->getObject(DebuggerAgentState::breakpointHints);
1703 for (auto breakpoints : potentialBreakpoints) {
1704 if (!breakpoints) continue;
1705 for (size_t i = 0; i < breakpoints->size(); ++i) {
1706 auto breakpointWithCondition = breakpoints->at(i);
1707 String16 breakpointId = breakpointWithCondition.first;
1708
1709 BreakpointType type;
1710 String16 selector;
1711 int lineNumber = 0;
1712 int columnNumber = 0;
1713 parseBreakpointId(breakpointId, &type, &selector, &lineNumber,
1714 &columnNumber);
1715
1716 if (!matches(m_inspector, *scriptRef, type, selector)) continue;
1717 String16 condition;
1718 breakpointWithCondition.second->asString(&condition);
1719 String16 hint;
1720 bool hasHint =
1721 breakpointHints && breakpointHints->getString(breakpointId, &hint);
1722 if (hasHint) {
1723 adjustBreakpointLocation(*scriptRef, hint, &lineNumber, &columnNumber);
1724 }
1725 std::unique_ptr<protocol::Debugger::Location> location =
1726 setBreakpointImpl(breakpointId, scriptId, condition, lineNumber,
1727 columnNumber);
1728 if (location)
1729 m_frontend.breakpointResolved(breakpointId, std::move(location));
1730 }
1731 }
1732 setScriptInstrumentationBreakpointIfNeeded(scriptRef);
1733 }
1734
setScriptInstrumentationBreakpointIfNeeded(V8DebuggerScript * scriptRef)1735 void V8DebuggerAgentImpl::setScriptInstrumentationBreakpointIfNeeded(
1736 V8DebuggerScript* scriptRef) {
1737 protocol::DictionaryValue* breakpoints =
1738 m_state->getObject(DebuggerAgentState::instrumentationBreakpoints);
1739 if (!breakpoints) return;
1740 bool isBlackboxed = isFunctionBlackboxed(
1741 scriptRef->scriptId(), v8::debug::Location(0, 0),
1742 v8::debug::Location(scriptRef->endLine(), scriptRef->endColumn()));
1743 if (isBlackboxed) return;
1744
1745 String16 sourceMapURL = scriptRef->sourceMappingURL();
1746 String16 breakpointId = generateInstrumentationBreakpointId(
1747 InstrumentationEnum::BeforeScriptExecution);
1748 if (!breakpoints->get(breakpointId)) {
1749 if (sourceMapURL.isEmpty()) return;
1750 breakpointId = generateInstrumentationBreakpointId(
1751 InstrumentationEnum::BeforeScriptWithSourceMapExecution);
1752 if (!breakpoints->get(breakpointId)) return;
1753 }
1754 v8::debug::BreakpointId debuggerBreakpointId;
1755 if (!scriptRef->setBreakpointOnRun(&debuggerBreakpointId)) return;
1756 std::unique_ptr<protocol::DictionaryValue> data =
1757 protocol::DictionaryValue::create();
1758 data->setString("url", scriptRef->sourceURL());
1759 data->setString("scriptId", scriptRef->scriptId());
1760 if (!sourceMapURL.isEmpty()) data->setString("sourceMapURL", sourceMapURL);
1761
1762 m_breakpointsOnScriptRun[debuggerBreakpointId] = std::move(data);
1763 m_debuggerBreakpointIdToBreakpointId[debuggerBreakpointId] = breakpointId;
1764 m_breakpointIdToDebuggerBreakpointIds[breakpointId].push_back(
1765 debuggerBreakpointId);
1766 }
1767
didPause(int contextId,v8::Local<v8::Value> exception,const std::vector<v8::debug::BreakpointId> & hitBreakpoints,v8::debug::ExceptionType exceptionType,bool isUncaught,bool isOOMBreak,bool isAssert)1768 void V8DebuggerAgentImpl::didPause(
1769 int contextId, v8::Local<v8::Value> exception,
1770 const std::vector<v8::debug::BreakpointId>& hitBreakpoints,
1771 v8::debug::ExceptionType exceptionType, bool isUncaught, bool isOOMBreak,
1772 bool isAssert) {
1773 v8::HandleScope handles(m_isolate);
1774
1775 std::vector<BreakReason> hitReasons;
1776
1777 if (isOOMBreak) {
1778 hitReasons.push_back(
1779 std::make_pair(protocol::Debugger::Paused::ReasonEnum::OOM, nullptr));
1780 } else if (isAssert) {
1781 hitReasons.push_back(std::make_pair(
1782 protocol::Debugger::Paused::ReasonEnum::Assert, nullptr));
1783 } else if (!exception.IsEmpty()) {
1784 InjectedScript* injectedScript = nullptr;
1785 m_session->findInjectedScript(contextId, injectedScript);
1786 if (injectedScript) {
1787 String16 breakReason =
1788 exceptionType == v8::debug::kPromiseRejection
1789 ? protocol::Debugger::Paused::ReasonEnum::PromiseRejection
1790 : protocol::Debugger::Paused::ReasonEnum::Exception;
1791 std::unique_ptr<protocol::Runtime::RemoteObject> obj;
1792 injectedScript->wrapObject(exception, kBacktraceObjectGroup,
1793 WrapMode::kNoPreview, &obj);
1794 std::unique_ptr<protocol::DictionaryValue> breakAuxData;
1795 if (obj) {
1796 std::vector<uint8_t> serialized;
1797 obj->AppendSerialized(&serialized);
1798 breakAuxData = protocol::DictionaryValue::cast(
1799 protocol::Value::parseBinary(serialized.data(), serialized.size()));
1800 breakAuxData->setBoolean("uncaught", isUncaught);
1801 }
1802 hitReasons.push_back(
1803 std::make_pair(breakReason, std::move(breakAuxData)));
1804 }
1805 }
1806
1807 auto hitBreakpointIds = std::make_unique<Array<String16>>();
1808
1809 for (const auto& id : hitBreakpoints) {
1810 auto it = m_breakpointsOnScriptRun.find(id);
1811 if (it != m_breakpointsOnScriptRun.end()) {
1812 hitReasons.push_back(std::make_pair(
1813 protocol::Debugger::Paused::ReasonEnum::Instrumentation,
1814 std::move(it->second)));
1815 m_breakpointsOnScriptRun.erase(it);
1816 continue;
1817 }
1818 auto breakpointIterator = m_debuggerBreakpointIdToBreakpointId.find(id);
1819 if (breakpointIterator == m_debuggerBreakpointIdToBreakpointId.end()) {
1820 continue;
1821 }
1822 const String16& breakpointId = breakpointIterator->second;
1823 hitBreakpointIds->emplace_back(breakpointId);
1824 BreakpointType type;
1825 parseBreakpointId(breakpointId, &type);
1826 if (type != BreakpointType::kDebugCommand) continue;
1827 hitReasons.push_back(std::make_pair(
1828 protocol::Debugger::Paused::ReasonEnum::DebugCommand, nullptr));
1829 }
1830
1831 for (size_t i = 0; i < m_breakReason.size(); ++i) {
1832 hitReasons.push_back(std::move(m_breakReason[i]));
1833 }
1834 clearBreakDetails();
1835
1836 String16 breakReason = protocol::Debugger::Paused::ReasonEnum::Other;
1837 std::unique_ptr<protocol::DictionaryValue> breakAuxData;
1838 if (hitReasons.size() == 1) {
1839 breakReason = hitReasons[0].first;
1840 breakAuxData = std::move(hitReasons[0].second);
1841 } else if (hitReasons.size() > 1) {
1842 breakReason = protocol::Debugger::Paused::ReasonEnum::Ambiguous;
1843 std::unique_ptr<protocol::ListValue> reasons =
1844 protocol::ListValue::create();
1845 for (size_t i = 0; i < hitReasons.size(); ++i) {
1846 std::unique_ptr<protocol::DictionaryValue> reason =
1847 protocol::DictionaryValue::create();
1848 reason->setString("reason", hitReasons[i].first);
1849 if (hitReasons[i].second)
1850 reason->setObject("auxData", std::move(hitReasons[i].second));
1851 reasons->pushValue(std::move(reason));
1852 }
1853 breakAuxData = protocol::DictionaryValue::create();
1854 breakAuxData->setArray("reasons", std::move(reasons));
1855 }
1856
1857 std::unique_ptr<Array<CallFrame>> protocolCallFrames;
1858 Response response = currentCallFrames(&protocolCallFrames);
1859 if (!response.IsSuccess())
1860 protocolCallFrames = std::make_unique<Array<CallFrame>>();
1861
1862 m_frontend.paused(std::move(protocolCallFrames), breakReason,
1863 std::move(breakAuxData), std::move(hitBreakpointIds),
1864 currentAsyncStackTrace(), currentExternalStackTrace());
1865 }
1866
didContinue()1867 void V8DebuggerAgentImpl::didContinue() {
1868 clearBreakDetails();
1869 m_frontend.resumed();
1870 m_frontend.flush();
1871 }
1872
breakProgram(const String16 & breakReason,std::unique_ptr<protocol::DictionaryValue> data)1873 void V8DebuggerAgentImpl::breakProgram(
1874 const String16& breakReason,
1875 std::unique_ptr<protocol::DictionaryValue> data) {
1876 if (!enabled() || m_skipAllPauses || !m_debugger->canBreakProgram()) return;
1877 std::vector<BreakReason> currentScheduledReason;
1878 currentScheduledReason.swap(m_breakReason);
1879 pushBreakDetails(breakReason, std::move(data));
1880
1881 int contextGroupId = m_session->contextGroupId();
1882 int sessionId = m_session->sessionId();
1883 V8InspectorImpl* inspector = m_inspector;
1884 m_debugger->breakProgram(contextGroupId);
1885 // Check that session and |this| are still around.
1886 if (!inspector->sessionById(contextGroupId, sessionId)) return;
1887 if (!enabled()) return;
1888
1889 popBreakDetails();
1890 m_breakReason.swap(currentScheduledReason);
1891 if (!m_breakReason.empty()) {
1892 m_debugger->setPauseOnNextCall(true, m_session->contextGroupId());
1893 }
1894 }
1895
setBreakpointFor(v8::Local<v8::Function> function,v8::Local<v8::String> condition,BreakpointSource source)1896 void V8DebuggerAgentImpl::setBreakpointFor(v8::Local<v8::Function> function,
1897 v8::Local<v8::String> condition,
1898 BreakpointSource source) {
1899 String16 breakpointId = generateBreakpointId(
1900 source == DebugCommandBreakpointSource ? BreakpointType::kDebugCommand
1901 : BreakpointType::kMonitorCommand,
1902 function);
1903 if (m_breakpointIdToDebuggerBreakpointIds.find(breakpointId) !=
1904 m_breakpointIdToDebuggerBreakpointIds.end()) {
1905 return;
1906 }
1907 setBreakpointImpl(breakpointId, function, condition);
1908 }
1909
removeBreakpointFor(v8::Local<v8::Function> function,BreakpointSource source)1910 void V8DebuggerAgentImpl::removeBreakpointFor(v8::Local<v8::Function> function,
1911 BreakpointSource source) {
1912 String16 breakpointId = generateBreakpointId(
1913 source == DebugCommandBreakpointSource ? BreakpointType::kDebugCommand
1914 : BreakpointType::kMonitorCommand,
1915 function);
1916 std::vector<V8DebuggerScript*> scripts;
1917 removeBreakpointImpl(breakpointId, scripts);
1918 }
1919
reset()1920 void V8DebuggerAgentImpl::reset() {
1921 if (!enabled()) return;
1922 m_blackboxedPositions.clear();
1923 resetBlackboxedStateCache();
1924 m_skipList.clear();
1925 m_scripts.clear();
1926 m_cachedScriptIds.clear();
1927 m_cachedScriptSize = 0;
1928 }
1929
ScriptCollected(const V8DebuggerScript * script)1930 void V8DebuggerAgentImpl::ScriptCollected(const V8DebuggerScript* script) {
1931 DCHECK_NE(m_scripts.find(script->scriptId()), m_scripts.end());
1932 m_cachedScriptIds.push_back(script->scriptId());
1933 // TODO(alph): Properly calculate size when sources are one-byte strings.
1934 m_cachedScriptSize += script->length() * sizeof(uint16_t);
1935
1936 while (m_cachedScriptSize > m_maxScriptCacheSize) {
1937 const String16& scriptId = m_cachedScriptIds.front();
1938 size_t scriptSize = m_scripts[scriptId]->length() * sizeof(uint16_t);
1939 DCHECK_GE(m_cachedScriptSize, scriptSize);
1940 m_cachedScriptSize -= scriptSize;
1941 m_scripts.erase(scriptId);
1942 m_cachedScriptIds.pop_front();
1943 }
1944 }
1945
processSkipList(protocol::Array<protocol::Debugger::LocationRange> * skipList)1946 Response V8DebuggerAgentImpl::processSkipList(
1947 protocol::Array<protocol::Debugger::LocationRange>* skipList) {
1948 std::unordered_map<String16, std::vector<std::pair<int, int>>> skipListInit;
1949 for (std::unique_ptr<protocol::Debugger::LocationRange>& range : *skipList) {
1950 protocol::Debugger::ScriptPosition* start = range->getStart();
1951 protocol::Debugger::ScriptPosition* end = range->getEnd();
1952 String16 scriptId = range->getScriptId();
1953
1954 auto it = m_scripts.find(scriptId);
1955 if (it == m_scripts.end())
1956 return Response::ServerError("No script with passed id.");
1957
1958 Response res = isValidPosition(start);
1959 if (res.IsError()) return res;
1960
1961 res = isValidPosition(end);
1962 if (res.IsError()) return res;
1963
1964 skipListInit[scriptId].emplace_back(start->getLineNumber(),
1965 start->getColumnNumber());
1966 skipListInit[scriptId].emplace_back(end->getLineNumber(),
1967 end->getColumnNumber());
1968 }
1969
1970 // Verify that the skipList is sorted, and that all ranges
1971 // are properly defined (start comes before end).
1972 for (auto skipListPair : skipListInit) {
1973 Response res = isValidRangeOfPositions(skipListPair.second);
1974 if (res.IsError()) return res;
1975 }
1976
1977 m_skipList = std::move(skipListInit);
1978 return Response::Success();
1979 }
1980 } // namespace v8_inspector
1981