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:
@@ -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()) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
}}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Referência em uma Nova Issue
Bloquear um usuário