escargot/src/debugger/DebuggerEscargot.cpp
Ádám László Kulcsár d7b58a0937 Implement escargot debugger restart support
Implement restart in escargot and python debugger.
Also add debugger test.

Signed-off-by: Ádám László Kulcsár <adam.kulcsar@szteszoftver.hu>
2026-05-06 15:41:55 +02:00

1123 lines
38 KiB
C++

/*
* Copyright (c) 2016-present Samsung Electronics Co., Ltd
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
#include "Escargot.h"
#include "Debugger.h"
#include "DebuggerTcp.h"
#include "interpreter/ByteCode.h"
#include "runtime/Context.h"
#include "runtime/Environment.h"
#include "runtime/EnvironmentRecord.h"
#include "runtime/GlobalObject.h"
#include "runtime/SandBox.h"
#include "parser/Script.h"
#include "DebuggerEscargot.h"
#include "HeapSnapshot.h"
#ifdef ESCARGOT_DEBUGGER
namespace Escargot {
void DebuggerEscargot::sendType(uint8_t type)
{
send(type, nullptr, 0);
}
void DebuggerEscargot::sendSubtype(uint8_t type, uint8_t subType)
{
send(type, &subType, 1);
}
void DebuggerEscargot::sendString(uint8_t type, const String* string)
{
size_t length = string->length();
if (string->has8BitContent()) {
const LChar* chars = string->characters8();
const size_t maxMessageLength = ESCARGOT_DEBUGGER_MAX_MESSAGE_LENGTH - 1;
while (length > maxMessageLength) {
if (!send(type, chars, maxMessageLength)) {
return;
}
length -= maxMessageLength;
chars += maxMessageLength;
}
send(type + 1, chars, length);
return;
}
const char16_t* chars = string->characters16();
const size_t maxMessageLength = (ESCARGOT_DEBUGGER_MAX_MESSAGE_LENGTH - 1) / 2;
while (length > maxMessageLength) {
if (!send(type + 2, chars, maxMessageLength * 2)) {
return;
}
length -= maxMessageLength;
chars += maxMessageLength;
}
send(type + 2 + 1, chars, length * 2);
}
void DebuggerEscargot::sendPointer(uint8_t type, const void* ptr)
{
// The pointer itself is sent, not the data pointed by it
send(type, (void*)&ptr, sizeof(void*));
}
void DebuggerEscargot::parseCompleted(String* source, String* srcName, size_t originLineOffset, String* error)
{
if (!enabled()) {
return;
}
sendString(ESCARGOT_MESSAGE_SOURCE_8BIT, source);
if (!enabled()) {
return;
}
sendString(ESCARGOT_MESSAGE_FILE_NAME_8BIT, srcName);
if (!enabled()) {
return;
}
if (error != nullptr) {
sendType(ESCARGOT_MESSAGE_PARSE_ERROR);
if (enabled()) {
sendString(ESCARGOT_MESSAGE_STRING_8BIT, error);
}
return;
}
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 (size_t j = 0; j < locationVector.size(); j++) {
ASSERT(locationVector[j].line > originLineOffset);
locationVector[j].line -= originLineOffset;
}
}
}
for (size_t i = 0; i < breakpointLocationsSize; i++) {
/* Send breakpoint locations. */
const size_t maxPacketLength = (ESCARGOT_DEBUGGER_MAX_MESSAGE_LENGTH - 1) / sizeof(BreakpointLocation);
BreakpointLocation* ptr = m_breakpointLocationsVector[i]->breakpointLocations.data();
size_t length = m_breakpointLocationsVector[i]->breakpointLocations.size();
if (length == 0) {
// empty breakpoint info, so continue to the next breakpoint info
continue;
}
while (length > maxPacketLength) {
if (!send(ESCARGOT_MESSAGE_BREAKPOINT_LOCATION, ptr, maxPacketLength * sizeof(BreakpointLocation))) {
return;
}
ptr += maxPacketLength;
length -= maxPacketLength;
}
if (!send(ESCARGOT_MESSAGE_BREAKPOINT_LOCATION, ptr, length * sizeof(BreakpointLocation))) {
return;
}
InterpretedCodeBlock* codeBlock = reinterpret_cast<InterpretedCodeBlock*>(m_breakpointLocationsVector[i]->weakCodeRef);
String* functionName = codeBlock->functionName().string();
/* Send function name. */
if (functionName->length() > 0) {
sendString(ESCARGOT_MESSAGE_FUNCTION_NAME_8BIT, functionName);
if (!enabled()) {
return;
}
}
/* Send function info. */
uint8_t* byteCodeStart = codeBlock->byteCodeBlock()->m_code.data();
// adjust startLine with originLineOffset
uint32_t startLine = (uint32_t)codeBlock->functionStart().line - originLineOffset;
uint32_t startColumn = (uint32_t)(codeBlock->functionStart().column + 1);
FunctionInfo functionInfo;
memcpy(&functionInfo.byteCodeStart, (void*)&byteCodeStart, sizeof(uint8_t*));
memcpy(&functionInfo.startLine, &startLine, sizeof(uint32_t));
memcpy(&functionInfo.startColumn, &startColumn, sizeof(uint32_t));
if (!send(ESCARGOT_MESSAGE_FUNCTION_PTR, (void*)&functionInfo, sizeof(FunctionInfo))) {
return;
}
}
sendType(ESCARGOT_MESSAGE_PARSE_DONE);
if (enabled() && pendingWait() && !m_watchEval) {
waitForResolvingPendingBreakpoints();
}
}
void DebuggerEscargot::sendBacktraceInfo(uint8_t type, ByteCodeBlock* byteCodeBlock, uint32_t line, uint32_t column, uint32_t executionStateDepth)
{
BacktraceInfo backtraceInfo;
uint8_t* byteCode = byteCodeBlock->m_code.data();
memcpy(&backtraceInfo.byteCode, &byteCode, sizeof(void*));
memcpy(&backtraceInfo.line, &line, sizeof(uint32_t));
memcpy(&backtraceInfo.column, &column, sizeof(uint32_t));
memcpy(&backtraceInfo.executionStateDepth, &executionStateDepth, sizeof(uint32_t));
send(type, &backtraceInfo, sizeof(BacktraceInfo));
}
void DebuggerEscargot::sendVariableObjectInfo(uint8_t subType, Object* object)
{
/* Maximum UINT32_MAX number of objects are stored. */
uint32_t size = (uint32_t)m_activeObjects.size();
uint32_t index;
for (index = 0; index < size; index++) {
if (m_activeObjects[index] == object) {
break;
}
}
if (index == size && size < UINT32_MAX) {
m_activeObjects.pushBack(object);
}
VariableObjectInfo variableObjectInfo;
variableObjectInfo.subType = subType;
memcpy(&variableObjectInfo.index, &index, sizeof(uint32_t));
send(ESCARGOT_MESSAGE_VARIABLE, &variableObjectInfo, sizeof(VariableObjectInfo));
}
bool DebuggerEscargot::stopAtBreakpoint(ByteCodeBlock* byteCodeBlock, uint32_t offset, ExecutionState* state)
{
if (m_stopState == ESCARGOT_DEBUGGER_IN_EVAL_MODE) {
m_delay--;
if (m_delay == 0) {
processEvents(state, byteCodeBlock);
}
return m_restartDebugging;
}
BreakpointOffset breakpointOffset;
void* byteCodeStart = byteCodeBlock->m_code.data();
memcpy(&breakpointOffset.byteCodeStart, (void*)&byteCodeStart, sizeof(void*));
memcpy(&breakpointOffset.offset, &offset, sizeof(uint32_t));
send(ESCARGOT_MESSAGE_BREAKPOINT_HIT, &breakpointOffset, sizeof(BreakpointOffset));
if (!enabled()) {
return m_restartDebugging;
}
ASSERT(m_activeObjects.size() == 0);
m_stopState = ESCARGOT_DEBUGGER_IN_WAIT_MODE;
while (processEvents(state, byteCodeBlock))
;
m_activeObjects.clear();
m_delay = ESCARGOT_DEBUGGER_MESSAGE_PROCESS_DELAY;
return m_restartDebugging;
}
void DebuggerEscargot::byteCodeReleaseNotification(ByteCodeBlock* byteCodeBlock)
{
// All messages which involves this pointer should be ignored until the confirmation arrives.
if (enabled()) {
m_releasedFunctions.push_back(reinterpret_cast<uintptr_t>(byteCodeBlock));
sendPointer(ESCARGOT_MESSAGE_RELEASE_FUNCTION, byteCodeBlock);
}
}
void DebuggerEscargot::exceptionCaught(String* message, SavedStackTraceDataVector& exceptionTrace)
{
if (!enabled()) {
return;
}
sendType(ESCARGOT_MESSAGE_EXCEPTION);
if (!enabled()) {
return;
}
sendString(ESCARGOT_MESSAGE_STRING_8BIT, message);
size_t size = exceptionTrace.size();
for (size_t i = 0; i < size && enabled(); i++) {
sendBacktraceInfo(ESCARGOT_MESSAGE_EXCEPTION_BACKTRACE, exceptionTrace[i].byteCodeBlock, exceptionTrace[i].line, exceptionTrace[i].column, UINT32_MAX);
}
}
void DebuggerEscargot::consoleOut(String* output)
{
if (enabled()) {
sendType(ESCARGOT_MESSAGE_PRINT);
if (enabled()) {
sendString(ESCARGOT_MESSAGE_STRING_8BIT, output);
}
}
}
String* DebuggerEscargot::getClientSource(String** sourceName)
{
if (!enabled()) {
return nullptr;
}
sendType(ESCARGOT_DEBUGGER_WAIT_FOR_SOURCE);
while (processEvents(nullptr, nullptr))
;
if (sourceName) {
*sourceName = m_clientSourceName;
}
String* sourceData = m_clientSourceData;
m_clientSourceName = nullptr;
m_clientSourceData = nullptr;
return sourceData;
}
bool DebuggerEscargot::getWaitBeforeExitClient()
{
sendType(ESCARGOT_DEBUGGER_WAIT_FOR_WAIT_EXIT);
while (processEvents(nullptr, nullptr))
;
return this->m_exitClient;
}
bool DebuggerEscargot::doEval(ExecutionState* state, Optional<ByteCodeBlock*> byteCodeBlock, uint8_t* buffer, size_t length)
{
uint8_t type = (uint8_t)(buffer[0] + 1);
uint32_t size;
char* data;
memcpy(&size, buffer + 1, sizeof(uint32_t));
if (size > ESCARGOT_DEBUGGER_MAX_MESSAGE_LENGTH - 1 - sizeof(uint32_t)) {
if (length != ESCARGOT_DEBUGGER_MAX_MESSAGE_LENGTH) {
goto error;
}
data = (char*)GC_MALLOC_ATOMIC(size);
char* ptr = data;
char* end = data + size;
length = ESCARGOT_DEBUGGER_MAX_MESSAGE_LENGTH - 1 - sizeof(uint32_t);
memcpy(ptr, buffer + 1 + sizeof(uint32_t), length);
ptr += length;
do {
if (receive(buffer, length)) {
if (buffer[0] != type || (length > (size_t)(end - ptr + 1))) {
goto error;
}
memcpy(ptr, buffer + 1, length - 1);
ptr += length - 1;
} else if (!enabled()) {
return false;
}
} while (ptr < end);
} else {
if (size + 1 + sizeof(uint32_t) != length) {
goto error;
}
data = (char*)buffer + 1 + sizeof(uint32_t);
}
String* str;
if (type == ESCARGOT_MESSAGE_EVAL_8BIT || type == ESCARGOT_MESSAGE_EVAL_WITHOUT_STOP_8BIT || type == ESCARGOT_MESSAGE_WATCH_8BIT) {
str = new Latin1String(data, size);
} else if (type == ESCARGOT_MESSAGE_EVAL_16BIT || type == ESCARGOT_MESSAGE_EVAL_WITHOUT_STOP_16BIT || type == ESCARGOT_MESSAGE_WATCH_16BIT) {
str = new UTF16String((char16_t*)data, size / 2);
} else if (type == ESCARGOT_DEBUGGER_CLIENT_SOURCE_8BIT) {
char* sourceNameSrc = (char*)memchr(data, '\0', size);
if (!sourceNameSrc) {
goto error;
}
uint32_t sourceNameLen = sourceNameSrc - data;
m_clientSourceName = new Latin1String(data, sourceNameLen);
m_clientSourceData = new Latin1String(data + sourceNameLen + 1, size - sourceNameLen - 1);
return false;
} else if (type == ESCARGOT_DEBUGGER_CLIENT_SOURCE_16BIT) {
char16_t* str = (char16_t*)data;
uint32_t sourceNameLen = 0;
size = size / 2;
for (; sourceNameLen != size; sourceNameLen++) {
if (str[sourceNameLen] == '\0') {
break;
}
}
if (sourceNameLen == size) {
goto error;
}
m_clientSourceName = new UTF16String(str, sourceNameLen);
m_clientSourceData = new UTF16String(str + sourceNameLen + 1, size - sourceNameLen - 1);
return false;
}
if (type == ESCARGOT_MESSAGE_WATCH_8BIT || type == ESCARGOT_MESSAGE_WATCH_16BIT) {
m_watchEval = true;
}
m_stopState = ESCARGOT_DEBUGGER_IN_EVAL_MODE;
try {
Value asValue(str);
Value result(Value::ForceUninitialized);
if (type == ESCARGOT_MESSAGE_EVAL_WITHOUT_STOP_8BIT || type == ESCARGOT_MESSAGE_EVAL_WITHOUT_STOP_16BIT) {
result = state->context()->globalObject()->eval(*state, asValue);
} else {
result = state->context()->globalObject()->evalLocal(*state, asValue, state->thisValue(), byteCodeBlock->m_codeBlock, true);
}
type = m_watchEval ? ESCARGOT_MESSAGE_WATCH_RESULT_8BIT : ESCARGOT_MESSAGE_EVAL_RESULT_8BIT;
str = result.toStringWithoutException(*state);
} catch (const Value& val) {
type = ESCARGOT_MESSAGE_EVAL_FAILED_8BIT;
str = val.toStringWithoutException(*state);
}
m_stopState = ESCARGOT_DEBUGGER_IN_WAIT_MODE;
m_watchEval = false;
if (enabled()) {
sendString(type, str);
}
return true;
error:
ESCARGOT_LOG_ERROR("Invalid eval message received. Closing connection.\n");
close(CloseProtocolError);
return false;
}
void DebuggerEscargot::getBacktrace(ExecutionState* state, uint32_t minDepth, uint32_t maxDepth, bool getTotal)
{
StackTraceDataOnStackVector stackTraceData;
bool hasSavedStackTrace = SandBox::createStackTrace(stackTraceData, *state, true);
uint32_t size = (uint32_t)stackTraceData.size();
uint32_t total = 0;
for (uint32_t i = 0; i < size; i++) {
if ((size_t)stackTraceData[i].loc.actualCodeBlock != SIZE_MAX) {
total++;
}
}
if (hasSavedStackTrace) {
total += (uint32_t)activeSavedStackTrace()->size();
}
if (getTotal && !send(ESCARGOT_MESSAGE_BACKTRACE_TOTAL, &total, sizeof(uint32_t))) {
return;
}
if (maxDepth == 0 || maxDepth > total) {
maxDepth = total;
}
if (minDepth >= total) {
minDepth = total;
}
ByteCodeLOCDataMap locMap;
uint32_t counter = 0;
for (uint32_t i = 0; i < size && counter < maxDepth; i++) {
if ((size_t)stackTraceData[i].loc.actualCodeBlock != SIZE_MAX) {
if (++counter <= minDepth) {
continue;
}
ByteCodeBlock* byteCodeBlock = stackTraceData[i].loc.actualCodeBlock;
uint32_t line, column;
if ((size_t)stackTraceData[i].loc.index == SIZE_MAX) {
size_t byteCodePosition = stackTraceData[i].loc.byteCodePosition;
ByteCodeLOCData* locData;
auto iterMap = locMap.find(byteCodeBlock);
if (iterMap == locMap.end()) {
locData = new ByteCodeLOCData();
locMap.insert(std::make_pair(byteCodeBlock, locData));
} else {
locData = iterMap->second;
}
ExtendedNodeLOC loc = byteCodeBlock->computeNodeLOCFromByteCode(state->context(), byteCodePosition, byteCodeBlock->m_codeBlock, locData);
line = (uint32_t)loc.line;
column = (uint32_t)loc.column;
} else {
line = (uint32_t)stackTraceData[i].loc.line;
column = (uint32_t)stackTraceData[i].loc.column;
}
sendBacktraceInfo(ESCARGOT_MESSAGE_BACKTRACE, byteCodeBlock, line, column, (uint32_t)stackTraceData[i].executionStateDepth);
if (!enabled()) {
return;
}
}
}
for (auto iter = locMap.begin(); iter != locMap.end(); iter++) {
delete iter->second;
}
if (hasSavedStackTrace) {
SavedStackTraceData* savedStackTracePtr = activeSavedStackTrace()->begin();
SavedStackTraceData* savedStackTraceEnd = activeSavedStackTrace()->end();
while (counter < maxDepth && savedStackTracePtr < savedStackTraceEnd) {
if (++counter <= minDepth) {
continue;
}
sendBacktraceInfo(ESCARGOT_MESSAGE_BACKTRACE, savedStackTracePtr->byteCodeBlock, savedStackTracePtr->line, savedStackTracePtr->column, UINT32_MAX);
savedStackTracePtr++;
}
}
sendType(ESCARGOT_MESSAGE_BACKTRACE_END);
}
void DebuggerEscargot::getScopeChain(ExecutionState* state, uint32_t stateIndex)
{
const size_t maxMessageLength = ESCARGOT_DEBUGGER_MAX_MESSAGE_LENGTH - 1;
uint8_t buffer[maxMessageLength];
size_t nextScope = 0;
while (stateIndex > 0) {
state = state->parent();
stateIndex--;
if (!state) {
send(ESCARGOT_MESSAGE_SCOPE_CHAIN_END, buffer, nextScope);
return;
}
}
LexicalEnvironment* lexEnv = state->lexicalEnvironment();
while (lexEnv) {
EnvironmentRecord* record = lexEnv->record();
uint8_t type;
if (nextScope >= maxMessageLength) {
if (!send(ESCARGOT_MESSAGE_SCOPE_CHAIN, buffer, maxMessageLength)) {
return;
}
nextScope = 0;
}
if (record->isGlobalEnvironmentRecord()) {
type = ESCARGOT_RECORD_GLOBAL_ENVIRONMENT;
} else if (record->isDeclarativeEnvironmentRecord()) {
DeclarativeEnvironmentRecord* declarativeRecord = record->asDeclarativeEnvironmentRecord();
if (declarativeRecord->isFunctionEnvironmentRecord()) {
type = ESCARGOT_RECORD_FUNCTION_ENVIRONMENT;
} else if (record->isModuleEnvironmentRecord()) {
type = ESCARGOT_RECORD_MODULE_ENVIRONMENT;
} else {
type = ESCARGOT_RECORD_DECLARATIVE_ENVIRONMENT;
}
} else if (record->isObjectEnvironmentRecord()) {
type = ESCARGOT_RECORD_OBJECT_ENVIRONMENT;
} else {
type = ESCARGOT_RECORD_UNKNOWN_ENVIRONMENT;
}
buffer[nextScope++] = type;
lexEnv = lexEnv->outerEnvironment();
}
send(ESCARGOT_MESSAGE_SCOPE_CHAIN_END, buffer, nextScope);
}
static void sendProperty(DebuggerEscargot* debugger, ExecutionState* state, AtomicString name, Value value)
{
uint8_t type = DebuggerEscargot::ESCARGOT_VARIABLE_UNDEFINED;
String* valueStr = nullptr;
if (value.isNull()) {
type = DebuggerEscargot::ESCARGOT_VARIABLE_NULL;
} else if (value.isTrue()) {
type = DebuggerEscargot::ESCARGOT_VARIABLE_TRUE;
} else if (value.isFalse()) {
type = DebuggerEscargot::ESCARGOT_VARIABLE_FALSE;
} else if (value.isNumber()) {
type = DebuggerEscargot::ESCARGOT_VARIABLE_NUMBER;
valueStr = value.toString(*state);
} else if (value.isString() || value.isSymbol() || value.isBigInt()) {
if (value.isString()) {
type = DebuggerEscargot::ESCARGOT_VARIABLE_STRING;
valueStr = value.asString();
} else if (value.isBigInt()) {
type = DebuggerEscargot::ESCARGOT_VARIABLE_BIGINT;
valueStr = value.asBigInt()->toString();
} else {
type = DebuggerEscargot::ESCARGOT_VARIABLE_SYMBOL;
Symbol* symbol = value.asSymbol();
valueStr = symbol->descriptionString();
}
if (valueStr->length() >= ESCARGOT_DEBUGGER_MAX_VARIABLE_LENGTH) {
type |= DebuggerEscargot::ESCARGOT_VARIABLE_LONG_VALUE;
valueStr = new (alloca(sizeof(StringView))) StringView(valueStr, 0, ESCARGOT_DEBUGGER_MAX_VARIABLE_LENGTH);
}
} else if (value.isFunction()) {
type = DebuggerEscargot::ESCARGOT_VARIABLE_FUNCTION;
} else if (value.isObject()) {
type = DebuggerEscargot::ESCARGOT_VARIABLE_OBJECT;
Object* valueObject = value.asObject();
if (valueObject->isArrayObject()) {
type = DebuggerEscargot::ESCARGOT_VARIABLE_ARRAY;
}
}
String* nameStr = name.string();
if (nameStr->length() > ESCARGOT_DEBUGGER_MAX_VARIABLE_LENGTH) {
type |= DebuggerEscargot::ESCARGOT_VARIABLE_LONG_NAME;
nameStr = new (alloca(sizeof(StringView))) StringView(nameStr, 0, ESCARGOT_DEBUGGER_MAX_VARIABLE_LENGTH);
}
if ((type & DebuggerEscargot::ESCARGOT_VARIABLE_TYPE_MASK) < DebuggerEscargot::ESCARGOT_VARIABLE_OBJECT) {
debugger->sendSubtype(DebuggerEscargot::ESCARGOT_MESSAGE_VARIABLE, type);
} else {
debugger->sendVariableObjectInfo(type, value.asObject());
}
if (debugger->connected()) {
debugger->sendString(DebuggerEscargot::ESCARGOT_MESSAGE_STRING_8BIT, nameStr);
}
if (valueStr && debugger->connected()) {
debugger->sendString(DebuggerEscargot::ESCARGOT_MESSAGE_STRING_8BIT, valueStr);
}
}
static void sendUnaccessibleProperty(DebuggerEscargot* debugger, AtomicString name)
{
uint8_t type = DebuggerEscargot::ESCARGOT_VARIABLE_UNACCESSIBLE;
String* nameStr = name.string();
if (nameStr->length() > ESCARGOT_DEBUGGER_MAX_VARIABLE_LENGTH) {
type |= DebuggerEscargot::ESCARGOT_VARIABLE_LONG_NAME;
nameStr = new (alloca(sizeof(StringView))) StringView(nameStr, 0, ESCARGOT_DEBUGGER_MAX_VARIABLE_LENGTH);
}
debugger->sendSubtype(DebuggerEscargot::ESCARGOT_MESSAGE_VARIABLE, type);
if (debugger->connected()) {
debugger->sendString(DebuggerEscargot::ESCARGOT_MESSAGE_STRING_8BIT, nameStr);
}
}
static void sendObjectProperties(DebuggerEscargot* debugger, ExecutionState* state, Object* object)
{
Object::OwnPropertyKeyVector keys = object->ownPropertyKeys(*state);
size_t size = keys.size();
for (size_t i = 0; i < size; i++) {
ObjectPropertyName propertyName(*state, keys[i]);
AtomicString name(*state, keys[i].toStringWithoutException(*state));
try {
ObjectGetResult result = object->getOwnProperty(*state, propertyName);
sendProperty(debugger, state, name, result.value(*state, Value(object)));
} catch (const Value& val) {
sendUnaccessibleProperty(debugger, name);
}
}
}
static void sendRecordProperties(DebuggerEscargot* debugger, ExecutionState* state, IdentifierRecordVector& identifiers, EnvironmentRecord* record)
{
size_t size = identifiers.size();
for (size_t i = 0; i < size; i++) {
AtomicString name = identifiers[i].m_name;
try {
EnvironmentRecord::GetBindingValueResult result = record->getBindingValue(*state, name);
ASSERT(result.m_hasBindingValue);
sendProperty(debugger, state, name, result.m_value);
} catch (const Value& val) {
sendUnaccessibleProperty(debugger, name);
}
}
}
static void sendRecordProperties(DebuggerEscargot* debugger, ExecutionState* state, const ModuleEnvironmentRecord::ModuleBindingRecordVector& bindings, ModuleEnvironmentRecord* record)
{
size_t size = bindings.size();
for (size_t i = 0; i < size; i++) {
AtomicString name = bindings[i].m_localName;
try {
EnvironmentRecord::GetBindingValueResult result = record->getBindingValue(*state, name);
ASSERT(result.m_hasBindingValue);
sendProperty(debugger, state, name, result.m_value);
} catch (const Value& val) {
sendUnaccessibleProperty(debugger, name);
}
}
}
void DebuggerEscargot::getScopeVariables(ExecutionState* state, uint32_t stateIndex, uint32_t index)
{
while (stateIndex > 0) {
state = state->parent();
stateIndex--;
if (!state) {
sendSubtype(ESCARGOT_MESSAGE_VARIABLE, ESCARGOT_VARIABLE_END);
return;
}
}
LexicalEnvironment* lexEnv = state->lexicalEnvironment();
while (lexEnv && index > 0) {
lexEnv = lexEnv->outerEnvironment();
index--;
}
if (!lexEnv) {
sendSubtype(ESCARGOT_MESSAGE_VARIABLE, ESCARGOT_VARIABLE_END);
return;
}
EnvironmentRecord* record = lexEnv->record();
if (record->isGlobalEnvironmentRecord()) {
GlobalEnvironmentRecord* global = record->asGlobalEnvironmentRecord();
sendRecordProperties(this, state, *global->m_globalDeclarativeRecord, record);
sendObjectProperties(this, state, global->m_globalObject);
} else if (record->isDeclarativeEnvironmentRecord()) {
DeclarativeEnvironmentRecord* declarativeRecord = record->asDeclarativeEnvironmentRecord();
if (declarativeRecord->isFunctionEnvironmentRecord()) {
IdentifierRecordVector* identifierRecordVector = declarativeRecord->asFunctionEnvironmentRecord()->getRecordVector();
if (identifierRecordVector != NULL) {
sendRecordProperties(this, state, *identifierRecordVector, record);
}
} else if (record->isModuleEnvironmentRecord()) {
sendRecordProperties(this, state, record->asModuleEnvironmentRecord()->moduleBindings(), record->asModuleEnvironmentRecord());
} else if (declarativeRecord->isDeclarativeEnvironmentRecordNotIndexed()) {
sendRecordProperties(this, state, declarativeRecord->asDeclarativeEnvironmentRecordNotIndexed()->m_recordVector, record);
}
} else if (record->isObjectEnvironmentRecord()) {
sendObjectProperties(this, state, record->asObjectEnvironmentRecord()->bindingObject());
}
sendSubtype(ESCARGOT_MESSAGE_VARIABLE, ESCARGOT_VARIABLE_END);
}
bool DebuggerEscargot::processEvents(ExecutionState* state, Optional<ByteCodeBlock*> byteCodeBlock, bool isBlockingRequest)
{
uint8_t buffer[ESCARGOT_DEBUGGER_MAX_MESSAGE_LENGTH];
size_t length;
while (true) {
if (isBlockingRequest) {
if (!receive(buffer, length)) {
break;
}
} else {
if (isThereAnyEvent()) {
if (!receive(buffer, length)) {
break;
}
} else {
return false;
}
}
switch (buffer[0]) {
case ESCARGOT_DEBUGGER_CLIENT_SOURCE_8BIT_START:
case ESCARGOT_DEBUGGER_CLIENT_SOURCE_16BIT_START: {
if ((length <= 1 + sizeof(uint32_t) || state != nullptr || byteCodeBlock)) {
break;
}
return doEval(state, byteCodeBlock, buffer, length);
}
case ESCARGOT_DEBUGGER_THERE_WAS_NO_SOURCE: {
if (length != 1 || state != nullptr || byteCodeBlock) {
break;
}
return false;
}
case ESCARGOT_DEBUGGER_WAIT_BEFORE_EXIT: {
m_exitClient = buffer[1];
return false;
}
case ESCARGOT_MESSAGE_FUNCTION_RELEASED: {
if (length != 1 + sizeof(uintptr_t)) {
break;
}
uintptr_t ptr;
memcpy(&ptr, buffer + 1, sizeof(uintptr_t));
for (size_t i = 0; i < m_releasedFunctions.size(); i++) {
if (m_releasedFunctions[i] == ptr) {
// Delete only the first instance.
m_releasedFunctions.erase(i);
return true;
}
}
break;
}
case ESCARGOT_MESSAGE_UPDATE_BREAKPOINT: {
if (length != 1 + 1 + sizeof(uintptr_t) + sizeof(uint32_t)) {
break;
}
uintptr_t ptr;
uint32_t offset;
memcpy(&ptr, buffer + 1 + 1, sizeof(uintptr_t));
memcpy(&offset, buffer + 1 + 1 + sizeof(uintptr_t), sizeof(uint32_t));
for (size_t i = 0; i < m_releasedFunctions.size(); i++) {
if (m_releasedFunctions[i] == ptr) {
// This function has been already freed.
return true;
}
}
ByteCode* breakpoint = (ByteCode*)(ptr + offset);
#if defined(ESCARGOT_COMPUTED_GOTO_INTERPRETER)
if (buffer[1] != 0) {
if (breakpoint->m_opcodeInAddress != g_opcodeTable.m_addressTable[BreakpointDisabledOpcode]) {
break;
}
breakpoint->m_opcodeInAddress = g_opcodeTable.m_addressTable[BreakpointEnabledOpcode];
} else {
if (breakpoint->m_opcodeInAddress != g_opcodeTable.m_addressTable[BreakpointEnabledOpcode]) {
break;
}
breakpoint->m_opcodeInAddress = g_opcodeTable.m_addressTable[BreakpointDisabledOpcode];
}
#else
if (buffer[1] != 0) {
if (breakpoint->m_opcode != BreakpointDisabledOpcode) {
break;
}
breakpoint->m_opcode = BreakpointEnabledOpcode;
} else {
if (breakpoint->m_opcode != BreakpointEnabledOpcode) {
break;
}
breakpoint->m_opcode = BreakpointDisabledOpcode;
}
#endif
return true;
}
case ESCARGOT_MESSAGE_CONTINUE: {
if (length != 1 || m_stopState != ESCARGOT_DEBUGGER_IN_WAIT_MODE) {
break;
}
m_stopState = nullptr;
return false;
}
case ESCARGOT_MESSAGE_STEP: {
if (length != 1 || m_stopState == ESCARGOT_DEBUGGER_IN_EVAL_MODE) {
break;
}
m_stopState = ESCARGOT_DEBUGGER_ALWAYS_STOP;
return false;
}
case ESCARGOT_MESSAGE_NEXT: {
if (length != 1 || m_stopState != ESCARGOT_DEBUGGER_IN_WAIT_MODE) {
break;
}
m_stopState = state;
return false;
}
case ESCARGOT_MESSAGE_FINISH: {
if (length != 1 || m_stopState != ESCARGOT_DEBUGGER_IN_WAIT_MODE) {
break;
}
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;
}
case ESCARGOT_MESSAGE_RESTART: {
setRestart(true);
return true;
}
case ESCARGOT_MESSAGE_WATCH_8BIT_START:
case ESCARGOT_MESSAGE_WATCH_16BIT_START:
case ESCARGOT_MESSAGE_EVAL_8BIT_START:
case ESCARGOT_MESSAGE_EVAL_16BIT_START: {
if ((length <= 1 + sizeof(uint32_t)) || m_stopState != ESCARGOT_DEBUGGER_IN_WAIT_MODE) {
break;
}
ASSERT(byteCodeBlock.hasValue());
return doEval(state, byteCodeBlock, buffer, length);
}
case ESCARGOT_MESSAGE_EVAL_WITHOUT_STOP_8BIT_START:
case ESCARGOT_MESSAGE_EVAL_WITHOUT_STOP_16BIT_START: {
if ((length <= 1 + sizeof(uint32_t)) || (m_stopState != ESCARGOT_DEBUGGER_IN_WAIT_MODE && m_stopState != ESCARGOT_DEBUGGER_ALWAYS_STOP)) {
break;
}
ASSERT(!byteCodeBlock.hasValue());
return doEval(state, nullptr, buffer, length);
}
case ESCARGOT_MESSAGE_GET_BACKTRACE: {
if ((length != 1 + sizeof(uint32_t) + sizeof(uint32_t) + 1) || m_stopState != ESCARGOT_DEBUGGER_IN_WAIT_MODE) {
break;
}
uint32_t minDepth;
uint32_t maxDepth;
memcpy(&minDepth, buffer + 1, sizeof(uint32_t));
memcpy(&maxDepth, buffer + 1 + sizeof(uint32_t), sizeof(uint32_t));
getBacktrace(state, minDepth, maxDepth, buffer[1 + sizeof(uint32_t) + sizeof(uint32_t)] != 0);
return true;
}
case ESCARGOT_MESSAGE_GET_SCOPE_CHAIN: {
if ((length != 1 + sizeof(uint32_t)) || m_stopState != ESCARGOT_DEBUGGER_IN_WAIT_MODE) {
break;
}
uint32_t stateIndex;
memcpy(&stateIndex, buffer + 1, sizeof(uint32_t));
getScopeChain(state, stateIndex);
return true;
}
case ESCARGOT_MESSAGE_GET_SCOPE_VARIABLES: {
if ((length != 1 + sizeof(uint32_t) + sizeof(uint32_t)) || m_stopState != ESCARGOT_DEBUGGER_IN_WAIT_MODE) {
break;
}
uint32_t stateIndex;
uint32_t index;
memcpy(&stateIndex, buffer + 1, sizeof(uint32_t));
memcpy(&index, buffer + 1 + sizeof(uint32_t), sizeof(uint32_t));
getScopeVariables(state, stateIndex, index);
return true;
}
case ESCARGOT_MESSAGE_GET_OBJECT: {
if (length != 1 + sizeof(uint32_t) || m_stopState != ESCARGOT_DEBUGGER_IN_WAIT_MODE) {
break;
}
uint32_t index;
memcpy(&index, buffer + 1, sizeof(uint32_t));
if (index < m_activeObjects.size()) {
sendObjectProperties(this, state, m_activeObjects[index]);
}
sendSubtype(ESCARGOT_MESSAGE_VARIABLE, ESCARGOT_VARIABLE_END);
return true;
}
case ESCARGOT_DEBUGGER_PENDING_CONFIG: {
if (length != 2) {
break;
}
m_pendingWait = buffer[1];
return true;
}
case ESCARGOT_DEBUGGER_PENDING_RESUME: {
if (!m_waitForResume || length != 1) {
break;
}
m_waitForResume = false;
return true;
}
case ESCARGOT_DEBUGGER_TAKE_HEAP_SNAPSHOT: {
HeapSnapshot snapshot;
std::string fileName = snapshot.takeHeapSnapshot(state);
if (fileName.empty()) {
ESCARGOT_LOG_ERROR("Error happened during heap snapshot creation. Aborting now.\n");
abort();
} else if (enabled()) {
String* data = String::fromUTF8(fileName.c_str(), fileName.length());
if (data == nullptr) {
ESCARGOT_LOG_ERROR("Error happened during heap snapshot creation. Aborting now.\n");
abort();
}
send(ESCARGOT_DEBUGGER_SNAPSHOT_FINISHED,
data->characters8(),
data->length());
}
return true;
}
}
ESCARGOT_LOG_ERROR("Invalid message received. Closing connection.\n");
close(CloseProtocolError);
return false;
}
return enabled();
}
void DebuggerEscargot::waitForResolvingPendingBreakpoints()
{
m_waitForResume = true;
sendType(ESCARGOT_DEBUGGER_WAITING_AFTER_PENDING);
while (m_waitForResume) {
processEvents(nullptr, nullptr);
if (!enabled()) {
break;
}
}
}
DebuggerEscargot::SavedStackTraceDataVector* Debugger::saveStackTrace(ExecutionState& state)
{
SavedStackTraceDataVector* savedStackTrace = new SavedStackTraceDataVector();
StackTraceDataOnStackVector stackTraceData;
ByteCodeLOCDataMap locMap;
uint32_t counter = 0;
bool hasSavedStackTrace = SandBox::createStackTrace(stackTraceData, state, true);
uint32_t total = (uint32_t)stackTraceData.size();
for (uint32_t i = 0; i < total && counter < ESCARGOT_DEBUGGER_MAX_STACK_TRACE_LENGTH; i++) {
if ((size_t)stackTraceData[i].loc.actualCodeBlock != SIZE_MAX) {
ByteCodeBlock* byteCodeBlock = stackTraceData[i].loc.actualCodeBlock;
uint32_t line, column;
counter++;
if ((size_t)stackTraceData[i].loc.index == SIZE_MAX) {
size_t byteCodePosition = stackTraceData[i].loc.byteCodePosition;
ByteCodeLOCData* locData;
auto iterMap = locMap.find(byteCodeBlock);
if (iterMap == locMap.end()) {
locData = new ByteCodeLOCData();
locMap.insert(std::make_pair(byteCodeBlock, locData));
} else {
locData = iterMap->second;
}
ExtendedNodeLOC loc = byteCodeBlock->computeNodeLOCFromByteCode(state.context(), byteCodePosition, byteCodeBlock->m_codeBlock, locData);
line = (uint32_t)loc.line;
column = (uint32_t)loc.column;
} else {
line = (uint32_t)stackTraceData[i].loc.line;
column = (uint32_t)stackTraceData[i].loc.column;
}
savedStackTrace->push_back(SavedStackTraceData(byteCodeBlock, line, column));
}
}
for (auto iter = locMap.begin(); iter != locMap.end(); iter++) {
delete iter->second;
}
if (hasSavedStackTrace) {
Debugger* debugger = state.context()->debugger();
SavedStackTraceData* savedStackTracePtr = debugger->activeSavedStackTrace()->begin();
SavedStackTraceData* savedStackTraceEnd = debugger->activeSavedStackTrace()->end();
while (counter < ESCARGOT_DEBUGGER_MAX_STACK_TRACE_LENGTH && savedStackTracePtr < savedStackTraceEnd) {
savedStackTrace->push_back(SavedStackTraceData(savedStackTracePtr->byteCodeBlock, savedStackTracePtr->line, savedStackTracePtr->column));
savedStackTracePtr++;
counter++;
}
}
return savedStackTrace;
}
void DebuggerEscargot::init(const char*, Context*)
{
union {
uint16_t u16Value;
uint8_t u8Value;
} endian;
endian.u16Value = 1;
MessageVersion version;
version.littleEndian = (endian.u8Value == 1);
uint32_t debuggerVersion = ESCARGOT_DEBUGGER_VERSION;
memcpy(version.version, &debuggerVersion, sizeof(uint32_t));
if (!send(ESCARGOT_MESSAGE_VERSION, &version, sizeof(version))) {
return;
}
MessageConfiguration configuration;
configuration.maxMessageSize = ESCARGOT_DEBUGGER_MAX_MESSAGE_LENGTH;
configuration.pointerSize = (uint8_t)sizeof(void*);
send(ESCARGOT_MESSAGE_CONFIGURATION, &configuration, sizeof(configuration));
}
} // namespace Escargot
#endif /* ESCARGOT_DEBUGGER */