Basic stepping in generators
Add reasonable behavior for stepping within continuations (generators). Stepping over a yield now does what one would expect. When the generator is driven from a C++ extension like ASIO, the next logical execution point is after the yield statement, and that's where we'll stop now. When driven from PHP, say in a loop calling send(), the next execution point is in fact the call site of send(), so we go there. Stepping off the end of the generator function, goes to the caller of send(), or the caller of the C++ extension. Stepping _out_ of a generator driven by a C++ extension ensures that we go to the caller and not back into the generator again. The logic for both cases is exactly the same. The difference comes from the fact that we don't actually debug C++ extensions. This also fixes a long-standing problem where breakpoints would interfere with control flow cmds on the same source line. This caused funny behavior, like taking multiple steps to get off of a breakpoint.
Esse commit está contido em:
@@ -23,8 +23,9 @@ namespace HPHP { namespace Eval {
|
||||
TRACE_SET_MOD(debuggerflow);
|
||||
|
||||
CmdFlowControl::~CmdFlowControl() {
|
||||
// Remove any location filter that may have been setup by this cmd.
|
||||
// Remove any location filter or step outs that may have been setup.
|
||||
removeLocationFilter();
|
||||
cleanupStepOuts();
|
||||
}
|
||||
|
||||
void CmdFlowControl::sendImpl(DebuggerThriftBuffer &thrift) {
|
||||
@@ -81,6 +82,9 @@ void CmdFlowControl::onSetup(DebuggerProxy &proxy, CmdInterrupt &interrupt) {
|
||||
// Setup the last location filter on the VM context for all offsets covered by
|
||||
// the current source line. This will short-circuit the work done in
|
||||
// phpDebuggerOpcodeHook() and ensure we don't interrupt on this source line.
|
||||
// We exclude continuation opcodes which transfer control out of the function,
|
||||
// which allows cmds to get a chance to alter their behavior when those opcodes
|
||||
// are encountered.
|
||||
void CmdFlowControl::installLocationFilterForLine(InterruptSite *site) {
|
||||
if (!site) return; // We may be stopped at a place with no source info.
|
||||
if (g_vmContext->m_lastLocFilter) {
|
||||
@@ -90,8 +94,12 @@ void CmdFlowControl::installLocationFilterForLine(InterruptSite *site) {
|
||||
}
|
||||
TRACE(3, "Prepare location filter for %s:%d, unit %p:\n",
|
||||
site->getFile(), site->getLine0(), site->getUnit());
|
||||
auto excludeContinuationReturns = [] (Opcode op) {
|
||||
return (op != OpContExit) && (op != OpContRetC);
|
||||
};
|
||||
g_vmContext->m_lastLocFilter->addRanges(site->getUnit(),
|
||||
site->getCurOffsetRange());
|
||||
site->getCurOffsetRange(),
|
||||
excludeContinuationReturns);
|
||||
}
|
||||
|
||||
void CmdFlowControl::removeLocationFilter() {
|
||||
@@ -101,14 +109,15 @@ void CmdFlowControl::removeLocationFilter() {
|
||||
}
|
||||
}
|
||||
|
||||
bool CmdFlowControl::atStepOutOffset(Offset o) {
|
||||
return ((o == m_stepOutOffset1) || (o == m_stepOutOffset2));
|
||||
}
|
||||
|
||||
bool CmdFlowControl::hasStepOuts() {
|
||||
return m_stepOutUnit != nullptr;
|
||||
}
|
||||
|
||||
bool CmdFlowControl::atStepOutOffset(Unit* unit, Offset o) {
|
||||
return (unit == m_stepOutUnit) &&
|
||||
((o == m_stepOutOffset1) || (o == m_stepOutOffset2));
|
||||
}
|
||||
|
||||
// Place internal breakpoints to get out of the current function. This may place
|
||||
// multiple internal breakpoints, and it may place them more than one frame up.
|
||||
// Some instructions can cause PHP to be invoked without an explicit call. A set
|
||||
|
||||
@@ -91,7 +91,7 @@ protected:
|
||||
void setupStepOuts();
|
||||
void cleanupStepOuts();
|
||||
bool hasStepOuts();
|
||||
bool atStepOutOffset(Offset o);
|
||||
bool atStepOutOffset(Unit* unit, Offset o);
|
||||
|
||||
bool m_complete;
|
||||
bool m_needsVMInterrupt;
|
||||
|
||||
@@ -279,7 +279,8 @@ bool CmdInterrupt::onServer(DebuggerProxy &proxy) {
|
||||
return proxy.sendToClient(this);
|
||||
}
|
||||
|
||||
bool CmdInterrupt::shouldBreak(const BreakPointInfoPtrVec &bps) {
|
||||
bool CmdInterrupt::shouldBreak(const BreakPointInfoPtrVec &bps,
|
||||
int stackDepth) {
|
||||
|
||||
switch (m_interrupt) {
|
||||
case SessionStarted:
|
||||
@@ -295,6 +296,7 @@ bool CmdInterrupt::shouldBreak(const BreakPointInfoPtrVec &bps) {
|
||||
if (m_site) {
|
||||
for (unsigned int i = 0; i < bps.size(); i++) {
|
||||
if (bps[i]->m_state != BreakPointInfo::Disabled &&
|
||||
bps[i]->breakable(stackDepth) &&
|
||||
bps[i]->match(getInterruptType(), *getSite())) {
|
||||
BreakPointInfoPtr bp(new BreakPointInfo());
|
||||
*bp = *bps[i]; // make a copy
|
||||
|
||||
@@ -47,7 +47,7 @@ public:
|
||||
virtual void setClientOutput(DebuggerClient &client);
|
||||
virtual bool onServer(DebuggerProxy &proxy);
|
||||
|
||||
bool shouldBreak(const BreakPointInfoPtrVec &bps);
|
||||
bool shouldBreak(const BreakPointInfoPtrVec &bps, int stackDepth);
|
||||
std::string getFileLine() const;
|
||||
|
||||
InterruptSite *getSite() { return m_site;}
|
||||
|
||||
@@ -15,13 +15,18 @@
|
||||
*/
|
||||
|
||||
#include "hphp/runtime/debugger/cmd/cmd_next.h"
|
||||
#include "hphp/runtime/vm/debugger_hook.h"
|
||||
|
||||
namespace HPHP { namespace Eval {
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
TRACE_SET_MOD(debuggerflow);
|
||||
|
||||
void CmdNext::help(DebuggerClient &client) {
|
||||
CmdNext::~CmdNext() {
|
||||
cleanupStepCont();
|
||||
}
|
||||
|
||||
void CmdNext::help(DebuggerClient& client) {
|
||||
client.helpTitle("Next Command");
|
||||
client.helpCmds(
|
||||
"[n]ext {count=1}", "steps over lines of code",
|
||||
@@ -33,20 +38,20 @@ void CmdNext::help(DebuggerClient &client) {
|
||||
);
|
||||
}
|
||||
|
||||
void CmdNext::onSetup(DebuggerProxy &proxy, CmdInterrupt &interrupt) {
|
||||
void CmdNext::onSetup(DebuggerProxy& proxy, CmdInterrupt& interrupt) {
|
||||
TRACE(2, "CmdNext::onSetup\n");
|
||||
assert(!m_complete); // Complete cmds should not be asked to do work.
|
||||
CmdFlowControl::onSetup(proxy, interrupt);
|
||||
m_stackDepth = proxy.getStackDepth();
|
||||
m_vmDepth = g_vmContext->m_nesting;
|
||||
m_loc = interrupt.getFileLine();
|
||||
|
||||
// Start by single-stepping the current line.
|
||||
installLocationFilterForLine(interrupt.getSite());
|
||||
m_needsVMInterrupt = true;
|
||||
ActRec *fp = g_vmContext->getFP();
|
||||
assert(fp); // All interrupts which reach a flow cmd have an AR.
|
||||
PC pc = g_vmContext->getPC();
|
||||
stepCurrentLine(interrupt, fp, pc);
|
||||
}
|
||||
|
||||
void CmdNext::onBeginInterrupt(DebuggerProxy &proxy, CmdInterrupt &interrupt) {
|
||||
void CmdNext::onBeginInterrupt(DebuggerProxy& proxy, CmdInterrupt& interrupt) {
|
||||
TRACE(2, "CmdNext::onBeginInterrupt\n");
|
||||
assert(!m_complete); // Complete cmds should not be asked to do work.
|
||||
if (interrupt.getInterruptType() == ExceptionThrown) {
|
||||
@@ -58,55 +63,147 @@ void CmdNext::onBeginInterrupt(DebuggerProxy &proxy, CmdInterrupt &interrupt) {
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef HPHP_TRACE
|
||||
ActRec *fp = g_vmContext->getFP();
|
||||
assert(fp);
|
||||
assert(fp); // All interrupts which reach a flow cmd have an AR.
|
||||
PC pc = g_vmContext->getPC();
|
||||
Opcode op = *((Opcode*)pc);
|
||||
Offset offset = fp->m_func->unit()->offsetOf(pc);
|
||||
Unit* unit = fp->m_func->unit();
|
||||
Offset offset = unit->offsetOf(pc);
|
||||
TRACE(2, "CmdNext: pc %p, opcode %s at '%s' offset %d\n",
|
||||
pc, opcodeToName(op), fp->m_func->fullName()->data(), offset);
|
||||
#endif
|
||||
pc, opcodeToName(*pc), fp->m_func->fullName()->data(), offset);
|
||||
|
||||
int currentVMDepth = g_vmContext->m_nesting;
|
||||
int currentStackDepth = proxy.getStackDepth();
|
||||
|
||||
TRACE(2, "Original depth %d:%d, current depth %d:%d\n",
|
||||
TRACE(2, "CmdNext: original depth %d:%d, current depth %d:%d\n",
|
||||
m_vmDepth, m_stackDepth, currentVMDepth, currentStackDepth);
|
||||
|
||||
if ((currentVMDepth < m_vmDepth) ||
|
||||
((currentVMDepth == m_vmDepth) && (currentStackDepth <= m_stackDepth))) {
|
||||
// We're at the same depth as when we started, or perhaps even shallower, so
|
||||
// there's no need for any step out breakpoint anymore.
|
||||
cleanupStepOuts();
|
||||
// Where are we on the stack now vs. when we started? Breaking the answer down
|
||||
// into distinct variables helps the clarity of the algorithm below.
|
||||
bool deeper = false;
|
||||
bool originalDepth = false;
|
||||
if ((currentVMDepth == m_vmDepth) && (currentStackDepth == m_stackDepth)) {
|
||||
originalDepth = true;
|
||||
} else if ((currentVMDepth > m_vmDepth) ||
|
||||
((currentVMDepth == m_vmDepth) &&
|
||||
(currentStackDepth > m_stackDepth))) {
|
||||
deeper = true;
|
||||
}
|
||||
|
||||
if (m_loc != interrupt.getFileLine()) {
|
||||
TRACE(2, "CmdNext: same depth or shallower, off original line.\n");
|
||||
m_complete = (decCount() == 0);
|
||||
if (!m_complete) {
|
||||
TRACE(2, "CmdNext: not complete, filter new line and keep stepping.\n");
|
||||
m_loc = interrupt.getFileLine();
|
||||
installLocationFilterForLine(interrupt.getSite());
|
||||
m_needsVMInterrupt = true;
|
||||
}
|
||||
// First consider if we've got internal breakpoints setup. These are used when
|
||||
// we can make an accurate prediction of where execution should flow,
|
||||
// eventually, and when we want to let the program run normally until we get
|
||||
// there.
|
||||
if (hasStepOuts() || hasStepCont()) {
|
||||
TRACE(2, "CmdNext: checking internal breakpoint(s)\n");
|
||||
if (atStepOutOffset(unit, offset)) {
|
||||
if (deeper) return; // Recursion
|
||||
TRACE(2, "CmdNext: hit step-out\n");
|
||||
} else if (atStepContOffset(unit, offset)) {
|
||||
// For step-conts we want to hit the exact same frame, not a call to the
|
||||
// same function higher or lower on the stack.
|
||||
if (!originalDepth) return;
|
||||
TRACE(2, "CmdNext: hit step-cont\n");
|
||||
} else {
|
||||
TRACE(2, "CmdNext: not complete, still on same line, keep stepping.\n");
|
||||
installLocationFilterForLine(interrupt.getSite());
|
||||
m_needsVMInterrupt = true;
|
||||
}
|
||||
} else {
|
||||
// Deeper, so let's setup a step out operation and turn interrupts off.
|
||||
TRACE(2, "CmdNext: deeper...\n");
|
||||
if (!hasStepOuts()) {
|
||||
// We can nuke the entire location filter here since we'll re-install it
|
||||
// when we get back to the old level. Keeping it installed may be more
|
||||
// efficient if we were on a large line, but there is a penalty for every
|
||||
// opcode executed while it's installed and that's bad if there's a lot of
|
||||
// code called from that line.
|
||||
removeLocationFilter();
|
||||
setupStepOuts();
|
||||
// We have internal breakpoints setup, but we haven't hit one yet. Keep
|
||||
// running until we reach one.
|
||||
TRACE(2, "CmdNext: waiting to hit internal breakpoint...\n");
|
||||
return;
|
||||
}
|
||||
// We've hit one internal breakpoint at a useful place, so we can remove
|
||||
// them all now.
|
||||
cleanupStepOuts();
|
||||
cleanupStepCont();
|
||||
}
|
||||
|
||||
if (deeper) {
|
||||
TRACE(2, "CmdNext: deeper, setup step out to get back to original line\n");
|
||||
setupStepOuts();
|
||||
// We can nuke the entire location filter here since we'll re-install it
|
||||
// when we get back to the old level. Keeping it installed may be more
|
||||
// efficient if we were on a large line, but there is a penalty for every
|
||||
// opcode executed while it's installed and that's bad if there's a lot of
|
||||
// code called from that line.
|
||||
removeLocationFilter();
|
||||
m_needsVMInterrupt = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (originalDepth && (m_loc == interrupt.getFileLine())) {
|
||||
TRACE(2, "CmdNext: not complete, still on same line\n");
|
||||
stepCurrentLine(interrupt, fp, pc);
|
||||
return;
|
||||
}
|
||||
|
||||
TRACE(2, "CmdNext: operation complete.\n");
|
||||
m_complete = (decCount() == 0);
|
||||
if (!m_complete) {
|
||||
TRACE(2, "CmdNext: repeat count > 0, start fresh.\n");
|
||||
onSetup(proxy, interrupt);
|
||||
}
|
||||
}
|
||||
|
||||
void CmdNext::stepCurrentLine(CmdInterrupt& interrupt, ActRec* fp, PC pc) {
|
||||
// Special handling for yields from generators. The destination of these
|
||||
// instructions is somewhat counter intuitive so we take care to ensure that
|
||||
// we step to the most appropriate place. For yeilds, we want to land on the
|
||||
// next statement when driven from a C++ iterator like ASIO. If the generator
|
||||
// is driven directly from PHP (i.e., a loop calling send($foo)) then we'll
|
||||
// land back at the callsite of send(). For returns from generators, we follow
|
||||
// the execution stack for now, and end up at the caller of ASIO or send().
|
||||
if (fp->m_func->isGenerator() &&
|
||||
((*pc == OpContExit) || (*pc == OpContRetC))) {
|
||||
TRACE(2, "CmdNext: encountered yield or return from generator\n");
|
||||
// Patch the projected return point(s) in both cases, to catch if we exit
|
||||
// the the asio iterator or if we are being iterated directly by PHP.
|
||||
setupStepOuts();
|
||||
if (*pc == OpContExit) {
|
||||
// Patch the next normal execution point so we can pickup the stepping
|
||||
// from there if the caller is C++.
|
||||
setupStepCont(fp, pc);
|
||||
}
|
||||
removeLocationFilter();
|
||||
m_needsVMInterrupt = false;
|
||||
return;
|
||||
}
|
||||
|
||||
installLocationFilterForLine(interrupt.getSite());
|
||||
m_needsVMInterrupt = true;
|
||||
}
|
||||
|
||||
bool CmdNext::hasStepCont() {
|
||||
return m_stepContUnit != nullptr;
|
||||
}
|
||||
|
||||
bool CmdNext::atStepContOffset(Unit* unit, Offset o) {
|
||||
return (unit == m_stepContUnit) && (o == m_stepContOffset);
|
||||
}
|
||||
|
||||
// A ContExit is followed by code to support ContRaise, then code for
|
||||
// ContSend/ContNext. We want to continue stepping on the latter. The normal
|
||||
// exception handling logic will take care of the former.
|
||||
// This logic is sensitive to the code gen here... we don't have access to the
|
||||
// offsets for the labels used to generate this code, so we rely on the
|
||||
// simplicity of the exceptional path.
|
||||
void CmdNext::setupStepCont(ActRec* fp, PC pc) {
|
||||
assert(*pc == OpContExit); // One byte
|
||||
assert(*(pc+1) == OpNull); // One byte
|
||||
assert(*(pc+2) == OpThrow); // One byte
|
||||
assert(*(pc+3) == OpNull); // One byte
|
||||
Offset nextInst = fp->m_func->unit()->offsetOf(pc + 4);
|
||||
m_stepContUnit = fp->m_func->unit();
|
||||
m_stepContOffset = nextInst;
|
||||
TRACE(2, "CmdNext: patch for cont step at '%s' offset %d\n",
|
||||
fp->m_func->fullName()->data(), nextInst);
|
||||
phpAddBreakPoint(m_stepContUnit, m_stepContOffset);
|
||||
}
|
||||
|
||||
void CmdNext::cleanupStepCont() {
|
||||
if (m_stepContUnit) {
|
||||
if (m_stepContOffset != InvalidAbsoluteOffset) {
|
||||
phpRemoveBreakPoint(m_stepContUnit, m_stepContOffset);
|
||||
m_stepContOffset = InvalidAbsoluteOffset;
|
||||
}
|
||||
m_stepContUnit = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,11 +25,24 @@ namespace HPHP { namespace Eval {
|
||||
DECLARE_BOOST_TYPES(CmdNext);
|
||||
class CmdNext : public CmdFlowControl {
|
||||
public:
|
||||
CmdNext() : CmdFlowControl(KindOfNext) {}
|
||||
CmdNext() : CmdFlowControl(KindOfNext),
|
||||
m_stepContUnit(nullptr),
|
||||
m_stepContOffset(InvalidAbsoluteOffset) {}
|
||||
virtual ~CmdNext();
|
||||
|
||||
virtual void help(DebuggerClient &client);
|
||||
virtual void onSetup(DebuggerProxy &proxy, CmdInterrupt &interrupt);
|
||||
virtual void onBeginInterrupt(DebuggerProxy &proxy, CmdInterrupt &interrupt);
|
||||
virtual void help(DebuggerClient& client);
|
||||
virtual void onSetup(DebuggerProxy& proxy, CmdInterrupt& interrupt);
|
||||
virtual void onBeginInterrupt(DebuggerProxy& proxy, CmdInterrupt& interrupt);
|
||||
|
||||
private:
|
||||
void stepCurrentLine(CmdInterrupt& interrupt, ActRec* fp, PC pc);
|
||||
bool hasStepCont();
|
||||
bool atStepContOffset(Unit* unit, Offset o);
|
||||
void setupStepCont(ActRec* fp, PC pc);
|
||||
void cleanupStepCont();
|
||||
|
||||
HPHP::Unit* m_stepContUnit;
|
||||
Offset m_stepContOffset;
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -1349,7 +1349,7 @@ void DebuggerClient::print(CStrRef msg) {
|
||||
FWRITE(color, 1, strlen(color), where); \
|
||||
} \
|
||||
FWRITE(msg.data(), 1, msg.length(), where); \
|
||||
if (UseColor && color) { \
|
||||
if (UseColor && color && RuntimeOption::EnableDebuggerColor) { \
|
||||
FWRITE(ANSI_COLOR_END, 1, strlen(ANSI_COLOR_END), where); \
|
||||
} \
|
||||
FWRITE("\n", 1, 1, where); \
|
||||
|
||||
@@ -254,22 +254,6 @@ void DebuggerProxy::interrupt(CmdInterrupt &cmd) {
|
||||
// thread the proxy considers "current".
|
||||
// NB: BreakPointReached really means we've got control of a VM thread from
|
||||
// the opcode hook. This could be for a breakpoint, stepping, etc.
|
||||
if (cmd.getInterruptType() == BreakPointReached) {
|
||||
// If we have this type of interrupt then the proxy must have outstanding
|
||||
// work to do. If it doesn't, then we've come too far processing this
|
||||
// interrupt.
|
||||
assert(needInterrupt());
|
||||
|
||||
// If the breakpoint is not to be processed, we should continue execution.
|
||||
BreakPointInfoPtr bp = getBreakPointAtCmd(cmd);
|
||||
if (bp) {
|
||||
if (!bp->breakable(getRealStackDepth())) {
|
||||
return;
|
||||
} else {
|
||||
bp->unsetBreakable(getRealStackDepth());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wait until this thread is the one this proxy wants to debug.
|
||||
if (!blockUntilOwn(cmd, true)) {
|
||||
@@ -284,6 +268,19 @@ void DebuggerProxy::interrupt(CmdInterrupt &cmd) {
|
||||
Lock lock(m_signalMutex); // Block the signal polling thread.
|
||||
m_signum = CmdSignal::SignalNone;
|
||||
processInterrupt(cmd);
|
||||
// If we've just processed a breakpoint, change it so we don't break at
|
||||
// it again until we're off this site.
|
||||
// Note: this feels quite out of place in here... should be part of
|
||||
// processing the break point itself. Added a note to task #2361050.
|
||||
if (cmd.getInterruptType() == BreakPointReached) {
|
||||
BreakPointInfoPtr bp = getBreakPointAtCmd(cmd);
|
||||
if (bp) {
|
||||
int stackDepth = getRealStackDepth();
|
||||
if (bp->breakable(stackDepth)) {
|
||||
bp->unsetBreakable(stackDepth);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (const DebuggerException &e) {
|
||||
switchThreadMode(Normal);
|
||||
throw;
|
||||
@@ -518,7 +515,7 @@ bool DebuggerProxy::blockUntilOwn(CmdInterrupt &cmd, bool check) {
|
||||
bool DebuggerProxy::checkBreakPoints(CmdInterrupt &cmd) {
|
||||
TRACE(2, "DebuggerProxy::checkBreakPoints\n");
|
||||
ReadLock lock(m_breakMutex);
|
||||
return cmd.shouldBreak(m_breakpoints);
|
||||
return cmd.shouldBreak(m_breakpoints, getRealStackDepth());
|
||||
}
|
||||
|
||||
// Check if we should stop due to flow control, breakpoints, and signals.
|
||||
|
||||
@@ -330,13 +330,20 @@ void PCFilter::PtrMap::clear() {
|
||||
m_root->clearImpl(PTRMAP_PTR_SIZE);
|
||||
}
|
||||
|
||||
void PCFilter::addRanges(const Unit* unit, const OffsetRangeVec& offsets) {
|
||||
// Adds a range of PCs to the filter given a collection of offset ranges.
|
||||
// Omit PCs which have opcodes that don't pass the given opcode filter.
|
||||
void PCFilter::addRanges(const Unit* unit, const OffsetRangeVec& offsets,
|
||||
OpcodeFilter isOpcodeAllowed) {
|
||||
for (auto range = offsets.cbegin(); range != offsets.cend(); ++range) {
|
||||
TRACE(3, "\toffsets [%d, %d)\n", range->m_base, range->m_past);
|
||||
for (PC pc = unit->at(range->m_base); pc < unit->at(range->m_past);
|
||||
pc += instrLen(pc)) {
|
||||
TRACE(3, "\t\tpc %p\n", pc);
|
||||
addPC(pc);
|
||||
if (isOpcodeAllowed(*pc)) {
|
||||
TRACE(3, "\t\tpc %p\n", pc);
|
||||
addPC(pc);
|
||||
} else {
|
||||
TRACE(3, "\t\tpc %p -- skipping (offset %d)\n", pc, unit->offsetOf(pc));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include "hphp/util/base.h"
|
||||
#include "hphp/runtime/vm/unit.h"
|
||||
#include <functional>
|
||||
|
||||
namespace HPHP {
|
||||
namespace Eval{
|
||||
@@ -108,8 +109,13 @@ private:
|
||||
public:
|
||||
PCFilter() {}
|
||||
|
||||
// Add/remove offsets, either individually or by range.
|
||||
void addRanges(const Unit* unit, const OffsetRangeVec& offsets);
|
||||
// Filter function to exclude opcodes when adding ranges.
|
||||
typedef std::function<bool(Opcode)> OpcodeFilter;
|
||||
|
||||
// Add/remove offsets, either individually or by range. By default allow all
|
||||
// opcodes.
|
||||
void addRanges(const Unit* unit, const OffsetRangeVec& offsets,
|
||||
OpcodeFilter isOpcodeAllowed = [] (Opcode) { return true; });
|
||||
void removeOffset(const Unit* unit, Offset offset);
|
||||
|
||||
// Add/remove/check explicit PCs.
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
// Test the following:
|
||||
// - Stepping over yeilds.
|
||||
// - Stepping over the return from a generator
|
||||
|
||||
function bar($a) {
|
||||
return $a + 2;
|
||||
}
|
||||
|
||||
function genFoo($a) {
|
||||
$a = bar($a);
|
||||
$z = yield $a+5;
|
||||
yield $z+1;
|
||||
error_log('Finished in genFoo');
|
||||
}
|
||||
|
||||
function foo($a) {
|
||||
$gen1 = genFoo($a);
|
||||
$gen1->next();
|
||||
while ($gen1->valid()) {
|
||||
$val = $gen1->current();
|
||||
var_dump($val);
|
||||
$gen1->send($a);
|
||||
}
|
||||
}
|
||||
|
||||
function test($a) {
|
||||
foo($a);
|
||||
}
|
||||
|
||||
error_log('flow_gen.php loaded');
|
||||
@@ -0,0 +1,166 @@
|
||||
Welcome to HipHop Debugger!
|
||||
Type "help" or "?" for a complete list of commands.
|
||||
|
||||
Program %s/flow_gen.php loaded. Type '[r]un' or '[c]ontinue' to go.
|
||||
hphpd> run
|
||||
flow_gen.php loaded
|
||||
Program %s/flow_gen.php exited normally.
|
||||
hphpd> break flow_gen.php:12
|
||||
Breakpoint 1 set on line 12 of flow_gen.php
|
||||
hphpd> @test(1)
|
||||
Breakpoint 1 reached at %s() on line 12 of %s/flow_gen.php
|
||||
11 function genFoo($a) {
|
||||
12 $a = bar($a);
|
||||
13 $z = yield $a+5;
|
||||
|
||||
hphpd> next
|
||||
Break at %s() on line 13 of %s/flow_gen.php
|
||||
12 $a = bar($a);
|
||||
13 $z = yield $a+5;
|
||||
14 yield $z+1;
|
||||
|
||||
hphpd> next
|
||||
Break at foo() on line 20 of %s/flow_gen.php
|
||||
19 $gen1 = genFoo($a);
|
||||
20 $gen1->next();
|
||||
21 while ($gen1->valid()) {
|
||||
|
||||
hphpd> next
|
||||
Break at foo() on line 21 of %s/flow_gen.php
|
||||
20 $gen1->next();
|
||||
21 while ($gen1->valid()) {
|
||||
22 $val = $gen1->current();
|
||||
|
||||
hphpd> next
|
||||
Break at foo() on line 22 of %s/flow_gen.php
|
||||
21 while ($gen1->valid()) {
|
||||
22 $val = $gen1->current();
|
||||
23 var_dump($val);
|
||||
|
||||
hphpd> next
|
||||
Break at foo() on line 23 of %s/flow_gen.php
|
||||
22 $val = $gen1->current();
|
||||
23 var_dump($val);
|
||||
24 $gen1->send($a);
|
||||
|
||||
hphpd> next
|
||||
Break at foo() on line 24 of %s/flow_gen.php
|
||||
23 var_dump($val);
|
||||
24 $gen1->send($a);
|
||||
25 }
|
||||
|
||||
hphpd> step
|
||||
Break at Continuation::send()
|
||||
hphpd> step
|
||||
Break at Continuation::send()
|
||||
hphpd> step
|
||||
Break at %s() on line 11 of %s/flow_gen.php
|
||||
10
|
||||
11 function genFoo($a) {
|
||||
12 $a = bar($a);
|
||||
13 $z = yield $a+5;
|
||||
14 yield $z+1;
|
||||
15 error_log('Finished in genFoo');
|
||||
16 }
|
||||
17
|
||||
|
||||
hphpd> step
|
||||
Break at %s() on line 13 of %s/flow_gen.php
|
||||
12 $a = bar($a);
|
||||
13 $z = yield $a+5;
|
||||
14 yield $z+1;
|
||||
|
||||
hphpd> next
|
||||
Break at %s() on line 14 of %s/flow_gen.php
|
||||
13 $z = yield $a+5;
|
||||
14 yield $z+1;
|
||||
15 error_log('Finished in genFoo');
|
||||
|
||||
hphpd> next
|
||||
Break at foo() on line 24 of %s/flow_gen.php
|
||||
23 var_dump($val);
|
||||
24 $gen1->send($a);
|
||||
25 }
|
||||
|
||||
hphpd> next
|
||||
Break at foo() on line 21 of %s/flow_gen.php
|
||||
20 $gen1->next();
|
||||
21 while ($gen1->valid()) {
|
||||
22 $val = $gen1->current();
|
||||
|
||||
hphpd> next
|
||||
Break at foo() on line 22 of %s/flow_gen.php
|
||||
21 while ($gen1->valid()) {
|
||||
22 $val = $gen1->current();
|
||||
23 var_dump($val);
|
||||
|
||||
hphpd> next
|
||||
Break at foo() on line 23 of %s/flow_gen.php
|
||||
22 $val = $gen1->current();
|
||||
23 var_dump($val);
|
||||
24 $gen1->send($a);
|
||||
|
||||
hphpd> next
|
||||
Break at foo() on line 24 of %s/flow_gen.php
|
||||
23 var_dump($val);
|
||||
24 $gen1->send($a);
|
||||
25 }
|
||||
|
||||
hphpd> step
|
||||
Break at Continuation::send()
|
||||
hphpd> step
|
||||
Break at Continuation::send()
|
||||
hphpd> step
|
||||
Break at %s() on line 11 of %s/flow_gen.php
|
||||
10
|
||||
11 function genFoo($a) {
|
||||
12 $a = bar($a);
|
||||
13 $z = yield $a+5;
|
||||
14 yield $z+1;
|
||||
15 error_log('Finished in genFoo');
|
||||
16 }
|
||||
17
|
||||
|
||||
hphpd> step
|
||||
Break at %s() on line 14 of %s/flow_gen.php
|
||||
13 $z = yield $a+5;
|
||||
14 yield $z+1;
|
||||
15 error_log('Finished in genFoo');
|
||||
|
||||
hphpd> next
|
||||
Break at %s() on line 15 of %s/flow_gen.php
|
||||
14 yield $z+1;
|
||||
15 error_log('Finished in genFoo');
|
||||
16 }
|
||||
|
||||
hphpd> next
|
||||
Finished in genFoo
|
||||
Break at %s() on line 11 of %s/flow_gen.php
|
||||
10
|
||||
11 function genFoo($a) {
|
||||
12 $a = bar($a);
|
||||
13 $z = yield $a+5;
|
||||
14 yield $z+1;
|
||||
15 error_log('Finished in genFoo');
|
||||
16 }
|
||||
17
|
||||
|
||||
hphpd> next
|
||||
Break at foo() on line 24 of %s/flow_gen.php
|
||||
23 var_dump($val);
|
||||
24 $gen1->send($a);
|
||||
25 }
|
||||
|
||||
hphpd> next
|
||||
Break at foo() on line 21 of %s/flow_gen.php
|
||||
20 $gen1->next();
|
||||
21 while ($gen1->valid()) {
|
||||
22 $val = $gen1->current();
|
||||
|
||||
hphpd> break clear all
|
||||
All breakpoints are cleared.
|
||||
hphpd> continue
|
||||
int(8)
|
||||
int(2)
|
||||
|
||||
hphpd> quit
|
||||
@@ -0,0 +1,30 @@
|
||||
run
|
||||
break flow_gen.php:12
|
||||
@test(1)
|
||||
next
|
||||
next
|
||||
next
|
||||
next
|
||||
next
|
||||
next
|
||||
step
|
||||
step
|
||||
step
|
||||
step
|
||||
next
|
||||
next
|
||||
next
|
||||
next
|
||||
next
|
||||
next
|
||||
step
|
||||
step
|
||||
step
|
||||
step
|
||||
next
|
||||
next
|
||||
next
|
||||
next
|
||||
break clear all
|
||||
continue
|
||||
quit
|
||||
@@ -0,0 +1 @@
|
||||
-m debug
|
||||
Referência em uma Nova Issue
Bloquear um usuário