Arquivos
hhvm/hphp/runtime/ext/ext_debugger.cpp
T
Herman Venter f9197ff35c Use & rather than * for pointers that should never be null
This is a rather mechanical refactor that uses references (&) rather than pointers (*) for parameters that are not permitted to ever be given null arguments. In effect, the onus for checking null pointers is shifted from the callee to the caller. The & type annotation makes it clear that the callee is not prepared to deal with a null pointer.
2013-05-20 13:52:30 -07:00

588 linhas
18 KiB
C++

/*
+----------------------------------------------------------------------+
| HipHop for PHP |
+----------------------------------------------------------------------+
| Copyright (c) 2010- Facebook, Inc. (http://www.facebook.com) |
| Copyright (c) 1997-2010 The PHP Group |
+----------------------------------------------------------------------+
| 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/ext/ext_debugger.h"
#include "hphp/runtime/ext/ext_string.h"
#include "hphp/runtime/eval/debugger/cmd/cmd_user.h"
#include "hphp/runtime/eval/debugger/cmd/cmd_interrupt.h"
#include "hphp/runtime/vm/debugger_hook.h"
#include "hphp/runtime/vm/translator/translator-inline.h"
#include "tbb/concurrent_hash_map.h"
#include "hphp/util/logger.h"
#include "hphp/system/lib/systemlib.h"
namespace HPHP {
///////////////////////////////////////////////////////////////////////////////
using namespace Eval;
using HPHP::Transl::CallerFrame;
const int64_t q_DebuggerClientCmdUser$$AUTO_COMPLETE_FILENAMES =
DebuggerClient::AutoCompleteFileNames;
const int64_t q_DebuggerClientCmdUser$$AUTO_COMPLETE_VARIABLES =
DebuggerClient::AutoCompleteVariables;
const int64_t q_DebuggerClientCmdUser$$AUTO_COMPLETE_CONSTANTS =
DebuggerClient::AutoCompleteConstants;
const int64_t q_DebuggerClientCmdUser$$AUTO_COMPLETE_CLASSES =
DebuggerClient::AutoCompleteClasses;
const int64_t q_DebuggerClientCmdUser$$AUTO_COMPLETE_FUNCTIONS =
DebuggerClient::AutoCompleteFunctions;
const int64_t q_DebuggerClientCmdUser$$AUTO_COMPLETE_CLASS_METHODS =
DebuggerClient::AutoCompleteClassMethods;
const int64_t q_DebuggerClientCmdUser$$AUTO_COMPLETE_CLASS_PROPERTIES =
DebuggerClient::AutoCompleteClassProperties;
const int64_t q_DebuggerClientCmdUser$$AUTO_COMPLETE_CLASS_CONSTANTS =
DebuggerClient::AutoCompleteClassConstants;
const int64_t q_DebuggerClientCmdUser$$AUTO_COMPLETE_KEYWORDS =
DebuggerClient::AutoCompleteKeyword;
const int64_t q_DebuggerClientCmdUser$$AUTO_COMPLETE_CODE =
DebuggerClient::AutoCompleteCode;
///////////////////////////////////////////////////////////////////////////////
bool f_hphpd_install_user_command(CStrRef cmd, CStrRef clsname) {
return CmdUser::InstallCommand(cmd, clsname);
}
Array f_hphpd_get_user_commands() {
return CmdUser::GetCommands();
}
static const Trace::Module TRACEMOD = Trace::bcinterp;
void f_hphpd_break(bool condition /* = true */) {
TRACE(5, "in f_hphpd_break()\n");
if (!RuntimeOption::EnableDebugger || !condition ||
g_vmContext->m_dbgNoBreak) {
TRACE(5, "bail !%d || !%d || %d\n", RuntimeOption::EnableDebugger,
condition, g_vmContext->m_dbgNoBreak);
return;
}
CallerFrame cf;
Debugger::InterruptVMHook(HardBreakPoint);
if (RuntimeOption::EvalJit && !g_vmContext->m_interpreting &&
DEBUGGER_FORCE_INTR) {
TRACE(5, "switch mode\n");
throw VMSwitchModeException(true);
}
TRACE(5, "out f_hphpd_break()\n");
}
typedef tbb::concurrent_hash_map<std::string, DebuggerClient*> DbgCltMap;
static DbgCltMap s_dbgCltMap;
// if the DebuggerClient with the same name is already in use, return null
Variant f_hphpd_get_client(CStrRef name /* = null */) {
if (name.empty()) {
return uninit_null();
}
DebuggerClient *client = NULL;
std::string nameStr = name->toCPPString();
{
DbgCltMap::accessor acc;
if (s_dbgCltMap.insert(acc, nameStr)) {
client = new DebuggerClient(nameStr);
acc->second = client;
} else {
client = acc->second;
}
if (!client->apiGrab()) {
// already grabbed by another request
return uninit_null();
}
}
p_DebuggerClient clt(NEWOBJ(c_DebuggerClient));
clt->m_client = client;
return clt;
}
Variant f_hphpd_client_ctrl(CStrRef name, CStrRef op) {
DebuggerClient *client = NULL;
std::string nameStr = name->toCPPString();
{
DbgCltMap::const_accessor acc;
if (!s_dbgCltMap.find(acc, nameStr)) {
if (op.equal("getstate")) {
return q_DebuggerClient$$STATE_INVALID;
} else {
raise_warning("client %s does not exist", name.data());
return uninit_null();
}
}
client = acc->second;
}
if (op.equal("interrupt")) {
if (client->getClientState() < DebuggerClient::StateReadyForCommand) {
raise_warning("client is not initialized");
return uninit_null();
}
if (client->getClientState() != DebuggerClient::StateBusy) {
raise_warning("client is not in a busy state");
return uninit_null();
}
client->onSignal(SIGINT);
return uninit_null();
} else if (op.equal("getstate")) {
return client->getClientState();
} else if (op.equal("reset")) {
// To handle the case when client is in a bad state, e.g. the grabbing
// request encountered error and did not get chance to destruct or call
// sweep. It will remove the client from the map. Here we'd rather take
// the risk of leaking the client than the risk of chasing dangling
// pointers.
//
// FIXME: it's unclear why it should be possible that we would not
// get a chance to destruct or call sweep.
return s_dbgCltMap.erase(nameStr);
}
raise_warning("unknown op %s", op.data());
return uninit_null();
}
///////////////////////////////////////////////////////////////////////////////
c_DebuggerProxyCmdUser::c_DebuggerProxyCmdUser(Class* cb) : ExtObjectData(cb) {
}
c_DebuggerProxyCmdUser::~c_DebuggerProxyCmdUser() {
}
void c_DebuggerProxyCmdUser::t___construct() {
}
bool c_DebuggerProxyCmdUser::t_islocal() {
return m_proxy->isLocal();
}
Variant c_DebuggerProxyCmdUser::t_send(CObjRef cmd) {
CmdUser cmdUser(cmd);
return m_proxy->sendToClient(&cmdUser);
}
///////////////////////////////////////////////////////////////////////////////
c_DebuggerClientCmdUser::c_DebuggerClientCmdUser(Class* cb) : ExtObjectData(cb) {
}
c_DebuggerClientCmdUser::~c_DebuggerClientCmdUser() {
}
void c_DebuggerClientCmdUser::t___construct() {
}
void c_DebuggerClientCmdUser::t_quit() {
m_client->quit();
}
static String format_string(DebuggerClient &client,
int _argc, CStrRef format, CArrRef _argv) {
Variant ret = f_sprintf(_argc, format, _argv);
if (ret.isString()) {
return ret;
}
client.error("Debugger extension failed to format string: %s",
format.data());
return "";
}
void c_DebuggerClientCmdUser::t_print(int _argc, CStrRef format,
CArrRef _argv /* = null_array */) {
m_client->print(format_string(*m_client, _argc, format, _argv));
}
void c_DebuggerClientCmdUser::t_help(int _argc, CStrRef format,
CArrRef _argv /* = null_array */) {
m_client->help(format_string(*m_client, _argc, format, _argv));
}
void c_DebuggerClientCmdUser::t_info(int _argc, CStrRef format,
CArrRef _argv /* = null_array */) {
m_client->info(format_string(*m_client, _argc, format, _argv));
}
void c_DebuggerClientCmdUser::t_output(int _argc, CStrRef format,
CArrRef _argv /* = null_array */) {
m_client->output(format_string(*m_client, _argc, format, _argv));
}
void c_DebuggerClientCmdUser::t_error(int _argc, CStrRef format,
CArrRef _argv /* = null_array */) {
m_client->error(format_string(*m_client, _argc, format, _argv));
}
void c_DebuggerClientCmdUser::t_code(CStrRef source, int highlight_line /* = 0 */,
int start_line_no /* = 0 */,
int end_line_no /* = 0 */) {
m_client->code(source, highlight_line, start_line_no, end_line_no);
}
Variant c_DebuggerClientCmdUser::t_ask(int _argc, CStrRef format,
CArrRef _argv /* = null_array */) {
String ret = format_string(*m_client, _argc, format, _argv);
return String::FromChar(m_client->ask("%s", ret.data()));
}
String c_DebuggerClientCmdUser::t_wrap(CStrRef str) {
return m_client->wrap(str.data());
}
void c_DebuggerClientCmdUser::t_helptitle(CStrRef str) {
m_client->helpTitle(str.data());
}
void c_DebuggerClientCmdUser::t_helpcmds(int _argc, CStrRef cmd, CStrRef desc,
CArrRef _argv /* = null_array */) {
std::vector<String> holders;
std::vector<const char *> cmds;
cmds.push_back(cmd.data());
cmds.push_back(desc.data());
for (int i = 0; i < _argv.size(); i++) {
String s = _argv[i].toString();
holders.push_back(s);
cmds.push_back(s.data());
}
m_client->helpCmds(cmds);
}
void c_DebuggerClientCmdUser::t_helpbody(CStrRef str) {
m_client->helpBody(str.data());
}
void c_DebuggerClientCmdUser::t_helpsection(CStrRef str) {
m_client->helpSection(str.data());
}
void c_DebuggerClientCmdUser::t_tutorial(CStrRef str) {
m_client->tutorial(str.data());
}
String c_DebuggerClientCmdUser::t_getcode() {
return m_client->getCode();
}
String c_DebuggerClientCmdUser::t_getcommand() {
return m_client->getCommand();
}
bool c_DebuggerClientCmdUser::t_arg(int index, CStrRef str) {
return m_client->arg(index + 1, str.data());
}
int64_t c_DebuggerClientCmdUser::t_argcount() {
return m_client->argCount() - 1;
}
String c_DebuggerClientCmdUser::t_argvalue(int index) {
return m_client->argValue(index + 1);
}
String c_DebuggerClientCmdUser::t_linerest(int index) {
return m_client->lineRest(index + 1);
}
Array c_DebuggerClientCmdUser::t_args() {
StringVec *args = m_client->args();
Array ret(Array::Create());
for (unsigned int i = 1; i < args->size(); i++) {
ret.append(String(args->at(i)));
}
return ret;
}
Variant c_DebuggerClientCmdUser::t_send(CObjRef cmd) {
CmdUser cmdUser(cmd);
m_client->sendToServer(&cmdUser);
return true;
}
Variant c_DebuggerClientCmdUser::t_xend(CObjRef cmd) {
CmdUser cmdUser(cmd);
CmdUserPtr ret = m_client->xend<CmdUser>(&cmdUser);
return ret->getUserCommand();
}
static const StaticString s_file("file");
static const StaticString s_line("line");
static const StaticString s_namespace("namespace");
static const StaticString s_class("class");
static const StaticString s_function("function");
static const StaticString s_text("text");
static const StaticString s_user("user");
static const StaticString s_configFName("configFName");
static const StaticString s_host("host");
static const StaticString s_port("port");
static const StaticString s_sandbox("sandbox");
Variant c_DebuggerClientCmdUser::t_getcurrentlocation() {
BreakPointInfoPtr bpi = m_client->getCurrentLocation();
if (!bpi) return Array::Create();
ArrayInit ret(6);
ret.set(s_file, String(bpi->m_file));
ret.set(s_line, (int64_t)bpi->m_line1);
ret.set(s_namespace, String(bpi->getNamespace()));
ret.set(s_class, String(bpi->getClass()));
ret.set(s_function, String(bpi->getFunction()));
ret.set(s_text, String(bpi->site()));
return ret.create();
}
Variant c_DebuggerClientCmdUser::t_getstacktrace() {
return m_client->getStackTrace();
}
int64_t c_DebuggerClientCmdUser::t_getframe() {
return m_client->getFrame();
}
void c_DebuggerClientCmdUser::t_printframe(int index) {
m_client->printFrame(index, m_client->getStackTrace()[index]);
}
void c_DebuggerClientCmdUser::t_addcompletion(CVarRef list) {
if (list.isInteger()) {
m_client->addCompletion((DebuggerClient::AutoComplete)list.toInt64());
} else {
Array arr = list.toArray(); // handles string, array and iterators
std::vector<std::string> items;
for (ArrayIter iter(arr); iter; ++iter) {
items.push_back(iter.second().toString()->toCPPString());
}
m_client->addCompletion(items);
}
}
///////////////////////////////////////////////////////////////////////////////
const int64_t q_DebuggerClient$$STATE_INVALID = -1;
const int64_t q_DebuggerClient$$STATE_UNINIT
= DebuggerClient::StateUninit;
const int64_t q_DebuggerClient$$STATE_INITIALIZING
= DebuggerClient::StateInitializing;
const int64_t q_DebuggerClient$$STATE_READY_FOR_COMMAND
= DebuggerClient::StateReadyForCommand;
const int64_t q_DebuggerClient$$STATE_BUSY
= DebuggerClient::StateBusy;
c_DebuggerClient::c_DebuggerClient(Class* cb) : ExtObjectData(cb) {
m_client = NULL;
}
c_DebuggerClient::~c_DebuggerClient() {
sweep();
}
void c_DebuggerClient::t___construct() {
}
int64_t c_DebuggerClient::t_getstate() {
if (!m_client) {
return q_DebuggerClient$$STATE_INVALID;
}
return m_client->getClientState();
}
Variant c_DebuggerClient::t_init(CVarRef options) {
if (!m_client) {
raise_warning("invalid client");
return false;
}
if (m_client->getClientState() != DebuggerClient::StateUninit) {
return m_client->getClientState() == DebuggerClient::StateReadyForCommand;
}
if (!options.isArray()) {
raise_warning("options must be an array");
return false;
}
m_client->setClientState(DebuggerClient::StateInitializing);
DebuggerClientOptions ops;
ops.apiMode = true;
Array opsArr = options.toArray();
if (opsArr.exists(s_user)) {
ops.user = opsArr.rvalAtRef(s_user).toString().data();
} else {
raise_warning("must specify user in options");
return false;
}
if (opsArr.exists(s_configFName)) {
ops.configFName = opsArr.rvalAtRef(s_configFName).toString().data();
FILE *f = fopen(ops.configFName.c_str(), "r");
if (!f) {
raise_warning("cannot access config file %s", ops.configFName.c_str());
return false;
}
fclose(f);
}
if (opsArr.exists(s_host)) {
ops.host = opsArr.rvalAtRef(s_host).toString().data();
}
if (opsArr.exists(s_port)) {
ops.port = opsArr.rvalAtRef(s_port).toInt32();
}
if (opsArr.exists(s_sandbox)) {
ops.sandbox = opsArr.rvalAtRef(s_sandbox).toString().data();
}
m_client->init(ops);
if (ops.host.empty()) {
ops.host = "localhost";
}
if (ops.port < 0) {
ops.port = RuntimeOption::DebuggerServerPort;
}
bool ret = m_client->connect(ops.host, ops.port);
if (!ret) {
raise_warning("failed to connect to hhvm %s:%d", ops.host.c_str(),
ops.port);
return false;
}
// To wait for the session start interrupt
DebuggerCommandPtr cmd = m_client->waitForNextInterrupt();
if (!cmd->is(DebuggerCommand::KindOfInterrupt) ||
dynamic_pointer_cast<CmdInterrupt>(cmd)->getInterruptType() !=
SessionStarted) {
raise_warning("failed to load sandbox");
return false;
}
ret = m_client->initializeMachine();
if (!ret) {
raise_warning("failed to initialize machine info");
return false;
}
// To wait for the machine loading sandbox
cmd = m_client->waitForNextInterrupt();
if (!cmd->is(DebuggerCommand::KindOfInterrupt) ||
dynamic_pointer_cast<CmdInterrupt>(cmd)->getInterruptType() !=
SessionStarted) {
raise_warning("failed to load sandbox");
return false;
}
m_client->setClientState(DebuggerClient::StateReadyForCommand);
return true;
}
Variant c_DebuggerClient::t_processcmd(CVarRef cmdName, CVarRef args) {
if (!m_client ||
m_client->getClientState() < DebuggerClient::StateReadyForCommand) {
raise_warning("client is not initialized");
return uninit_null();
}
if (m_client->getClientState() != DebuggerClient::StateReadyForCommand) {
raise_warning("client is not ready to take command");
return uninit_null();
}
if (!cmdName.isString()) {
raise_warning("cmdName must be string");
return uninit_null();
}
if (!args.isNull() && !args.isArray()) {
raise_warning("args must be null or array");
return uninit_null();
}
static const char *s_allowedCmds[] = {
"break", "continue", "down", "exception", "frame", "global",
"help", "info", "konstant", "next", "out", "print", "quit", "step",
"up", "variable", "where", "bt", "set", "inst", "=", "@", NULL
};
bool allowed = false;
for (int i = 0; ; i++) {
const char *cmd = s_allowedCmds[i];
if (cmd == NULL) {
break;
}
if (cmdName.same(cmd)) {
allowed = true;
break;
}
}
if (!allowed) {
raise_warning("unsupported command %s", cmdName.toString().data());
return uninit_null();
}
m_client->setCommand(cmdName.toString().data());
StringVec *clientArgs = m_client->args();
clientArgs->clear();
if (!args.isNull()) {
for (ArrayIter iter(args.toArray()); iter; ++iter) {
CStrRef arg = iter.second().toString();
clientArgs->push_back(std::string(arg.data(), arg.size()));
}
}
try {
if (!m_client->process()) {
raise_warning("command \"%s\" not found", cmdName.toString().data());
}
} catch (DebuggerConsoleExitException &e) {
// Flow-control command goes here
Logger::Info("wait for debugger client to stop");
m_client->setTakingInterrupt();
m_client->setClientState(DebuggerClient::StateBusy);
DebuggerCommandPtr cmd = m_client->waitForNextInterrupt();
if (!cmd) {
raise_warning("not getting a command");
} else if (cmd->is(DebuggerCommand::KindOfInterrupt)) {
CmdInterruptPtr cmdInterrupt = dynamic_pointer_cast<CmdInterrupt>(cmd);
cmdInterrupt->onClient(*m_client);
} else {
// Previous pending commands
cmd->handleReply(*m_client);
cmd->setClientOutput(*m_client);
}
Logger::Info("debugger client ready for command");
} catch (DebuggerClientExitException &e) {
const std::string& nameStr = m_client->getNameApi();
Logger::Info("client %s disconnected", nameStr.c_str());
s_dbgCltMap.erase(nameStr);
delete m_client;
m_client = NULL;
return true;
} catch (DebuggerProtocolException &e) {
raise_warning("DebuggerProtocolException");
return uninit_null();
}
return m_client->getOutputArray();
}
void c_DebuggerClient::sweep() {
if (m_client) {
// Note: it's important that resetSmartAllocatedMembers happens
// before clearCachedLocal(), because the smart allocated pointers
// are already invalid.
m_client->resetSmartAllocatedMembers();
m_client->clearCachedLocal();
m_client->apiFree();
}
}
///////////////////////////////////////////////////////////////////////////////
}