OLD | NEW |
| (Empty) |
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/V8Debugger.h" | |
6 | |
7 #include "src/inspector/DebuggerScript.h" | |
8 #include "src/inspector/ScriptBreakpoint.h" | |
9 #include "src/inspector/StringUtil.h" | |
10 #include "src/inspector/V8Compat.h" | |
11 #include "src/inspector/V8DebuggerAgentImpl.h" | |
12 #include "src/inspector/V8InspectorImpl.h" | |
13 #include "src/inspector/V8InternalValueType.h" | |
14 #include "src/inspector/V8StackTraceImpl.h" | |
15 #include "src/inspector/V8ValueCopier.h" | |
16 #include "src/inspector/protocol/Protocol.h" | |
17 #include "src/inspector/public/V8InspectorClient.h" | |
18 | |
19 namespace v8_inspector { | |
20 | |
21 namespace { | |
22 const char stepIntoV8MethodName[] = "stepIntoStatement"; | |
23 const char stepOutV8MethodName[] = "stepOutOfFunction"; | |
24 static const char v8AsyncTaskEventEnqueue[] = "enqueue"; | |
25 static const char v8AsyncTaskEventWillHandle[] = "willHandle"; | |
26 static const char v8AsyncTaskEventDidHandle[] = "didHandle"; | |
27 | |
28 inline v8::Local<v8::Boolean> v8Boolean(bool value, v8::Isolate* isolate) | |
29 { | |
30 return value ? v8::True(isolate) : v8::False(isolate); | |
31 } | |
32 | |
33 } // namespace | |
34 | |
35 static bool inLiveEditScope = false; | |
36 | |
37 v8::MaybeLocal<v8::Value> V8Debugger::callDebuggerMethod(const char* functionNam
e, int argc, v8::Local<v8::Value> argv[]) | |
38 { | |
39 v8::MicrotasksScope microtasks(m_isolate, v8::MicrotasksScope::kDoNotRunMicr
otasks); | |
40 v8::Local<v8::Object> debuggerScript = m_debuggerScript.Get(m_isolate); | |
41 v8::Local<v8::Function> function = v8::Local<v8::Function>::Cast(debuggerScr
ipt->Get(toV8StringInternalized(m_isolate, functionName))); | |
42 DCHECK(m_isolate->InContext()); | |
43 return function->Call(m_isolate->GetCurrentContext(), debuggerScript, argc,
argv); | |
44 } | |
45 | |
46 V8Debugger::V8Debugger(v8::Isolate* isolate, V8InspectorImpl* inspector) | |
47 : m_isolate(isolate) | |
48 , m_inspector(inspector) | |
49 , m_lastContextId(0) | |
50 , m_enableCount(0) | |
51 , m_breakpointsActivated(true) | |
52 , m_runningNestedMessageLoop(false) | |
53 , m_ignoreScriptParsedEventsCounter(0) | |
54 , m_maxAsyncCallStackDepth(0) | |
55 { | |
56 } | |
57 | |
58 V8Debugger::~V8Debugger() | |
59 { | |
60 } | |
61 | |
62 void V8Debugger::enable() | |
63 { | |
64 if (m_enableCount++) | |
65 return; | |
66 DCHECK(!enabled()); | |
67 v8::HandleScope scope(m_isolate); | |
68 v8::Debug::SetDebugEventListener(m_isolate, &V8Debugger::v8DebugEventCallbac
k, v8::External::New(m_isolate, this)); | |
69 m_debuggerContext.Reset(m_isolate, v8::Debug::GetDebugContext(m_isolate)); | |
70 compileDebuggerScript(); | |
71 } | |
72 | |
73 void V8Debugger::disable() | |
74 { | |
75 if (--m_enableCount) | |
76 return; | |
77 DCHECK(enabled()); | |
78 clearBreakpoints(); | |
79 m_debuggerScript.Reset(); | |
80 m_debuggerContext.Reset(); | |
81 allAsyncTasksCanceled(); | |
82 v8::Debug::SetDebugEventListener(m_isolate, nullptr); | |
83 } | |
84 | |
85 bool V8Debugger::enabled() const | |
86 { | |
87 return !m_debuggerScript.IsEmpty(); | |
88 } | |
89 | |
90 // static | |
91 int V8Debugger::contextId(v8::Local<v8::Context> context) | |
92 { | |
93 v8::Local<v8::Value> data = context->GetEmbedderData(static_cast<int>(v8::Co
ntext::kDebugIdIndex)); | |
94 if (data.IsEmpty() || !data->IsString()) | |
95 return 0; | |
96 String16 dataString = toProtocolString(data.As<v8::String>()); | |
97 if (dataString.isEmpty()) | |
98 return 0; | |
99 size_t commaPos = dataString.find(","); | |
100 if (commaPos == String16::kNotFound) | |
101 return 0; | |
102 size_t commaPos2 = dataString.find(",", commaPos + 1); | |
103 if (commaPos2 == String16::kNotFound) | |
104 return 0; | |
105 return dataString.substring(commaPos + 1, commaPos2 - commaPos - 1).toIntege
r(); | |
106 } | |
107 | |
108 // static | |
109 int V8Debugger::getGroupId(v8::Local<v8::Context> context) | |
110 { | |
111 v8::Local<v8::Value> data = context->GetEmbedderData(static_cast<int>(v8::Co
ntext::kDebugIdIndex)); | |
112 if (data.IsEmpty() || !data->IsString()) | |
113 return 0; | |
114 String16 dataString = toProtocolString(data.As<v8::String>()); | |
115 if (dataString.isEmpty()) | |
116 return 0; | |
117 size_t commaPos = dataString.find(","); | |
118 if (commaPos == String16::kNotFound) | |
119 return 0; | |
120 return dataString.substring(0, commaPos).toInteger(); | |
121 } | |
122 | |
123 void V8Debugger::getCompiledScripts(int contextGroupId, std::vector<std::unique_
ptr<V8DebuggerScript>>& result) | |
124 { | |
125 v8::HandleScope scope(m_isolate); | |
126 v8::MicrotasksScope microtasks(m_isolate, v8::MicrotasksScope::kDoNotRunMicr
otasks); | |
127 v8::Local<v8::Object> debuggerScript = m_debuggerScript.Get(m_isolate); | |
128 DCHECK(!debuggerScript->IsUndefined()); | |
129 v8::Local<v8::Function> getScriptsFunction = v8::Local<v8::Function>::Cast(d
ebuggerScript->Get(toV8StringInternalized(m_isolate, "getScripts"))); | |
130 v8::Local<v8::Value> argv[] = { v8::Integer::New(m_isolate, contextGroupId)
}; | |
131 v8::Local<v8::Value> value; | |
132 if (!getScriptsFunction->Call(debuggerContext(), debuggerScript, V8_INSPECTO
R_ARRAY_LENGTH(argv), argv).ToLocal(&value)) | |
133 return; | |
134 DCHECK(value->IsArray()); | |
135 v8::Local<v8::Array> scriptsArray = v8::Local<v8::Array>::Cast(value); | |
136 result.reserve(scriptsArray->Length()); | |
137 for (unsigned i = 0; i < scriptsArray->Length(); ++i) { | |
138 v8::Local<v8::Object> scriptObject = v8::Local<v8::Object>::Cast(scripts
Array->Get(v8::Integer::New(m_isolate, i))); | |
139 result.push_back(wrapUnique(new V8DebuggerScript(m_isolate, scriptObject
, inLiveEditScope))); | |
140 } | |
141 } | |
142 | |
143 String16 V8Debugger::setBreakpoint(const String16& sourceID, const ScriptBreakpo
int& scriptBreakpoint, int* actualLineNumber, int* actualColumnNumber) | |
144 { | |
145 v8::HandleScope scope(m_isolate); | |
146 v8::Context::Scope contextScope(debuggerContext()); | |
147 | |
148 v8::Local<v8::Object> info = v8::Object::New(m_isolate); | |
149 info->Set(toV8StringInternalized(m_isolate, "sourceID"), toV8String(m_isolat
e, sourceID)); | |
150 info->Set(toV8StringInternalized(m_isolate, "lineNumber"), v8::Integer::New(
m_isolate, scriptBreakpoint.lineNumber)); | |
151 info->Set(toV8StringInternalized(m_isolate, "columnNumber"), v8::Integer::Ne
w(m_isolate, scriptBreakpoint.columnNumber)); | |
152 info->Set(toV8StringInternalized(m_isolate, "condition"), toV8String(m_isola
te, scriptBreakpoint.condition)); | |
153 | |
154 v8::Local<v8::Function> setBreakpointFunction = v8::Local<v8::Function>::Cas
t(m_debuggerScript.Get(m_isolate)->Get(toV8StringInternalized(m_isolate, "setBre
akpoint"))); | |
155 v8::Local<v8::Value> breakpointId = v8::Debug::Call(debuggerContext(), setBr
eakpointFunction, info).ToLocalChecked(); | |
156 if (!breakpointId->IsString()) | |
157 return ""; | |
158 *actualLineNumber = info->Get(toV8StringInternalized(m_isolate, "lineNumber"
))->Int32Value(); | |
159 *actualColumnNumber = info->Get(toV8StringInternalized(m_isolate, "columnNum
ber"))->Int32Value(); | |
160 return toProtocolString(breakpointId.As<v8::String>()); | |
161 } | |
162 | |
163 void V8Debugger::removeBreakpoint(const String16& breakpointId) | |
164 { | |
165 v8::HandleScope scope(m_isolate); | |
166 v8::Context::Scope contextScope(debuggerContext()); | |
167 | |
168 v8::Local<v8::Object> info = v8::Object::New(m_isolate); | |
169 info->Set(toV8StringInternalized(m_isolate, "breakpointId"), toV8String(m_is
olate, breakpointId)); | |
170 | |
171 v8::Local<v8::Function> removeBreakpointFunction = v8::Local<v8::Function>::
Cast(m_debuggerScript.Get(m_isolate)->Get(toV8StringInternalized(m_isolate, "rem
oveBreakpoint"))); | |
172 v8::Debug::Call(debuggerContext(), removeBreakpointFunction, info).ToLocalCh
ecked(); | |
173 } | |
174 | |
175 void V8Debugger::clearBreakpoints() | |
176 { | |
177 v8::HandleScope scope(m_isolate); | |
178 v8::Context::Scope contextScope(debuggerContext()); | |
179 | |
180 v8::Local<v8::Function> clearBreakpoints = v8::Local<v8::Function>::Cast(m_d
ebuggerScript.Get(m_isolate)->Get(toV8StringInternalized(m_isolate, "clearBreakp
oints"))); | |
181 v8::Debug::Call(debuggerContext(), clearBreakpoints).ToLocalChecked(); | |
182 } | |
183 | |
184 void V8Debugger::setBreakpointsActivated(bool activated) | |
185 { | |
186 if (!enabled()) { | |
187 NOTREACHED(); | |
188 return; | |
189 } | |
190 v8::HandleScope scope(m_isolate); | |
191 v8::Context::Scope contextScope(debuggerContext()); | |
192 | |
193 v8::Local<v8::Object> info = v8::Object::New(m_isolate); | |
194 info->Set(toV8StringInternalized(m_isolate, "enabled"), v8::Boolean::New(m_i
solate, activated)); | |
195 v8::Local<v8::Function> setBreakpointsActivated = v8::Local<v8::Function>::C
ast(m_debuggerScript.Get(m_isolate)->Get(toV8StringInternalized(m_isolate, "setB
reakpointsActivated"))); | |
196 v8::Debug::Call(debuggerContext(), setBreakpointsActivated, info).ToLocalChe
cked(); | |
197 | |
198 m_breakpointsActivated = activated; | |
199 } | |
200 | |
201 V8Debugger::PauseOnExceptionsState V8Debugger::getPauseOnExceptionsState() | |
202 { | |
203 DCHECK(enabled()); | |
204 v8::HandleScope scope(m_isolate); | |
205 v8::Context::Scope contextScope(debuggerContext()); | |
206 | |
207 v8::Local<v8::Value> argv[] = { v8::Undefined(m_isolate) }; | |
208 v8::Local<v8::Value> result = callDebuggerMethod("pauseOnExceptionsState", 0
, argv).ToLocalChecked(); | |
209 return static_cast<V8Debugger::PauseOnExceptionsState>(result->Int32Value())
; | |
210 } | |
211 | |
212 void V8Debugger::setPauseOnExceptionsState(PauseOnExceptionsState pauseOnExcepti
onsState) | |
213 { | |
214 DCHECK(enabled()); | |
215 v8::HandleScope scope(m_isolate); | |
216 v8::Context::Scope contextScope(debuggerContext()); | |
217 | |
218 v8::Local<v8::Value> argv[] = { v8::Int32::New(m_isolate, pauseOnExceptionsS
tate) }; | |
219 callDebuggerMethod("setPauseOnExceptionsState", 1, argv); | |
220 } | |
221 | |
222 void V8Debugger::setPauseOnNextStatement(bool pause) | |
223 { | |
224 if (m_runningNestedMessageLoop) | |
225 return; | |
226 if (pause) | |
227 v8::Debug::DebugBreak(m_isolate); | |
228 else | |
229 v8::Debug::CancelDebugBreak(m_isolate); | |
230 } | |
231 | |
232 bool V8Debugger::canBreakProgram() | |
233 { | |
234 if (!m_breakpointsActivated) | |
235 return false; | |
236 return m_isolate->InContext(); | |
237 } | |
238 | |
239 void V8Debugger::breakProgram() | |
240 { | |
241 if (isPaused()) { | |
242 DCHECK(!m_runningNestedMessageLoop); | |
243 v8::Local<v8::Value> exception; | |
244 v8::Local<v8::Array> hitBreakpoints; | |
245 handleProgramBreak(m_pausedContext, m_executionState, exception, hitBrea
kpoints); | |
246 return; | |
247 } | |
248 | |
249 if (!canBreakProgram()) | |
250 return; | |
251 | |
252 v8::HandleScope scope(m_isolate); | |
253 v8::Local<v8::Function> breakFunction; | |
254 if (!V8_FUNCTION_NEW_REMOVE_PROTOTYPE(m_isolate->GetCurrentContext(), &V8Deb
ugger::breakProgramCallback, v8::External::New(m_isolate, this), 0).ToLocal(&bre
akFunction)) | |
255 return; | |
256 v8::Debug::Call(debuggerContext(), breakFunction).ToLocalChecked(); | |
257 } | |
258 | |
259 void V8Debugger::continueProgram() | |
260 { | |
261 if (isPaused()) | |
262 m_inspector->client()->quitMessageLoopOnPause(); | |
263 m_pausedContext.Clear(); | |
264 m_executionState.Clear(); | |
265 } | |
266 | |
267 void V8Debugger::stepIntoStatement() | |
268 { | |
269 DCHECK(isPaused()); | |
270 DCHECK(!m_executionState.IsEmpty()); | |
271 v8::HandleScope handleScope(m_isolate); | |
272 v8::Local<v8::Value> argv[] = { m_executionState }; | |
273 callDebuggerMethod(stepIntoV8MethodName, 1, argv); | |
274 continueProgram(); | |
275 } | |
276 | |
277 void V8Debugger::stepOverStatement() | |
278 { | |
279 DCHECK(isPaused()); | |
280 DCHECK(!m_executionState.IsEmpty()); | |
281 v8::HandleScope handleScope(m_isolate); | |
282 v8::Local<v8::Value> argv[] = { m_executionState }; | |
283 callDebuggerMethod("stepOverStatement", 1, argv); | |
284 continueProgram(); | |
285 } | |
286 | |
287 void V8Debugger::stepOutOfFunction() | |
288 { | |
289 DCHECK(isPaused()); | |
290 DCHECK(!m_executionState.IsEmpty()); | |
291 v8::HandleScope handleScope(m_isolate); | |
292 v8::Local<v8::Value> argv[] = { m_executionState }; | |
293 callDebuggerMethod(stepOutV8MethodName, 1, argv); | |
294 continueProgram(); | |
295 } | |
296 | |
297 void V8Debugger::clearStepping() | |
298 { | |
299 DCHECK(enabled()); | |
300 v8::HandleScope scope(m_isolate); | |
301 v8::Context::Scope contextScope(debuggerContext()); | |
302 | |
303 v8::Local<v8::Value> argv[] = { v8::Undefined(m_isolate) }; | |
304 callDebuggerMethod("clearStepping", 0, argv); | |
305 } | |
306 | |
307 bool V8Debugger::setScriptSource(const String16& sourceID, v8::Local<v8::String>
newSource, bool dryRun, ErrorString* error, Maybe<protocol::Runtime::ExceptionD
etails>* exceptionDetails, JavaScriptCallFrames* newCallFrames, Maybe<bool>* sta
ckChanged) | |
308 { | |
309 class EnableLiveEditScope { | |
310 public: | |
311 explicit EnableLiveEditScope(v8::Isolate* isolate) : m_isolate(isolate) | |
312 { | |
313 v8::Debug::SetLiveEditEnabled(m_isolate, true); | |
314 inLiveEditScope = true; | |
315 } | |
316 ~EnableLiveEditScope() | |
317 { | |
318 v8::Debug::SetLiveEditEnabled(m_isolate, false); | |
319 inLiveEditScope = false; | |
320 } | |
321 private: | |
322 v8::Isolate* m_isolate; | |
323 }; | |
324 | |
325 DCHECK(enabled()); | |
326 v8::HandleScope scope(m_isolate); | |
327 | |
328 std::unique_ptr<v8::Context::Scope> contextScope; | |
329 if (!isPaused()) | |
330 contextScope = wrapUnique(new v8::Context::Scope(debuggerContext())); | |
331 | |
332 v8::Local<v8::Value> argv[] = { toV8String(m_isolate, sourceID), newSource,
v8Boolean(dryRun, m_isolate) }; | |
333 | |
334 v8::Local<v8::Value> v8result; | |
335 { | |
336 EnableLiveEditScope enableLiveEditScope(m_isolate); | |
337 v8::TryCatch tryCatch(m_isolate); | |
338 tryCatch.SetVerbose(false); | |
339 v8::MaybeLocal<v8::Value> maybeResult = callDebuggerMethod("liveEditScri
ptSource", 3, argv); | |
340 if (tryCatch.HasCaught()) { | |
341 v8::Local<v8::Message> message = tryCatch.Message(); | |
342 if (!message.IsEmpty()) | |
343 *error = toProtocolStringWithTypeCheck(message->Get()); | |
344 else | |
345 *error = "Unknown error."; | |
346 return false; | |
347 } | |
348 v8result = maybeResult.ToLocalChecked(); | |
349 } | |
350 DCHECK(!v8result.IsEmpty()); | |
351 v8::Local<v8::Object> resultTuple = v8result->ToObject(m_isolate); | |
352 int code = static_cast<int>(resultTuple->Get(0)->ToInteger(m_isolate)->Value
()); | |
353 switch (code) { | |
354 case 0: | |
355 { | |
356 *stackChanged = resultTuple->Get(1)->BooleanValue(); | |
357 // Call stack may have changed after if the edited function was on t
he stack. | |
358 if (!dryRun && isPaused()) { | |
359 JavaScriptCallFrames frames = currentCallFrames(); | |
360 newCallFrames->swap(frames); | |
361 } | |
362 return true; | |
363 } | |
364 // Compile error. | |
365 case 1: | |
366 { | |
367 *exceptionDetails = protocol::Runtime::ExceptionDetails::create() | |
368 .setExceptionId(m_inspector->nextExceptionId()) | |
369 .setText(toProtocolStringWithTypeCheck(resultTuple->Get(2))) | |
370 .setLineNumber(resultTuple->Get(3)->ToInteger(m_isolate)->Value(
) - 1) | |
371 .setColumnNumber(resultTuple->Get(4)->ToInteger(m_isolate)->Valu
e() - 1).build(); | |
372 return false; | |
373 } | |
374 } | |
375 *error = "Unknown error."; | |
376 return false; | |
377 } | |
378 | |
379 JavaScriptCallFrames V8Debugger::currentCallFrames(int limit) | |
380 { | |
381 if (!m_isolate->InContext()) | |
382 return JavaScriptCallFrames(); | |
383 v8::Local<v8::Value> currentCallFramesV8; | |
384 if (m_executionState.IsEmpty()) { | |
385 v8::Local<v8::Function> currentCallFramesFunction = v8::Local<v8::Functi
on>::Cast(m_debuggerScript.Get(m_isolate)->Get(toV8StringInternalized(m_isolate,
"currentCallFrames"))); | |
386 currentCallFramesV8 = v8::Debug::Call(debuggerContext(), currentCallFram
esFunction, v8::Integer::New(m_isolate, limit)).ToLocalChecked(); | |
387 } else { | |
388 v8::Local<v8::Value> argv[] = { m_executionState, v8::Integer::New(m_iso
late, limit) }; | |
389 currentCallFramesV8 = callDebuggerMethod("currentCallFrames", V8_INSPECT
OR_ARRAY_LENGTH(argv), argv).ToLocalChecked(); | |
390 } | |
391 DCHECK(!currentCallFramesV8.IsEmpty()); | |
392 if (!currentCallFramesV8->IsArray()) | |
393 return JavaScriptCallFrames(); | |
394 v8::Local<v8::Array> callFramesArray = currentCallFramesV8.As<v8::Array>(); | |
395 JavaScriptCallFrames callFrames; | |
396 for (size_t i = 0; i < callFramesArray->Length(); ++i) { | |
397 v8::Local<v8::Value> callFrameValue; | |
398 if (!callFramesArray->Get(debuggerContext(), i).ToLocal(&callFrameValue)
) | |
399 return JavaScriptCallFrames(); | |
400 if (!callFrameValue->IsObject()) | |
401 return JavaScriptCallFrames(); | |
402 v8::Local<v8::Object> callFrameObject = callFrameValue.As<v8::Object>(); | |
403 callFrames.push_back(JavaScriptCallFrame::create(debuggerContext(), v8::
Local<v8::Object>::Cast(callFrameObject))); | |
404 } | |
405 return callFrames; | |
406 } | |
407 | |
408 static V8Debugger* toV8Debugger(v8::Local<v8::Value> data) | |
409 { | |
410 void* p = v8::Local<v8::External>::Cast(data)->Value(); | |
411 return static_cast<V8Debugger*>(p); | |
412 } | |
413 | |
414 void V8Debugger::breakProgramCallback(const v8::FunctionCallbackInfo<v8::Value>&
info) | |
415 { | |
416 DCHECK_EQ(info.Length(), 2); | |
417 V8Debugger* thisPtr = toV8Debugger(info.Data()); | |
418 if (!thisPtr->enabled()) | |
419 return; | |
420 v8::Local<v8::Context> pausedContext = thisPtr->m_isolate->GetCurrentContext
(); | |
421 v8::Local<v8::Value> exception; | |
422 v8::Local<v8::Array> hitBreakpoints; | |
423 thisPtr->handleProgramBreak(pausedContext, v8::Local<v8::Object>::Cast(info[
0]), exception, hitBreakpoints); | |
424 } | |
425 | |
426 void V8Debugger::handleProgramBreak(v8::Local<v8::Context> pausedContext, v8::Lo
cal<v8::Object> executionState, v8::Local<v8::Value> exception, v8::Local<v8::Ar
ray> hitBreakpointNumbers, bool isPromiseRejection) | |
427 { | |
428 // Don't allow nested breaks. | |
429 if (m_runningNestedMessageLoop) | |
430 return; | |
431 | |
432 V8DebuggerAgentImpl* agent = m_inspector->enabledDebuggerAgentForGroup(getGr
oupId(pausedContext)); | |
433 if (!agent) | |
434 return; | |
435 | |
436 std::vector<String16> breakpointIds; | |
437 if (!hitBreakpointNumbers.IsEmpty()) { | |
438 breakpointIds.reserve(hitBreakpointNumbers->Length()); | |
439 for (size_t i = 0; i < hitBreakpointNumbers->Length(); i++) { | |
440 v8::Local<v8::Value> hitBreakpointNumber = hitBreakpointNumbers->Get
(i); | |
441 DCHECK(!hitBreakpointNumber.IsEmpty() && hitBreakpointNumber->IsInt3
2()); | |
442 breakpointIds.push_back(String16::fromInteger(hitBreakpointNumber->I
nt32Value())); | |
443 } | |
444 } | |
445 | |
446 m_pausedContext = pausedContext; | |
447 m_executionState = executionState; | |
448 V8DebuggerAgentImpl::SkipPauseRequest result = agent->didPause(pausedContext
, exception, breakpointIds, isPromiseRejection); | |
449 if (result == V8DebuggerAgentImpl::RequestNoSkip) { | |
450 m_runningNestedMessageLoop = true; | |
451 int groupId = getGroupId(pausedContext); | |
452 DCHECK(groupId); | |
453 m_inspector->client()->runMessageLoopOnPause(groupId); | |
454 // The agent may have been removed in the nested loop. | |
455 agent = m_inspector->enabledDebuggerAgentForGroup(getGroupId(pausedConte
xt)); | |
456 if (agent) | |
457 agent->didContinue(); | |
458 m_runningNestedMessageLoop = false; | |
459 } | |
460 m_pausedContext.Clear(); | |
461 m_executionState.Clear(); | |
462 | |
463 if (result == V8DebuggerAgentImpl::RequestStepFrame) { | |
464 v8::Local<v8::Value> argv[] = { executionState }; | |
465 callDebuggerMethod("stepFrameStatement", 1, argv); | |
466 } else if (result == V8DebuggerAgentImpl::RequestStepInto) { | |
467 v8::Local<v8::Value> argv[] = { executionState }; | |
468 callDebuggerMethod(stepIntoV8MethodName, 1, argv); | |
469 } else if (result == V8DebuggerAgentImpl::RequestStepOut) { | |
470 v8::Local<v8::Value> argv[] = { executionState }; | |
471 callDebuggerMethod(stepOutV8MethodName, 1, argv); | |
472 } | |
473 } | |
474 | |
475 void V8Debugger::v8DebugEventCallback(const v8::Debug::EventDetails& eventDetail
s) | |
476 { | |
477 V8Debugger* thisPtr = toV8Debugger(eventDetails.GetCallbackData()); | |
478 thisPtr->handleV8DebugEvent(eventDetails); | |
479 } | |
480 | |
481 v8::Local<v8::Value> V8Debugger::callInternalGetterFunction(v8::Local<v8::Object
> object, const char* functionName) | |
482 { | |
483 v8::MicrotasksScope microtasks(m_isolate, v8::MicrotasksScope::kDoNotRunMicr
otasks); | |
484 v8::Local<v8::Value> getterValue = object->Get(toV8StringInternalized(m_isol
ate, functionName)); | |
485 DCHECK(!getterValue.IsEmpty() && getterValue->IsFunction()); | |
486 return v8::Local<v8::Function>::Cast(getterValue)->Call(m_isolate->GetCurren
tContext(), object, 0, 0).ToLocalChecked(); | |
487 } | |
488 | |
489 void V8Debugger::handleV8DebugEvent(const v8::Debug::EventDetails& eventDetails) | |
490 { | |
491 if (!enabled()) | |
492 return; | |
493 v8::DebugEvent event = eventDetails.GetEvent(); | |
494 if (event != v8::AsyncTaskEvent && event != v8::Break && event != v8::Except
ion && event != v8::AfterCompile && event != v8::BeforeCompile && event != v8::C
ompileError) | |
495 return; | |
496 | |
497 v8::Local<v8::Context> eventContext = eventDetails.GetEventContext(); | |
498 DCHECK(!eventContext.IsEmpty()); | |
499 | |
500 if (event == v8::AsyncTaskEvent) { | |
501 v8::HandleScope scope(m_isolate); | |
502 handleV8AsyncTaskEvent(eventContext, eventDetails.GetExecutionState(), e
ventDetails.GetEventData()); | |
503 return; | |
504 } | |
505 | |
506 V8DebuggerAgentImpl* agent = m_inspector->enabledDebuggerAgentForGroup(getGr
oupId(eventContext)); | |
507 if (agent) { | |
508 v8::HandleScope scope(m_isolate); | |
509 if (m_ignoreScriptParsedEventsCounter == 0 && (event == v8::AfterCompile
|| event == v8::CompileError)) { | |
510 v8::Context::Scope contextScope(debuggerContext()); | |
511 v8::Local<v8::Value> argv[] = { eventDetails.GetEventData() }; | |
512 v8::Local<v8::Value> value = callDebuggerMethod("getAfterCompileScri
pt", 1, argv).ToLocalChecked(); | |
513 if (value->IsNull()) | |
514 return; | |
515 DCHECK(value->IsObject()); | |
516 v8::Local<v8::Object> scriptObject = v8::Local<v8::Object>::Cast(val
ue); | |
517 agent->didParseSource(wrapUnique(new V8DebuggerScript(m_isolate, scr
iptObject, inLiveEditScope)), event == v8::AfterCompile); | |
518 } else if (event == v8::Exception) { | |
519 v8::Local<v8::Object> eventData = eventDetails.GetEventData(); | |
520 v8::Local<v8::Value> exception = callInternalGetterFunction(eventDat
a, "exception"); | |
521 v8::Local<v8::Value> promise = callInternalGetterFunction(eventData,
"promise"); | |
522 bool isPromiseRejection = !promise.IsEmpty() && promise->IsObject(); | |
523 handleProgramBreak(eventContext, eventDetails.GetExecutionState(), e
xception, v8::Local<v8::Array>(), isPromiseRejection); | |
524 } else if (event == v8::Break) { | |
525 v8::Local<v8::Value> argv[] = { eventDetails.GetEventData() }; | |
526 v8::Local<v8::Value> hitBreakpoints = callDebuggerMethod("getBreakpo
intNumbers", 1, argv).ToLocalChecked(); | |
527 DCHECK(hitBreakpoints->IsArray()); | |
528 handleProgramBreak(eventContext, eventDetails.GetExecutionState(), v
8::Local<v8::Value>(), hitBreakpoints.As<v8::Array>()); | |
529 } | |
530 } | |
531 } | |
532 | |
533 void V8Debugger::handleV8AsyncTaskEvent(v8::Local<v8::Context> context, v8::Loca
l<v8::Object> executionState, v8::Local<v8::Object> eventData) | |
534 { | |
535 if (!m_maxAsyncCallStackDepth) | |
536 return; | |
537 | |
538 String16 type = toProtocolStringWithTypeCheck(callInternalGetterFunction(eve
ntData, "type")); | |
539 String16 name = toProtocolStringWithTypeCheck(callInternalGetterFunction(eve
ntData, "name")); | |
540 int id = callInternalGetterFunction(eventData, "id")->ToInteger(m_isolate)->
Value(); | |
541 // The scopes for the ids are defined by the eventData.name namespaces. Ther
e are currently two namespaces: "Object." and "Promise.". | |
542 void* ptr = reinterpret_cast<void*>(id * 4 + (name[0] == 'P' ? 2 : 0) + 1); | |
543 if (type == v8AsyncTaskEventEnqueue) | |
544 asyncTaskScheduled(name, ptr, false); | |
545 else if (type == v8AsyncTaskEventWillHandle) | |
546 asyncTaskStarted(ptr); | |
547 else if (type == v8AsyncTaskEventDidHandle) | |
548 asyncTaskFinished(ptr); | |
549 else | |
550 NOTREACHED(); | |
551 } | |
552 | |
553 V8StackTraceImpl* V8Debugger::currentAsyncCallChain() | |
554 { | |
555 if (!m_currentStacks.size()) | |
556 return nullptr; | |
557 return m_currentStacks.back().get(); | |
558 } | |
559 | |
560 void V8Debugger::compileDebuggerScript() | |
561 { | |
562 if (!m_debuggerScript.IsEmpty()) { | |
563 NOTREACHED(); | |
564 return; | |
565 } | |
566 | |
567 v8::HandleScope scope(m_isolate); | |
568 v8::Context::Scope contextScope(debuggerContext()); | |
569 | |
570 v8::Local<v8::String> scriptValue = v8::String::NewFromUtf8(m_isolate, Debug
gerScript_js, v8::NewStringType::kInternalized, sizeof(DebuggerScript_js)).ToLoc
alChecked(); | |
571 v8::Local<v8::Value> value; | |
572 if (!m_inspector->compileAndRunInternalScript(debuggerContext(), scriptValue
).ToLocal(&value)) { | |
573 NOTREACHED(); | |
574 return; | |
575 } | |
576 DCHECK(value->IsObject()); | |
577 m_debuggerScript.Reset(m_isolate, value.As<v8::Object>()); | |
578 } | |
579 | |
580 v8::Local<v8::Context> V8Debugger::debuggerContext() const | |
581 { | |
582 DCHECK(!m_debuggerContext.IsEmpty()); | |
583 return m_debuggerContext.Get(m_isolate); | |
584 } | |
585 | |
586 v8::MaybeLocal<v8::Value> V8Debugger::functionScopes(v8::Local<v8::Context> cont
ext, v8::Local<v8::Function> function) | |
587 { | |
588 if (!enabled()) { | |
589 NOTREACHED(); | |
590 return v8::Local<v8::Value>::New(m_isolate, v8::Undefined(m_isolate)); | |
591 } | |
592 v8::Local<v8::Value> argv[] = { function }; | |
593 v8::Local<v8::Value> scopesValue; | |
594 if (!callDebuggerMethod("getFunctionScopes", 1, argv).ToLocal(&scopesValue)) | |
595 return v8::MaybeLocal<v8::Value>(); | |
596 v8::Local<v8::Value> copied; | |
597 if (!copyValueFromDebuggerContext(m_isolate, debuggerContext(), context, sco
pesValue).ToLocal(&copied) || !copied->IsArray()) | |
598 return v8::MaybeLocal<v8::Value>(); | |
599 if (!markAsInternal(context, v8::Local<v8::Array>::Cast(copied), V8InternalV
alueType::kScopeList)) | |
600 return v8::MaybeLocal<v8::Value>(); | |
601 if (!markArrayEntriesAsInternal(context, v8::Local<v8::Array>::Cast(copied),
V8InternalValueType::kScope)) | |
602 return v8::MaybeLocal<v8::Value>(); | |
603 return copied; | |
604 } | |
605 | |
606 v8::MaybeLocal<v8::Array> V8Debugger::internalProperties(v8::Local<v8::Context>
context, v8::Local<v8::Value> value) | |
607 { | |
608 v8::Local<v8::Array> properties; | |
609 if (!v8::Debug::GetInternalProperties(m_isolate, value).ToLocal(&properties)
) | |
610 return v8::MaybeLocal<v8::Array>(); | |
611 if (value->IsFunction()) { | |
612 v8::Local<v8::Function> function = value.As<v8::Function>(); | |
613 v8::Local<v8::Value> location = functionLocation(context, function); | |
614 if (location->IsObject()) { | |
615 createDataProperty(context, properties, properties->Length(), toV8St
ringInternalized(m_isolate, "[[FunctionLocation]]")); | |
616 createDataProperty(context, properties, properties->Length(), locati
on); | |
617 } | |
618 if (function->IsGeneratorFunction()) { | |
619 createDataProperty(context, properties, properties->Length(), toV8St
ringInternalized(m_isolate, "[[IsGenerator]]")); | |
620 createDataProperty(context, properties, properties->Length(), v8::Tr
ue(m_isolate)); | |
621 } | |
622 } | |
623 if (!enabled()) | |
624 return properties; | |
625 if (value->IsMap() || value->IsWeakMap() || value->IsSet() || value->IsWeakS
et() || value->IsSetIterator() || value->IsMapIterator()) { | |
626 v8::Local<v8::Value> entries = collectionEntries(context, v8::Local<v8::
Object>::Cast(value)); | |
627 if (entries->IsArray()) { | |
628 createDataProperty(context, properties, properties->Length(), toV8St
ringInternalized(m_isolate, "[[Entries]]")); | |
629 createDataProperty(context, properties, properties->Length(), entrie
s); | |
630 } | |
631 } | |
632 if (value->IsGeneratorObject()) { | |
633 v8::Local<v8::Value> location = generatorObjectLocation(context, v8::Loc
al<v8::Object>::Cast(value)); | |
634 if (location->IsObject()) { | |
635 createDataProperty(context, properties, properties->Length(), toV8St
ringInternalized(m_isolate, "[[GeneratorLocation]]")); | |
636 createDataProperty(context, properties, properties->Length(), locati
on); | |
637 } | |
638 } | |
639 if (value->IsFunction()) { | |
640 v8::Local<v8::Function> function = value.As<v8::Function>(); | |
641 v8::Local<v8::Value> boundFunction = function->GetBoundFunction(); | |
642 v8::Local<v8::Value> scopes; | |
643 if (boundFunction->IsUndefined() && functionScopes(context, function).To
Local(&scopes)) { | |
644 createDataProperty(context, properties, properties->Length(), toV8St
ringInternalized(m_isolate, "[[Scopes]]")); | |
645 createDataProperty(context, properties, properties->Length(), scopes
); | |
646 } | |
647 } | |
648 return properties; | |
649 } | |
650 | |
651 v8::Local<v8::Value> V8Debugger::collectionEntries(v8::Local<v8::Context> contex
t, v8::Local<v8::Object> object) | |
652 { | |
653 if (!enabled()) { | |
654 NOTREACHED(); | |
655 return v8::Undefined(m_isolate); | |
656 } | |
657 v8::Local<v8::Value> argv[] = { object }; | |
658 v8::Local<v8::Value> entriesValue = callDebuggerMethod("getCollectionEntries
", 1, argv).ToLocalChecked(); | |
659 v8::Local<v8::Value> copied; | |
660 if (!copyValueFromDebuggerContext(m_isolate, debuggerContext(), context, ent
riesValue).ToLocal(&copied) || !copied->IsArray()) | |
661 return v8::Undefined(m_isolate); | |
662 if (!markArrayEntriesAsInternal(context, v8::Local<v8::Array>::Cast(copied),
V8InternalValueType::kEntry)) | |
663 return v8::Undefined(m_isolate); | |
664 return copied; | |
665 } | |
666 | |
667 v8::Local<v8::Value> V8Debugger::generatorObjectLocation(v8::Local<v8::Context>
context, v8::Local<v8::Object> object) | |
668 { | |
669 if (!enabled()) { | |
670 NOTREACHED(); | |
671 return v8::Null(m_isolate); | |
672 } | |
673 v8::Local<v8::Value> argv[] = { object }; | |
674 v8::Local<v8::Value> location = callDebuggerMethod("getGeneratorObjectLocati
on", 1, argv).ToLocalChecked(); | |
675 v8::Local<v8::Value> copied; | |
676 if (!copyValueFromDebuggerContext(m_isolate, debuggerContext(), context, loc
ation).ToLocal(&copied) || !copied->IsObject()) | |
677 return v8::Null(m_isolate); | |
678 if (!markAsInternal(context, v8::Local<v8::Object>::Cast(copied), V8Internal
ValueType::kLocation)) | |
679 return v8::Null(m_isolate); | |
680 return copied; | |
681 } | |
682 | |
683 v8::Local<v8::Value> V8Debugger::functionLocation(v8::Local<v8::Context> context
, v8::Local<v8::Function> function) | |
684 { | |
685 int scriptId = function->ScriptId(); | |
686 if (scriptId == v8::UnboundScript::kNoScriptId) | |
687 return v8::Null(m_isolate); | |
688 int lineNumber = function->GetScriptLineNumber(); | |
689 int columnNumber = function->GetScriptColumnNumber(); | |
690 if (lineNumber == v8::Function::kLineOffsetNotFound || columnNumber == v8::F
unction::kLineOffsetNotFound) | |
691 return v8::Null(m_isolate); | |
692 v8::Local<v8::Object> location = v8::Object::New(m_isolate); | |
693 if (!location->SetPrototype(context, v8::Null(m_isolate)).FromMaybe(false)) | |
694 return v8::Null(m_isolate); | |
695 if (!createDataProperty(context, location, toV8StringInternalized(m_isolate,
"scriptId"), toV8String(m_isolate, String16::fromInteger(scriptId))).FromMaybe(
false)) | |
696 return v8::Null(m_isolate); | |
697 if (!createDataProperty(context, location, toV8StringInternalized(m_isolate,
"lineNumber"), v8::Integer::New(m_isolate, lineNumber)).FromMaybe(false)) | |
698 return v8::Null(m_isolate); | |
699 if (!createDataProperty(context, location, toV8StringInternalized(m_isolate,
"columnNumber"), v8::Integer::New(m_isolate, columnNumber)).FromMaybe(false)) | |
700 return v8::Null(m_isolate); | |
701 if (!markAsInternal(context, location, V8InternalValueType::kLocation)) | |
702 return v8::Null(m_isolate); | |
703 return location; | |
704 } | |
705 | |
706 bool V8Debugger::isPaused() | |
707 { | |
708 return !m_pausedContext.IsEmpty(); | |
709 } | |
710 | |
711 std::unique_ptr<V8StackTraceImpl> V8Debugger::createStackTrace(v8::Local<v8::Sta
ckTrace> stackTrace) | |
712 { | |
713 int contextGroupId = m_isolate->InContext() ? getGroupId(m_isolate->GetCurre
ntContext()) : 0; | |
714 return V8StackTraceImpl::create(this, contextGroupId, stackTrace, V8StackTra
ceImpl::maxCallStackSizeToCapture); | |
715 } | |
716 | |
717 int V8Debugger::markContext(const V8ContextInfo& info) | |
718 { | |
719 DCHECK(info.context->GetIsolate() == m_isolate); | |
720 int contextId = ++m_lastContextId; | |
721 String16 debugData = String16::fromInteger(info.contextGroupId) + "," + Stri
ng16::fromInteger(contextId) + "," + toString16(info.auxData); | |
722 v8::Context::Scope contextScope(info.context); | |
723 info.context->SetEmbedderData(static_cast<int>(v8::Context::kDebugIdIndex),
toV8String(m_isolate, debugData)); | |
724 return contextId; | |
725 } | |
726 | |
727 void V8Debugger::setAsyncCallStackDepth(V8DebuggerAgentImpl* agent, int depth) | |
728 { | |
729 if (depth <= 0) | |
730 m_maxAsyncCallStackDepthMap.erase(agent); | |
731 else | |
732 m_maxAsyncCallStackDepthMap[agent] = depth; | |
733 | |
734 int maxAsyncCallStackDepth = 0; | |
735 for (const auto& pair : m_maxAsyncCallStackDepthMap) { | |
736 if (pair.second > maxAsyncCallStackDepth) | |
737 maxAsyncCallStackDepth = pair.second; | |
738 } | |
739 | |
740 if (m_maxAsyncCallStackDepth == maxAsyncCallStackDepth) | |
741 return; | |
742 m_maxAsyncCallStackDepth = maxAsyncCallStackDepth; | |
743 if (!maxAsyncCallStackDepth) | |
744 allAsyncTasksCanceled(); | |
745 } | |
746 | |
747 void V8Debugger::asyncTaskScheduled(const StringView& taskName, void* task, bool
recurring) | |
748 { | |
749 if (!m_maxAsyncCallStackDepth) | |
750 return; | |
751 asyncTaskScheduled(toString16(taskName), task, recurring); | |
752 } | |
753 | |
754 void V8Debugger::asyncTaskScheduled(const String16& taskName, void* task, bool r
ecurring) | |
755 { | |
756 if (!m_maxAsyncCallStackDepth) | |
757 return; | |
758 v8::HandleScope scope(m_isolate); | |
759 int contextGroupId = m_isolate->InContext() ? getGroupId(m_isolate->GetCurre
ntContext()) : 0; | |
760 std::unique_ptr<V8StackTraceImpl> chain = V8StackTraceImpl::capture(this, co
ntextGroupId, V8StackTraceImpl::maxCallStackSizeToCapture, taskName); | |
761 if (chain) { | |
762 m_asyncTaskStacks[task] = std::move(chain); | |
763 if (recurring) | |
764 m_recurringTasks.insert(task); | |
765 } | |
766 } | |
767 | |
768 void V8Debugger::asyncTaskCanceled(void* task) | |
769 { | |
770 if (!m_maxAsyncCallStackDepth) | |
771 return; | |
772 m_asyncTaskStacks.erase(task); | |
773 m_recurringTasks.erase(task); | |
774 } | |
775 | |
776 void V8Debugger::asyncTaskStarted(void* task) | |
777 { | |
778 if (!m_maxAsyncCallStackDepth) | |
779 return; | |
780 m_currentTasks.push_back(task); | |
781 AsyncTaskToStackTrace::iterator stackIt = m_asyncTaskStacks.find(task); | |
782 // Needs to support following order of events: | |
783 // - asyncTaskScheduled | |
784 // <-- attached here --> | |
785 // - asyncTaskStarted | |
786 // - asyncTaskCanceled <-- canceled before finished | |
787 // <-- async stack requested here --> | |
788 // - asyncTaskFinished | |
789 std::unique_ptr<V8StackTraceImpl> stack; | |
790 if (stackIt != m_asyncTaskStacks.end() && stackIt->second) | |
791 stack = stackIt->second->cloneImpl(); | |
792 m_currentStacks.push_back(std::move(stack)); | |
793 } | |
794 | |
795 void V8Debugger::asyncTaskFinished(void* task) | |
796 { | |
797 if (!m_maxAsyncCallStackDepth) | |
798 return; | |
799 // We could start instrumenting half way and the stack is empty. | |
800 if (!m_currentStacks.size()) | |
801 return; | |
802 | |
803 DCHECK(m_currentTasks.back() == task); | |
804 m_currentTasks.pop_back(); | |
805 | |
806 m_currentStacks.pop_back(); | |
807 if (m_recurringTasks.find(task) == m_recurringTasks.end()) | |
808 m_asyncTaskStacks.erase(task); | |
809 } | |
810 | |
811 void V8Debugger::allAsyncTasksCanceled() | |
812 { | |
813 m_asyncTaskStacks.clear(); | |
814 m_recurringTasks.clear(); | |
815 m_currentStacks.clear(); | |
816 m_currentTasks.clear(); | |
817 } | |
818 | |
819 void V8Debugger::muteScriptParsedEvents() | |
820 { | |
821 ++m_ignoreScriptParsedEventsCounter; | |
822 } | |
823 | |
824 void V8Debugger::unmuteScriptParsedEvents() | |
825 { | |
826 --m_ignoreScriptParsedEventsCounter; | |
827 DCHECK_GE(m_ignoreScriptParsedEventsCounter, 0); | |
828 } | |
829 | |
830 std::unique_ptr<V8StackTraceImpl> V8Debugger::captureStackTrace(bool fullStack) | |
831 { | |
832 if (!m_isolate->InContext()) | |
833 return nullptr; | |
834 | |
835 v8::HandleScope handles(m_isolate); | |
836 int contextGroupId = getGroupId(m_isolate->GetCurrentContext()); | |
837 if (!contextGroupId) | |
838 return nullptr; | |
839 | |
840 size_t stackSize = fullStack ? V8StackTraceImpl::maxCallStackSizeToCapture :
1; | |
841 if (m_inspector->enabledRuntimeAgentForGroup(contextGroupId)) | |
842 stackSize = V8StackTraceImpl::maxCallStackSizeToCapture; | |
843 | |
844 return V8StackTraceImpl::capture(this, contextGroupId, stackSize); | |
845 } | |
846 | |
847 } // namespace v8_inspector | |
OLD | NEW |