• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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(&currentCallFramesV8)) {
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