blob: 282e4870009d5518e0d58706b6b67248d57ff6fd [file] [log] [blame]
// 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.
#ifndef V8_INSPECTOR_V8_DEBUGGER_H_
#define V8_INSPECTOR_V8_DEBUGGER_H_
#include <list>
#include <memory>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "src/base/macros.h"
#include "src/inspector/inspected-context.h"
#include "src/inspector/protocol/Debugger.h"
#include "src/inspector/protocol/Forward.h"
#include "src/inspector/protocol/Runtime.h"
#include "src/inspector/v8-debugger-script.h"
#include "include/v8-inspector.h"
namespace v8_inspector {
class AsyncStackTrace;
class StackFrame;
class V8Debugger;
class V8DebuggerAgentImpl;
class V8InspectorImpl;
class V8StackTraceImpl;
struct V8StackTraceId;
enum class WrapMode { kForceValue, kNoPreview, kWithPreview };
using protocol::Response;
using TerminateExecutionCallback =
protocol::Runtime::Backend::TerminateExecutionCallback;
// This debugger id tries to be unique by generating two random
// numbers, which should most likely avoid collisions.
// Debugger id has a 1:1 mapping to context group. It is used to
// attribute stack traces to a particular debugging, when doing any
// cross-debugger operations (e.g. async step in).
// See also Runtime.UniqueDebuggerId in the protocol.
class V8DebuggerId {
public:
V8DebuggerId() = default;
explicit V8DebuggerId(std::pair<int64_t, int64_t>);
explicit V8DebuggerId(const String16&);
V8DebuggerId(const V8DebuggerId&) V8_NOEXCEPT = default;
~V8DebuggerId() = default;
static V8DebuggerId generate(v8::Isolate*);
String16 toString() const;
bool isValid() const;
std::pair<int64_t, int64_t> pair() const;
private:
int64_t m_first = 0;
int64_t m_second = 0;
};
class V8Debugger : public v8::debug::DebugDelegate,
public v8::debug::AsyncEventDelegate {
public:
V8Debugger(v8::Isolate*, V8InspectorImpl*);
~V8Debugger() override;
V8Debugger(const V8Debugger&) = delete;
V8Debugger& operator=(const V8Debugger&) = delete;
bool enabled() const;
v8::Isolate* isolate() const { return m_isolate; }
void setBreakpointsActive(bool);
v8::debug::ExceptionBreakState getPauseOnExceptionsState();
void setPauseOnExceptionsState(v8::debug::ExceptionBreakState);
bool canBreakProgram();
void breakProgram(int targetContextGroupId);
void interruptAndBreak(int targetContextGroupId);
void continueProgram(int targetContextGroupId,
bool terminateOnResume = false);
void breakProgramOnAssert(int targetContextGroupId);
void setPauseOnNextCall(bool, int targetContextGroupId);
void stepIntoStatement(int targetContextGroupId, bool breakOnAsyncCall);
void stepOverStatement(int targetContextGroupId);
void stepOutOfFunction(int targetContextGroupId);
void terminateExecution(std::unique_ptr<TerminateExecutionCallback> callback);
Response continueToLocation(int targetContextGroupId,
V8DebuggerScript* script,
std::unique_ptr<protocol::Debugger::Location>,
const String16& targetCallFramess);
// Each script inherits debug data from v8::Context where it has been
// compiled.
// Only scripts whose debug data matches |contextGroupId| will be reported.
// Passing 0 will result in reporting all scripts.
std::vector<std::unique_ptr<V8DebuggerScript>> getCompiledScripts(
int contextGroupId, V8DebuggerAgentImpl* agent);
void enable();
void disable();
bool isPaused() const { return m_pausedContextGroupId; }
bool isPausedInContextGroup(int contextGroupId) const;
int maxAsyncCallChainDepth() { return m_maxAsyncCallStackDepth; }
void setAsyncCallStackDepth(V8DebuggerAgentImpl*, int);
std::shared_ptr<AsyncStackTrace> currentAsyncParent();
V8StackTraceId currentExternalParent();
std::shared_ptr<StackFrame> symbolize(v8::Local<v8::StackFrame> v8Frame);
std::unique_ptr<V8StackTraceImpl> createStackTrace(v8::Local<v8::StackTrace>);
std::unique_ptr<V8StackTraceImpl> captureStackTrace(bool fullStack);
v8::MaybeLocal<v8::Array> internalProperties(v8::Local<v8::Context>,
v8::Local<v8::Value>);
v8::Local<v8::Array> queryObjects(v8::Local<v8::Context> context,
v8::Local<v8::Object> prototype);
void asyncTaskScheduled(const StringView& taskName, void* task,
bool recurring);
void asyncTaskCanceled(void* task);
void asyncTaskStarted(void* task);
void asyncTaskFinished(void* task);
void allAsyncTasksCanceled();
V8StackTraceId storeCurrentStackTrace(const StringView& description);
void externalAsyncTaskStarted(const V8StackTraceId& parent);
void externalAsyncTaskFinished(const V8StackTraceId& parent);
uintptr_t storeStackTrace(std::shared_ptr<AsyncStackTrace> stack);
void muteScriptParsedEvents();
void unmuteScriptParsedEvents();
V8InspectorImpl* inspector() { return m_inspector; }
void setMaxAsyncTaskStacksForTest(int limit);
void dumpAsyncTaskStacksStateForTest();
V8DebuggerId debuggerIdFor(int contextGroupId);
std::shared_ptr<AsyncStackTrace> stackTraceFor(int contextGroupId,
const V8StackTraceId& id);
void reportTermination();
private:
bool addInternalObject(v8::Local<v8::Context> context,
v8::Local<v8::Object> object,
V8InternalValueType type);
void clearContinueToLocation();
bool shouldContinueToCurrentLocation();
static size_t nearHeapLimitCallback(void* data, size_t current_heap_limit,
size_t initial_heap_limit);
static void terminateExecutionCompletedCallback(v8::Isolate* isolate);
static void terminateExecutionCompletedCallbackIgnoringData(
v8::Isolate* isolate, void*);
void handleProgramBreak(
v8::Local<v8::Context> pausedContext, v8::Local<v8::Value> exception,
const std::vector<v8::debug::BreakpointId>& hitBreakpoints,
v8::debug::ExceptionType exception_type = v8::debug::kException,
bool isUncaught = false);
enum ScopeTargetKind {
FUNCTION,
GENERATOR,
};
v8::MaybeLocal<v8::Value> getTargetScopes(v8::Local<v8::Context>,
v8::Local<v8::Value>,
ScopeTargetKind);
v8::MaybeLocal<v8::Value> functionScopes(v8::Local<v8::Context>,
v8::Local<v8::Function>);
v8::MaybeLocal<v8::Value> generatorScopes(v8::Local<v8::Context>,
v8::Local<v8::Value>);
v8::MaybeLocal<v8::Array> collectionsEntries(v8::Local<v8::Context> context,
v8::Local<v8::Value> value);
void asyncTaskScheduledForStack(const String16& taskName, void* task,
bool recurring);
void asyncTaskCanceledForStack(void* task);
void asyncTaskStartedForStack(void* task);
void asyncTaskFinishedForStack(void* task);
void asyncTaskCandidateForStepping(void* task);
void asyncTaskStartedForStepping(void* task);
void asyncTaskFinishedForStepping(void* task);
void asyncTaskCanceledForStepping(void* task);
// v8::debug::DebugEventListener implementation.
void AsyncEventOccurred(v8::debug::DebugAsyncActionType type, int id,
bool isBlackboxed) override;
void ScriptCompiled(v8::Local<v8::debug::Script> script, bool is_live_edited,
bool has_compile_error) override;
void BreakProgramRequested(
v8::Local<v8::Context> paused_context,
const std::vector<v8::debug::BreakpointId>& break_points_hit) override;
void ExceptionThrown(v8::Local<v8::Context> paused_context,
v8::Local<v8::Value> exception,
v8::Local<v8::Value> promise, bool is_uncaught,
v8::debug::ExceptionType exception_type) override;
bool IsFunctionBlackboxed(v8::Local<v8::debug::Script> script,
const v8::debug::Location& start,
const v8::debug::Location& end) override;
bool ShouldBeSkipped(v8::Local<v8::debug::Script> script, int line,
int column) override;
int currentContextGroupId();
bool asyncStepOutOfFunction(int targetContextGroupId, bool onlyAtReturn);
bool hasScheduledBreakOnNextFunctionCall() const;
v8::Isolate* m_isolate;
V8InspectorImpl* m_inspector;
int m_enableCount;
int m_breakpointsActiveCount = 0;
int m_ignoreScriptParsedEventsCounter;
size_t m_originalHeapLimit = 0;
bool m_scheduledOOMBreak = false;
bool m_scheduledAssertBreak = false;
int m_targetContextGroupId = 0;
int m_pausedContextGroupId = 0;
int m_continueToLocationBreakpointId;
String16 m_continueToLocationTargetCallFrames;
std::unique_ptr<V8StackTraceImpl> m_continueToLocationStack;
using AsyncTaskToStackTrace =
std::unordered_map<void*, std::weak_ptr<AsyncStackTrace>>;
AsyncTaskToStackTrace m_asyncTaskStacks;
std::unordered_set<void*> m_recurringTasks;
int m_maxAsyncCallStacks;
int m_maxAsyncCallStackDepth;
std::vector<void*> m_currentTasks;
std::vector<std::shared_ptr<AsyncStackTrace>> m_currentAsyncParent;
std::vector<V8StackTraceId> m_currentExternalParent;
void collectOldAsyncStacksIfNeeded();
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;
std::unordered_map<V8DebuggerAgentImpl*, int> m_maxAsyncCallStackDepthMap;
void* m_taskWithScheduledBreak = nullptr;
// If any of the following three is true, we schedule pause on next JS
// execution using SetBreakOnNextFunctionCall.
bool m_externalAsyncTaskPauseRequested = false; // External async task.
bool m_taskWithScheduledBreakPauseRequested = false; // Local async task.
bool m_pauseOnNextCallRequested = false; // setPauseOnNextCall API call.
v8::debug::ExceptionBreakState m_pauseOnExceptionsState;
// Whether we should pause on async call execution (if any) while stepping in.
// See Debugger.stepInto for details.
bool m_pauseOnAsyncCall = false;
using StackTraceIdToStackTrace =
std::unordered_map<uintptr_t, std::weak_ptr<AsyncStackTrace>>;
StackTraceIdToStackTrace m_storedStackTraces;
uintptr_t m_lastStackTraceId = 0;
std::unordered_map<int, V8DebuggerId> m_contextGroupIdToDebuggerId;
std::unique_ptr<TerminateExecutionCallback> m_terminateExecutionCallback;
};
} // namespace v8_inspector
#endif // V8_INSPECTOR_V8_DEBUGGER_H_