Arquivos
hhvm/hphp/runtime/debugger/debugger_client.cpp
T
Drew Paroski 84b9d9a3a2 Separate resources from objects, part 1
In HHVM (and HPHPc before it) we've been piggybacking resources on the
KindOfObject machinery. At the language level, resource is considered to
be a different type than object, and there are a number of differences
in behavior between objects and resources (ex. resources don't allow for
dynamic properties, resources don't work with the clone operator, the
"(object)" cast behaves differently for resources vs. objects, etc).

Piggybacking resources on the KindOfObject machinery has some downsides.
Code that deals with KindOfObject values often needs to check if the value
is a resource and go down a different code path. This makes things harder
to maintain and harder to keep parity with Zend. Also, these extra branches
hurt performance a little, and they make it harder for the JIT to do a good
job in some cases when its generating machine code that operates on objects.

This diff prepares the code base for a new KindOfResource type by adding a
new "Resource" smart pointer type (currently a typedef for the Object smart
pointer type) and it updates the C++ code and the idl files appropriately.
This diff is essentially a cosmetic change and should not impact run time
behavior. In the next diff (part 2) we'll actually add a new KindOfResource
type, detach ResourceData from the ObjectData inheritence hierarchy, and
provide a real implementation for the Resource smart pointer type (instead
of just aliasing the Object smart pointer type).
2013-07-10 11:16:33 -07:00

2453 linhas
76 KiB
C++

