Arquivos
hhvm/hphp/runtime/debugger/cmd/cmd_print.cpp
T
2013-06-25 13:19:07 -07:00

393 linhas
12 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/cmd/cmd_print.h"
#include "hphp/runtime/base/time/datetime.h"
#include "hphp/runtime/base/string_util.h"
#include "hphp/runtime/vm/debugger_hook.h"
namespace HPHP { namespace Eval {
///////////////////////////////////////////////////////////////////////////////
TRACE_SET_MOD(debugger);
const char *CmdPrint::Formats[] = {
"x", "v", "hex", "oct", "dec", "unsigned", "time", nullptr
};
std::string CmdPrint::FormatResult(const char *format, CVarRef ret) {
if (format == nullptr) {
String sret = DebuggerClient::FormatVariable(ret, -1);
return string(sret.data(), sret.size());
}
if (strcmp(format, "v") == 0) {
String sret = DebuggerClient::FormatVariable(ret, -1, true);
return string(sret.data(), sret.size());
}
if (strcmp(format, "dec") == 0 ||
strcmp(format, "unsigned") == 0 ||
ret.isInteger()) {
int64_t nret = ret.toInt64();
char buf[64];
if (strcmp(format, "hex") == 0 || strcmp(format, "x") == 0) {
snprintf(buf, sizeof(buf), "%" PRIx64, nret);
return buf;
}
if (strcmp(format, "oct") == 0) {
snprintf(buf, sizeof(buf), "%" PRIo64, nret);
return buf;
}
if (strcmp(format, "dec") == 0) {
snprintf(buf, sizeof(buf), "%" PRId64, nret);
return buf;
}
if (strcmp(format, "unsigned") == 0) {
snprintf(buf, sizeof(buf), "%" PRIu64, nret);
return buf;
}
if (strcmp(format, "time") == 0) {
StringBuffer sb;
DateTime dt(nret);
sb.append("RFC822: ");
sb.append(dt.toString(DateTime::DateFormat::RFC822));
sb.append("\nRFC850: ");
sb.append(dt.toString(DateTime::DateFormat::RFC850));
sb.append("\nRFC1036: ");
sb.append(dt.toString(DateTime::DateFormat::RFC1036));
sb.append("\nRFC1123/RSS: ");
sb.append(dt.toString(DateTime::DateFormat::RFC1123));
sb.append("\nRFC2822: ");
sb.append(dt.toString(DateTime::DateFormat::RFC2822));
sb.append("\nRFC3339/ATOM/W3C: ");
sb.append(dt.toString(DateTime::DateFormat::RFC3339));
sb.append("\nISO8601: ");
sb.append(dt.toString(DateTime::DateFormat::ISO8601));
sb.append("\nCookie: ");
sb.append(dt.toString(DateTime::DateFormat::Cookie));
sb.append("\nHttpHeader: ");
sb.append(dt.toString(DateTime::DateFormat::HttpHeader));
return sb.data();
}
assert(false);
}
String sret = DebuggerClient::FormatVariable(ret, -1);
if (strcmp(format, "hex") == 0 || strcmp(format, "x") == 0 ||
strcmp(format, "oct") == 0) {
StringBuffer sb;
for (int i = 0; i < sret.size(); i++) {
char ch = sret[i];
if (isprint(ch)) {
sb.append(ch);
} else {
char buf[6];
if (strcmp(format, "oct") == 0) {
snprintf(buf, sizeof(buf), "\\%03o", ch);
} else {
snprintf(buf, sizeof(buf), "\\x%02x", ch);
}
sb.append(buf);
}
}
return sb.data() ? sb.data() : "";
}
if (strcmp(format, "time") == 0) {
DateTime dt;
int64_t ts = -1;
if (dt.fromString(ret.toString(), SmartObject<TimeZone>())) {
bool err;
ts = dt.toTimeStamp(err);
}
return String(ts).data();
}
assert(false);
return "";
}
///////////////////////////////////////////////////////////////////////////////
void CmdPrint::sendImpl(DebuggerThriftBuffer &thrift) {
DebuggerCommand::sendImpl(thrift);
if (m_printLevel > 0) {
g_context->setDebuggerPrintLevel(m_printLevel);
}
{
String sdata;
DebuggerWireHelpers::WireSerialize(m_ret, sdata);
thrift.write(sdata);
}
if (m_printLevel > 0) {
g_context->setDebuggerPrintLevel(-1);
}
thrift.write(m_output);
thrift.write(m_frame);
thrift.write(m_bypassAccessCheck);
thrift.write(m_printLevel);
thrift.write(m_noBreak);
}
void CmdPrint::recvImpl(DebuggerThriftBuffer &thrift) {
DebuggerCommand::recvImpl(thrift);
{
String sdata;
thrift.read(sdata);
int error = DebuggerWireHelpers::WireUnserialize(sdata, m_ret);
if (error) {
m_ret = uninit_null();
}
if (error == DebuggerWireHelpers::HitLimit) {
m_wireError = "Hit unserialization limit. "
"Try with smaller print level";
}
}
thrift.read(m_output);
thrift.read(m_frame);
thrift.read(m_bypassAccessCheck);
thrift.read(m_printLevel);
thrift.read(m_noBreak);
}
void CmdPrint::list(DebuggerClient &client) {
if (client.arg(1, "clear")) {
client.addCompletion("all");
return;
}
client.addCompletion(DebuggerClient::AutoCompleteCode);
if (client.argCount() == 0) {
client.addCompletion(Formats);
client.addCompletion("always");
client.addCompletion("list");
client.addCompletion("clear");
} else if (client.argCount() == 1 && client.arg(1, "always")) {
client.addCompletion(Formats);
}
}
void CmdPrint::help(DebuggerClient &client) {
client.helpTitle("Print Command");
client.helpCmds(
"[p]rint {php}", "prints result of PHP code, (print_r)",
"[p]rint v {php}", "prints result of PHP code, (var_dump)",
"[p]rint x {php}", "prints hex encoded string or number",
"[p]rint [h]ex {php}", "prints hex encoded string or number",
"[p]rint [o]ct {php}", "prints octal encoded string or number",
"[p]rint [d]ec {php}", "prints as signed integer",
"[p]rint [u]nsigned {php}", "prints as unsigned integer",
"[p]rint [t]ime {php}", "converts between time and timestamp",
"", "",
"[p]rint [a]lways {above}", "adds a watch expression at break",
"[p]rint [l]ist", "lists watch expressions",
"[p]rint [c]lear {index}", "clears a watch expression",
"[p]rint [c]lear [a]ll", "clears all watch expressions",
nullptr
);
client.helpBody(
"Prints result of an expression in certain format. If '[a]lways' is "
"specified, the expression will be added to a watch list. At every break, "
"either at a breakpoint or caused by step commands, these expressions "
"will be evaluated and printed out."
);
}
void CmdPrint::processList(DebuggerClient &client) {
DebuggerClient::WatchPtrVec &watches = client.getWatches();
for (int i = 0; i < (int)watches.size(); i++) {
client.print(" %d %s %s", i + 1,
StringUtil::Pad(watches[i]->first, 8, " ",
StringUtil::PadType::Left).data(),
watches[i]->second.c_str());
}
if (watches.empty()) {
client.tutorial(
"Use '[p]rint [a]lways ...' to set new watch expressions. "
"Use '[p]rint ?|[h]elp' to read how to set them. "
);
} else {
client.tutorial(
"Use '[p]rint [c]lear {index}|[a]ll' to remove watch expression(s). "
);
}
}
void CmdPrint::processClear(DebuggerClient &client) {
DebuggerClient::WatchPtrVec &watches = client.getWatches();
if (watches.empty()) {
client.error("There is no watch expression to clear.");
client.tutorial(
"Use '[p]rint [a]lways ...' to set new watch expressions. "
"Use '[p]rint ?|[h]elp' to read how to set them. "
);
return;
}
if (client.arg(2, "all")) {
watches.clear();
client.info("All watch expressions are cleared.");
return;
}
string snum = client.argValue(2);
if (!DebuggerClient::IsValidNumber(snum)) {
client.error("'[p]rint [c]lear' needs an {index} argument.");
client.tutorial(
"You will have to run '[p]rint [l]ist' first to see a list of valid "
"numbers or indices to specify."
);
return;
}
int num = atoi(snum.c_str()) - 1;
if (num < 0 || num >= (int)watches.size()) {
client.error("\"%s\" is not a valid index. Choose one from this list:",
snum.c_str());
processList(client);
return;
}
watches.erase(watches.begin() + num);
}
Variant CmdPrint::processWatch(DebuggerClient &client, const char *format,
const std::string &php) {
m_body = php;
m_frame = client.getFrame();
m_noBreak = true;
CmdPrintPtr res = client.xend<CmdPrint>(this);
if (!res->m_output.empty()) {
client.output(res->m_output);
}
return res->m_ret;
}
void CmdPrint::handleReply(DebuggerClient &client) {
if (!m_output.empty()) {
client.output(m_output);
}
client.output(m_ret.toString());
}
void CmdPrint::onClientImpl(DebuggerClient &client) {
if (DebuggerCommand::displayedHelp(client)) return;
if (client.argCount() == 0) {
help(client);
return;
}
int index = 1;
if (client.arg(1, "always")) {
m_isForWatch = true;
if (client.argCount() == 1) {
client.error("'[p]rint [a]lways' needs an expression to watch.");
return;
}
index++;
} else if (client.arg(1, "list")) {
m_isForWatch = true;
processList(client);
return;
} else if (client.arg(1, "clear")) {
m_isForWatch = true;
processClear(client);
return;
}
const char *format = nullptr;
for (const char **fmt = Formats; *fmt; fmt++) {
if (client.arg(index, *fmt)) {
format = *fmt;
index++;
break;
}
}
m_body = client.lineRest(index);
if (m_isForWatch) {
client.addWatch(format, m_body);
return;
}
m_bypassAccessCheck = client.getDebuggerBypassCheck();
m_printLevel = client.getDebuggerPrintLevel();
assert(m_printLevel <= 0 || m_printLevel >= DebuggerClient::MinPrintLevel);
m_frame = client.getFrame();
CmdPrintPtr res = client.xendWithNestedExecution<CmdPrint>(this);
if (!res->is(m_type)) {
assert(client.isApiMode());
m_incomplete = true;
res->setClientOutput(client);
} else {
m_output = res->m_output;
m_ret = res->m_ret;
if (!m_output.empty()) {
client.output(m_output);
}
client.output(FormatResult(format, m_ret));
}
}
const StaticString
s_format("format"),
s_php("php"),
s_body("body"),
s_value_serialize("value_serialize"),
s_value("value");
void CmdPrint::setClientOutput(DebuggerClient &client) {
client.setOutputType(DebuggerClient::OTValues);
Array values;
if (m_isForWatch) {
// Manipulating the watch list, output the current list
DebuggerClient::WatchPtrVec &watches = client.getWatches();
for (int i = 0; i < (int)watches.size(); i++) {
ArrayInit watch(2);
watch.set(s_format, watches[i]->first);
watch.set(s_php, watches[i]->second);
values.append(watch.create());
}
} else {
// Just print an expression, do similar output as eval
values.set(s_body, m_body);
if (client.getDebuggerClientApiModeSerialize()) {
values.set(s_value_serialize,
DebuggerClient::FormatVariable(m_ret, 200));
} else {
values.set(s_value, m_ret);
}
}
client.setOTValues(values);
}
bool CmdPrint::onServer(DebuggerProxy &proxy) {
PCFilter* locSave = g_vmContext->m_lastLocFilter;
g_vmContext->m_lastLocFilter = new PCFilter();
g_vmContext->setDebuggerBypassCheck(m_bypassAccessCheck);
{
EvalBreakControl eval(m_noBreak);
m_ret = DebuggerProxy::ExecutePHP(DebuggerProxy::MakePHPReturn(m_body),
m_output, !proxy.isLocal(), m_frame);
}
g_vmContext->setDebuggerBypassCheck(false);
delete g_vmContext->m_lastLocFilter;
g_vmContext->m_lastLocFilter = locSave;
return proxy.sendToClient(this);
}
///////////////////////////////////////////////////////////////////////////////
}}