98483c74d6
This is a partial step towards merging the HPHP::VM namespace up into its parent. To keep it reviewable/mergeable I'm not doing everything at once here, but most of the code I've touched seems improved. I've drawn an invisible line around the jit, Unit and its cohort (Class, Func, PreClass, etc.); we'll get back to them soon.
907 linhas
24 KiB
C++
907 linhas
24 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/break_point.h>
|
|
#include <runtime/eval/debugger/debugger.h>
|
|
#include <runtime/eval/debugger/debugger_proxy.h>
|
|
#include <runtime/eval/debugger/debugger_thrift_buffer.h>
|
|
#include <runtime/base/preg.h>
|
|
#include <runtime/base/execution_context.h>
|
|
#include <runtime/base/class_info.h>
|
|
#include <runtime/base/stat_cache.h>
|
|
#include <runtime/vm/translator/translator-inline.h>
|
|
|
|
namespace HPHP { namespace Eval {
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
static const Trace::Module TRACEMOD = Trace::debugger;
|
|
|
|
int InterruptSite::getFileLen() const {
|
|
TRACE(2, "InterruptSite::getFileLen\n");
|
|
if (m_file.empty()) {
|
|
getFile();
|
|
}
|
|
return m_file.size();
|
|
}
|
|
|
|
std::string InterruptSite::desc() const {
|
|
TRACE(2, "InterruptSite::desc\n");
|
|
string ret;
|
|
if (m_exception.isNull()) {
|
|
ret = "Break";
|
|
} else if (m_exception.isObject()) {
|
|
ret = "Exception thrown";
|
|
} else {
|
|
ret = "Error occurred";
|
|
}
|
|
|
|
const char *cls = getClass();
|
|
const char *func = getFunction();
|
|
if (func && *func) {
|
|
ret += " at ";
|
|
if (cls && *cls) {
|
|
ret += cls;
|
|
ret += "::";
|
|
}
|
|
ret += func;
|
|
ret += "()";
|
|
}
|
|
|
|
string file = getFile();
|
|
int line0 = getLine0();
|
|
if (line0) {
|
|
ret += " on line " + lexical_cast<string>(line0);
|
|
if (!file.empty()) {
|
|
ret += " of " + file;
|
|
}
|
|
} else if (!file.empty()) {
|
|
ret += " in " + file;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
InterruptSiteVM::InterruptSiteVM(bool hardBreakPoint /* = false */,
|
|
CVarRef e /* = null_variant */)
|
|
: InterruptSite(e), m_unit(nullptr), m_valid(false), m_funcEntry(false) {
|
|
TRACE(2, "InterruptSite::InterruptSiteVM\n");
|
|
VM::Transl::VMRegAnchor _;
|
|
#define bail_on(c) if (c) { return; }
|
|
VMExecutionContext* context = g_vmContext;
|
|
ActRec *fp = context->getFP();
|
|
bail_on(!fp);
|
|
Offset offset;
|
|
if (hardBreakPoint && fp->skipFrame()) {
|
|
// for hard breakpoint, the fp is for an extension function,
|
|
// so we need to construct the site on the caller
|
|
fp = context->getPrevVMState(fp, &offset);
|
|
assert(fp);
|
|
bail_on(!fp->m_func);
|
|
m_unit = fp->m_func->unit();
|
|
bail_on(!m_unit);
|
|
} else {
|
|
const uchar *pc = context->getPC();
|
|
bail_on(!fp->m_func);
|
|
m_unit = fp->m_func->unit();
|
|
bail_on(!m_unit);
|
|
offset = m_unit->offsetOf(pc);
|
|
if (offset == fp->m_func->base()) {
|
|
m_funcEntry = true;
|
|
}
|
|
}
|
|
m_file = m_unit->filepath()->data();
|
|
if (m_unit->getSourceLoc(offset, m_sourceLoc)) {
|
|
m_line0 = m_sourceLoc.line0;
|
|
m_char0 = m_sourceLoc.char0;
|
|
m_line1 = m_sourceLoc.line1;
|
|
m_char1 = m_sourceLoc.char1;
|
|
|
|
if (g_context->getDebuggerSmallStep()) {
|
|
// get offset range for the pc only
|
|
VM::OffsetRange range;
|
|
if (m_unit->getOffsetRange(offset, range)) {
|
|
m_offsetRangeVec.push_back(range);
|
|
}
|
|
} else {
|
|
// get offset ranges for the whole line
|
|
// we use m_line1 here because it seems working better than m_line0
|
|
// in a handful of cases for our bytecode-source mapping. we probably
|
|
// should consider modify the mapping to make stepping easier.
|
|
if (!m_unit->getOffsetRanges(m_line1, m_offsetRangeVec)) {
|
|
m_offsetRangeVec.clear();
|
|
}
|
|
}
|
|
}
|
|
m_function = fp->m_func->name()->data();
|
|
if (fp->m_func->preClass()) {
|
|
m_class = fp->m_func->preClass()->name()->data();
|
|
} else {
|
|
m_class = "";
|
|
}
|
|
#undef bail_on
|
|
m_valid = true;
|
|
}
|
|
|
|
const char *InterruptSiteVM::getFile() const {
|
|
TRACE(2, "InterruptSiteVM::getFile\n");
|
|
return m_file.data();
|
|
}
|
|
|
|
const char *InterruptSiteVM::getClass() const {
|
|
TRACE(2, "InterruptSiteVM::getClass\n");
|
|
if (m_class) {
|
|
return m_class;
|
|
}
|
|
return "";
|
|
}
|
|
|
|
const char *InterruptSiteVM::getFunction() const {
|
|
TRACE(2, "InterruptSiteVM::getFunction\n");
|
|
if (m_function) {
|
|
return m_function;
|
|
}
|
|
return "";
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
const char *BreakPointInfo::ErrorClassName = "@";
|
|
|
|
const char *BreakPointInfo::GetInterruptName(InterruptType interrupt) {
|
|
TRACE(2, "BreakPointInfo::GetInterruptName\n");
|
|
switch (interrupt) {
|
|
case RequestStarted: return "start of request";
|
|
case RequestEnded: return "end of request or start of psp";
|
|
case PSPEnded: return "end of psp";
|
|
default:
|
|
assert(false);
|
|
break;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
BreakPointInfo::BreakPointInfo(bool regex, State state,
|
|
const std::string &file, int line)
|
|
: m_index(0), m_state(state), m_valid(true),
|
|
m_interrupt(BreakPointReached),
|
|
m_file(file), m_line1(line), m_line2(line),
|
|
m_regex(regex), m_check(false) {
|
|
TRACE(2, "BreakPointInfo::BreakPointInfo..const std::string &file, int)\n");
|
|
createIndex();
|
|
}
|
|
|
|
BreakPointInfo::BreakPointInfo(bool regex, State state,
|
|
InterruptType interrupt,
|
|
const std::string &url)
|
|
: m_index(0), m_state(state), m_valid(true), m_interrupt(interrupt),
|
|
m_line1(0), m_line2(0), m_url(url),
|
|
m_regex(regex), m_check(false) {
|
|
TRACE(2, "BreakPointInfo::BreakPointInfo..const std::string &url)\n");
|
|
createIndex();
|
|
}
|
|
|
|
BreakPointInfo::BreakPointInfo(bool regex, State state,
|
|
InterruptType interrupt,
|
|
const std::string &exp,
|
|
const std::string &file)
|
|
: m_index(0), m_state(state), m_valid(true), m_interrupt(interrupt),
|
|
m_line1(0), m_line2(0),
|
|
m_regex(regex), m_check(false) {
|
|
TRACE(2, "BreakPointInfo::BreakPointInfo..const std::string &file)\n");
|
|
if (m_interrupt == ExceptionThrown) {
|
|
parseExceptionThrown(exp);
|
|
if (!m_file.empty() || m_line1 || m_line2 || !m_funcs.empty()) {
|
|
m_valid = false;
|
|
}
|
|
} else {
|
|
parseBreakPointReached(exp, file);
|
|
}
|
|
createIndex();
|
|
}
|
|
|
|
static int s_max_breakpoint_index = 0;
|
|
void BreakPointInfo::createIndex() {
|
|
TRACE(2, "BreakPointInfo::createIndex\n");
|
|
m_index = ++s_max_breakpoint_index;
|
|
}
|
|
|
|
BreakPointInfo::~BreakPointInfo() {
|
|
TRACE(2, "BreakPointInfo::~BreakPointInfo\n");
|
|
if (m_index && m_index == s_max_breakpoint_index) {
|
|
--s_max_breakpoint_index;
|
|
}
|
|
}
|
|
|
|
void BreakPointInfo::sendImpl(DebuggerThriftBuffer &thrift) {
|
|
TRACE(2, "BreakPointInfo::sendImpl\n");
|
|
thrift.write(m_state);
|
|
thrift.write(m_interrupt);
|
|
thrift.write(m_file);
|
|
thrift.write(m_line1);
|
|
thrift.write(m_line2);
|
|
thrift.write(m_namespace);
|
|
thrift.write(m_class);
|
|
thrift.write(m_funcs);
|
|
thrift.write(m_url);
|
|
thrift.write(m_regex);
|
|
thrift.write(m_check);
|
|
thrift.write(m_clause);
|
|
thrift.write(m_output);
|
|
thrift.write(m_exceptionClass);
|
|
thrift.write(m_exceptionObject);
|
|
}
|
|
|
|
void BreakPointInfo::recvImpl(DebuggerThriftBuffer &thrift) {
|
|
TRACE(2, "BreakPointInfo::recvImpl\n");
|
|
thrift.read(m_state);
|
|
thrift.read(m_interrupt);
|
|
thrift.read(m_file);
|
|
thrift.read(m_line1);
|
|
thrift.read(m_line2);
|
|
thrift.read(m_namespace);
|
|
thrift.read(m_class);
|
|
thrift.read(m_funcs);
|
|
thrift.read(m_url);
|
|
thrift.read(m_regex);
|
|
thrift.read(m_check);
|
|
thrift.read(m_clause);
|
|
thrift.read(m_output);
|
|
thrift.read(m_exceptionClass);
|
|
thrift.read(m_exceptionObject);
|
|
}
|
|
|
|
void BreakPointInfo::setClause(const std::string &clause, bool check) {
|
|
TRACE(2, "BreakPointInfo::setClause\n");
|
|
m_clause = clause;
|
|
m_check = check;
|
|
}
|
|
|
|
void BreakPointInfo::changeBreakPointDepth(int stackDepth) {
|
|
TRACE(2, "BreakPointInfo::changeBreakPointDepth\n");
|
|
// if the breakpoint is equal or lower than the stack depth
|
|
// delete it
|
|
breakDepthStack.remove_if(
|
|
std::bind2nd(std::greater_equal<int>(), stackDepth)
|
|
);
|
|
}
|
|
|
|
void BreakPointInfo::unsetBreakable(int stackDepth) {
|
|
TRACE(2, "BreakPointInfo::unsetBreakable\n");
|
|
breakDepthStack.push_back(stackDepth);
|
|
}
|
|
|
|
void BreakPointInfo::setBreakable(int stackDepth) {
|
|
TRACE(2, "BreakPointInfo::setBreakable\n");
|
|
if (!breakDepthStack.empty() && breakDepthStack.back() == stackDepth) {
|
|
breakDepthStack.pop_back();
|
|
}
|
|
}
|
|
|
|
bool BreakPointInfo::breakable(int stackDepth) const {
|
|
TRACE(2, "BreakPointInfo::breakable\n");
|
|
if (!breakDepthStack.empty() && breakDepthStack.back() == stackDepth) {
|
|
return false;
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
void BreakPointInfo::toggle() {
|
|
TRACE(2, "BreakPointInfo::toggle\n");
|
|
switch (m_state) {
|
|
case Always: setState(Once); break;
|
|
case Once: setState(Disabled); break;
|
|
case Disabled: setState(Always); break;
|
|
default:
|
|
assert(false);
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool BreakPointInfo::valid() {
|
|
TRACE(2, "BreakPointInfo::valid\n");
|
|
if (m_valid) {
|
|
switch (m_interrupt) {
|
|
case BreakPointReached:
|
|
if (!getFuncName().empty()) {
|
|
if (!m_file.empty() || m_line1 != 0) {
|
|
return false;
|
|
}
|
|
} else {
|
|
if (m_file.empty() || m_line1 == 0) {
|
|
return false;
|
|
}
|
|
}
|
|
if (m_regex || m_funcs.size() > 1) {
|
|
return false;
|
|
}
|
|
return (m_line1 && m_line2) || !m_file.empty() || !m_funcs.empty();
|
|
case ExceptionThrown:
|
|
return !m_class.empty();
|
|
case RequestStarted:
|
|
case RequestEnded:
|
|
case PSPEnded:
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool BreakPointInfo::same(BreakPointInfoPtr bpi) {
|
|
TRACE(2, "BreakPointInfo::same\n");
|
|
return desc() == bpi->desc();
|
|
}
|
|
|
|
bool BreakPointInfo::match(InterruptType interrupt, InterruptSite &site) {
|
|
TRACE(2, "BreakPointInfo::match\n");
|
|
if (m_interrupt == interrupt) {
|
|
switch (interrupt) {
|
|
case RequestStarted:
|
|
case RequestEnded:
|
|
case PSPEnded:
|
|
return
|
|
checkUrl(site.url());
|
|
case ExceptionThrown:
|
|
return
|
|
checkException(site.getException()) &&
|
|
checkUrl(site.url()) && checkClause();
|
|
case BreakPointReached:
|
|
{
|
|
bool match =
|
|
Match(site.getFile(), site.getFileLen(), m_file, m_regex, false) &&
|
|
checkLines(site.getLine0()) && checkStack(site) &&
|
|
checkUrl(site.url()) && checkClause();
|
|
|
|
InterruptSiteVM &siteVM = dynamic_cast<InterruptSiteVM&>(site);
|
|
if (!getFuncName().empty()) {
|
|
// function entry breakpoint
|
|
match = match && siteVM.funcEntry();
|
|
}
|
|
return match;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
std::string BreakPointInfo::state(bool padding) const {
|
|
TRACE(2, "BreakPointInfo::state\n");
|
|
switch (m_state) {
|
|
case Always: return padding ? "ALWAYS " : "ALWAYS" ;
|
|
case Once: return padding ? "ONCE " : "ONCE" ;
|
|
case Disabled: return padding ? "DISABLED" : "DISABLED";
|
|
default:
|
|
assert(false);
|
|
break;
|
|
}
|
|
return "";
|
|
}
|
|
|
|
std::string BreakPointInfo::regex(const std::string &name) const {
|
|
TRACE(2, "BreakPointInfo::regex\n");
|
|
if (m_regex) {
|
|
return "regex{" + name + "}";
|
|
}
|
|
return name;
|
|
}
|
|
|
|
std::string BreakPointInfo::getNamespace() const {
|
|
TRACE(2, "BreakPointInfo::getNamespace\n");
|
|
if (!m_funcs.empty()) {
|
|
return m_funcs[0]->m_namespace;
|
|
}
|
|
return "";
|
|
}
|
|
|
|
std::string BreakPointInfo::getClass() const {
|
|
TRACE(2, "BreakPointInfo::getClass\n");
|
|
if (!m_funcs.empty()) {
|
|
return m_funcs[0]->m_class;
|
|
}
|
|
return "";
|
|
}
|
|
|
|
std::string BreakPointInfo::getFunction() const {
|
|
TRACE(2, "BreakPointInfo::getFunction\n");
|
|
if (!m_funcs.empty()) {
|
|
return m_funcs[0]->m_function;
|
|
}
|
|
return "";
|
|
}
|
|
|
|
std::string BreakPointInfo::getFuncName() const {
|
|
TRACE(2, "BreakPointInfo::getFuncName\n");
|
|
if (!m_funcs.empty()) {
|
|
return m_funcs[0]->getName();
|
|
}
|
|
return "";
|
|
}
|
|
|
|
std::string BreakPointInfo::site() const {
|
|
TRACE(2, "BreakPointInfo::site\n");
|
|
string ret;
|
|
|
|
string preposition = "at ";
|
|
if (!m_funcs.empty()) {
|
|
ret = m_funcs[0]->site(preposition);
|
|
for (unsigned int i = 1; i < m_funcs.size(); i++) {
|
|
ret += " called by ";
|
|
string tmp;
|
|
ret += m_funcs[i]->site(tmp);
|
|
}
|
|
}
|
|
|
|
if (!m_file.empty() || m_line1) {
|
|
if (!ret.empty()) {
|
|
ret += " ";
|
|
} else {
|
|
preposition = "";
|
|
}
|
|
if (m_line1) {
|
|
ret += "on line " + lexical_cast<string>(m_line1);
|
|
if (!m_file.empty()) {
|
|
ret += " of " + m_file;
|
|
}
|
|
} else {
|
|
ret += "in " + m_file;
|
|
}
|
|
}
|
|
|
|
return preposition + ret;
|
|
}
|
|
|
|
std::string BreakPointInfo::descBreakPointReached() const {
|
|
TRACE(2, "BreakPointInfo::descBreakPointReached\n");
|
|
string ret;
|
|
for (unsigned int i = 0; i < m_funcs.size(); i++) {
|
|
ret += (i == 0 ? "upon entering " : " called by ");
|
|
ret += m_funcs[i]->desc(this);
|
|
}
|
|
|
|
if (!m_file.empty() || m_line1 || m_line2) {
|
|
if (!ret.empty()) {
|
|
ret += " ";
|
|
}
|
|
if (m_line1 || m_line2) {
|
|
if (m_line1 == m_line2) {
|
|
ret += "on line " + lexical_cast<string>(m_line1);
|
|
} else if (m_line2 == -1) {
|
|
ret += "between line " + lexical_cast<string>(m_line1) + " and end";
|
|
} else {
|
|
ret += "between line " + lexical_cast<string>(m_line1) +
|
|
" and line " + lexical_cast<string>(m_line2);
|
|
}
|
|
if (!m_file.empty()) {
|
|
ret += " of " + regex(m_file);
|
|
} else {
|
|
ret += " of any file";
|
|
}
|
|
} else {
|
|
ret += "on any lines in " + regex(m_file);
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
std::string BreakPointInfo::descExceptionThrown() const {
|
|
TRACE(2, "BreakPointInfo::descExceptionThrown\n");
|
|
string ret;
|
|
if (!m_namespace.empty() || !m_class.empty()) {
|
|
if (m_class == ErrorClassName) {
|
|
ret = "right after an error";
|
|
} else {
|
|
ret = "right before throwing ";
|
|
if (!m_class.empty()) {
|
|
if (!m_namespace.empty()) {
|
|
ret += regex(m_namespace) + "::";
|
|
}
|
|
ret += regex(m_class);
|
|
} else {
|
|
ret += "any exceptions in namespace " + regex(m_namespace);
|
|
}
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
std::string BreakPointInfo::desc() const {
|
|
TRACE(2, "BreakPointInfo::desc\n");
|
|
string ret;
|
|
switch (m_interrupt) {
|
|
case BreakPointReached:
|
|
ret = descBreakPointReached();
|
|
break;
|
|
case ExceptionThrown:
|
|
ret = descExceptionThrown();
|
|
break;
|
|
default:
|
|
ret = GetInterruptName((InterruptType)m_interrupt);
|
|
break;
|
|
}
|
|
|
|
if (!m_url.empty()) {
|
|
ret += " when request is " + regex(m_url);
|
|
}
|
|
|
|
if (!m_clause.empty()) {
|
|
if (m_check) {
|
|
ret += " if " + m_clause;
|
|
} else {
|
|
ret += " && " + m_clause;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool BreakPointInfo::parseLines(const std::string &token) {
|
|
TRACE(2, "BreakPointInfo::parseLines\n");
|
|
if (token.empty()) return false;
|
|
|
|
for (unsigned int i = 0; i < token.size(); i++) {
|
|
char ch = token[i];
|
|
if ((ch < '0' || ch > '9') && ch != '-') {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (m_line1 || m_line2) {
|
|
m_valid = false; // lines are specified twice
|
|
return true;
|
|
}
|
|
|
|
vector<string> lines;
|
|
Util::split('-', token.c_str(), lines);
|
|
if (lines.empty() || lines.size() > 2 ||
|
|
(lines.size() == 2 && lines[0].empty() && lines[1].empty())) {
|
|
m_valid = false;
|
|
return true;
|
|
}
|
|
|
|
if (lines[0].empty()) {
|
|
m_line1 = 1;
|
|
} else {
|
|
m_line1 = atoi(lines[0].c_str());
|
|
if (m_line1 <= 0) {
|
|
m_valid = false;
|
|
return true;
|
|
}
|
|
}
|
|
if (lines.size() == 1) {
|
|
m_line2 = m_line1;
|
|
return true;
|
|
}
|
|
|
|
if (lines[1].empty()) {
|
|
m_line2 = -1;
|
|
} else {
|
|
m_line2 = atoi(lines[1].c_str());
|
|
if (m_line2 <= 0) {
|
|
m_valid = false;
|
|
return true;
|
|
}
|
|
if (m_line2 < m_line1) {
|
|
int32_t tmp = m_line1;
|
|
m_line1 = m_line2;
|
|
m_line2 = tmp;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void BreakPointInfo::parseBreakPointReached(const std::string &exp,
|
|
const std::string &file) {
|
|
TRACE(2, "BreakPointInfo::parseBreakPointReached\n");
|
|
string input = exp;
|
|
|
|
// everything after @ is URL
|
|
size_t pos = input.find('@');
|
|
if (pos != string::npos) {
|
|
m_url = input.substr(pos + 1);
|
|
if (pos == 0) return;
|
|
input = input.substr(0, pos);
|
|
}
|
|
|
|
vector<string> tokens;
|
|
bool seenClass = false;
|
|
bool seenFile = false;
|
|
Util::replaceAll(input, "=>", "=>:");
|
|
Util::split(':', input.c_str(), tokens);
|
|
for (unsigned int i = 0; i < tokens.size(); i++) {
|
|
const std::string &token = tokens[i];
|
|
if (parseLines(token)) continue;
|
|
|
|
// if i'm ended with "::"
|
|
if (i+1 < tokens.size() && tokens[i+1].empty()) {
|
|
if (seenClass) {
|
|
m_valid = false;
|
|
return;
|
|
}
|
|
seenClass = true;
|
|
if (i+3 < tokens.size() && tokens[i+3].empty()) {
|
|
i += 2;
|
|
m_namespace = token;
|
|
m_class = tokens[i];
|
|
} else {
|
|
m_class = token;
|
|
}
|
|
|
|
i++;
|
|
continue;
|
|
}
|
|
|
|
string ending;
|
|
if (token.size() >= 2) {
|
|
ending = token.substr(token.size() - 2);
|
|
}
|
|
if (token.size() >= 2 && (ending == "=>" || ending == "()")) {
|
|
string func = token.substr(0, token.size() - 2);
|
|
if (ending == "=>" &&
|
|
func.size() >= 2 && func.substr(func.size() - 2) == "()") {
|
|
func = func.substr(0, func.size() - 2);
|
|
}
|
|
if (token.empty()) {
|
|
m_valid = false;
|
|
return;
|
|
}
|
|
DFunctionInfoPtr pfunc(new DFunctionInfo());
|
|
pfunc->m_namespace = m_namespace;
|
|
pfunc->m_class = m_class;
|
|
pfunc->m_function = func;
|
|
m_funcs.insert(m_funcs.begin(), pfunc);
|
|
m_namespace.clear();
|
|
m_class.clear();
|
|
seenClass = false;
|
|
} else {
|
|
if (!m_file.empty()) {
|
|
m_valid = false;
|
|
return;
|
|
}
|
|
if (seenFile) {
|
|
m_valid = false;
|
|
return;
|
|
}
|
|
seenFile = true;
|
|
m_file = token;
|
|
}
|
|
}
|
|
|
|
if (!m_namespace.empty() || !m_class.empty()) {
|
|
DFunctionInfoPtr func(new DFunctionInfo());
|
|
func->m_namespace = m_namespace;
|
|
func->m_class = m_class;
|
|
m_funcs.insert(m_funcs.begin(), func);
|
|
}
|
|
|
|
if (m_line1 && m_file.empty()) {
|
|
m_file = file; // default to current file
|
|
}
|
|
}
|
|
|
|
void BreakPointInfo::parseExceptionThrown(const std::string &exp) {
|
|
TRACE(2, "BreakPointInfo::parseExceptionThrown\n");
|
|
string input = exp;
|
|
|
|
// everything after @ is URL
|
|
size_t pos = input.find('@');
|
|
if (pos != string::npos) {
|
|
m_url = input.substr(pos + 1);
|
|
input = input.substr(0, pos);
|
|
}
|
|
|
|
pos = input.find("::");
|
|
if (pos != string::npos) {
|
|
if (pos) {
|
|
m_namespace = input.substr(0, pos);
|
|
}
|
|
m_class = input.substr(pos + 2);
|
|
} else {
|
|
m_class = input;
|
|
}
|
|
if (strncasecmp(m_class.c_str(), "error", m_class.size()) == 0) {
|
|
m_class = ErrorClassName;
|
|
}
|
|
}
|
|
|
|
bool BreakPointInfo::MatchFile(const char *haystack, int haystack_len,
|
|
const std::string &needle) {
|
|
TRACE(2, "BreakPointInfo::MatchFile(const char *haystack\n");
|
|
int pos = haystack_len - needle.size();
|
|
if ((pos == 0 || haystack[pos - 1] == '/') &&
|
|
strcasecmp(haystack + pos, needle.c_str()) == 0) {
|
|
return true;
|
|
}
|
|
if (strcasecmp(StatCache::realpath(needle.c_str()).c_str(), haystack)
|
|
== 0) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool BreakPointInfo::MatchFile(const std::string& file,
|
|
const std::string& fullPath,
|
|
const std::string& relPath) {
|
|
TRACE(2, "BreakPointInfo::MatchFile(const std::string&\n");
|
|
if (file == fullPath || file == relPath) {
|
|
return true;
|
|
}
|
|
if (file.find('/') == std::string::npos &&
|
|
file == fullPath.substr(fullPath.rfind('/') + 1)) {
|
|
return true;
|
|
}
|
|
// file is possibly setup with a symlink in the path
|
|
if (StatCache::realpath(file.c_str()) ==
|
|
StatCache::realpath(fullPath.c_str())) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool BreakPointInfo::MatchClass(const char *fcls, const std::string &bcls,
|
|
bool regex, const char *func) {
|
|
TRACE(2, "BreakPointInfo::MatchClass\n");
|
|
if (bcls.empty()) return true;
|
|
if (!fcls || !*fcls) return false;
|
|
if (regex || !func || !*func) {
|
|
return Match(fcls, 0, bcls, true, true);
|
|
}
|
|
|
|
StackStringData sdBClsName(bcls.c_str());
|
|
VM::Class* clsB = VM::Unit::lookupClass(&sdBClsName);
|
|
StackStringData sdFClsName(fcls);
|
|
VM::Class* clsF = VM::Unit::lookupClass(&sdFClsName);
|
|
if (!clsB) return false;
|
|
if (clsB == clsF) return true;
|
|
StackStringData sdFuncName(func);
|
|
VM::Func* f = clsB->lookupMethod(&sdFuncName);
|
|
if (!f) return false;
|
|
return (f->baseCls() == clsF);
|
|
}
|
|
|
|
bool BreakPointInfo::Match(const char *haystack, int haystack_len,
|
|
const std::string &needle, bool regex, bool exact) {
|
|
TRACE(2, "BreakPointInfo::Match\n");
|
|
if (needle.empty()) {
|
|
return true;
|
|
}
|
|
if (!haystack || !*haystack) {
|
|
return false;
|
|
}
|
|
|
|
if (!regex) {
|
|
if (exact) {
|
|
return strcasecmp(haystack, needle.c_str()) == 0;
|
|
}
|
|
return MatchFile(haystack, haystack_len, needle);
|
|
}
|
|
|
|
Variant matches;
|
|
Variant r = preg_match(String(needle.c_str(), needle.size(),
|
|
AttachLiteral),
|
|
String(haystack, haystack_len, AttachLiteral),
|
|
matches);
|
|
return r.same(1);
|
|
}
|
|
|
|
bool BreakPointInfo::checkException(CVarRef e) {
|
|
TRACE(2, "BreakPointInfo::checkException\n");
|
|
assert(!e.isNull());
|
|
if (e.isObject()) {
|
|
if (m_regex) {
|
|
return Match(m_class.c_str(), m_class.size(),
|
|
e.toObject()->o_getClassName().data(), true, false);
|
|
}
|
|
return e.instanceof(m_class.c_str());
|
|
}
|
|
return Match(m_class.c_str(), m_class.size(), ErrorClassName, m_regex,
|
|
false);
|
|
}
|
|
|
|
bool BreakPointInfo::checkUrl(std::string &url) {
|
|
TRACE(2, "BreakPointInfo::checkUrl\n");
|
|
if (!m_url.empty()) {
|
|
if (url.empty()) {
|
|
url = "/";
|
|
Transport *transport = g_context->getTransport();
|
|
if (transport) {
|
|
url += transport->getCommand();
|
|
}
|
|
}
|
|
return Match(url.c_str(), url.size(), m_url, m_regex, false);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool BreakPointInfo::checkLines(int line) {
|
|
TRACE(2, "BreakPointInfo::checkLines\n");
|
|
if (m_line1) {
|
|
assert(m_line2 == -1 || m_line2 >= m_line1);
|
|
return line >= m_line1 && (m_line2 == -1 || line <= m_line2);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool BreakPointInfo::checkStack(InterruptSite &site) {
|
|
TRACE(2, "BreakPointInfo::checkStack\n");
|
|
if (m_funcs.empty()) return true;
|
|
|
|
if (!Match(site.getNamespace(), 0, m_funcs[0]->m_namespace, m_regex, true) ||
|
|
!Match(site.getFunction(), 0, m_funcs[0]->m_function, m_regex, true) ||
|
|
!MatchClass(site.getClass(), m_funcs[0]->m_class, m_regex,
|
|
site.getFunction())) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool BreakPointInfo::checkClause() {
|
|
TRACE(2, "BreakPointInfo::checkClause\n");
|
|
if (!m_clause.empty()) {
|
|
if (m_php.empty()) {
|
|
if (m_check) {
|
|
m_php = DebuggerProxy::MakePHPReturn(m_clause);
|
|
} else {
|
|
m_php = DebuggerProxy::MakePHP(m_clause);
|
|
}
|
|
}
|
|
String output;
|
|
{
|
|
EvalBreakControl eval(false);
|
|
Variant ret = DebuggerProxyVM::ExecutePHP(m_php, output, false, 0);
|
|
if (m_check) {
|
|
return ret.toBoolean();
|
|
}
|
|
}
|
|
m_output = std::string(output.data(), output.size());
|
|
return true;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
void BreakPointInfo::SendImpl(const BreakPointInfoPtrVec &bps,
|
|
DebuggerThriftBuffer &thrift) {
|
|
TRACE(2, "BreakPointInfo::SendImpl\n");
|
|
int16_t size = bps.size();
|
|
thrift.write(size);
|
|
for (int i = 0; i < size; i++) {
|
|
bps[i]->sendImpl(thrift);
|
|
}
|
|
}
|
|
|
|
void BreakPointInfo::RecvImpl(BreakPointInfoPtrVec &bps,
|
|
DebuggerThriftBuffer &thrift) {
|
|
TRACE(2, "BreakPointInfo::RecvImpl\n");
|
|
int16_t size;
|
|
thrift.read(size);
|
|
bps.resize(size);
|
|
for (int i = 0; i < size; i++) {
|
|
BreakPointInfoPtr bpi(new BreakPointInfo());
|
|
bpi->recvImpl(thrift);
|
|
bps[i] = bpi;
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
}}
|