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