From 96d6bab29120fbe20f9bfed91c598b0ca9bd8edf Mon Sep 17 00:00:00 2001 From: Mike Magruder Date: Fri, 7 Jun 2013 10:29:16 -0700 Subject: [PATCH] Cleanup flow control around exceptions There were multiple issues with flow control when exceptions occur. Fixed these by ditching the reliance on the exception thrown interrupt and introduce an exception handler interrupt, which indicates control is about to pass to a catch clause. This gives us much better insight into how execution is flowing and how we might need to adjust an in-flight stepping operation. --- hphp/runtime/debugger/break_point.cpp | 1 + hphp/runtime/debugger/break_point.h | 6 + hphp/runtime/debugger/cmd/cmd_interrupt.cpp | 3 + hphp/runtime/debugger/cmd/cmd_next.cpp | 74 ++- hphp/runtime/debugger/cmd/cmd_next.h | 5 +- hphp/runtime/debugger/cmd/cmd_out.cpp | 22 +- hphp/runtime/debugger/cmd/cmd_step.cpp | 3 + hphp/runtime/debugger/debugger.cpp | 2 + hphp/runtime/debugger/debugger_proxy.cpp | 2 +- hphp/runtime/vm/bytecode.cpp | 4 +- hphp/runtime/vm/debugger_hook.cpp | 18 +- hphp/runtime/vm/debugger_hook.h | 3 +- hphp/test/quick/debugger/flow_excep.php | 64 +++ .../quick/debugger/flow_excep.php.expectf | 469 ++++++++++++++++++ hphp/test/quick/debugger/flow_excep.php.in | 71 +++ .../test/quick/debugger/flow_excep.php.norepo | 0 hphp/test/quick/debugger/flow_excep.php.opts | 1 + hphp/test/quick/debugger/flow_gen_excep.php | 41 ++ .../quick/debugger/flow_gen_excep.php.expectf | 102 ++++ .../test/quick/debugger/flow_gen_excep.php.in | 19 + .../quick/debugger/flow_gen_excep.php.norepo | 0 .../quick/debugger/flow_gen_excep.php.opts | 1 + 22 files changed, 880 insertions(+), 31 deletions(-) create mode 100644 hphp/test/quick/debugger/flow_excep.php create mode 100644 hphp/test/quick/debugger/flow_excep.php.expectf create mode 100644 hphp/test/quick/debugger/flow_excep.php.in create mode 100644 hphp/test/quick/debugger/flow_excep.php.norepo create mode 100644 hphp/test/quick/debugger/flow_excep.php.opts create mode 100644 hphp/test/quick/debugger/flow_gen_excep.php create mode 100644 hphp/test/quick/debugger/flow_gen_excep.php.expectf create mode 100644 hphp/test/quick/debugger/flow_gen_excep.php.in create mode 100644 hphp/test/quick/debugger/flow_gen_excep.php.norepo create mode 100644 hphp/test/quick/debugger/flow_gen_excep.php.opts diff --git a/hphp/runtime/debugger/break_point.cpp b/hphp/runtime/debugger/break_point.cpp index 261d55cee..b13a95ba3 100644 --- a/hphp/runtime/debugger/break_point.cpp +++ b/hphp/runtime/debugger/break_point.cpp @@ -182,6 +182,7 @@ BreakPointInfo::BreakPointInfo(bool regex, State state, m_line1(0), m_line2(0), m_regex(regex), m_check(false) { TRACE(2, "BreakPointInfo::BreakPointInfo..const std::string &file)\n"); + assert(m_interrupt != ExceptionHandler); // Server-side only. if (m_interrupt == ExceptionThrown) { parseExceptionThrown(exp); if (!m_file.empty() || m_line1 || m_line2 || !m_funcs.empty()) { diff --git a/hphp/runtime/debugger/break_point.h b/hphp/runtime/debugger/break_point.h index c524e837e..eb4c6adef 100644 --- a/hphp/runtime/debugger/break_point.h +++ b/hphp/runtime/debugger/break_point.h @@ -31,6 +31,12 @@ enum InterruptType { HardBreakPoint, // From f_hphpd_break(). BreakPointReached, // Break from the VM interpreter loop ExceptionThrown, + + // Interrupts for exception handler entry are, for now, server-side only and + // only for flow control. We could consider exposing this to clients, and + // allowing it as a break point much like ExceptionThrown is, but the value + // seems quite low at this time. We assert this stays server-side. + ExceptionHandler, }; // Represents a site in the code, at the source level. diff --git a/hphp/runtime/debugger/cmd/cmd_interrupt.cpp b/hphp/runtime/debugger/cmd/cmd_interrupt.cpp index 226ed77ad..95c7ee58b 100644 --- a/hphp/runtime/debugger/cmd/cmd_interrupt.cpp +++ b/hphp/runtime/debugger/cmd/cmd_interrupt.cpp @@ -25,6 +25,7 @@ TRACE_SET_MOD(debugger); void CmdInterrupt::sendImpl(DebuggerThriftBuffer &thrift) { DebuggerCommand::sendImpl(thrift); + assert(m_interrupt != ExceptionHandler); // Server-side only. thrift.write(m_interrupt); thrift.write(m_program); thrift.write(m_errorMsg); @@ -307,6 +308,8 @@ bool CmdInterrupt::shouldBreak(const BreakPointInfoPtrVec &bps, } } return !m_matched.empty(); + case ExceptionHandler: + return false; // For flow control only at this time. } assert(false); return false; diff --git a/hphp/runtime/debugger/cmd/cmd_next.cpp b/hphp/runtime/debugger/cmd/cmd_next.cpp index 2036b1f0e..e0830fb14 100644 --- a/hphp/runtime/debugger/cmd/cmd_next.cpp +++ b/hphp/runtime/debugger/cmd/cmd_next.cpp @@ -16,6 +16,8 @@ #include "hphp/runtime/debugger/cmd/cmd_next.h" #include "hphp/runtime/vm/debugger_hook.h" +#include "hphp/runtime/vm/runtime.h" +#include "hphp/runtime/ext/ext_continuation.h" namespace HPHP { namespace Eval { /////////////////////////////////////////////////////////////////////////////// @@ -54,14 +56,6 @@ void CmdNext::onSetup(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) { - // If an exception is thrown we turn interrupts on to ensure we stop when - // control reaches the first catch clause. - TRACE(2, "CmdNext: exception thrown\n"); - removeLocationFilter(); - m_needsVMInterrupt = true; - return; - } ActRec *fp = g_vmContext->getFP(); assert(fp); // All interrupts which reach a flow cmd have an AR. @@ -89,6 +83,8 @@ void CmdNext::onBeginInterrupt(DebuggerProxy& proxy, CmdInterrupt& interrupt) { deeper = true; } + m_needsVMInterrupt = false; // Will be set again below if still needed. + // 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 @@ -99,22 +95,55 @@ void CmdNext::onBeginInterrupt(DebuggerProxy& proxy, CmdInterrupt& interrupt) { 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; + // For step-conts we want to hit the exact same frame, for the same + // continuation, not a call to the same function higher or lower on the + // stack. + if (!originalDepth || (m_stepContTag != getContinuationTag(fp))) return; TRACE(2, "CmdNext: hit step-cont\n"); + } else if (interrupt.getInterruptType() == ExceptionHandler) { + // Entering an exception handler may take us someplace we weren't + // expecting. Adjust internal breakpoints accordingly. First case is easy. + if (deeper) { + TRACE(2, "CmdNext: exception handler, deeper\n"); + return; + } + // For step-conts, we ignore handlers at the original level if we're not + // in the original continuation. We don't care about exception handlers + // in continuations being driven at the same level. + if (hasStepCont() && originalDepth && + (m_stepContTag != getContinuationTag(fp))) { + TRACE(2, "CmdNext: exception handler, original depth, wrong cont\n"); + return; + } + // Sometimes we have handlers in generated code, i.e., Continuation::next. + // These just help propagate exceptions so ignore those. + if (fp->m_func->line1() == 0) { + TRACE(2, "CmdNext: exception handler, ignoring func with no source\n"); + return; + } + TRACE(2, "CmdNext: exception handler altering expected flow\n"); } else { // 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. + // We've hit one internal breakpoint at a useful place, or decided we don't, + // need them, so we can remove them all now. cleanupStepOuts(); cleanupStepCont(); } + if (interrupt.getInterruptType() == ExceptionHandler) { + // If we're about to enter an exception handler we turn interrupts on to + // ensure we stop when control reaches the handler. The normal logic below + // will decide if we're done at that point or not. + TRACE(2, "CmdNext: exception handler\n"); + removeLocationFilter(); + m_needsVMInterrupt = true; + return; + } + if (deeper) { TRACE(2, "CmdNext: deeper, setup step out to get back to original line\n"); setupStepOuts(); @@ -124,7 +153,6 @@ void CmdNext::onBeginInterrupt(DebuggerProxy& proxy, CmdInterrupt& interrupt) { // 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; } @@ -162,7 +190,6 @@ void CmdNext::stepCurrentLine(CmdInterrupt& interrupt, ActRec* fp, PC pc) { setupStepCont(fp, pc); } removeLocationFilter(); - m_needsVMInterrupt = false; return; } @@ -192,6 +219,7 @@ void CmdNext::setupStepCont(ActRec* fp, PC pc) { Offset nextInst = fp->m_func->unit()->offsetOf(pc + 4); m_stepContUnit = fp->m_func->unit(); m_stepContOffset = nextInst; + m_stepContTag = getContinuationTag(fp); TRACE(2, "CmdNext: patch for cont step at '%s' offset %d\n", fp->m_func->fullName()->data(), nextInst); phpAddBreakPoint(m_stepContUnit, m_stepContOffset); @@ -203,9 +231,25 @@ void CmdNext::cleanupStepCont() { phpRemoveBreakPoint(m_stepContUnit, m_stepContOffset); m_stepContOffset = InvalidAbsoluteOffset; } + m_stepContTag = nullptr; m_stepContUnit = nullptr; } } +// Use the address of the c_Continuation object as a tag for this stepping +// operation, to ensure we only stop once we're back to the same continuation. +// Since we'll either stop when we get out of whatever is driving this +// continuation, or we'll stop when we get back into it, we know the object +// will remain alive. +void* CmdNext::getContinuationTag(ActRec* fp) { + TypedValue* tv = frame_local(fp, 0); + assert(tv->m_type == HPHP::KindOfObject); + assert(dynamic_cast(tv->m_data.pobj)); + c_Continuation* cont = static_cast(tv->m_data.pobj); + TRACE(2, "CmdNext: continuation tag %p for %s\n", cont, + cont->t_getorigfuncname()->data()); + return cont; +} + /////////////////////////////////////////////////////////////////////////////// }} diff --git a/hphp/runtime/debugger/cmd/cmd_next.h b/hphp/runtime/debugger/cmd/cmd_next.h index 3f3782638..3c3897d7e 100644 --- a/hphp/runtime/debugger/cmd/cmd_next.h +++ b/hphp/runtime/debugger/cmd/cmd_next.h @@ -27,7 +27,8 @@ class CmdNext : public CmdFlowControl { public: CmdNext() : CmdFlowControl(KindOfNext), m_stepContUnit(nullptr), - m_stepContOffset(InvalidAbsoluteOffset) {} + m_stepContOffset(InvalidAbsoluteOffset), + m_stepContTag(nullptr) {} virtual ~CmdNext(); virtual void help(DebuggerClient& client); @@ -40,9 +41,11 @@ private: bool atStepContOffset(Unit* unit, Offset o); void setupStepCont(ActRec* fp, PC pc); void cleanupStepCont(); + void* getContinuationTag(ActRec* fp); HPHP::Unit* m_stepContUnit; Offset m_stepContOffset; + void* m_stepContTag; // Unique identifier for the continuation we're stepping }; /////////////////////////////////////////////////////////////////////////////// diff --git a/hphp/runtime/debugger/cmd/cmd_out.cpp b/hphp/runtime/debugger/cmd/cmd_out.cpp index 17e94bd37..b09da1eb1 100644 --- a/hphp/runtime/debugger/cmd/cmd_out.cpp +++ b/hphp/runtime/debugger/cmd/cmd_out.cpp @@ -45,22 +45,27 @@ void CmdOut::onSetup(DebuggerProxy &proxy, CmdInterrupt &interrupt) { } void CmdOut::onBeginInterrupt(DebuggerProxy &proxy, CmdInterrupt &interrupt) { - TRACE(2, "CmdNext::onBeginInterrupt\n"); + TRACE(2, "CmdOut::onBeginInterrupt\n"); assert(!m_complete); // Complete cmds should not be asked to do work. - if (interrupt.getInterruptType() == ExceptionThrown) { - // If an exception is thrown we turn interrupts on to ensure we stop when - // control reaches the first catch clause. - removeLocationFilter(); - m_needsVMInterrupt = true; - return; - } + m_needsVMInterrupt = false; int currentVMDepth = g_vmContext->m_nesting; int currentStackDepth = proxy.getStackDepth(); // Deeper or same depth? Keep running. if ((currentVMDepth > m_vmDepth) || ((currentVMDepth == m_vmDepth) && (currentStackDepth >= m_stackDepth))) { + TRACE(2, "CmdOut: deeper, keep running...\n"); + return; + } + + if (interrupt.getInterruptType() == ExceptionHandler) { + // If we're about to enter an exception handler we turn interrupts on to + // ensure we stop when control reaches the handler. The normal logic below + // will decide if we're done at that point or not. + TRACE(2, "CmdOut: exception thrown\n"); + removeLocationFilter(); + m_needsVMInterrupt = true; return; } @@ -71,7 +76,6 @@ void CmdOut::onBeginInterrupt(DebuggerProxy &proxy, CmdInterrupt &interrupt) { TRACE(2, "CmdOut: not complete, step out again.\n"); onSetup(proxy, interrupt); } - m_needsVMInterrupt = false; } /////////////////////////////////////////////////////////////////////////////// diff --git a/hphp/runtime/debugger/cmd/cmd_step.cpp b/hphp/runtime/debugger/cmd/cmd_step.cpp index cfdf8ad75..ae39ee088 100644 --- a/hphp/runtime/debugger/cmd/cmd_step.cpp +++ b/hphp/runtime/debugger/cmd/cmd_step.cpp @@ -47,6 +47,9 @@ void CmdStep::onSetup(DebuggerProxy &proxy, CmdInterrupt &interrupt) { } void CmdStep::onBeginInterrupt(DebuggerProxy &proxy, CmdInterrupt &interrupt) { + // Step doesn't care about this interrupt... we just stay the course and + // keep stepping. + if (interrupt.getInterruptType() == ExceptionHandler) return; m_complete = (decCount() == 0); if (!m_complete) { installLocationFilterForLine(interrupt.getSite()); diff --git a/hphp/runtime/debugger/debugger.cpp b/hphp/runtime/debugger/debugger.cpp index 1d6b32267..e18df27be 100644 --- a/hphp/runtime/debugger/debugger.cpp +++ b/hphp/runtime/debugger/debugger.cpp @@ -283,6 +283,7 @@ void Debugger::Interrupt(int type, const char *program, // response to various events. While this function is quite general wrt. the // type of interrupt, practically the type will be one of the following: // - ExceptionThrown +// - ExceptionHandler // - BreakPointReached // - HardBreakPoint // @@ -618,6 +619,7 @@ const char *Debugger::InterruptTypeName(CmdInterrupt &cmd) { case HardBreakPoint: return "HardBreakPoint"; case BreakPointReached: return "BreakPointReached"; case ExceptionThrown: return "ExceptionThrown"; + case ExceptionHandler: return "ExceptionHandler"; default: return "unknown"; } diff --git a/hphp/runtime/debugger/debugger_proxy.cpp b/hphp/runtime/debugger/debugger_proxy.cpp index ebc99bed6..88c72688e 100644 --- a/hphp/runtime/debugger/debugger_proxy.cpp +++ b/hphp/runtime/debugger/debugger_proxy.cpp @@ -542,7 +542,7 @@ bool DebuggerProxy::checkFlowBreak(CmdInterrupt &cmd) { if ((cmd.getInterruptType() == BreakPointReached || cmd.getInterruptType() == HardBreakPoint || - cmd.getInterruptType() == ExceptionThrown) && m_flow) { + cmd.getInterruptType() == ExceptionHandler) && m_flow) { if (!m_flow->is(DebuggerCommand::KindOfContinue)) { m_flow->onBeginInterrupt(*this, cmd); if (m_flow->complete()) { diff --git a/hphp/runtime/vm/bytecode.cpp b/hphp/runtime/vm/bytecode.cpp index 4d59b268c..5b38a7ad6 100644 --- a/hphp/runtime/vm/bytecode.cpp +++ b/hphp/runtime/vm/bytecode.cpp @@ -912,6 +912,7 @@ UnwindStatus Stack::unwindFrag(ActRec* fp, int offset, func->unit()->offsetOf(pc)); fault.m_savedRaiseOffset = func->unit()->offsetOf(pc); pc = (uchar*)(func->unit()->entry() + eh->m_fault); + DEBUGGER_ATTACHED_ONLY(phpDebuggerExceptionHandlerHook()); return UnwindResumeVM; case EHEnt::EHType_Catch: // Note: we skip catch clauses if we have a pending C++ exception @@ -929,6 +930,7 @@ UnwindStatus Stack::unwindFrag(ActRec* fp, int offset, if (cls && obj->instanceof(cls)) { pc = handler; FTRACE(1, "unwindFrag: entering catch at {}\n", pc); + DEBUGGER_ATTACHED_ONLY(phpDebuggerExceptionHandlerHook()); return UnwindResumeVM; } } @@ -4627,7 +4629,7 @@ inline void OPTBLD_INLINE VMExecutionContext::iopThrow(PC& pc) { Object obj(c1->m_data.pobj); m_stack.popC(); - DEBUGGER_ATTACHED_ONLY(phpDebuggerExceptionHook(obj.get())); + DEBUGGER_ATTACHED_ONLY(phpDebuggerExceptionThrownHook(obj.get())); throw obj; } diff --git a/hphp/runtime/vm/debugger_hook.cpp b/hphp/runtime/vm/debugger_hook.cpp index 10416aa1b..35dbd8586 100644 --- a/hphp/runtime/vm/debugger_hook.cpp +++ b/hphp/runtime/vm/debugger_hook.cpp @@ -67,14 +67,26 @@ void phpDebuggerOpcodeHook(const uchar* pc) { // Hook called from iopThrow to signal that we are about to throw an exception. // NB: this does not hook any portion of exception unwind. -void phpDebuggerExceptionHook(ObjectData* e) { - TRACE(5, "in phpDebuggerExceptionHook()\n"); +void phpDebuggerExceptionThrownHook(ObjectData* e) { + TRACE(5, "in phpDebuggerExceptionThrownHook()\n"); if (UNLIKELY(g_vmContext->m_dbgNoBreak)) { TRACE(5, "NoBreak flag is on\n"); return; } Eval::Debugger::InterruptVMHook(Eval::ExceptionThrown, e); - TRACE(5, "out phpDebuggerExceptionHook()\n"); + TRACE(5, "out phpDebuggerExceptionThrownHook()\n"); +} + +// Hook called from exception unwind to signal that we are about to handle an +// exception. +void phpDebuggerExceptionHandlerHook() { + TRACE(5, "in phpDebuggerExceptionHandlerHook()\n"); + if (UNLIKELY(g_vmContext->m_dbgNoBreak)) { + TRACE(5, "NoBreak flag is on\n"); + return; + } + Eval::Debugger::InterruptVMHook(Eval::ExceptionHandler); + TRACE(5, "out phpDebuggerExceptionHandlerHook()\n"); } bool isDebuggerAttachedProcess() { diff --git a/hphp/runtime/vm/debugger_hook.h b/hphp/runtime/vm/debugger_hook.h index 6b093ab04..8b34c0a1a 100644 --- a/hphp/runtime/vm/debugger_hook.h +++ b/hphp/runtime/vm/debugger_hook.h @@ -39,7 +39,8 @@ namespace HPHP { // debugging to give the debugger a chance to act. The debugger may block // execution indefinitely within one of these hooks. void phpDebuggerOpcodeHook(const uchar* pc); -void phpDebuggerExceptionHook(ObjectData* e); +void phpDebuggerExceptionThrownHook(ObjectData* e); +void phpDebuggerExceptionHandlerHook(); void phpDebuggerEvalHook(const Func* f); void phpDebuggerFileLoadHook(Eval::PhpFile* efile); class Class; diff --git a/hphp/test/quick/debugger/flow_excep.php b/hphp/test/quick/debugger/flow_excep.php new file mode 100644 index 000000000..03cbadf8b --- /dev/null +++ b/hphp/test/quick/debugger/flow_excep.php @@ -0,0 +1,64 @@ +getMessage()); + $x++; + } + return $x; +} + +function throwNoCatch($a) { + $x = $a + 1; + $e = new Ex1('Thrown from throwNoCatch '.$x); + throw $e; +} + +function throwFromCallee($a) { + $x = $a + 1; + try { + throwNoCatch($x); + } catch (Ex1 $e) { + printf("Caught %s in throwFromCallee()\n", $e->getMessage()); + $x++; + } + return $x; +} + +function foo($a) { + $x = $a + 1; + $x = throwAndCatch($x); + try { + $x = throwAndCatch($x); + throw new Ex1('Thrown from foo '.$x); + } catch (Exception $e) { + printf("Caught %s in foo()\n", $e->getMessage()); + $x = throwAndCatch($x); + $x++; + } + return $x; +} + +function main() { + $x = throwFromCallee(1); + var_dump($x); + + $x = foo(2); + var_dump($x); +} + +main(); + + diff --git a/hphp/test/quick/debugger/flow_excep.php.expectf b/hphp/test/quick/debugger/flow_excep.php.expectf new file mode 100644 index 000000000..09a5ad03a --- /dev/null +++ b/hphp/test/quick/debugger/flow_excep.php.expectf @@ -0,0 +1,469 @@ +Program %s/flow_excep.php loaded. Type '[r]un' or '[c]ontinue' to go. +break main() +Breakpoint 1 set upon entering main() +run +Breakpoint 1 reached at main() on line 55 of %s/flow_excep.php + 54 function main() { + 55 $x = throwFromCallee(1); + 56 var_dump($x); + +next +Caught Thrown from throwNoCatch 3 in throwFromCallee() +Break at main() on line 56 of %s/flow_excep.php + 55 $x = throwFromCallee(1); + 56 var_dump($x); + 57 + +next +int(3) +Break at main() on line 58 of %s/flow_excep.php + 57 + 58 $x = foo(2); + 59 var_dump($x); + +next +Caught Thrown from throwAndCatch 4 in throwAndCatch() +Caught Thrown from throwAndCatch 6 in throwAndCatch() +Caught Thrown from foo 7 in foo() +Caught Thrown from throwAndCatch 8 in throwAndCatch() +Break at main() on line 59 of %s/flow_excep.php + 58 $x = foo(2); + 59 var_dump($x); + 60 } + +continue +int(10) +Program %s/flow_excep.php exited normally. +run +Breakpoint 1 reached at main() on line 55 of %s/flow_excep.php + 54 function main() { + 55 $x = throwFromCallee(1); + 56 var_dump($x); + +step +Break at throwFromCallee() on line 30 of %s/flow_excep.php + 29 function throwFromCallee($a) { + 30 $x = $a + 1; + 31 try { + +out +Caught Thrown from throwNoCatch 3 in throwFromCallee() +Break at main() on line 55 of %s/flow_excep.php + 54 function main() { + 55 $x = throwFromCallee(1); + 56 var_dump($x); + +next 2 +int(3) +Break at main() on line 58 of %s/flow_excep.php + 57 + 58 $x = foo(2); + 59 var_dump($x); + +step +Break at foo() on line 41 of %s/flow_excep.php + 40 function foo($a) { + 41 $x = $a + 1; + 42 $x = throwAndCatch($x); + +next +Break at foo() on line 42 of %s/flow_excep.php + 41 $x = $a + 1; + 42 $x = throwAndCatch($x); + 43 try { + +step +Break at throwAndCatch() on line 13 of %s/flow_excep.php + 12 function throwAndCatch($a) { + 13 $x = $a + 1; + 14 try { + +out +Caught Thrown from throwAndCatch 4 in throwAndCatch() +Break at foo() on line 42 of %s/flow_excep.php + 41 $x = $a + 1; + 42 $x = throwAndCatch($x); + 43 try { + +out +Caught Thrown from throwAndCatch 6 in throwAndCatch() +Caught Thrown from foo 7 in foo() +Caught Thrown from throwAndCatch 8 in throwAndCatch() +Break at main() on line 58 of %s/flow_excep.php + 57 + 58 $x = foo(2); + 59 var_dump($x); + +continue +int(10) +Program %s/flow_excep.php exited normally. +run +Breakpoint 1 reached at main() on line 55 of %s/flow_excep.php + 54 function main() { + 55 $x = throwFromCallee(1); + 56 var_dump($x); + +step +Break at throwFromCallee() on line 30 of %s/flow_excep.php + 29 function throwFromCallee($a) { + 30 $x = $a + 1; + 31 try { + +next +Break at throwFromCallee() on line 32 of %s/flow_excep.php + 31 try { + 32 throwNoCatch($x); + 33 } catch (Ex1 $e) { + +next +Break at throwFromCallee() on line 31 of %s/flow_excep.php + 30 $x = $a + 1; + 31 try { + 32 throwNoCatch($x); + 33 } catch (Ex1 $e) { + 34 printf("Caught %s in throwFromCallee()\n", $e->getMessage()); + 35 $x++; + 36 } + 37 return $x; + +next +Break at throwFromCallee() on line 34 of %s/flow_excep.php + 33 } catch (Ex1 $e) { + 34 printf("Caught %s in throwFromCallee()\n", $e->getMessage()); + 35 $x++; + +next +Caught Thrown from throwNoCatch 3 in throwFromCallee() +Break at throwFromCallee() on line 35 of %s/flow_excep.php + 34 printf("Caught %s in throwFromCallee()\n", $e->getMessage()); + 35 $x++; + 36 } + +continue +int(3) +Caught Thrown from throwAndCatch 4 in throwAndCatch() +Caught Thrown from throwAndCatch 6 in throwAndCatch() +Caught Thrown from foo 7 in foo() +Caught Thrown from throwAndCatch 8 in throwAndCatch() +int(10) +Program %s/flow_excep.php exited normally. +run +Breakpoint 1 reached at main() on line 55 of %s/flow_excep.php + 54 function main() { + 55 $x = throwFromCallee(1); + 56 var_dump($x); + +step +Break at throwFromCallee() on line 30 of %s/flow_excep.php + 29 function throwFromCallee($a) { + 30 $x = $a + 1; + 31 try { + +next +Break at throwFromCallee() on line 32 of %s/flow_excep.php + 31 try { + 32 throwNoCatch($x); + 33 } catch (Ex1 $e) { + +step +Break at throwNoCatch() on line 24 of %s/flow_excep.php + 23 function throwNoCatch($a) { + 24 $x = $a + 1; + 25 $e = new Ex1('Thrown from throwNoCatch '.$x); + +out +Break at throwFromCallee() on line 31 of %s/flow_excep.php + 30 $x = $a + 1; + 31 try { + 32 throwNoCatch($x); + 33 } catch (Ex1 $e) { + 34 printf("Caught %s in throwFromCallee()\n", $e->getMessage()); + 35 $x++; + 36 } + 37 return $x; + +next +Break at throwFromCallee() on line 34 of %s/flow_excep.php + 33 } catch (Ex1 $e) { + 34 printf("Caught %s in throwFromCallee()\n", $e->getMessage()); + 35 $x++; + +out +Caught Thrown from throwNoCatch 3 in throwFromCallee() +Break at main() on line 55 of %s/flow_excep.php + 54 function main() { + 55 $x = throwFromCallee(1); + 56 var_dump($x); + +continue +int(3) +Caught Thrown from throwAndCatch 4 in throwAndCatch() +Caught Thrown from throwAndCatch 6 in throwAndCatch() +Caught Thrown from foo 7 in foo() +Caught Thrown from throwAndCatch 8 in throwAndCatch() +int(10) +Program %s/flow_excep.php exited normally. +run +Breakpoint 1 reached at main() on line 55 of %s/flow_excep.php + 54 function main() { + 55 $x = throwFromCallee(1); + 56 var_dump($x); + +step +Break at throwFromCallee() on line 30 of %s/flow_excep.php + 29 function throwFromCallee($a) { + 30 $x = $a + 1; + 31 try { + +next +Break at throwFromCallee() on line 32 of %s/flow_excep.php + 31 try { + 32 throwNoCatch($x); + 33 } catch (Ex1 $e) { + +step +Break at throwNoCatch() on line 24 of %s/flow_excep.php + 23 function throwNoCatch($a) { + 24 $x = $a + 1; + 25 $e = new Ex1('Thrown from throwNoCatch '.$x); + +next +Break at throwNoCatch() on line 25 of %s/flow_excep.php + 24 $x = $a + 1; + 25 $e = new Ex1('Thrown from throwNoCatch '.$x); + 26 throw $e; + +next +Break at throwNoCatch() on line 26 of %s/flow_excep.php + 25 $e = new Ex1('Thrown from throwNoCatch '.$x); + 26 throw $e; + 27 } + +step +Break at throwFromCallee() on line 31 of %s/flow_excep.php + 30 $x = $a + 1; + 31 try { + 32 throwNoCatch($x); + 33 } catch (Ex1 $e) { + 34 printf("Caught %s in throwFromCallee()\n", $e->getMessage()); + 35 $x++; + 36 } + 37 return $x; + +step +Break at throwFromCallee() on line 34 of %s/flow_excep.php + 33 } catch (Ex1 $e) { + 34 printf("Caught %s in throwFromCallee()\n", $e->getMessage()); + 35 $x++; + +continue +Caught Thrown from throwNoCatch 3 in throwFromCallee() +int(3) +Caught Thrown from throwAndCatch 4 in throwAndCatch() +Caught Thrown from throwAndCatch 6 in throwAndCatch() +Caught Thrown from foo 7 in foo() +Caught Thrown from throwAndCatch 8 in throwAndCatch() +int(10) +Program %s/flow_excep.php exited normally. +run +Breakpoint 1 reached at main() on line 55 of %s/flow_excep.php + 54 function main() { + 55 $x = throwFromCallee(1); + 56 var_dump($x); + +next 2 +Caught Thrown from throwNoCatch 3 in throwFromCallee() +int(3) +Break at main() on line 58 of %s/flow_excep.php + 57 + 58 $x = foo(2); + 59 var_dump($x); + +step +Break at foo() on line 41 of %s/flow_excep.php + 40 function foo($a) { + 41 $x = $a + 1; + 42 $x = throwAndCatch($x); + +next 2 +Caught Thrown from throwAndCatch 4 in throwAndCatch() +Break at foo() on line 44 of %s/flow_excep.php + 43 try { + 44 $x = throwAndCatch($x); + 45 throw new Ex1('Thrown from foo '.$x); + +step +Break at throwAndCatch() on line 13 of %s/flow_excep.php + 12 function throwAndCatch($a) { + 13 $x = $a + 1; + 14 try { + +next +Break at throwAndCatch() on line 15 of %s/flow_excep.php + 14 try { + 15 throw new Ex2('Thrown from throwAndCatch '.$x); + 16 } catch (Ex2 $e) { + +next +Break at throwAndCatch() on line 14 of %s/flow_excep.php + 13 $x = $a + 1; + 14 try { + 15 throw new Ex2('Thrown from throwAndCatch '.$x); + 16 } catch (Ex2 $e) { + 17 printf("Caught %s in throwAndCatch()\n", $e->getMessage()); + 18 $x++; + 19 } + 20 return $x; + +next +Break at throwAndCatch() on line 17 of %s/flow_excep.php + 16 } catch (Ex2 $e) { + 17 printf("Caught %s in throwAndCatch()\n", $e->getMessage()); + 18 $x++; + +out +Caught Thrown from throwAndCatch 6 in throwAndCatch() +Break at foo() on line 44 of %s/flow_excep.php + 43 try { + 44 $x = throwAndCatch($x); + 45 throw new Ex1('Thrown from foo '.$x); + +next +Break at foo() on line 45 of %s/flow_excep.php + 44 $x = throwAndCatch($x); + 45 throw new Ex1('Thrown from foo '.$x); + 46 } catch (Exception $e) { + +next +Break at foo() on line 43 of %s/flow_excep.php + 42 $x = throwAndCatch($x); + 43 try { + 44 $x = throwAndCatch($x); + 45 throw new Ex1('Thrown from foo '.$x); + 46 } catch (Exception $e) { + 47 printf("Caught %s in foo()\n", $e->getMessage()); + 48 $x = throwAndCatch($x); + 49 $x++; + 50 } + 51 return $x; + +next +Break at foo() on line 47 of %s/flow_excep.php + 46 } catch (Exception $e) { + 47 printf("Caught %s in foo()\n", $e->getMessage()); + 48 $x = throwAndCatch($x); + +out +Caught Thrown from foo 7 in foo() +Caught Thrown from throwAndCatch 8 in throwAndCatch() +Break at main() on line 58 of %s/flow_excep.php + 57 + 58 $x = foo(2); + 59 var_dump($x); + +continue +int(10) +Program %s/flow_excep.php exited normally. +run +Breakpoint 1 reached at main() on line 55 of %s/flow_excep.php + 54 function main() { + 55 $x = throwFromCallee(1); + 56 var_dump($x); + +next 2 +Caught Thrown from throwNoCatch 3 in throwFromCallee() +int(3) +Break at main() on line 58 of %s/flow_excep.php + 57 + 58 $x = foo(2); + 59 var_dump($x); + +step +Break at foo() on line 41 of %s/flow_excep.php + 40 function foo($a) { + 41 $x = $a + 1; + 42 $x = throwAndCatch($x); + +next 6 +Caught Thrown from throwAndCatch 4 in throwAndCatch() +Caught Thrown from throwAndCatch 6 in throwAndCatch() +Caught Thrown from foo 7 in foo() +Break at foo() on line 48 of %s/flow_excep.php + 47 printf("Caught %s in foo()\n", $e->getMessage()); + 48 $x = throwAndCatch($x); + 49 $x++; + +next +Caught Thrown from throwAndCatch 8 in throwAndCatch() +Break at foo() on line 49 of %s/flow_excep.php + 48 $x = throwAndCatch($x); + 49 $x++; + 50 } + +continue +int(10) +Program %s/flow_excep.php exited normally. +run +Breakpoint 1 reached at main() on line 55 of %s/flow_excep.php + 54 function main() { + 55 $x = throwFromCallee(1); + 56 var_dump($x); + +next 2 +Caught Thrown from throwNoCatch 3 in throwFromCallee() +int(3) +Break at main() on line 58 of %s/flow_excep.php + 57 + 58 $x = foo(2); + 59 var_dump($x); + +step +Break at foo() on line 41 of %s/flow_excep.php + 40 function foo($a) { + 41 $x = $a + 1; + 42 $x = throwAndCatch($x); + +next 6 +Caught Thrown from throwAndCatch 4 in throwAndCatch() +Caught Thrown from throwAndCatch 6 in throwAndCatch() +Caught Thrown from foo 7 in foo() +Break at foo() on line 48 of %s/flow_excep.php + 47 printf("Caught %s in foo()\n", $e->getMessage()); + 48 $x = throwAndCatch($x); + 49 $x++; + +step +Break at throwAndCatch() on line 13 of %s/flow_excep.php + 12 function throwAndCatch($a) { + 13 $x = $a + 1; + 14 try { + +next +Break at throwAndCatch() on line 15 of %s/flow_excep.php + 14 try { + 15 throw new Ex2('Thrown from throwAndCatch '.$x); + 16 } catch (Ex2 $e) { + +out +Caught Thrown from throwAndCatch 8 in throwAndCatch() +Break at foo() on line 48 of %s/flow_excep.php + 47 printf("Caught %s in foo()\n", $e->getMessage()); + 48 $x = throwAndCatch($x); + 49 $x++; + +next +Break at foo() on line 49 of %s/flow_excep.php + 48 $x = throwAndCatch($x); + 49 $x++; + 50 } + +continue +int(10) +Program %s/flow_excep.php exited normally. +run +Breakpoint 1 reached at main() on line 55 of %s/flow_excep.php + 54 function main() { + 55 $x = throwFromCallee(1); + 56 var_dump($x); + +quit diff --git a/hphp/test/quick/debugger/flow_excep.php.in b/hphp/test/quick/debugger/flow_excep.php.in new file mode 100644 index 000000000..a3f5a7063 --- /dev/null +++ b/hphp/test/quick/debugger/flow_excep.php.in @@ -0,0 +1,71 @@ +break main() +run +next +next +next +continue +run +step +out +next 2 +step +next +step +out +out +continue +run +step +next +next +next +next +continue +run +step +next +step +out +next +out +continue +run +step +next +step +next +next +step +step +continue +run +next 2 +step +next 2 +step +next +next +next +out +next +next +next +out +continue +run +next 2 +step +next 6 +next +continue +run +next 2 +step +next 6 +step +next +out +next +continue +run +quit diff --git a/hphp/test/quick/debugger/flow_excep.php.norepo b/hphp/test/quick/debugger/flow_excep.php.norepo new file mode 100644 index 000000000..e69de29bb diff --git a/hphp/test/quick/debugger/flow_excep.php.opts b/hphp/test/quick/debugger/flow_excep.php.opts new file mode 100644 index 000000000..cf014a32f --- /dev/null +++ b/hphp/test/quick/debugger/flow_excep.php.opts @@ -0,0 +1 @@ + -m debug \ No newline at end of file diff --git a/hphp/test/quick/debugger/flow_gen_excep.php b/hphp/test/quick/debugger/flow_gen_excep.php new file mode 100644 index 000000000..2c88a6eeb --- /dev/null +++ b/hphp/test/quick/debugger/flow_gen_excep.php @@ -0,0 +1,41 @@ +getMessage()); + } + yield $a+1; + error_log('Finished in genFoo'); +} + +function foo($a) { + $gen1 = genFoo($a); + $gen1->next(); + while ($gen1->valid()) { + $val = $gen1->current(); + var_dump($val); + try { + $gen1->raise(new Exception('Exception given to continuation '.$val)); + } catch (Exception $e) { + printf("Caught %s in foo()\n", $e->getMessage()); + } + } +} + +function main($a) { + foo($a); +} + +main(1); + diff --git a/hphp/test/quick/debugger/flow_gen_excep.php.expectf b/hphp/test/quick/debugger/flow_gen_excep.php.expectf new file mode 100644 index 000000000..a0ec209dd --- /dev/null +++ b/hphp/test/quick/debugger/flow_gen_excep.php.expectf @@ -0,0 +1,102 @@ +Program %s/flow_gen_excep.php loaded. Type '[r]un' or '[c]ontinue' to go. +run +int(8) +Caught Exception given to continuation 8 in genFoo() +int(4) +Caught Exception given to continuation 4 in foo() +Program %s/flow_gen_excep.php exited normally. +break foo() +Breakpoint 1 set upon entering foo() +next +Program %s/flow_gen_excep.php loaded. Type '[r]un' or '[c]ontinue' to go. +next +Breakpoint 1 reached at foo() on line 23 of %s/flow_gen_excep.php + 22 function foo($a) { + 23 $gen1 = genFoo($a); + 24 $gen1->next(); + +next +Break at foo() on line 24 of %s/flow_gen_excep.php + 23 $gen1 = genFoo($a); + 24 $gen1->next(); + 25 while ($gen1->valid()) { + +next +Break at foo() on line 25 of %s/flow_gen_excep.php + 24 $gen1->next(); + 25 while ($gen1->valid()) { + 26 $val = $gen1->current(); + +next +Break at foo() on line 26 of %s/flow_gen_excep.php + 25 while ($gen1->valid()) { + 26 $val = $gen1->current(); + 27 var_dump($val); + +next +Break at foo() on line 27 of %s/flow_gen_excep.php + 26 $val = $gen1->current(); + 27 var_dump($val); + 28 try { + +next +int(8) +Break at foo() on line 29 of %s/flow_gen_excep.php + 28 try { + 29 $gen1->raise(new Exception('Exception given to continuation '.$val)); + 30 } catch (Exception $e) { + +next +Caught Exception given to continuation 8 in genFoo() +Break at foo() on line 25 of %s/flow_gen_excep.php + 24 $gen1->next(); + 25 while ($gen1->valid()) { + 26 $val = $gen1->current(); + +next +Break at foo() on line 26 of %s/flow_gen_excep.php + 25 while ($gen1->valid()) { + 26 $val = $gen1->current(); + 27 var_dump($val); + +next +Break at foo() on line 27 of %s/flow_gen_excep.php + 26 $val = $gen1->current(); + 27 var_dump($val); + 28 try { + +next +int(4) +Break at foo() on line 29 of %s/flow_gen_excep.php + 28 try { + 29 $gen1->raise(new Exception('Exception given to continuation '.$val)); + 30 } catch (Exception $e) { + +next +Break at foo() on line 28 of %s/flow_gen_excep.php + 27 var_dump($val); + 28 try { + 29 $gen1->raise(new Exception('Exception given to continuation '.$val)); + 30 } catch (Exception $e) { + 31 printf("Caught %s in foo()\n", $e->getMessage()); + 32 } + 33 } + +next +Break at foo() on line 31 of %s/flow_gen_excep.php + 30 } catch (Exception $e) { + 31 printf("Caught %s in foo()\n", $e->getMessage()); + 32 } + +next +Caught Exception given to continuation 4 in foo() +Break at foo() on line 25 of %s/flow_gen_excep.php + 24 $gen1->next(); + 25 while ($gen1->valid()) { + 26 $val = $gen1->current(); + +break clear all +All breakpoints are cleared. +continue +Program %s/flow_gen_excep.php exited normally. +quit diff --git a/hphp/test/quick/debugger/flow_gen_excep.php.in b/hphp/test/quick/debugger/flow_gen_excep.php.in new file mode 100644 index 000000000..114b0e600 --- /dev/null +++ b/hphp/test/quick/debugger/flow_gen_excep.php.in @@ -0,0 +1,19 @@ +run +break foo() +next +next +next +next +next +next +next +next +next +next +next +next +next +next +break clear all +continue +quit diff --git a/hphp/test/quick/debugger/flow_gen_excep.php.norepo b/hphp/test/quick/debugger/flow_gen_excep.php.norepo new file mode 100644 index 000000000..e69de29bb diff --git a/hphp/test/quick/debugger/flow_gen_excep.php.opts b/hphp/test/quick/debugger/flow_gen_excep.php.opts new file mode 100644 index 000000000..cf014a32f --- /dev/null +++ b/hphp/test/quick/debugger/flow_gen_excep.php.opts @@ -0,0 +1 @@ + -m debug \ No newline at end of file