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 "src/debug/debug-interface.h"
10 #include "src/inspector/injected-script.h"
11 #include "src/inspector/inspected-context.h"
12 #include "src/inspector/java-script-call-frame.h"
13 #include "src/inspector/protocol/Protocol.h"
14 #include "src/inspector/remote-object-id.h"
15 #include "src/inspector/script-breakpoint.h"
16 #include "src/inspector/search-util.h"
17 #include "src/inspector/string-util.h"
18 #include "src/inspector/v8-debugger-script.h"
19 #include "src/inspector/v8-debugger.h"
20 #include "src/inspector/v8-inspector-impl.h"
21 #include "src/inspector/v8-inspector-session-impl.h"
22 #include "src/inspector/v8-regex.h"
23 #include "src/inspector/v8-runtime-agent-impl.h"
24 #include "src/inspector/v8-stack-trace-impl.h"
25 #include "src/inspector/v8-value-copier.h"
26
27 #include "include/v8-inspector.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::Runtime::ExceptionDetails;
36 using protocol::Runtime::ScriptId;
37 using protocol::Runtime::StackTrace;
38 using protocol::Runtime::RemoteObject;
39
40 namespace DebuggerAgentState {
41 static const char javaScriptBreakpoints[] = "javaScriptBreakopints";
42 static const char pauseOnExceptionsState[] = "pauseOnExceptionsState";
43 static const char asyncCallStackDepth[] = "asyncCallStackDepth";
44 static const char blackboxPattern[] = "blackboxPattern";
45 static const char debuggerEnabled[] = "debuggerEnabled";
46
47 // Breakpoint properties.
48 static const char url[] = "url";
49 static const char isRegex[] = "isRegex";
50 static const char lineNumber[] = "lineNumber";
51 static const char columnNumber[] = "columnNumber";
52 static const char condition[] = "condition";
53 static const char skipAllPauses[] = "skipAllPauses";
54
55 } // namespace DebuggerAgentState
56
57 static const int kMaxSkipStepFrameCount = 128;
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
breakpointIdSuffix(V8DebuggerAgentImpl::BreakpointSource source)63 static String16 breakpointIdSuffix(
64 V8DebuggerAgentImpl::BreakpointSource source) {
65 switch (source) {
66 case V8DebuggerAgentImpl::UserBreakpointSource:
67 break;
68 case V8DebuggerAgentImpl::DebugCommandBreakpointSource:
69 return ":debug";
70 case V8DebuggerAgentImpl::MonitorCommandBreakpointSource:
71 return ":monitor";
72 }
73 return String16();
74 }
75
generateBreakpointId(const String16 & scriptId,int lineNumber,int columnNumber,V8DebuggerAgentImpl::BreakpointSource source)76 static String16 generateBreakpointId(
77 const String16& scriptId, int lineNumber, int columnNumber,
78 V8DebuggerAgentImpl::BreakpointSource source) {
79 String16Builder builder;
80 builder.append(scriptId);
81 builder.append(':');
82 builder.appendNumber(lineNumber);
83 builder.append(':');
84 builder.appendNumber(columnNumber);
85 builder.append(breakpointIdSuffix(source));
86 return builder.toString();
87 }
88
positionComparator(const std::pair<int,int> & a,const std::pair<int,int> & b)89 static bool positionComparator(const std::pair<int, int>& a,
90 const std::pair<int, int>& b) {
91 if (a.first != b.first) return a.first < b.first;
92 return a.second < b.second;
93 }
94
buildProtocolLocation(const String16 & scriptId,int lineNumber,int columnNumber)95 static std::unique_ptr<protocol::Debugger::Location> buildProtocolLocation(
96 const String16& scriptId, int lineNumber, int columnNumber) {
97 return protocol::Debugger::Location::create()
98 .setScriptId(scriptId)
99 .setLineNumber(lineNumber)
100 .setColumnNumber(columnNumber)
101 .build();
102 }
103
V8DebuggerAgentImpl(V8InspectorSessionImpl * session,protocol::FrontendChannel * frontendChannel,protocol::DictionaryValue * state)104 V8DebuggerAgentImpl::V8DebuggerAgentImpl(
105 V8InspectorSessionImpl* session, protocol::FrontendChannel* frontendChannel,
106 protocol::DictionaryValue* state)
107 : m_inspector(session->inspector()),
108 m_debugger(m_inspector->debugger()),
109 m_session(session),
110 m_enabled(false),
111 m_state(state),
112 m_frontend(frontendChannel),
113 m_isolate(m_inspector->isolate()),
114 m_breakReason(protocol::Debugger::Paused::ReasonEnum::Other),
115 m_scheduledDebuggerStep(NoStep),
116 m_skipNextDebuggerStepOut(false),
117 m_javaScriptPauseScheduled(false),
118 m_steppingFromFramework(false),
119 m_pausingOnNativeEvent(false),
120 m_skippedStepFrameCount(0),
121 m_recursionLevelForStepOut(0),
122 m_recursionLevelForStepFrame(0),
123 m_skipAllPauses(false) {
124 clearBreakDetails();
125 }
126
~V8DebuggerAgentImpl()127 V8DebuggerAgentImpl::~V8DebuggerAgentImpl() {}
128
enableImpl()129 void V8DebuggerAgentImpl::enableImpl() {
130 // m_inspector->addListener may result in reporting all parsed scripts to
131 // the agent so it should already be in enabled state by then.
132 m_enabled = true;
133 m_state->setBoolean(DebuggerAgentState::debuggerEnabled, true);
134 m_debugger->enable();
135
136 std::vector<std::unique_ptr<V8DebuggerScript>> compiledScripts;
137 m_debugger->getCompiledScripts(m_session->contextGroupId(), compiledScripts);
138 for (size_t i = 0; i < compiledScripts.size(); i++)
139 didParseSource(std::move(compiledScripts[i]), true);
140
141 // FIXME(WK44513): breakpoints activated flag should be synchronized between
142 // all front-ends
143 m_debugger->setBreakpointsActivated(true);
144 }
145
enabled()146 bool V8DebuggerAgentImpl::enabled() { return m_enabled; }
147
enable()148 Response V8DebuggerAgentImpl::enable() {
149 if (enabled()) return Response::OK();
150
151 if (!m_inspector->client()->canExecuteScripts(m_session->contextGroupId()))
152 return Response::Error("Script execution is prohibited");
153
154 enableImpl();
155 return Response::OK();
156 }
157
disable()158 Response V8DebuggerAgentImpl::disable() {
159 if (!enabled()) return Response::OK();
160
161 m_state->setObject(DebuggerAgentState::javaScriptBreakpoints,
162 protocol::DictionaryValue::create());
163 m_state->setInteger(DebuggerAgentState::pauseOnExceptionsState,
164 v8::DebugInterface::NoBreakOnException);
165 m_state->setInteger(DebuggerAgentState::asyncCallStackDepth, 0);
166
167 if (!m_pausedContext.IsEmpty()) m_debugger->continueProgram();
168 m_debugger->disable();
169 m_pausedContext.Reset();
170 JavaScriptCallFrames emptyCallFrames;
171 m_pausedCallFrames.swap(emptyCallFrames);
172 m_scripts.clear();
173 m_blackboxedPositions.clear();
174 m_breakpointIdToDebuggerBreakpointIds.clear();
175 m_debugger->setAsyncCallStackDepth(this, 0);
176 m_continueToLocationBreakpointId = String16();
177 clearBreakDetails();
178 m_scheduledDebuggerStep = NoStep;
179 m_skipNextDebuggerStepOut = false;
180 m_javaScriptPauseScheduled = false;
181 m_steppingFromFramework = false;
182 m_pausingOnNativeEvent = false;
183 m_skippedStepFrameCount = 0;
184 m_recursionLevelForStepFrame = 0;
185 m_skipAllPauses = false;
186 m_blackboxPattern = nullptr;
187 m_state->remove(DebuggerAgentState::blackboxPattern);
188 m_enabled = false;
189 m_state->setBoolean(DebuggerAgentState::debuggerEnabled, false);
190 return Response::OK();
191 }
192
restore()193 void V8DebuggerAgentImpl::restore() {
194 DCHECK(!m_enabled);
195 if (!m_state->booleanProperty(DebuggerAgentState::debuggerEnabled, false))
196 return;
197 if (!m_inspector->client()->canExecuteScripts(m_session->contextGroupId()))
198 return;
199
200 enableImpl();
201
202 int pauseState = v8::DebugInterface::NoBreakOnException;
203 m_state->getInteger(DebuggerAgentState::pauseOnExceptionsState, &pauseState);
204 setPauseOnExceptionsImpl(pauseState);
205
206 m_skipAllPauses =
207 m_state->booleanProperty(DebuggerAgentState::skipAllPauses, false);
208
209 int asyncCallStackDepth = 0;
210 m_state->getInteger(DebuggerAgentState::asyncCallStackDepth,
211 &asyncCallStackDepth);
212 m_debugger->setAsyncCallStackDepth(this, asyncCallStackDepth);
213
214 String16 blackboxPattern;
215 if (m_state->getString(DebuggerAgentState::blackboxPattern,
216 &blackboxPattern)) {
217 setBlackboxPattern(blackboxPattern);
218 }
219 }
220
setBreakpointsActive(bool active)221 Response V8DebuggerAgentImpl::setBreakpointsActive(bool active) {
222 if (!enabled()) return Response::Error(kDebuggerNotEnabled);
223 m_debugger->setBreakpointsActivated(active);
224 return Response::OK();
225 }
226
setSkipAllPauses(bool skip)227 Response V8DebuggerAgentImpl::setSkipAllPauses(bool skip) {
228 m_skipAllPauses = skip;
229 m_state->setBoolean(DebuggerAgentState::skipAllPauses, m_skipAllPauses);
230 return Response::OK();
231 }
232
233 static std::unique_ptr<protocol::DictionaryValue>
buildObjectForBreakpointCookie(const String16 & url,int lineNumber,int columnNumber,const String16 & condition,bool isRegex)234 buildObjectForBreakpointCookie(const String16& url, int lineNumber,
235 int columnNumber, const String16& condition,
236 bool isRegex) {
237 std::unique_ptr<protocol::DictionaryValue> breakpointObject =
238 protocol::DictionaryValue::create();
239 breakpointObject->setString(DebuggerAgentState::url, url);
240 breakpointObject->setInteger(DebuggerAgentState::lineNumber, lineNumber);
241 breakpointObject->setInteger(DebuggerAgentState::columnNumber, columnNumber);
242 breakpointObject->setString(DebuggerAgentState::condition, condition);
243 breakpointObject->setBoolean(DebuggerAgentState::isRegex, isRegex);
244 return breakpointObject;
245 }
246
matches(V8InspectorImpl * inspector,const String16 & url,const String16 & pattern,bool isRegex)247 static bool matches(V8InspectorImpl* inspector, const String16& url,
248 const String16& pattern, bool isRegex) {
249 if (isRegex) {
250 V8Regex regex(inspector, pattern, true);
251 return regex.match(url) != -1;
252 }
253 return url == pattern;
254 }
255
setBreakpointByUrl(int lineNumber,Maybe<String16> optionalURL,Maybe<String16> optionalURLRegex,Maybe<int> optionalColumnNumber,Maybe<String16> optionalCondition,String16 * outBreakpointId,std::unique_ptr<protocol::Array<protocol::Debugger::Location>> * locations)256 Response V8DebuggerAgentImpl::setBreakpointByUrl(
257 int lineNumber, Maybe<String16> optionalURL,
258 Maybe<String16> optionalURLRegex, Maybe<int> optionalColumnNumber,
259 Maybe<String16> optionalCondition, String16* outBreakpointId,
260 std::unique_ptr<protocol::Array<protocol::Debugger::Location>>* locations) {
261 *locations = Array<protocol::Debugger::Location>::create();
262 if (optionalURL.isJust() == optionalURLRegex.isJust())
263 return Response::Error("Either url or urlRegex must be specified.");
264
265 String16 url = optionalURL.isJust() ? optionalURL.fromJust()
266 : optionalURLRegex.fromJust();
267 int columnNumber = 0;
268 if (optionalColumnNumber.isJust()) {
269 columnNumber = optionalColumnNumber.fromJust();
270 if (columnNumber < 0) return Response::Error("Incorrect column number");
271 }
272 String16 condition = optionalCondition.fromMaybe("");
273 bool isRegex = optionalURLRegex.isJust();
274
275 String16 breakpointId = (isRegex ? "/" + url + "/" : url) + ":" +
276 String16::fromInteger(lineNumber) + ":" +
277 String16::fromInteger(columnNumber);
278 protocol::DictionaryValue* breakpointsCookie =
279 m_state->getObject(DebuggerAgentState::javaScriptBreakpoints);
280 if (!breakpointsCookie) {
281 std::unique_ptr<protocol::DictionaryValue> newValue =
282 protocol::DictionaryValue::create();
283 breakpointsCookie = newValue.get();
284 m_state->setObject(DebuggerAgentState::javaScriptBreakpoints,
285 std::move(newValue));
286 }
287 if (breakpointsCookie->get(breakpointId))
288 return Response::Error("Breakpoint at specified location already exists.");
289
290 breakpointsCookie->setObject(
291 breakpointId, buildObjectForBreakpointCookie(
292 url, lineNumber, columnNumber, condition, isRegex));
293
294 ScriptBreakpoint breakpoint(lineNumber, columnNumber, condition);
295 for (const auto& script : m_scripts) {
296 if (!matches(m_inspector, script.second->sourceURL(), url, isRegex))
297 continue;
298 std::unique_ptr<protocol::Debugger::Location> location = resolveBreakpoint(
299 breakpointId, script.first, breakpoint, UserBreakpointSource);
300 if (location) (*locations)->addItem(std::move(location));
301 }
302
303 *outBreakpointId = breakpointId;
304 return Response::OK();
305 }
306
setBreakpoint(std::unique_ptr<protocol::Debugger::Location> location,Maybe<String16> optionalCondition,String16 * outBreakpointId,std::unique_ptr<protocol::Debugger::Location> * actualLocation)307 Response V8DebuggerAgentImpl::setBreakpoint(
308 std::unique_ptr<protocol::Debugger::Location> location,
309 Maybe<String16> optionalCondition, String16* outBreakpointId,
310 std::unique_ptr<protocol::Debugger::Location>* actualLocation) {
311 String16 scriptId = location->getScriptId();
312 int lineNumber = location->getLineNumber();
313 int columnNumber = location->getColumnNumber(0);
314
315 String16 condition = optionalCondition.fromMaybe("");
316
317 String16 breakpointId = generateBreakpointId(
318 scriptId, lineNumber, columnNumber, UserBreakpointSource);
319 if (m_breakpointIdToDebuggerBreakpointIds.find(breakpointId) !=
320 m_breakpointIdToDebuggerBreakpointIds.end()) {
321 return Response::Error("Breakpoint at specified location already exists.");
322 }
323 ScriptBreakpoint breakpoint(lineNumber, columnNumber, condition);
324 *actualLocation = resolveBreakpoint(breakpointId, scriptId, breakpoint,
325 UserBreakpointSource);
326 if (!*actualLocation) return Response::Error("Could not resolve breakpoint");
327 *outBreakpointId = breakpointId;
328 return Response::OK();
329 }
330
removeBreakpoint(const String16 & breakpointId)331 Response V8DebuggerAgentImpl::removeBreakpoint(const String16& breakpointId) {
332 if (!enabled()) return Response::Error(kDebuggerNotEnabled);
333 protocol::DictionaryValue* breakpointsCookie =
334 m_state->getObject(DebuggerAgentState::javaScriptBreakpoints);
335 if (breakpointsCookie) breakpointsCookie->remove(breakpointId);
336 removeBreakpointImpl(breakpointId);
337 return Response::OK();
338 }
339
removeBreakpointImpl(const String16 & breakpointId)340 void V8DebuggerAgentImpl::removeBreakpointImpl(const String16& breakpointId) {
341 DCHECK(enabled());
342 BreakpointIdToDebuggerBreakpointIdsMap::iterator
343 debuggerBreakpointIdsIterator =
344 m_breakpointIdToDebuggerBreakpointIds.find(breakpointId);
345 if (debuggerBreakpointIdsIterator ==
346 m_breakpointIdToDebuggerBreakpointIds.end())
347 return;
348 const std::vector<String16>& ids = debuggerBreakpointIdsIterator->second;
349 for (size_t i = 0; i < ids.size(); ++i) {
350 const String16& debuggerBreakpointId = ids[i];
351
352 m_debugger->removeBreakpoint(debuggerBreakpointId);
353 m_serverBreakpoints.erase(debuggerBreakpointId);
354 }
355 m_breakpointIdToDebuggerBreakpointIds.erase(breakpointId);
356 }
357
getPossibleBreakpoints(std::unique_ptr<protocol::Debugger::Location> start,Maybe<protocol::Debugger::Location> end,std::unique_ptr<protocol::Array<protocol::Debugger::Location>> * locations)358 Response V8DebuggerAgentImpl::getPossibleBreakpoints(
359 std::unique_ptr<protocol::Debugger::Location> start,
360 Maybe<protocol::Debugger::Location> end,
361 std::unique_ptr<protocol::Array<protocol::Debugger::Location>>* locations) {
362 String16 scriptId = start->getScriptId();
363
364 if (start->getLineNumber() < 0 || start->getColumnNumber(0) < 0)
365 return Response::Error(
366 "start.lineNumber and start.columnNumber should be >= 0");
367
368 v8::DebugInterface::Location v8Start(start->getLineNumber(),
369 start->getColumnNumber(0));
370 v8::DebugInterface::Location v8End;
371 if (end.isJust()) {
372 if (end.fromJust()->getScriptId() != scriptId)
373 return Response::Error("Locations should contain the same scriptId");
374 int line = end.fromJust()->getLineNumber();
375 int column = end.fromJust()->getColumnNumber(0);
376 if (line < 0 || column < 0)
377 return Response::Error(
378 "end.lineNumber and end.columnNumber should be >= 0");
379 v8End = v8::DebugInterface::Location(line, column);
380 }
381 auto it = m_scripts.find(scriptId);
382 if (it == m_scripts.end()) return Response::Error("Script not found");
383
384 std::vector<v8::DebugInterface::Location> v8Locations;
385 if (!it->second->getPossibleBreakpoints(v8Start, v8End, &v8Locations))
386 return Response::InternalError();
387
388 *locations = protocol::Array<protocol::Debugger::Location>::create();
389 for (size_t i = 0; i < v8Locations.size(); ++i) {
390 (*locations)
391 ->addItem(protocol::Debugger::Location::create()
392 .setScriptId(scriptId)
393 .setLineNumber(v8Locations[i].GetLineNumber())
394 .setColumnNumber(v8Locations[i].GetColumnNumber())
395 .build());
396 }
397 return Response::OK();
398 }
399
continueToLocation(std::unique_ptr<protocol::Debugger::Location> location)400 Response V8DebuggerAgentImpl::continueToLocation(
401 std::unique_ptr<protocol::Debugger::Location> location) {
402 if (!enabled()) return Response::Error(kDebuggerNotEnabled);
403 if (!m_continueToLocationBreakpointId.isEmpty()) {
404 m_debugger->removeBreakpoint(m_continueToLocationBreakpointId);
405 m_continueToLocationBreakpointId = "";
406 }
407
408 String16 scriptId = location->getScriptId();
409 int lineNumber = location->getLineNumber();
410 int columnNumber = location->getColumnNumber(0);
411
412 ScriptBreakpoint breakpoint(lineNumber, columnNumber, "");
413 m_continueToLocationBreakpointId = m_debugger->setBreakpoint(
414 scriptId, breakpoint, &lineNumber, &columnNumber);
415 return resume();
416 }
417
isCurrentCallStackEmptyOrBlackboxed()418 bool V8DebuggerAgentImpl::isCurrentCallStackEmptyOrBlackboxed() {
419 DCHECK(enabled());
420 JavaScriptCallFrames callFrames = m_debugger->currentCallFrames();
421 for (size_t index = 0; index < callFrames.size(); ++index) {
422 if (!isCallFrameWithUnknownScriptOrBlackboxed(callFrames[index].get()))
423 return false;
424 }
425 return true;
426 }
427
isTopPausedCallFrameBlackboxed()428 bool V8DebuggerAgentImpl::isTopPausedCallFrameBlackboxed() {
429 DCHECK(enabled());
430 JavaScriptCallFrame* frame =
431 m_pausedCallFrames.size() ? m_pausedCallFrames[0].get() : nullptr;
432 return isCallFrameWithUnknownScriptOrBlackboxed(frame);
433 }
434
isCallFrameWithUnknownScriptOrBlackboxed(JavaScriptCallFrame * frame)435 bool V8DebuggerAgentImpl::isCallFrameWithUnknownScriptOrBlackboxed(
436 JavaScriptCallFrame* frame) {
437 if (!frame) return true;
438 ScriptsMap::iterator it =
439 m_scripts.find(String16::fromInteger(frame->sourceID()));
440 if (it == m_scripts.end()) {
441 // Unknown scripts are blackboxed.
442 return true;
443 }
444 if (m_blackboxPattern) {
445 const String16& scriptSourceURL = it->second->sourceURL();
446 if (!scriptSourceURL.isEmpty() &&
447 m_blackboxPattern->match(scriptSourceURL) != -1)
448 return true;
449 }
450 auto itBlackboxedPositions =
451 m_blackboxedPositions.find(String16::fromInteger(frame->sourceID()));
452 if (itBlackboxedPositions == m_blackboxedPositions.end()) return false;
453
454 const std::vector<std::pair<int, int>>& ranges =
455 itBlackboxedPositions->second;
456 auto itRange = std::lower_bound(
457 ranges.begin(), ranges.end(),
458 std::make_pair(frame->line(), frame->column()), positionComparator);
459 // Ranges array contains positions in script where blackbox state is changed.
460 // [(0,0) ... ranges[0]) isn't blackboxed, [ranges[0] ... ranges[1]) is
461 // blackboxed...
462 return std::distance(ranges.begin(), itRange) % 2;
463 }
464
465 V8DebuggerAgentImpl::SkipPauseRequest
shouldSkipExceptionPause(JavaScriptCallFrame * topCallFrame)466 V8DebuggerAgentImpl::shouldSkipExceptionPause(
467 JavaScriptCallFrame* topCallFrame) {
468 if (m_steppingFromFramework) return RequestNoSkip;
469 if (isCallFrameWithUnknownScriptOrBlackboxed(topCallFrame))
470 return RequestContinue;
471 return RequestNoSkip;
472 }
473
shouldSkipStepPause(JavaScriptCallFrame * topCallFrame)474 V8DebuggerAgentImpl::SkipPauseRequest V8DebuggerAgentImpl::shouldSkipStepPause(
475 JavaScriptCallFrame* topCallFrame) {
476 if (m_steppingFromFramework) return RequestNoSkip;
477
478 if (m_skipNextDebuggerStepOut) {
479 m_skipNextDebuggerStepOut = false;
480 if (m_scheduledDebuggerStep == StepOut) return RequestStepOut;
481 }
482
483 if (!isCallFrameWithUnknownScriptOrBlackboxed(topCallFrame))
484 return RequestNoSkip;
485
486 if (m_skippedStepFrameCount >= kMaxSkipStepFrameCount) return RequestStepOut;
487
488 if (!m_skippedStepFrameCount) m_recursionLevelForStepFrame = 1;
489
490 ++m_skippedStepFrameCount;
491 return RequestStepFrame;
492 }
493
494 std::unique_ptr<protocol::Debugger::Location>
resolveBreakpoint(const String16 & breakpointId,const String16 & scriptId,const ScriptBreakpoint & breakpoint,BreakpointSource source)495 V8DebuggerAgentImpl::resolveBreakpoint(const String16& breakpointId,
496 const String16& scriptId,
497 const ScriptBreakpoint& breakpoint,
498 BreakpointSource source) {
499 DCHECK(enabled());
500 // FIXME: remove these checks once crbug.com/520702 is resolved.
501 CHECK(!breakpointId.isEmpty());
502 CHECK(!scriptId.isEmpty());
503 ScriptsMap::iterator scriptIterator = m_scripts.find(scriptId);
504 if (scriptIterator == m_scripts.end()) return nullptr;
505 if (breakpoint.lineNumber < scriptIterator->second->startLine() ||
506 scriptIterator->second->endLine() < breakpoint.lineNumber)
507 return nullptr;
508
509 int actualLineNumber;
510 int actualColumnNumber;
511 String16 debuggerBreakpointId = m_debugger->setBreakpoint(
512 scriptId, breakpoint, &actualLineNumber, &actualColumnNumber);
513 if (debuggerBreakpointId.isEmpty()) return nullptr;
514
515 m_serverBreakpoints[debuggerBreakpointId] =
516 std::make_pair(breakpointId, source);
517 CHECK(!breakpointId.isEmpty());
518
519 m_breakpointIdToDebuggerBreakpointIds[breakpointId].push_back(
520 debuggerBreakpointId);
521 return buildProtocolLocation(scriptId, actualLineNumber, actualColumnNumber);
522 }
523
searchInContent(const String16 & scriptId,const String16 & query,Maybe<bool> optionalCaseSensitive,Maybe<bool> optionalIsRegex,std::unique_ptr<Array<protocol::Debugger::SearchMatch>> * results)524 Response V8DebuggerAgentImpl::searchInContent(
525 const String16& scriptId, const String16& query,
526 Maybe<bool> optionalCaseSensitive, Maybe<bool> optionalIsRegex,
527 std::unique_ptr<Array<protocol::Debugger::SearchMatch>>* results) {
528 v8::HandleScope handles(m_isolate);
529 ScriptsMap::iterator it = m_scripts.find(scriptId);
530 if (it == m_scripts.end())
531 return Response::Error("No script for id: " + scriptId);
532
533 std::vector<std::unique_ptr<protocol::Debugger::SearchMatch>> matches =
534 searchInTextByLinesImpl(m_session,
535 toProtocolString(it->second->source(m_isolate)),
536 query, optionalCaseSensitive.fromMaybe(false),
537 optionalIsRegex.fromMaybe(false));
538 *results = protocol::Array<protocol::Debugger::SearchMatch>::create();
539 for (size_t i = 0; i < matches.size(); ++i)
540 (*results)->addItem(std::move(matches[i]));
541 return Response::OK();
542 }
543
setScriptSource(const String16 & scriptId,const String16 & newContent,Maybe<bool> dryRun,Maybe<protocol::Array<protocol::Debugger::CallFrame>> * newCallFrames,Maybe<bool> * stackChanged,Maybe<StackTrace> * asyncStackTrace,Maybe<protocol::Runtime::ExceptionDetails> * optOutCompileError)544 Response V8DebuggerAgentImpl::setScriptSource(
545 const String16& scriptId, const String16& newContent, Maybe<bool> dryRun,
546 Maybe<protocol::Array<protocol::Debugger::CallFrame>>* newCallFrames,
547 Maybe<bool>* stackChanged, Maybe<StackTrace>* asyncStackTrace,
548 Maybe<protocol::Runtime::ExceptionDetails>* optOutCompileError) {
549 if (!enabled()) return Response::Error(kDebuggerNotEnabled);
550
551 v8::HandleScope handles(m_isolate);
552 v8::Local<v8::String> newSource = toV8String(m_isolate, newContent);
553 bool compileError = false;
554 Response response = m_debugger->setScriptSource(
555 scriptId, newSource, dryRun.fromMaybe(false), optOutCompileError,
556 &m_pausedCallFrames, stackChanged, &compileError);
557 if (!response.isSuccess() || compileError) return response;
558
559 ScriptsMap::iterator it = m_scripts.find(scriptId);
560 if (it != m_scripts.end()) it->second->setSource(newSource);
561
562 std::unique_ptr<Array<CallFrame>> callFrames;
563 response = currentCallFrames(&callFrames);
564 if (!response.isSuccess()) return response;
565 *newCallFrames = std::move(callFrames);
566 *asyncStackTrace = currentAsyncStackTrace();
567 return Response::OK();
568 }
569
restartFrame(const String16 & callFrameId,std::unique_ptr<Array<CallFrame>> * newCallFrames,Maybe<StackTrace> * asyncStackTrace)570 Response V8DebuggerAgentImpl::restartFrame(
571 const String16& callFrameId,
572 std::unique_ptr<Array<CallFrame>>* newCallFrames,
573 Maybe<StackTrace>* asyncStackTrace) {
574 if (m_pausedContext.IsEmpty()) return Response::Error(kDebuggerNotPaused);
575 InjectedScript::CallFrameScope scope(m_inspector, m_session->contextGroupId(),
576 callFrameId);
577 Response response = scope.initialize();
578 if (!response.isSuccess()) return response;
579 if (scope.frameOrdinal() >= m_pausedCallFrames.size())
580 return Response::Error("Could not find call frame with given id");
581
582 v8::Local<v8::Value> resultValue;
583 v8::Local<v8::Boolean> result;
584 if (!m_pausedCallFrames[scope.frameOrdinal()]->restart().ToLocal(
585 &resultValue) ||
586 scope.tryCatch().HasCaught() ||
587 !resultValue->ToBoolean(scope.context()).ToLocal(&result) ||
588 !result->Value()) {
589 return Response::InternalError();
590 }
591 JavaScriptCallFrames frames = m_debugger->currentCallFrames();
592 m_pausedCallFrames.swap(frames);
593
594 response = currentCallFrames(newCallFrames);
595 if (!response.isSuccess()) return response;
596 *asyncStackTrace = currentAsyncStackTrace();
597 return Response::OK();
598 }
599
getScriptSource(const String16 & scriptId,String16 * scriptSource)600 Response V8DebuggerAgentImpl::getScriptSource(const String16& scriptId,
601 String16* scriptSource) {
602 if (!enabled()) return Response::Error(kDebuggerNotEnabled);
603 ScriptsMap::iterator it = m_scripts.find(scriptId);
604 if (it == m_scripts.end())
605 return Response::Error("No script for id: " + scriptId);
606 v8::HandleScope handles(m_isolate);
607 *scriptSource = toProtocolString(it->second->source(m_isolate));
608 return Response::OK();
609 }
610
schedulePauseOnNextStatement(const String16 & breakReason,std::unique_ptr<protocol::DictionaryValue> data)611 void V8DebuggerAgentImpl::schedulePauseOnNextStatement(
612 const String16& breakReason,
613 std::unique_ptr<protocol::DictionaryValue> data) {
614 if (!enabled() || m_scheduledDebuggerStep == StepInto ||
615 m_javaScriptPauseScheduled || m_debugger->isPaused() ||
616 !m_debugger->breakpointsActivated())
617 return;
618 m_breakReason = breakReason;
619 m_breakAuxData = std::move(data);
620 m_pausingOnNativeEvent = true;
621 m_skipNextDebuggerStepOut = false;
622 m_debugger->setPauseOnNextStatement(true);
623 }
624
schedulePauseOnNextStatementIfSteppingInto()625 void V8DebuggerAgentImpl::schedulePauseOnNextStatementIfSteppingInto() {
626 DCHECK(enabled());
627 if (m_scheduledDebuggerStep != StepInto || m_javaScriptPauseScheduled ||
628 m_debugger->isPaused())
629 return;
630 clearBreakDetails();
631 m_pausingOnNativeEvent = false;
632 m_skippedStepFrameCount = 0;
633 m_recursionLevelForStepFrame = 0;
634 m_debugger->setPauseOnNextStatement(true);
635 }
636
cancelPauseOnNextStatement()637 void V8DebuggerAgentImpl::cancelPauseOnNextStatement() {
638 if (m_javaScriptPauseScheduled || m_debugger->isPaused()) return;
639 clearBreakDetails();
640 m_pausingOnNativeEvent = false;
641 m_debugger->setPauseOnNextStatement(false);
642 }
643
pause()644 Response V8DebuggerAgentImpl::pause() {
645 if (!enabled()) return Response::Error(kDebuggerNotEnabled);
646 if (m_javaScriptPauseScheduled || m_debugger->isPaused())
647 return Response::OK();
648 clearBreakDetails();
649 m_javaScriptPauseScheduled = true;
650 m_scheduledDebuggerStep = NoStep;
651 m_skippedStepFrameCount = 0;
652 m_steppingFromFramework = false;
653 m_debugger->setPauseOnNextStatement(true);
654 return Response::OK();
655 }
656
resume()657 Response V8DebuggerAgentImpl::resume() {
658 if (m_pausedContext.IsEmpty()) return Response::Error(kDebuggerNotPaused);
659 m_scheduledDebuggerStep = NoStep;
660 m_steppingFromFramework = false;
661 m_session->releaseObjectGroup(kBacktraceObjectGroup);
662 m_debugger->continueProgram();
663 return Response::OK();
664 }
665
stepOver()666 Response V8DebuggerAgentImpl::stepOver() {
667 if (m_pausedContext.IsEmpty()) return Response::Error(kDebuggerNotPaused);
668 // StepOver at function return point should fallback to StepInto.
669 JavaScriptCallFrame* frame =
670 !m_pausedCallFrames.empty() ? m_pausedCallFrames[0].get() : nullptr;
671 if (frame && frame->isAtReturn()) return stepInto();
672 m_scheduledDebuggerStep = StepOver;
673 m_steppingFromFramework = isTopPausedCallFrameBlackboxed();
674 m_session->releaseObjectGroup(kBacktraceObjectGroup);
675 m_debugger->stepOverStatement();
676 return Response::OK();
677 }
678
stepInto()679 Response V8DebuggerAgentImpl::stepInto() {
680 if (m_pausedContext.IsEmpty()) return Response::Error(kDebuggerNotPaused);
681 m_scheduledDebuggerStep = StepInto;
682 m_steppingFromFramework = isTopPausedCallFrameBlackboxed();
683 m_session->releaseObjectGroup(kBacktraceObjectGroup);
684 m_debugger->stepIntoStatement();
685 return Response::OK();
686 }
687
stepOut()688 Response V8DebuggerAgentImpl::stepOut() {
689 if (m_pausedContext.IsEmpty()) return Response::Error(kDebuggerNotPaused);
690 m_scheduledDebuggerStep = StepOut;
691 m_skipNextDebuggerStepOut = false;
692 m_recursionLevelForStepOut = 1;
693 m_steppingFromFramework = isTopPausedCallFrameBlackboxed();
694 m_session->releaseObjectGroup(kBacktraceObjectGroup);
695 m_debugger->stepOutOfFunction();
696 return Response::OK();
697 }
698
setPauseOnExceptions(const String16 & stringPauseState)699 Response V8DebuggerAgentImpl::setPauseOnExceptions(
700 const String16& stringPauseState) {
701 if (!enabled()) return Response::Error(kDebuggerNotEnabled);
702 v8::DebugInterface::ExceptionBreakState pauseState;
703 if (stringPauseState == "none") {
704 pauseState = v8::DebugInterface::NoBreakOnException;
705 } else if (stringPauseState == "all") {
706 pauseState = v8::DebugInterface::BreakOnAnyException;
707 } else if (stringPauseState == "uncaught") {
708 pauseState = v8::DebugInterface::BreakOnUncaughtException;
709 } else {
710 return Response::Error("Unknown pause on exceptions mode: " +
711 stringPauseState);
712 }
713 setPauseOnExceptionsImpl(pauseState);
714 return Response::OK();
715 }
716
setPauseOnExceptionsImpl(int pauseState)717 void V8DebuggerAgentImpl::setPauseOnExceptionsImpl(int pauseState) {
718 m_debugger->setPauseOnExceptionsState(
719 static_cast<v8::DebugInterface::ExceptionBreakState>(pauseState));
720 m_state->setInteger(DebuggerAgentState::pauseOnExceptionsState, pauseState);
721 }
722
evaluateOnCallFrame(const String16 & callFrameId,const String16 & expression,Maybe<String16> objectGroup,Maybe<bool> includeCommandLineAPI,Maybe<bool> silent,Maybe<bool> returnByValue,Maybe<bool> generatePreview,std::unique_ptr<RemoteObject> * result,Maybe<protocol::Runtime::ExceptionDetails> * exceptionDetails)723 Response V8DebuggerAgentImpl::evaluateOnCallFrame(
724 const String16& callFrameId, const String16& expression,
725 Maybe<String16> objectGroup, Maybe<bool> includeCommandLineAPI,
726 Maybe<bool> silent, Maybe<bool> returnByValue, Maybe<bool> generatePreview,
727 std::unique_ptr<RemoteObject>* result,
728 Maybe<protocol::Runtime::ExceptionDetails>* exceptionDetails) {
729 if (m_pausedContext.IsEmpty()) return Response::Error(kDebuggerNotPaused);
730 InjectedScript::CallFrameScope scope(m_inspector, m_session->contextGroupId(),
731 callFrameId);
732 Response response = scope.initialize();
733 if (!response.isSuccess()) return response;
734 if (scope.frameOrdinal() >= m_pausedCallFrames.size())
735 return Response::Error("Could not find call frame with given id");
736
737 if (includeCommandLineAPI.fromMaybe(false)) scope.installCommandLineAPI();
738 if (silent.fromMaybe(false)) scope.ignoreExceptionsAndMuteConsole();
739
740 v8::MaybeLocal<v8::Value> maybeResultValue =
741 m_pausedCallFrames[scope.frameOrdinal()]->evaluate(
742 toV8String(m_isolate, expression));
743
744 // Re-initialize after running client's code, as it could have destroyed
745 // context or session.
746 response = scope.initialize();
747 if (!response.isSuccess()) return response;
748 return scope.injectedScript()->wrapEvaluateResult(
749 maybeResultValue, scope.tryCatch(), objectGroup.fromMaybe(""),
750 returnByValue.fromMaybe(false), generatePreview.fromMaybe(false), result,
751 exceptionDetails);
752 }
753
setVariableValue(int scopeNumber,const String16 & variableName,std::unique_ptr<protocol::Runtime::CallArgument> newValueArgument,const String16 & callFrameId)754 Response V8DebuggerAgentImpl::setVariableValue(
755 int scopeNumber, const String16& variableName,
756 std::unique_ptr<protocol::Runtime::CallArgument> newValueArgument,
757 const String16& callFrameId) {
758 if (!enabled()) return Response::Error(kDebuggerNotEnabled);
759 if (m_pausedContext.IsEmpty()) return Response::Error(kDebuggerNotPaused);
760 InjectedScript::CallFrameScope scope(m_inspector, m_session->contextGroupId(),
761 callFrameId);
762 Response response = scope.initialize();
763 if (!response.isSuccess()) return response;
764 v8::Local<v8::Value> newValue;
765 response = scope.injectedScript()->resolveCallArgument(newValueArgument.get(),
766 &newValue);
767 if (!response.isSuccess()) return response;
768
769 if (scope.frameOrdinal() >= m_pausedCallFrames.size())
770 return Response::Error("Could not find call frame with given id");
771 v8::MaybeLocal<v8::Value> result =
772 m_pausedCallFrames[scope.frameOrdinal()]->setVariableValue(
773 scopeNumber, toV8String(m_isolate, variableName), newValue);
774 if (scope.tryCatch().HasCaught() || result.IsEmpty())
775 return Response::InternalError();
776 return Response::OK();
777 }
778
setAsyncCallStackDepth(int depth)779 Response V8DebuggerAgentImpl::setAsyncCallStackDepth(int depth) {
780 if (!enabled()) return Response::Error(kDebuggerNotEnabled);
781 m_state->setInteger(DebuggerAgentState::asyncCallStackDepth, depth);
782 m_debugger->setAsyncCallStackDepth(this, depth);
783 return Response::OK();
784 }
785
setBlackboxPatterns(std::unique_ptr<protocol::Array<String16>> patterns)786 Response V8DebuggerAgentImpl::setBlackboxPatterns(
787 std::unique_ptr<protocol::Array<String16>> patterns) {
788 if (!patterns->length()) {
789 m_blackboxPattern = nullptr;
790 m_state->remove(DebuggerAgentState::blackboxPattern);
791 return Response::OK();
792 }
793
794 String16Builder patternBuilder;
795 patternBuilder.append('(');
796 for (size_t i = 0; i < patterns->length() - 1; ++i) {
797 patternBuilder.append(patterns->get(i));
798 patternBuilder.append("|");
799 }
800 patternBuilder.append(patterns->get(patterns->length() - 1));
801 patternBuilder.append(')');
802 String16 pattern = patternBuilder.toString();
803 Response response = setBlackboxPattern(pattern);
804 if (!response.isSuccess()) return response;
805 m_state->setString(DebuggerAgentState::blackboxPattern, pattern);
806 return Response::OK();
807 }
808
setBlackboxPattern(const String16 & pattern)809 Response V8DebuggerAgentImpl::setBlackboxPattern(const String16& pattern) {
810 std::unique_ptr<V8Regex> regex(new V8Regex(
811 m_inspector, pattern, true /** caseSensitive */, false /** multiline */));
812 if (!regex->isValid())
813 return Response::Error("Pattern parser error: " + regex->errorMessage());
814 m_blackboxPattern = std::move(regex);
815 return Response::OK();
816 }
817
setBlackboxedRanges(const String16 & scriptId,std::unique_ptr<protocol::Array<protocol::Debugger::ScriptPosition>> inPositions)818 Response V8DebuggerAgentImpl::setBlackboxedRanges(
819 const String16& scriptId,
820 std::unique_ptr<protocol::Array<protocol::Debugger::ScriptPosition>>
821 inPositions) {
822 if (m_scripts.find(scriptId) == m_scripts.end())
823 return Response::Error("No script with passed id.");
824
825 if (!inPositions->length()) {
826 m_blackboxedPositions.erase(scriptId);
827 return Response::OK();
828 }
829
830 std::vector<std::pair<int, int>> positions;
831 positions.reserve(inPositions->length());
832 for (size_t i = 0; i < inPositions->length(); ++i) {
833 protocol::Debugger::ScriptPosition* position = inPositions->get(i);
834 if (position->getLineNumber() < 0)
835 return Response::Error("Position missing 'line' or 'line' < 0.");
836 if (position->getColumnNumber() < 0)
837 return Response::Error("Position missing 'column' or 'column' < 0.");
838 positions.push_back(
839 std::make_pair(position->getLineNumber(), position->getColumnNumber()));
840 }
841
842 for (size_t i = 1; i < positions.size(); ++i) {
843 if (positions[i - 1].first < positions[i].first) continue;
844 if (positions[i - 1].first == positions[i].first &&
845 positions[i - 1].second < positions[i].second)
846 continue;
847 return Response::Error(
848 "Input positions array is not sorted or contains duplicate values.");
849 }
850
851 m_blackboxedPositions[scriptId] = positions;
852 return Response::OK();
853 }
854
willExecuteScript(int scriptId)855 void V8DebuggerAgentImpl::willExecuteScript(int scriptId) {
856 changeJavaScriptRecursionLevel(+1);
857 // Fast return.
858 if (m_scheduledDebuggerStep != StepInto) return;
859 schedulePauseOnNextStatementIfSteppingInto();
860 }
861
didExecuteScript()862 void V8DebuggerAgentImpl::didExecuteScript() {
863 changeJavaScriptRecursionLevel(-1);
864 }
865
changeJavaScriptRecursionLevel(int step)866 void V8DebuggerAgentImpl::changeJavaScriptRecursionLevel(int step) {
867 if (m_javaScriptPauseScheduled && !m_skipAllPauses &&
868 !m_debugger->isPaused()) {
869 // Do not ever loose user's pause request until we have actually paused.
870 m_debugger->setPauseOnNextStatement(true);
871 }
872 if (m_scheduledDebuggerStep == StepOut) {
873 m_recursionLevelForStepOut += step;
874 if (!m_recursionLevelForStepOut) {
875 // When StepOut crosses a task boundary (i.e. js -> c++) from where it was
876 // requested,
877 // switch stepping to step into a next JS task, as if we exited to a
878 // blackboxed framework.
879 m_scheduledDebuggerStep = StepInto;
880 m_skipNextDebuggerStepOut = false;
881 }
882 }
883 if (m_recursionLevelForStepFrame) {
884 m_recursionLevelForStepFrame += step;
885 if (!m_recursionLevelForStepFrame) {
886 // We have walked through a blackboxed framework and got back to where we
887 // started.
888 // If there was no stepping scheduled, we should cancel the stepping
889 // explicitly,
890 // since there may be a scheduled StepFrame left.
891 // Otherwise, if we were stepping in/over, the StepFrame will stop at the
892 // right location,
893 // whereas if we were stepping out, we should continue doing so after
894 // debugger pauses
895 // from the old StepFrame.
896 m_skippedStepFrameCount = 0;
897 if (m_scheduledDebuggerStep == NoStep)
898 m_debugger->clearStepping();
899 else if (m_scheduledDebuggerStep == StepOut)
900 m_skipNextDebuggerStepOut = true;
901 }
902 }
903 }
904
currentCallFrames(std::unique_ptr<Array<CallFrame>> * result)905 Response V8DebuggerAgentImpl::currentCallFrames(
906 std::unique_ptr<Array<CallFrame>>* result) {
907 if (m_pausedContext.IsEmpty() || !m_pausedCallFrames.size()) {
908 *result = Array<CallFrame>::create();
909 return Response::OK();
910 }
911 v8::HandleScope handles(m_isolate);
912 v8::Local<v8::Context> debuggerContext =
913 v8::DebugInterface::GetDebugContext(m_isolate);
914 v8::Context::Scope contextScope(debuggerContext);
915
916 v8::Local<v8::Array> objects = v8::Array::New(m_isolate);
917
918 for (size_t frameOrdinal = 0; frameOrdinal < m_pausedCallFrames.size();
919 ++frameOrdinal) {
920 const std::unique_ptr<JavaScriptCallFrame>& currentCallFrame =
921 m_pausedCallFrames[frameOrdinal];
922
923 v8::Local<v8::Object> details = currentCallFrame->details();
924 if (details.IsEmpty()) return Response::InternalError();
925
926 int contextId = currentCallFrame->contextId();
927
928 InjectedScript* injectedScript = nullptr;
929 if (contextId) m_session->findInjectedScript(contextId, injectedScript);
930
931 String16 callFrameId =
932 RemoteCallFrameId::serialize(contextId, static_cast<int>(frameOrdinal));
933 if (!details
934 ->Set(debuggerContext,
935 toV8StringInternalized(m_isolate, "callFrameId"),
936 toV8String(m_isolate, callFrameId))
937 .FromMaybe(false)) {
938 return Response::InternalError();
939 }
940
941 if (injectedScript) {
942 v8::Local<v8::Value> scopeChain;
943 if (!details
944 ->Get(debuggerContext,
945 toV8StringInternalized(m_isolate, "scopeChain"))
946 .ToLocal(&scopeChain) ||
947 !scopeChain->IsArray()) {
948 return Response::InternalError();
949 }
950 v8::Local<v8::Array> scopeChainArray = scopeChain.As<v8::Array>();
951 Response response = injectedScript->wrapPropertyInArray(
952 scopeChainArray, toV8StringInternalized(m_isolate, "object"),
953 kBacktraceObjectGroup);
954 if (!response.isSuccess()) return response;
955 response = injectedScript->wrapObjectProperty(
956 details, toV8StringInternalized(m_isolate, "this"),
957 kBacktraceObjectGroup);
958 if (!response.isSuccess()) return response;
959 if (details
960 ->Has(debuggerContext,
961 toV8StringInternalized(m_isolate, "returnValue"))
962 .FromMaybe(false)) {
963 response = injectedScript->wrapObjectProperty(
964 details, toV8StringInternalized(m_isolate, "returnValue"),
965 kBacktraceObjectGroup);
966 if (!response.isSuccess()) return response;
967 }
968 } else {
969 if (!details
970 ->Set(debuggerContext,
971 toV8StringInternalized(m_isolate, "scopeChain"),
972 v8::Array::New(m_isolate, 0))
973 .FromMaybe(false)) {
974 return Response::InternalError();
975 }
976 v8::Local<v8::Object> remoteObject = v8::Object::New(m_isolate);
977 if (!remoteObject
978 ->Set(debuggerContext, toV8StringInternalized(m_isolate, "type"),
979 toV8StringInternalized(m_isolate, "undefined"))
980 .FromMaybe(false)) {
981 return Response::InternalError();
982 }
983 if (!details
984 ->Set(debuggerContext, toV8StringInternalized(m_isolate, "this"),
985 remoteObject)
986 .FromMaybe(false)) {
987 return Response::InternalError();
988 }
989 if (!details
990 ->Delete(debuggerContext,
991 toV8StringInternalized(m_isolate, "returnValue"))
992 .FromMaybe(false)) {
993 return Response::InternalError();
994 }
995 }
996
997 if (!objects->Set(debuggerContext, static_cast<int>(frameOrdinal), details)
998 .FromMaybe(false)) {
999 return Response::InternalError();
1000 }
1001 }
1002
1003 std::unique_ptr<protocol::Value> protocolValue;
1004 Response response = toProtocolValue(debuggerContext, objects, &protocolValue);
1005 if (!response.isSuccess()) return response;
1006 protocol::ErrorSupport errorSupport;
1007 *result = Array<CallFrame>::parse(protocolValue.get(), &errorSupport);
1008 if (!*result) return Response::Error(errorSupport.errors());
1009 return Response::OK();
1010 }
1011
currentAsyncStackTrace()1012 std::unique_ptr<StackTrace> V8DebuggerAgentImpl::currentAsyncStackTrace() {
1013 if (m_pausedContext.IsEmpty()) return nullptr;
1014 V8StackTraceImpl* stackTrace = m_debugger->currentAsyncCallChain();
1015 return stackTrace ? stackTrace->buildInspectorObjectForTail(m_debugger)
1016 : nullptr;
1017 }
1018
didParseSource(std::unique_ptr<V8DebuggerScript> script,bool success)1019 void V8DebuggerAgentImpl::didParseSource(
1020 std::unique_ptr<V8DebuggerScript> script, bool success) {
1021 v8::HandleScope handles(m_isolate);
1022 String16 scriptSource = toProtocolString(script->source(m_isolate));
1023 if (!success) script->setSourceURL(findSourceURL(scriptSource, false));
1024 if (!success)
1025 script->setSourceMappingURL(findSourceMapURL(scriptSource, false));
1026
1027 std::unique_ptr<protocol::DictionaryValue> executionContextAuxData;
1028 if (!script->executionContextAuxData().isEmpty())
1029 executionContextAuxData = protocol::DictionaryValue::cast(
1030 protocol::parseJSON(script->executionContextAuxData()));
1031 bool isLiveEdit = script->isLiveEdit();
1032 bool hasSourceURL = script->hasSourceURL();
1033 String16 scriptId = script->scriptId();
1034 String16 scriptURL = script->sourceURL();
1035
1036 Maybe<String16> sourceMapURLParam = script->sourceMappingURL();
1037 Maybe<protocol::DictionaryValue> executionContextAuxDataParam(
1038 std::move(executionContextAuxData));
1039 const bool* isLiveEditParam = isLiveEdit ? &isLiveEdit : nullptr;
1040 const bool* hasSourceURLParam = hasSourceURL ? &hasSourceURL : nullptr;
1041 if (success)
1042 m_frontend.scriptParsed(
1043 scriptId, scriptURL, script->startLine(), script->startColumn(),
1044 script->endLine(), script->endColumn(), script->executionContextId(),
1045 script->hash(), std::move(executionContextAuxDataParam),
1046 isLiveEditParam, std::move(sourceMapURLParam), hasSourceURLParam);
1047 else
1048 m_frontend.scriptFailedToParse(
1049 scriptId, scriptURL, script->startLine(), script->startColumn(),
1050 script->endLine(), script->endColumn(), script->executionContextId(),
1051 script->hash(), std::move(executionContextAuxDataParam),
1052 std::move(sourceMapURLParam), hasSourceURLParam);
1053
1054 m_scripts[scriptId] = std::move(script);
1055
1056 if (scriptURL.isEmpty() || !success) return;
1057
1058 protocol::DictionaryValue* breakpointsCookie =
1059 m_state->getObject(DebuggerAgentState::javaScriptBreakpoints);
1060 if (!breakpointsCookie) return;
1061
1062 for (size_t i = 0; i < breakpointsCookie->size(); ++i) {
1063 auto cookie = breakpointsCookie->at(i);
1064 protocol::DictionaryValue* breakpointObject =
1065 protocol::DictionaryValue::cast(cookie.second);
1066 bool isRegex;
1067 breakpointObject->getBoolean(DebuggerAgentState::isRegex, &isRegex);
1068 String16 url;
1069 breakpointObject->getString(DebuggerAgentState::url, &url);
1070 if (!matches(m_inspector, scriptURL, url, isRegex)) continue;
1071 ScriptBreakpoint breakpoint;
1072 breakpointObject->getInteger(DebuggerAgentState::lineNumber,
1073 &breakpoint.lineNumber);
1074 breakpointObject->getInteger(DebuggerAgentState::columnNumber,
1075 &breakpoint.columnNumber);
1076 breakpointObject->getString(DebuggerAgentState::condition,
1077 &breakpoint.condition);
1078 std::unique_ptr<protocol::Debugger::Location> location = resolveBreakpoint(
1079 cookie.first, scriptId, breakpoint, UserBreakpointSource);
1080 if (location)
1081 m_frontend.breakpointResolved(cookie.first, std::move(location));
1082 }
1083 }
1084
didPause(v8::Local<v8::Context> context,v8::Local<v8::Value> exception,const std::vector<String16> & hitBreakpoints,bool isPromiseRejection,bool isUncaught)1085 V8DebuggerAgentImpl::SkipPauseRequest V8DebuggerAgentImpl::didPause(
1086 v8::Local<v8::Context> context, v8::Local<v8::Value> exception,
1087 const std::vector<String16>& hitBreakpoints, bool isPromiseRejection,
1088 bool isUncaught) {
1089 JavaScriptCallFrames callFrames = m_debugger->currentCallFrames(1);
1090 JavaScriptCallFrame* topCallFrame =
1091 !callFrames.empty() ? callFrames.begin()->get() : nullptr;
1092
1093 V8DebuggerAgentImpl::SkipPauseRequest result;
1094 if (m_skipAllPauses)
1095 result = RequestContinue;
1096 else if (!hitBreakpoints.empty())
1097 result = RequestNoSkip; // Don't skip explicit breakpoints even if set in
1098 // frameworks.
1099 else if (!exception.IsEmpty())
1100 result = shouldSkipExceptionPause(topCallFrame);
1101 else if (m_scheduledDebuggerStep != NoStep || m_javaScriptPauseScheduled ||
1102 m_pausingOnNativeEvent)
1103 result = shouldSkipStepPause(topCallFrame);
1104 else
1105 result = RequestNoSkip;
1106
1107 m_skipNextDebuggerStepOut = false;
1108 if (result != RequestNoSkip) return result;
1109 // Skip pauses inside V8 internal scripts and on syntax errors.
1110 if (!topCallFrame) return RequestContinue;
1111
1112 DCHECK(m_pausedContext.IsEmpty());
1113 JavaScriptCallFrames frames = m_debugger->currentCallFrames();
1114 m_pausedCallFrames.swap(frames);
1115 m_pausedContext.Reset(m_isolate, context);
1116 v8::HandleScope handles(m_isolate);
1117
1118 if (!exception.IsEmpty()) {
1119 InjectedScript* injectedScript = nullptr;
1120 m_session->findInjectedScript(V8Debugger::contextId(context),
1121 injectedScript);
1122 if (injectedScript) {
1123 m_breakReason =
1124 isPromiseRejection
1125 ? protocol::Debugger::Paused::ReasonEnum::PromiseRejection
1126 : protocol::Debugger::Paused::ReasonEnum::Exception;
1127 std::unique_ptr<protocol::Runtime::RemoteObject> obj;
1128 injectedScript->wrapObject(exception, kBacktraceObjectGroup, false, false,
1129 &obj);
1130 if (obj) {
1131 m_breakAuxData = obj->serialize();
1132 m_breakAuxData->setBoolean("uncaught", isUncaught);
1133 } else {
1134 m_breakAuxData = nullptr;
1135 }
1136 // m_breakAuxData might be null after this.
1137 }
1138 }
1139
1140 std::unique_ptr<Array<String16>> hitBreakpointIds = Array<String16>::create();
1141
1142 for (const auto& point : hitBreakpoints) {
1143 DebugServerBreakpointToBreakpointIdAndSourceMap::iterator
1144 breakpointIterator = m_serverBreakpoints.find(point);
1145 if (breakpointIterator != m_serverBreakpoints.end()) {
1146 const String16& localId = breakpointIterator->second.first;
1147 hitBreakpointIds->addItem(localId);
1148
1149 BreakpointSource source = breakpointIterator->second.second;
1150 if (m_breakReason == protocol::Debugger::Paused::ReasonEnum::Other &&
1151 source == DebugCommandBreakpointSource)
1152 m_breakReason = protocol::Debugger::Paused::ReasonEnum::DebugCommand;
1153 }
1154 }
1155
1156 std::unique_ptr<Array<CallFrame>> protocolCallFrames;
1157 Response response = currentCallFrames(&protocolCallFrames);
1158 if (!response.isSuccess()) protocolCallFrames = Array<CallFrame>::create();
1159 m_frontend.paused(std::move(protocolCallFrames), m_breakReason,
1160 std::move(m_breakAuxData), std::move(hitBreakpointIds),
1161 currentAsyncStackTrace());
1162 m_scheduledDebuggerStep = NoStep;
1163 m_javaScriptPauseScheduled = false;
1164 m_steppingFromFramework = false;
1165 m_pausingOnNativeEvent = false;
1166 m_skippedStepFrameCount = 0;
1167 m_recursionLevelForStepFrame = 0;
1168
1169 if (!m_continueToLocationBreakpointId.isEmpty()) {
1170 m_debugger->removeBreakpoint(m_continueToLocationBreakpointId);
1171 m_continueToLocationBreakpointId = "";
1172 }
1173 return result;
1174 }
1175
didContinue()1176 void V8DebuggerAgentImpl::didContinue() {
1177 m_pausedContext.Reset();
1178 JavaScriptCallFrames emptyCallFrames;
1179 m_pausedCallFrames.swap(emptyCallFrames);
1180 clearBreakDetails();
1181 m_frontend.resumed();
1182 }
1183
breakProgram(const String16 & breakReason,std::unique_ptr<protocol::DictionaryValue> data)1184 void V8DebuggerAgentImpl::breakProgram(
1185 const String16& breakReason,
1186 std::unique_ptr<protocol::DictionaryValue> data) {
1187 if (!enabled() || m_skipAllPauses || !m_pausedContext.IsEmpty() ||
1188 isCurrentCallStackEmptyOrBlackboxed() ||
1189 !m_debugger->breakpointsActivated())
1190 return;
1191 m_breakReason = breakReason;
1192 m_breakAuxData = std::move(data);
1193 m_scheduledDebuggerStep = NoStep;
1194 m_steppingFromFramework = false;
1195 m_pausingOnNativeEvent = false;
1196 m_debugger->breakProgram();
1197 }
1198
breakProgramOnException(const String16 & breakReason,std::unique_ptr<protocol::DictionaryValue> data)1199 void V8DebuggerAgentImpl::breakProgramOnException(
1200 const String16& breakReason,
1201 std::unique_ptr<protocol::DictionaryValue> data) {
1202 if (!enabled() ||
1203 m_debugger->getPauseOnExceptionsState() ==
1204 v8::DebugInterface::NoBreakOnException)
1205 return;
1206 breakProgram(breakReason, std::move(data));
1207 }
1208
clearBreakDetails()1209 void V8DebuggerAgentImpl::clearBreakDetails() {
1210 m_breakReason = protocol::Debugger::Paused::ReasonEnum::Other;
1211 m_breakAuxData = nullptr;
1212 }
1213
setBreakpointAt(const String16 & scriptId,int lineNumber,int columnNumber,BreakpointSource source,const String16 & condition)1214 void V8DebuggerAgentImpl::setBreakpointAt(const String16& scriptId,
1215 int lineNumber, int columnNumber,
1216 BreakpointSource source,
1217 const String16& condition) {
1218 String16 breakpointId =
1219 generateBreakpointId(scriptId, lineNumber, columnNumber, source);
1220 ScriptBreakpoint breakpoint(lineNumber, columnNumber, condition);
1221 resolveBreakpoint(breakpointId, scriptId, breakpoint, source);
1222 }
1223
removeBreakpointAt(const String16 & scriptId,int lineNumber,int columnNumber,BreakpointSource source)1224 void V8DebuggerAgentImpl::removeBreakpointAt(const String16& scriptId,
1225 int lineNumber, int columnNumber,
1226 BreakpointSource source) {
1227 removeBreakpointImpl(
1228 generateBreakpointId(scriptId, lineNumber, columnNumber, source));
1229 }
1230
reset()1231 void V8DebuggerAgentImpl::reset() {
1232 if (!enabled()) return;
1233 m_scheduledDebuggerStep = NoStep;
1234 m_scripts.clear();
1235 m_blackboxedPositions.clear();
1236 m_breakpointIdToDebuggerBreakpointIds.clear();
1237 }
1238
1239 } // namespace v8_inspector
1240