/*
+----------------------------------------------------------------------+
| HipHop for PHP |
+----------------------------------------------------------------------+
| Copyright (c) 2010-2013 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 "hphp/runtime/debugger/debugger_client.h"
#include "hphp/runtime/debugger/debugger_command.h"
#include "hphp/runtime/debugger/cmd/all.h"
#include "hphp/runtime/base/complex_types.h"
#include "hphp/runtime/base/variable_serializer.h"
#include "hphp/runtime/base/string_util.h"
#include "hphp/runtime/base/preg.h"
#include "hphp/runtime/ext/ext_json.h"
#include "hphp/runtime/ext/ext_socket.h"
#include "hphp/runtime/ext/ext_network.h"
#include "hphp/util/text_color.h"
#include "hphp/util/text_art.h"
#include "hphp/util/logger.h"
#include "hphp/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 {
///////////////////////////////////////////////////////////////////////////////
TRACE_SET_MOD(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;
}
///////////////////////////////////////////////////////////////////////////////
// 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;
int secLeft = secWait - (now - m_sigTime);
if (secLeft <= 0) {
usageLogEvent("signal quit");
error("Program is not responding. Please restart debugger to get a "
"new connection.");
quit(); // NB: the machine is running, so can't send a real CmdQuit.
return;
}
usageLogEvent("signal wait");
info("Please wait. If not responding in %d second%s, "
"press Ctrl-C again to quit.", secLeft, secLeft > 1 ? "s" : "");
} else {
usageLogEvent("signal start");
info("Pausing program execution, please wait...");
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");
SmartPtr<Socket> ret = getStaticDebuggerClient().connectLocal();
getStaticDebuggerClient().start(options);
return ret;
}
void DebuggerClient::Shutdown() {
TRACE(2, "DebuggerClient::Shutdown\n");
if (debugger_client) {
debugger_client->resetSmartAllocatedMembers();
debugger_client.reset();
}
}
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::Type::VarDump :
VariableSerializer::Type::DebuggerDump;
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::Type::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_inputState(TakingCommand),
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) {
TRACE(2, "DebuggerClient::DebuggerClient\n");
Debugger::InitUsageLogging();
}
DebuggerClient::~DebuggerClient() {
TRACE(2, "DebuggerClient::~DebuggerClient\n");
m_stopped = true;
m_mainThread.waitForEnd();
FILE *f = getLogFileHandler();
if (f != nullptr) {
fclose(f);
setLogFileHandler(nullptr);
}
}
void DebuggerClient::closeAllConnections() {
TRACE(2, "DebuggerClient::closeAllConnections\n");
for (unsigned int i = 0; i < m_machines.size(); i++) {
m_machines[i]->m_thrift.close();
}
}
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));
// First check for an existing connect, and reuse that.
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;
usageLogEvent("RPC connect", m_rpcHost);
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();
Resource obj(sock); // Destroy sock if we don't connect.
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);
m_machine->m_thrift.close(); // Close the old socket, it may still be open.
Socket *sock = new Socket(socket(PF_INET, SOCK_STREAM, 0), PF_INET,
host.c_str(), port);
sock->unregister();
Resource obj(sock); // Destroy sock if we don't connect.
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));
m_machines.push_back(machine);
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 || !RuntimeOption::EnableDebuggerPrompt) {
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();
}
usageLogEvent("init");
loadConfig();
if (!options.cmds.empty()) {
RuntimeOption::EnableDebuggerColor = false;
RuntimeOption::EnableDebuggerPrompt = false;
s_use_utf8 = false;
}
if (options.apiMode) {
RuntimeOption::EnableDebuggerColor = false;
RuntimeOption::EnableDebuggerPrompt = false;
m_outputBuf.clear();
}
if (UseColor && RuntimeOption::EnableDebuggerColor) Debugger::SetTextColors();
if (!NoPrompt && RuntimeOption::EnableDebuggerPrompt) {
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);
} else {
if (options.fileName.empty()) {
help("Note: no server specified, debugging local scripts only.");
help("If you want to connect to a server, launch with \"-h\" or use:");
help(" [m]achine [c]onnect <servername>\n");
}
}
}
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();
}
// Executed by m_mainThread to run the command-line debugger.
void DebuggerClient::run() {
TRACE(2, "DebuggerClient::run\n");
StackTraceNoHeap::AddExtraLogging("IsDebugger", "True");
// 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) {
bool reconnect = false;
try {
eventLoop(TopLevel, DebuggerCommand::KindOfNone, "Main client loop");
} catch (DebuggerClientExitException &e) { /* normal exit */
} catch (DebuggerServerLostException &e) {
// Loss of connection
TRACE_RB(1, "DebuggerClient::run: server lost exception\n");
usageLogEvent("DebuggerServerLostException", m_commandCanonical);
reconnect = true;
} catch (DebuggerProtocolException &e) {
// Bad or unexpected data. Give reconnect a shot, it could help...
TRACE_RB(1, "DebuggerClient::run: protocol exception\n");
usageLogEvent("DebuggerProtocolException", m_commandCanonical);
reconnect = true;
} catch (...) {
TRACE_RB(1, "DebuggerClient::run: unknown exception\n");
usageLogEvent("UnknownException", m_commandCanonical);
Logger::Error("Unhandled exception, exiting.");
}
// Note: it's silly to try to reconnect when stopping, or if we have a
// problem while quitting.
if (reconnect && !m_stopped && (m_commandCanonical != "quit")) {
usageLogEvent("reconnect attempt", m_commandCanonical);
if (DebuggerClient::reconnect()) {
usageLogEvent("reconnect success", m_commandCanonical);
continue;
}
usageLogEvent("reconnect failed", m_commandCanonical);
Logger::Error("Unable to reconnect to server, exiting.");
}
break;
}
usageLogEvent("exit");
// Closing all proxy connections will force the local proxy to pop out of
// it's wait, and eventually exit the main thread.
closeAllConnections();
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
// Execute the initial connection protocol with a machine. A connection has been
// established, and the proxy has responded with an interrupt giving us initial
// control. Send breakpoints to the server, and then attach to the sandbox
// if necessary. If we attach to a sandbox, then the process is off and running
// again (CmdMachine continues execution on a successful attach) so return false
// to indicate that a client should wait for another interrupt before attempting
// further communication. Returns true if the protocol is complete and the
// machine is at an interrupt.
bool DebuggerClient::initializeMachine() {
TRACE(2, "DebuggerClient::initializeMachine\n");
// 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::SendClientBreakpointListToServer(*this);
}
// attaching to default sandbox
int waitForSandbox = false;
if (!m_machine->m_sandboxAttached) {
const char *user = m_options.user.empty() ?
nullptr : m_options.user.c_str();
m_machine->m_sandboxAttached = (waitForSandbox =
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() && waitForSandbox) {
// Return false to wait for next interrupt from server
return false;
}
return true;
}
// Only used in API mode, to listen for another interupt from the machine.
DebuggerCommandPtr DebuggerClient::waitForNextInterrupt() {
TRACE(2, "DebuggerClient::waitForNextInterrupt\n");
assert(isApiMode());
const char *func = "DebuggerClient::waitForNextInterrupt()";
m_machine->m_interrupting = false; // Machine is running again.
m_inputState = TakingInterrupt;
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)) {
cmd->onClient(*this);
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();
}
} else {
CmdInterruptPtr intr = dynamic_pointer_cast<CmdInterrupt>(cmd);
Debugger::UsageLogInterrupt(getUsageMode(), getSandboxId(),
*intr.get());
}
m_sigTime = 0;
m_machine->m_interrupting = true;
setClientState(StateReadyForCommand);
m_inputState = TakingCommand;
return cmd;
}
}
return DebuggerCommandPtr();
}
// The main execution loop of DebuggerClient. This waits for interrupts from
// the server (and responds to polls for signals). On interrupt, it presents
// a command prompt, and continues pumping interrupts when a command lets the
// machine run again.
// For nested loops it returns the command that completed the loop, which will
// match the exptectedCmd passed in.
// For all loop types, throws one of a variety of exceptions for various errors,
// and throws DebuggerClientExitException when the event loop is terminated
// due to the client stopping.
DebuggerCommandPtr DebuggerClient::eventLoop(EventLoopKind loopKind,
int expectedCmd,
const char *caller) {
TRACE(2, "DebuggerClient::eventLoop\n");
if (loopKind == NestedWithExecution) {
// Some callers have caused the server to start executing more PHP, so
// update the machine/client state accordingly.
m_inputState = TakingInterrupt;
m_machine->m_interrupting = false;
}
while (!m_stopped) {
DebuggerCommandPtr cmd;
if (DebuggerCommand::Receive(m_machine->m_thrift, cmd, caller)) {
if (!cmd) {
Logger::Error("Unable to communicate with server. Server's down?");
throw DebuggerServerLostException();
}
if (cmd->is(DebuggerCommand::KindOfSignal)) {
// Respond to signal polling from the server.
cmd->onClient(*this);
continue;
}
if (!cmd->getWireError().empty()) {
error("wire error: %s", cmd->getWireError().data());
}
if ((loopKind != TopLevel) &&
cmd->is((DebuggerCommand::Type)expectedCmd)) {
// For the nested cases, the caller has sent a cmd to the server and is
// expecting a specific response. When we get it, return it.
usageLogEvent("command done", boost::lexical_cast<string>(expectedCmd));
m_machine->m_interrupting = true; // Machine is stopped
m_inputState = TakingCommand;
return cmd;
}
if ((loopKind == Nested) || !cmd->is(DebuggerCommand::KindOfInterrupt)) {
Logger::Error("Received bad cmd type %d, unable to communicate "
"with server.", cmd->getType());
throw DebuggerProtocolException();
}
if ((loopKind != TopLevel) && isApiMode()) {
// Hit breakpoint during eval. Return the interrupt, but remember what
// cmd we were originally waiting for.
if (expectedCmd == DebuggerCommand::KindOfEval ||
expectedCmd == DebuggerCommand::KindOfPrint) {
m_pendingCommands.push_back(expectedCmd);
return cmd;
} else {
Logger::Error("Received bad cmd type %d, unable to communicate "
"with server.", cmd->getType());
throw DebuggerProtocolException();
}
}
m_sigTime = 0;
CmdInterruptPtr intr = dynamic_pointer_cast<CmdInterrupt>(cmd);
Debugger::UsageLogInterrupt(getUsageMode(), getSandboxId(), *intr.get());
cmd->onClient(*this);
// When we make a new connection to a machine, we have to wait for it
// to interrupt us before we can send it any messages. This is our
// opportunity to complete the connection and make it ready to use.
if (!m_machine->m_initialized) {
if (!initializeMachine()) {
// False means the machine is running and we need to wait for
// another interrupt.
continue;
}
}
// Execution has been interrupted, so go ahead and give the user
// the prompt back.
m_machine->m_interrupting = true; // Machine is stopped
m_inputState = TakingCommand;
console(); // Prompt loop
m_inputState = TakingInterrupt;
m_machine->m_interrupting = false; // Machine is running again.
}
}
throw DebuggerClientExitException(); // Stopped, so exit.
}
// Execute the interactive command loop for the debugger client. This will
// present the prompt, wait for user input, and execute commands, then rinse
// and repeat. The loop terminates when a command is executed that causes the
// machine to resume execution, or which should cause the client to exit.
// This function is only entered when the machine being debugged is paused.
//
// If this function returns it means the process is running again.
// NB: exceptions derrived from DebuggerException or DebuggerClientExeption
// indicate the machine remains paused.
void 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
print("quit");
line = "quit";
}
} else if (!NoPrompt && RuntimeOption::EnableDebuggerPrompt) {
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 (DebuggerConsoleExitException &e) {
return;
}
}
} 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;
}
break;
}
}
}
not_reached();
}
///////////////////////////////////////////////////////////////////////////////
// output functions
String DebuggerClient::getPrintString() {
TRACE(2, "DebuggerClient::getPrintString\n");
String s(m_outputBuf); // makes a copy;
m_outputBuf.clear();
return s;
}
const StaticString
s_output_type("output_type"),
s_file("file"),
s_line("line"),
s_line_no("line_no"),
s_watch_values("watch_values"),
s_stacktrace("stacktrace"),
s_frame("frame"),
s_values("values"),
s_text("text"),
s_invalid("invalid"),
s_cmd("cmd"),
s_code_loc("code_loc");
Array DebuggerClient::getOutputArray() {
TRACE(2, "DebuggerClient::getOutputArray\n");
Array ret;
switch (m_outputType) {
case OTCodeLoc:
ret.set(s_output_type, s_code_loc);
ret.set(s_file, m_otFile);
ret.set(s_line_no, m_otLineNo);
ret.set(s_watch_values, m_otValues);
break;
case OTStacktrace:
ret.set(s_output_type, s_stacktrace);
ret.set(s_stacktrace, m_stacktrace);
ret.set(s_frame, m_frame);
break;
case OTValues:
ret.set(s_output_type, s_values);
ret.set(s_values, m_otValues);
break;
case OTText:
ret.set(s_output_type, s_text);
break;
default:
ret.set(s_output_type, s_invalid);
}
ret.set(s_cmd, m_command);
ret.set(s_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.toString(), firstLine, lastLine,
beginHighlightLine,
beginHighlightColumn,
endHighlightLine,
endHighlightColumn);
}
}
}
bool DebuggerClient::code(CStrRef source, int line1 /*= 0*/, int line2 /*= 0*/,
int lineFocus0 /* = 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, lineFocus0, charFocus0,
lineFocus1, charFocus1);
}
if (!highlighted.empty()) {
print(highlighted);
return true;
}
return false;
}
String highlighted;
if (isApiMode()) {
highlighted = source;
} else {
highlighted = highlight_php(source, 1, lineFocus0, 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(),
UseColor && RuntimeOption::EnableDebuggerColor ? ANSI_COLOR_END : "\0");
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 && RuntimeOption::EnableDebuggerColor) {
msg = InfoColor + msg + ANSI_COLOR_END;
}
fwrite(msg.data(), 1, msg.length(), stdout);
fflush(stdout);
return tolower(getchar());
}
#define DWRITE(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");
DWRITE(s.data(), 1, s.length(), stdout);
DWRITE("\n", 1, 1, stdout);
fflush(stdout);
}
void DebuggerClient::print(CStrRef msg) {
TRACE(2, "DebuggerClient::print(CStrRef msg)\n");
DWRITE(msg.data(), 1, msg.length(), stdout);
DWRITE("\n", 1, 1, stdout);
fflush(stdout);
}
#define IMPLEMENT_COLOR_OUTPUT(name, where, color) \
void DebuggerClient::name(CStrRef msg) { \
if (UseColor && color && RuntimeOption::EnableDebuggerColor) { \
DWRITE(color, 1, strlen(color), where); \
} \
DWRITE(msg.data(), 1, msg.length(), where); \
if (UseColor && color && RuntimeOption::EnableDebuggerColor) { \
DWRITE(ANSI_COLOR_END, 1, strlen(ANSI_COLOR_END), where); \
} \
DWRITE("\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 DWRITE
#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].toString(), leftMax));
if (n == 0) line.append(" ");
line.append(" ");
line.append(lines2[n].toString());
sb.append(StringUtil::Trim(line.detach(), StringUtil::TrimType::Right));
sb.append("\n");
}
}
help(sb.detach());
}
void DebuggerClient::helpBody(const std::string &s) {
TRACE(2, "DebuggerClient::helpBody\n");
help("%s", "");
help(wrap(s));
help("%s", "");
}
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 == "complete") NEW_CMD_NAME("complete", CmdComplete);
// Internal testing
if (m_command == "internaltesting") {
NEW_CMD_NAME("internaltesting", CmdInternalTesting);
}
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
}
// Parses the current command string. If invalid return false.
// Otherwise, carry out the command and return true.
// NB: the command may throw a variety of exceptions derrived from
// DebuggerClientException.
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 '$': {
processTakeCode();
return true;
}
case '<': {
if (match("<?php")) {
processTakeCode();
return true;
}
}
case '?': {
if (match("?")) {
usageLogCommand("help", m_line);
CmdHelp().onClient(*this);
return true;
}
if (match("?>")) {
processEval();
return true;
}
break;
}
default: {
DebuggerCommand *cmd = createCommand();
if (cmd) {
usageLogCommand(m_commandCanonical, m_line);
if (cmd->is(DebuggerCommand::KindOfRun)) playMacro("startup");
DebuggerCommandPtr deleter(cmd);
cmd->onClient(*this);
} else {
processTakeCode();
}
return true;
}
}
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,
EventLoopKind loopKind) {
TRACE(2, "DebuggerClient::xend\n");
sendToServer(cmd);
return eventLoop(loopKind, cmd->getType(), "Receive for command");
}
void DebuggerClient::sendToServer(DebuggerCommand *cmd) {
TRACE(2, "DebuggerClient::sendToServer\n");
if (!cmd->send(m_machine->m_thrift)) {
Logger::Error("Send command: unable to communicate with server.");
throw DebuggerProtocolException();
}
}
///////////////////////////////////////////////////////////////////////////////
// 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;
}
// Parses the current command line as a code execution command
// and carries out the command.
void DebuggerClient::processTakeCode() {
TRACE(2, "DebuggerClient::processTakeCode\n");
assert(m_inputState == TakingCommand);
char first = m_line[0];
if (first == '@') {
usageLogCommand("@", m_line);
m_code = string("<?php ") + (m_line.c_str() + 1) + ";";
processEval();
return;
} else if (first == '=') {
usageLogCommand("=", 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;
processEval();
return;
} else if (first != '<') {
usageLogCommand("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 + ";";
processEval();
return;
}
usageLogCommand("<?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);
processEval();
}
}
void DebuggerClient::processEval() {
TRACE(2, "DebuggerClient::processEval\n");
m_inputState = TakingCommand;
m_acLiveListsDirty = true;
CmdEval().onClient(*this);
}
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");
closeAllConnections();
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();
}
// Update the current sandbox in the current machine. This should always be
// called once we're attached to a machine.
void DebuggerClient::setSandbox(DSandboxInfoPtr sandbox) {
assert(m_machine != nullptr);
m_machine->m_sandbox = sandbox;
}
// Return the ID of the current sandbox, if there is one. If we're connected to
// a machine that is attached to a sandbox, then we'll have an ID.
std::string DebuggerClient::getSandboxId() {
if ((m_machine != nullptr) && m_machine->m_sandboxAttached &&
(m_machine->m_sandbox != nullptr)) {
return m_machine->m_sandbox->id();
}
return "None";
}
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();
}
// Retrieves the current source location (file, line).
// The current location is initially determined by the
// breakpoint where the debugger is currently stopped and can
// thereafter be modified by list commands and by switching the
// the stack frame. The lineFocus and and charFocus parameters
// are non zero only when the source location comes from a breakpoint.
// They can be used to highlight the location of the current breakpoint
// in the edit window of an attached IDE, for example.
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].toArray();
if (!frame.isNull()) {
String file = frame[s_file].toString();
int line = frame[s_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);
}
}
}
const StaticString
s_args("args"),
s_namespace("namespace"),
s_class("class"),
s_function("function");
void DebuggerClient::printFrame(int index, CArrRef frame) {
TRACE(2, "DebuggerClient::printFrame\n");
StringBuffer args;
for (ArrayIter iter(frame[s_args].toArray()); iter; ++iter) {
if (!args.empty()) args.append(", ");
String value = FormatVariable(iter.second());
args.append(value);
}
StringBuffer func;
if (frame.exists(s_namespace)) {
func.append(frame[s_namespace].toString());
func.append("::");
}
if (frame.exists(s_class)) {
func.append(frame[s_class].toString());
func.append("::");
}
func.append(frame[s_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[s_file].toString().data(),
(int)frame[s_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");
usageLogEvent("api grab");
Lock l(m_inApiUseLck);
if (m_inApiUse) {
return false;
}
m_inApiUse = true;
return true;
}
void DebuggerClient::apiFree() {
TRACE(2, "DebuggerClient::apiFree\n");
usageLogEvent("api grab");
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
const char *DebuggerClient::getUsageMode() {
return isApiMode() ? "api" : "terminal";
}
// Log the execution of a command.
void DebuggerClient::usageLogCommand(const std::string &cmd,
const std::string &data) {
Debugger::UsageLog(getUsageMode(), getSandboxId(), cmd, data);
}
// Log random, interesting events in the client.
void DebuggerClient::usageLogEvent(const std::string &eventName,
const std::string &data) {
Debugger::UsageLog(getUsageMode(), getSandboxId(),
"ClientEvent: " + eventName, data);
}
///////////////////////////////////////////////////////////////////////////////
// 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(), "r");
bool needToWriteFile = f == nullptr;
if (needToWriteFile) 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 && RuntimeOption::EnableDebuggerColor) {
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());
setDebuggerClientSmallStep(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();
m_zendExe = m_config["ZendExecutable"].getString("php");
if (needToWriteFile) {
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";
}
///////////////////////////////////////////////////////////////////////////////
}}