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