Arquivos
hhvm/hphp/runtime/eval/debugger/cmd/cmd_info.cpp
T
Herman Venter 1d6efdcbb4 Rename DebuggerProxy::send, DebuggerClient::send and DebuggerClient::recv
I found myself getting confused, thinking that proxy->send(cmd) sends a command to the proxy, when in fact it causes the proxy to send the command to the client. These are now named sendToClient, sendToServer and recvFromServer so that future readers (including my future self) will not make this mistake again.
2013-04-24 17:13:58 -07:00

522 linhas
18 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/cmd/cmd_info.h>
#include <runtime/eval/debugger/cmd/cmd_variable.h>
#include <runtime/ext/ext_reflection.h>
#include <runtime/base/preg.h>
#include <util/logger.h>
namespace HPHP { namespace Eval {
///////////////////////////////////////////////////////////////////////////////
void CmdInfo::sendImpl(DebuggerThriftBuffer &thrift) {
DebuggerCommand::sendImpl(thrift);
thrift.write(m_type);
thrift.write(m_symbol);
thrift.write(m_info);
if (m_acLiveLists) {
thrift.write(true);
thrift.write((int8_t)DebuggerClient::AutoCompleteCount);
for (int i = 0; i < DebuggerClient::AutoCompleteCount; i++) {
thrift.write((*m_acLiveLists)[i]);
}
} else {
thrift.write(false);
}
}
void CmdInfo::recvImpl(DebuggerThriftBuffer &thrift) {
DebuggerCommand::recvImpl(thrift);
thrift.read(m_type);
thrift.read(m_symbol);
thrift.read(m_info);
bool hasLists;
thrift.read(hasLists);
if (hasLists) {
m_acLiveLists = DebuggerClient::CreateNewLiveLists();
int8_t count;
thrift.read(count);
for (int i = 0; i < count; i++) {
if (i < DebuggerClient::AutoCompleteCount) {
thrift.read((*m_acLiveLists)[i]);
} else {
vector<std::string> future;
thrift.read(future);
}
}
}
}
void CmdInfo::list(DebuggerClient *client) {
client->addCompletion(DebuggerClient::AutoCompleteFunctions);
client->addCompletion(DebuggerClient::AutoCompleteClasses);
client->addCompletion(DebuggerClient::AutoCompleteClassMethods);
client->addCompletion(DebuggerClient::AutoCompleteClassProperties);
client->addCompletion(DebuggerClient::AutoCompleteClassConstants);
}
bool CmdInfo::help(DebuggerClient *client) {
client->helpTitle("Info Command");
client->helpCmds(
"info", "displays current function's info",
"info {cls}", "displays declaration of this class",
"info {function}", "displays declaration of this function",
"info {cls::method}", "displays declaration of this method",
"info {cls::constant}", "displays declaration of this constant",
"info {cls::$property}", "displays declaration of this property",
nullptr
);
client->helpBody(
"Use this command to display declaration of a symbol."
);
return true;
}
bool CmdInfo::parseZeroArg(DebuggerClient *client) {
assert(client->argCount() == 0);
BreakPointInfoPtr bpi = client->getCurrentLocation();
if (bpi) {
m_symbol = bpi->getClass();
m_type = KindOfClass;
if (m_symbol.empty()) {
m_symbol = bpi->getFunction();
m_type = KindOfFunction;
}
}
return !m_symbol.empty();
}
void CmdInfo::parseOneArg(DebuggerClient *client, string &subsymbol) {
assert(client->argCount() == 1);
string symbol = client->argValue(1);
size_t pos = symbol.find("::");
if (pos != string::npos) {
m_symbol = String(symbol.substr(0, pos));
m_type = KindOfClass;
subsymbol = symbol.substr(pos + 2);
} else if (symbol.size() > 2 && symbol.substr(symbol.size() - 2) == "()") {
m_symbol = symbol.substr(0, symbol.size() - 2);
m_type = KindOfFunction;
} else {
m_symbol = String(symbol);
m_type = KindOfUnknown;
}
}
bool CmdInfo::onClient(DebuggerClient *client) {
if (DebuggerCommand::onClient(client)) return true;
string subsymbol;
if (client->argCount() == 0) {
if (!parseZeroArg(client)) {
client->error("There is no current function or method to look up.");
client->tutorial(
"You can only use '[i]nfo' without a symbol name when you are running "
"your program and it breaks at a function or a class method. It will "
"then look up information about that function or method."
);
return true;
}
} else if (client->argCount() == 1) {
parseOneArg(client, subsymbol);
} else {
return help(client);
}
CmdInfoPtr cmd = client->xend<CmdInfo>(this);
Array info = cmd->m_info;
if (info.empty()) {
client->info("(specified symbol cannot be found)");
} else {
for (ArrayIter iter(info); iter; ++iter) {
StringBuffer sb;
PrintInfo(client, sb, iter.second(), subsymbol);
client->code(sb.detach());
}
}
return true;
}
void CmdInfo::UpdateLiveLists(DebuggerClient *client) {
CmdInfo cmd;
cmd.m_type = KindOfLiveLists;
CmdInfoPtr res = client->xend<CmdInfo>(&cmd);
client->setLiveLists(res->m_acLiveLists);
}
String CmdInfo::GetProtoType(DebuggerClient *client, const std::string &cls,
const std::string &func) {
CmdInfo cmd;
cmd.m_type = KindOfFunction;
if (cls.empty()) {
cmd.m_symbol = String(func);
} else {
cmd.m_symbol = String(cls) + "::" + String(func);
}
CmdInfoPtr res = client->xend<CmdInfo>(&cmd);
Array info = res->m_info;
if (!info.empty()) {
info = info[0];
if (info.exists("params")) {
StringBuffer sb;
sb.printf("function %s%s(%s);\n",
info["ref"].toBoolean() ? "&" : "",
info["name"].toString().data(),
GetParams(info["params"], info["varg"]).data());
return sb.detach();
}
}
return String();
}
bool CmdInfo::onServer(DebuggerProxy *proxy) {
if (m_type == KindOfLiveLists) {
std::vector<String> tmpAcLiveLists[DebuggerClient::AutoCompleteCount];
m_acLiveLists = DebuggerClient::CreateNewLiveLists();
try {
ClassInfo::GetSymbolNames(
tmpAcLiveLists[DebuggerClient::AutoCompleteClasses],
tmpAcLiveLists[DebuggerClient::AutoCompleteFunctions],
tmpAcLiveLists[DebuggerClient::AutoCompleteConstants],
&tmpAcLiveLists[DebuggerClient::AutoCompleteClassMethods],
&tmpAcLiveLists[DebuggerClient::AutoCompleteClassProperties],
&tmpAcLiveLists[DebuggerClient::AutoCompleteClassConstants]);
} catch (Exception &e) {
Logger::Error("Caught exception %s, auto-complete lists incomplete",
e.getMessage().c_str());
} catch(...) {
Logger::Error("Caught unknown exception, auto-complete lists incomplete");
}
int tempList[] = {DebuggerClient::AutoCompleteClasses,
DebuggerClient::AutoCompleteFunctions,
DebuggerClient::AutoCompleteConstants,
DebuggerClient::AutoCompleteClassMethods,
DebuggerClient::AutoCompleteClassProperties,
DebuggerClient::AutoCompleteClassConstants};
for (unsigned int i = 0 ; i < sizeof(tempList)/sizeof(int); ++i) {
for (unsigned int j = 0 ; j < tmpAcLiveLists[tempList[i]].size(); ++j) {
(*m_acLiveLists)[tempList[i]].push_back(
tmpAcLiveLists[tempList[i]][j]->toCPPString());
}
}
Array variables = g_vmContext->getLocalDefinedVariables(0);
variables += CmdVariable::GetGlobalVariables();
vector<std::string> &vars =
(*m_acLiveLists)[DebuggerClient::AutoCompleteVariables];
vars.reserve(variables.size());
for (ArrayIter iter(variables); iter; ++iter) {
vars.push_back("$" + iter.first().toString()->toCPPString());
}
return proxy->sendToClient(this);
}
if (m_type == KindOfUnknown || m_type == KindOfClass) {
try {
Array ret = f_hphp_get_class_info(m_symbol);
if (!ret.empty()) {
m_info.append(ret);
}
} catch (...) {}
}
if (m_type == KindOfUnknown || m_type == KindOfFunction) {
try {
Array ret = f_hphp_get_function_info(m_symbol);
if (!ret.empty()) {
m_info.append(ret);
}
} catch (...) {}
}
return proxy->sendToClient(this);
}
///////////////////////////////////////////////////////////////////////////////
void CmdInfo::PrintDocComments(StringBuffer &sb, CArrRef info) {
if (info["doc"].isString()) {
String doc = info["doc"].toString();
int space1 = 0; // best guess
int space2 = 3; // best guess
Variant matches1, matches2;
Variant ret1 = preg_match("#^( *)/\\*#s", doc, matches1);
Variant ret2 = preg_match("#\n( *)\\*#s", doc, matches2);
if (!same(ret1, false) && !same(ret2, false)) {
// we have perfect doc comment blocks, so we can re-adjust spaces
space1 = matches1[1].toString().size();
space2 = matches2[1].toString().size();
}
String spaces = StringUtil::Repeat(" ", space2 - space1 - 1);
sb.printf("%s%s\n", spaces.data(), doc.data());
}
}
void CmdInfo::PrintHeader(DebuggerClient *client, StringBuffer &sb,
CArrRef info) {
if (!info["internal"].toBoolean()) {
String file = info["file"].toString();
int line1 = info["line1"].toInt32();
int line2 = info["line2"].toInt32();
if (file.empty() && line1 == 0 && line2 == 0) {
sb.printf("// (source unknown)\n");
} else if (line1 == 0 && line2 == 0) {
sb.printf("// defined in %s\n", file.data());
} else if (line1 && line2 && line1 != line2) {
sb.printf("// defined on line %d to %d of %s\n", line1, line2,
file.data());
client->setListLocation(file.data(), line1 - 1, false);
} else {
int line = line1 ? line1 : line2;
sb.printf("// defined on line %d of %s\n", line, file.data());
client->setListLocation(file.data(), line - 1, false);
}
}
PrintDocComments(sb, info);
}
String CmdInfo::GetParams(CArrRef params, bool varg,
bool detailed /* = false */) {
StringBuffer args;
for (ArrayIter iter(params); iter; ++iter) {
if (!args.empty()) {
args.append(", ");
}
Array arg = iter.second().toArray();
if (!arg["type"].toString().empty()) {
args.append(arg["type"].toString());
args.append(' ');
}
if (arg["ref"].toBoolean()) {
args.append('&');
}
args.append('$');
args.append(arg["name"].toString());
if (arg.exists("default")) {
args.append(" = ");
Variant defValue = arg["default"];
String defText = arg["defaultText"];
if (!defText.empty()) {
args.append(defText);
} else if (defValue.isObject()) {
// ClassInfo was not able to serialize the value, so ext_reflection
// prepared a stdClass error object. We should fall back to display
// the original PHP text, if there.
args.append(defValue.o_get("msg").toString());
} else if (detailed) {
args.append(DebuggerClient::FormatVariable(arg["default"], -1));
} else {
args.append(DebuggerClient::FormatVariable(arg["default"]));
}
}
}
if (varg) {
if (!args.empty()) {
args.append(", ");
}
args.append("...");
}
return args.detach();
}
String CmdInfo::GetModifier(CArrRef info, const char *name) {
if (info[name].toBoolean()) {
return String(name) + " ";
}
return "";
}
String CmdInfo::FindSubSymbol(CArrRef symbols, const std::string &symbol) {
for (ArrayIter iter(symbols); iter; ++iter) {
String key = iter.first().toString();
if (strcasecmp(key.data(), symbol.c_str()) == 0) {
return key;
}
}
return String();
}
bool CmdInfo::TryConstant(StringBuffer &sb, CArrRef info,
const std::string &subsymbol) {
String key = FindSubSymbol(info["constants"], subsymbol);
if (!key.isNull()) {
sb.printf(" const %s = %s;\n", key.data(),
DebuggerClient::FormatVariable
(info["constants"][key], -1).data());
return true;
}
return false;
}
bool CmdInfo::TryProperty(StringBuffer &sb, CArrRef info,
const std::string &subsymbol) {
String key = FindSubSymbol(info["properties"],
subsymbol[0] == '$' ?
subsymbol.substr(1) : subsymbol);
if (!key.isNull()) {
Array prop = info["properties"][key];
PrintDocComments(sb, prop);
sb.printf(" %s %s$%s;\n",
prop["access"].toString().data(),
GetModifier(prop, "static").data(),
prop["name"].toString().data());
return true;
}
key = FindSubSymbol(info["private_properties"],
subsymbol[0] == '$' ?
subsymbol.substr(1) : subsymbol);
if (!key.isNull()) {
Array prop = info["private_properties"][key];
PrintDocComments(sb, prop);
sb.printf(" private %s$%s;\n",
GetModifier(prop, "static").data(),
prop["name"].toString().data());
return true;
}
return false;
}
bool CmdInfo::TryMethod(DebuggerClient *client, StringBuffer &sb, CArrRef info,
std::string subsymbol) {
if (subsymbol.size() > 2 && subsymbol.substr(subsymbol.size() - 2) == "()") {
subsymbol = subsymbol.substr(0, subsymbol.size() - 2);
}
String key = FindSubSymbol(info["methods"], subsymbol);
if (!key.isNull()) {
Array func = info["methods"][key].toArray();
PrintHeader(client, sb, func);
sb.printf("%s %s%s%sfunction %s::%s%s(%s);\n",
func["access"].toString().data(),
GetModifier(func, "static").data(),
GetModifier(func, "final").data(),
GetModifier(func, "abstract").data(),
info["name"].toString().data(),
func["ref"].toBoolean() ? "&" : "",
func["name"].toString().data(),
GetParams(func["params"], func["varg"], true).data());
return true;
}
return false;
}
void CmdInfo::PrintInfo(DebuggerClient *client, StringBuffer &sb, CArrRef info,
const std::string &subsymbol) {
if (info.exists("params")) {
PrintHeader(client, sb, info);
sb.printf("function %s%s(%s);\n",
info["ref"].toBoolean() ? "&" : "",
info["name"].toString().data(),
GetParams(info["params"], info["varg"]).data());
return;
}
bool found = false;
if (!subsymbol.empty()) {
if (TryConstant(sb, info, subsymbol)) found = true;
if (TryProperty(sb, info, subsymbol)) found = true;
if (TryMethod(client, sb, info, subsymbol)) found = true;
if (found) return;
client->info("Specified symbol cannot be found. Here the whole class:\n");
}
PrintHeader(client, sb, info);
StringBuffer parents;
String parent = info["parent"].toString();
if (!parent.empty()) {
parents.append("extends ");
parents.append(parent);
parents.append(' ');
}
if (!info["interfaces"].toArray().empty()) {
parents.append("implements ");
bool first = true;
for (ArrayIter iter(info["interfaces"]); iter; ++iter) {
if (first) {
first = false;
} else {
parents.append(", ");
}
parents.append(iter.first().toString());
}
parents.append(' ');
}
parent = parents.detach();
sb.printf("%s%s%s %s %s{\n",
GetModifier(info, "final").data(),
GetModifier(info, "abstract").data(),
info["interface"].toBoolean() ? "interface" : "class",
info["name"].toString().data(),
parent.data());
if (!info["constants"].toArray().empty()) {
sb.printf(" // constants\n");
for (ArrayIter iter(info["constants"]); iter; ++iter) {
sb.printf(" const %s = %s;\n",
iter.first().toString().data(),
DebuggerClient::FormatVariable(iter.second()).data());
}
}
if (!info["properties"].toArray().empty() ||
!info["private_properties"].toArray().empty()) {
sb.printf(" // properties\n");
for (ArrayIter iter(info["properties"]); iter; ++iter) {
Array prop = iter.second().toArray();
sb.printf(" %s%s %s$%s;\n",
prop["doc"].toBoolean() ? "[doc] " : "",
prop["access"].toString().data(),
GetModifier(prop, "static").data(),
prop["name"].toString().data());
}
for (ArrayIter iter(info["private_properties"]); iter; ++iter) {
Array prop = iter.second().toArray();
sb.printf(" %sprivate %s$%s;\n",
prop["doc"].toBoolean() ? "[doc] " : "",
GetModifier(prop, "static").data(),
prop["name"].toString().data());
}
}
if (!info["methods"].toArray().empty()) {
sb.printf(" // methods\n");
for (ArrayIter iter(info["methods"]); iter; ++iter) {
Array func = iter.second().toArray();
sb.printf(" %s%s %s%s%sfunction %s%s(%s);\n",
func["doc"].toBoolean() ? "[doc] " : "",
func["access"].toString().data(),
GetModifier(func, "static").data(),
GetModifier(func, "final").data(),
GetModifier(func, "abstract").data(),
func["ref"].toBoolean() ? "&" : "",
func["name"].toString().data(),
GetParams(func["params"], func["varg"]).data());
}
}
sb.printf("}\n");
}
///////////////////////////////////////////////////////////////////////////////
}}