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