Arquivos
hhvm/hphp/runtime/eval/debugger/debugger_client.cpp
T
Edwin Smith f29ee5314d Remove String::operator const char*().
Too many ways to shoot self in foot with this gem.
2013-04-25 11:34:21 -07:00

2426 linhas
72 KiB
C++

/*
+----------------------------------------------------------------------+
| HipHop for PHP |
+----------------------------------------------------------------------+
| Copyright (c) 2010- Facebook, Inc. (http://www.facebook.com) |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
*/
#include <runtime/eval/debugger/debugger_client.h>
#include <runtime/eval/debugger/debugger_command.h>
#include <runtime/eval/debugger/cmd/all.h>
#include <runtime/base/complex_types.h>
#include <runtime/base/variable_serializer.h>
#include <runtime/base/string_util.h>
#include <runtime/base/preg.h>
#include <runtime/ext/ext_json.h>
#include <runtime/ext/ext_socket.h>
#include <runtime/ext/ext_network.h>
#include <util/text_color.h>
#include <util/text_art.h>
#include <util/logger.h>
#include <util/process.h>
#include <boost/format.hpp>
#include <boost/scoped_ptr.hpp>
#define USE_VARARGS
#define PREFER_STDARG
#include <readline/readline.h>
#include <readline/history.h>
using namespace HPHP::Util::TextArt;
#define PHP_WORD_BREAK_CHARACTERS " \t\n\"\\'`@=;,|{[()]}+*%^!~&"
namespace HPHP { namespace Eval {
///////////////////////////////////////////////////////////////////////////////
static const Trace::Module TRACEMOD = Trace::debugger;
static boost::scoped_ptr<DebuggerClient> debugger_client;
static DebuggerClient& getStaticDebuggerClient() {
TRACE(2, "DebuggerClient::getStaticDebuggerClient\n");
/*
* DebuggerClient acquires global mutexes in its constructor, so we
* allocate s_debugger_client lazily to ensure that all of the
* global mutexes have been initialized before we enter the
* constructor.
*
* This initialization is thread-safe because program_functions.cpp
* must call Debugger::StartClient (which ends up here) before any
* additional threads are created.
*/
if (!debugger_client) {
debugger_client.reset(new DebuggerClient);
}
return *debugger_client;
}
void shutdown_hphpd() {
TRACE(2, "DebuggerClient::shutdown_hphpd\n");
if (debugger_client) {
debugger_client->resetSmartAllocatedMembers();
debugger_client.reset();
}
}
///////////////////////////////////////////////////////////////////////////////
// readline setups
static char* debugger_generator(const char* text, int state) {
TRACE(2, "DebuggerClient::debugger_generator\n");
return getStaticDebuggerClient().getCompletion(text, state);
}
static char **debugger_completion(const char *text, int start, int end) {
TRACE(2, "DebuggerClient::debugger_completion\n");
if (getStaticDebuggerClient().setCompletion(text, start, end)) {
return rl_completion_matches((char*)text, &debugger_generator);
}
return nullptr;
}
static void debugger_signal_handler(int sig) {
TRACE(2, "DebuggerClient::debugger_signal_handler\n");
getStaticDebuggerClient().onSignal(sig);
}
void DebuggerClient::onSignal(int sig) {
TRACE(2, "DebuggerClient::onSignal\n");
if (m_inputState == TakingInterrupt) {
time_t now = time(0);
if (m_sigTime) {
int secWait = 10;
if (now - m_sigTime > secWait) {
error("Program is not responding. Please restart debugger to get a "
"new connection.");
quit();
return;
}
info("Please wait. If not responding in %d seconds, "
"press Ctrl-C again to quit.", secWait);
} else {
info("Pausing program execution, please wait...");
usageLogSignal();
m_sigTime = now;
}
m_signum = CmdSignal::SignalBreak;
} else {
rl_replace_line("", 0);
rl_redisplay();
}
}
int DebuggerClient::pollSignal() {
TRACE(2, "DebuggerClient::pollSignal\n");
int ret = m_signum;
m_signum = CmdSignal::SignalNone;
return ret;
}
///////////////////////////////////////////////////////////////////////////////
/**
* Initialization and shutdown.
*/
class ReadlineApp {
public:
ReadlineApp() {
TRACE(2, "ReadlineApp::ReadlineApp\n");
DebuggerClient::AdjustScreenMetrics();
rl_attempted_completion_function = debugger_completion;
rl_basic_word_break_characters = PHP_WORD_BREAK_CHARACTERS;
rl_catch_signals = 0;
signal(SIGINT, debugger_signal_handler);
TRACE(3, "ReadlineApp::ReadlineApp, about to call read_history\n");
read_history((Process::GetHomeDirectory() +
DebuggerClient::HistoryFileName).c_str());
TRACE(3, "ReadlineApp::ReadlineApp, done calling read_history\n");
}
~ReadlineApp() {
TRACE(2, "ReadlineApp::~ReadlineApp\n");
write_history((Process::GetHomeDirectory() +
DebuggerClient::HistoryFileName).c_str());
}
};
/**
* Displaying a spinning wait icon.
*/
class ReadlineWaitCursor {
public:
ReadlineWaitCursor()
: m_thread(this, &ReadlineWaitCursor::animate), m_waiting(true) {
TRACE(2, "ReadlineWaitCursor::ReadlineWaitCursor\n");
m_thread.start();
}
~ReadlineWaitCursor() {
TRACE(2, "ReadlineWaitCursor::~ReadlineWaitCursor\n");
m_waiting = false;
m_thread.waitForEnd();
}
void animate() {
TRACE(2, "ReadlineWaitCursor::animate\n");
m_line = rl_line_buffer;
while (m_waiting) {
frame('|'); frame('/'); frame('-'); frame('\\');
rl_replace_line(m_line.c_str(), 1);
rl_redisplay();
}
}
private:
AsyncFunc<ReadlineWaitCursor> m_thread;
bool m_waiting;
string m_line;
void frame(char ch) {
TRACE(2, "ReadlineWaitCursor::getStaticDebuggerClient\n");
string line = m_line + ch;
rl_replace_line(line.c_str(), 1);
rl_redisplay();
usleep(100000);
}
};
///////////////////////////////////////////////////////////////////////////////
int DebuggerClient::LineWidth = 76;
int DebuggerClient::CodeBlockSize = 20;
int DebuggerClient::ScrollBlockSize = 20;
const char *DebuggerClient::LineNoFormat = "%4d ";
const char *DebuggerClient::LocalPrompt = "hphpd";
const char *DebuggerClient::ConfigFileName = ".hphpd.hdf";
const char *DebuggerClient::HistoryFileName = ".hphpd.history";
std::string DebuggerClient::HomePrefix = "/home";
bool DebuggerClient::UseColor = true;
bool DebuggerClient::NoPrompt = false;
const char *DebuggerClient::HelpColor = nullptr;
const char *DebuggerClient::InfoColor = nullptr;
const char *DebuggerClient::OutputColor = nullptr;
const char *DebuggerClient::ErrorColor = nullptr;
const char *DebuggerClient::ItemNameColor = nullptr;
const char *DebuggerClient::HighlightForeColor = nullptr;
const char *DebuggerClient::HighlightBgColor = nullptr;
const char *DebuggerClient::DefaultCodeColors[] = {
/* None */ nullptr, nullptr,
/* Keyword */ nullptr, nullptr,
/* Comment */ nullptr, nullptr,
/* String */ nullptr, nullptr,
/* Variable */ nullptr, nullptr,
/* Html */ nullptr, nullptr,
/* Tag */ nullptr, nullptr,
/* Declaration */ nullptr, nullptr,
/* Constant */ nullptr, nullptr,
/* LineNo */ nullptr, nullptr,
};
void DebuggerClient::LoadColors(Hdf hdf) {
TRACE(2, "DebuggerClient::LoadColors\n");
HelpColor = LoadColor(hdf["Help"], "BROWN");
InfoColor = LoadColor(hdf["Info"], "GREEN");
OutputColor = LoadColor(hdf["Output"], "CYAN");
ErrorColor = LoadColor(hdf["Error"], "RED");
ItemNameColor = LoadColor(hdf["ItemName"], "GRAY");
HighlightForeColor = LoadColor(hdf["HighlightForeground"], "RED");
HighlightBgColor = LoadBgColor(hdf["HighlightBackground"], "GRAY");
Hdf code = hdf["Code"];
LoadCodeColor(CodeColorKeyword, code["Keyword"], "CYAN");
LoadCodeColor(CodeColorComment, code["Comment"], "RED");
LoadCodeColor(CodeColorString, code["String"], "GREEN");
LoadCodeColor(CodeColorVariable, code["Variable"], "BROWN");
LoadCodeColor(CodeColorHtml, code["Html"], "GRAY");
LoadCodeColor(CodeColorTag, code["Tag"], "MAGENTA");
LoadCodeColor(CodeColorDeclaration, code["Declaration"], "BLUE");
LoadCodeColor(CodeColorConstant, code["Constant"], "MAGENTA");
LoadCodeColor(CodeColorLineNo, code["LineNo"], "GRAY");
}
const char *DebuggerClient::LoadColor(Hdf hdf, const char *defaultName) {
TRACE(2, "DebuggerClient::LoadColor\n");
const char *name = hdf.get(defaultName);
hdf = name; // for starter
const char *color = get_color_by_name(name);
if (color == nullptr) {
Logger::Error("Bad color name %s", name);
color = get_color_by_name(defaultName);
}
return color;
}
const char *DebuggerClient::LoadBgColor(Hdf hdf, const char *defaultName) {
TRACE(2, "DebuggerClient::LoadBgColor\n");
const char *name = hdf.get(defaultName);
hdf = name; // for starter
const char *color = get_bgcolor_by_name(name);
if (color == nullptr) {
Logger::Error("Bad color name %s", name);
color = get_bgcolor_by_name(defaultName);
}
return color;
}
void DebuggerClient::LoadCodeColor(CodeColor index, Hdf hdf,
const char *defaultName) {
TRACE(2, "DebuggerClient::LoadCodeColor\n");
const char *color = LoadColor(hdf, defaultName);
DefaultCodeColors[index * 2] = color;
DefaultCodeColors[index * 2 + 1] = color ? ANSI_COLOR_END : nullptr;
}
SmartPtr<Socket> DebuggerClient::Start(const DebuggerClientOptions &options) {
TRACE(2, "DebuggerClient::Start\n");
Debugger::SetTextColors();
SmartPtr<Socket> ret = getStaticDebuggerClient().connectLocal();
getStaticDebuggerClient().start(options);
return ret;
}
void DebuggerClient::Stop() {
TRACE(2, "DebuggerClient::Stop\n");
getStaticDebuggerClient().stop();
}
void DebuggerClient::AdjustScreenMetrics() {
TRACE(2, "entered: DebuggerClient::AdjustScreenMetrics\n");
int rows = 0; int cols = 0;
rl_get_screen_size(&rows, &cols);
if (rows > 0 && cols > 0) {
LineWidth = cols - 4;
ScrollBlockSize = CodeBlockSize = rows - (rows >> 2);
}
TRACE(2, "leaving: DebuggerClient::AdjustScreenMetrics\n");
}
bool DebuggerClient::IsValidNumber(const std::string &arg) {
TRACE(2, "DebuggerClient::IsValidNumber\n");
if (arg.empty()) return false;
for (unsigned int i = 0; i < arg.size(); i++) {
if (!isdigit(arg[i])) {
return false;
}
}
return true;
}
String DebuggerClient::FormatVariable(CVarRef v, int maxlen /* = 80 */,
bool vardump /* = false */) {
TRACE(2, "DebuggerClient::FormatVariable\n");
String value;
if (maxlen <= 0) {
try {
VariableSerializer::Type t = vardump ?
VariableSerializer::VarDump :
VariableSerializer::PrintR;
VariableSerializer vs(t, 0, 2);
value = vs.serialize(v, true);
} catch (StringBufferLimitException &e) {
value = "Serialization limit reached";
} catch (...) {
assert(false);
throw;
}
} else {
VariableSerializer vs(VariableSerializer::DebuggerDump, 0, 2);
value = vs.serializeWithLimit(v, maxlen);
}
if (maxlen <= 0 || value.length() - maxlen < 30) {
return value;
}
StringBuffer sb;
sb.append(value.substr(0, maxlen));
sb.append(" ...(omitted)");
return sb.detach();
}
String DebuggerClient::FormatInfoVec(const IDebuggable::InfoVec &info,
int *nameLen /* = NULL */) {
TRACE(2, "DebuggerClient::FormatInfoVec\n");
// vertical align names
int maxlen = 0;
for (unsigned int i = 0; i < info.size(); i++) {
int len = strlen(info[i].first);
if (len > maxlen) maxlen = len;
}
// print
StringBuffer sb;
for (unsigned int i = 0; i < info.size(); i++) {
if (ItemNameColor) sb.append(ItemNameColor);
string name = info[i].first;
name += ": ";
sb.append(name.substr(0, maxlen + 4));
if (ItemNameColor) sb.append(ANSI_COLOR_END);
if (OutputColor) sb.append(OutputColor);
sb.append(info[i].second);
if (OutputColor) sb.append(ANSI_COLOR_END);
sb.append("\n");
}
if (nameLen) *nameLen = maxlen + 4;
return sb.detach();
}
String DebuggerClient::FormatTitle(const char *title) {
TRACE(2, "DebuggerClient::FormatTitle\n");
String dash = StringUtil::Repeat(BOX_H, (LineWidth - strlen(title)) / 2 - 4);
StringBuffer sb;
sb.append("\n");
sb.append(" ");
sb.append(dash);
sb.append(" "); sb.append(title); sb.append(" ");
sb.append(dash);
sb.append("\n");
return sb.detach();
}
///////////////////////////////////////////////////////////////////////////////
DebuggerClient::DebuggerClient(std::string name /* = "" */)
: m_tutorial(0), m_printFunction(""),
m_logFile(""), m_logFileHandler(nullptr),
m_mainThread(this, &DebuggerClient::run), m_stopped(false),
m_quitting(false),
m_inputState(TakingCommand), m_runState(NotYet),
m_signum(CmdSignal::SignalNone), m_sigTime(0),
m_acLen(0), m_acIndex(0), m_acPos(0), m_acLiveListsDirty(true),
m_threadId(0), m_listLine(0), m_listLineFocus(0), m_frame(0),
m_clientState(StateUninit), m_inApiUse(false),
m_nameForApi(name), m_usageLogFP(nullptr) {
TRACE(2, "DebuggerClient::DebuggerClient\n");
initUsageLogging();
}
DebuggerClient::~DebuggerClient() {
TRACE(2, "DebuggerClient::~DebuggerClient\n");
m_stopped = true;
m_mainThread.waitForEnd();
FILE *f = getLogFileHandler();
if (f != nullptr) {
fclose(f);
setLogFileHandler(nullptr);
}
finiUsageLogging();
}
void DebuggerClient::reset() {
TRACE(2, "DebuggerClient::reset\n");
for (unsigned int i = 0; i < m_machines.size(); i++) {
m_machines[i]->m_thrift.close();
}
m_stacktrace.reset();
m_acItems.clear();
m_acLiveLists.reset();
m_machines.clear();
m_machine.reset();
}
bool DebuggerClient::isLocal() {
TRACE(2, "DebuggerClient::isLocal\n");
return !isApiMode() && m_machines[0] == m_machine;
}
bool DebuggerClient::connect(const std::string &host, int port) {
TRACE(2, "DebuggerClient::connect\n");
assert(isApiMode() ||
(!m_machines.empty() && m_machines[0]->m_name == LocalPrompt));
for (unsigned int i = 1; i < m_machines.size(); i++) {
if (f_gethostbyname(m_machines[i]->m_name) ==
f_gethostbyname(host)) {
switchMachine(m_machines[i]);
return false;
}
}
return connectRemote(host, port);
}
bool DebuggerClient::connectRPC(const std::string &host, int port) {
TRACE(2, "DebuggerClient::connectRPC\n");
assert(!m_machines.empty());
DMachineInfoPtr local = m_machines[0];
assert(local->m_name == LocalPrompt);
local->m_rpcHost = host;
local->m_rpcPort = port;
switchMachine(local);
m_rpcHost = "rpc:" + host;
return !local->m_interrupting;
}
bool DebuggerClient::disconnect() {
TRACE(2, "DebuggerClient::disconnect\n");
assert(!m_machines.empty());
DMachineInfoPtr local = m_machines[0];
assert(local->m_name == LocalPrompt);
local->m_rpcHost.clear();
local->m_rpcPort = 0;
switchMachine(local);
return !local->m_interrupting;
}
void DebuggerClient::switchMachine(DMachineInfoPtr machine) {
TRACE(2, "DebuggerClient::switchMachine\n");
m_rpcHost.clear();
machine->m_initialized = false; // even if m_machine == machine
if (m_machine != machine) {
m_machine = machine;
m_sandboxes.clear();
m_threads.clear();
m_threadId = 0;
m_breakpoint.reset();
m_matched.clear();
m_listFile.clear();
m_listLine = 0;
m_listLineFocus = 0;
m_stacktrace.reset();
m_frame = 0;
}
}
SmartPtr<Socket> DebuggerClient::connectLocal() {
TRACE(2, "DebuggerClient::connectLocal\n");
int fds[2];
if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) != 0) {
throw Exception("unable to create socket pair for local debugging");
}
SmartPtr<Socket> socket1(new Socket(fds[0], AF_UNIX));
SmartPtr<Socket> socket2(new Socket(fds[1], AF_UNIX));
socket1->unregister();
socket2->unregister();
DMachineInfoPtr machine(new DMachineInfo());
machine->m_sandboxAttached = true;
machine->m_name = LocalPrompt;
machine->m_thrift.create(socket1);
assert(m_machines.empty());
m_machines.push_back(machine);
switchMachine(machine);
return socket2;
}
bool DebuggerClient::connectRemote(const std::string &host, int port) {
TRACE(2, "DebuggerClient::connectRemote\n");
if (port <= 0) {
port = RuntimeOption::DebuggerServerPort;
}
info("Connecting to %s:%d...", host.c_str(), port);
Socket *sock = new Socket(socket(PF_INET, SOCK_STREAM, 0), PF_INET,
host.c_str(), port);
// Ensure the socket is not swept---it is cached across requests in
// API mode, and in client mode we expect to destruct it ourselves
// when ~DebuggerClient runs.
sock->unregister();
Object obj(sock);
if (f_socket_connect(sock, String(host), port)) {
DMachineInfoPtr machine(new DMachineInfo());
machine->m_name = host;
machine->m_port = port;
machine->m_thrift.create(SmartPtr<Socket>(sock));
m_machines.push_back(machine);
switchMachine(machine);
return true;
}
error("Unable to connect to %s:%d.", host.c_str(), port);
return false;
}
bool DebuggerClient::reconnect() {
TRACE(2, "DebuggerClient::reconnect\n");
assert(m_machine);
string &host = m_machine->m_name;
int port = m_machine->m_port;
if (port) {
info("Re-connecting to %s:%d...", host.c_str(), port);
Socket *sock = new Socket(socket(PF_INET, SOCK_STREAM, 0), PF_INET,
host.c_str(), port);
sock->unregister();
Object obj(sock);
if (f_socket_connect(sock, String(host), port)) {
for (unsigned int i = 0; i < m_machines.size(); i++) {
if (m_machines[i] == m_machine) {
m_machines.erase(m_machines.begin() + i);
break;
}
}
DMachineInfoPtr machine(new DMachineInfo());
machine->m_name = host;
machine->m_port = port;
machine->m_thrift.create(SmartPtr<Socket>(sock));
switchMachine(machine);
return true;
}
error("Still unable to connect to %s:%d.", host.c_str(), port);
}
return false;
}
std::string DebuggerClient::getPrompt() {
TRACE(2, "DebuggerClient::getPrompt\n");
if (NoPrompt) {
return "";
}
string *name = &m_machine->m_name;
if (!m_rpcHost.empty()) {
name = &m_rpcHost;
}
if (m_inputState == TakingCode) {
string prompt = " ";
for (unsigned int i = 2; i < name->size() + 2; i++) {
prompt += '.';
}
prompt += ' ';
return prompt;
}
return *name + "> ";
}
void DebuggerClient::init(const DebuggerClientOptions &options) {
TRACE(2, "DebuggerClient::init\n");
m_options = options;
if (isApiMode()) {
if (options.user.empty()) {
return;
}
m_configFileName = options.configFName;
if (m_configFileName.empty()) {
m_configFileName = HomePrefix + "/" + options.user + "/" +
ConfigFileName;
}
} else {
if (!options.configFName.empty()) {
m_configFileName = options.configFName;
}
m_options.user = Process::GetCurrentUser();
}
usageLogInit();
loadConfig();
if (!options.cmds.empty()) {
UseColor = false;
s_use_utf8 = false;
NoPrompt = true;
}
if (options.apiMode) {
UseColor = false;
NoPrompt = true;
m_outputBuf.clear();
}
if (!NoPrompt) {
info("Welcome to HipHop Debugger!");
info("Type \"help\" or \"?\" for a complete list of commands.\n");
}
if (!options.host.empty()) {
connectRemote(options.host, options.port);
}
}
void DebuggerClient::start(const DebuggerClientOptions &options) {
TRACE(2, "DebuggerClient::start\n");
init(options);
m_mainThread.start();
}
void DebuggerClient::stop() {
TRACE(2, "DebuggerClient::stop\n");
m_stopped = true;
m_mainThread.waitForEnd();
}
void DebuggerClient::run() {
TRACE(2, "DebuggerClient::run\n");
// Make sure we don't run the interface thread for API mode
assert(!isApiMode());
ReadlineApp app;
TRACE(3, "DebuggerClient::run, about to call playMacro\n");
playMacro("startup");
if (!m_options.cmds.empty()) {
m_macroPlaying = MacroPtr(new Macro());
m_macroPlaying->m_cmds = m_options.cmds;
m_macroPlaying->m_cmds.push_back("q");
m_macroPlaying->m_index = 0;
}
hphp_session_init();
ExecutionContext *context = hphp_context_init();
if (m_options.extension.empty()) {
hphp_invoke_simple("", true); // warm-up only
} else {
hphp_invoke_simple(m_options.extension);
}
while (true) {
try {
runImpl();
} catch (DebuggerServerLostException &e) {
if (reconnect()) {
continue;
}
} catch (...) {
Logger::Error("Unhandled exception from DebuggerClient::runImpl().");
}
break;
}
// We are about to exit from client and idealy we should cleanup,
// but we the reset() where will try to cleanup Socket under DMachineInfo,
// which is created by another thread. If it's cleaned up here, later we'll
// have a SEGV when trying to sweep the object from the other thread.
// We should refactor the code to avoid Sweepable object being passed across
// threads later.
// reset();
hphp_context_exit(context, false);
hphp_session_exit();
}
///////////////////////////////////////////////////////////////////////////////
// auto-complete
void DebuggerClient::updateLiveLists() {
TRACE(2, "DebuggerClient::updateLiveLists\n");
ReadlineWaitCursor waitCursor;
CmdInfo::UpdateLiveLists(this);
m_acLiveListsDirty = false;
}
void DebuggerClient::promptFunctionPrototype() {
TRACE(2, "DebuggerClient::promptFunctionPrototype\n");
if (m_acProtoTypePrompted) return;
m_acProtoTypePrompted = true;
const char *p0 = rl_line_buffer;
int len = strlen(p0);
if (len < 2) return;
const char *pLast = p0 + len - 1;
while (pLast > p0 && isspace(*pLast)) --pLast;
if (pLast == p0 || *pLast-- != '(') return;
while (pLast > p0 && isspace(*pLast)) --pLast;
const char *p = pLast;
while (p >= p0 && (isalnum(*p) || *p == '_')) --p;
if (p == pLast) return;
string cls;
string func(p + 1, pLast - p);
if (p > p0 && *p-- == ':' && *p-- == ':') {
pLast = p;
while (p >= p0 && (isalnum(*p) || *p == '_')) --p;
if (pLast > p) {
cls = string(p + 1, pLast - p);
}
}
String output = highlight_code(CmdInfo::GetProtoType(this, cls, func));
print("\n%s", output.data());
rl_forced_update_display();
}
bool DebuggerClient::setCompletion(const char *text, int start, int end) {
TRACE(2, "DebuggerClient::setCompletion\n");
if (m_inputState == TakingCommand) {
parseCommand(rl_line_buffer);
if (*text) {
if (!m_args.empty()) {
m_args.resize(m_args.size() - 1);
} else {
m_command.clear();
}
}
}
return true;
}
void DebuggerClient::addCompletion(AutoComplete type) {
TRACE(2, "DebuggerClient::addCompletion(AutoComplete type)\n");
if (type < 0 || type >= AutoCompleteCount) {
Logger::Error("Invalid auto completion enum: %d", type);
return;
}
if (type == AutoCompleteCode) {
addCompletion(AutoCompleteVariables);
addCompletion(AutoCompleteConstants);
addCompletion(AutoCompleteClasses);
addCompletion(AutoCompleteFunctions);
addCompletion(AutoCompleteClassMethods);
addCompletion(AutoCompleteClassProperties);
addCompletion(AutoCompleteClassConstants);
addCompletion(AutoCompleteKeyword);
} else if (type == AutoCompleteKeyword) {
addCompletion(PHP_KEYWORDS);
} else {
m_acLists.push_back((const char **)type);
}
if (type == AutoCompleteFunctions || type == AutoCompleteClassMethods) {
rl_completion_suppress_append = 1;
promptFunctionPrototype();
}
}
void DebuggerClient::addCompletion(const char **list) {
TRACE(2, "DebuggerClient::addCompletion(const char **list)\n");
m_acLists.push_back(list);
}
void DebuggerClient::addCompletion(const char *name) {
TRACE(2, "DebuggerClient::addCompletion(const char *name)\n");
m_acStrings.push_back(name);
}
void DebuggerClient::addCompletion(const std::vector<std::string> &items) {
TRACE(2, "DebuggerClient::addCompletion(const std::vector<std::string>)\n");
m_acItems.insert(m_acItems.end(), items.begin(), items.end());
}
char *DebuggerClient::getCompletion(const std::vector<std::string> &items,
const char *text) {
TRACE(2, "DebuggerClient::getCompletion(const std::vector<std::string>\n");
while (++m_acPos < (int)items.size()) {
const char *p = items[m_acPos].c_str();
if (m_acLen == 0 || strncasecmp(p, text, m_acLen) == 0) {
return strdup(p);
}
}
m_acPos = -1;
return nullptr;
}
std::vector<std::string> DebuggerClient::getAllCompletions(
std::string const &text) {
TRACE(2, "DebuggerClient::getAllCompletions\n");
std::vector<std::string> res;
if (m_acLiveListsDirty) {
updateLiveLists();
}
for (int i = 0; i < AutoCompleteCount; ++i) {
const std::vector<std::string> &items = (*m_acLiveLists)[i];
for (size_t j = 0; j < items.size(); ++j) {
const char *p = items[j].c_str();
if (strncasecmp(p, text.c_str(), text.length()) == 0) {
res.push_back(std::string(p));
}
}
}
return res;
}
char *DebuggerClient::getCompletion(const std::vector<const char *> &items,
const char *text) {
TRACE(2, "DebuggerClient::getCompletion(const std::vector<const char *>\n");
while (++m_acPos < (int)items.size()) {
const char *p = items[m_acPos];
if (m_acLen == 0 || strncasecmp(p, text, m_acLen) == 0) {
return strdup(p);
}
}
m_acPos = -1;
return nullptr;
}
static char first_non_whitespace(const char *s) {
TRACE(2, "DebuggerClient::first_non_whitespace\n");
while (*s && isspace(*s)) s++;
return *s;
}
char *DebuggerClient::getCompletion(const char *text, int state) {
TRACE(2, "DebuggerClient::getCompletion\n");
if (state == 0) {
m_acLen = strlen(text);
m_acIndex = 0;
m_acPos = -1;
m_acLists.clear();
m_acStrings.clear();
m_acItems.clear();
m_acProtoTypePrompted = false;
if (m_inputState == TakingCommand) {
switch (first_non_whitespace(rl_line_buffer)) {
case '<':
if (strncasecmp(m_command.substr(0, 5).c_str(), "<?php", 5)) {
addCompletion("<?php");
break;
}
case '@':
case '=':
case '$': {
addCompletion(AutoCompleteCode);
break;
}
default: {
if (m_command.empty()) {
addCompletion(GetCommands());
addCompletion("@");
addCompletion("=");
addCompletion("<?php");
addCompletion("?>");
} else {
DebuggerCommand *cmd = createCommand();
if (cmd) {
if (cmd->is(DebuggerCommand::KindOfRun)) playMacro("startup");
DebuggerCommandPtr deleter(cmd);
cmd->list(this);
}
}
break;
}
}
} else {
assert(m_inputState == TakingCode);
if (!*rl_line_buffer) {
addCompletion("?>"); // so we tab, we're done
} else {
addCompletion(AutoCompleteCode);
}
}
}
for (; m_acIndex < (int)m_acLists.size(); m_acIndex++) {
const char **list = m_acLists[m_acIndex];
if ((int64_t)list == AutoCompleteFileNames) {
char *p = rl_filename_completion_function(text, ++m_acPos);
if (p) return p;
} else if ((int64_t)list >= 0 && (int64_t)list < AutoCompleteCount) {
if (m_acLiveListsDirty) {
updateLiveLists();
assert(!m_acLiveListsDirty);
}
char *p = getCompletion((*m_acLiveLists)[(int64_t)list], text);
if (p) return p;
} else {
for (const char *p = list[++m_acPos]; p; p = list[++m_acPos]) {
if (m_acLen == 0 || strncasecmp(p, text, m_acLen) == 0) {
return strdup(p);
}
}
}
m_acPos = -1;
}
char *p = getCompletion(m_acStrings, text);
if (p) return p;
return getCompletion(m_acItems, text);
}
///////////////////////////////////////////////////////////////////////////////
// main
bool DebuggerClient::initializeMachine() {
TRACE(2, "DebuggerClient::initializeMachine\n");
if (!m_machine->m_initialized) {
// set/clear intercept for RPC thread
if (!m_machines.empty() && m_machine == m_machines[0]) {
CmdMachine::UpdateIntercept(this, m_machine->m_rpcHost,
m_machine->m_rpcPort);
}
// upload breakpoints
if (!m_breakpoints.empty()) {
info("Updating breakpoints...");
CmdBreak().update(this);
}
// attaching to default sandbox
int waitForgSandbox = false;
if (!m_machine->m_sandboxAttached) {
const char *user = m_options.user.empty() ?
nullptr : m_options.user.c_str();
m_machine->m_sandboxAttached = (waitForgSandbox =
CmdMachine::AttachSandbox(this, user, m_options.sandbox.c_str()));
if (!m_machine->m_sandboxAttached) {
Logger::Error("Unable to communicate with default sandbox.");
}
}
m_machine->m_initialized = true;
if (!isApiMode() && waitForgSandbox) {
// Throw exception here to wait for next interrupt from server
throw DebuggerConsoleExitException();
}
}
return true;
}
DebuggerCommandPtr DebuggerClient::waitForNextInterrupt() {
TRACE(2, "DebuggerClient::waitForNextInterrupt\n");
const char *func = "DebuggerClient::waitForNextInterrupt()";
while (!m_stopped) {
DebuggerCommandPtr cmd;
if (DebuggerCommand::Receive(m_machine->m_thrift, cmd, func)) {
if (!cmd) {
Logger::Error("Unable to communicate with server.");
return DebuggerCommandPtr();
}
if (cmd->is(DebuggerCommand::KindOfSignal)) {
if (!cmd->onClientD(this)) {
Logger::Error("Unable to handle signal command.");
return DebuggerCommandPtr();
}
continue;
}
if (!cmd->is(DebuggerCommand::KindOfInterrupt)) {
int expected = DebuggerCommand::KindOfInterrupt;
if (!m_pendingCommands.empty() &&
cmd->is((DebuggerCommand::Type)m_pendingCommands.back())) {
m_pendingCommands.pop_back();
// found a match, fall through
} else {
Logger::Error("Bad cmd type: %d expecting %d", cmd->getType(),
expected);
return DebuggerCommandPtr();
}
}
m_sigTime = 0;
m_machine->m_interrupting = true;
setClientState(StateReadyForCommand);
m_inputState = TakingCommand;
usageLogInterrupt(cmd);
return cmd;
}
}
return DebuggerCommandPtr();
}
void DebuggerClient::runImpl() {
TRACE(2, "DebuggerClient::runImpl\n");
const char *func = "DebuggerClient::runImpl()";
try {
while (!m_stopped) {
DebuggerCommandPtr cmd;
if (DebuggerCommand::Receive(m_machine->m_thrift, cmd, func)) {
if (!cmd) {
Logger::Error("Unable to communicate with server. Server's down?");
throw DebuggerServerLostException();
}
if (cmd->is(DebuggerCommand::KindOfSignal)) {
if (!cmd->onClientD(this)) {
Logger::Error("%s: unable to poll signal", func);
return;
}
continue;
}
if (!cmd->is(DebuggerCommand::KindOfInterrupt)) {
Logger::Error("%s: bad cmd type: %d", func, cmd->getType());
return;
}
m_sigTime = 0;
usageLogInterrupt(cmd);
{
if (!cmd->onClientD(this)) {
Logger::Error("%s: unable to process %d", func, cmd->getType());
return;
}
}
m_machine->m_interrupting = true;
setClientState(StateReadyForCommand);
m_inputState = TakingCommand;
if (!m_machine->m_initialized) {
try {
if (!initializeMachine()) {
return;
}
} catch (DebuggerConsoleExitException &e) {
continue;
}
}
if (!console()) {
return;
}
setClientState(StateBusy);
m_inputState = TakingInterrupt;
}
}
} catch (DebuggerClientExitException &e) { /* normal exit */ }
}
bool DebuggerClient::console() {
TRACE(2, "DebuggerClient::console\n");
while (true) {
const char *line = nullptr;
string holder;
if (m_macroPlaying) {
if (m_macroPlaying->m_index < m_macroPlaying->m_cmds.size()) {
holder = m_macroPlaying->m_cmds[m_macroPlaying->m_index++];
line = holder.c_str();
} else {
m_macroPlaying.reset();
}
}
if (line == nullptr) {
line = readline(getPrompt().c_str());
if (line == nullptr) {
// treat ^D as quit
try {
print("quit");
quit();
} catch (DebuggerClientExitException &e) {
return false;
}
}
} else if (!NoPrompt) {
print("%s%s", getPrompt().c_str(), line);
}
if (*line && !m_macroPlaying) {
// even if line is bad command, we still want to remember it, so
// people can go back and fix typos
add_history(line);
}
AdjustScreenMetrics();
if (*line) {
if (parse(line)) {
try {
record(line);
m_prevCmd = m_command;
if (!process()) {
error("command \"" + m_command + "\" not found");
m_command.clear();
}
} catch (DebuggerClientExitException &e) {
return false;
} catch (DebuggerConsoleExitException &e) {
return true;
} catch (DebuggerProtocolException &e) {
return false;
}
}
} else if (m_inputState == TakingCommand) {
switch (m_prevCmd[0]) {
case 'l': // list
m_args.clear(); // as if just "list"
// fall through
case 'c': // continue
case 's': // step
case 'n': // next
case 'o': // out
try {
record(line);
m_command = m_prevCmd;
process(); // replay the same command
} catch (DebuggerConsoleExitException &e) {
return true;
} catch (DebuggerProtocolException &e) {
return false;
}
break;
}
}
}
return true;
}
///////////////////////////////////////////////////////////////////////////////
// output functions
String DebuggerClient::getPrintString() {
TRACE(2, "DebuggerClient::getPrintString\n");
String s(m_outputBuf); // makes a copy;
m_outputBuf.clear();
return s;
}
Array DebuggerClient::getOutputArray() {
TRACE(2, "DebuggerClient::getOutputArray\n");
Array ret;
switch (m_outputType) {
case OTCodeLoc:
ret.set("output_type", "code_loc");
ret.set("file", m_otFile);
ret.set("line_no", m_otLineNo);
ret.set("watch_values", m_otValues);
break;
case OTStacktrace:
ret.set("output_type", "stacktrace");
ret.set("stacktrace", m_stacktrace);
ret.set("frame", m_frame);
break;
case OTValues:
ret.set("output_type", "values");
ret.set("values", m_otValues);
break;
case OTText:
ret.set("output_type", "text");
break;
default:
ret.set("output_type", "invalid");
}
ret.set("cmd", m_command);
ret.set("text", getPrintString());
return ret;
}
//
// Called when a breakpoint is reached, to produce the console
// spew showing the code around the breakpoint.
//
void DebuggerClient::shortCode(BreakPointInfoPtr bp) {
TRACE(2, "DebuggerClient::shortCode\n");
if (bp && !bp->m_file.empty() && bp->m_line1) {
Variant source = CmdList::GetSourceFile(this, bp->m_file);
if (source.isString()) {
// Line and column where highlight should start and end
int beginHighlightLine = bp->m_line1;
int beginHighlightColumn = bp->m_char1;
int endHighlightLine = bp->m_line2;
int endHighlightColumn = bp->m_char2;
// Lines where source listing should start and end
int firstLine = std::max(beginHighlightLine - 1, 1);
int lastLine = endHighlightLine + 1;
int maxLines = getDebuggerClientMaxCodeLines();
// If MaxCodeLines == 0: don't spew any code after a [s]tep or [n]ext
// command.
if (maxLines == 0) {
return;
}
// If MaxCodeLines > 0: limit spew to a maximum of # lines.
if (maxLines > 0) {
int numHighlightLines = endHighlightLine - beginHighlightLine + 1;
if (numHighlightLines > maxLines) {
// If there are too many highlight lines, truncate spew
// by setting lastLine ...
lastLine = beginHighlightLine + maxLines - 1;
// ... and set endHighlightLine/Column so that it is just past the end
// of the spew, and all code up to the truncation will be highlighted.
endHighlightLine = lastLine + 1;
endHighlightColumn = 1;
}
}
code(source, beginHighlightLine,
firstLine, lastLine,
beginHighlightColumn,
endHighlightLine,
endHighlightColumn);
}
}
}
bool DebuggerClient::code(CStrRef source, int lineFocus, int line1 /* = 0 */,
int line2 /* = 0 */, int charFocus0 /* = 0 */,
int lineFocus1 /* = 0 */, int charFocus1 /* = 0 */) {
TRACE(2, "DebuggerClient::code\n");
if (line1 == 0 && line2 == 0) {
String highlighted;
if (isApiMode()) {
highlighted = source;
} else {
highlighted = highlight_code(source, 0, lineFocus, charFocus0,
lineFocus1, charFocus1);
}
if (!highlighted.empty()) {
print(highlighted);
return true;
}
return false;
}
String highlighted;
if (isApiMode()) {
highlighted = source;
} else {
highlighted = highlight_php(source, 1, lineFocus, charFocus0,
lineFocus1, charFocus1);
}
int line = 1;
const char *begin = highlighted.data();
StringBuffer sb;
for (const char *p = begin; *p; p++) {
if (*p == '\n') {
if (line >= line1) {
sb.append(begin, p - begin + 1);
}
if (++line > line2) break;
begin = p + 1;
}
}
if (!sb.empty()) {
print("%s%s", sb.data(), isApiMode() ? "\0" : ANSI_COLOR_END);
return true;
}
return false;
}
char DebuggerClient::ask(const char *fmt, ...) {
TRACE(2, "DebuggerClient::ask\n");
assert(!isApiMode());
string msg;
va_list ap;
va_start(ap, fmt);
Util::string_vsnprintf(msg, fmt, ap); va_end(ap);
if (UseColor && InfoColor) {
msg = InfoColor + msg + ANSI_COLOR_END;
}
fwrite(msg.data(), 1, msg.length(), stdout);
fflush(stdout);
return tolower(getchar());
}
#define FWRITE(ptr, size, nmemb, stream) \
do { \
if (isApiMode()) { \
m_outputBuf.append(ptr, size * nmemb); \
} \
\
/* LogFile debugger setting */ \
FILE *f = getLogFileHandler(); \
if (f != nullptr) { \
fwrite(ptr, size, nmemb, f); \
} \
\
/* For debugging, still output to stdout */ \
fwrite(ptr, size, nmemb, stream); \
} while (0) \
void DebuggerClient::print(const char *fmt, ...) {
TRACE(2, "DebuggerClient::print(const char *fmt, ...)\n");
string msg;
va_list ap;
va_start(ap, fmt);
Util::string_vsnprintf(msg, fmt, ap); va_end(ap);
print(msg);
}
void DebuggerClient::print(const std::string &s) {
TRACE(2, "DebuggerClient::print(const std::string &s)\n");
FWRITE(s.data(), 1, s.length(), stdout);
FWRITE("\n", 1, 1, stdout);
fflush(stdout);
}
void DebuggerClient::print(CStrRef msg) {
TRACE(2, "DebuggerClient::print(CStrRef msg)\n");
FWRITE(msg.data(), 1, msg.length(), stdout);
FWRITE("\n", 1, 1, stdout);
fflush(stdout);
}
#define IMPLEMENT_COLOR_OUTPUT(name, where, color) \
void DebuggerClient::name(CStrRef msg) { \
if (UseColor && color) { \
FWRITE(color, 1, strlen(color), where); \
} \
FWRITE(msg.data(), 1, msg.length(), where); \
if (UseColor && color) { \
FWRITE(ANSI_COLOR_END, 1, strlen(ANSI_COLOR_END), where); \
} \
FWRITE("\n", 1, 1, where); \
fflush(where); \
} \
void DebuggerClient::name(const char *fmt, ...) { \
string msg; \
va_list ap; \
va_start(ap, fmt); \
Util::string_vsnprintf(msg, fmt, ap); va_end(ap); \
name(msg); \
} \
void DebuggerClient::name(const std::string &msg) { \
name(String(msg.data(), msg.length(), AttachLiteral)); \
} \
IMPLEMENT_COLOR_OUTPUT(help, stdout, HelpColor);
IMPLEMENT_COLOR_OUTPUT(info, stdout, InfoColor);
IMPLEMENT_COLOR_OUTPUT(output, stdout, OutputColor);
IMPLEMENT_COLOR_OUTPUT(error, stderr, ErrorColor);
#undef FWRITE
#undef IMPLEMENT_COLOR_OUTPUT
string DebuggerClient::wrap(const std::string &s) {
TRACE(2, "DebuggerClient::wrap\n");
String ret = StringUtil::WordWrap(String(s.c_str(), s.size(), AttachLiteral),
LineWidth - 4, "\n", true);
return string(ret.data(), ret.size());
}
void DebuggerClient::helpTitle(const char *title) {
TRACE(2, "DebuggerClient::helpTitle\n");
help(FormatTitle(title));
}
void DebuggerClient::helpCmds(const char *cmd, const char *desc, ...) {
TRACE(2, "DebuggerClient::helpCmds(const char *cmd, const char *desc,...)\n");
std::vector<const char *> cmds;
cmds.push_back(cmd);
cmds.push_back(desc);
va_list ap;
va_start(ap, desc);
const char *s = va_arg(ap, const char *);
while (s) {
cmds.push_back(s);
s = va_arg(ap, const char *);
}
va_end(ap);
helpCmds(cmds);
}
void DebuggerClient::helpCmds(const std::vector<const char *> &cmds) {
TRACE(2, "DebuggerClient::helpCmds(const std::vector<const char *> &cmds)\n");
int left = 0; int right = 0;
for (unsigned int i = 0; i < cmds.size(); i++) {
int &width = (i % 2 ? right : left);
int len = strlen(cmds[i]);
if (width < len) width = len;
}
int margin = 8;
int leftMax = LineWidth / 3 - margin;
int rightMax = LineWidth * 3 / 3 - margin;
if (left > leftMax) left = leftMax;
if (right > rightMax) right = rightMax;
StringBuffer sb;
for (unsigned int i = 0; i < cmds.size() - 1; i += 2) {
String cmd(cmds[i], AttachLiteral);
String desc(cmds[i+1], AttachLiteral);
// two special formats
if (cmd.empty() && desc.empty()) {
sb.append("\n");
continue;
}
if (desc.empty()) {
sb.append(FormatTitle(cmd.data()));
sb.append("\n");
continue;
}
cmd = StringUtil::WordWrap(cmd, left, "\n", true);
desc = StringUtil::WordWrap(desc, right, "\n", true);
Array lines1 = StringUtil::Explode(cmd, "\n").toArray();
Array lines2 = StringUtil::Explode(desc, "\n").toArray();
for (int n = 0; n < lines1.size() || n < lines2.size(); n++) {
StringBuffer line;
line.append(" ");
if (n) line.append(" ");
line.append(StringUtil::Pad(lines1[n], leftMax));
if (n == 0) line.append(" ");
line.append(" ");
line.append(lines2[n].toString());
sb.append(StringUtil::Trim(line.detach(), StringUtil::TrimRight));
sb.append("\n");
}
}
help(sb.detach());
}
void DebuggerClient::helpBody(const std::string &s) {
TRACE(2, "DebuggerClient::helpBody\n");
help("");
help(wrap(s));
help("");
}
void DebuggerClient::helpSection(const std::string &s) {
TRACE(2, "DebuggerClient::helpSection\n");
help(wrap(s));
}
void DebuggerClient::tutorial(const char *text) {
TRACE(2, "DebuggerClient::tutorial\n");
if (m_tutorial < 0) return;
String ret = String(text).replace("\t", " ");
ret = StringUtil::WordWrap(ret, LineWidth - 4, "\n", true);
Array lines = StringUtil::Explode(ret, "\n").toArray();
StringBuffer sb;
String header = " Tutorial - '[h]elp [t]utorial off' to turn off ";
String hr = StringUtil::Repeat(BOX_H, LineWidth - 2);
sb.append(BOX_UL); sb.append(hr); sb.append(BOX_UR); sb.append("\n");
int wh = (LineWidth - 2 - header.size()) / 2;
sb.append(BOX_V);
sb.append(StringUtil::Repeat(" ", wh));
sb.append(header);
sb.append(StringUtil::Repeat(" ", wh));
sb.append(BOX_V);
sb.append("\n");
sb.append(BOX_VL); sb.append(hr); sb.append(BOX_VR); sb.append("\n");
for (ArrayIter iter(lines); iter; ++iter) {
sb.append(BOX_V); sb.append(' ');
sb.append(StringUtil::Pad(iter.second(), LineWidth - 4));
sb.append(' '); sb.append(BOX_V); sb.append("\n");
}
sb.append(BOX_LL); sb.append(hr); sb.append(BOX_LR); sb.append("\n");
String content = sb.detach();
if (m_tutorial == 0) {
String hash = StringUtil::MD5(content);
if (m_tutorialVisited.find(hash.data()) != m_tutorialVisited.end()) {
return;
}
m_tutorialVisited.insert(hash.data());
saveConfig();
}
help(content);
}
void DebuggerClient::setTutorial(int mode) {
TRACE(2, "DebuggerClient::setTutorial\n");
if (m_tutorial != mode) {
m_tutorial = mode;
m_tutorialVisited.clear();
saveConfig();
}
}
///////////////////////////////////////////////////////////////////////////////
// command processing
const char **DebuggerClient::GetCommands() {
static const char *cmds[] = {
"abort", "break", "continue", "down", "exception",
"frame", "global", "help", "info",
"konstant", "list", "machine", "next", "out",
"print", "quit", "run", "step", "thread",
"up", "variable", "where", "x", "y",
"zend", "!", "&",
nullptr
};
return cmds;
}
void DebuggerClient::shiftCommand() {
TRACE(2, "DebuggerClient::shiftCommand\n");
if (m_command.size() > 1) {
m_args.insert(m_args.begin(), m_command.substr(1));
m_argIdx.insert(m_argIdx.begin(), 1);
m_command = m_command.substr(0, 1);
}
}
DebuggerCommand *DebuggerClient::createCommand() {
TRACE(2, "DebuggerClient::createCommand\n");
#define MATCH_CMD(name, cmd) \
do { \
if (match(name)) { \
m_commandCanonical = name; \
return new cmd(); \
} \
return nullptr; \
} while(0) \
#define NEW_CMD_NAME(name, cmd) \
do { \
m_commandCanonical = name; \
return new cmd(); \
} while(0) \
// give gdb users some love
if (m_command == "bt") NEW_CMD_NAME("where", CmdWhere);
if (m_command == "set") NEW_CMD_NAME("config", CmdConfig);
if (m_command == "inst") NEW_CMD_NAME("instrument", CmdInstrument);
if (m_command == "complete") NEW_CMD_NAME("complete", CmdComplete);
switch (tolower(m_command[0])) {
case 'a': MATCH_CMD("abort" , CmdAbort );
case 'b': MATCH_CMD("break" , CmdBreak );
case 'c': MATCH_CMD("continue" , CmdContinue );
case 'd': MATCH_CMD("down" , CmdDown );
case 'e': MATCH_CMD("exception", CmdException);
case 'f': MATCH_CMD("frame" , CmdFrame );
case 'g': MATCH_CMD("global" , CmdGlobal );
case 'h': MATCH_CMD("help" , CmdHelp );
case 'i': MATCH_CMD("info" , CmdInfo );
case 'k': MATCH_CMD("konstant" , CmdConstant );
case 'l': MATCH_CMD("list" , CmdList );
case 'm': MATCH_CMD("machine" , CmdMachine );
case 'n': MATCH_CMD("next" , CmdNext );
case 'o': MATCH_CMD("out" , CmdOut );
case 'p': MATCH_CMD("print" , CmdPrint );
case 'q': MATCH_CMD("quit" , CmdQuit );
case 'r': MATCH_CMD("run" , CmdRun );
case 's': MATCH_CMD("step" , CmdStep );
case 't': MATCH_CMD("thread" , CmdThread );
case 'u': MATCH_CMD("up" , CmdUp );
case 'v': MATCH_CMD("variable" , CmdVariable );
case 'w': MATCH_CMD("where" , CmdWhere );
case 'z': MATCH_CMD("zend" , CmdZend );
// these single lettter commands allow "x{cmd}" and "x {cmd}"
case 'x': shiftCommand(); NEW_CMD_NAME("extended", CmdExtended);
case 'y': shiftCommand(); NEW_CMD_NAME("user", CmdUser);
case '!': shiftCommand(); NEW_CMD_NAME("shell", CmdShell);
case '&': shiftCommand(); NEW_CMD_NAME("macro", CmdMacro);
}
return nullptr;
#undef MATCH_CMD
#undef NEW_CMD_NAME
}
bool DebuggerClient::process() {
TRACE(2, "DebuggerClient::process\n");
clearCachedLocal();
if (isApiMode()) {
// construct m_line based on m_command and m_args
m_line = m_command;
m_argIdx.clear();
m_argIdx.push_back(m_line.size());
for (unsigned int i = 0; i < m_args.size(); i++) {
m_line += " " + m_args[i];
m_argIdx.push_back(m_line.size());
}
}
switch (tolower(m_command[0])) {
case '@':
case '=':
case '$': {
return processTakeCode();
}
case '<': {
return match("<?php") && processTakeCode();
}
case '?': {
if (match("?")) {
usageLog("help", m_line);
return CmdHelp().onClientD(this);
}
if (match("?>")) return processEval();
break;
}
default: {
DebuggerCommand *cmd = createCommand();
if (cmd) {
usageLog(m_commandCanonical, m_line);
if (cmd->is(DebuggerCommand::KindOfRun)) playMacro("startup");
DebuggerCommandPtr deleter(cmd);
return cmd->onClientD(this);
} else {
return processTakeCode();
}
break;
}
}
return false;
}
///////////////////////////////////////////////////////////////////////////////
// helpers
void DebuggerClient::addToken(std::string &token, int idx) {
TRACE(2, "DebuggerClient::addToken\n");
m_argIdx.push_back(idx);
if (m_command.empty()) {
m_command = token;
} else {
m_args.push_back(token);
}
token.clear();
}
void DebuggerClient::parseCommand(const char *line) {
TRACE(2, "DebuggerClient::parseCommand\n");
m_command.clear();
m_args.clear();
char quote = 0;
string token;
m_argIdx.clear();
int i = 0;
for (i = 0; line[i]; i++) {
char ch = line[i];
char next = line[i+1];
switch (ch) {
case ' ':
if (!quote) {
if (!token.empty()) {
addToken(token, i);
}
} else {
token += ch;
}
break;
case '"':
case '\'':
if (quote == 0) {
quote = ch;
token += ch;
break;
}
if (quote == ch && (next == ' ' || next == 0)) {
token += ch;
addToken(token, i);
quote = 0;
break;
}
token += ch;
break;
case '\\':
if ((next == ' ' || next == '"' || next == '\'' || next == '\\')) {
if (quote == '\'') {
token += ch;
}
i++;
token += next;
break;
}
// fall through
default:
token += ch;
break;
}
}
if (!token.empty()) {
addToken(token, i);
}
}
bool DebuggerClient::parse(const char *line) {
TRACE(2, "DebuggerClient::parse\n");
if (m_inputState != TakingCode) {
while (isspace(*line)) line++;
}
m_line = line;
if (m_inputState == TakingCommand) {
parseCommand(line);
return true;
}
if (m_inputState == TakingCode) {
int pos = checkEvalEnd();
if (pos >= 0) {
if (pos > 0) {
m_code += m_line.substr(0, pos);
}
processEval();
} else {
if (!strncasecmp(m_line.c_str(), "abort", m_line.size())) {
m_code.clear();
m_inputState = TakingCommand;
return false;
}
m_code += m_line + "\n";
}
}
return false;
}
bool DebuggerClient::match(const char *cmd) {
TRACE(2, "DebuggerClient::match\n");
assert(cmd && *cmd);
return !strncasecmp(m_command.c_str(), cmd, m_command.size());
}
bool DebuggerClient::Match(const char *input, const char *cmd) {
TRACE(2, "DebuggerClient::Match\n");
return !strncasecmp(input, cmd, strlen(input));
}
bool DebuggerClient::arg(int index, const char *s) {
TRACE(2, "DebuggerClient::arg\n");
assert(s && *s);
assert(index > 0);
--index;
return (int)m_args.size() > index &&
!strncasecmp(m_args[index].c_str(), s, m_args[index].size());
}
std::string DebuggerClient::argValue(int index) {
TRACE(2, "DebuggerClient::argValue\n");
assert(index > 0);
--index;
if (index >= 0 && index < (int)m_args.size()) {
return m_args[index];
}
return "";
}
std::string DebuggerClient::lineRest(int index) {
TRACE(2, "DebuggerClient::lineRest\n");
assert(index > 0);
return m_line.substr(m_argIdx[index - 1] + 1);
}
///////////////////////////////////////////////////////////////////////////////
// comunication with DebuggerProxy
DebuggerCommandPtr DebuggerClient::xend(DebuggerCommand *cmd) {
TRACE(2, "DebuggerClient::xend\n");
sendToServer(cmd);
return recvFromServer(cmd->getType());
}
void DebuggerClient::sendToServer(DebuggerCommand *cmd) {
TRACE(2, "DebuggerClient::sendToServer\n");
if (!cmd->send(m_machine->m_thrift)) {
throw DebuggerProtocolException();
}
}
DebuggerCommandPtr DebuggerClient::recvFromServer(int expected) {
TRACE(2, "DebuggerClient::recvFromServer\n");
const char *func = "DebuggerClient::recvFromServer ()";
DebuggerCommandPtr res;
while (true) {
while (!DebuggerCommand::Receive(m_machine->m_thrift, res, func)) {
if (m_stopped) throw DebuggerClientExitException();
}
if (!res) {
Logger::Error("Unable to communicate with server. Server's down?");
throw DebuggerServerLostException();
}
if (!res->getWireError().empty()) {
error("wire error: %s", res->getWireError().data());
}
if (res->is((DebuggerCommand::Type)expected)) {
usageLogDone(boost::lexical_cast<string>(expected));
break;
}
if (!res->is(DebuggerCommand::KindOfInterrupt)) {
Logger::Error("%s: unexpected return: %d", func, res->getType());
throw DebuggerProtocolException();
}
usageLogInterrupt(res);
if (isApiMode()) {
// Hit breakpoint during eval
// Could also happen with CmdPrint, but since CmdPrint is also used
// for handling watch, it could potentially lead to infinite recursion
// Do not support KindOfPrint until we separate watch from it.
if (expected == DebuggerCommand::KindOfEval ||
expected == DebuggerCommand::KindOfPrint) {
m_pendingCommands.push_back(expected);
} else {
continue;
}
break;
}
// eval() can cause more breakpoints
if (!res->onClientD(this) || !console()) {
if (m_quitting) {
throw DebuggerClientExitException();
} else {
Logger::Error("%s: unable to process %d", func, res->getType());
throw DebuggerProtocolException();
}
}
}
return res;
}
///////////////////////////////////////////////////////////////////////////////
// helpers
int DebuggerClient::checkEvalEnd() {
TRACE(2, "DebuggerClient::checkEvalEnd\n");
size_t pos = m_line.rfind("?>");
if (pos == string::npos) {
return -1;
}
for (size_t p = pos + 2; p < m_line.size(); p++) {
if (!isspace(m_line[p])) {
return -1;
}
}
return pos;
}
bool DebuggerClient::processTakeCode() {
TRACE(2, "DebuggerClient::processTakeCode\n");
assert(m_inputState == TakingCommand);
char first = m_line[0];
if (first == '@') {
usageLog("@", m_line);
m_code = string("<?php ") + (m_line.c_str() + 1) + ";";
return processEval();
} else if (first == '=') {
usageLog("=", m_line);
while (m_line.at(m_line.size() - 1) == ';') {
// strip the trailing ;
m_line = m_line.substr(0, m_line.size() - 1);
}
m_code = string("<?php $_=(") + m_line.substr(1) + "); " + m_printFunction;
return processEval();
} else if (first != '<') {
usageLog("eval", m_line);
// User entered something that did not start with @, =, or <
// and also was not a debugger command. Interpret it as PHP.
m_code = "<?php ";
m_code += m_line + ";";
return processEval();
}
usageLog("<?php", m_line);
m_code = "<?php ";
m_code += m_line.substr(m_command.length()) + "\n";
m_inputState = TakingCode;
int pos = checkEvalEnd();
if (pos >= 0) {
m_code.resize(m_code.size() - m_line.size() + pos - 1);
return processEval();
}
return true;
}
bool DebuggerClient::processEval() {
TRACE(2, "DebuggerClient::processEval\n");
m_runState = Running;
m_inputState = TakingCommand;
m_acLiveListsDirty = true;
CmdEval().onClientD(this);
return true;
}
void DebuggerClient::swapHelp() {
TRACE(2, "DebuggerClient::swapHelp\n");
assert(m_args.size() > 0);
m_command = m_args[0];
m_args[0] = "help";
}
void DebuggerClient::quit() {
TRACE(2, "DebuggerClient::quit\n");
m_quitting = true;
for (unsigned int i = 0; i < m_machines.size(); i++) {
m_machines[i]->m_thrift.close();
}
throw DebuggerClientExitException();
}
DSandboxInfoPtr DebuggerClient::getSandbox(int index) const {
TRACE(2, "DebuggerClient::getSandbox\n");
if (index > 0) {
--index;
if (index >= 0 && index < (int)m_sandboxes.size()) {
return m_sandboxes[index];
}
}
return DSandboxInfoPtr();
}
void DebuggerClient::updateThreads(DThreadInfoPtrVec threads) {
TRACE(2, "DebuggerClient::updateThreads\n");
m_threads = threads;
for (unsigned int i = 0; i < m_threads.size(); i++) {
DThreadInfoPtr thread = m_threads[i];
std::map<int64_t, int>::const_iterator iter =
m_threadIdMap.find(thread->m_id);
if (iter != m_threadIdMap.end()) {
m_threads[i]->m_index = iter->second;
} else {
int index = m_threadIdMap.size() + 1;
m_threadIdMap[thread->m_id] = index;
m_threads[i]->m_index = index;
}
}
}
DThreadInfoPtr DebuggerClient::getThread(int index) const {
TRACE(2, "DebuggerClient::getThread\n");
for (unsigned int i = 0; i < m_threads.size(); i++) {
if (m_threads[i]->m_index == index) {
return m_threads[i];
}
}
return DThreadInfoPtr();
}
void DebuggerClient::getListLocation(std::string &file, int &line,
int &lineFocus0, int &charFocus0,
int &lineFocus1, int &charFocus1) {
TRACE(2, "DebuggerClient::getListLocation\n");
lineFocus0 = charFocus0 = lineFocus1 = charFocus1 = 0;
if (m_listFile.empty() && m_breakpoint) {
setListLocation(m_breakpoint->m_file, m_breakpoint->m_line1, true);
lineFocus0 = m_breakpoint->m_line1;
charFocus0 = m_breakpoint->m_char1;
lineFocus1 = m_breakpoint->m_line2;
charFocus1 = m_breakpoint->m_char2;
} else if (m_listLineFocus) {
lineFocus0 = m_listLineFocus;
}
file = m_listFile;
line = m_listLine;
}
void DebuggerClient::setListLocation(const std::string &file, int line,
bool center) {
TRACE(2, "DebuggerClient::setListLocation\n");
m_listFile = file;
if (!m_listFile.empty() && m_listFile[0] != '/' && !m_sourceRoot.empty()) {
if (m_sourceRoot[m_sourceRoot.size() - 1] != '/') {
m_sourceRoot += "/";
}
m_listFile = m_sourceRoot + m_listFile;
}
m_listLine = line;
if (center && m_listLine) {
m_listLineFocus = m_listLine;
m_listLine -= CodeBlockSize / 2;
if (m_listLine < 0) {
m_listLine = 0;
}
}
}
void DebuggerClient::setSourceRoot(const std::string &sourceRoot) {
TRACE(2, "DebuggerClient::setSourceRoot\n");
m_config["SourceRoot"] = m_sourceRoot = sourceRoot;
saveConfig();
// apply change right away
setListLocation(m_listFile, m_listLine, true);
}
void DebuggerClient::setMatchedBreakPoints(BreakPointInfoPtrVec breakpoints) {
TRACE(2, "DebuggerClient::setMatchedBreakPoints\n");
m_matched = breakpoints;
}
void DebuggerClient::setCurrentLocation(int64_t threadId,
BreakPointInfoPtr breakpoint) {
TRACE(2, "DebuggerClient::setCurrentLocation\n");
m_threadId = threadId;
m_breakpoint = breakpoint;
m_stacktrace.reset();
m_listFile.clear();
m_listLine = 0;
m_listLineFocus = 0;
m_acLiveListsDirty = true;
}
void DebuggerClient::addWatch(const char *fmt, const std::string &php) {
TRACE(2, "DebuggerClient::addWatch\n");
WatchPtr watch(new Watch());
watch->first = fmt;
watch->second = php;
m_watches.push_back(watch);
}
void DebuggerClient::setStackTrace(CArrRef stacktrace) {
TRACE(2, "DebuggerClient::setStackTrace\n");
m_stacktrace = stacktrace;
}
void DebuggerClient::moveToFrame(int index, bool display /* = true */) {
TRACE(2, "DebuggerClient::moveToFrame\n");
m_frame = index;
if (m_frame >= m_stacktrace.size()) {
m_frame = m_stacktrace.size() - 1;
}
if (m_frame < 0) {
m_frame = 0;
}
CArrRef frame = m_stacktrace[m_frame];
if (!frame.isNull()) {
String file = frame["file"];
int line = frame["line"].toInt32();
if (!file.empty() && line) {
if (m_frame == 0) {
m_listFile.clear();
m_listLine = 0;
m_listLineFocus = 0;
} else {
setListLocation(file.data(), line, true);
}
}
if (display) {
printFrame(m_frame, frame);
}
}
}
void DebuggerClient::printFrame(int index, CArrRef frame) {
TRACE(2, "DebuggerClient::printFrame\n");
StringBuffer args;
for (ArrayIter iter(frame["args"]); iter; ++iter) {
if (!args.empty()) args.append(", ");
String value = FormatVariable(iter.second());
args.append(value);
}
StringBuffer func;
if (frame.exists("namespace")) {
func.append(frame["namespace"].toString());
func.append("::");
}
if (frame.exists("class")) {
func.append(frame["class"].toString());
func.append("::");
}
func.append(frame["function"].toString());
String sindex(index);
print("#%s %s (%s)\n %s at %s:%d",
sindex.data(),
func.data() ? func.data() : "",
args.data() ? args.data() : "",
String(" ").substr(0, sindex.size()).data(),
frame["file"].toString().data(),
(int)frame["line"].toInt32());
}
void DebuggerClient::startMacro(std::string name) {
TRACE(2, "DebuggerClient::startMacro\n");
if (m_macroRecording &&
ask("We are recording a macro. Do you want to save? [Y/n]") != 'n') {
endMacro();
}
if (name.empty()) {
name = "default";
} else {
for (unsigned int i = 0; i < m_macros.size(); i++) {
if (m_macros[i]->m_name == name) {
if (ask("This will overwrite existing one. Proceed? [y/N]") != 'y') {
return;
}
}
break;
}
}
m_macroRecording = MacroPtr(new Macro());
m_macroRecording->m_name = name;
}
void DebuggerClient::endMacro() {
TRACE(2, "DebuggerClient::endMacro\n");
if (!m_macroRecording) {
error("There is no ongoing recording.");
tutorial("Use '& [s]tart {name}' or '& [s]tart' command to start "
"macro recording first.");
return;
}
bool found = false;
for (unsigned int i = 0; i < m_macros.size(); i++) {
if (m_macros[i]->m_name == m_macroRecording->m_name) {
m_macros[i] = m_macroRecording;
found = true;
break;
}
}
if (!found) {
m_macros.push_back(m_macroRecording);
}
saveConfig();
m_macroRecording.reset();
}
bool DebuggerClient::playMacro(std::string name) {
TRACE(2, "DebuggerClient::playMacro\n");
if (name.empty()) {
name = "default";
}
for (unsigned int i = 0; i < m_macros.size(); i++) {
if (m_macros[i]->m_name == name) {
m_macroPlaying = m_macros[i];
m_macroPlaying->m_index = 0;
return true;
}
}
return false;
}
bool DebuggerClient::deleteMacro(int index) {
TRACE(2, "DebuggerClient::deleteMacro\n");
--index;
if (index >= 0 && index < (int)m_macros.size()) {
if (ask("Are you sure you want to delete the macro? [y/N]") != 'y') {
return true;
}
m_macros.erase(m_macros.begin() + index);
saveConfig();
return true;
}
return false;
}
void DebuggerClient::record(const char *line) {
TRACE(2, "DebuggerClient::record\n");
assert(line);
if (m_macroRecording && line[0] != '&') {
m_macroRecording->m_cmds.push_back(line);
}
}
///////////////////////////////////////////////////////////////////////////////
// helpers for server API
bool DebuggerClient::apiGrab() {
TRACE(2, "DebuggerClient::apiGrab\n");
Lock l(m_inApiUseLck);
if (m_inApiUse) {
return false;
}
m_inApiUse = true;
return true;
}
void DebuggerClient::apiFree() {
TRACE(2, "DebuggerClient::apiFree\n");
Lock l(m_inApiUseLck);
m_inApiUse = false;
}
void DebuggerClient::resetSmartAllocatedMembers() {
TRACE(2, "DebuggerClient::resetSmartAllocatedMembers\n");
// Essentially sets these to null: so it's safe to run their
// destructors at sweep time or after the SmartAllocators are torn
// down.
new (&m_stacktrace) Array();
new (&m_otValues) Array();
}
///////////////////////////////////////////////////////////////////////////////
// helpers for usage logging
void DebuggerClient::initUsageLogging() {
TRACE(2, "DebuggerClient::initUsageLogging\n");
// Usage log file is a runtime option instead of debugger config because
// it is for internal monitoring and we don't want users to modify this.
if (RuntimeOption::DebuggerUsageLogFile.empty()) {
return;
}
mode_t old = umask(0);
m_usageLogFP = fopen(RuntimeOption::DebuggerUsageLogFile.c_str(), "a");
if (!m_usageLogFP) {
Logger::Error("failed to open debugger usage log file %s",
RuntimeOption::DebuggerUsageLogFile.c_str());
}
umask(old);
}
void DebuggerClient::finiUsageLogging() {
TRACE(2, "DebuggerClient::finiUsageLogging\n");
if (m_usageLogFP) {
fclose(m_usageLogFP);
m_usageLogFP = nullptr;
}
}
void DebuggerClient::usageLog(const std::string& cmd, const std::string& line) {
TRACE(2, "DebuggerClient::usageLog\n");
if (!m_usageLogFP) {
return;
}
if (m_usageLogHeader.empty()) {
std::string login = m_options.user;
std::string host = Process::GetHostName();
std::string clientMode = isApiMode() ? "api" : "terminal";
m_usageLogHeader = login + " " + host + " hhvm " + clientMode;
}
struct timespec tp;
gettime(CLOCK_REALTIME, &tp);
#define MAX_LINE 1024
char buf[MAX_LINE];
int len = snprintf(buf, MAX_LINE, "%s %" PRId64 " %" PRId64 " %s #### %s",
m_usageLogHeader.c_str(),
(int64_t)tp.tv_sec, (int64_t)tp.tv_nsec,
cmd.c_str(), line.c_str());
len = len >= MAX_LINE ? MAX_LINE - 1: len;
buf[len] = '\n';
#undef MAX_LINE
fwrite(buf, len + 1, 1, m_usageLogFP);
fflush(m_usageLogFP);
}
void DebuggerClient::usageLogInterrupt(DebuggerCommandPtr cmd) {
TRACE(2, "DebuggerClient::usageLogInterrupt\n");
CmdInterruptPtr intr = dynamic_pointer_cast<CmdInterrupt>(cmd);
if (!intr) {
return;
}
switch (intr->getInterruptType()) {
case SessionStarted: usageLog("interrupt", "SessionStarted"); break;
case SessionEnded: usageLog("interrupt", "SessionEnded"); break;
case RequestStarted: usageLog("interrupt", "RequestStarted"); break;
case RequestEnded: usageLog("interrupt", "RequestEnded"); break;
case PSPEnded: usageLog("interrupt", "PSPEnded"); break;
case HardBreakPoint: usageLog("interrupt", "HardBreakPoint"); break;
case BreakPointReached: usageLog("interrupt", "BreakPointReached"); break;
case ExceptionThrown: usageLog("interrupt", "ExceptionThrown"); break;
default:
usageLog("interrupt", "unknown");
}
}
///////////////////////////////////////////////////////////////////////////////
// configuration
void DebuggerClient::loadConfig() {
TRACE(2, "DebuggerClient::loadConfig\n");
if (m_configFileName.empty()) {
m_configFileName = Process::GetHomeDirectory() + ConfigFileName;
}
// make sure file exists
FILE *f = fopen(m_configFileName.c_str(), "a");
if (f) {
fclose(f);
} else {
m_configFileName.clear();
return;
}
try {
m_config.open(m_configFileName);
} catch (const HdfException &e) {
Logger::Error("Unable to load configuration file: %s", e.what());
m_configFileName.clear();
}
s_use_utf8 = m_config["UTF8"].getBool(true);
m_config["UTF8"] = s_use_utf8; // for starter
Hdf color = m_config["Color"];
UseColor = color.getBool(true);
color = UseColor; // for starter
if (UseColor) {
defineColors(); // (1) no one can overwrite, (2) for starter
LoadColors(color);
}
m_tutorial = m_config["Tutorial"].getInt32(0);
std::string pprint = m_config["PrettyPrint"].getString("hphpd_print_value");
setDebuggerBypassCheck(m_config["BypassAccessCheck"].getBool());
setDebuggerSmallStep(m_config["SmallStep"].getBool());
int printLevel = m_config["PrintLevel"].getInt16(3);
if (printLevel > 0 && printLevel < MinPrintLevel) {
printLevel = MinPrintLevel;
}
setDebuggerPrintLevel(printLevel);
int maxCodeLines = m_config["MaxCodeLines"].getInt16(-1);
m_config["MaxCodeLines"] = maxCodeLines;
setDebuggerClientMaxCodeLines(maxCodeLines);
m_printFunction = (boost::format(
"(function_exists(\"%s\") ? %s($_) : print_r(print_r($_, true)));")
% pprint % pprint).str();
m_config["Tutorial"]["Visited"].get(m_tutorialVisited);
for (Hdf node = m_config["Macros"].firstChild(); node.exists();
node = node.next()) {
MacroPtr macro(new Macro());
macro->load(node);
m_macros.push_back(macro);
}
m_sourceRoot = m_config["SourceRoot"].getString();
saveConfig(); // so to generate a starter for people
}
void DebuggerClient::saveConfig() {
TRACE(2, "DebuggerClient::saveConfig\n");
if (m_configFileName.empty()) {
// we are not touching a file that was not successfully loaded earlier
return;
}
m_config["Tutorial"] = m_tutorial;
Hdf visited = m_config["Tutorial"]["Visited"];
unsigned int i = 0;
for (std::set<string>::const_iterator iter = m_tutorialVisited.begin();
iter != m_tutorialVisited.end(); ++iter) {
visited[i++] = *iter;
}
m_config.remove("Macros");
Hdf macros = m_config["Macros"];
for (i = 0; i < m_macros.size(); i++) {
m_macros[i]->save(macros[i]);
}
m_config.write(m_configFileName);
}
void DebuggerClient::defineColors() {
TRACE(2, "DebuggerClient::defineColors\n");
vector<string> names;
get_supported_colors(names);
Hdf support = m_config["Color"]["SupportedNames"];
for (unsigned int i = 0; i < names.size(); i++) {
support[i+1] = names[i];
}
Hdf emacs = m_config["Color"]["Palette"]["emacs"];
emacs["Keyword"] = "CYAN";
emacs["Comment"] = "RED";
emacs["String"] = "GREEN";
emacs["Variable"] = "BROWN";
emacs["Html"] = "GRAY";
emacs["Tag"] = "MAGENTA";
emacs["Declaration"] = "BLUE";
emacs["Constant"] = "MAGENTA";
emacs["LineNo"] = "GRAY";
Hdf vim = m_config["Color"]["Palette"]["vim"];
vim["Keyword"] = "MAGENTA";
vim["Comment"] = "BLUE";
vim["String"] = "RED";
vim["Variable"] = "CYAN";
vim["Html"] = "GRAY";
vim["Tag"] = "MAGENTA";
vim["Declaration"] = "WHITE";
vim["Constant"] = "WHITE";
vim["LineNo"] = "GRAY";
}
///////////////////////////////////////////////////////////////////////////////
}}