Add support for breakpoints in the Devtools Debugger

- Add, remove breakpoints
- Resume execution
- Step Into, Step Out, Step Over
- Deactivate/Reactivate all breakpoints

Signed-off-by: Máté Tokodi <mate.tokodi@szteszoftver.hu>
This commit is contained in:
Máté Tokodi 2026-04-24 19:25:05 +02:00
commit 74cc8347e3
7 changed files with 564 additions and 239 deletions

View file

@ -20,11 +20,14 @@
#ifndef __Debugger__
#define __Debugger__
#include "runtime/Environment.h"
#include "util/Vector.h"
#ifdef ESCARGOT_DEBUGGER
namespace Escargot {
class ByteCode;
#define ESCARGOT_DEBUGGER_MAX_STACK_TRACE_LENGTH 8
/* WebSocket max length encoded in one byte. */
@ -57,6 +60,17 @@ public:
uint32_t offset; // bytecode offset
};
struct BreakpointByteCodeLocation {
BreakpointByteCodeLocation(const uint32_t line, ByteCode* breakpointByteCode)
: line(line)
, byteCode(breakpointByteCode)
{
}
uint32_t line; // source code line
ByteCode* byteCode; // bytecode pointer
};
typedef std::vector<BreakpointLocation> BreakpointLocationVector;
struct BreakpointLocationsInfo {
@ -150,6 +164,24 @@ public:
}
}
static LexicalEnvironment* getFunctionLexEnv(ExecutionState* state)
{
LexicalEnvironment* lexEnv = state->lexicalEnvironment();
while (lexEnv) {
EnvironmentRecord* record = lexEnv->record();
if (record->isDeclarativeEnvironmentRecord()
&& record->asDeclarativeEnvironmentRecord()->isFunctionEnvironmentRecord()) {
return lexEnv;
}
lexEnv = lexEnv->outerEnvironment();
}
return nullptr;
}
void setStopState(ExecutionState* stopState)
{
m_stopState = stopState;

View file

@ -16,14 +16,18 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
#include <memory>
#include <string>
#include <stdexcept>
#include "Escargot.h"
#include "DebuggerTcp.h"
#include "DebuggerDevtools.h"
#include "DebuggerHttpRouter.h"
#include "DebuggerDevtoolsMessageBuilder.h"
#include "interpreter/ByteCode.h"
#include "parser/Script.h"
#include "rapidjson/document.h"
#include "rapidjson/prettywriter.h"
#include "rapidjson/stringbuffer.h"
@ -33,15 +37,44 @@
namespace Escargot {
bool DebuggerDevtools::sendMessage(const std::string& msg, const int length)
template <typename... Args>
std::string string_format(const std::string& format, Args... args)
{
const int size_s = std::snprintf(nullptr, 0, format.c_str(), args...) + 1; // Extra space for '\0'
if (size_s <= 0) {
throw std::runtime_error("Error during formatting.");
}
const auto size = static_cast<size_t>(size_s);
const std::unique_ptr<char[]> buf(new char[size]);
std::snprintf(buf.get(), size, format.c_str(), args...);
return { buf.get(), buf.get() + size - 1 }; // We don't want the '\0' inside
}
bool DebuggerDevtools::sendMessage(const std::string& msg, const size_t length)
{
if (UNLIKELY(!m_networkEnabled || !m_debuggerEnabled || !m_runtimeEnabled)) {
m_pendingMessages.emplace_back(msg);
return true;
}
ESCARGOT_LOG_INFO("Sending message: %s\n", msg.c_str());
return send(0, msg.c_str(), length == -1 ? msg.length() : length);
const bool result = send(0, msg.c_str(), length == static_cast<size_t>(-1) ? msg.length() : length);
if (result) {
ESCARGOT_LOG_INFO("Sent message: %s\n", msg.c_str());
} else {
ESCARGOT_LOG_ERROR("Error sending message!");
}
return result;
}
bool DebuggerDevtools::sendJSONDocument(const rapidjson::Document& document)
{
rapidjson::StringBuffer sb;
rapidjson::Writer<rapidjson::StringBuffer> writer(sb);
document.Accept(writer);
const char* jsonReplyString = sb.GetString();
return sendMessage(jsonReplyString);
}
void DebuggerDevtools::init(const char* options, Context* context)
@ -83,7 +116,19 @@ uint8_t DebuggerDevtools::registerScript(String* source, String* srcName)
return newId;
}
void DebuggerDevtools::parseCompleted(String* source, String* srcName, size_t originLineOffset, String* error)
static void computeEndLocation(const LChar* src, size_t length, uint32_t& endLine, uint32_t& endColumn)
{
for (size_t i = 0; i < length; i++) {
if (src[i] == '\n') {
endLine++;
endColumn = 0;
} else {
endColumn++;
}
}
}
void DebuggerDevtools::parseCompleted(String* source, String* srcName, const size_t originLineOffset, String* error)
{
if (!enabled()) {
return;
@ -94,44 +139,121 @@ void DebuggerDevtools::parseCompleted(String* source, String* srcName, size_t or
return;
}
uint8_t scriptId = registerScript(source, srcName);
const uint8_t scriptId = registerScript(source, srcName);
std::set<BreakpointByteCodeLocation, decltype(compareBreakpointLocations)*> breakpointLocationsSet(compareBreakpointLocations);
m_breakpointInfo.emplace(scriptId, breakpointLocationsSet);
sendMessage(DebuggerDevtoolsMessageBuilder::buildScriptParsedMessage(scriptId, source, srcName));
const size_t breakpointLocationsSize = m_breakpointLocationsVector.size();
if (originLineOffset > 0) {
for (size_t i = 0; i < breakpointLocationsSize; i++) {
// adjust line offset for manipulated source code
// inserted breakpoint's line info should be bigger than `originLineOffset`
BreakpointLocationVector& locationVector = m_breakpointLocationsVector[i]->breakpointLocations;
for (auto& j : locationVector) {
ASSERT(j.line > originLineOffset);
j.line -= originLineOffset;
}
}
}
for (size_t i = 0; i < breakpointLocationsSize; i++) {
/* function bytecode information */
InterpretedCodeBlock* codeBlock = reinterpret_cast<InterpretedCodeBlock*>(m_breakpointLocationsVector[i]->weakCodeRef);
uint8_t* byteCodeStart = codeBlock->byteCodeBlock()->m_code.data();
/* save breakpoint locations. */
BreakpointLocationVector breakpointLocations = m_breakpointLocationsVector[i]->breakpointLocations;
for (auto& breakpointLocation : breakpointLocations) {
auto b = BreakpointByteCodeLocation(breakpointLocation.line, reinterpret_cast<ByteCode*>(byteCodeStart + breakpointLocation.offset));
b.byteCode->m_loc.line = breakpointLocation.line;
m_breakpointInfo[scriptId].insert(b);
}
}
rapidjson::Document reply;
reply.SetObject();
rapidjson::Value method(rapidjson::kStringType);
rapidjson::Value paramsObject(rapidjson::kObjectType);
rapidjson::Value resolvedBreakpointsArray(rapidjson::kArrayType);
reply.AddMember("method", "Debugger.scriptParsed", reply.GetAllocator());
reply.AddMember("params", paramsObject, reply.GetAllocator());
rapidjson::Value scriptIdValue;
std::string scriptIdString = string_format("%d", scriptId);
scriptIdValue.SetString(scriptIdString.c_str(), scriptIdString.length(), reply.GetAllocator());
reply["params"].AddMember("scriptId", scriptIdValue, reply.GetAllocator());
rapidjson::Value urlString;
std::string url = reinterpret_cast<const char*>(srcName->characters8());
urlString.SetString(url.c_str(), url.length(), reply.GetAllocator());
reply["params"].AddMember("url", urlString, reply.GetAllocator());
reply["params"].AddMember("startLine", 0, reply.GetAllocator());
reply["params"].AddMember("startColumn", 0, reply.GetAllocator());
uint32_t endLine = 0;
uint32_t endColumn = 0;
computeEndLocation(source->characters8(), source->length(), endLine, endColumn);
reply["params"].AddMember("endLine", endLine, reply.GetAllocator());
reply["params"].AddMember("endColumn", endColumn, reply.GetAllocator());
reply["params"].AddMember("executionContextId", 1, reply.GetAllocator());
sendJSONDocument(reply);
}
void DebuggerDevtools::sendPausedEvent(ByteCodeBlock* byteCodeBlock, uint32_t offset, ExecutionState* state)
void DebuggerDevtools::sendPausedEvent(ByteCodeBlock* byteCodeBlock, const uint32_t offset, ExecutionState* state, const bool breakpoint)
{
// TODO: Placeholder info
const char* msg = "{\"method\":\"Debugger.paused\","
"\"params\":{"
"\"callFrames\":[{"
"\"callFrameId\":\"frame:0\","
"\"functionName\":\"\","
"\"location\":{"
"\"scriptId\":\"1\","
"\"lineNumber\":0,"
"\"columnNumber\":0"
"},"
"\"url\":\"hello.js\","
"\"scopeChain\":[{"
"\"type\":\"global\","
"\"object\":{"
"\"type\":\"object\","
"\"className\":\"global\","
"\"description\":\"global\","
"\"objectId\":\"global:1\""
"}"
"}],"
"\"this\":{"
"\"type\":\"undefined\""
"}"
"}],"
"\"reason\":\"breakpoint\","
"\"hitBreakpoints\":[\"breakpoint:1\"]"
"}"
"}";
const auto* byteCode = reinterpret_cast<ByteCode*>(byteCodeBlock->m_code.data() + offset);
const auto* filename = reinterpret_cast<const char*>(byteCodeBlock->codeBlock()->script()->srcName()->characters8());
const uint8_t scripId = m_scriptIdByUrl[reinterpret_cast<const char*>(byteCodeBlock->codeBlock()->script()->srcName()->characters8())];
sendMessage(msg);
const uint64_t line = byteCode->m_loc.line - 1; // chrome starts line indexes at 0
const uint64_t column = byteCode->m_loc.column - 1; // chrome starts column indexes at 0
// TODO: some placeholder info
const std::string msg = string_format("{\"method\":\"Debugger.paused\","
"\"params\":{"
"\"callFrames\":[{"
"\"callFrameId\":\"frame:0\","
"\"functionName\":\"%s\","
"\"location\":{"
"\"scriptId\":\"%d\","
"\"lineNumber\":%lu,"
"\"columnNumber\":%lu"
"},"
"\"url\":\"%s\","
"\"scopeChain\":[{"
"\"type\":\"global\","
"\"object\":{"
"\"type\":\"object\","
"\"className\":\"global\","
"\"description\":\"global\","
"\"objectId\":\"global:1\""
"}"
"}],"
"\"this\":{"
"\"type\":\"undefined\""
"}"
"}],"
"\"reason\":\"%s\","
"\"hitBreakpoints\":[\"%s:%lu:%lu\"]"
"}"
"}",
reinterpret_cast<const char*>(byteCodeBlock->codeBlock()->functionName().string()->characters8()),
scripId,
line,
column,
filename,
breakpoint ? "Breakpoint" : "Break on start",
filename,
line,
column);
sendMessage(msg, msg.length());
}
void DebuggerDevtools::stopAtBreakpoint(ByteCodeBlock* byteCodeBlock, uint32_t offset, ExecutionState* state)
@ -144,8 +266,11 @@ void DebuggerDevtools::stopAtBreakpoint(ByteCodeBlock* byteCodeBlock, uint32_t o
return;
}
uint8_t* byteCodeStart = byteCodeBlock->m_code.data();
sendPausedEvent(byteCodeBlock, offset, state);
sendPausedEvent(byteCodeBlock, offset, state, !m_startBreakpoint);
if (m_startBreakpoint) {
m_startBreakpoint = false;
}
if (!enabled()) {
return;
@ -191,7 +316,7 @@ constexpr MessageType messageType(const char (&methodName)[N], const MessageHand
return MessageType{ methodName, handler };
}
bool DebuggerDevtools::resume(rapidjson::Document& jsonMessage)
bool DebuggerDevtools::resume(rapidjson::Document& jsonMessage, ExecutionState* state)
{
replyOK(jsonMessage);
@ -202,126 +327,360 @@ bool DebuggerDevtools::resume(rapidjson::Document& jsonMessage)
return false;
}
bool DebuggerDevtools::sendProperties(rapidjson::Document& jsonMessage)
bool DebuggerDevtools::stepInto(rapidjson::Document& jsonMessage, ExecutionState* state)
{
uint32_t requestId = jsonMessage["id"].GetUint();
replyOK(jsonMessage);
char buffer[4096];
if (m_stopState == ESCARGOT_DEBUGGER_IN_EVAL_MODE) {
return true;
}
m_stopState = ESCARGOT_DEBUGGER_ALWAYS_STOP;
return false;
}
bool DebuggerDevtools::stepOver(rapidjson::Document& jsonMessage, ExecutionState* state)
{
replyOK(jsonMessage);
if (m_stopState != ESCARGOT_DEBUGGER_IN_WAIT_MODE) {
return true;
}
m_stopState = state;
return false;
}
bool DebuggerDevtools::stepOut(rapidjson::Document& jsonMessage, ExecutionState* state)
{
replyOK(jsonMessage);
if (m_stopState != ESCARGOT_DEBUGGER_IN_WAIT_MODE) {
return true;
}
const LexicalEnvironment* lexEnv = getFunctionLexEnv(state);
if (!lexEnv) {
m_stopState = nullptr;
return false;
}
ExecutionState* stopState = state->parent();
while (stopState && getFunctionLexEnv(stopState) == lexEnv) {
stopState = stopState->parent();
}
m_stopState = stopState;
return false;
}
bool DebuggerDevtools::sendProperties(rapidjson::Document& jsonMessage, ExecutionState* state)
{
// TODO: placeholder info
snprintf(buffer, sizeof(buffer),
"{\"id\":%u,\"result\":{"
"\"result\":["
"{"
"\"name\":\"print\","
"\"configurable\":true,"
"\"enumerable\":true,"
"\"value\":{"
"\"type\":\"function\","
"\"description\":\"function print()\""
"}"
"},"
"{"
"\"name\":\"c\","
"\"configurable\":true,"
"\"enumerable\":true,"
"\"value\":{"
"\"type\":\"number\","
"\"value\":8,"
"\"description\":\"8\""
"}"
"},"
"{"
"\"name\":\"globalVar\","
"\"configurable\":true,"
"\"enumerable\":true,"
"\"value\":{"
"\"type\":\"string\","
"\"value\":\"escargot\","
"\"description\":\"escargot\""
"}"
"}"
"]"
"}}",
requestId);
const std::string msg = string_format("{\"id\":%u,\"result\":{"
"\"result\":["
"{"
"\"name\":\"print\","
"\"configurable\":true,"
"\"enumerable\":true,"
"\"value\":{"
"\"type\":\"function\","
"\"description\":\"function print()\""
"}"
"},"
"{"
"\"name\":\"c\","
"\"configurable\":true,"
"\"enumerable\":true,"
"\"value\":{"
"\"type\":\"number\","
"\"value\":8,"
"\"description\":\"8\""
"}"
"},"
"{"
"\"name\":\"globalVar\","
"\"configurable\":true,"
"\"enumerable\":true,"
"\"value\":{"
"\"type\":\"string\","
"\"value\":\"escargot\","
"\"description\":\"escargot\""
"}"
"}"
"]"
"}}",
jsonMessage["id"].GetUint());
sendMessage(buffer);
sendMessage(msg, msg.length());
return true;
}
bool DebuggerDevtools::sendSourceCode(rapidjson::Document& jsonMessage)
bool DebuggerDevtools::sendSourceCode(rapidjson::Document& jsonMessage, ExecutionState* state)
{
const uint32_t requestId = jsonMessage["id"].GetUint();
const uint32_t scriptId = std::stoi(jsonMessage["params"]["scriptId"].GetString());
auto it = m_scriptsById.find(scriptId);
const auto it = m_scriptsById.find(scriptId);
if (it == m_scriptsById.end()) {
return false;
}
String* source = it->second.source;
const String* source = it->second.source;
if (!source->is8Bit()) {
ESCARGOT_LOG_ERROR("Only 8 bit characters are supported right now...");
return false;
}
std::string message = DebuggerDevtoolsMessageBuilder::buildSourceCodeMessage(requestId, source);
const std::string message = DebuggerDevtoolsMessageBuilder::buildSourceCodeMessage(requestId, source);
return sendMessage(message);
}
bool DebuggerDevtools::replyOK(rapidjson::Document& jsonMessage)
bool DebuggerDevtools::replyOK(rapidjson::Document& jsonMessage, ExecutionState* state)
{
char reply[32];
const int jsonReplyStringLength = snprintf(reply, sizeof(reply),
R"({"id": %d, "result": {}})",
jsonMessage["id"].GetInt());
const std::string jsonReplyString = string_format(R"({"id": %d, "result": {}})",
jsonMessage["id"].GetInt());
sendMessage(reply, jsonReplyStringLength);
sendMessage(jsonReplyString, jsonReplyString.length());
return true;
}
bool DebuggerDevtools::enableNetwork(rapidjson::Document& jsonMessage)
bool DebuggerDevtools::enableNetwork(rapidjson::Document& jsonMessage, ExecutionState* state)
{
this->m_networkEnabled = true;
return replyOK(jsonMessage);
}
bool DebuggerDevtools::enableDebugger(rapidjson::Document& jsonMessage)
bool DebuggerDevtools::enableDebugger(rapidjson::Document& jsonMessage, ExecutionState* state)
{
this->m_debuggerEnabled = true;
return replyOK(jsonMessage);
rapidjson::Document reply;
reply.SetObject();
reply.AddMember("id", jsonMessage["id"], reply.GetAllocator());
rapidjson::Value result(rapidjson::kObjectType);
reply.AddMember("result", result, reply.GetAllocator());
rapidjson::Value debuggerIdValue;
std::string debuggerIdString = string_format("escargot-%d", rand() % 1000000);
debuggerIdValue.SetString(debuggerIdString.c_str(), debuggerIdString.length(), reply.GetAllocator());
reply["result"].AddMember("debuggerId", debuggerIdValue, reply.GetAllocator());
return sendJSONDocument(reply);
}
bool DebuggerDevtools::enableRuntime(rapidjson::Document& jsonMessage)
bool DebuggerDevtools::enableRuntime(rapidjson::Document& jsonMessage, ExecutionState* state)
{
this->m_runtimeEnabled = true;
return replyOK(jsonMessage);
}
bool DebuggerDevtools::enableProfiler(rapidjson::Document& jsonMessage)
bool DebuggerDevtools::enableProfiler(rapidjson::Document& jsonMessage, ExecutionState* state)
{
this->m_profilerEnabled = true;
return replyOK(jsonMessage);
}
bool DebuggerDevtools::setPauseOnExceptions(rapidjson::Document& jsonMessage)
bool DebuggerDevtools::setPauseOnExceptions(rapidjson::Document& jsonMessage, ExecutionState* state)
{
// TODO: handdle other parameters: state: none (unset), uncaught, caught, all
this->m_pauseOnExceptions = true;
return replyOK(jsonMessage);
}
bool DebuggerDevtools::replyMethodNotFound(rapidjson::Document& jsonMessage)
void setBreakpointState(ByteCode* breakpoint, const bool enabled)
{
char reply[256];
#if defined(ESCARGOT_COMPUTED_GOTO_INTERPRETER)
if (enabled) {
if (breakpoint->m_opcodeInAddress != g_opcodeTable.m_addressTable[BreakpointDisabledOpcode]) {
return;
}
breakpoint->m_opcodeInAddress = g_opcodeTable.m_addressTable[BreakpointEnabledOpcode];
} else {
if (breakpoint->m_opcodeInAddress != g_opcodeTable.m_addressTable[BreakpointEnabledOpcode]) {
return;
}
breakpoint->m_opcodeInAddress = g_opcodeTable.m_addressTable[BreakpointDisabledOpcode];
}
#else
if (enabled) {
if (breakpoint->m_opcode != BreakpointDisabledOpcode) {
return;
}
breakpoint->m_opcode = BreakpointEnabledOpcode;
} else {
if (breakpoint->m_opcode != BreakpointEnabledOpcode) {
return;
}
breakpoint->m_opcode = BreakpointDisabledOpcode;
}
#endif
}
const int jsonReplyStringLength = snprintf(reply, sizeof(reply),
R"({"id":%d,"error":{"code":-32601,"message":"'%s' wasn't found"}})",
jsonMessage["id"].GetInt(),
jsonMessage["method"].GetString());
bool DebuggerDevtools::setBreakpointsActive(rapidjson::Document& jsonMessage, ExecutionState* state)
{
this->m_breakpointsActive = jsonMessage["params"]["active"].GetBool();
sendMessage(reply, jsonReplyStringLength);
for (ByteCode* breakpointBytecode : m_setBreakPoints) {
setBreakpointState(breakpointBytecode, m_breakpointsActive);
}
return replyOK(jsonMessage);
}
bool DebuggerDevtools::setBreakpointByUrl(rapidjson::Document& jsonMessage, ExecutionState* state)
{
const std::string breakpointCondition = jsonMessage["params"]["condition"].GetString();
if (!breakpointCondition.empty()) {
ESCARGOT_LOG_ERROR("Warning: Breakpoint conditions are not supported!");
}
std::string breakpointFile = jsonMessage["params"]["url"].GetString();
if (breakpointFile.find("file://") == 0) {
breakpointFile.erase(0, 7);
}
const uint8_t scriptId = this->m_scriptIdByUrl[breakpointFile];
std::string scriptIdString = string_format("%d", scriptId);
const uint32_t lineNumber = jsonMessage["params"]["lineNumber"].GetUint() + 1; // chrome starts line indexes at 0
const uint32_t columnNumber = jsonMessage["params"]["columnNumber"].GetUint() + 1; // chrome starts column indexes at 0
rapidjson::Document reply;
reply.SetObject();
rapidjson::Value resultObject(rapidjson::kObjectType);
rapidjson::Value resultArray(rapidjson::kArrayType);
rapidjson::Value breakpointID(rapidjson::kStringType);
reply.AddMember("id", jsonMessage["id"].GetInt(), reply.GetAllocator());
reply.AddMember("result", resultObject, reply.GetAllocator());
reply["result"].AddMember("locations", resultArray, reply.GetAllocator());
for (BreakpointByteCodeLocation breakpointInfo : m_breakpointInfo[scriptId]) {
if ((lineNumber == breakpointInfo.line && (columnNumber == breakpointInfo.byteCode->m_loc.column || columnNumber == 1))
|| (reply["result"]["locations"].Empty() && breakpointInfo.line > lineNumber)) {
rapidjson::Value locationObject(rapidjson::kObjectType);
rapidjson::Value scriptIdValue;
scriptIdValue.SetString(scriptIdString.c_str(), scriptIdString.length(), reply.GetAllocator());
locationObject.AddMember("scriptId", scriptIdValue, reply.GetAllocator());
locationObject.AddMember("lineNumber", breakpointInfo.line - 1, reply.GetAllocator());
locationObject.AddMember("columnNumber", breakpointInfo.byteCode->m_loc.column - 1, reply.GetAllocator());
reply["result"]["locations"].PushBack(locationObject, reply.GetAllocator());
std::string breakpointIdString = string_format("%s:%d:%lu", breakpointFile.c_str(), breakpointInfo.line, breakpointInfo.byteCode->m_loc.column);
breakpointID.SetString(breakpointIdString.c_str(), breakpointIdString.length(), reply.GetAllocator());
reply["result"].AddMember("breakpointId", breakpointID, reply.GetAllocator());
/* Enable breakpoint */
setBreakpointState(breakpointInfo.byteCode, true);
m_setBreakPoints.emplace(breakpointInfo.byteCode);
break;
}
}
const auto ret = sendJSONDocument(reply);
return ret;
}
bool DebuggerDevtools::removeBreakpoint(rapidjson::Document& jsonMessage, ExecutionState* state)
{
const std::string breakpointId = jsonMessage["params"]["breakpointId"].GetString();
std::stringstream ss(breakpointId);
std::string breakpointIdInfo[3];
constexpr char delim = ':';
int i = 0;
while (std::getline(ss, breakpointIdInfo[i], delim)) {
++i;
}
ASSERT(i == 3);
const std::string breakpointFile = breakpointIdInfo[0];
const uint32_t lineNumber = stoi(breakpointIdInfo[1]);
const uint32_t columnNumber = stoi(breakpointIdInfo[2]);
const uint8_t scriptId = this->m_scriptIdByUrl[breakpointFile];
for (BreakpointByteCodeLocation breakpointInfo : m_breakpointInfo[scriptId]) {
if (lineNumber == breakpointInfo.line && columnNumber == breakpointInfo.byteCode->m_loc.column) {
/* Disable breakpoint */
setBreakpointState(breakpointInfo.byteCode, false);
m_setBreakPoints.erase(breakpointInfo.byteCode);
break;
}
}
return replyOK(jsonMessage);
}
bool DebuggerDevtools::sendPossibleBreakpoints(rapidjson::Document& jsonMessage, ExecutionState* state)
{
if (jsonMessage["params"]["restrictToFunction"].GetBool()) {
ESCARGOT_LOG_ERROR("Warning: restrictToFunction is not supported\n");
}
std::string scriptIdString = jsonMessage["params"]["start"]["scriptId"].GetString();
const uint8_t scriptId = std::stoi(scriptIdString);
if (m_breakpointInfo.find(scriptId) == m_breakpointInfo.end()) {
ESCARGOT_LOG_ERROR("Script Id not found: %d", scriptId);
return replyOK(jsonMessage);
}
if (scriptId != std::stoi(jsonMessage["params"]["end"]["scriptId"].GetString())) {
ESCARGOT_LOG_ERROR("Error: Script ranges across multiple scripts not supported!\n");
return replyMethodNotFound(jsonMessage);
}
const uint32_t startLine = jsonMessage["params"]["start"]["lineNumber"].GetUint() + 1; // chrome starts line indexes at 0
const uint32_t startColumn = jsonMessage["params"]["start"]["columnNumber"].GetUint() + 1; // chrome starts column indexes at 0
const uint32_t endLine = jsonMessage["params"]["end"]["lineNumber"].GetUint() + 1; // chrome starts line indexes at 0
const uint32_t endColumn = jsonMessage["params"]["end"]["columnNumber"].GetUint() + 1; // chrome starts column indexes at 0
rapidjson::Document reply;
reply.SetObject();
rapidjson::Value resultObject(rapidjson::kObjectType);
rapidjson::Value resultArray(rapidjson::kArrayType);
reply.AddMember("id", jsonMessage["id"].GetInt(), reply.GetAllocator());
reply.AddMember("result", resultObject, reply.GetAllocator());
reply["result"].AddMember("locations", resultArray, reply.GetAllocator());
for (BreakpointByteCodeLocation breakpointInfo : m_breakpointInfo[scriptId]) {
if (((startLine <= breakpointInfo.line && breakpointInfo.line <= endLine)
&& startColumn <= breakpointInfo.byteCode->m_loc.column && breakpointInfo.byteCode->m_loc.column <= endColumn)
|| (reply["result"]["locations"].Empty() && breakpointInfo.line > startLine && breakpointInfo.line > endLine)) {
rapidjson::Value locationObject(rapidjson::kObjectType);
rapidjson::Value scriptIdValue;
scriptIdValue.SetString(scriptIdString.c_str(), scriptIdString.length(), reply.GetAllocator());
locationObject.AddMember("scriptId", scriptIdValue, reply.GetAllocator());
locationObject.AddMember("lineNumber", breakpointInfo.line - 1, reply.GetAllocator());
locationObject.AddMember("columnNumber", breakpointInfo.byteCode->m_loc.column - 1, reply.GetAllocator());
reply["result"]["locations"].PushBack(locationObject, reply.GetAllocator());
}
}
return sendJSONDocument(reply);
}
bool DebuggerDevtools::replyMethodNotFound(rapidjson::Document& jsonMessage, ExecutionState* state)
{
const std::string jsonReplyString = string_format(R"({"id":%d,"error":{"code":-32601,"message":"'%s' wasn't found"}})",
jsonMessage["id"].GetInt(), jsonMessage["method"].GetString());
sendMessage(jsonReplyString, jsonReplyString.length());
return true;
}
@ -334,11 +693,18 @@ bool DebuggerDevtools::processEvents(ExecutionState* state, Optional<ByteCodeBlo
// NOTE: keep sorted
static constexpr MessageType messageTypes[] = {
messageType("Debugger.enable", &DebuggerDevtools::enableDebugger),
messageType("Debugger.getPossibleBreakpoints", &DebuggerDevtools::sendPossibleBreakpoints),
messageType("Debugger.getScriptSource", &DebuggerDevtools::sendSourceCode),
messageType("Debugger.removeBreakpoint", &DebuggerDevtools::removeBreakpoint),
messageType("Debugger.resume", &DebuggerDevtools::resume),
messageType("Debugger.setAsyncCallStackDepth", &DebuggerDevtools::replyOK), // we may be able to set something for this one
messageType("Debugger.setBlackboxPatterns", &DebuggerDevtools::replyOK), // we ignore this for now, but if needed set skipSourceName in DebuggerTcp
messageType("Debugger.setBreakpointByUrl", &DebuggerDevtools::setBreakpointByUrl),
messageType("Debugger.setBreakpointsActive", &DebuggerDevtools::setBreakpointsActive),
messageType("Debugger.setPauseOnExceptions", &DebuggerDevtools::setPauseOnExceptions),
messageType("Debugger.stepInto", &DebuggerDevtools::stepInto),
messageType("Debugger.stepOut", &DebuggerDevtools::stepOut),
messageType("Debugger.stepOver", &DebuggerDevtools::stepOver),
messageType("Network.clearAcceptedEncodingsOverride", &DebuggerDevtools::replyMethodNotFound),
messageType("Network.emulateNetworkConditionsByRule", &DebuggerDevtools::replyMethodNotFound),
messageType("Network.enable", &DebuggerDevtools::enableNetwork),
@ -348,7 +714,7 @@ bool DebuggerDevtools::processEvents(ExecutionState* state, Optional<ByteCodeBlo
messageType("Profiler.enable", &DebuggerDevtools::enableProfiler),
messageType("Runtime.enable", &DebuggerDevtools::enableRuntime),
messageType("Runtime.getProperties", &DebuggerDevtools::sendProperties),
messageType("Runtime.runIfWaitingForDebugger", &DebuggerDevtools::resume),
messageType("Runtime.runIfWaitingForDebugger", &DebuggerDevtools::replyOK),
};
while (true) {
@ -389,7 +755,7 @@ bool DebuggerDevtools::processEvents(ExecutionState* state, Optional<ByteCodeBlo
continue;
}
return (this->*message.handler)(jsonMessage);
return (this->*message.handler)(jsonMessage, state);
}
ESCARGOT_LOG_ERROR("Debugger function not supported: %s\n", reinterpret_cast<const char*>(buffer));
this->replyMethodNotFound(jsonMessage); // maybe reply with not supported instead? if there is such a reply in the spec

View file

@ -22,6 +22,7 @@
#include "Debugger.h"
#include "DebuggerTcp.h"
#include "interpreter/ByteCode.h"
#include "rapidjson/document.h"
#include "rapidjson/rapidjson.h"
@ -48,7 +49,8 @@ public:
{
}
bool sendMessage(const std::string& msg, const int length = -1);
bool sendMessage(const std::string& msg, size_t length = -1);
bool sendJSONDocument(const rapidjson::Document& document);
void init(const char* options, Context* context) override;
bool skipSourceCode(String* srcName) const override;
@ -61,22 +63,37 @@ public:
bool getWaitBeforeExitClient() override;
void sendPausedEvent(ByteCodeBlock* byteCodeBlock, uint32_t offset, ExecutionState* state);
void sendPausedEvent(ByteCodeBlock* byteCodeBlock, uint32_t offset, ExecutionState* state, bool breakpoint = false);
protected:
bool processEvents(ExecutionState* state, Optional<ByteCodeBlock*> byteCodeBlock, bool isBlockingRequest = true) override;
private:
bool sendProperties(rapidjson::Document& jsonMessage);
bool resume(rapidjson::Document& jsonMessage);
bool sendSourceCode(rapidjson::Document& jsonMessage);
bool replyOK(rapidjson::Document& jsonMessage);
bool enableNetwork(rapidjson::Document& jsonMessage);
bool enableDebugger(rapidjson::Document& jsonMessage);
bool enableRuntime(rapidjson::Document& jsonMessage);
bool enableProfiler(rapidjson::Document& jsonMessage);
bool setPauseOnExceptions(rapidjson::Document& jsonMessage);
bool replyMethodNotFound(rapidjson::Document& jsonMessage);
bool sendProperties(rapidjson::Document& jsonMessage, ExecutionState* state = nullptr);
bool resume(rapidjson::Document& jsonMessage, ExecutionState* state = nullptr);
bool stepOver(rapidjson::Document& jsonMessage, ExecutionState* state = nullptr);
bool stepOut(rapidjson::Document& jsonMessage, ExecutionState* state);
bool stepInto(rapidjson::Document& jsonMessage, ExecutionState* state = nullptr);
bool sendSourceCode(rapidjson::Document& jsonMessage, ExecutionState* state = nullptr);
bool replyOK(rapidjson::Document& jsonMessage, ExecutionState* state = nullptr);
bool enableNetwork(rapidjson::Document& jsonMessage, ExecutionState* state = nullptr);
bool enableDebugger(rapidjson::Document& jsonMessage, ExecutionState* state = nullptr);
bool enableRuntime(rapidjson::Document& jsonMessage, ExecutionState* state = nullptr);
bool enableProfiler(rapidjson::Document& jsonMessage, ExecutionState* state = nullptr);
bool setPauseOnExceptions(rapidjson::Document& jsonMessage, ExecutionState* state = nullptr);
bool setBreakpointsActive(rapidjson::Document& jsonMessage, ExecutionState* state = nullptr);
bool setBreakpointByUrl(rapidjson::Document& jsonMessage, ExecutionState* state = nullptr);
bool removeBreakpoint(rapidjson::Document& jsonMessage, ExecutionState* state = nullptr);
bool sendPossibleBreakpoints(rapidjson::Document& jsonMessage, ExecutionState* state = nullptr);
bool replyMethodNotFound(rapidjson::Document& jsonMessage, ExecutionState* state = nullptr);
static bool compareBreakpointLocations(const BreakpointByteCodeLocation& a, const BreakpointByteCodeLocation& b)
{
if (LIKELY(a.line != b.line)) {
return a.line < b.line;
}
return a.byteCode->m_loc.column <= b.byteCode->m_loc.column;
}
uint8_t registerScript(String* url, String* source);
@ -85,15 +102,19 @@ private:
bool m_runtimeEnabled = false;
bool m_profilerEnabled = false;
bool m_pauseOnExceptions = false;
bool m_breakpointsActive = false;
bool m_startBreakpoint = true;
std::unordered_map<uint8_t, ScriptInfo> m_scriptsById;
std::unordered_map<std::string, uint8_t> m_scriptIdByUrl;
uint8_t m_nextScriptId = 1;
std::unordered_map<uint8_t, std::set<BreakpointByteCodeLocation, decltype(compareBreakpointLocations)*>> m_breakpointInfo;
std::vector<std::string> m_pendingMessages;
std::set<ByteCode*> m_setBreakPoints; // stores set breakpoints for enabling/disabling in bulk with the `Deactivate Breakpoints` button
};
using MessageHandler = bool (DebuggerDevtools::*)(rapidjson::Document&);
using MessageHandler = bool (DebuggerDevtools::*)(rapidjson::Document&, ExecutionState* state);
struct MessageType {
const char* methodName;

View file

@ -24,57 +24,12 @@
#include "Escargot.h"
#include "runtime/String.h" // for split function
#include "interpreter/ByteCode.h"
#include "rapidjson/document.h"
#include "rapidjson/prettywriter.h"
#include "rapidjson/stringbuffer.h"
namespace Escargot {
static std::string jsonEscape(const LChar* src, size_t len)
{
std::string out;
out.reserve(len + 16);
for (size_t i = 0; i < len; ++i) {
LChar c = src[i];
switch (c) {
case '\"':
out += "\\\"";
break;
case '\\':
out += "\\\\";
break;
case '\n':
out += "\\n";
break;
case '\r':
out += "\\r";
break;
case '\t':
out += "\\t";
break;
default:
out += c;
break;
}
}
return out;
}
static void computeEndLocation(const LChar* src, size_t length, uint32_t& endLine, uint32_t& endColumn)
{
for (size_t i = 0; i < length; i++) {
if (src[i] == '\n') {
endLine++;
endColumn = 0;
} else {
endColumn++;
}
}
}
std::string DebuggerDevtoolsMessageBuilder::buildEmptyMessage(const uint32_t id)
{
char buffer[64];
@ -89,47 +44,6 @@ std::string DebuggerDevtoolsMessageBuilder::buildEmptyMessage(const uint32_t id)
return { buffer, static_cast<size_t>(written) };
}
std::string DebuggerDevtoolsMessageBuilder::buildScriptParsedMessage(const uint8_t scriptId, const String* source, const String* srcName)
{
if (!source || !srcName || !source->is8Bit() || !srcName->is8Bit()) {
return {};
}
const LChar* sourceName = srcName->characters8();
const LChar* sourceCode = source->characters8();
size_t sourceNameLength = srcName->length();
size_t sourceCodeLength = source->length();
uint32_t endLine = 0;
uint32_t endColumn = 0;
computeEndLocation(sourceCode, sourceCodeLength, endLine, endColumn);
char buffer[512];
int written = snprintf(buffer, sizeof(buffer),
"{\"method\":\"Debugger.scriptParsed\","
"\"params\":{"
"\"scriptId\":\"%u\","
"\"url\":\"%.*s\","
"\"startLine\":0,"
"\"startColumn\":0,"
"\"endLine\":%u,"
"\"endColumn\":%u,"
"\"executionContextId\":1"
"}"
"}",
scriptId,
static_cast<int>(sourceNameLength),
reinterpret_cast<const char*>(sourceName),
endLine,
endColumn);
if (written < 0 || static_cast<size_t>(written) >= sizeof(buffer)) {
return {};
}
return { buffer, static_cast<size_t>(written) };
}
std::string DebuggerDevtoolsMessageBuilder::buildSourceCodeMessage(const uint8_t requestId, const String* source)
{
if (!source || !source->is8Bit()) {
@ -137,20 +51,23 @@ std::string DebuggerDevtoolsMessageBuilder::buildSourceCodeMessage(const uint8_t
}
const LChar* sourceCode = source->characters8();
size_t sourceCodeLength = source->length();
// Special characters in source code must be escaped.
std::string escapedSource = jsonEscape(sourceCode, sourceCodeLength);
const size_t sourceCodeLength = source->length();
rapidjson::StringBuffer sb;
rapidjson::Writer<rapidjson::StringBuffer> writer(sb);
writer.String(reinterpret_cast<const rapidjson::Writer<rapidjson::GenericStringBuffer<rapidjson::UTF8<>>>::Ch*>(sourceCode), sourceCodeLength);
const std::string escaped = sb.GetString();
// FIXME: buffer size depends on length of source.
char buffer[4096];
int written = snprintf(buffer, sizeof(buffer),
"{\"id\":%u,"
"\"result\":{"
"\"scriptSource\":\"%s\""
"}"
"}",
requestId,
escapedSource.c_str());
const int written = snprintf(buffer, sizeof(buffer),
"{\"id\":%u,"
"\"result\":{"
"\"scriptSource\":%s"
"}"
"}",
requestId,
escaped.c_str());
if (written < 0 || static_cast<size_t>(written) >= sizeof(buffer)) {
return {};

View file

@ -755,23 +755,6 @@ void DebuggerEscargot::getScopeVariables(ExecutionState* state, uint32_t stateIn
sendSubtype(ESCARGOT_MESSAGE_VARIABLE, ESCARGOT_VARIABLE_END);
}
static LexicalEnvironment* getFunctionLexEnv(ExecutionState* state)
{
LexicalEnvironment* lexEnv = state->lexicalEnvironment();
while (lexEnv) {
EnvironmentRecord* record = lexEnv->record();
if (record->isDeclarativeEnvironmentRecord()
&& record->asDeclarativeEnvironmentRecord()->isFunctionEnvironmentRecord()) {
return lexEnv;
}
lexEnv = lexEnv->outerEnvironment();
}
return nullptr;
}
bool DebuggerEscargot::processEvents(ExecutionState* state, Optional<ByteCodeBlock*> byteCodeBlock, bool isBlockingRequest)
{
uint8_t buffer[ESCARGOT_DEBUGGER_MAX_MESSAGE_LENGTH];

View file

@ -207,7 +207,6 @@ bool DebuggerTcp::send(const uint8_t type, const void* buffer, const size_t leng
return false;
}
// ESCARGOT_LOG_INFO("Sent message: %s\n", static_cast<const char*>(buffer));
return true;
}

View file

@ -3308,12 +3308,19 @@ public:
context->m_locData->push_back(std::make_pair(start, idx));
}
#ifndef NDEBUG
const auto loc = computeNodeLOC(m_codeBlock->src(), m_codeBlock->functionStart(), idx);
ByteCodeLOC* bytecodeLoc = &reinterpret_cast<ByteCode*>(first)->m_loc;
bytecodeLoc->index = loc.index;
bytecodeLoc->line = loc.line;
bytecodeLoc->column = loc.column;
#endif
m_code.resizeWithUninitializedValues(m_code.size() + sizeof(CodeType));
for (size_t i = 0; i < sizeof(CodeType); i++) {
m_code[start++] = *first;
first++;
}
m_requiredOperandRegisterNumber = std::max(m_requiredOperandRegisterNumber, (ByteCodeRegisterIndex)context->m_baseRegisterCount);
// TODO throw exception