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.
Esse commit está contido em:
Mike Magruder
2013-06-07 10:29:16 -07:00
commit de sgolemon
commit 96d6bab291
22 arquivos alterados com 880 adições e 31 exclusões
+1
Ver Arquivo
@@ -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()) {
+6
Ver Arquivo
@@ -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.
@@ -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;
+59 -15
Ver Arquivo
@@ -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<c_Continuation*>(tv->m_data.pobj));
c_Continuation* cont = static_cast<c_Continuation*>(tv->m_data.pobj);
TRACE(2, "CmdNext: continuation tag %p for %s\n", cont,
cont->t_getorigfuncname()->data());
return cont;
}
///////////////////////////////////////////////////////////////////////////////
}}
+4 -1
Ver Arquivo
@@ -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
};
///////////////////////////////////////////////////////////////////////////////
+13 -9
Ver Arquivo
@@ -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;
}
///////////////////////////////////////////////////////////////////////////////
+3
Ver Arquivo
@@ -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());
+2
Ver Arquivo
@@ -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";
}
+1 -1
Ver Arquivo
@@ -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()) {
+3 -1
Ver Arquivo
@@ -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;
}
+15 -3
Ver Arquivo
@@ -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() {
+2 -1
Ver Arquivo
@@ -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;
+64
Ver Arquivo
@@ -0,0 +1,64 @@
<?php
// Copyright 2004-present Facebook. All Rights Reserved.
// Test flow control around exceptions. Stepping into/over/out of throws,
// catches, stepping over calls that have throw/catch within, etc.
class Ex1 extends Exception {
}
class Ex2 extends Exception {
}
function throwAndCatch($a) {
$x = $a + 1;
try {
throw new Ex2('Thrown from throwAndCatch '.$x);
} catch (Ex2 $e) {
printf("Caught %s in throwAndCatch()\n", $e->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();
@@ -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
+71
Ver Arquivo
@@ -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
@@ -0,0 +1 @@
-m debug
+41
Ver Arquivo
@@ -0,0 +1,41 @@
<?php
// Test stepping with exceptions in continuations. This is a very simple test
// which simply confirms that exceptions flowing around continuations don't
// disturb step over.
function bar($a) {
return $a + 2;
}
function genFoo($a) {
$a = bar($a);
try {
$z = yield $a+5;
$z++;
} catch (Exception $e) {
printf("Caught %s in genFoo()\n", $e->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);
@@ -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
@@ -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
@@ -0,0 +1 @@
-m debug