If sub A pushes an error handler and then calls B, B can do a 'clear_eh' to get rid of A's handler. This seems to work until B returns, at which point the control stack unwinding done by RetContinuation destroys the rest of the stack looking for the missing handler. The patch detects the problem in clear_eh, and signals a real_exception.
Perhaps more controversially, it also makes it an error if clear_eh finds something other than an exception at TOS. The original code did nothing, which doesn't seem right. Error handlers are lexical features of the source code, as are most other things on the control stack [1], so finding a non-exception at TOS indicates a compiler bug (or a logic error in hand-written PIR). Fortunately, this doesn't affect any of the existing test cases -- so I added a new one to test regression. You could also argue that the stack unwinding in RetContinuation.invoke is broken. I tend to agree, but any such fix would be superceded by an implemention of rezipping. This pop_exception fix might also have to change, but probably only in detail. -- Bob [1] The sole exception is the return address used by the bsr/jsr and ret instructions; these aren't bound to any lexical features of the compiled language.
Index: src/ops/core.ops =================================================================== --- src/ops/core.ops (revision 10945) +++ src/ops/core.ops (working copy) @@ -645,12 +645,16 @@ =item B<push_eh>(labelconst INT) -Create an exception handler for the given catch label and push it onto -the control stack. +Create an exception handler that transfers control to the specified +catch label and push it onto the control stack. Such handlers remain +in effect in the current dynamic context, and are popped automatically +on exit. =item B<clear_eh>() -Clear out the most recently placed exception. +Remove the exception on the top of the control stack. A "No exception +to pop" error is signalled if the top of the stack is not an +exception, or the exception does not belong to the current context. =item B<throw>(in PMC) Index: src/exceptions.c =================================================================== --- src/exceptions.c (revision 10945) +++ src/exceptions.c (working copy) @@ -327,12 +327,22 @@ { Stack_entry_type type; PMC *handler; + parrot_context_t *current_ctx = CONTEXT(interpreter->ctx); + parrot_context_t *prev_ctx = current_ctx->prev; + Stack_Chunk_t *prev_base = (prev_ctx ? prev_ctx->control_stack : NULL); - handler = stack_peek(interpreter, CONTEXT(interpreter->ctx)->control_stack, &type); + if (prev_base == current_ctx->control_stack) { + real_exception(interpreter, NULL, INVALID_OPERATION, + "No exception to pop."); + } + handler = stack_peek(interpreter, current_ctx->control_stack, &type); if (type != STACK_ENTRY_PMC || - handler->vtable->base_type != enum_class_Exception_Handler) - return; /* no exception on TOS */ - (void)stack_pop(interpreter, &CONTEXT(interpreter->ctx)->control_stack, NULL, + handler->vtable->base_type != enum_class_Exception_Handler) { + /* no exception on TOS */ + real_exception(interpreter, NULL, INVALID_OPERATION, + "No exception to pop."); + } + (void)stack_pop(interpreter, ¤t_ctx->control_stack, NULL, STACK_ENTRY_PMC); } Index: t/pmc/exception.t =================================================================== --- t/pmc/exception.t (revision 10945) +++ t/pmc/exception.t (working copy) @@ -6,7 +6,7 @@ use warnings; use lib qw( . lib ../lib ../../lib ); use Test::More; -use Parrot::Test tests => 26; +use Parrot::Test tests => 28; =head1 NAME @@ -591,3 +591,42 @@ Error: something happened Outer value OUTPUT + +pir_output_like(<<'CODE', <<'OUTPUT', 'clear_eh out of context (1)'); +.sub main :main + pushmark 1 + clear_eh + print "no exceptions.\n" +.end +CODE +/No exception to pop./ +OUTPUT + +pir_output_is(<<'CODE', <<'OUTPUT', 'clear_eh out of context (2)'); +.sub main :main + .local pmc outer, cont + push_eh handler + test1() + print "skipped.\n" + goto done +handler: + .local pmc exception + .get_results (exception) + print "Error: " + print exception + print "\n" +done: + print "done.\n" +.end +.sub test1 + .local pmc exit + print "[in test1]\n" + ## clear_eh is illegal here, and signals an exception. + clear_eh + print "[cleared]\n" +.end +CODE +[in test1] +Error: No exception to pop. +done. +OUTPUT