Commit 38be4a17 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-Original-Commit-Position: refs/heads/master@{#44670}
Committed: https://chromium.googlesource.com/v8/v8/+/1bca73bc832c645138bd3e0306fcaa8bb44dad04
Review-Url: https://codereview.chromium.org/2816043006
Cr-Commit-Position: refs/heads/master@{#44694}
parent 7fcf658a
...@@ -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;
......
This diff is collapsed.
...@@ -5,93 +5,115 @@ ...@@ -5,93 +5,115 @@
#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: public:
static const size_t maxCallStackSizeToCapture = 200;
class Frame {
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:
String16 m_functionName;
String16 m_scriptId;
String16 m_sourceURL;
int m_lineNumber; // 0-based.
int m_columnNumber; // 0-based.
};
private: private:
V8StackTraceImpl(int contextGroupId, const String16& description, V8StackTraceImpl(const std::vector<Frame> frames, int maxAsyncDepth,
std::vector<Frame>& frames, std::shared_ptr<AsyncStackTrace> asyncParent,
std::unique_ptr<V8StackTraceImpl> parent, std::shared_ptr<AsyncStackTrace> asyncCreation);
std::unique_ptr<V8StackTraceImpl> creation);
std::unique_ptr<protocol::Runtime::StackTrace> buildInspectorObjectImpl( std::vector<Frame> m_frames;
V8StackTraceImpl* creation) const; int m_maxAsyncDepth;
std::weak_ptr<AsyncStackTrace> m_asyncParent;
std::weak_ptr<AsyncStackTrace> m_asyncCreation;
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; int m_contextGroupId;
String16 m_description; String16 m_description;
std::vector<Frame> m_frames;
std::unique_ptr<V8StackTraceImpl> m_parent;
std::unique_ptr<V8StackTraceImpl> m_creation;
DISALLOW_COPY_AND_ASSIGN(V8StackTraceImpl); 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
......
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');
}
...@@ -85,6 +85,25 @@ void MessageHandler(v8::Local<v8::Message> message, ...@@ -85,6 +85,25 @@ void MessageHandler(v8::Local<v8::Message> message,
inspector->createStackTrace(stack), script_id); inspector->createStackTrace(stack), script_id);
} }
v8::Local<v8::String> ToString(v8::Isolate* isolate,
const v8_inspector::StringView& string) {
if (string.is8Bit())
return v8::String::NewFromOneByte(isolate, string.characters8(),
v8::NewStringType::kNormal,
static_cast<int>(string.length()))
.ToLocalChecked();
else
return v8::String::NewFromTwoByte(isolate, string.characters16(),
v8::NewStringType::kNormal,
static_cast<int>(string.length()))
.ToLocalChecked();
}
void Print(v8::Isolate* isolate, const v8_inspector::StringView& string) {
v8::Local<v8::String> v8_string = ToString(isolate, string);
v8::String::Utf8Value utf8_string(v8_string);
fwrite(*utf8_string, sizeof(**utf8_string), utf8_string.length(), stdout);
}
} // namespace } // namespace
class ConnectTask : public TaskRunner::Task { class ConnectTask : public TaskRunner::Task {
...@@ -265,6 +284,10 @@ void InspectorClientImpl::setMemoryInfoForTest( ...@@ -265,6 +284,10 @@ void InspectorClientImpl::setMemoryInfoForTest(
memory_info_.Reset(isolate_, memory_info); memory_info_.Reset(isolate_, memory_info);
} }
void InspectorClientImpl::setLogConsoleApiMessageCalls(bool log) {
log_console_api_message_calls_ = log;
}
v8::MaybeLocal<v8::Value> InspectorClientImpl::memoryInfo( v8::MaybeLocal<v8::Value> InspectorClientImpl::memoryInfo(
v8::Isolate* isolate, v8::Local<v8::Context>) { v8::Isolate* isolate, v8::Local<v8::Context>) {
if (memory_info_.IsEmpty()) return v8::MaybeLocal<v8::Value>(); if (memory_info_.IsEmpty()) return v8::MaybeLocal<v8::Value>();
...@@ -279,6 +302,20 @@ void InspectorClientImpl::quitMessageLoopOnPause() { ...@@ -279,6 +302,20 @@ void InspectorClientImpl::quitMessageLoopOnPause() {
task_runner_->QuitMessageLoop(); task_runner_->QuitMessageLoop();
} }
void InspectorClientImpl::consoleAPIMessage(
int contextGroupId, v8::Isolate::MessageErrorLevel level,
const v8_inspector::StringView& message,
const v8_inspector::StringView& url, unsigned lineNumber,
unsigned columnNumber, v8_inspector::V8StackTrace* stack) {
if (!log_console_api_message_calls_) return;
Print(isolate_, message);
fprintf(stdout, " (");
Print(isolate_, url);
fprintf(stdout, ":%d:%d)", lineNumber, columnNumber);
Print(isolate_, stack->toString()->string());
fprintf(stdout, "\n");
}
v8_inspector::V8Inspector* InspectorClientImpl::InspectorFromContext( v8_inspector::V8Inspector* InspectorClientImpl::InspectorFromContext(
v8::Local<v8::Context> context) { v8::Local<v8::Context> context) {
return InspectorClientFromContext(context)->inspector_.get(); return InspectorClientFromContext(context)->inspector_.get();
......
...@@ -40,6 +40,7 @@ class InspectorClientImpl : public v8_inspector::V8InspectorClient { ...@@ -40,6 +40,7 @@ class InspectorClientImpl : public v8_inspector::V8InspectorClient {
void setCurrentTimeMSForTest(double time); void setCurrentTimeMSForTest(double time);
void setMemoryInfoForTest(v8::Local<v8::Value> memory_info); void setMemoryInfoForTest(v8::Local<v8::Value> memory_info);
void setLogConsoleApiMessageCalls(bool log);
private: private:
// V8InspectorClient implementation. // V8InspectorClient implementation.
...@@ -51,7 +52,12 @@ class InspectorClientImpl : public v8_inspector::V8InspectorClient { ...@@ -51,7 +52,12 @@ class InspectorClientImpl : public v8_inspector::V8InspectorClient {
v8::Local<v8::Context>) override; v8::Local<v8::Context>) override;
void runMessageLoopOnPause(int context_group_id) override; void runMessageLoopOnPause(int context_group_id) override;
void quitMessageLoopOnPause() override; void quitMessageLoopOnPause() override;
void consoleAPIMessage(int contextGroupId,
v8::Isolate::MessageErrorLevel level,
const v8_inspector::StringView& message,
const v8_inspector::StringView& url,
unsigned lineNumber, unsigned columnNumber,
v8_inspector::V8StackTrace*) override;
friend class SendMessageToBackendTask; friend class SendMessageToBackendTask;
friend class ConnectTask; friend class ConnectTask;
...@@ -75,6 +81,7 @@ class InspectorClientImpl : public v8_inspector::V8InspectorClient { ...@@ -75,6 +81,7 @@ class InspectorClientImpl : public v8_inspector::V8InspectorClient {
bool current_time_set_for_test_ = false; bool current_time_set_for_test_ = false;
double current_time_ = 0.0; double current_time_ = 0.0;
bool log_console_api_message_calls_ = false;
DISALLOW_COPY_AND_ASSIGN(InspectorClientImpl); DISALLOW_COPY_AND_ASSIGN(InspectorClientImpl);
}; };
......
...@@ -61,6 +61,7 @@ class UtilsExtension : public v8::Extension { ...@@ -61,6 +61,7 @@ class UtilsExtension : public v8::Extension {
"native function schedulePauseOnNextStatement();" "native function schedulePauseOnNextStatement();"
"native function cancelPauseOnNextStatement();" "native function cancelPauseOnNextStatement();"
"native function reconnect();" "native function reconnect();"
"native function setLogConsoleApiMessageCalls();"
"native function createContextGroup();") {} "native function createContextGroup();") {}
virtual v8::Local<v8::FunctionTemplate> GetNativeFunctionTemplate( virtual v8::Local<v8::FunctionTemplate> GetNativeFunctionTemplate(
v8::Isolate* isolate, v8::Local<v8::String> name) { v8::Isolate* isolate, v8::Local<v8::String> name) {
...@@ -136,6 +137,14 @@ class UtilsExtension : public v8::Extension { ...@@ -136,6 +137,14 @@ class UtilsExtension : public v8::Extension {
.ToLocalChecked()) .ToLocalChecked())
.FromJust()) { .FromJust()) {
return v8::FunctionTemplate::New(isolate, UtilsExtension::Reconnect); return v8::FunctionTemplate::New(isolate, UtilsExtension::Reconnect);
} else if (name->Equals(context,
v8::String::NewFromUtf8(
isolate, "setLogConsoleApiMessageCalls",
v8::NewStringType::kNormal)
.ToLocalChecked())
.FromJust()) {
return v8::FunctionTemplate::New(
isolate, UtilsExtension::SetLogConsoleApiMessageCalls);
} else if (name->Equals(context, v8::String::NewFromUtf8( } else if (name->Equals(context, v8::String::NewFromUtf8(
isolate, "createContextGroup", isolate, "createContextGroup",
v8::NewStringType::kNormal) v8::NewStringType::kNormal)
...@@ -318,6 +327,16 @@ class UtilsExtension : public v8::Extension { ...@@ -318,6 +327,16 @@ class UtilsExtension : public v8::Extension {
ready_semaphore.Wait(); ready_semaphore.Wait();
} }
static void SetLogConsoleApiMessageCalls(
const v8::FunctionCallbackInfo<v8::Value>& args) {
if (args.Length() != 1 || !args[0]->IsBoolean()) {
fprintf(stderr, "Internal error: setLogConsoleApiMessageCalls(bool).");
Exit();
}
inspector_client_->setLogConsoleApiMessageCalls(
args[0].As<v8::Boolean>()->Value());
}
static void CreateContextGroup( static void CreateContextGroup(
const v8::FunctionCallbackInfo<v8::Value>& args) { const v8::FunctionCallbackInfo<v8::Value>& args) {
if (args.Length() != 0) { if (args.Length() != 0) {
......
...@@ -57,6 +57,8 @@ var utils = {}; ...@@ -57,6 +57,8 @@ var utils = {};
this.cancelPauseOnNextStatement = null; this.cancelPauseOnNextStatement = null;
utils.reconnect = reconnect; utils.reconnect = reconnect;
this.reconnect = null; this.reconnect = null;
utils.setLogConsoleApiMessageCalls = setLogConsoleApiMessageCalls;
this.setLogConsoleApiMessageCalls = null;
utils.createContextGroup = createContextGroup; utils.createContextGroup = createContextGroup;
this.createContextGroup = null; this.createContextGroup = null;
})(); })();
......
Checks that we passed correct arguments in V8InspectorClient::consoleAPIMessage. Note: lines and columns are 1-based.
42 (:1:9)
at (anonymous function) (:1:9)
239 (:13:15)
at b (:13:15)
at a (:15:5)
at consoleTrace (:17:3)
at (anonymous function) (:1:1)
// 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 passed correct arguments in ' +
'V8InspectorClient::consoleAPIMessage. Note: lines and columns are 1-based.');
InspectorTest.addScript(`
function consoleTrace() {
function a() {
function b() {
console.trace(239);
}
b();
}
a();
}
`, 8, 26);
Protocol.Runtime.enable();
utils.setLogConsoleApiMessageCalls(true);
(async function test() {
Protocol.Runtime.evaluate({expression: 'console.log(42)'});
await Protocol.Runtime.onceConsoleAPICalled()
Protocol.Runtime.evaluate({expression: 'consoleTrace()'});
await Protocol.Runtime.onceConsoleAPICalled()
InspectorTest.completeTest();
})();
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