blueloveTH 14 часов назад
Родитель
Сommit
75f06868ba
4 измененных файлов с 73 добавлено и 10 удалено
  1. 6 1
      src/compiler/compiler.c
  2. 13 8
      src/interpreter/ceval.c
  3. 9 1
      src/interpreter/frame.c
  4. 45 0
      tests/280_exception.py

+ 6 - 1
src/compiler/compiler.c

@@ -2697,6 +2697,12 @@ static Error* compile_try_except(Compiler* self) {
     patches[patches_length++] = Ctx__emit_(ctx(), OP_JUMP_FORWARD, BC_NOARG, BC_KEEPLINE);
     Ctx__exit_block(ctx());
 
+    // Take the exception out of flight here, at the handler entry, before any
+    // `except <expr>` is evaluated. Otherwise an expression that raises (an
+    // undefined name, a call that fails) would raise while the original
+    // exception is still pending.
+    Ctx__emit_(ctx(), OP_HANDLE_EXCEPTION, BC_NOARG, BC_KEEPLINE);
+
     do {
         if(patches_length == 8) {
             return SyntaxError(self, "maximum number of except clauses reached");
@@ -2719,7 +2725,6 @@ static Error* compile_try_except(Compiler* self) {
         }
         int patch = Ctx__emit_(ctx(), OP_POP_JUMP_IF_FALSE, BC_NOARG, BC_KEEPLINE);
         // on match
-        Ctx__emit_(ctx(), OP_HANDLE_EXCEPTION, BC_NOARG, BC_KEEPLINE);
         if(as_name) {
             Ctx__emit_(ctx(), OP_PUSH_EXCEPTION, BC_NOARG, BC_KEEPLINE);
             Ctx__emit_store_name(ctx(), name_scope(self), as_name, BC_KEEPLINE);

+ 13 - 8
src/interpreter/ceval.c

@@ -1175,10 +1175,14 @@ __NEXT_STEP:
             DISPATCH();
         }
         case OP_EXCEPTION_MATCH: {
+            // OP_HANDLE_EXCEPTION at the handler entry already moved the
+            // exception into the frame, so nothing is in flight here
+            FrameExcInfo* info = Frame__top_exc_info(frame);
+            assert(info != NULL && !py_isnil(&info->exc));
             bool ok = false;
             bool has_invalid = false;
             if(TOP()->type == tp_type) {
-                ok = py_isinstance(&self->unhandled_exc, py_totype(TOP()));
+                ok = py_isinstance(&info->exc, py_totype(TOP()));
             } else if(TOP()->type == tp_tuple) {
                 int len = py_tuple_len(TOP());
                 py_ObjectRef data = py_tuple_data(TOP());
@@ -1190,7 +1194,7 @@ __NEXT_STEP:
                 }
                 if(!has_invalid) {
                     for(int i = 0; i < len; i++) {
-                        if(py_isinstance(&self->unhandled_exc, py_totype(data + i))) {
+                        if(py_isinstance(&info->exc, py_totype(data + i))) {
                             ok = true;
                             break;
                         }
@@ -1200,7 +1204,7 @@ __NEXT_STEP:
                 has_invalid = true;
             }
             if(has_invalid) {
-                py_newnil(&self->unhandled_exc);
+                // raise first, so `py_raise` can chain `info->exc`, then drop it
                 TypeError("catching classes that do not inherit from BaseException is not allowed");
                 c11_vector__pop(&frame->exc_stack);
                 goto __ERROR;
@@ -1240,11 +1244,12 @@ __NEXT_STEP:
             goto __ERROR;
         }
         case OP_RE_RAISE: {
-            if(py_isnil(&self->unhandled_exc)) {
-                FrameExcInfo* info = Frame__top_exc_info(frame);
-                assert(info != NULL && !py_isnil(&info->exc));
-                self->unhandled_exc = info->exc;
-            }
+            // OP_HANDLE_EXCEPTION at the handler entry took the exception out of
+            // flight, so the frame is the one holding it and we put it back
+            assert(py_isnil(&self->unhandled_exc));
+            FrameExcInfo* info = Frame__top_exc_info(frame);
+            assert(info != NULL && !py_isnil(&info->exc));
+            self->unhandled_exc = info->exc;
             c11_vector__pop(&frame->exc_stack);
             goto __ERROR_RE_RAISE;
         }

+ 9 - 1
src/interpreter/frame.c

@@ -64,7 +64,15 @@ int Frame__goto_exception_handler(py_Frame* self, ValueStack* value_stack, py_Re
     FrameExcInfo* p = self->exc_stack.data;
     for(int i = self->exc_stack.length - 1; i >= 0; i--) {
         CodeBlock* block = c11__at(CodeBlock, &self->co->blocks, p[i].iblock);
-        if(py_isnil(&p[i].exc) && self->ip >= block->start && self->ip < block->end) {
+        if(py_isnil(&p[i].exc)) {
+            // A nil `exc` means OP_HANDLE_EXCEPTION has not run for this block,
+            // i.e. we are still inside its `try` body, so it can take over.
+            // Anything raised from an `except <expr>` or from a handler body
+            // finds `exc` already set and falls through to the outer block.
+            // `ip == block->end` is the handler entry itself, which is still
+            // reachable: the watchdog checks for a timeout on the instruction
+            // boundary right before OP_HANDLE_EXCEPTION gets to run.
+            assert(self->ip >= block->start && self->ip <= block->end);
             value_stack->sp = (self->p0 + p[i].offset);  // unwind the stack
             return block->end;
         } else {

+ 45 - 0
tests/280_exception.py

@@ -308,3 +308,48 @@ def finally_return():
     
 assert finally_return() == 1
 """
+
+# An exception raised while evaluating an `except` clause must propagate to the
+# enclosing block, never be caught by the very handler that is being entered.
+def _boom():
+    raise TypeError('boom')
+
+# the clause expression itself raises
+try:
+    try:
+        x, y = [1]
+        exit(1)
+    except (IndexError, _boom()):
+        exit(1)
+except TypeError as e:
+    assert str(e) == 'boom'
+
+# a later clause is the one that fails
+try:
+    try:
+        x, y = [1]
+        exit(1)
+    except IndexError:
+        exit(1)
+    except undefinedbar:
+        exit(1)
+except NameError:
+    pass
+
+# a handler body that raises is not caught by its own try block either
+try:
+    try:
+        raise KeyError('k')
+    except KeyError:
+        raise IndexError('i')
+except IndexError as e:
+    assert str(e) == 'i'
+
+# a non-type in an `except` tuple is still a TypeError
+try:
+    try:
+        raise KeyError('k')
+    except (IndexError, 1):
+        exit(1)
+except TypeError:
+    pass