1 // Copyright 2016 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "src/inspector/v8-debugger.h"
6 
7 #include "src/inspector/debugger-script.h"
8 #include "src/inspector/protocol/Protocol.h"
9 #include "src/inspector/script-breakpoint.h"
10 #include "src/inspector/string-util.h"
11 #include "src/inspector/v8-debugger-agent-impl.h"
12 #include "src/inspector/v8-inspector-impl.h"
13 #include "src/inspector/v8-internal-value-type.h"
14 #include "src/inspector/v8-stack-trace-impl.h"
15 #include "src/inspector/v8-value-copier.h"
16 
17 #include "include/v8-util.h"
18 
19 namespace v8_inspector {
20 
21 namespace {
22 static const char v8AsyncTaskEventEnqueue[] = "enqueue";
23 static const char v8AsyncTaskEventEnqueueRecurring[] = "enqueueRecurring";
24 static const char v8AsyncTaskEventWillHandle[] = "willHandle";
25 static const char v8AsyncTaskEventDidHandle[] = "didHandle";
26 static const char v8AsyncTaskEventCancel[] = "cancel";
27 
v8Boolean(bool value,v8::Isolate * isolate)28 inline v8::Local<v8::Boolean> v8Boolean(bool value, v8::Isolate* isolate) {
29   return value ? v8::True(isolate) : v8::False(isolate);
30 }
31 
32 }  // namespace
33 
34 static bool inLiveEditScope = false;
35 
callDebuggerMethod(const char * functionName,int argc,v8::Local<v8::Value> argv[])36 v8::MaybeLocal<v8::Value> V8Debugger::callDebuggerMethod(
37     const char* functionName, int argc, v8::Local<v8::Value> argv[]) {
38   v8::MicrotasksScope microtasks(m_isolate,
39                                  v8::MicrotasksScope::kDoNotRunMicrotasks);
40   DCHECK(m_isolate->InContext());
41   v8::Local<v8::Context> context = m_isolate->GetCurrentContext();
42   v8::Local<v8::Object> debuggerScript = m_debuggerScript.Get(m_isolate);
43   v8::Local<v8::Function> function = v8::Local<v8::Function>::Cast(
44       debuggerScript
45           ->Get(context, toV8StringInternalized(m_isolate, functionName))
46           .ToLocalChecked());
47   return function->Call(context, debuggerScript, argc, argv);
48 }
49 
V8Debugger(v8::Isolate * isolate,V8InspectorImpl * inspector)50 V8Debugger::V8Debugger(v8::Isolate* isolate, V8InspectorImpl* inspector)
51     : m_isolate(isolate),
52       m_inspector(inspector),
53       m_lastContextId(0),
54       m_enableCount(0),
55       m_breakpointsActivated(true),
56       m_runningNestedMessageLoop(false),
57       m_ignoreScriptParsedEventsCounter(0),
58       m_maxAsyncCallStackDepth(0),
59       m_pauseOnExceptionsState(v8::DebugInterface::NoBreakOnException) {}
60 
~V8Debugger()61 V8Debugger::~V8Debugger() {}
62 
enable()63 void V8Debugger::enable() {
64   if (m_enableCount++) return;
65   DCHECK(!enabled());
66   v8::HandleScope scope(m_isolate);
67   v8::DebugInterface::SetDebugEventListener(m_isolate,
68                                             &V8Debugger::v8DebugEventCallback,
69                                             v8::External::New(m_isolate, this));
70   m_debuggerContext.Reset(m_isolate,
71                           v8::DebugInterface::GetDebugContext(m_isolate));
72   v8::DebugInterface::ChangeBreakOnException(
73       m_isolate, v8::DebugInterface::NoBreakOnException);
74   m_pauseOnExceptionsState = v8::DebugInterface::NoBreakOnException;
75   compileDebuggerScript();
76 }
77 
disable()78 void V8Debugger::disable() {
79   if (--m_enableCount) return;
80   DCHECK(enabled());
81   clearBreakpoints();
82   m_debuggerScript.Reset();
83   m_debuggerContext.Reset();
84   allAsyncTasksCanceled();
85   v8::DebugInterface::SetDebugEventListener(m_isolate, nullptr);
86 }
87 
enabled() const88 bool V8Debugger::enabled() const { return !m_debuggerScript.IsEmpty(); }
89 
90 // static
contextId(v8::Local<v8::Context> context)91 int V8Debugger::contextId(v8::Local<v8::Context> context) {
92   v8::Local<v8::Value> data =
93       context->GetEmbedderData(static_cast<int>(v8::Context::kDebugIdIndex));
94   if (data.IsEmpty() || !data->IsString()) return 0;
95   String16 dataString = toProtocolString(data.As<v8::String>());
96   if (dataString.isEmpty()) return 0;
97   size_t commaPos = dataString.find(",");
98   if (commaPos == String16::kNotFound) return 0;
99   size_t commaPos2 = dataString.find(",", commaPos + 1);
100   if (commaPos2 == String16::kNotFound) return 0;
101   return dataString.substring(commaPos + 1, commaPos2 - commaPos - 1)
102       .toInteger();
103 }
104 
105 // static
getGroupId(v8::Local<v8::Context> context)106 int V8Debugger::getGroupId(v8::Local<v8::Context> context) {
107   v8::Local<v8::Value> data =
108       context->GetEmbedderData(static_cast<int>(v8::Context::kDebugIdIndex));
109   if (data.IsEmpty() || !data->IsString()) return 0;
110   String16 dataString = toProtocolString(data.As<v8::String>());
111   if (dataString.isEmpty()) return 0;
112   size_t commaPos = dataString.find(",");
113   if (commaPos == String16::kNotFound) return 0;
114   return dataString.substring(0, commaPos).toInteger();
115 }
116 
getCompiledScripts(int contextGroupId,std::vector<std::unique_ptr<V8DebuggerScript>> & result)117 void V8Debugger::getCompiledScripts(
118     int contextGroupId,
119     std::vector<std::unique_ptr<V8DebuggerScript>>& result) {
120   v8::HandleScope scope(m_isolate);
121   v8::PersistentValueVector<v8::DebugInterface::Script> scripts(m_isolate);
122   v8::DebugInterface::GetLoadedScripts(m_isolate, scripts);
123   String16 contextPrefix = String16::fromInteger(contextGroupId) + ",";
124   for (size_t i = 0; i < scripts.Size(); ++i) {
125     v8::Local<v8::DebugInterface::Script> script = scripts.Get(i);
126     if (!script->WasCompiled()) continue;
127     v8::ScriptOriginOptions origin = script->OriginOptions();
128     if (origin.IsEmbedderDebugScript()) continue;
129     v8::Local<v8::String> v8ContextData;
130     if (!script->ContextData().ToLocal(&v8ContextData)) continue;
131     String16 contextData = toProtocolString(v8ContextData);
132     if (contextData.find(contextPrefix) != 0) continue;
133     result.push_back(
134         wrapUnique(new V8DebuggerScript(m_isolate, script, false)));
135   }
136 }
137 
setBreakpoint(const String16 & sourceID,const ScriptBreakpoint & scriptBreakpoint,int * actualLineNumber,int * actualColumnNumber)138 String16 V8Debugger::setBreakpoint(const String16& sourceID,
139                                    const ScriptBreakpoint& scriptBreakpoint,
140                                    int* actualLineNumber,
141                                    int* actualColumnNumber) {
142   v8::HandleScope scope(m_isolate);
143   v8::Local<v8::Context> context = debuggerContext();
144   v8::Context::Scope contextScope(context);
145 
146   v8::Local<v8::Object> info = v8::Object::New(m_isolate);
147   bool success = false;
148   success = info->Set(context, toV8StringInternalized(m_isolate, "sourceID"),
149                       toV8String(m_isolate, sourceID))
150                 .FromMaybe(false);
151   DCHECK(success);
152   success = info->Set(context, toV8StringInternalized(m_isolate, "lineNumber"),
153                       v8::Integer::New(m_isolate, scriptBreakpoint.lineNumber))
154                 .FromMaybe(false);
155   DCHECK(success);
156   success =
157       info->Set(context, toV8StringInternalized(m_isolate, "columnNumber"),
158                 v8::Integer::New(m_isolate, scriptBreakpoint.columnNumber))
159           .FromMaybe(false);
160   DCHECK(success);
161   success = info->Set(context, toV8StringInternalized(m_isolate, "condition"),
162                       toV8String(m_isolate, scriptBreakpoint.condition))
163                 .FromMaybe(false);
164   DCHECK(success);
165 
166   v8::Local<v8::Function> setBreakpointFunction = v8::Local<v8::Function>::Cast(
167       m_debuggerScript.Get(m_isolate)
168           ->Get(context, toV8StringInternalized(m_isolate, "setBreakpoint"))
169           .ToLocalChecked());
170   v8::Local<v8::Value> breakpointId =
171       v8::DebugInterface::Call(debuggerContext(), setBreakpointFunction, info)
172           .ToLocalChecked();
173   if (!breakpointId->IsString()) return "";
174   *actualLineNumber =
175       info->Get(context, toV8StringInternalized(m_isolate, "lineNumber"))
176           .ToLocalChecked()
177           ->Int32Value(context)
178           .FromJust();
179   *actualColumnNumber =
180       info->Get(context, toV8StringInternalized(m_isolate, "columnNumber"))
181           .ToLocalChecked()
182           ->Int32Value(context)
183           .FromJust();
184   return toProtocolString(breakpointId.As<v8::String>());
185 }
186 
removeBreakpoint(const String16 & breakpointId)187 void V8Debugger::removeBreakpoint(const String16& breakpointId) {
188   v8::HandleScope scope(m_isolate);
189   v8::Local<v8::Context> context = debuggerContext();
190   v8::Context::Scope contextScope(context);
191 
192   v8::Local<v8::Object> info = v8::Object::New(m_isolate);
193   bool success = false;
194   success =
195       info->Set(context, toV8StringInternalized(m_isolate, "breakpointId"),
196                 toV8String(m_isolate, breakpointId))
197           .FromMaybe(false);
198   DCHECK(success);
199 
200   v8::Local<v8::Function> removeBreakpointFunction =
201       v8::Local<v8::Function>::Cast(
202           m_debuggerScript.Get(m_isolate)
203               ->Get(context,
204                     toV8StringInternalized(m_isolate, "removeBreakpoint"))
205               .ToLocalChecked());
206   v8::DebugInterface::Call(debuggerContext(), removeBreakpointFunction, info)
207       .ToLocalChecked();
208 }
209 
clearBreakpoints()210 void V8Debugger::clearBreakpoints() {
211   v8::HandleScope scope(m_isolate);
212   v8::Local<v8::Context> context = debuggerContext();
213   v8::Context::Scope contextScope(context);
214 
215   v8::Local<v8::Function> clearBreakpoints = v8::Local<v8::Function>::Cast(
216       m_debuggerScript.Get(m_isolate)
217           ->Get(context, toV8StringInternalized(m_isolate, "clearBreakpoints"))
218           .ToLocalChecked());
219   v8::DebugInterface::Call(debuggerContext(), clearBreakpoints)
220       .ToLocalChecked();
221 }
222 
setBreakpointsActivated(bool activated)223 void V8Debugger::setBreakpointsActivated(bool activated) {
224   if (!enabled()) {
225     UNREACHABLE();
226     return;
227   }
228   v8::HandleScope scope(m_isolate);
229   v8::Local<v8::Context> context = debuggerContext();
230   v8::Context::Scope contextScope(context);
231 
232   v8::Local<v8::Object> info = v8::Object::New(m_isolate);
233   bool success = false;
234   success = info->Set(context, toV8StringInternalized(m_isolate, "enabled"),
235                       v8::Boolean::New(m_isolate, activated))
236                 .FromMaybe(false);
237   DCHECK(success);
238   v8::Local<v8::Function> setBreakpointsActivated =
239       v8::Local<v8::Function>::Cast(
240           m_debuggerScript.Get(m_isolate)
241               ->Get(context, toV8StringInternalized(m_isolate,
242                                                     "setBreakpointsActivated"))
243               .ToLocalChecked());
244   v8::DebugInterface::Call(debuggerContext(), setBreakpointsActivated, info)
245       .ToLocalChecked();
246 
247   m_breakpointsActivated = activated;
248 }
249 
250 v8::DebugInterface::ExceptionBreakState
getPauseOnExceptionsState()251 V8Debugger::getPauseOnExceptionsState() {
252   DCHECK(enabled());
253   return m_pauseOnExceptionsState;
254 }
255 
setPauseOnExceptionsState(v8::DebugInterface::ExceptionBreakState pauseOnExceptionsState)256 void V8Debugger::setPauseOnExceptionsState(
257     v8::DebugInterface::ExceptionBreakState pauseOnExceptionsState) {
258   DCHECK(enabled());
259   if (m_pauseOnExceptionsState == pauseOnExceptionsState) return;
260   v8::DebugInterface::ChangeBreakOnException(m_isolate, pauseOnExceptionsState);
261   m_pauseOnExceptionsState = pauseOnExceptionsState;
262 }
263 
setPauseOnNextStatement(bool pause)264 void V8Debugger::setPauseOnNextStatement(bool pause) {
265   if (m_runningNestedMessageLoop) return;
266   if (pause)
267     v8::DebugInterface::DebugBreak(m_isolate);
268   else
269     v8::DebugInterface::CancelDebugBreak(m_isolate);
270 }
271 
canBreakProgram()272 bool V8Debugger::canBreakProgram() {
273   if (!m_breakpointsActivated) return false;
274   return m_isolate->InContext();
275 }
276 
breakProgram()277 void V8Debugger::breakProgram() {
278   if (isPaused()) {
279     DCHECK(!m_runningNestedMessageLoop);
280     v8::Local<v8::Value> exception;
281     v8::Local<v8::Array> hitBreakpoints;
282     handleProgramBreak(m_pausedContext, m_executionState, exception,
283                        hitBreakpoints);
284     return;
285   }
286 
287   if (!canBreakProgram()) return;
288 
289   v8::HandleScope scope(m_isolate);
290   v8::Local<v8::Function> breakFunction;
291   if (!v8::Function::New(m_isolate->GetCurrentContext(),
292                          &V8Debugger::breakProgramCallback,
293                          v8::External::New(m_isolate, this), 0,
294                          v8::ConstructorBehavior::kThrow)
295            .ToLocal(&breakFunction))
296     return;
297   v8::DebugInterface::Call(debuggerContext(), breakFunction).ToLocalChecked();
298 }
299 
continueProgram()300 void V8Debugger::continueProgram() {
301   if (isPaused()) m_inspector->client()->quitMessageLoopOnPause();
302   m_pausedContext.Clear();
303   m_executionState.Clear();
304 }
305 
stepIntoStatement()306 void V8Debugger::stepIntoStatement() {
307   DCHECK(isPaused());
308   DCHECK(!m_executionState.IsEmpty());
309   v8::DebugInterface::PrepareStep(m_isolate, v8::DebugInterface::StepIn);
310   continueProgram();
311 }
312 
stepOverStatement()313 void V8Debugger::stepOverStatement() {
314   DCHECK(isPaused());
315   DCHECK(!m_executionState.IsEmpty());
316   v8::DebugInterface::PrepareStep(m_isolate, v8::DebugInterface::StepNext);
317   continueProgram();
318 }
319 
stepOutOfFunction()320 void V8Debugger::stepOutOfFunction() {
321   DCHECK(isPaused());
322   DCHECK(!m_executionState.IsEmpty());
323   v8::DebugInterface::PrepareStep(m_isolate, v8::DebugInterface::StepOut);
324   continueProgram();
325 }
326 
clearStepping()327 void V8Debugger::clearStepping() {
328   DCHECK(enabled());
329   v8::DebugInterface::ClearStepping(m_isolate);
330 }
331 
setScriptSource(const String16 & sourceID,v8::Local<v8::String> newSource,bool dryRun,Maybe<protocol::Runtime::ExceptionDetails> * exceptionDetails,JavaScriptCallFrames * newCallFrames,Maybe<bool> * stackChanged,bool * compileError)332 Response V8Debugger::setScriptSource(
333     const String16& sourceID, v8::Local<v8::String> newSource, bool dryRun,
334     Maybe<protocol::Runtime::ExceptionDetails>* exceptionDetails,
335     JavaScriptCallFrames* newCallFrames, Maybe<bool>* stackChanged,
336     bool* compileError) {
337   class EnableLiveEditScope {
338    public:
339     explicit EnableLiveEditScope(v8::Isolate* isolate) : m_isolate(isolate) {
340       v8::DebugInterface::SetLiveEditEnabled(m_isolate, true);
341       inLiveEditScope = true;
342     }
343     ~EnableLiveEditScope() {
344       v8::DebugInterface::SetLiveEditEnabled(m_isolate, false);
345       inLiveEditScope = false;
346     }
347 
348    private:
349     v8::Isolate* m_isolate;
350   };
351 
352   *compileError = false;
353   DCHECK(enabled());
354   v8::HandleScope scope(m_isolate);
355 
356   std::unique_ptr<v8::Context::Scope> contextScope;
357   if (!isPaused())
358     contextScope = wrapUnique(new v8::Context::Scope(debuggerContext()));
359 
360   v8::Local<v8::Value> argv[] = {toV8String(m_isolate, sourceID), newSource,
361                                  v8Boolean(dryRun, m_isolate)};
362 
363   v8::Local<v8::Value> v8result;
364   {
365     EnableLiveEditScope enableLiveEditScope(m_isolate);
366     v8::TryCatch tryCatch(m_isolate);
367     tryCatch.SetVerbose(false);
368     v8::MaybeLocal<v8::Value> maybeResult =
369         callDebuggerMethod("liveEditScriptSource", 3, argv);
370     if (tryCatch.HasCaught()) {
371       v8::Local<v8::Message> message = tryCatch.Message();
372       if (!message.IsEmpty())
373         return Response::Error(toProtocolStringWithTypeCheck(message->Get()));
374       else
375         return Response::InternalError();
376     }
377     v8result = maybeResult.ToLocalChecked();
378   }
379   DCHECK(!v8result.IsEmpty());
380   v8::Local<v8::Context> context = m_isolate->GetCurrentContext();
381   v8::Local<v8::Object> resultTuple =
382       v8result->ToObject(context).ToLocalChecked();
383   int code = static_cast<int>(resultTuple->Get(context, 0)
384                                   .ToLocalChecked()
385                                   ->ToInteger(context)
386                                   .ToLocalChecked()
387                                   ->Value());
388   switch (code) {
389     case 0: {
390       *stackChanged = resultTuple->Get(context, 1)
391                           .ToLocalChecked()
392                           ->BooleanValue(context)
393                           .FromJust();
394       // Call stack may have changed after if the edited function was on the
395       // stack.
396       if (!dryRun && isPaused()) {
397         JavaScriptCallFrames frames = currentCallFrames();
398         newCallFrames->swap(frames);
399       }
400       return Response::OK();
401     }
402     // Compile error.
403     case 1: {
404       *exceptionDetails =
405           protocol::Runtime::ExceptionDetails::create()
406               .setExceptionId(m_inspector->nextExceptionId())
407               .setText(toProtocolStringWithTypeCheck(
408                   resultTuple->Get(context, 2).ToLocalChecked()))
409               .setLineNumber(static_cast<int>(resultTuple->Get(context, 3)
410                                                   .ToLocalChecked()
411                                                   ->ToInteger(context)
412                                                   .ToLocalChecked()
413                                                   ->Value()) -
414                              1)
415               .setColumnNumber(static_cast<int>(resultTuple->Get(context, 4)
416                                                     .ToLocalChecked()
417                                                     ->ToInteger(context)
418                                                     .ToLocalChecked()
419                                                     ->Value()) -
420                                1)
421               .build();
422       *compileError = true;
423       return Response::OK();
424     }
425   }
426   return Response::InternalError();
427 }
428 
currentCallFrames(int limit)429 JavaScriptCallFrames V8Debugger::currentCallFrames(int limit) {
430   if (!m_isolate->InContext()) return JavaScriptCallFrames();
431   v8::Local<v8::Value> currentCallFramesV8;
432   if (m_executionState.IsEmpty()) {
433     v8::Local<v8::Function> currentCallFramesFunction =
434         v8::Local<v8::Function>::Cast(
435             m_debuggerScript.Get(m_isolate)
436                 ->Get(debuggerContext(),
437                       toV8StringInternalized(m_isolate, "currentCallFrames"))
438                 .ToLocalChecked());
439     currentCallFramesV8 =
440         v8::DebugInterface::Call(debuggerContext(), currentCallFramesFunction,
441                                  v8::Integer::New(m_isolate, limit))
442             .ToLocalChecked();
443   } else {
444     v8::Local<v8::Value> argv[] = {m_executionState,
445                                    v8::Integer::New(m_isolate, limit)};
446     currentCallFramesV8 =
447         callDebuggerMethod("currentCallFrames", arraysize(argv), argv)
448             .ToLocalChecked();
449   }
450   DCHECK(!currentCallFramesV8.IsEmpty());
451   if (!currentCallFramesV8->IsArray()) return JavaScriptCallFrames();
452   v8::Local<v8::Array> callFramesArray = currentCallFramesV8.As<v8::Array>();
453   JavaScriptCallFrames callFrames;
454   for (uint32_t i = 0; i < callFramesArray->Length(); ++i) {
455     v8::Local<v8::Value> callFrameValue;
456     if (!callFramesArray->Get(debuggerContext(), i).ToLocal(&callFrameValue))
457       return JavaScriptCallFrames();
458     if (!callFrameValue->IsObject()) return JavaScriptCallFrames();
459     v8::Local<v8::Object> callFrameObject = callFrameValue.As<v8::Object>();
460     callFrames.push_back(JavaScriptCallFrame::create(
461         debuggerContext(), v8::Local<v8::Object>::Cast(callFrameObject)));
462   }
463   return callFrames;
464 }
465 
toV8Debugger(v8::Local<v8::Value> data)466 static V8Debugger* toV8Debugger(v8::Local<v8::Value> data) {
467   void* p = v8::Local<v8::External>::Cast(data)->Value();
468   return static_cast<V8Debugger*>(p);
469 }
470 
breakProgramCallback(const v8::FunctionCallbackInfo<v8::Value> & info)471 void V8Debugger::breakProgramCallback(
472     const v8::FunctionCallbackInfo<v8::Value>& info) {
473   DCHECK_EQ(info.Length(), 2);
474   V8Debugger* thisPtr = toV8Debugger(info.Data());
475   if (!thisPtr->enabled()) return;
476   v8::Local<v8::Context> pausedContext =
477       thisPtr->m_isolate->GetCurrentContext();
478   v8::Local<v8::Value> exception;
479   v8::Local<v8::Array> hitBreakpoints;
480   thisPtr->handleProgramBreak(pausedContext,
481                               v8::Local<v8::Object>::Cast(info[0]), exception,
482                               hitBreakpoints);
483 }
484 
handleProgramBreak(v8::Local<v8::Context> pausedContext,v8::Local<v8::Object> executionState,v8::Local<v8::Value> exception,v8::Local<v8::Array> hitBreakpointNumbers,bool isPromiseRejection,bool isUncaught)485 void V8Debugger::handleProgramBreak(v8::Local<v8::Context> pausedContext,
486                                     v8::Local<v8::Object> executionState,
487                                     v8::Local<v8::Value> exception,
488                                     v8::Local<v8::Array> hitBreakpointNumbers,
489                                     bool isPromiseRejection, bool isUncaught) {
490   // Don't allow nested breaks.
491   if (m_runningNestedMessageLoop) return;
492 
493   V8DebuggerAgentImpl* agent =
494       m_inspector->enabledDebuggerAgentForGroup(getGroupId(pausedContext));
495   if (!agent) return;
496 
497   std::vector<String16> breakpointIds;
498   if (!hitBreakpointNumbers.IsEmpty()) {
499     breakpointIds.reserve(hitBreakpointNumbers->Length());
500     for (uint32_t i = 0; i < hitBreakpointNumbers->Length(); i++) {
501       v8::Local<v8::Value> hitBreakpointNumber =
502           hitBreakpointNumbers->Get(debuggerContext(), i).ToLocalChecked();
503       DCHECK(hitBreakpointNumber->IsInt32());
504       breakpointIds.push_back(String16::fromInteger(
505           hitBreakpointNumber->Int32Value(debuggerContext()).FromJust()));
506     }
507   }
508 
509   m_pausedContext = pausedContext;
510   m_executionState = executionState;
511   V8DebuggerAgentImpl::SkipPauseRequest result = agent->didPause(
512       pausedContext, exception, breakpointIds, isPromiseRejection, isUncaught);
513   if (result == V8DebuggerAgentImpl::RequestNoSkip) {
514     m_runningNestedMessageLoop = true;
515     int groupId = getGroupId(pausedContext);
516     DCHECK(groupId);
517     m_inspector->client()->runMessageLoopOnPause(groupId);
518     // The agent may have been removed in the nested loop.
519     agent =
520         m_inspector->enabledDebuggerAgentForGroup(getGroupId(pausedContext));
521     if (agent) agent->didContinue();
522     m_runningNestedMessageLoop = false;
523   }
524   m_pausedContext.Clear();
525   m_executionState.Clear();
526 
527   if (result == V8DebuggerAgentImpl::RequestStepFrame) {
528     v8::DebugInterface::PrepareStep(m_isolate, v8::DebugInterface::StepFrame);
529   } else if (result == V8DebuggerAgentImpl::RequestStepInto) {
530     v8::DebugInterface::PrepareStep(m_isolate, v8::DebugInterface::StepIn);
531   } else if (result == V8DebuggerAgentImpl::RequestStepOut) {
532     v8::DebugInterface::PrepareStep(m_isolate, v8::DebugInterface::StepOut);
533   }
534 }
535 
v8DebugEventCallback(const v8::DebugInterface::EventDetails & eventDetails)536 void V8Debugger::v8DebugEventCallback(
537     const v8::DebugInterface::EventDetails& eventDetails) {
538   V8Debugger* thisPtr = toV8Debugger(eventDetails.GetCallbackData());
539   thisPtr->handleV8DebugEvent(eventDetails);
540 }
541 
callInternalGetterFunction(v8::Local<v8::Object> object,const char * functionName)542 v8::Local<v8::Value> V8Debugger::callInternalGetterFunction(
543     v8::Local<v8::Object> object, const char* functionName) {
544   v8::MicrotasksScope microtasks(m_isolate,
545                                  v8::MicrotasksScope::kDoNotRunMicrotasks);
546   v8::Local<v8::Value> getterValue =
547       object
548           ->Get(m_isolate->GetCurrentContext(),
549                 toV8StringInternalized(m_isolate, functionName))
550           .ToLocalChecked();
551   DCHECK(!getterValue.IsEmpty() && getterValue->IsFunction());
552   return v8::Local<v8::Function>::Cast(getterValue)
553       ->Call(m_isolate->GetCurrentContext(), object, 0, nullptr)
554       .ToLocalChecked();
555 }
556 
handleV8DebugEvent(const v8::DebugInterface::EventDetails & eventDetails)557 void V8Debugger::handleV8DebugEvent(
558     const v8::DebugInterface::EventDetails& eventDetails) {
559   if (!enabled()) return;
560   v8::DebugEvent event = eventDetails.GetEvent();
561   if (event != v8::AsyncTaskEvent && event != v8::Break &&
562       event != v8::Exception && event != v8::AfterCompile &&
563       event != v8::BeforeCompile && event != v8::CompileError)
564     return;
565 
566   v8::Local<v8::Context> eventContext = eventDetails.GetEventContext();
567   DCHECK(!eventContext.IsEmpty());
568 
569   if (event == v8::AsyncTaskEvent) {
570     v8::HandleScope scope(m_isolate);
571     handleV8AsyncTaskEvent(eventContext, eventDetails.GetExecutionState(),
572                            eventDetails.GetEventData());
573     return;
574   }
575 
576   V8DebuggerAgentImpl* agent =
577       m_inspector->enabledDebuggerAgentForGroup(getGroupId(eventContext));
578   if (agent) {
579     v8::HandleScope scope(m_isolate);
580     if (m_ignoreScriptParsedEventsCounter == 0 &&
581         (event == v8::AfterCompile || event == v8::CompileError)) {
582       v8::Local<v8::Context> context = debuggerContext();
583       v8::Context::Scope contextScope(context);
584       v8::Local<v8::Value> argv[] = {eventDetails.GetEventData()};
585       v8::Local<v8::Value> value =
586           callDebuggerMethod("getAfterCompileScript", 1, argv).ToLocalChecked();
587       if (value->IsNull()) return;
588       DCHECK(value->IsObject());
589       v8::Local<v8::Object> scriptObject = v8::Local<v8::Object>::Cast(value);
590       v8::Local<v8::DebugInterface::Script> script;
591       if (!v8::DebugInterface::Script::Wrap(m_isolate, scriptObject)
592                .ToLocal(&script))
593         return;
594       agent->didParseSource(
595           wrapUnique(new V8DebuggerScript(m_isolate, script, inLiveEditScope)),
596           event == v8::AfterCompile);
597     } else if (event == v8::Exception) {
598       v8::Local<v8::Context> context = debuggerContext();
599       v8::Local<v8::Object> eventData = eventDetails.GetEventData();
600       v8::Local<v8::Value> exception =
601           callInternalGetterFunction(eventData, "exception");
602       v8::Local<v8::Value> promise =
603           callInternalGetterFunction(eventData, "promise");
604       bool isPromiseRejection = !promise.IsEmpty() && promise->IsObject();
605       v8::Local<v8::Value> uncaught =
606           callInternalGetterFunction(eventData, "uncaught");
607       bool isUncaught = uncaught->BooleanValue(context).FromJust();
608       handleProgramBreak(eventContext, eventDetails.GetExecutionState(),
609                          exception, v8::Local<v8::Array>(), isPromiseRejection,
610                          isUncaught);
611     } else if (event == v8::Break) {
612       v8::Local<v8::Value> argv[] = {eventDetails.GetEventData()};
613       v8::Local<v8::Value> hitBreakpoints =
614           callDebuggerMethod("getBreakpointNumbers", 1, argv).ToLocalChecked();
615       DCHECK(hitBreakpoints->IsArray());
616       handleProgramBreak(eventContext, eventDetails.GetExecutionState(),
617                          v8::Local<v8::Value>(),
618                          hitBreakpoints.As<v8::Array>());
619     }
620   }
621 }
622 
handleV8AsyncTaskEvent(v8::Local<v8::Context> context,v8::Local<v8::Object> executionState,v8::Local<v8::Object> eventData)623 void V8Debugger::handleV8AsyncTaskEvent(v8::Local<v8::Context> context,
624                                         v8::Local<v8::Object> executionState,
625                                         v8::Local<v8::Object> eventData) {
626   if (!m_maxAsyncCallStackDepth) return;
627 
628   String16 type = toProtocolStringWithTypeCheck(
629       callInternalGetterFunction(eventData, "type"));
630   String16 name = toProtocolStringWithTypeCheck(
631       callInternalGetterFunction(eventData, "name"));
632   int id = static_cast<int>(callInternalGetterFunction(eventData, "id")
633                                 ->ToInteger(context)
634                                 .ToLocalChecked()
635                                 ->Value());
636   // Async task events from Promises are given misaligned pointers to prevent
637   // from overlapping with other Blink task identifiers. There is a single
638   // namespace of such ids, managed by src/js/promise.js.
639   void* ptr = reinterpret_cast<void*>(id * 2 + 1);
640   if (type == v8AsyncTaskEventEnqueue)
641     asyncTaskScheduled(name, ptr, false);
642   else if (type == v8AsyncTaskEventEnqueueRecurring)
643     asyncTaskScheduled(name, ptr, true);
644   else if (type == v8AsyncTaskEventWillHandle)
645     asyncTaskStarted(ptr);
646   else if (type == v8AsyncTaskEventDidHandle)
647     asyncTaskFinished(ptr);
648   else if (type == v8AsyncTaskEventCancel)
649     asyncTaskCanceled(ptr);
650   else
651     UNREACHABLE();
652 }
653 
currentAsyncCallChain()654 V8StackTraceImpl* V8Debugger::currentAsyncCallChain() {
655   if (!m_currentStacks.size()) return nullptr;
656   return m_currentStacks.back().get();
657 }
658 
compileDebuggerScript()659 void V8Debugger::compileDebuggerScript() {
660   if (!m_debuggerScript.IsEmpty()) {
661     UNREACHABLE();
662     return;
663   }
664 
665   v8::HandleScope scope(m_isolate);
666   v8::Context::Scope contextScope(debuggerContext());
667 
668   v8::Local<v8::String> scriptValue =
669       v8::String::NewFromUtf8(m_isolate, DebuggerScript_js,
670                               v8::NewStringType::kInternalized,
671                               sizeof(DebuggerScript_js))
672           .ToLocalChecked();
673   v8::Local<v8::Value> value;
674   if (!m_inspector->compileAndRunInternalScript(debuggerContext(), scriptValue)
675            .ToLocal(&value)) {
676     UNREACHABLE();
677     return;
678   }
679   DCHECK(value->IsObject());
680   m_debuggerScript.Reset(m_isolate, value.As<v8::Object>());
681 }
682 
debuggerContext() const683 v8::Local<v8::Context> V8Debugger::debuggerContext() const {
684   DCHECK(!m_debuggerContext.IsEmpty());
685   return m_debuggerContext.Get(m_isolate);
686 }
687 
functionScopes(v8::Local<v8::Context> context,v8::Local<v8::Function> function)688 v8::MaybeLocal<v8::Value> V8Debugger::functionScopes(
689     v8::Local<v8::Context> context, v8::Local<v8::Function> function) {
690   if (!enabled()) {
691     UNREACHABLE();
692     return v8::Local<v8::Value>::New(m_isolate, v8::Undefined(m_isolate));
693   }
694   v8::Local<v8::Value> argv[] = {function};
695   v8::Local<v8::Value> scopesValue;
696   if (!callDebuggerMethod("getFunctionScopes", 1, argv).ToLocal(&scopesValue))
697     return v8::MaybeLocal<v8::Value>();
698   v8::Local<v8::Value> copied;
699   if (!copyValueFromDebuggerContext(m_isolate, debuggerContext(), context,
700                                     scopesValue)
701            .ToLocal(&copied) ||
702       !copied->IsArray())
703     return v8::MaybeLocal<v8::Value>();
704   if (!markAsInternal(context, v8::Local<v8::Array>::Cast(copied),
705                       V8InternalValueType::kScopeList))
706     return v8::MaybeLocal<v8::Value>();
707   if (!markArrayEntriesAsInternal(context, v8::Local<v8::Array>::Cast(copied),
708                                   V8InternalValueType::kScope))
709     return v8::MaybeLocal<v8::Value>();
710   return copied;
711 }
712 
internalProperties(v8::Local<v8::Context> context,v8::Local<v8::Value> value)713 v8::MaybeLocal<v8::Array> V8Debugger::internalProperties(
714     v8::Local<v8::Context> context, v8::Local<v8::Value> value) {
715   v8::Local<v8::Array> properties;
716   if (!v8::DebugInterface::GetInternalProperties(m_isolate, value)
717            .ToLocal(&properties))
718     return v8::MaybeLocal<v8::Array>();
719   if (value->IsFunction()) {
720     v8::Local<v8::Function> function = value.As<v8::Function>();
721     v8::Local<v8::Value> location = functionLocation(context, function);
722     if (location->IsObject()) {
723       createDataProperty(
724           context, properties, properties->Length(),
725           toV8StringInternalized(m_isolate, "[[FunctionLocation]]"));
726       createDataProperty(context, properties, properties->Length(), location);
727     }
728     if (function->IsGeneratorFunction()) {
729       createDataProperty(context, properties, properties->Length(),
730                          toV8StringInternalized(m_isolate, "[[IsGenerator]]"));
731       createDataProperty(context, properties, properties->Length(),
732                          v8::True(m_isolate));
733     }
734   }
735   if (!enabled()) return properties;
736   if (value->IsMap() || value->IsWeakMap() || value->IsSet() ||
737       value->IsWeakSet() || value->IsSetIterator() || value->IsMapIterator()) {
738     v8::Local<v8::Value> entries =
739         collectionEntries(context, v8::Local<v8::Object>::Cast(value));
740     if (entries->IsArray()) {
741       createDataProperty(context, properties, properties->Length(),
742                          toV8StringInternalized(m_isolate, "[[Entries]]"));
743       createDataProperty(context, properties, properties->Length(), entries);
744     }
745   }
746   if (value->IsGeneratorObject()) {
747     v8::Local<v8::Value> location =
748         generatorObjectLocation(context, v8::Local<v8::Object>::Cast(value));
749     if (location->IsObject()) {
750       createDataProperty(
751           context, properties, properties->Length(),
752           toV8StringInternalized(m_isolate, "[[GeneratorLocation]]"));
753       createDataProperty(context, properties, properties->Length(), location);
754     }
755   }
756   if (value->IsFunction()) {
757     v8::Local<v8::Function> function = value.As<v8::Function>();
758     v8::Local<v8::Value> boundFunction = function->GetBoundFunction();
759     v8::Local<v8::Value> scopes;
760     if (boundFunction->IsUndefined() &&
761         functionScopes(context, function).ToLocal(&scopes)) {
762       createDataProperty(context, properties, properties->Length(),
763                          toV8StringInternalized(m_isolate, "[[Scopes]]"));
764       createDataProperty(context, properties, properties->Length(), scopes);
765     }
766   }
767   return properties;
768 }
769 
collectionEntries(v8::Local<v8::Context> context,v8::Local<v8::Object> object)770 v8::Local<v8::Value> V8Debugger::collectionEntries(
771     v8::Local<v8::Context> context, v8::Local<v8::Object> object) {
772   if (!enabled()) {
773     UNREACHABLE();
774     return v8::Undefined(m_isolate);
775   }
776   v8::Local<v8::Value> argv[] = {object};
777   v8::Local<v8::Value> entriesValue =
778       callDebuggerMethod("getCollectionEntries", 1, argv).ToLocalChecked();
779   if (!entriesValue->IsArray()) return v8::Undefined(m_isolate);
780 
781   v8::Local<v8::Array> entries = entriesValue.As<v8::Array>();
782   v8::Local<v8::Array> copiedArray =
783       v8::Array::New(m_isolate, entries->Length());
784   if (!copiedArray->SetPrototype(context, v8::Null(m_isolate)).FromMaybe(false))
785     return v8::Undefined(m_isolate);
786   for (uint32_t i = 0; i < entries->Length(); ++i) {
787     v8::Local<v8::Value> item;
788     if (!entries->Get(debuggerContext(), i).ToLocal(&item))
789       return v8::Undefined(m_isolate);
790     v8::Local<v8::Value> copied;
791     if (!copyValueFromDebuggerContext(m_isolate, debuggerContext(), context,
792                                       item)
793              .ToLocal(&copied))
794       return v8::Undefined(m_isolate);
795     if (!createDataProperty(context, copiedArray, i, copied).FromMaybe(false))
796       return v8::Undefined(m_isolate);
797   }
798   if (!markArrayEntriesAsInternal(context,
799                                   v8::Local<v8::Array>::Cast(copiedArray),
800                                   V8InternalValueType::kEntry))
801     return v8::Undefined(m_isolate);
802   return copiedArray;
803 }
804 
generatorObjectLocation(v8::Local<v8::Context> context,v8::Local<v8::Object> object)805 v8::Local<v8::Value> V8Debugger::generatorObjectLocation(
806     v8::Local<v8::Context> context, v8::Local<v8::Object> object) {
807   if (!enabled()) {
808     UNREACHABLE();
809     return v8::Null(m_isolate);
810   }
811   v8::Local<v8::Value> argv[] = {object};
812   v8::Local<v8::Value> location =
813       callDebuggerMethod("getGeneratorObjectLocation", 1, argv)
814           .ToLocalChecked();
815   v8::Local<v8::Value> copied;
816   if (!copyValueFromDebuggerContext(m_isolate, debuggerContext(), context,
817                                     location)
818            .ToLocal(&copied) ||
819       !copied->IsObject())
820     return v8::Null(m_isolate);
821   if (!markAsInternal(context, v8::Local<v8::Object>::Cast(copied),
822                       V8InternalValueType::kLocation))
823     return v8::Null(m_isolate);
824   return copied;
825 }
826 
functionLocation(v8::Local<v8::Context> context,v8::Local<v8::Function> function)827 v8::Local<v8::Value> V8Debugger::functionLocation(
828     v8::Local<v8::Context> context, v8::Local<v8::Function> function) {
829   int scriptId = function->ScriptId();
830   if (scriptId == v8::UnboundScript::kNoScriptId) return v8::Null(m_isolate);
831   int lineNumber = function->GetScriptLineNumber();
832   int columnNumber = function->GetScriptColumnNumber();
833   if (lineNumber == v8::Function::kLineOffsetNotFound ||
834       columnNumber == v8::Function::kLineOffsetNotFound)
835     return v8::Null(m_isolate);
836   v8::Local<v8::Object> location = v8::Object::New(m_isolate);
837   if (!location->SetPrototype(context, v8::Null(m_isolate)).FromMaybe(false))
838     return v8::Null(m_isolate);
839   if (!createDataProperty(
840            context, location, toV8StringInternalized(m_isolate, "scriptId"),
841            toV8String(m_isolate, String16::fromInteger(scriptId)))
842            .FromMaybe(false))
843     return v8::Null(m_isolate);
844   if (!createDataProperty(context, location,
845                           toV8StringInternalized(m_isolate, "lineNumber"),
846                           v8::Integer::New(m_isolate, lineNumber))
847            .FromMaybe(false))
848     return v8::Null(m_isolate);
849   if (!createDataProperty(context, location,
850                           toV8StringInternalized(m_isolate, "columnNumber"),
851                           v8::Integer::New(m_isolate, columnNumber))
852            .FromMaybe(false))
853     return v8::Null(m_isolate);
854   if (!markAsInternal(context, location, V8InternalValueType::kLocation))
855     return v8::Null(m_isolate);
856   return location;
857 }
858 
isPaused()859 bool V8Debugger::isPaused() { return !m_pausedContext.IsEmpty(); }
860 
createStackTrace(v8::Local<v8::StackTrace> stackTrace)861 std::unique_ptr<V8StackTraceImpl> V8Debugger::createStackTrace(
862     v8::Local<v8::StackTrace> stackTrace) {
863   int contextGroupId =
864       m_isolate->InContext() ? getGroupId(m_isolate->GetCurrentContext()) : 0;
865   return V8StackTraceImpl::create(this, contextGroupId, stackTrace,
866                                   V8StackTraceImpl::maxCallStackSizeToCapture);
867 }
868 
markContext(const V8ContextInfo & info)869 int V8Debugger::markContext(const V8ContextInfo& info) {
870   DCHECK(info.context->GetIsolate() == m_isolate);
871   int contextId = ++m_lastContextId;
872   String16 debugData = String16::fromInteger(info.contextGroupId) + "," +
873                        String16::fromInteger(contextId) + "," +
874                        toString16(info.auxData);
875   v8::Context::Scope contextScope(info.context);
876   info.context->SetEmbedderData(static_cast<int>(v8::Context::kDebugIdIndex),
877                                 toV8String(m_isolate, debugData));
878   return contextId;
879 }
880 
setAsyncCallStackDepth(V8DebuggerAgentImpl * agent,int depth)881 void V8Debugger::setAsyncCallStackDepth(V8DebuggerAgentImpl* agent, int depth) {
882   if (depth <= 0)
883     m_maxAsyncCallStackDepthMap.erase(agent);
884   else
885     m_maxAsyncCallStackDepthMap[agent] = depth;
886 
887   int maxAsyncCallStackDepth = 0;
888   for (const auto& pair : m_maxAsyncCallStackDepthMap) {
889     if (pair.second > maxAsyncCallStackDepth)
890       maxAsyncCallStackDepth = pair.second;
891   }
892 
893   if (m_maxAsyncCallStackDepth == maxAsyncCallStackDepth) return;
894   m_maxAsyncCallStackDepth = maxAsyncCallStackDepth;
895   if (!maxAsyncCallStackDepth) allAsyncTasksCanceled();
896 }
897 
asyncTaskScheduled(const StringView & taskName,void * task,bool recurring)898 void V8Debugger::asyncTaskScheduled(const StringView& taskName, void* task,
899                                     bool recurring) {
900   if (!m_maxAsyncCallStackDepth) return;
901   asyncTaskScheduled(toString16(taskName), task, recurring);
902 }
903 
asyncTaskScheduled(const String16 & taskName,void * task,bool recurring)904 void V8Debugger::asyncTaskScheduled(const String16& taskName, void* task,
905                                     bool recurring) {
906   if (!m_maxAsyncCallStackDepth) return;
907   v8::HandleScope scope(m_isolate);
908   int contextGroupId =
909       m_isolate->InContext() ? getGroupId(m_isolate->GetCurrentContext()) : 0;
910   std::unique_ptr<V8StackTraceImpl> chain = V8StackTraceImpl::capture(
911       this, contextGroupId, V8StackTraceImpl::maxCallStackSizeToCapture,
912       taskName);
913   if (chain) {
914     m_asyncTaskStacks[task] = std::move(chain);
915     if (recurring) m_recurringTasks.insert(task);
916   }
917 }
918 
asyncTaskCanceled(void * task)919 void V8Debugger::asyncTaskCanceled(void* task) {
920   if (!m_maxAsyncCallStackDepth) return;
921   m_asyncTaskStacks.erase(task);
922   m_recurringTasks.erase(task);
923 }
924 
asyncTaskStarted(void * task)925 void V8Debugger::asyncTaskStarted(void* task) {
926   if (!m_maxAsyncCallStackDepth) return;
927   m_currentTasks.push_back(task);
928   AsyncTaskToStackTrace::iterator stackIt = m_asyncTaskStacks.find(task);
929   // Needs to support following order of events:
930   // - asyncTaskScheduled
931   //   <-- attached here -->
932   // - asyncTaskStarted
933   // - asyncTaskCanceled <-- canceled before finished
934   //   <-- async stack requested here -->
935   // - asyncTaskFinished
936   std::unique_ptr<V8StackTraceImpl> stack;
937   if (stackIt != m_asyncTaskStacks.end() && stackIt->second)
938     stack = stackIt->second->cloneImpl();
939   m_currentStacks.push_back(std::move(stack));
940 }
941 
asyncTaskFinished(void * task)942 void V8Debugger::asyncTaskFinished(void* task) {
943   if (!m_maxAsyncCallStackDepth) return;
944   // We could start instrumenting half way and the stack is empty.
945   if (!m_currentStacks.size()) return;
946 
947   DCHECK(m_currentTasks.back() == task);
948   m_currentTasks.pop_back();
949 
950   m_currentStacks.pop_back();
951   if (m_recurringTasks.find(task) == m_recurringTasks.end())
952     m_asyncTaskStacks.erase(task);
953 }
954 
allAsyncTasksCanceled()955 void V8Debugger::allAsyncTasksCanceled() {
956   m_asyncTaskStacks.clear();
957   m_recurringTasks.clear();
958   m_currentStacks.clear();
959   m_currentTasks.clear();
960 }
961 
muteScriptParsedEvents()962 void V8Debugger::muteScriptParsedEvents() {
963   ++m_ignoreScriptParsedEventsCounter;
964 }
965 
unmuteScriptParsedEvents()966 void V8Debugger::unmuteScriptParsedEvents() {
967   --m_ignoreScriptParsedEventsCounter;
968   DCHECK_GE(m_ignoreScriptParsedEventsCounter, 0);
969 }
970 
captureStackTrace(bool fullStack)971 std::unique_ptr<V8StackTraceImpl> V8Debugger::captureStackTrace(
972     bool fullStack) {
973   if (!m_isolate->InContext()) return nullptr;
974 
975   v8::HandleScope handles(m_isolate);
976   int contextGroupId = getGroupId(m_isolate->GetCurrentContext());
977   if (!contextGroupId) return nullptr;
978 
979   size_t stackSize =
980       fullStack ? V8StackTraceImpl::maxCallStackSizeToCapture : 1;
981   if (m_inspector->enabledRuntimeAgentForGroup(contextGroupId))
982     stackSize = V8StackTraceImpl::maxCallStackSizeToCapture;
983 
984   return V8StackTraceImpl::capture(this, contextGroupId, stackSize);
985 }
986 
987 }  // namespace v8_inspector
988