Arquivos
hhvm/hphp/runtime/debugger/break_point.cpp
T
Herman Venter a8749c1618 Remove useless change to test/run
Experimental attempt to make sure test output does not contain non ASCII characters is actually completely useless because it sanitizes an integer. Remove it.
2013-07-01 13:40:56 -07:00

1009 linhas
30 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/break_point.h"
#include "hphp/runtime/debugger/debugger.h"
#include "hphp/runtime/debugger/debugger_proxy.h"
#include "hphp/runtime/debugger/debugger_thrift_buffer.h"
#include "hphp/runtime/base/preg.h"
#include "hphp/runtime/base/execution_context.h"
#include "hphp/runtime/base/class_info.h"
#include "hphp/runtime/base/stat_cache.h"
#include "hphp/runtime/vm/jit/translator-inline.h"
#include "hphp/runtime/base/comparisons.h"
namespace HPHP { namespace Eval {
///////////////////////////////////////////////////////////////////////////////
TRACE_SET_MOD(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_error.isNull()) {
ret = "Break";
} else if (m_error.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;
}
InterruptSite::InterruptSite(bool hardBreakPoint, CVarRef error)
: m_error(error), m_class(nullptr), m_function(nullptr),
m_file((StringData*)nullptr),
m_line0(0), m_char0(0), m_line1(0), m_char1(0),
m_offset(InvalidAbsoluteOffset), m_unit(nullptr), m_valid(false),
m_funcEntry(false) {
TRACE(2, "InterruptSite::InterruptSite\n");
Transl::VMRegAnchor _;
#define bail_on(c) if (c) { return; }
VMExecutionContext* context = g_vmContext;
ActRec *fp = context->getFP();
bail_on(!fp);
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, &m_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);
m_offset = m_unit->offsetOf(pc);
if (m_offset == fp->m_func->base()) {
m_funcEntry = true;
}
}
m_file = m_unit->filepath()->data();
if (m_unit->getSourceLoc(m_offset, m_sourceLoc)) {
m_line0 = m_sourceLoc.line0;
m_char0 = m_sourceLoc.char0;
m_line1 = m_sourceLoc.line1;
m_char1 = m_sourceLoc.char1;
}
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 *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_interruptType(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_interruptType(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_interruptType(interrupt),
m_line1(0), m_line2(0),
m_regex(regex), m_check(false) {
TRACE(2, "BreakPointInfo::BreakPointInfo..const std::string &file)\n");
assert(m_interruptType != ExceptionHandler); // Server-side only.
if (m_interruptType == ExceptionThrown) {
parseExceptionThrown(exp);
} 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(int version, DebuggerThriftBuffer &thrift) {
TRACE(2, "BreakPointInfo::sendImpl\n");
thrift.write((int8_t)m_state);
if (version >= 1) thrift.write((int8_t)m_bindState);
thrift.write((int8_t)m_interruptType);
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(int version, DebuggerThriftBuffer &thrift) {
TRACE(2, "BreakPointInfo::recvImpl\n");
int8_t tmp;
thrift.read(tmp); m_state = (State)tmp;
if (version >= 1) {
thrift.read(tmp); m_bindState = (BindState)tmp;
}
thrift.read(tmp); m_interruptType = (InterruptType)tmp;
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_interruptType) {
case BreakPointReached:
if (!getFuncName().empty()) {
if (!m_file.empty() || m_line1 != 0) {
return false;
}
} else {
if (m_file.empty() || m_line1 == 0 || m_line2 != m_line1) {
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;
default:
break;
}
}
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_interruptType == interrupt) {
switch (interrupt) {
case RequestStarted:
case RequestEnded:
case PSPEnded:
return
checkUrl(site.url());
case ExceptionThrown:
return
checkExceptionOrError(site.getError()) &&
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();
if (!getFuncName().empty()) {
// function entry breakpoint
match = match && site.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_interruptType) {
case BreakPointReached:
ret = descBreakPointReached();
break;
case ExceptionThrown:
ret = descExceptionThrown();
break;
default:
ret = GetInterruptName((InterruptType)m_interruptType);
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;
}
void mangleXhpName(const std::string &source, std::string &target) {
auto oldLen = source.length();
size_t newLen = 0;
size_t index = 0;
for (; index < oldLen; index++) {
auto ch = source[index];
if (ch != ':' && ch != '-') continue;
newLen = 4+index;
break;
}
if (newLen == 0) {
target = source;
return;
}
for (; index < oldLen; index++) {
if (source[index] == ':') newLen += 2; else newLen +=1;
}
target.clear();
target.reserve(newLen);
target.append("xhp_");
for (index = 0; index < oldLen; index++) {
auto ch = source[index];
if (ch == '-') {
target.push_back('_');
} else if (ch == ':') {
if (index > 0) {
target.push_back('_');
target.push_back('_');
}
} else {
target.push_back(ch);
}
}
}
int32_t scanName(const std::string &str, int32_t offset) {
auto len = str.length();
assert(0 <= offset && offset < len);
while (offset < len) {
char ch = str[offset];
if (ch == ':' || ch == '\\' || ch == ',' || ch == '(' || ch == '=' ||
ch == '@') {
if (offset+1 >= len) return offset;
char ch1 = str[offset+1];
if (ch == ':') {
if (ch1 == ':' || ('0' <= ch1 && ch1 <= '9')) return offset;
} else if (ch == '(') {
if (ch1 == ')') return offset;
} else if (ch == '=') {
if (ch1 == '>') return offset;
} else {
assert (ch == '\\' || ch == ',' || ch == '@');
return offset;
}
}
offset++;
}
return offset;
}
int32_t scanNumber(const std::string &str, int32_t offset, int32_t& value) {
value = 0;
auto len = str.length();
assert(0 <= offset && offset < len);
while (offset < len) {
char ch = str[offset];
if (ch < '0' || ch > '9') return offset;
value = value*10 + (ch - '0');
offset++;
}
return offset;
}
int32_t BreakPointInfo::parseFileLocation(const std::string &str,
int32_t offset) {
auto len = str.length();
assert(0 <= offset && offset < len);
auto offset1 = scanNumber(str, offset, m_line1);
if (offset1 == offset) return offset; //Did not find a number
m_line2 = m_line1; //so that we always have a range
if (offset1 >= len) return len; //Nothing follows the number
auto ch = str[offset1];
if (ch == '-') {
if (offset1+1 >= len) return offset; //Invalid file location
auto offset2 = scanNumber(str, offset1+1, m_line2);
if (offset1+1 == offset2) return offset; //Invalid file location
return offset2;
}
return offset1;
}
/* The parser accepts the following syntax, which harks back to pre VM days
(all components are optional, as long as there is at least one component):
{file location},{call}=>{call}()@{url}
{call}=>{call}(),{file location}@{url}
file location: {file}:{line1}-{line2}
call: \{namespace}\{cls}::{func}
Currently semantic checks will disallow expressions that specify
both file locations and calls.
*/
void BreakPointInfo::parseBreakPointReached(const std::string &exp,
const std::string &file) {
TRACE(2, "BreakPointInfo::parseBreakPointReached\n");
string name;
auto len = exp.length();
auto offset0 = 0;
//Look for leading number by itself
auto offset1 = scanNumber(exp, offset0, m_line1);
if (offset1 == len) {
m_line2 = m_line1;
m_file = file;
return;
}
// Skip over a leading backslash
if (len > 0 && exp[0] == '\\') offset0++;
offset1 = scanName(exp, offset0);
// check that exp starts with a file or method name
if (offset1 == offset0) goto returnInvalid;
name = exp.substr(offset0, offset1-offset0);
if (offset0 == 0) {
// parse {file location} if appropriate
if (offset1 < len && exp[offset1] == ',') {
m_file = name;
name.clear();
offset1 += 1;
} else if (offset1 < len-1 && exp[offset1] == ':' &&
exp[offset1+1] != ':') {
m_file = name;
name.clear();
offset1 += 1;
auto offset2 = parseFileLocation(exp, offset1);
// check for {file}:{something that is not a number}
if (offset2 == offset1) goto returnInvalid;
offset1 = offset2;
if (offset1 >= len) return; // file location without anything else
if (exp[offset1] == '@') goto parseUrl; // file location followed by url
// check for {file}{ something other than @ or :}
if (exp[offset1] != ',') goto returnInvalid;
offset1 += 1;
}
}
// parse {func}() or {func}=>{func}() or {func}=>{func}=>{func}() and so on
while (true) {
if (name.empty()) {
if (len > offset1 && exp[offset1] == '\\') offset1++;
auto offset2 = scanName(exp, offset1);
// check for {something other than a name}
if (offset2 == offset1) goto returnInvalid;
name = exp.substr(offset1, offset2-offset1);
}
while (offset1 < len && exp[offset1] == '\\') {
if (!m_namespace.empty()) m_namespace += "\\";
m_namespace += name;
offset1 += 1;
auto offset2 = scanName(exp, offset1);
// check for {namespace}\{something that is not a name}
if (offset2 == offset1) goto returnInvalid;
name = exp.substr(offset1, offset2-offset1);
offset1 = offset2;
}
if (offset1 < len-1 && exp[offset1] == ':' && exp[offset1+1] == ':') {
m_class = name;
offset1 += 2;
auto offset2 = scanName(exp, offset1);
// check for {namespace}\{class}::{something that is not a name}
if (offset2 == offset1) goto returnInvalid;
name = exp.substr(offset1, offset2-offset1);
offset1 = offset2;
}
// Now we have a namespace, class and func name.
// The namespace only or the namespace and class might be empty.
DFunctionInfoPtr pfunc(new DFunctionInfo());
if (m_class.empty()) {
if (m_namespace.empty())
pfunc->m_function = name;
else {
// Yes this does seem beyond strange, but that is what the PHP parser
// does when it sees a function declared inside a namespace, so we
// too have to pretend there is no namespace here. At some point
// the parser hack may have to go away. At that stage, this code
// will have to change, as well as other parts of the debugger.
pfunc->m_function = m_namespace + "\\" + name;
}
} else {
mangleXhpName(m_class, pfunc->m_class);
if (!m_namespace.empty()) {
// Emulate parser hack. See longer comment above.
pfunc->m_class = m_namespace + "\\" + pfunc->m_class;
}
pfunc->m_function = name;
}
m_funcs.insert(m_funcs.begin(), pfunc);
m_namespace.clear();
m_class.clear();
name.clear();
// If we are now at () we skip over it and terminate the loop
if (offset1 < len && exp[offset1] == '(') {
// check for {func}{(}{not )}
if (offset1+1 >= len || exp[offset1+1] != ')') goto returnInvalid;
offset1 += 2;
break; // parsed the last (perhaps only) call in a function call chain
}
// If we are now at => we need to carry on with the loop
if (offset1 < len-1 && exp[offset1] == '=' && exp[offset1+1] == '>') {
offset1 += 2;
continue;
}
goto returnInvalid; // {func calls}{not () or =>}
}
if (m_file.empty()) {
if (offset1 < len && exp[offset1] == ',') {
auto offset2 = scanName(exp, ++offset1);
// check for {func calls}:{not a filename}
if (offset2 == offset1) goto returnInvalid;
m_file = exp.substr(offset1, offset2-offset1);
offset1 = offset2;
if (offset1 < len && exp[offset1] == ':') {
offset2 = parseFileLocation(exp, offset1+1);
// check for {file}:{something that is not a number}
if (offset2 == offset2+1) goto returnInvalid;
offset1 = offset2;
}
}
}
parseUrl:
if (offset1 < len-2 && exp[offset1] == '@') {
offset1++;
m_url = exp.substr(offset1, len-offset1);
} else {
// check for unparsed characters at end of exp
if (offset1 != len) goto returnInvalid;
}
return;
returnInvalid:
m_valid = false;
}
void BreakPointInfo::parseExceptionThrown(const std::string &exp) {
TRACE(2, "BreakPointInfo::parseExceptionThrown\n");
string name;
auto len = exp.length();
auto offset0 = 0;
// Skip over a leading backslash
if (len > 0 && exp[0] == '\\') offset0++;
auto offset1 = scanName(exp, offset0);
// check that exp starts with a name
if (offset1 == offset0) goto returnInvalid;
name = exp.substr(offset0, offset1-offset0);
if (name.empty()) {
if (len > offset1 && exp[offset1] == '\\') offset1++;
auto offset2 = scanName(exp, offset1);
// check for {something other than a name}
if (offset2 == offset1) goto returnInvalid;
name = exp.substr(offset1, offset2-offset1);
}
while (offset1 < len && exp[offset1] == '\\') {
if (!m_namespace.empty()) m_namespace += "\\";
m_namespace += name;
offset1 += 1;
auto offset2 = scanName(exp, offset1);
// check for {namespace}\{something that is not a name}
if (offset2 == offset1) goto returnInvalid;
name = exp.substr(offset1, offset2-offset1);
offset1 = offset2;
}
m_class = name;
// Now we have a namespace and class name.
// The namespace might be empty.
mangleXhpName(m_class, m_class);
if (m_class == "error") m_class = ErrorClassName;
if (!m_namespace.empty()) {
m_class = m_namespace + "\\" + m_class;
m_namespace.clear();
}
if (offset1 < len-2 && exp[offset1] == '@') {
offset1++;
m_url = exp.substr(offset1, len-offset1);
} else {
// check for unparsed characters at end of exp
if (offset1 != len) goto returnInvalid;
}
return;
returnInvalid:
m_valid = false;
}
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;
}
// Returns true if file is a suffix path of fullPath
bool BreakPointInfo::MatchFile(const std::string& file,
const std::string& fullPath) {
TRACE(2, "BreakPointInfo::MatchFile(const std::string&\n");
if (file == fullPath) {
return true;
}
if (file.size() > 0 && file[0] != '/') {
auto pos = fullPath.rfind(file);
// check for match
if (pos == std::string::npos) return false;
// check if match is a suffix
if (pos + file.size() > fullPath.size()) return false;
// check if suffix is a sub path
if (pos == 0 || fullPath[pos-1] != '/') return false;
return true;
}
// Perhaps file or fullPath is a symlink.
auto realFile = StatCache::realpath(file.c_str());
auto realFullPath = StatCache::realpath(fullPath.c_str());
if (realFile != file || realFullPath != fullPath) {
return MatchFile(realFile, realFullPath);
}
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());
Class* clsB = Unit::lookupClass(&sdBClsName);
StackStringData sdFClsName(fcls);
Class* clsF = Unit::lookupClass(&sdFClsName);
if (!clsB) return false;
if (clsB == clsF) return true;
StackStringData sdFuncName(func);
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 HPHP::same(r, 1);
}
bool BreakPointInfo::checkExceptionOrError(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;
{
// Don't hit more breakpoints while attempting to decide if we should stop
// at this breakpoint.
EvalBreakControl eval(true);
Variant ret = DebuggerProxy::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(int version, 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(version, thrift);
}
}
void BreakPointInfo::RecvImpl(int version, 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(version, thrift);
bps[i] = bpi;
}
}
///////////////////////////////////////////////////////////////////////////////
}}