0ad/source/scriptinterface/DebuggingServer.cpp
Yves 20ed5b118c Unify script conversions and remove JSInterface_Vector3D.
Because it was historically grown, we have some duplicated code for
converting script types to native types.
This patch removes the file JSConversions.cpp and moves some code to
ScriptConversions.cpp.
The places using JSConversions.cpp are changed to use the
ScriptInterface's conversion functions in ScriptConversions.cpp.
I also removed JSInterface_Vector3D because it had additional
requirements to the conversion code that no other code has and because
it's currently not used. I think it doesn't make sense to maintain code
just because it could possibly be used again later.

Closes #2213
Refs #1886

This was SVN commit r14036.
2013-10-20 17:13:53 +00:00

569 lines
15 KiB
C++

/* Copyright (C) 2013 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/
#include "precompiled.h"
#include "DebuggingServer.h"
#include "ThreadDebugger.h"
#include "ps/CLogger.h"
#include "ps/Filesystem.h"
CDebuggingServer* g_DebuggingServer = NULL;
const char* CDebuggingServer::header400 =
"HTTP/1.1 400 Bad Request\r\n"
"Content-Type: text/plain; charset=utf-8\r\n\r\n"
"Invalid request";
void CDebuggingServer::GetAllCallstacks(std::stringstream& response)
{
CScopeLock lock(m_Mutex);
response.str(std::string());
std::stringstream stream;
uint nbrCallstacksWritten = 0;
std::list<CThreadDebugger*>::iterator itr;
if (!m_ThreadDebuggers.empty())
{
response << "[";
for (itr = m_ThreadDebuggers.begin(); itr != m_ThreadDebuggers.end(); ++itr)
{
if ((*itr)->GetIsInBreak())
{
stream.str(std::string());
(*itr)->GetCallstack(stream);
if ((int)stream.tellp() != 0)
{
if (nbrCallstacksWritten != 0)
response << ",";
response << "{" << "\"ThreadDebuggerID\" : " << (*itr)->GetID() << ", \"CallStack\" : " << stream.str() << "}";
nbrCallstacksWritten++;
}
}
}
response << "]";
}
}
void CDebuggingServer::GetStackFrameData(std::stringstream& response, uint nestingLevel, uint threadDebuggerID, STACK_INFO stackInfoKind)
{
CScopeLock lock(m_Mutex);
response.str(std::string());
std::stringstream stream;
std::list<CThreadDebugger*>::iterator itr;
for (itr = m_ThreadDebuggers.begin(); itr != m_ThreadDebuggers.end(); ++itr)
{
if ((*itr)->GetID() == threadDebuggerID && (*itr)->GetIsInBreak())
{
(*itr)->GetStackFrameData(stream, nestingLevel, stackInfoKind);
if ((int)stream.tellp() != 0)
{
response << stream.str();
}
}
}
}
CDebuggingServer::CDebuggingServer() :
m_MgContext(NULL)
{
m_BreakPointsSem = SDL_CreateSemaphore(0);
ENSURE(m_BreakPointsSem);
SDL_SemPost(m_BreakPointsSem);
m_LastThreadDebuggerID = 0; // Next will be 1, 0 is reserved
m_BreakRequestedByThread = false;
m_BreakRequestedByUser = false;
m_SettingSimultaneousThreadBreak = true;
m_SettingBreakOnException = true;
EnableHTTP();
LOGWARNING(L"Javascript debugging webserver enabled.");
}
CDebuggingServer::~CDebuggingServer()
{
SDL_DestroySemaphore(m_BreakPointsSem);
if (m_MgContext)
{
mg_stop(m_MgContext);
m_MgContext = NULL;
}
}
bool CDebuggingServer::SetNextDbgCmd(uint threadDebuggerID, DBGCMD dbgCmd)
{
CScopeLock lock(m_Mutex);
std::list<CThreadDebugger*>::iterator itr;
for (itr = m_ThreadDebuggers.begin(); itr != m_ThreadDebuggers.end(); ++itr)
{
if ((*itr)->GetID() == threadDebuggerID || threadDebuggerID == 0)
{
if (DBG_CMD_NONE == (*itr)->GetNextDbgCmd() && (*itr)->GetIsInBreak())
{
SetBreakRequestedByThread(false);
SetBreakRequestedByUser(false);
(*itr)->SetNextDbgCmd(dbgCmd);
}
}
}
return true;
}
void CDebuggingServer::SetBreakRequestedByThread(bool Enabled)
{
CScopeLock lock(m_Mutex1);
m_BreakRequestedByThread = Enabled;
}
bool CDebuggingServer::GetBreakRequestedByThread()
{
CScopeLock lock(m_Mutex1);
return m_BreakRequestedByThread;
}
void CDebuggingServer::SetBreakRequestedByUser(bool Enabled)
{
CScopeLock lock(m_Mutex1);
m_BreakRequestedByUser = Enabled;
}
bool CDebuggingServer::GetBreakRequestedByUser()
{
CScopeLock lock(m_Mutex1);
return m_BreakRequestedByUser;
}
void CDebuggingServer::SetSettingBreakOnException(bool Enabled)
{
CScopeLock lock(m_Mutex);
m_SettingBreakOnException = Enabled;
}
void CDebuggingServer::SetSettingSimultaneousThreadBreak(bool Enabled)
{
CScopeLock lock(m_Mutex1);
m_SettingSimultaneousThreadBreak = Enabled;
}
bool CDebuggingServer::GetSettingBreakOnException()
{
CScopeLock lock(m_Mutex);
return m_SettingBreakOnException;
}
bool CDebuggingServer::GetSettingSimultaneousThreadBreak()
{
CScopeLock lock(m_Mutex1);
return m_SettingSimultaneousThreadBreak;
}
static Status AddFileResponse(const VfsPath& pathname, const CFileInfo& UNUSED(fileInfo), const uintptr_t cbData)
{
std::vector<std::string>& templates = *(std::vector<std::string>*)cbData;
std::wstring str(pathname.string());
templates.push_back(std::string(str.begin(), str.end()));
return INFO::OK;
}
void CDebuggingServer::EnumVfsJSFiles(std::stringstream& response)
{
VfsPath path = L"";
VfsPaths pathnames;
response.str(std::string());
std::vector<std::string> templates;
vfs::ForEachFile(g_VFS, "", AddFileResponse, (uintptr_t)&templates, L"*.js", vfs::DIR_RECURSIVE);
std::vector<std::string>::iterator itr;
response << "[";
for (itr = templates.begin(); itr != templates.end(); ++itr)
{
if (itr != templates.begin())
response << ",";
response << "\"" << *itr << "\"";
}
response << "]";
}
void CDebuggingServer::GetFile(std::string filename, std::stringstream& response)
{
CVFSFile file;
if (file.Load(g_VFS, filename) != PSRETURN_OK)
{
response << "Failed to load the file contents";
return;
}
std::string code = file.DecodeUTF8(); // assume it's UTF-8
response << code;
}
static void* MgDebuggingServerCallback_(mg_event event, struct mg_connection *conn, const struct mg_request_info *request_info)
{
CDebuggingServer* debuggingServer = (CDebuggingServer*)request_info->user_data;
ENSURE(debuggingServer);
return debuggingServer->MgDebuggingServerCallback(event, conn, request_info);
}
void* CDebuggingServer::MgDebuggingServerCallback(mg_event event, struct mg_connection *conn, const struct mg_request_info *request_info)
{
void* handled = (void*)""; // arbitrary non-NULL pointer to indicate successful handling
const char* header200 =
"HTTP/1.1 200 OK\r\n"
"Access-Control-Allow-Origin: *\r\n" // TODO: not great for security
"Content-Type: text/plain; charset=utf-8\r\n\r\n";
const char* header404 =
"HTTP/1.1 404 Not Found\r\n"
"Content-Type: text/plain; charset=utf-8\r\n\r\n"
"Unrecognised URI";
switch (event)
{
case MG_NEW_REQUEST:
{
std::stringstream stream;
std::string uri = request_info->uri;
if (uri == "/GetThreadDebuggerStatus")
{
GetThreadDebuggerStatus(stream);
}
else if (uri == "/EnumVfsJSFiles")
{
EnumVfsJSFiles(stream);
}
else if (uri == "/GetAllCallstacks")
{
GetAllCallstacks(stream);
}
else if (uri == "/Continue")
{
uint threadDebuggerID;
if (!GetWebArgs(conn, request_info, "threadDebuggerID", threadDebuggerID))
return handled;
// TODO: handle the return value
SetNextDbgCmd(threadDebuggerID, DBG_CMD_CONTINUE);
}
else if (uri == "/Break")
{
SetBreakRequestedByUser(true);
}
else if (uri == "/SetSettingSimultaneousThreadBreak")
{
std::string strEnabled;
bool bEnabled = false;
if (!GetWebArgs(conn, request_info, "enabled", strEnabled))
return handled;
// TODO: handle the return value
if (strEnabled == "true")
bEnabled = true;
else if (strEnabled == "false")
bEnabled = false;
else
return handled; // TODO: return an error state
SetSettingSimultaneousThreadBreak(bEnabled);
}
else if (uri == "/GetSettingSimultaneousThreadBreak")
{
stream << "{ \"Enabled\" : " << (GetSettingSimultaneousThreadBreak() ? "true" : "false") << " } ";
}
else if (uri == "/SetSettingBreakOnException")
{
std::string strEnabled;
bool bEnabled = false;
if (!GetWebArgs(conn, request_info, "enabled", strEnabled))
return handled;
// TODO: handle the return value
if (strEnabled == "true")
bEnabled = true;
else if (strEnabled == "false")
bEnabled = false;
else
return handled; // TODO: return an error state
SetSettingBreakOnException(bEnabled);
}
else if (uri == "/GetSettingBreakOnException")
{
stream << "{ \"Enabled\" : " << (GetSettingBreakOnException() ? "true" : "false") << " } ";
}
else if (uri == "/Step")
{
uint threadDebuggerID;
if (!GetWebArgs(conn, request_info, "threadDebuggerID", threadDebuggerID))
return handled;
// TODO: handle the return value
SetNextDbgCmd(threadDebuggerID, DBG_CMD_SINGLESTEP);
}
else if (uri == "/StepInto")
{
uint threadDebuggerID;
if (!GetWebArgs(conn, request_info, "threadDebuggerID", threadDebuggerID))
return handled;
// TODO: handle the return value
SetNextDbgCmd(threadDebuggerID, DBG_CMD_STEPINTO);
}
else if (uri == "/StepOut")
{
uint threadDebuggerID;
if (!GetWebArgs(conn, request_info, "threadDebuggerID", threadDebuggerID))
return handled;
// TODO: handle the return value
SetNextDbgCmd(threadDebuggerID, DBG_CMD_STEPOUT);
}
else if (uri == "/GetStackFrame")
{
uint nestingLevel;
uint threadDebuggerID;
if (!GetWebArgs(conn, request_info, "nestingLevel", nestingLevel) ||
!GetWebArgs(conn, request_info, "threadDebuggerID", threadDebuggerID))
{
return handled;
}
GetStackFrameData(stream, nestingLevel, threadDebuggerID, STACK_INFO_LOCALS);
}
else if (uri == "/GetStackFrameThis")
{
uint nestingLevel;
uint threadDebuggerID;
if (!GetWebArgs(conn, request_info, "nestingLevel", nestingLevel) ||
!GetWebArgs(conn, request_info, "threadDebuggerID", threadDebuggerID))
{
return handled;
}
GetStackFrameData(stream, nestingLevel, threadDebuggerID, STACK_INFO_THIS);
}
else if (uri == "/GetCurrentGlobalObject")
{
uint threadDebuggerID;
if (!GetWebArgs(conn, request_info, "threadDebuggerID", threadDebuggerID))
{
return handled;
}
GetStackFrameData(stream, 0, threadDebuggerID, STACK_INFO_GLOBALOBJECT);
}
else if (uri == "/ToggleBreakpoint")
{
std::string filename;
uint line;
if (!GetWebArgs(conn, request_info, "filename", filename) ||
!GetWebArgs(conn, request_info, "line", line))
{
return handled;
}
ToggleBreakPoint(filename, line);
}
else if (uri == "/GetFile")
{
std::string filename;
if (!GetWebArgs(conn, request_info, "filename", filename))
return handled;
GetFile(filename, stream);
}
else
{
mg_printf(conn, "%s", header404);
return handled;
}
mg_printf(conn, "%s", header200);
std::string str = stream.str();
mg_write(conn, str.c_str(), str.length());
return handled;
}
case MG_HTTP_ERROR:
return NULL;
case MG_EVENT_LOG:
// Called by Mongoose's cry()
LOGERROR(L"Mongoose error: %hs", request_info->log_message);
return NULL;
case MG_INIT_SSL:
return NULL;
default:
debug_warn(L"Invalid Mongoose event type");
return NULL;
}
};
void CDebuggingServer::EnableHTTP()
{
// Ignore multiple enablings
if (m_MgContext)
return;
const char *options[] = {
"listening_ports", "127.0.0.1:9000", // bind to localhost for security
"num_threads", "6", // enough for the browser's parallel connection limit
NULL
};
m_MgContext = mg_start(MgDebuggingServerCallback_, this, options);
ENSURE(m_MgContext);
}
bool CDebuggingServer::GetWebArgs(struct mg_connection *conn, const struct mg_request_info* request_info, std::string argName, uint& arg)
{
if (!request_info->query_string)
{
mg_printf(conn, "%s (no query string)", header400);
return false;
}
char buf[256];
int len = mg_get_var(request_info->query_string, strlen(request_info->query_string), argName.c_str(), buf, ARRAY_SIZE(buf));
if (len < 0)
{
mg_printf(conn, "%s (no '%s')", header400, argName.c_str());
return false;
}
arg = atoi(buf);
return true;
}
bool CDebuggingServer::GetWebArgs(struct mg_connection *conn, const struct mg_request_info* request_info, std::string argName, std::string& arg)
{
if (!request_info->query_string)
{
mg_printf(conn, "%s (no query string)", header400);
return false;
}
char buf[256];
int len = mg_get_var(request_info->query_string, strlen(request_info->query_string), argName.c_str(), buf, ARRAY_SIZE(buf));
if (len < 0)
{
mg_printf(conn, "%s (no '%s')", header400, argName.c_str());
return false;
}
arg = buf;
return true;
}
void CDebuggingServer::RegisterScriptinterface(std::string name, ScriptInterface* pScriptInterface)
{
CScopeLock lock(m_Mutex);
CThreadDebugger* pThreadDebugger = new CThreadDebugger;
// ThreadID 0 is reserved
pThreadDebugger->Initialize(++m_LastThreadDebuggerID, name, pScriptInterface, this);
m_ThreadDebuggers.push_back(pThreadDebugger);
}
void CDebuggingServer::UnRegisterScriptinterface(ScriptInterface* pScriptInterface)
{
CScopeLock lock(m_Mutex);
std::list<CThreadDebugger*>::iterator itr;
for (itr = m_ThreadDebuggers.begin(); itr != m_ThreadDebuggers.end(); ++itr)
{
if ((*itr)->CompareScriptInterfacePtr(pScriptInterface))
{
delete (*itr);
m_ThreadDebuggers.erase(itr);
break;
}
}
}
void CDebuggingServer::GetThreadDebuggerStatus(std::stringstream& response)
{
CScopeLock lock(m_Mutex);
response.str(std::string());
std::list<CThreadDebugger*>::iterator itr;
response << "[";
for (itr = m_ThreadDebuggers.begin(); itr != m_ThreadDebuggers.end(); ++itr)
{
if (itr == m_ThreadDebuggers.begin())
response << "{ ";
else
response << ",{ ";
response << "\"ThreadDebuggerID\" : " << (*itr)->GetID() << ",";
response << "\"ScriptInterfaceName\" : \"" << (*itr)->GetName() << "\",";
response << "\"ThreadInBreak\" : " << ((*itr)->GetIsInBreak() ? "true" : "false") << ",";
response << "\"BreakFileName\" : \"" << (*itr)->GetBreakFileName() << "\",";
response << "\"BreakLine\" : " << (*itr)->GetLastBreakLine();
response << " }";
}
response << "]";
}
void CDebuggingServer::ToggleBreakPoint(std::string filename, uint line)
{
// First, pass the message to all associated CThreadDebugger objects and check if one returns true (handled);
{
CScopeLock lock(m_Mutex);
std::list<CThreadDebugger*>::iterator itr;
for (itr = m_ThreadDebuggers.begin(); itr != m_ThreadDebuggers.end(); ++itr)
{
if ((*itr)->ToggleBreakPoint(filename, line))
return;
}
}
// If the breakpoint isn't handled yet search the breakpoints registered in this class
std::list<CBreakPoint>* pBreakPoints = NULL;
double breakPointsLockID = AquireBreakPointAccess(&pBreakPoints);
// If set, delete
bool deleted = false;
for (std::list<CBreakPoint>::iterator itr = pBreakPoints->begin(); itr != pBreakPoints->end(); ++itr)
{
if ((*itr).m_Filename == filename && (*itr).m_UserLine == line)
{
itr = pBreakPoints->erase(itr);
deleted = true;
break;
}
}
// If not set, set
if (!deleted)
{
CBreakPoint bP;
bP.m_Filename = filename;
bP.m_UserLine = line;
pBreakPoints->push_back(bP);
}
ReleaseBreakPointAccess(breakPointsLockID);
return;
}
double CDebuggingServer::AquireBreakPointAccess(std::list<CBreakPoint>** breakPoints)
{
int ret;
ret = SDL_SemWait(m_BreakPointsSem);
ENSURE(ret == 0);
(*breakPoints) = &m_BreakPoints;
m_BreakPointsLockID = timer_Time();
return m_BreakPointsLockID;
}
void CDebuggingServer::ReleaseBreakPointAccess(double breakPointsLockID)
{
ENSURE(m_BreakPointsLockID == breakPointsLockID);
SDL_SemPost(m_BreakPointsSem);
}