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:
Mike Magruder
2013-05-23 19:31:39 -07:00
commit de Sara Golemon
commit 5299e5ba0d
14 arquivos alterados com 439 adições e 79 exclusões
+15 -6
Ver Arquivo
@@ -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
+1 -1
Ver Arquivo
@@ -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;
+3 -1
Ver Arquivo
@@ -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
+1 -1
Ver Arquivo
@@ -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;}
+140 -43
Ver Arquivo
@@ -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;
}
}
+17 -4
Ver Arquivo
@@ -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;
};
///////////////////////////////////////////////////////////////////////////////
+1 -1
Ver Arquivo
@@ -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); \
+14 -17
Ver Arquivo
@@ -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.
+10 -3
Ver Arquivo
@@ -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));
}
}
}
}
+8 -2
Ver Arquivo
@@ -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.
+32
Ver Arquivo
@@ -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');
+166
Ver Arquivo
@@ -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
+30
Ver Arquivo
@@ -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
+1
Ver Arquivo
@@ -0,0 +1 @@
-m debug