Commit 1bca73bc authored by kozyatinskiy's avatar kozyatinskiy Committed by Commit bot

[inspector] avoid cloning of async call chains

- separated V8StackTraceImpl and AsyncStackTrace,
- V8Debugger owns all AsyncStackTrace and cleanup half of them when limit is reached (first created - first cleaned),
- V8StackTraceImpl, AsyncStackTrace and async-task-related tables in V8Debugger have weak reference to other async stack traces.
- async tasks are cleared with related async stacks.

BUG=v8:6189
R=dgozman@chromium.org

Review-Url: https://codereview.chromium.org/2816043006
Cr-Commit-Position: refs/heads/master@{#44670}
parent 71cf4890
...@@ -263,7 +263,7 @@ void V8Console::countCallback(const v8::FunctionCallbackInfo<v8::Value>& info) { ...@@ -263,7 +263,7 @@ void V8Console::countCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
String16 identifier; String16 identifier;
if (title.isEmpty()) { if (title.isEmpty()) {
std::unique_ptr<V8StackTraceImpl> stackTrace = std::unique_ptr<V8StackTraceImpl> stackTrace =
V8StackTraceImpl::capture(m_inspector->debugger(), 0, 1); V8StackTraceImpl::capture(m_inspector->debugger(), helper.groupId(), 1);
if (stackTrace && !stackTrace->isEmpty()) { if (stackTrace && !stackTrace->isEmpty()) {
identifier = toString16(stackTrace->topSourceURL()) + ":" + identifier = toString16(stackTrace->topSourceURL()) + ":" +
String16::fromInteger(stackTrace->topLineNumber()); String16::fromInteger(stackTrace->topLineNumber());
......
...@@ -34,7 +34,6 @@ using protocol::Debugger::BreakpointId; ...@@ -34,7 +34,6 @@ using protocol::Debugger::BreakpointId;
using protocol::Debugger::CallFrame; using protocol::Debugger::CallFrame;
using protocol::Runtime::ExceptionDetails; using protocol::Runtime::ExceptionDetails;
using protocol::Runtime::ScriptId; using protocol::Runtime::ScriptId;
using protocol::Runtime::StackTrace;
using protocol::Runtime::RemoteObject; using protocol::Runtime::RemoteObject;
namespace DebuggerAgentState { namespace DebuggerAgentState {
...@@ -598,7 +597,8 @@ Response V8DebuggerAgentImpl::searchInContent( ...@@ -598,7 +597,8 @@ Response V8DebuggerAgentImpl::searchInContent(
Response V8DebuggerAgentImpl::setScriptSource( Response V8DebuggerAgentImpl::setScriptSource(
const String16& scriptId, const String16& newContent, Maybe<bool> dryRun, const String16& scriptId, const String16& newContent, Maybe<bool> dryRun,
Maybe<protocol::Array<protocol::Debugger::CallFrame>>* newCallFrames, Maybe<protocol::Array<protocol::Debugger::CallFrame>>* newCallFrames,
Maybe<bool>* stackChanged, Maybe<StackTrace>* asyncStackTrace, Maybe<bool>* stackChanged,
Maybe<protocol::Runtime::StackTrace>* asyncStackTrace,
Maybe<protocol::Runtime::ExceptionDetails>* optOutCompileError) { Maybe<protocol::Runtime::ExceptionDetails>* optOutCompileError) {
if (!enabled()) return Response::Error(kDebuggerNotEnabled); if (!enabled()) return Response::Error(kDebuggerNotEnabled);
...@@ -631,7 +631,7 @@ Response V8DebuggerAgentImpl::setScriptSource( ...@@ -631,7 +631,7 @@ Response V8DebuggerAgentImpl::setScriptSource(
Response V8DebuggerAgentImpl::restartFrame( Response V8DebuggerAgentImpl::restartFrame(
const String16& callFrameId, const String16& callFrameId,
std::unique_ptr<Array<CallFrame>>* newCallFrames, std::unique_ptr<Array<CallFrame>>* newCallFrames,
Maybe<StackTrace>* asyncStackTrace) { Maybe<protocol::Runtime::StackTrace>* asyncStackTrace) {
if (!isPaused()) return Response::Error(kDebuggerNotPaused); if (!isPaused()) return Response::Error(kDebuggerNotPaused);
InjectedScript::CallFrameScope scope(m_inspector, m_session->contextGroupId(), InjectedScript::CallFrameScope scope(m_inspector, m_session->contextGroupId(),
callFrameId); callFrameId);
...@@ -1028,9 +1028,14 @@ Response V8DebuggerAgentImpl::currentCallFrames( ...@@ -1028,9 +1028,14 @@ Response V8DebuggerAgentImpl::currentCallFrames(
return Response::OK(); return Response::OK();
} }
std::unique_ptr<StackTrace> V8DebuggerAgentImpl::currentAsyncStackTrace() { std::unique_ptr<protocol::Runtime::StackTrace>
if (!isPaused()) return nullptr; V8DebuggerAgentImpl::currentAsyncStackTrace() {
return V8StackTraceImpl::buildInspectorObjectForTail(m_debugger); std::shared_ptr<AsyncStackTrace> asyncParent =
m_debugger->currentAsyncParent();
if (!asyncParent) return nullptr;
return asyncParent->buildInspectorObject(
m_debugger->currentAsyncCreation().get(),
m_debugger->maxAsyncCallChainDepth() - 1);
} }
bool V8DebuggerAgentImpl::isPaused() const { return m_debugger->isPaused(); } bool V8DebuggerAgentImpl::isPaused() const { return m_debugger->isPaused(); }
......
...@@ -23,7 +23,6 @@ class V8DebuggerScript; ...@@ -23,7 +23,6 @@ class V8DebuggerScript;
class V8InspectorImpl; class V8InspectorImpl;
class V8InspectorSessionImpl; class V8InspectorSessionImpl;
class V8Regex; class V8Regex;
class V8StackTraceImpl;
using protocol::Maybe; using protocol::Maybe;
using protocol::Response; using protocol::Response;
......
...@@ -21,10 +21,7 @@ namespace v8_inspector { ...@@ -21,10 +21,7 @@ namespace v8_inspector {
namespace { namespace {
// Based on DevTools frontend measurement, with asyncCallStackDepth = 4, static const int kMaxAsyncTaskStacks = 1024 * 1024;
// average async call stack tail requires ~1 Kb. Let's reserve ~ 128 Mb
// for async stacks.
static const int kMaxAsyncTaskStacks = 128 * 1024;
inline v8::Local<v8::Boolean> v8Boolean(bool value, v8::Isolate* isolate) { inline v8::Local<v8::Boolean> v8Boolean(bool value, v8::Isolate* isolate) {
return value ? v8::True(isolate) : v8::False(isolate); return value ? v8::True(isolate) : v8::False(isolate);
...@@ -167,7 +164,6 @@ V8Debugger::V8Debugger(v8::Isolate* isolate, V8InspectorImpl* inspector) ...@@ -167,7 +164,6 @@ V8Debugger::V8Debugger(v8::Isolate* isolate, V8InspectorImpl* inspector)
m_runningNestedMessageLoop(false), m_runningNestedMessageLoop(false),
m_ignoreScriptParsedEventsCounter(0), m_ignoreScriptParsedEventsCounter(0),
m_maxAsyncCallStacks(kMaxAsyncTaskStacks), m_maxAsyncCallStacks(kMaxAsyncTaskStacks),
m_lastTaskId(0),
m_maxAsyncCallStackDepth(0), m_maxAsyncCallStackDepth(0),
m_pauseOnExceptionsState(v8::debug::NoBreakOnException), m_pauseOnExceptionsState(v8::debug::NoBreakOnException),
m_wasmTranslation(isolate) {} m_wasmTranslation(isolate) {}
...@@ -662,10 +658,6 @@ void V8Debugger::PromiseEventOccurred(v8::debug::PromiseDebugActionType type, ...@@ -662,10 +658,6 @@ void V8Debugger::PromiseEventOccurred(v8::debug::PromiseDebugActionType type,
case v8::debug::kDebugEnqueuePromiseReject: case v8::debug::kDebugEnqueuePromiseReject:
asyncTaskScheduledForStack("Promise.reject", task, true); asyncTaskScheduledForStack("Promise.reject", task, true);
break; break;
case v8::debug::kDebugPromiseCollected:
asyncTaskCanceledForStack(task);
asyncTaskCanceledForStepping(task);
break;
case v8::debug::kDebugWillHandle: case v8::debug::kDebugWillHandle:
asyncTaskStartedForStack(task); asyncTaskStartedForStack(task);
asyncTaskStartedForStepping(task); asyncTaskStartedForStepping(task);
...@@ -674,17 +666,20 @@ void V8Debugger::PromiseEventOccurred(v8::debug::PromiseDebugActionType type, ...@@ -674,17 +666,20 @@ void V8Debugger::PromiseEventOccurred(v8::debug::PromiseDebugActionType type,
asyncTaskFinishedForStack(task); asyncTaskFinishedForStack(task);
asyncTaskFinishedForStepping(task); asyncTaskFinishedForStepping(task);
break; break;
case v8::debug::kDebugPromiseCollected:
asyncTaskCanceledForStack(task);
asyncTaskCanceledForStepping(task);
break;
} }
} }
V8StackTraceImpl* V8Debugger::currentAsyncCallChain() { std::shared_ptr<AsyncStackTrace> V8Debugger::currentAsyncParent() {
if (!m_currentStacks.size()) return nullptr; return m_currentAsyncParent.empty() ? nullptr : m_currentAsyncParent.back();
return m_currentStacks.back().get();
} }
V8StackTraceImpl* V8Debugger::currentAsyncTaskCreationStack() { std::shared_ptr<AsyncStackTrace> V8Debugger::currentAsyncCreation() {
if (!m_currentCreationStacks.size()) return nullptr; return m_currentAsyncCreation.empty() ? nullptr
return m_currentCreationStacks.back().get(); : m_currentAsyncCreation.back();
} }
void V8Debugger::compileDebuggerScript() { void V8Debugger::compileDebuggerScript() {
...@@ -825,8 +820,8 @@ v8::MaybeLocal<v8::Array> V8Debugger::internalProperties( ...@@ -825,8 +820,8 @@ v8::MaybeLocal<v8::Array> V8Debugger::internalProperties(
} }
std::unique_ptr<V8StackTraceImpl> V8Debugger::createStackTrace( std::unique_ptr<V8StackTraceImpl> V8Debugger::createStackTrace(
v8::Local<v8::StackTrace> stackTrace) { v8::Local<v8::StackTrace> v8StackTrace) {
return V8StackTraceImpl::create(this, currentContextGroupId(), stackTrace, return V8StackTraceImpl::create(this, currentContextGroupId(), v8StackTrace,
V8StackTraceImpl::maxCallStackSizeToCapture); V8StackTraceImpl::maxCallStackSizeToCapture);
} }
...@@ -847,31 +842,18 @@ void V8Debugger::setAsyncCallStackDepth(V8DebuggerAgentImpl* agent, int depth) { ...@@ -847,31 +842,18 @@ void V8Debugger::setAsyncCallStackDepth(V8DebuggerAgentImpl* agent, int depth) {
if (!maxAsyncCallStackDepth) allAsyncTasksCanceled(); if (!maxAsyncCallStackDepth) allAsyncTasksCanceled();
} }
void V8Debugger::registerAsyncTaskIfNeeded(void* task) {
if (m_taskToId.find(task) != m_taskToId.end()) return;
int id = ++m_lastTaskId;
m_taskToId[task] = id;
m_idToTask[id] = task;
if (static_cast<int>(m_idToTask.size()) > m_maxAsyncCallStacks) {
void* taskToRemove = m_idToTask.begin()->second;
asyncTaskCanceledForStack(taskToRemove);
}
}
void V8Debugger::asyncTaskCreatedForStack(void* task, void* parentTask) { void V8Debugger::asyncTaskCreatedForStack(void* task, void* parentTask) {
if (!m_maxAsyncCallStackDepth) return; if (!m_maxAsyncCallStackDepth) return;
if (parentTask) m_parentTask[task] = parentTask; if (parentTask) m_parentTask[task] = parentTask;
v8::HandleScope scope(m_isolate); v8::HandleScope scope(m_isolate);
// We don't need to pass context group id here because we get this callback std::shared_ptr<AsyncStackTrace> asyncCreation =
// from V8 for promise events only. AsyncStackTrace::capture(this, currentContextGroupId(), String16(), 1);
// Passing one as maxStackSize forces no async chain for the new stack and // Passing one as maxStackSize forces no async chain for the new stack.
// allows us to not grow exponentially. if (asyncCreation && !asyncCreation->isEmpty()) {
std::unique_ptr<V8StackTraceImpl> creationStack = m_asyncTaskCreationStacks[task] = asyncCreation;
V8StackTraceImpl::capture(this, 0, 1, String16()); m_allAsyncStacks.push_back(asyncCreation);
if (creationStack && !creationStack->isEmpty()) { ++m_asyncStacksCount;
m_asyncTaskCreationStacks[task] = std::move(creationStack); collectOldAsyncStacksIfNeeded();
registerAsyncTaskIfNeeded(task);
} }
} }
...@@ -900,13 +882,16 @@ void V8Debugger::asyncTaskScheduledForStack(const String16& taskName, ...@@ -900,13 +882,16 @@ void V8Debugger::asyncTaskScheduledForStack(const String16& taskName,
void* task, bool recurring) { void* task, bool recurring) {
if (!m_maxAsyncCallStackDepth) return; if (!m_maxAsyncCallStackDepth) return;
v8::HandleScope scope(m_isolate); v8::HandleScope scope(m_isolate);
std::unique_ptr<V8StackTraceImpl> chain = V8StackTraceImpl::capture( std::shared_ptr<AsyncStackTrace> asyncStack =
this, currentContextGroupId(), AsyncStackTrace::capture(this, currentContextGroupId(), taskName,
V8StackTraceImpl::maxCallStackSizeToCapture, taskName); V8StackTraceImpl::maxCallStackSizeToCapture);
if (chain) { if (asyncStack) {
m_asyncTaskStacks[task] = std::move(chain); m_asyncTaskStacks[task] = asyncStack;
if (recurring) m_recurringTasks.insert(task); if (recurring) m_recurringTasks.insert(task);
registerAsyncTaskIfNeeded(task);
m_allAsyncStacks.push_back(asyncStack);
++m_asyncStacksCount;
collectOldAsyncStacksIfNeeded();
} }
} }
...@@ -916,10 +901,6 @@ void V8Debugger::asyncTaskCanceledForStack(void* task) { ...@@ -916,10 +901,6 @@ void V8Debugger::asyncTaskCanceledForStack(void* task) {
m_recurringTasks.erase(task); m_recurringTasks.erase(task);
m_parentTask.erase(task); m_parentTask.erase(task);
m_asyncTaskCreationStacks.erase(task); m_asyncTaskCreationStacks.erase(task);
auto it = m_taskToId.find(task);
if (it == m_taskToId.end()) return;
m_idToTask.erase(it->second);
m_taskToId.erase(it);
} }
void V8Debugger::asyncTaskStartedForStack(void* task) { void V8Debugger::asyncTaskStartedForStack(void* task) {
...@@ -935,28 +916,28 @@ void V8Debugger::asyncTaskStartedForStack(void* task) { ...@@ -935,28 +916,28 @@ void V8Debugger::asyncTaskStartedForStack(void* task) {
// - asyncTaskCanceled <-- canceled before finished // - asyncTaskCanceled <-- canceled before finished
// <-- async stack requested here --> // <-- async stack requested here -->
// - asyncTaskFinished // - asyncTaskFinished
std::unique_ptr<V8StackTraceImpl> stack; std::shared_ptr<AsyncStackTrace> asyncParent;
if (stackIt != m_asyncTaskStacks.end() && stackIt->second) if (stackIt != m_asyncTaskStacks.end()) asyncParent = stackIt->second.lock();
stack = stackIt->second->cloneImpl();
auto itCreation = m_asyncTaskCreationStacks.find(task); auto itCreation = m_asyncTaskCreationStacks.find(task);
if (stack && itCreation != m_asyncTaskCreationStacks.end()) { if (asyncParent && itCreation != m_asyncTaskCreationStacks.end()) {
m_currentCreationStacks.push_back(itCreation->second->cloneImpl()); m_currentAsyncCreation.push_back(itCreation->second.lock());
} else { } else {
m_currentCreationStacks.push_back(nullptr); m_currentAsyncCreation.push_back(nullptr);
} }
m_currentStacks.push_back(std::move(stack)); m_currentAsyncParent.push_back(asyncParent);
} }
void V8Debugger::asyncTaskFinishedForStack(void* task) { void V8Debugger::asyncTaskFinishedForStack(void* task) {
if (!m_maxAsyncCallStackDepth) return; if (!m_maxAsyncCallStackDepth) return;
// We could start instrumenting half way and the stack is empty. // We could start instrumenting half way and the stack is empty.
if (!m_currentStacks.size()) return; if (!m_currentTasks.size()) return;
DCHECK(m_currentTasks.back() == task); DCHECK(m_currentTasks.back() == task);
m_currentTasks.pop_back(); m_currentTasks.pop_back();
DCHECK(m_currentStacks.size() == m_currentCreationStacks.size()); DCHECK(m_currentAsyncParent.size() == m_currentAsyncCreation.size());
m_currentStacks.pop_back(); m_currentAsyncParent.pop_back();
m_currentCreationStacks.pop_back(); m_currentAsyncCreation.pop_back();
if (m_recurringTasks.find(task) == m_recurringTasks.end()) { if (m_recurringTasks.find(task) == m_recurringTasks.end()) {
asyncTaskCanceledForStack(task); asyncTaskCanceledForStack(task);
} }
...@@ -993,14 +974,14 @@ void V8Debugger::asyncTaskCanceledForStepping(void* task) { ...@@ -993,14 +974,14 @@ void V8Debugger::asyncTaskCanceledForStepping(void* task) {
void V8Debugger::allAsyncTasksCanceled() { void V8Debugger::allAsyncTasksCanceled() {
m_asyncTaskStacks.clear(); m_asyncTaskStacks.clear();
m_recurringTasks.clear(); m_recurringTasks.clear();
m_currentStacks.clear(); m_currentAsyncParent.clear();
m_currentCreationStacks.clear(); m_currentAsyncCreation.clear();
m_currentTasks.clear(); m_currentTasks.clear();
m_parentTask.clear(); m_parentTask.clear();
m_asyncTaskCreationStacks.clear(); m_asyncTaskCreationStacks.clear();
m_idToTask.clear();
m_taskToId.clear(); m_allAsyncStacks.clear();
m_lastTaskId = 0; m_asyncStacksCount = 0;
} }
void V8Debugger::muteScriptParsedEvents() { void V8Debugger::muteScriptParsedEvents() {
...@@ -1020,11 +1001,10 @@ std::unique_ptr<V8StackTraceImpl> V8Debugger::captureStackTrace( ...@@ -1020,11 +1001,10 @@ std::unique_ptr<V8StackTraceImpl> V8Debugger::captureStackTrace(
int contextGroupId = currentContextGroupId(); int contextGroupId = currentContextGroupId();
if (!contextGroupId) return nullptr; if (!contextGroupId) return nullptr;
size_t stackSize = int stackSize = 1;
fullStack ? V8StackTraceImpl::maxCallStackSizeToCapture : 1; if (fullStack || m_inspector->enabledRuntimeAgentForGroup(contextGroupId)) {
if (m_inspector->enabledRuntimeAgentForGroup(contextGroupId))
stackSize = V8StackTraceImpl::maxCallStackSizeToCapture; stackSize = V8StackTraceImpl::maxCallStackSizeToCapture;
}
return V8StackTraceImpl::capture(this, contextGroupId, stackSize); return V8StackTraceImpl::capture(this, contextGroupId, stackSize);
} }
...@@ -1033,4 +1013,30 @@ int V8Debugger::currentContextGroupId() { ...@@ -1033,4 +1013,30 @@ int V8Debugger::currentContextGroupId() {
return m_inspector->contextGroupId(m_isolate->GetCurrentContext()); return m_inspector->contextGroupId(m_isolate->GetCurrentContext());
} }
void V8Debugger::collectOldAsyncStacksIfNeeded() {
if (m_asyncStacksCount <= m_maxAsyncCallStacks) return;
int halfOfLimitRoundedUp =
m_maxAsyncCallStacks / 2 + m_maxAsyncCallStacks % 2;
while (m_asyncStacksCount > halfOfLimitRoundedUp) {
m_allAsyncStacks.pop_front();
--m_asyncStacksCount;
}
removeOldAsyncTasks(m_asyncTaskStacks);
removeOldAsyncTasks(m_asyncTaskCreationStacks);
}
void V8Debugger::removeOldAsyncTasks(AsyncTaskToStackTrace& map) {
AsyncTaskToStackTrace cleanCopy;
for (auto it : map) {
if (!it.second.expired()) cleanCopy.insert(it);
}
map.swap(cleanCopy);
}
void V8Debugger::setMaxAsyncTaskStacksForTest(int limit) {
m_maxAsyncCallStacks = 0;
collectOldAsyncStacksIfNeeded();
m_maxAsyncCallStacks = limit;
}
} // namespace v8_inspector } // namespace v8_inspector
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#ifndef V8_INSPECTOR_V8DEBUGGER_H_ #ifndef V8_INSPECTOR_V8DEBUGGER_H_
#define V8_INSPECTOR_V8DEBUGGER_H_ #define V8_INSPECTOR_V8DEBUGGER_H_
#include <list>
#include <vector> #include <vector>
#include "src/base/macros.h" #include "src/base/macros.h"
...@@ -20,7 +21,9 @@ ...@@ -20,7 +21,9 @@
namespace v8_inspector { namespace v8_inspector {
class AsyncStackTrace;
struct ScriptBreakpoint; struct ScriptBreakpoint;
class V8Debugger;
class V8DebuggerAgentImpl; class V8DebuggerAgentImpl;
class V8InspectorImpl; class V8InspectorImpl;
class V8StackTraceImpl; class V8StackTraceImpl;
...@@ -35,6 +38,7 @@ class V8Debugger : public v8::debug::DebugDelegate { ...@@ -35,6 +38,7 @@ class V8Debugger : public v8::debug::DebugDelegate {
~V8Debugger(); ~V8Debugger();
bool enabled() const; bool enabled() const;
v8::Isolate* isolate() const { return m_isolate; }
String16 setBreakpoint(const ScriptBreakpoint&, int* actualLineNumber, String16 setBreakpoint(const ScriptBreakpoint&, int* actualLineNumber,
int* actualColumnNumber); int* actualColumnNumber);
...@@ -76,9 +80,11 @@ class V8Debugger : public v8::debug::DebugDelegate { ...@@ -76,9 +80,11 @@ class V8Debugger : public v8::debug::DebugDelegate {
v8::Local<v8::Context> pausedContext() { return m_pausedContext; } v8::Local<v8::Context> pausedContext() { return m_pausedContext; }
int maxAsyncCallChainDepth() { return m_maxAsyncCallStackDepth; } int maxAsyncCallChainDepth() { return m_maxAsyncCallStackDepth; }
V8StackTraceImpl* currentAsyncCallChain();
V8StackTraceImpl* currentAsyncTaskCreationStack();
void setAsyncCallStackDepth(V8DebuggerAgentImpl*, int); void setAsyncCallStackDepth(V8DebuggerAgentImpl*, int);
std::shared_ptr<AsyncStackTrace> currentAsyncParent();
std::shared_ptr<AsyncStackTrace> currentAsyncCreation();
std::unique_ptr<V8StackTraceImpl> createStackTrace(v8::Local<v8::StackTrace>); std::unique_ptr<V8StackTraceImpl> createStackTrace(v8::Local<v8::StackTrace>);
std::unique_ptr<V8StackTraceImpl> captureStackTrace(bool fullStack); std::unique_ptr<V8StackTraceImpl> captureStackTrace(bool fullStack);
...@@ -99,7 +105,7 @@ class V8Debugger : public v8::debug::DebugDelegate { ...@@ -99,7 +105,7 @@ class V8Debugger : public v8::debug::DebugDelegate {
WasmTranslation* wasmTranslation() { return &m_wasmTranslation; } WasmTranslation* wasmTranslation() { return &m_wasmTranslation; }
void setMaxAsyncTaskStacksForTest(int limit) { m_maxAsyncCallStacks = limit; } void setMaxAsyncTaskStacksForTest(int limit);
private: private:
void compileDebuggerScript(); void compileDebuggerScript();
...@@ -144,8 +150,6 @@ class V8Debugger : public v8::debug::DebugDelegate { ...@@ -144,8 +150,6 @@ class V8Debugger : public v8::debug::DebugDelegate {
void asyncTaskFinishedForStepping(void* task); void asyncTaskFinishedForStepping(void* task);
void asyncTaskCanceledForStepping(void* task); void asyncTaskCanceledForStepping(void* task);
void registerAsyncTaskIfNeeded(void* task);
// v8::debug::DebugEventListener implementation. // v8::debug::DebugEventListener implementation.
void PromiseEventOccurred(v8::debug::PromiseDebugActionType type, int id, void PromiseEventOccurred(v8::debug::PromiseDebugActionType type, int id,
int parentId, bool createdByUser) override; int parentId, bool createdByUser) override;
...@@ -178,18 +182,24 @@ class V8Debugger : public v8::debug::DebugDelegate { ...@@ -178,18 +182,24 @@ class V8Debugger : public v8::debug::DebugDelegate {
int m_targetContextGroupId = 0; int m_targetContextGroupId = 0;
using AsyncTaskToStackTrace = using AsyncTaskToStackTrace =
protocol::HashMap<void*, std::unique_ptr<V8StackTraceImpl>>; protocol::HashMap<void*, std::weak_ptr<AsyncStackTrace>>;
AsyncTaskToStackTrace m_asyncTaskStacks; AsyncTaskToStackTrace m_asyncTaskStacks;
AsyncTaskToStackTrace m_asyncTaskCreationStacks; AsyncTaskToStackTrace m_asyncTaskCreationStacks;
int m_maxAsyncCallStacks; int m_maxAsyncCallStacks;
std::map<int, void*> m_idToTask;
std::unordered_map<void*, int> m_taskToId;
int m_lastTaskId;
protocol::HashSet<void*> m_recurringTasks; protocol::HashSet<void*> m_recurringTasks;
int m_maxAsyncCallStackDepth; int m_maxAsyncCallStackDepth;
std::vector<void*> m_currentTasks; std::vector<void*> m_currentTasks;
std::vector<std::unique_ptr<V8StackTraceImpl>> m_currentStacks; std::vector<std::shared_ptr<AsyncStackTrace>> m_currentAsyncParent;
std::vector<std::unique_ptr<V8StackTraceImpl>> m_currentCreationStacks; std::vector<std::shared_ptr<AsyncStackTrace>> m_currentAsyncCreation;
void collectOldAsyncStacksIfNeeded();
void removeOldAsyncTasks(AsyncTaskToStackTrace& map);
int m_asyncStacksCount = 0;
// V8Debugger owns all the async stacks, while most of the other references
// are weak, which allows to collect some stacks when there are too many.
std::list<std::shared_ptr<AsyncStackTrace>> m_allAsyncStacks;
protocol::HashMap<V8DebuggerAgentImpl*, int> m_maxAsyncCallStackDepthMap; protocol::HashMap<V8DebuggerAgentImpl*, int> m_maxAsyncCallStackDepthMap;
protocol::HashMap<void*, void*> m_parentTask; protocol::HashMap<void*, void*> m_parentTask;
protocol::HashMap<void*, void*> m_firstNextTask; protocol::HashMap<void*, void*> m_firstNextTask;
......
...@@ -4,12 +4,10 @@ ...@@ -4,12 +4,10 @@
#include "src/inspector/v8-stack-trace-impl.h" #include "src/inspector/v8-stack-trace-impl.h"
#include "src/inspector/string-util.h" #include <algorithm>
#include "src/inspector/v8-debugger-agent-impl.h"
#include "src/inspector/v8-debugger.h"
#include "src/inspector/v8-inspector-impl.h"
#include "include/v8-version.h" #include "src/inspector/v8-debugger.h"
#include "src/inspector/wasm-translation.h"
namespace v8_inspector { namespace v8_inspector {
...@@ -17,77 +15,115 @@ namespace { ...@@ -17,77 +15,115 @@ namespace {
static const v8::StackTrace::StackTraceOptions stackTraceOptions = static const v8::StackTrace::StackTraceOptions stackTraceOptions =
static_cast<v8::StackTrace::StackTraceOptions>( static_cast<v8::StackTrace::StackTraceOptions>(
v8::StackTrace::kLineNumber | v8::StackTrace::kColumnOffset | v8::StackTrace::kDetailed |
v8::StackTrace::kScriptId | v8::StackTrace::kScriptNameOrSourceURL |
v8::StackTrace::kFunctionName |
v8::StackTrace::kExposeFramesAcrossSecurityOrigins); v8::StackTrace::kExposeFramesAcrossSecurityOrigins);
V8StackTraceImpl::Frame toFrame(v8::Local<v8::StackFrame> frame, std::vector<V8StackTraceImpl::Frame> toFramesVector(
WasmTranslation* wasmTranslation) { V8Debugger* debugger, v8::Local<v8::StackTrace> v8StackTrace,
String16 scriptId = String16::fromInteger(frame->GetScriptId()); int maxStackSize) {
String16 sourceName; DCHECK(debugger->isolate()->InContext());
v8::Local<v8::String> sourceNameValue(frame->GetScriptNameOrSourceURL()); int frameCount = std::min(v8StackTrace->GetFrameCount(), maxStackSize);
if (!sourceNameValue.IsEmpty()) std::vector<V8StackTraceImpl::Frame> frames;
sourceName = toProtocolString(sourceNameValue); for (int i = 0; i < frameCount; ++i) {
v8::Local<v8::StackFrame> v8Frame = v8StackTrace->GetFrame(i);
String16 functionName; frames.emplace_back(v8Frame);
v8::Local<v8::String> functionNameValue(frame->GetFunctionName());
if (!functionNameValue.IsEmpty())
functionName = toProtocolString(functionNameValue);
int sourceLineNumber = frame->GetLineNumber() - 1;
int sourceColumn = frame->GetColumn() - 1;
// TODO(clemensh): Figure out a way to do this translation only right before // TODO(clemensh): Figure out a way to do this translation only right before
// sending the stack trace over wire. // sending the stack trace over wire.
if (frame->IsWasm()) { if (v8Frame->IsWasm()) frames.back().translate(debugger->wasmTranslation());
wasmTranslation->TranslateWasmScriptLocationToProtocolLocation(
&scriptId, &sourceLineNumber, &sourceColumn);
} }
return V8StackTraceImpl::Frame(functionName, scriptId, sourceName, return frames;
sourceLineNumber + 1, sourceColumn + 1);
} }
void toFramesVector(v8::Local<v8::StackTrace> stackTrace, void calculateAsyncChain(V8Debugger* debugger, int contextGroupId,
std::vector<V8StackTraceImpl::Frame>& frames, std::shared_ptr<AsyncStackTrace>* asyncParent,
size_t maxStackSize, v8::Isolate* isolate, std::shared_ptr<AsyncStackTrace>* asyncCreation,
V8Debugger* debugger) { int* maxAsyncDepth) {
DCHECK(isolate->InContext()); *asyncParent = debugger->currentAsyncParent();
int frameCount = stackTrace->GetFrameCount(); *asyncCreation = debugger->currentAsyncCreation();
if (frameCount > static_cast<int>(maxStackSize)) if (maxAsyncDepth) *maxAsyncDepth = debugger->maxAsyncCallChainDepth();
frameCount = static_cast<int>(maxStackSize);
WasmTranslation* wasmTranslation = debugger->wasmTranslation(); DCHECK(!*asyncParent || !*asyncCreation ||
for (int i = 0; i < frameCount; i++) { (*asyncParent)->contextGroupId() ==
v8::Local<v8::StackFrame> stackFrame = stackTrace->GetFrame(i); (*asyncCreation)->contextGroupId());
frames.push_back(toFrame(stackFrame, wasmTranslation)); // Do not accidentally append async call chain from another group. This should
// not happen if we have proper instrumentation, but let's double-check to be
// safe.
if (contextGroupId && *asyncParent && (*asyncParent)->contextGroupId() &&
(*asyncParent)->contextGroupId() != contextGroupId) {
asyncParent->reset();
asyncCreation->reset();
if (maxAsyncDepth) *maxAsyncDepth = 0;
return;
}
// Only the top stack in the chain may be empty and doesn't contain creation
// stack, so ensure that second stack is non-empty (it's the top of appended
// chain).
if (*asyncParent && !(*asyncCreation) && !(*asyncParent)->creation().lock() &&
(*asyncParent)->isEmpty()) {
*asyncParent = (*asyncParent)->parent().lock();
}
}
std::unique_ptr<protocol::Runtime::StackTrace> buildInspectorObjectCommon(
const std::vector<V8StackTraceImpl::Frame>& frames,
const std::shared_ptr<AsyncStackTrace>& asyncParent,
const std::shared_ptr<AsyncStackTrace>& asyncCreation, int maxAsyncDepth) {
std::unique_ptr<protocol::Array<protocol::Runtime::CallFrame>>
inspectorFrames = protocol::Array<protocol::Runtime::CallFrame>::create();
for (size_t i = 0; i < frames.size(); i++) {
inspectorFrames->addItem(frames[i].buildInspectorObject());
}
std::unique_ptr<protocol::Runtime::StackTrace> stackTrace =
protocol::Runtime::StackTrace::create()
.setCallFrames(std::move(inspectorFrames))
.build();
if (asyncParent && maxAsyncDepth > 0) {
stackTrace->setParent(asyncParent->buildInspectorObject(asyncCreation.get(),
maxAsyncDepth - 1));
} }
return stackTrace;
} }
} // namespace } // namespace
V8StackTraceImpl::Frame::Frame(const String16& functionName, V8StackTraceImpl::Frame::Frame(v8::Local<v8::StackFrame> v8Frame)
const String16& scriptId, : m_functionName(toProtocolString(v8Frame->GetFunctionName())),
const String16& scriptName, int lineNumber, m_scriptId(String16::fromInteger(v8Frame->GetScriptId())),
int column) m_sourceURL(toProtocolString(v8Frame->GetScriptNameOrSourceURL())),
: m_functionName(functionName), m_lineNumber(v8Frame->GetLineNumber() - 1),
m_scriptId(scriptId), m_columnNumber(v8Frame->GetColumn() - 1) {
m_scriptName(scriptName), DCHECK(m_lineNumber + 1 != v8::Message::kNoLineNumberInfo);
m_lineNumber(lineNumber), DCHECK(m_columnNumber + 1 != v8::Message::kNoColumnInfo);
m_columnNumber(column) {
DCHECK(m_lineNumber != v8::Message::kNoLineNumberInfo);
DCHECK(m_columnNumber != v8::Message::kNoColumnInfo);
} }
// buildInspectorObject() and SourceLocation's toTracedValue() should set the void V8StackTraceImpl::Frame::translate(WasmTranslation* wasmTranslation) {
// same fields. wasmTranslation->TranslateWasmScriptLocationToProtocolLocation(
// If either of them is modified, the other should be also modified. &m_scriptId, &m_lineNumber, &m_columnNumber);
}
const String16& V8StackTraceImpl::Frame::functionName() const {
return m_functionName;
}
const String16& V8StackTraceImpl::Frame::scriptId() const { return m_scriptId; }
const String16& V8StackTraceImpl::Frame::sourceURL() const {
return m_sourceURL;
}
int V8StackTraceImpl::Frame::lineNumber() const { return m_lineNumber; }
int V8StackTraceImpl::Frame::columnNumber() const { return m_columnNumber; }
std::unique_ptr<protocol::Runtime::CallFrame> std::unique_ptr<protocol::Runtime::CallFrame>
V8StackTraceImpl::Frame::buildInspectorObject() const { V8StackTraceImpl::Frame::buildInspectorObject() const {
return protocol::Runtime::CallFrame::create() return protocol::Runtime::CallFrame::create()
.setFunctionName(m_functionName) .setFunctionName(m_functionName)
.setScriptId(m_scriptId) .setScriptId(m_scriptId)
.setUrl(m_scriptName) .setUrl(m_sourceURL)
.setLineNumber(m_lineNumber - 1) .setLineNumber(m_lineNumber)
.setColumnNumber(m_columnNumber - 1) .setColumnNumber(m_columnNumber)
.build(); .build();
} }
...@@ -95,196 +131,96 @@ V8StackTraceImpl::Frame::buildInspectorObject() const { ...@@ -95,196 +131,96 @@ V8StackTraceImpl::Frame::buildInspectorObject() const {
void V8StackTraceImpl::setCaptureStackTraceForUncaughtExceptions( void V8StackTraceImpl::setCaptureStackTraceForUncaughtExceptions(
v8::Isolate* isolate, bool capture) { v8::Isolate* isolate, bool capture) {
isolate->SetCaptureStackTraceForUncaughtExceptions( isolate->SetCaptureStackTraceForUncaughtExceptions(
capture, V8StackTraceImpl::maxCallStackSizeToCapture, stackTraceOptions); capture, V8StackTraceImpl::maxCallStackSizeToCapture);
} }
// static // static
std::unique_ptr<V8StackTraceImpl> V8StackTraceImpl::create( std::unique_ptr<V8StackTraceImpl> V8StackTraceImpl::create(
V8Debugger* debugger, int contextGroupId, V8Debugger* debugger, int contextGroupId,
v8::Local<v8::StackTrace> stackTrace, size_t maxStackSize, v8::Local<v8::StackTrace> v8StackTrace, int maxStackSize) {
const String16& description) {
DCHECK(debugger); DCHECK(debugger);
v8::Isolate* isolate = debugger->inspector()->isolate();
v8::HandleScope scope(isolate);
std::vector<V8StackTraceImpl::Frame> frames;
if (!stackTrace.IsEmpty()) {
toFramesVector(stackTrace, frames, maxStackSize, isolate, debugger);
}
int maxAsyncCallChainDepth = 1;
V8StackTraceImpl* asyncCallChain = nullptr;
V8StackTraceImpl* creationStack = nullptr;
if (maxStackSize > 1) {
asyncCallChain = debugger->currentAsyncCallChain();
creationStack = debugger->currentAsyncTaskCreationStack();
maxAsyncCallChainDepth = debugger->maxAsyncCallChainDepth();
}
// Do not accidentally append async call chain from another group. This should v8::Isolate* isolate = debugger->isolate();
// not happen if we have proper instrumentation, but let's double-check to be v8::HandleScope scope(isolate);
// safe.
if (contextGroupId && asyncCallChain && asyncCallChain->m_contextGroupId &&
asyncCallChain->m_contextGroupId != contextGroupId) {
asyncCallChain = nullptr;
creationStack = nullptr;
maxAsyncCallChainDepth = 1;
}
// Only the top stack in the chain may be empty and doesn't contain creation
// stack , so ensure that second stack is non-empty (it's the top of appended
// chain).
if (asyncCallChain && !creationStack && !asyncCallChain->m_creation &&
asyncCallChain->isEmpty()) {
asyncCallChain = asyncCallChain->m_parent.get();
}
if (frames.empty() && !creationStack && !asyncCallChain) return nullptr;
// When async call chain is empty but doesn't contain useful schedule stack
// and parent async call chain contains creationg stack but doesn't
// synchronous we can merge them together.
// e.g. Promise ThenableJob.
if (asyncCallChain && frames.empty() &&
asyncCallChain->m_description == description && !creationStack) {
return asyncCallChain->cloneImpl();
}
std::unique_ptr<V8StackTraceImpl> result(new V8StackTraceImpl(
contextGroupId, description, frames,
asyncCallChain ? asyncCallChain->cloneImpl() : nullptr,
creationStack ? creationStack->cloneImpl() : nullptr));
// Crop to not exceed maxAsyncCallChainDepth. std::vector<V8StackTraceImpl::Frame> frames;
V8StackTraceImpl* deepest = result.get(); if (!v8StackTrace.IsEmpty() && v8StackTrace->GetFrameCount()) {
while (deepest && maxAsyncCallChainDepth) { frames = toFramesVector(debugger, v8StackTrace, maxStackSize);
deepest = deepest->m_parent.get();
maxAsyncCallChainDepth--;
} }
if (deepest) deepest->m_parent.reset();
return result; int maxAsyncDepth = 0;
std::shared_ptr<AsyncStackTrace> asyncParent;
std::shared_ptr<AsyncStackTrace> asyncCreation;
calculateAsyncChain(debugger, contextGroupId, &asyncParent, &asyncCreation,
&maxAsyncDepth);
if (frames.empty() && !asyncCreation && !asyncParent) return nullptr;
return std::unique_ptr<V8StackTraceImpl>(
new V8StackTraceImpl(frames, maxAsyncDepth, asyncParent, asyncCreation));
} }
// static // static
std::unique_ptr<V8StackTraceImpl> V8StackTraceImpl::capture( std::unique_ptr<V8StackTraceImpl> V8StackTraceImpl::capture(
V8Debugger* debugger, int contextGroupId, size_t maxStackSize, V8Debugger* debugger, int contextGroupId, int maxStackSize) {
const String16& description) {
DCHECK(debugger); DCHECK(debugger);
v8::Isolate* isolate = debugger->inspector()->isolate(); v8::Isolate* isolate = debugger->isolate();
v8::HandleScope handleScope(isolate); v8::HandleScope handleScope(isolate);
v8::Local<v8::StackTrace> stackTrace; v8::Local<v8::StackTrace> v8StackTrace;
if (isolate->InContext()) { if (isolate->InContext()) {
stackTrace = v8::StackTrace::CurrentStackTrace( v8StackTrace = v8::StackTrace::CurrentStackTrace(isolate, maxStackSize,
isolate, static_cast<int>(maxStackSize), stackTraceOptions); stackTraceOptions);
} }
return V8StackTraceImpl::create(debugger, contextGroupId, stackTrace, return V8StackTraceImpl::create(debugger, contextGroupId, v8StackTrace,
maxStackSize, description); maxStackSize);
} }
std::unique_ptr<V8StackTraceImpl> V8StackTraceImpl::cloneImpl() { V8StackTraceImpl::V8StackTraceImpl(
std::vector<Frame> framesCopy(m_frames); const std::vector<Frame> frames, int maxAsyncDepth,
std::unique_ptr<V8StackTraceImpl> copy( std::shared_ptr<AsyncStackTrace> asyncParent,
new V8StackTraceImpl(m_contextGroupId, m_description, framesCopy, std::shared_ptr<AsyncStackTrace> asyncCreation)
m_parent ? m_parent->cloneImpl() : nullptr, : m_frames(frames),
m_creation ? m_creation->cloneImpl() : nullptr)); m_maxAsyncDepth(maxAsyncDepth),
return copy; m_asyncParent(asyncParent),
} m_asyncCreation(asyncCreation) {}
std::unique_ptr<V8StackTrace> V8StackTraceImpl::clone() { V8StackTraceImpl::~V8StackTraceImpl() {}
std::vector<Frame> frames;
for (size_t i = 0; i < m_frames.size(); i++) {
frames.push_back(m_frames.at(i));
}
return std::unique_ptr<V8StackTraceImpl>(new V8StackTraceImpl(
m_contextGroupId, m_description, frames, nullptr, nullptr));
}
V8StackTraceImpl::V8StackTraceImpl(int contextGroupId, std::unique_ptr<V8StackTrace> V8StackTraceImpl::clone() {
const String16& description, return std::unique_ptr<V8StackTrace>(
std::vector<Frame>& frames, new V8StackTraceImpl(m_frames, 0, std::shared_ptr<AsyncStackTrace>(),
std::unique_ptr<V8StackTraceImpl> parent, std::shared_ptr<AsyncStackTrace>()));
std::unique_ptr<V8StackTraceImpl> creation)
: m_contextGroupId(contextGroupId),
m_description(description),
m_parent(std::move(parent)),
m_creation(std::move(creation)) {
m_frames.swap(frames);
} }
V8StackTraceImpl::~V8StackTraceImpl() {} bool V8StackTraceImpl::isEmpty() const { return m_frames.empty(); }
StringView V8StackTraceImpl::topSourceURL() const { StringView V8StackTraceImpl::topSourceURL() const {
DCHECK(m_frames.size()); return toStringView(m_frames[0].sourceURL());
return toStringView(m_frames[0].m_scriptName);
} }
int V8StackTraceImpl::topLineNumber() const { int V8StackTraceImpl::topLineNumber() const {
DCHECK(m_frames.size()); return m_frames[0].lineNumber() + 1;
return m_frames[0].m_lineNumber;
} }
int V8StackTraceImpl::topColumnNumber() const { int V8StackTraceImpl::topColumnNumber() const {
DCHECK(m_frames.size()); return m_frames[0].columnNumber() + 1;
return m_frames[0].m_columnNumber;
}
StringView V8StackTraceImpl::topFunctionName() const {
DCHECK(m_frames.size());
return toStringView(m_frames[0].m_functionName);
} }
StringView V8StackTraceImpl::topScriptId() const { StringView V8StackTraceImpl::topScriptId() const {
DCHECK(m_frames.size()); return toStringView(m_frames[0].scriptId());
return toStringView(m_frames[0].m_scriptId);
} }
std::unique_ptr<protocol::Runtime::StackTrace> StringView V8StackTraceImpl::topFunctionName() const {
V8StackTraceImpl::buildInspectorObjectImpl() const { return toStringView(m_frames[0].functionName());
return buildInspectorObjectImpl(nullptr);
}
std::unique_ptr<protocol::Runtime::StackTrace>
V8StackTraceImpl::buildInspectorObjectImpl(V8StackTraceImpl* creation) const {
std::unique_ptr<protocol::Array<protocol::Runtime::CallFrame>> frames =
protocol::Array<protocol::Runtime::CallFrame>::create();
for (size_t i = 0; i < m_frames.size(); i++)
frames->addItem(m_frames.at(i).buildInspectorObject());
std::unique_ptr<protocol::Runtime::StackTrace> stackTrace =
protocol::Runtime::StackTrace::create()
.setCallFrames(std::move(frames))
.build();
if (!m_description.isEmpty()) stackTrace->setDescription(m_description);
if (m_parent) {
stackTrace->setParent(m_parent->buildInspectorObjectImpl(m_creation.get()));
}
if (creation && creation->m_frames.size()) {
stackTrace->setPromiseCreationFrame(
creation->m_frames[0].buildInspectorObject());
}
return stackTrace;
} }
// static
std::unique_ptr<protocol::Runtime::StackTrace> std::unique_ptr<protocol::Runtime::StackTrace>
V8StackTraceImpl::buildInspectorObjectForTail(V8Debugger* debugger) { V8StackTraceImpl::buildInspectorObjectImpl() const {
DCHECK(debugger); return buildInspectorObjectCommon(m_frames, m_asyncParent.lock(),
v8::HandleScope handleScope(debugger->inspector()->isolate()); m_asyncCreation.lock(), m_maxAsyncDepth);
// Next call collapses possible empty stack and ensures
// maxAsyncCallChainDepth.
V8StackTraceImpl* asyncChain = debugger->currentAsyncCallChain();
if (!asyncChain) return nullptr;
std::unique_ptr<V8StackTraceImpl> fullChain = V8StackTraceImpl::create(
debugger, asyncChain->m_contextGroupId, v8::Local<v8::StackTrace>(),
V8StackTraceImpl::maxCallStackSizeToCapture);
if (!fullChain || !fullChain->m_parent) return nullptr;
return fullChain->m_parent->buildInspectorObjectImpl(
fullChain->m_creation.get());
} }
std::unique_ptr<protocol::Runtime::API::StackTrace> std::unique_ptr<protocol::Runtime::API::StackTrace>
V8StackTraceImpl::buildInspectorObject() const { V8StackTraceImpl::buildInspectorObject() const {
return buildInspectorObjectImpl(nullptr); return buildInspectorObjectImpl();
} }
std::unique_ptr<StringBuffer> V8StackTraceImpl::toString() const { std::unique_ptr<StringBuffer> V8StackTraceImpl::toString() const {
...@@ -306,4 +242,77 @@ std::unique_ptr<StringBuffer> V8StackTraceImpl::toString() const { ...@@ -306,4 +242,77 @@ std::unique_ptr<StringBuffer> V8StackTraceImpl::toString() const {
return StringBufferImpl::adopt(string); return StringBufferImpl::adopt(string);
} }
// static
std::shared_ptr<AsyncStackTrace> AsyncStackTrace::capture(
V8Debugger* debugger, int contextGroupId, const String16& description,
int maxStackSize) {
DCHECK(debugger);
v8::Isolate* isolate = debugger->isolate();
v8::HandleScope handleScope(isolate);
std::vector<V8StackTraceImpl::Frame> frames;
if (isolate->InContext()) {
v8::Local<v8::StackTrace> v8StackTrace = v8::StackTrace::CurrentStackTrace(
isolate, maxStackSize, stackTraceOptions);
frames = toFramesVector(debugger, v8StackTrace, maxStackSize);
}
std::shared_ptr<AsyncStackTrace> asyncParent;
std::shared_ptr<AsyncStackTrace> asyncCreation;
calculateAsyncChain(debugger, contextGroupId, &asyncParent, &asyncCreation,
nullptr);
if (frames.empty() && !asyncCreation && !asyncParent) return nullptr;
// When async call chain is empty but doesn't contain useful schedule stack
// and parent async call chain contains creationg stack but doesn't
// synchronous we can merge them together.
// e.g. Promise ThenableJob.
if (asyncParent && frames.empty() &&
asyncParent->m_description == description && !asyncCreation) {
return asyncParent;
}
return std::shared_ptr<AsyncStackTrace>(new AsyncStackTrace(
contextGroupId, description, frames, asyncParent, asyncCreation));
}
AsyncStackTrace::AsyncStackTrace(
int contextGroupId, const String16& description,
const std::vector<V8StackTraceImpl::Frame>& frames,
std::shared_ptr<AsyncStackTrace> asyncParent,
std::shared_ptr<AsyncStackTrace> asyncCreation)
: m_contextGroupId(contextGroupId),
m_description(description),
m_frames(frames),
m_asyncParent(asyncParent),
m_asyncCreation(asyncCreation) {}
std::unique_ptr<protocol::Runtime::StackTrace>
AsyncStackTrace::buildInspectorObject(AsyncStackTrace* asyncCreation,
int maxAsyncDepth) const {
std::unique_ptr<protocol::Runtime::StackTrace> stackTrace =
buildInspectorObjectCommon(m_frames, m_asyncParent.lock(),
m_asyncCreation.lock(), maxAsyncDepth);
if (!m_description.isEmpty()) stackTrace->setDescription(m_description);
if (asyncCreation && !asyncCreation->isEmpty()) {
stackTrace->setPromiseCreationFrame(
asyncCreation->m_frames[0].buildInspectorObject());
}
return stackTrace;
}
int AsyncStackTrace::contextGroupId() const { return m_contextGroupId; }
std::weak_ptr<AsyncStackTrace> AsyncStackTrace::parent() const {
return m_asyncParent;
}
std::weak_ptr<AsyncStackTrace> AsyncStackTrace::creation() const {
return m_asyncCreation;
}
bool AsyncStackTrace::isEmpty() const { return m_frames.empty(); }
} // namespace v8_inspector } // namespace v8_inspector
...@@ -5,95 +5,117 @@ ...@@ -5,95 +5,117 @@
#ifndef V8_INSPECTOR_V8STACKTRACEIMPL_H_ #ifndef V8_INSPECTOR_V8STACKTRACEIMPL_H_
#define V8_INSPECTOR_V8STACKTRACEIMPL_H_ #define V8_INSPECTOR_V8STACKTRACEIMPL_H_
#include <memory>
#include <vector> #include <vector>
#include "include/v8-inspector.h"
#include "include/v8.h"
#include "src/base/macros.h" #include "src/base/macros.h"
#include "src/inspector/protocol/Forward.h"
#include "src/inspector/protocol/Runtime.h" #include "src/inspector/protocol/Runtime.h"
#include "src/inspector/string-16.h"
#include "include/v8-inspector.h"
namespace v8_inspector { namespace v8_inspector {
class AsyncStackTrace;
class V8Debugger; class V8Debugger;
class WasmTranslation;
// Note: async stack trace may have empty top stack with non-empty tail to class V8StackTraceImpl : public V8StackTrace {
// indicate that current native-only state had some async story.
// On the other hand, any non-top async stack is guaranteed to be non-empty.
class V8StackTraceImpl final : public V8StackTrace {
public:
static const size_t maxCallStackSizeToCapture = 200;
class Frame {
public: public:
Frame(const String16& functionName, const String16& scriptId,
const String16& scriptName, int lineNumber, int column = 0);
~Frame() = default;
const String16& functionName() const { return m_functionName; }
const String16& scriptId() const { return m_scriptId; }
const String16& sourceURL() const { return m_scriptName; }
int lineNumber() const { return m_lineNumber; }
int columnNumber() const { return m_columnNumber; }
private:
friend class V8StackTraceImpl;
std::unique_ptr<protocol::Runtime::CallFrame> buildInspectorObject() const;
String16 m_functionName;
String16 m_scriptId;
String16 m_scriptName;
int m_lineNumber;
int m_columnNumber;
};
static void setCaptureStackTraceForUncaughtExceptions(v8::Isolate*, static void setCaptureStackTraceForUncaughtExceptions(v8::Isolate*,
bool capture); bool capture);
static std::unique_ptr<V8StackTraceImpl> create( static const int maxCallStackSizeToCapture = 200;
V8Debugger*, int contextGroupId, v8::Local<v8::StackTrace>, static std::unique_ptr<V8StackTraceImpl> create(V8Debugger*,
size_t maxStackSize, const String16& description = String16()); int contextGroupId,
static std::unique_ptr<V8StackTraceImpl> capture( v8::Local<v8::StackTrace>,
V8Debugger*, int contextGroupId, size_t maxStackSize, int maxStackSize);
const String16& description = String16()); static std::unique_ptr<V8StackTraceImpl> capture(V8Debugger*,
int contextGroupId,
// This method drops the async chain. Use cloneImpl() instead. int maxStackSize);
std::unique_ptr<V8StackTrace> clone() override;
std::unique_ptr<V8StackTraceImpl> cloneImpl(); ~V8StackTraceImpl() override;
static std::unique_ptr<protocol::Runtime::StackTrace>
buildInspectorObjectForTail(V8Debugger*);
std::unique_ptr<protocol::Runtime::StackTrace> buildInspectorObjectImpl() std::unique_ptr<protocol::Runtime::StackTrace> buildInspectorObjectImpl()
const; const;
~V8StackTraceImpl() override;
// V8StackTrace implementation. // V8StackTrace implementation.
bool isEmpty() const override { return !m_frames.size(); }; // This method drops the async stack trace.
std::unique_ptr<V8StackTrace> clone() override;
bool isEmpty() const override;
StringView topSourceURL() const override; StringView topSourceURL() const override;
int topLineNumber() const override; int topLineNumber() const override; // 1-based.
int topColumnNumber() const override; int topColumnNumber() const override; // 1-based.
StringView topScriptId() const override; StringView topScriptId() const override;
StringView topFunctionName() const override; StringView topFunctionName() const override;
std::unique_ptr<protocol::Runtime::API::StackTrace> buildInspectorObject() std::unique_ptr<protocol::Runtime::API::StackTrace> buildInspectorObject()
const override; const override;
std::unique_ptr<StringBuffer> toString() const override; std::unique_ptr<StringBuffer> toString() const override;
class Frame {
public:
explicit Frame(v8::Local<v8::StackFrame> frame);
~Frame() = default;
void translate(WasmTranslation* wasmTranslation);
const String16& functionName() const;
const String16& scriptId() const;
const String16& sourceURL() const;
int lineNumber() const; // 0-based.
int columnNumber() const; // 0-based.
std::unique_ptr<protocol::Runtime::CallFrame> buildInspectorObject() const;
private: private:
V8StackTraceImpl(int contextGroupId, const String16& description, String16 m_functionName;
std::vector<Frame>& frames, String16 m_scriptId;
std::unique_ptr<V8StackTraceImpl> parent, String16 m_sourceURL;
std::unique_ptr<V8StackTraceImpl> creation); int m_lineNumber; // 0-based.
int m_columnNumber; // 0-based.
};
std::unique_ptr<protocol::Runtime::StackTrace> buildInspectorObjectImpl( private:
V8StackTraceImpl* creation) const; V8StackTraceImpl(const std::vector<Frame> frames, int maxAsyncDepth,
std::shared_ptr<AsyncStackTrace> asyncParent,
std::shared_ptr<AsyncStackTrace> asyncCreation);
int m_contextGroupId;
String16 m_description;
std::vector<Frame> m_frames; std::vector<Frame> m_frames;
std::unique_ptr<V8StackTraceImpl> m_parent; int m_maxAsyncDepth;
std::unique_ptr<V8StackTraceImpl> m_creation; std::weak_ptr<AsyncStackTrace> m_asyncParent;
std::weak_ptr<AsyncStackTrace> m_asyncCreation;
DISALLOW_COPY_AND_ASSIGN(V8StackTraceImpl); DISALLOW_COPY_AND_ASSIGN(V8StackTraceImpl);
}; };
class AsyncStackTrace {
public:
static std::shared_ptr<AsyncStackTrace> capture(V8Debugger*,
int contextGroupId,
const String16& description,
int maxStackSize);
std::unique_ptr<protocol::Runtime::StackTrace> buildInspectorObject(
AsyncStackTrace* asyncCreation, int maxAsyncDepth) const;
int contextGroupId() const;
std::weak_ptr<AsyncStackTrace> parent() const;
std::weak_ptr<AsyncStackTrace> creation() const;
bool isEmpty() const;
private:
AsyncStackTrace(int contextGroupId, const String16& description,
const std::vector<V8StackTraceImpl::Frame>& frames,
std::shared_ptr<AsyncStackTrace> asyncParent,
std::shared_ptr<AsyncStackTrace> asyncCreation);
int m_contextGroupId;
String16 m_description;
std::vector<V8StackTraceImpl::Frame> m_frames;
std::weak_ptr<AsyncStackTrace> m_asyncParent;
std::weak_ptr<AsyncStackTrace> m_asyncCreation;
DISALLOW_COPY_AND_ASSIGN(AsyncStackTrace);
};
} // namespace v8_inspector } // namespace v8_inspector
#endif // V8_INSPECTOR_V8STACKTRACEIMPL_H_ #endif // V8_INSPECTOR_V8STACKTRACEIMPL_H_
Checks that async stacks works good with different limits
Running test: testZeroLimit
foo1 (test.js:11:2)
Running test: testTwoLimit
foo1 (test.js:11:2)
-- Promise.resolve --
promise (test.js:23:2)
(anonymous) (expr.js:0:0)
Running test: testOneLimitTwoPromises
foo1 (test.js:11:2)
foo2 (test.js:15:2)
Running test: testFourLimitTwoPromises
foo1 (test.js:11:2)
foo2 (test.js:15:2)
Running test: testSixLimitTwoPromises
foo1 (test.js:11:2)
foo2 (test.js:15:2)
-- Promise.resolve --
twoPromises (test.js:35:2)
(anonymous) (expr.js:0:0)
Running test: testTwoLimitTwoSetTimeouts
foo1 (test.js:11:2)
foo2 (test.js:15:2)
-- setTimeout --
twoSetTimeout (test.js:41:2)
(anonymous) (expr.js:0:0)
Running test: testThreeLimitTwoSetTimeouts
foo1 (test.js:11:2)
-- setTimeout --
twoSetTimeout (test.js:40:2)
(anonymous) (expr.js:0:0)
foo2 (test.js:15:2)
-- setTimeout --
twoSetTimeout (test.js:41:2)
(anonymous) (expr.js:0:0)
Running test: testTenLimitTwentySetTimeouts
foo1 (:0:17)
(anonymous) (:0:28)
foo2 (:0:17)
(anonymous) (:0:28)
foo3 (:0:17)
(anonymous) (:0:28)
foo4 (:0:17)
(anonymous) (:0:28)
foo5 (:0:17)
(anonymous) (:0:28)
foo6 (:0:17)
(anonymous) (:0:28)
foo7 (:0:17)
(anonymous) (:0:28)
foo8 (:0:17)
(anonymous) (:0:28)
foo9 (:0:17)
(anonymous) (:0:28)
foo10 (:0:18)
(anonymous) (:0:29)
foo11 (:0:18)
(anonymous) (:0:29)
-- setTimeout --
twentySetTimeout (test.js:49:4)
(anonymous) (expr.js:0:0)
foo12 (:0:18)
(anonymous) (:0:29)
-- setTimeout --
twentySetTimeout (test.js:49:4)
(anonymous) (expr.js:0:0)
foo13 (:0:18)
(anonymous) (:0:29)
-- setTimeout --
twentySetTimeout (test.js:49:4)
(anonymous) (expr.js:0:0)
foo14 (:0:18)
(anonymous) (:0:29)
-- setTimeout --
twentySetTimeout (test.js:49:4)
(anonymous) (expr.js:0:0)
foo15 (:0:18)
(anonymous) (:0:29)
-- setTimeout --
twentySetTimeout (test.js:49:4)
(anonymous) (expr.js:0:0)
foo16 (:0:18)
(anonymous) (:0:29)
-- setTimeout --
twentySetTimeout (test.js:49:4)
(anonymous) (expr.js:0:0)
foo17 (:0:18)
(anonymous) (:0:29)
-- setTimeout --
twentySetTimeout (test.js:49:4)
(anonymous) (expr.js:0:0)
foo18 (:0:18)
(anonymous) (:0:29)
-- setTimeout --
twentySetTimeout (test.js:49:4)
(anonymous) (expr.js:0:0)
foo19 (:0:18)
(anonymous) (:0:29)
-- setTimeout --
twentySetTimeout (test.js:49:4)
(anonymous) (expr.js:0:0)
// Copyright 2016 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
InspectorTest.log('Checks that async stacks works good with different limits');
InspectorTest.addScript(`
var resolveTest;
function foo1() {
debugger;
}
function foo2() {
debugger;
if (resolveTest) resolveTest();
}
function promise() {
var resolve1;
var p1 = new Promise(resolve => resolve1 = resolve);
var p2 = p1.then(foo1);
resolve1();
return p2;
}
function twoPromises() {
var resolve1;
var resolve2;
var p1 = new Promise(resolve => resolve1 = resolve);
var p2 = new Promise(resolve => resolve2 = resolve);
var p3 = p1.then(foo1);
var p4 = p2.then(foo2);
resolve1();
resolve2();
return Promise.all([p3, p4]);
}
function twoSetTimeout() {
setTimeout(foo1, 0);
setTimeout(foo2, 0);
return new Promise(resolve => resolveTest = resolve);
}
function twentySetTimeout() {
var resolve1;
var p1 = new Promise(resolve => resolve1 = resolve);
for (var i = 1; i <= 19; ++i)
setTimeout('(function foo' + i + '(){debugger;})()',0);
setTimeout(resolve1, 0);
return p1;
}
//# sourceURL=test.js`, 7, 26);
InspectorTest.setupScriptMap();
Protocol.Debugger.onPaused(message => {
InspectorTest.logCallFrames(message.params.callFrames);
var asyncStackTrace = message.params.asyncStackTrace;
while (asyncStackTrace) {
InspectorTest.log(`-- ${asyncStackTrace.description} --`);
InspectorTest.logCallFrames(asyncStackTrace.callFrames);
asyncStackTrace = asyncStackTrace.parent;
}
InspectorTest.log('');
Protocol.Debugger.resume();
});
Protocol.Debugger.enable();
Protocol.Debugger.setAsyncCallStackDepth({ maxDepth: 128 });
InspectorTest.runTestSuite([
function testZeroLimit(next) {
Protocol.Runtime.evaluate({
expression: 'setMaxAsyncTaskStacks(0)//# sourceURL=expr.js'})
.then(() => Protocol.Runtime.evaluate({
expression: 'promise()//# sourceURL=expr.js', awaitPromise: true
}))
.then(() => cancelAllAsyncTasks())
.then(next);
},
function testTwoLimit(next) {
// we need one stack for parent task and one for next task.
Protocol.Runtime
.evaluate({expression: 'setMaxAsyncTaskStacks(2)//# sourceURL=expr.js'})
.then(() => Protocol.Runtime.evaluate({
expression: 'promise()//# sourceURL=expr.js',
awaitPromise: true
}))
.then(() => cancelAllAsyncTasks())
.then(next);
},
function testOneLimitTwoPromises(next) {
// Should be no async stacks because when first microtask is finished
// it will resolve and schedule p3 - will remove async stack for scheduled
// p2.
Protocol.Runtime.evaluate({
expression: 'setMaxAsyncTaskStacks(1)//# sourceURL=expr.js'})
.then(() => Protocol.Runtime.evaluate({
expression: 'twoPromises()//# sourceURL=expr.js', awaitPromise: true
}))
.then(() => cancelAllAsyncTasks())
.then(next);
},
function testFourLimitTwoPromises(next) {
Protocol.Runtime
.evaluate({expression: 'setMaxAsyncTaskStacks(4)//# sourceURL=expr.js'})
.then(() => Protocol.Runtime.evaluate({
expression: 'twoPromises()//# sourceURL=expr.js',
awaitPromise: true
}))
.then(() => cancelAllAsyncTasks())
.then(next);
},
function testSixLimitTwoPromises(next) {
Protocol.Runtime
.evaluate({expression: 'setMaxAsyncTaskStacks(6)//# sourceURL=expr.js'})
.then(() => Protocol.Runtime.evaluate({
expression: 'twoPromises()//# sourceURL=expr.js',
awaitPromise: true
}))
.then(() => cancelAllAsyncTasks())
.then(next);
},
function testTwoLimitTwoSetTimeouts(next) {
Protocol.Runtime.evaluate({
expression: 'setMaxAsyncTaskStacks(2)//# sourceURL=expr.js'})
.then(() => Protocol.Runtime.evaluate({
expression: 'twoSetTimeout()//# sourceURL=expr.js', awaitPromise: true
}))
.then(() => cancelAllAsyncTasks())
.then(next);
},
function testThreeLimitTwoSetTimeouts(next) {
Protocol.Runtime.evaluate({
expression: 'setMaxAsyncTaskStacks(3)//# sourceURL=expr.js'})
.then(() => Protocol.Runtime.evaluate({
expression: 'twoSetTimeout()//# sourceURL=expr.js', awaitPromise: true
}))
.then(() => cancelAllAsyncTasks())
.then(next);
},
function testTenLimitTwentySetTimeouts(next) {
Protocol.Runtime.evaluate({
expression: 'setMaxAsyncTaskStacks(10)//# sourceURL=expr.js'})
.then(() => Protocol.Runtime.evaluate({
expression: 'twentySetTimeout()//# sourceURL=expr.js',
awaitPromise: true
}))
.then(() => cancelAllAsyncTasks())
.then(next);
}
]);
function cancelAllAsyncTasks() {
return Protocol.Debugger.setAsyncCallStackDepth({ maxDepth: 0 })
.then(() => Protocol.Debugger.setAsyncCallStackDepth({ maxDepth: 128 }));
}
Checks that we drop old async call chains.
Running test: testInfrastructure
setMaxAsyncTaskStacks(1024)
Run expression 'console.trace(42)' with async chain len: 1
actual async chain len: 1
setMaxAsyncTaskStacks(1024)
Run expression 'console.trace(42)' with async chain len: 2
actual async chain len: 2
setMaxAsyncTaskStacks(1024)
Run expression 'console.trace(42)' with async chain len: 5
actual async chain len: 5
setMaxAsyncTaskStacks(1024)
Run expression 'console.trace(42)' with async chain len: 1
actual async chain len: 1
setMaxAsyncTaskStacks(1024)
Run expression 'console.trace(42)' with async chain len: 2
actual async chain len: 2
setMaxAsyncTaskStacks(1024)
Run expression 'console.trace(42)' with async chain len: 5
actual async chain len: 5
Running test: testZeroLimit
setMaxAsyncTaskStacks(0)
Run expression 'console.trace(42)' with async chain len: 1
actual async chain len: 0
setMaxAsyncTaskStacks(0)
Run expression 'console.trace(42)' with async chain len: 2
actual async chain len: 0
setMaxAsyncTaskStacks(0)
Run expression 'console.trace(42)' with async chain len: 1
actual async chain len: 0
setMaxAsyncTaskStacks(0)
Run expression 'console.trace(42)' with async chain len: 2
actual async chain len: 0
Running test: testOneLimit
setMaxAsyncTaskStacks(1)
Run expression 'console.trace(42)' with async chain len: 1
actual async chain len: 0
setMaxAsyncTaskStacks(1)
Run expression 'console.trace(42)' with async chain len: 2
actual async chain len: 0
setMaxAsyncTaskStacks(1)
Run expression 'console.trace(42)' with async chain len: 1
actual async chain len: 1
setMaxAsyncTaskStacks(1)
Run expression 'console.trace(42)' with async chain len: 2
actual async chain len: 1
Running test: testTwoLimit
setMaxAsyncTaskStacks(2)
Run expression 'console.trace(42)' with async chain len: 1
actual async chain len: 0
setMaxAsyncTaskStacks(2)
Run expression 'console.trace(42)' with async chain len: 2
actual async chain len: 0
setMaxAsyncTaskStacks(2)
Run expression 'console.trace(42)' with async chain len: 3
actual async chain len: 0
setMaxAsyncTaskStacks(2)
Run expression 'console.trace(42)' with async chain len: 1
actual async chain len: 1
setMaxAsyncTaskStacks(2)
Run expression 'console.trace(42)' with async chain len: 2
actual async chain len: 2
setMaxAsyncTaskStacks(2)
Run expression 'console.trace(42)' with async chain len: 3
actual async chain len: 1
Running test: testMoreThanTwoLimit
setMaxAsyncTaskStacks(3)
Run expression 'console.trace(42)' with async chain len: 1
actual async chain len: 1
setMaxAsyncTaskStacks(3)
Run expression 'console.trace(42)' with async chain len: 2
actual async chain len: 0
setMaxAsyncTaskStacks(3)
Run expression 'console.trace(42)' with async chain len: 3
actual async chain len: 0
setMaxAsyncTaskStacks(3)
Run expression 'console.trace(42)' with async chain len: 1
actual async chain len: 1
setMaxAsyncTaskStacks(3)
Run expression 'console.trace(42)' with async chain len: 2
actual async chain len: 2
setMaxAsyncTaskStacks(3)
Run expression 'console.trace(42)' with async chain len: 3
actual async chain len: 3
setMaxAsyncTaskStacks(4)
Run expression 'console.trace(42)' with async chain len: 1
actual async chain len: 1
setMaxAsyncTaskStacks(4)
Run expression 'console.trace(42)' with async chain len: 2
actual async chain len: 1
setMaxAsyncTaskStacks(4)
Run expression 'console.trace(42)' with async chain len: 3
actual async chain len: 0
setMaxAsyncTaskStacks(4)
Run expression 'console.trace(42)' with async chain len: 1
actual async chain len: 1
setMaxAsyncTaskStacks(4)
Run expression 'console.trace(42)' with async chain len: 2
actual async chain len: 2
setMaxAsyncTaskStacks(4)
Run expression 'console.trace(42)' with async chain len: 3
actual async chain len: 3
setMaxAsyncTaskStacks(5)
Run expression 'console.trace(42)' with async chain len: 1
actual async chain len: 1
setMaxAsyncTaskStacks(5)
Run expression 'console.trace(42)' with async chain len: 2
actual async chain len: 2
setMaxAsyncTaskStacks(5)
Run expression 'console.trace(42)' with async chain len: 3
actual async chain len: 2
setMaxAsyncTaskStacks(5)
Run expression 'console.trace(42)' with async chain len: 1
actual async chain len: 1
setMaxAsyncTaskStacks(5)
Run expression 'console.trace(42)' with async chain len: 2
actual async chain len: 2
setMaxAsyncTaskStacks(5)
Run expression 'console.trace(42)' with async chain len: 3
actual async chain len: 3
setMaxAsyncTaskStacks(6)
Run expression 'console.trace(42)' with async chain len: 1
actual async chain len: 1
setMaxAsyncTaskStacks(6)
Run expression 'console.trace(42)' with async chain len: 2
actual async chain len: 2
setMaxAsyncTaskStacks(6)
Run expression 'console.trace(42)' with async chain len: 3
actual async chain len: 2
setMaxAsyncTaskStacks(6)
Run expression 'console.trace(42)' with async chain len: 1
actual async chain len: 1
setMaxAsyncTaskStacks(6)
Run expression 'console.trace(42)' with async chain len: 2
actual async chain len: 2
setMaxAsyncTaskStacks(6)
Run expression 'console.trace(42)' with async chain len: 3
actual async chain len: 3
setMaxAsyncTaskStacks(7)
Run expression 'console.trace(42)' with async chain len: 1
actual async chain len: 1
setMaxAsyncTaskStacks(7)
Run expression 'console.trace(42)' with async chain len: 2
actual async chain len: 2
setMaxAsyncTaskStacks(7)
Run expression 'console.trace(42)' with async chain len: 3
actual async chain len: 3
setMaxAsyncTaskStacks(7)
Run expression 'console.trace(42)' with async chain len: 1
actual async chain len: 1
setMaxAsyncTaskStacks(7)
Run expression 'console.trace(42)' with async chain len: 2
actual async chain len: 2
setMaxAsyncTaskStacks(7)
Run expression 'console.trace(42)' with async chain len: 3
actual async chain len: 3
// Copyright 2017 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
InspectorTest.log('Checks that we drop old async call chains.');
Protocol.Debugger.enable();
Protocol.Runtime.enable();
InspectorTest.runAsyncTestSuite([
async function testInfrastructure() {
Protocol.Debugger.setAsyncCallStackDepth({maxDepth: 128});
await setMaxAsyncTaskStacks(1024);
runWithAsyncChainPromise(1, 'console.trace(42)');
dumpAsyncChainLength(await Protocol.Runtime.onceConsoleAPICalled());
await setMaxAsyncTaskStacks(1024);
runWithAsyncChainPromise(2, 'console.trace(42)');
dumpAsyncChainLength(await Protocol.Runtime.onceConsoleAPICalled());
await setMaxAsyncTaskStacks(1024);
runWithAsyncChainPromise(5, 'console.trace(42)');
dumpAsyncChainLength(await Protocol.Runtime.onceConsoleAPICalled());
await setMaxAsyncTaskStacks(1024);
runWithAsyncChainSetTimeout(1, 'console.trace(42)');
dumpAsyncChainLength(await Protocol.Runtime.onceConsoleAPICalled());
await setMaxAsyncTaskStacks(1024);
runWithAsyncChainSetTimeout(2, 'console.trace(42)');
dumpAsyncChainLength(await Protocol.Runtime.onceConsoleAPICalled());
await setMaxAsyncTaskStacks(1024);
runWithAsyncChainSetTimeout(5, 'console.trace(42)');
dumpAsyncChainLength(await Protocol.Runtime.onceConsoleAPICalled());
},
async function testZeroLimit() {
const limit = 0;
Protocol.Debugger.setAsyncCallStackDepth({maxDepth: 128});
await setMaxAsyncTaskStacks(limit);
runWithAsyncChainPromise(1, 'console.trace(42)');
dumpAsyncChainLength(await Protocol.Runtime.onceConsoleAPICalled());
await setMaxAsyncTaskStacks(limit);
runWithAsyncChainPromise(2, 'console.trace(42)');
dumpAsyncChainLength(await Protocol.Runtime.onceConsoleAPICalled());
await setMaxAsyncTaskStacks(limit);
runWithAsyncChainSetTimeout(1, 'console.trace(42)');
dumpAsyncChainLength(await Protocol.Runtime.onceConsoleAPICalled());
await setMaxAsyncTaskStacks(limit);
runWithAsyncChainSetTimeout(2, 'console.trace(42)');
dumpAsyncChainLength(await Protocol.Runtime.onceConsoleAPICalled());
},
async function testOneLimit() {
const limit = 1;
Protocol.Debugger.setAsyncCallStackDepth({maxDepth: 128});
await setMaxAsyncTaskStacks(limit);
runWithAsyncChainPromise(1, 'console.trace(42)');
dumpAsyncChainLength(await Protocol.Runtime.onceConsoleAPICalled());
await setMaxAsyncTaskStacks(limit);
runWithAsyncChainPromise(2, 'console.trace(42)');
dumpAsyncChainLength(await Protocol.Runtime.onceConsoleAPICalled());
await setMaxAsyncTaskStacks(limit);
runWithAsyncChainSetTimeout(1, 'console.trace(42)');
dumpAsyncChainLength(await Protocol.Runtime.onceConsoleAPICalled());
await setMaxAsyncTaskStacks(limit);
runWithAsyncChainSetTimeout(2, 'console.trace(42)');
dumpAsyncChainLength(await Protocol.Runtime.onceConsoleAPICalled());
},
async function testTwoLimit() {
const limit = 2;
Protocol.Debugger.setAsyncCallStackDepth({maxDepth: 128});
await setMaxAsyncTaskStacks(limit);
runWithAsyncChainPromise(1, 'console.trace(42)');
dumpAsyncChainLength(await Protocol.Runtime.onceConsoleAPICalled());
await setMaxAsyncTaskStacks(limit);
runWithAsyncChainPromise(2, 'console.trace(42)');
dumpAsyncChainLength(await Protocol.Runtime.onceConsoleAPICalled());
await setMaxAsyncTaskStacks(limit);
runWithAsyncChainPromise(3, 'console.trace(42)');
dumpAsyncChainLength(await Protocol.Runtime.onceConsoleAPICalled());
await setMaxAsyncTaskStacks(limit);
runWithAsyncChainSetTimeout(1, 'console.trace(42)');
dumpAsyncChainLength(await Protocol.Runtime.onceConsoleAPICalled());
await setMaxAsyncTaskStacks(limit);
runWithAsyncChainSetTimeout(2, 'console.trace(42)');
dumpAsyncChainLength(await Protocol.Runtime.onceConsoleAPICalled());
await setMaxAsyncTaskStacks(limit);
runWithAsyncChainSetTimeout(3, 'console.trace(42)');
dumpAsyncChainLength(await Protocol.Runtime.onceConsoleAPICalled());
},
async function testMoreThanTwoLimit() {
for (let limit = 3; limit <= 7; ++limit) {
Protocol.Debugger.setAsyncCallStackDepth({maxDepth: 128});
await setMaxAsyncTaskStacks(limit);
runWithAsyncChainPromise(1, 'console.trace(42)');
dumpAsyncChainLength(await Protocol.Runtime.onceConsoleAPICalled());
await setMaxAsyncTaskStacks(limit);
runWithAsyncChainPromise(2, 'console.trace(42)');
dumpAsyncChainLength(await Protocol.Runtime.onceConsoleAPICalled());
await setMaxAsyncTaskStacks(limit);
runWithAsyncChainPromise(3, 'console.trace(42)');
dumpAsyncChainLength(await Protocol.Runtime.onceConsoleAPICalled());
await setMaxAsyncTaskStacks(limit);
runWithAsyncChainSetTimeout(1, 'console.trace(42)');
dumpAsyncChainLength(await Protocol.Runtime.onceConsoleAPICalled());
await setMaxAsyncTaskStacks(limit);
runWithAsyncChainSetTimeout(2, 'console.trace(42)');
dumpAsyncChainLength(await Protocol.Runtime.onceConsoleAPICalled());
await setMaxAsyncTaskStacks(limit);
runWithAsyncChainSetTimeout(3, 'console.trace(42)');
dumpAsyncChainLength(await Protocol.Runtime.onceConsoleAPICalled());
}
},
]);
function runWithAsyncChainPromise(len, source) {
InspectorTest.log(`Run expression '${source}' with async chain len: ${len}`);
let then = '.then(() => 1)';
let pause = `.then(() => { ${source} })`;
Protocol.Runtime.evaluate({
expression: `Promise.resolve()${then.repeat(len - 1)}${pause}`
});
}
function runWithAsyncChainSetTimeout(len, source) {
InspectorTest.log(`Run expression '${source}' with async chain len: ${len}`);
let setTimeoutPrefix = '() => setTimeout(';
let setTimeoutSuffix = ', 0)';
Protocol.Runtime.evaluate({
expression: `setTimeout(${setTimeoutPrefix.repeat(len - 1)}'${source}'${setTimeoutSuffix.repeat(len - 1)}, 0)`
});
}
function dumpAsyncChainLength(message) {
let stackTrace = message.params.asyncStackTrace || message.params.stackTrace.parent;
let asyncChainCount = 0;
while (stackTrace) {
++asyncChainCount;
stackTrace = stackTrace.parent;
}
InspectorTest.log(`actual async chain len: ${asyncChainCount}\n`);
}
async function setMaxAsyncTaskStacks(max) {
let expression = `setMaxAsyncTaskStacks(${max})`;
InspectorTest.log(expression);
await Protocol.Runtime.evaluate({expression});
}
Checks that we report not more then maxDepth call chains.
Running test: testPaused
Actual call chain length: 8
setAsyncCallStackDepth(maxDepth): 16
reported: 8
Actual call chain length: 8
setAsyncCallStackDepth(maxDepth): 8
reported: 8
Actual call chain length: 8
setAsyncCallStackDepth(maxDepth): 7
reported: 7
Actual call chain length: 8
setAsyncCallStackDepth(maxDepth): 0
reported: 0
Running test: testConsoleTrace
Actual call chain length: 8
setAsyncCallStackDepth(maxDepth): 16
reported: 8
Actual call chain length: 8
setAsyncCallStackDepth(maxDepth): 8
reported: 8
Actual call chain length: 8
setAsyncCallStackDepth(maxDepth): 7
reported: 7
Actual call chain length: 8
setAsyncCallStackDepth(maxDepth): 0
reported: 0
// Copyright 2017 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
InspectorTest.log('Checks that we report not more then maxDepth call chains.');
InspectorTest.addScript(`
function promisesChain(num) {
var p = Promise.resolve();
for (var i = 0; i < num - 1; ++i) {
p = p.then(() => 42);
}
return p;
}
`);
Protocol.Debugger.enable();
InspectorTest.runAsyncTestSuite([
async function testPaused() {
let callback = '() => { debugger; }';
startTest({ generated: 8, limit: 16, callback});
dumpCaptured((await Protocol.Debugger.oncePaused()).params.asyncStackTrace);
await Protocol.Debugger.resume();
startTest({ generated: 8, limit: 8, callback});
dumpCaptured((await Protocol.Debugger.oncePaused()).params.asyncStackTrace);
await Protocol.Debugger.resume();
startTest({ generated: 8, limit: 7, callback});
dumpCaptured((await Protocol.Debugger.oncePaused()).params.asyncStackTrace);
await Protocol.Debugger.resume();
startTest({ generated: 8, limit: 0, callback});
dumpCaptured((await Protocol.Debugger.oncePaused()).params.asyncStackTrace);
await Protocol.Debugger.resume();
},
async function testConsoleTrace() {
await Protocol.Runtime.enable();
let callback = '() => { console.trace(42); }';
startTest({ generated: 8, limit: 16, callback});
let msg = await Protocol.Runtime.onceConsoleAPICalled();
dumpCaptured(msg.params.stackTrace.parent);
startTest({ generated: 8, limit: 8, callback});
msg = await Protocol.Runtime.onceConsoleAPICalled();
dumpCaptured(msg.params.stackTrace.parent);
startTest({ generated: 8, limit: 7, callback});
msg = await Protocol.Runtime.onceConsoleAPICalled();
dumpCaptured(msg.params.stackTrace.parent);
startTest({ generated: 8, limit: 0, callback});
msg = await Protocol.Runtime.onceConsoleAPICalled();
dumpCaptured(msg.params.stackTrace.parent);
await Protocol.Runtime.disable();
}
]);
function startTest(params) {
InspectorTest.log('Actual call chain length: ' + params.generated);
InspectorTest.log('setAsyncCallStackDepth(maxDepth): ' + params.limit);
Protocol.Debugger.setAsyncCallStackDepth({maxDepth: params.limit});
Protocol.Runtime.evaluate({expression:
`promisesChain(${params.generated}).then(${params.callback})`});
}
function dumpCaptured(stack) {
let count = 0;
while (stack) {
++count;
stack = stack.parent;
}
InspectorTest.log('reported: ' + count + '\n');
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment