diff --git a/hphp/runtime/debugger/cmd/cmd_flow_control.cpp b/hphp/runtime/debugger/cmd/cmd_flow_control.cpp index 6599766bc..bc09ddfbc 100644 --- a/hphp/runtime/debugger/cmd/cmd_flow_control.cpp +++ b/hphp/runtime/debugger/cmd/cmd_flow_control.cpp @@ -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 diff --git a/hphp/runtime/debugger/cmd/cmd_flow_control.h b/hphp/runtime/debugger/cmd/cmd_flow_control.h index 3ef824d30..068738674 100644 --- a/hphp/runtime/debugger/cmd/cmd_flow_control.h +++ b/hphp/runtime/debugger/cmd/cmd_flow_control.h @@ -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; diff --git a/hphp/runtime/debugger/cmd/cmd_interrupt.cpp b/hphp/runtime/debugger/cmd/cmd_interrupt.cpp index f4b410fc2..5370d0449 100644 --- a/hphp/runtime/debugger/cmd/cmd_interrupt.cpp +++ b/hphp/runtime/debugger/cmd/cmd_interrupt.cpp @@ -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 diff --git a/hphp/runtime/debugger/cmd/cmd_interrupt.h b/hphp/runtime/debugger/cmd/cmd_interrupt.h index 85d22dfb4..303e39539 100644 --- a/hphp/runtime/debugger/cmd/cmd_interrupt.h +++ b/hphp/runtime/debugger/cmd/cmd_interrupt.h @@ -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;} diff --git a/hphp/runtime/debugger/cmd/cmd_next.cpp b/hphp/runtime/debugger/cmd/cmd_next.cpp index 265a64233..2036b1f0e 100644 --- a/hphp/runtime/debugger/cmd/cmd_next.cpp +++ b/hphp/runtime/debugger/cmd/cmd_next.cpp @@ -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; } } diff --git a/hphp/runtime/debugger/cmd/cmd_next.h b/hphp/runtime/debugger/cmd/cmd_next.h index 0124ed192..3f3782638 100644 --- a/hphp/runtime/debugger/cmd/cmd_next.h +++ b/hphp/runtime/debugger/cmd/cmd_next.h @@ -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; }; /////////////////////////////////////////////////////////////////////////////// diff --git a/hphp/runtime/debugger/debugger_client.cpp b/hphp/runtime/debugger/debugger_client.cpp index 3732b821e..c09145a18 100644 --- a/hphp/runtime/debugger/debugger_client.cpp +++ b/hphp/runtime/debugger/debugger_client.cpp @@ -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); \ diff --git a/hphp/runtime/debugger/debugger_proxy.cpp b/hphp/runtime/debugger/debugger_proxy.cpp index 9aa783894..ea8b38be5 100644 --- a/hphp/runtime/debugger/debugger_proxy.cpp +++ b/hphp/runtime/debugger/debugger_proxy.cpp @@ -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. diff --git a/hphp/runtime/vm/debugger_hook.cpp b/hphp/runtime/vm/debugger_hook.cpp index f9e1e27c4..10416aa1b 100644 --- a/hphp/runtime/vm/debugger_hook.cpp +++ b/hphp/runtime/vm/debugger_hook.cpp @@ -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)); + } } } } diff --git a/hphp/runtime/vm/debugger_hook.h b/hphp/runtime/vm/debugger_hook.h index 2c2421669..2255753af 100644 --- a/hphp/runtime/vm/debugger_hook.h +++ b/hphp/runtime/vm/debugger_hook.h @@ -19,6 +19,7 @@ #include "hphp/util/base.h" #include "hphp/runtime/vm/unit.h" +#include 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 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. diff --git a/hphp/test/quick/debugger/flow_gen.php b/hphp/test/quick/debugger/flow_gen.php new file mode 100644 index 000000000..4c4644fae --- /dev/null +++ b/hphp/test/quick/debugger/flow_gen.php @@ -0,0 +1,32 @@ +next(); + while ($gen1->valid()) { + $val = $gen1->current(); + var_dump($val); + $gen1->send($a); + } +} + +function test($a) { + foo($a); +} + +error_log('flow_gen.php loaded'); diff --git a/hphp/test/quick/debugger/flow_gen.php.expectf b/hphp/test/quick/debugger/flow_gen.php.expectf new file mode 100644 index 000000000..c7153e21c --- /dev/null +++ b/hphp/test/quick/debugger/flow_gen.php.expectf @@ -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 diff --git a/hphp/test/quick/debugger/flow_gen.php.in b/hphp/test/quick/debugger/flow_gen.php.in new file mode 100644 index 000000000..4ae881d69 --- /dev/null +++ b/hphp/test/quick/debugger/flow_gen.php.in @@ -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 diff --git a/hphp/test/quick/debugger/flow_gen.php.opts b/hphp/test/quick/debugger/flow_gen.php.opts new file mode 100644 index 000000000..cf014a32f --- /dev/null +++ b/hphp/test/quick/debugger/flow_gen.php.opts @@ -0,0 +1 @@ + -m debug \ No newline at end of file