917fffb462
Currently the debugger prints the line where an exception has been thrown, along with the type exception. Then it prints the source listing of the exception. Then it prints a serialization of the exception, which includes a full stack trace. Since the debugger has a command for printing stack traces, the latter bit information seems completely redundant when stopped in the command line client. This diff suppresses the stack trace in that case. It also suppresses redundant diagnostics that get generated during the debugger's attempt to find and print the source code in response to a list command. Finally, the quit command was too eager to let the client die after notifying the proxy, causing the proxy to get a pipe closed exception rather than the quit command, which often allowed the program to carry on running after the client has already quit. The quit command now waits for an acknowledgement from the proxy before shutting down.
368 linhas
14 KiB
C++
368 linhas
14 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_list.h"
|
|
#include "hphp/runtime/debugger/cmd/cmd_info.h"
|
|
#include "hphp/runtime/base/file/file.h"
|
|
#include "hphp/runtime/ext/ext_file.h"
|
|
|
|
namespace HPHP { namespace Eval {
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
TRACE_SET_MOD(debugger);
|
|
|
|
// Always called from send and implements specific
|
|
// logic for serializing a list command to send via Thrift.
|
|
void CmdList::sendImpl(DebuggerThriftBuffer &thrift) {
|
|
DebuggerCommand::sendImpl(thrift);
|
|
thrift.write(m_file);
|
|
thrift.write(m_line1);
|
|
thrift.write(m_line2);
|
|
thrift.write(m_code);
|
|
}
|
|
|
|
// Always called from recv and implements specific
|
|
// logic for deserializing a list command received via Thrift.
|
|
void CmdList::recvImpl(DebuggerThriftBuffer &thrift) {
|
|
DebuggerCommand::recvImpl(thrift);
|
|
thrift.read(m_file);
|
|
thrift.read(m_line1);
|
|
thrift.read(m_line2);
|
|
thrift.read(m_code);
|
|
}
|
|
|
|
// Informs the client of all strings that may follow a list command.
|
|
// Used for auto completion. The client uses the prefix of the argument
|
|
// following the command to narrow down the list displayed to the user.
|
|
void CmdList::list(DebuggerClient &client) {
|
|
client.addCompletion(DebuggerClient::AutoCompleteFileNames);
|
|
}
|
|
|
|
// The text to display when the debugger client processes "help list".
|
|
void CmdList::help(DebuggerClient &client) {
|
|
client.helpTitle("List Command");
|
|
client.helpCmds(
|
|
"list", "displays current block of source code",
|
|
"list {line}", "displays code around specified line",
|
|
"list {line1}-{line2}", "displays specified block of source code",
|
|
"list {line1}-", "displays code starting with the line",
|
|
"list -{line2}", "displays code ending with the line",
|
|
"list {file}", "displays beginning lines of the file",
|
|
"list {cls}", "displays beginning lines of the class",
|
|
"list {function}", "displays beginning lines of the function",
|
|
"list {cls::method}", "displays beginning lines of the method",
|
|
"list {file}:{line}", "displays code around specified file:line",
|
|
"list {file}:{l1}-{l2}", "displays specified block in the file",
|
|
"list {file}:{l1}-", "displays specified block in the file",
|
|
"list {file}:-{l2}", "displays specified block in the file",
|
|
"list {directory}", "sets PHP source root directory",
|
|
nullptr
|
|
);
|
|
client.helpBody(
|
|
"Use list command to display PHP source code. In remote debugging, this "
|
|
"is displaying source code on server side. When server side cannot find "
|
|
"the file, it will fall back to local files.\n"
|
|
"\n"
|
|
"Hit return to display more lines of code after current display.\n"
|
|
"\n"
|
|
"When a directory name is specified, this will become a root directory "
|
|
"for resolving relative paths of PHP files. Files with absolute paths "
|
|
"will not be affected by this setting. This directory will be stored "
|
|
"in configuration file for future sessions as well."
|
|
);
|
|
}
|
|
|
|
// Retrieves the current source location (file, line).
|
|
// The current location is initially determined by the location
|
|
// where execution was interrupted to hand control back to
|
|
// the debugger client 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.
|
|
//
|
|
// If m_line1 and m_line2 are currently 0 (because they have not been specified
|
|
// as parameters to the list command), they are updated to point to a block of
|
|
// code one line beyond the current line maintained by the client.
|
|
// This has the effect that a succession of list commands that specify no
|
|
// parameters will scroll sequentially through the source code in blocks
|
|
// of DebuggerClient::CodeBlockSize.
|
|
void CmdList::getListLocation(DebuggerClient &client, int &lineFocus0,
|
|
int &charFocus0, int &lineFocus1,
|
|
int &charFocus1) {
|
|
int currentLine = 0;
|
|
client.getListLocation(m_file, currentLine, lineFocus0, charFocus0,
|
|
lineFocus1, charFocus1);
|
|
if (m_line1 == 0 && m_line2 == 0) {
|
|
m_line1 = currentLine + 1;
|
|
m_line2 = m_line1 + DebuggerClient::CodeBlockSize;
|
|
}
|
|
}
|
|
|
|
// If there is no current file, print the desired range of eval code
|
|
// or give an error message if the debugger is not currently performing
|
|
// an eval command.
|
|
void CmdList::listEvalCode(DebuggerClient &client) {
|
|
assert(m_file.empty());
|
|
|
|
string evalCode = client.getCode();
|
|
if (evalCode.empty()) {
|
|
client.error("There is no current source file.");
|
|
} else {
|
|
client.print(highlight_php(evalCode));
|
|
}
|
|
}
|
|
|
|
// Sends this list command to the server to retrieve the source to be listed
|
|
// and then displays the source on the client. The client's current line
|
|
// is then updated to point to the last listed line.
|
|
// Returns false if the server was unable to return source for this command.
|
|
bool CmdList::listFileRange(DebuggerClient &client,
|
|
int lineFocus0, int charFocus0,
|
|
int lineFocus1, int charFocus1) {
|
|
if (m_line1 <= 0) m_line1 = 1;
|
|
if (m_line2 <= 0) m_line2 = 1;
|
|
if (m_line1 > m_line2) {
|
|
int32_t tmp = m_line1;
|
|
m_line1 = m_line2;
|
|
m_line2 = tmp;
|
|
}
|
|
|
|
CmdListPtr res = client.xend<CmdList>(this);
|
|
if (res->m_code.isString()) {
|
|
if (!client.code(res->m_code, m_line1, m_line2, lineFocus0, charFocus0,
|
|
lineFocus1, charFocus1)) {
|
|
client.info("No more lines in %s to display.", m_file.c_str());
|
|
}
|
|
client.setListLocation(m_file, m_line2, false);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static const StaticString
|
|
s_methods("methods"),
|
|
s_file("file"),
|
|
s_line1("line2"),
|
|
s_line2("line2");
|
|
|
|
// Sends an Info command to the server to retrieve source location
|
|
// information for the function or class specified by the command
|
|
// argument. Then updates this command with the source information
|
|
// and sends it to the server in order to retrieve the source
|
|
// text from the server.
|
|
// Returns false if the server was unable to return the information
|
|
// needed for this command.
|
|
bool CmdList::listFunctionOrClass(DebuggerClient &client) {
|
|
assert(client.argCount() == 1);
|
|
CmdInfoPtr cmdInfo(new CmdInfo());
|
|
DebuggerCommandPtr deleter(cmdInfo);
|
|
string subsymbol;
|
|
cmdInfo->parseOneArg(client, subsymbol);
|
|
CmdInfoPtr cmd = client.xend<CmdInfo>(cmdInfo.get());
|
|
Array info = cmd->getInfo();
|
|
if (info.empty()) return false;
|
|
always_assert(info.size() == 1);
|
|
ArrayIter iter(info);
|
|
Array funcInfo = iter.second();
|
|
if (!subsymbol.empty()) {
|
|
String key = CmdInfo::FindSubSymbol(funcInfo[s_methods], subsymbol);
|
|
if (key.isNull()) return false;
|
|
funcInfo = funcInfo[s_methods][key].toArray();
|
|
}
|
|
String file = funcInfo[s_file].toString();
|
|
int line1 = funcInfo[s_line1].toInt32();
|
|
int line2 = funcInfo[s_line2].toInt32();
|
|
int line = line1 ? line1 : line2;
|
|
if (file.empty() || !line) return false;
|
|
client.setListLocation(file.data(), line - 1, false);
|
|
line = 0;
|
|
int charFocus0 = 0;
|
|
int lineFocus1 = 0;
|
|
int charFocus1 = 0;
|
|
m_file.clear();
|
|
m_line1 = m_line2 = 0;
|
|
getListLocation(client, line, charFocus0, lineFocus1, charFocus1);
|
|
if (m_file.empty()) {
|
|
listEvalCode(client);
|
|
return true;
|
|
}
|
|
return listFileRange(client, line, charFocus0, lineFocus1, charFocus1);
|
|
}
|
|
|
|
// Checks the command arguments, report errors and returning as appropriate.
|
|
// Then communicates with the server to retrieve source information. Also
|
|
// retrieves and updates location information stored in the client.
|
|
void CmdList::onClientImpl(DebuggerClient &client) {
|
|
if (DebuggerCommand::displayedHelp(client)) return;
|
|
if (client.argCount() > 1) {
|
|
help(client);
|
|
return;
|
|
}
|
|
|
|
int line = 0;
|
|
m_line1 = m_line2 = 0;
|
|
if (client.argCount() == 1) {
|
|
string arg = client.argValue(1);
|
|
if (DebuggerClient::IsValidNumber(arg)) {
|
|
line = atoi(arg.c_str());
|
|
if (line <= 0) {
|
|
client.error("A line number has to be a positive integer.");
|
|
help(client);
|
|
return;
|
|
}
|
|
m_line1 = line - DebuggerClient::CodeBlockSize/2;
|
|
m_line2 = m_line1 + DebuggerClient::CodeBlockSize;
|
|
} else if (arg.find("::") != string::npos) {
|
|
if (!listFunctionOrClass(client)) {
|
|
client.error("Unable to read specified method.");
|
|
}
|
|
return;
|
|
} else {
|
|
size_t pos = arg.find(':');
|
|
if (pos != string::npos) {
|
|
m_file = arg.substr(0, pos);
|
|
if (m_file.empty()) {
|
|
client.error("File name cannot be empty.");
|
|
help(client);
|
|
return;
|
|
}
|
|
arg = arg.substr(pos + 1);
|
|
}
|
|
pos = arg.find('-');
|
|
if (pos != string::npos) {
|
|
string line1 = arg.substr(0, pos);
|
|
string line2 = arg.substr(pos + 1);
|
|
if (!DebuggerClient::IsValidNumber(line1) ||
|
|
!DebuggerClient::IsValidNumber(line2)) {
|
|
if (m_file.empty()) {
|
|
m_file = arg;
|
|
m_line1 = 1;
|
|
m_line2 = DebuggerClient::CodeBlockSize;
|
|
} else {
|
|
client.error("Line numbers have to be integers.");
|
|
help(client);
|
|
return;
|
|
}
|
|
} else {
|
|
m_line1 = atoi(line1.c_str());
|
|
m_line2 = atoi(line2.c_str());
|
|
if (line1.empty()) {
|
|
m_line1 = m_line2 - DebuggerClient::CodeBlockSize;
|
|
}
|
|
if (line2.empty()) {
|
|
m_line2 = m_line1 + DebuggerClient::CodeBlockSize;
|
|
}
|
|
if (m_line1 <= 0 || m_line2 <= 0) {
|
|
client.error("Line numbers have to be positive integers.");
|
|
help(client);
|
|
return;
|
|
}
|
|
}
|
|
} else {
|
|
if (!DebuggerClient::IsValidNumber(arg)) {
|
|
if (m_file.empty()) {
|
|
m_file = arg;
|
|
m_line1 = 1;
|
|
m_line2 = DebuggerClient::CodeBlockSize;
|
|
} else {
|
|
client.error("A line number has to be an integer.");
|
|
help(client);
|
|
return;
|
|
}
|
|
} else {
|
|
int line = atoi(arg.c_str());
|
|
if (line <= 0) {
|
|
client.error("A line number has to be a positive integer.");
|
|
help(client);
|
|
return;
|
|
}
|
|
m_line1 = line - DebuggerClient::CodeBlockSize/2;
|
|
m_line2 = m_line1 + DebuggerClient::CodeBlockSize;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int charFocus0 = 0;
|
|
int lineFocus1 = 0;
|
|
int charFocus1 = 0;
|
|
|
|
if (m_file.empty()) {
|
|
getListLocation(client, line, charFocus0, lineFocus1, charFocus1);
|
|
if (m_file.empty()) {
|
|
listEvalCode(client);
|
|
return;
|
|
}
|
|
} else if (m_file[0] == '/') {
|
|
struct stat sb;
|
|
stat(m_file.c_str(), &sb);
|
|
if ((sb.st_mode & S_IFMT) == S_IFDIR) {
|
|
client.setSourceRoot(m_file);
|
|
client.info("PHP source root directory is set to %s", m_file.c_str());
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (listFileRange(client, line, charFocus0, lineFocus1, charFocus1)) {
|
|
return;
|
|
} else if (client.argCount() != 1 || !listFunctionOrClass(client)) {
|
|
client.error(
|
|
"Unable to read specified function, class or source file location.");
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Tries to read the contents of the file whose path is specified in m_file.
|
|
// If the path cannot be resolved and is relative, the path of the sandbox
|
|
// is used to qualify the relative path. If the contents cannot be retrieved
|
|
// m_code will be an empty string.
|
|
// The function returns false if the reply to the client fails during the
|
|
// sending process.
|
|
bool CmdList::onServer(DebuggerProxy &proxy) {
|
|
auto savedWarningFrequency = RuntimeOption::WarningFrequency;
|
|
RuntimeOption::WarningFrequency = 0;
|
|
m_code = f_file_get_contents(m_file.c_str());
|
|
if (!proxy.isLocal() && !m_code && m_file[0] != '/') {
|
|
DSandboxInfo info = proxy.getSandbox();
|
|
if (info.m_path.empty()) {
|
|
raise_warning("path for sandbox %s is not setup, run a web request",
|
|
info.desc().c_str());
|
|
} else {
|
|
std::string full_path = info.m_path + m_file;
|
|
m_code = f_file_get_contents(full_path.c_str());
|
|
}
|
|
}
|
|
RuntimeOption::WarningFrequency = savedWarningFrequency;
|
|
return proxy.sendToClient((DebuggerCommand*)this);
|
|
}
|
|
|
|
// Sends a "list file" command to the proxy attached to the given client.
|
|
// Returns false if the file does not exist or could not be read as an
|
|
// HPHP::String instance containing the contents of the file.
|
|
Variant CmdList::GetSourceFile(DebuggerClient &client,
|
|
const std::string &file) {
|
|
CmdList cmd;
|
|
cmd.m_file = file;
|
|
CmdListPtr res = client.xend<CmdList>(&cmd);
|
|
return res->m_code;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
}}
|