From 5299e5ba0d6de2ad1a96af4f7f86fea36c66fb5c Mon Sep 17 00:00:00 2001 From: Mike Magruder Date: Thu, 23 May 2013 19:31:39 -0700 Subject: [PATCH] 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. --- .../runtime/debugger/cmd/cmd_flow_control.cpp | 21 +- hphp/runtime/debugger/cmd/cmd_flow_control.h | 2 +- hphp/runtime/debugger/cmd/cmd_interrupt.cpp | 4 +- hphp/runtime/debugger/cmd/cmd_interrupt.h | 2 +- hphp/runtime/debugger/cmd/cmd_next.cpp | 183 ++++++++++++++---- hphp/runtime/debugger/cmd/cmd_next.h | 21 +- hphp/runtime/debugger/debugger_client.cpp | 2 +- hphp/runtime/debugger/debugger_proxy.cpp | 31 ++- hphp/runtime/vm/debugger_hook.cpp | 13 +- hphp/runtime/vm/debugger_hook.h | 10 +- hphp/test/quick/debugger/flow_gen.php | 32 +++ hphp/test/quick/debugger/flow_gen.php.expectf | 166 ++++++++++++++++ hphp/test/quick/debugger/flow_gen.php.in | 30 +++ hphp/test/quick/debugger/flow_gen.php.opts | 1 + 14 files changed, 439 insertions(+), 79 deletions(-) create mode 100644 hphp/test/quick/debugger/flow_gen.php create mode 100644 hphp/test/quick/debugger/flow_gen.php.expectf create mode 100644 hphp/test/quick/debugger/flow_gen.php.in create mode 100644 hphp/test/quick/debugger/flow_gen.php.opts 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