大师兄的Python源码学习笔记(十八): 虚拟机中的控制流(五

2021-05-28  本文已影响0人  superkmi

大师兄的Python源码学习笔记(十七): 虚拟机中的控制流(四)
大师兄的Python源码学习笔记(十九): 虚拟机中的函数机制(一)

四、虚拟机中的异常控制流

2 异常控制语义结构

demo.py

try:
    raise Exception("an exception")
except Exception as e:
    ...
finally:
    ...
  1           0 SETUP_FINALLY           52 (to 54)
              2 SETUP_EXCEPT            12 (to 16)

  2           4 LOAD_NAME                0 (Exception)
              6 LOAD_CONST               0 ('an exception')
              8 CALL_FUNCTION            1
             10 RAISE_VARARGS            1
             12 POP_BLOCK
             14 JUMP_FORWARD            34 (to 50)

  3     >>   16 DUP_TOP
             18 LOAD_NAME                0 (Exception)
             20 COMPARE_OP              10 (exception match)
             22 POP_JUMP_IF_FALSE       48
             24 POP_TOP
             26 STORE_NAME               1 (e)
             28 POP_TOP
             30 SETUP_FINALLY            4 (to 36)

  4          32 POP_BLOCK
             34 LOAD_CONST               1 (None)
        >>   36 LOAD_CONST               1 (None)
             38 STORE_NAME               1 (e)
             40 DELETE_NAME              1 (e)
             42 END_FINALLY
             44 POP_EXCEPT
             46 JUMP_FORWARD             2 (to 50)
        >>   48 END_FINALLY
        >>   50 POP_BLOCK
             52 LOAD_CONST               1 (None)

  6     >>   54 END_FINALLY
             56 LOAD_CONST               1 (None)
             58 RETURN_VALUE
ceval.c

        TARGET(SETUP_LOOP)
        TARGET(SETUP_EXCEPT)
        TARGET(SETUP_FINALLY) {
            /* NOTE: If you add any new block-setup opcodes that
               are not try/except/finally handlers, you may need
               to update the PyGen_NeedsFinalizing() function.
               */

            PyFrame_BlockSetup(f, opcode, INSTR_OFFSET() + oparg,
                               STACK_LEVEL());
            DISPATCH();
        }
Objects\frameobject.c

void
PyFrame_BlockSetup(PyFrameObject *f, int type, int handler, int level)
{
    PyTryBlock *b;
    if (f->f_iblock >= CO_MAXBLOCKS)
        Py_FatalError("XXX block stack overflow");
    b = &f->f_blockstack[f->f_iblock++];
    b->b_type = type;
    b->b_level = level;
    b->b_handler = handler;
}
  • 从上面的代码可以看出PyFrame_BlockSetup对当前PyFrameObject进行了配置:
Objects\frameobject.c

typedef struct _frame {
   ... ...
   PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
   ... ...
} PyFrameObject;
Objects\frameobject.c

typedef struct {
   int b_type;                 /* what kind of block this is */
   int b_handler;              /* where to jump to find handler */
   int b_level;                /* value stack level to pop to */
} PyTryBlock;
ceval.c

TARGET(RAISE_VARARGS) {
            PyObject *cause = NULL, *exc = NULL;
            switch (oparg) {
            case 2:
                cause = POP(); /* cause */
                /* fall through */
            case 1:
                exc = POP(); /* exc */
                /* fall through */
            case 0:
                if (do_raise(exc, cause)) {
                    why = WHY_EXCEPTION;
                    goto fast_block_end;
                }
                break;
            default:
                PyErr_SetString(PyExc_SystemError,
                           "bad RAISE_VARARGS oparg");
                break;
            }
            goto error;
        }
  • 这里oparg参数为1,所以将异常对象取出赋值给w,接着运行do_raise函数:
ceval.c

static int
do_raise(PyObject *exc, PyObject *cause)
{
   PyObject *type = NULL, *value = NULL;

   if (exc == NULL) {
       /* Reraise */
       PyThreadState *tstate = PyThreadState_GET();
       _PyErr_StackItem *exc_info = _PyErr_GetTopmostException(tstate);
       PyObject *tb;
       type = exc_info->exc_type;
       value = exc_info->exc_value;
       tb = exc_info->exc_traceback;
       if (type == Py_None || type == NULL) {
           PyErr_SetString(PyExc_RuntimeError,
                           "No active exception to reraise");
           return 0;
       }
       Py_XINCREF(type);
       Py_XINCREF(value);
       Py_XINCREF(tb);
       PyErr_Restore(type, value, tb);
       return 1;
   }

   /* We support the following forms of raise:
      raise
      raise <instance>
      raise <type> */

   if (PyExceptionClass_Check(exc)) {
       type = exc;
       value = _PyObject_CallNoArg(exc);
       if (value == NULL)
           goto raise_error;
       if (!PyExceptionInstance_Check(value)) {
           PyErr_Format(PyExc_TypeError,
                        "calling %R should have returned an instance of "
                        "BaseException, not %R",
                        type, Py_TYPE(value));
           goto raise_error;
       }
   }
   else if (PyExceptionInstance_Check(exc)) {
       value = exc;
       type = PyExceptionInstance_Class(exc);
       Py_INCREF(type);
   }
   else {
       /* Not something you can raise.  You get an exception
          anyway, just not what you specified :-) */
       Py_DECREF(exc);
       PyErr_SetString(PyExc_TypeError,
                       "exceptions must derive from BaseException");
       goto raise_error;
   }

   assert(type != NULL);
   assert(value != NULL);

   if (cause) {
       PyObject *fixed_cause;
       if (PyExceptionClass_Check(cause)) {
           fixed_cause = _PyObject_CallNoArg(cause);
           if (fixed_cause == NULL)
               goto raise_error;
           Py_DECREF(cause);
       }
       else if (PyExceptionInstance_Check(cause)) {
           fixed_cause = cause;
       }
       else if (cause == Py_None) {
           Py_DECREF(cause);
           fixed_cause = NULL;
       }
       else {
           PyErr_SetString(PyExc_TypeError,
                           "exception causes must derive from "
                           "BaseException");
           goto raise_error;
       }
       PyException_SetCause(value, fixed_cause);
   }

   PyErr_SetObject(type, value);
   /* PyErr_SetObject incref's its arguments */
   Py_DECREF(value);
   Py_DECREF(type);
   return 0;

raise_error:
   Py_XDECREF(value);
   Py_XDECREF(type);
   Py_XDECREF(cause);
   return 0;
}
  • do_raise将异常对象存储到当前线程状态中,并在结束后跳到fast_block_end
ceval.c

fast_block_end:
       assert(why != WHY_NOT);

       /* Unwind stacks if a (pseudo) exception occurred */
       while (why != WHY_NOT && f->f_iblock > 0) {
           /* Peek at the current block. */
           PyTryBlock *b = &f->f_blockstack[f->f_iblock - 1];

           assert(why != WHY_YIELD);
           if (b->b_type == SETUP_LOOP && why == WHY_CONTINUE) {
               why = WHY_NOT;
               JUMPTO(PyLong_AS_LONG(retval));
               Py_DECREF(retval);
               break;
           }
           /* Now we have to pop the block. */
           f->f_iblock--;

           if (b->b_type == EXCEPT_HANDLER) {
               UNWIND_EXCEPT_HANDLER(b);
               continue;
           }
           UNWIND_BLOCK(b);
           if (b->b_type == SETUP_LOOP && why == WHY_BREAK) {
               why = WHY_NOT;
               JUMPTO(b->b_handler);
               break;
           }
           if (why == WHY_EXCEPTION && (b->b_type == SETUP_EXCEPT
               || b->b_type == SETUP_FINALLY)) {
               PyObject *exc, *val, *tb;
               int handler = b->b_handler;
               _PyErr_StackItem *exc_info = tstate->exc_info;
               /* Beware, this invalidates all b->b_* fields */
               PyFrame_BlockSetup(f, EXCEPT_HANDLER, -1, STACK_LEVEL());
               PUSH(exc_info->exc_traceback);
               PUSH(exc_info->exc_value);
               if (exc_info->exc_type != NULL) {
                   PUSH(exc_info->exc_type);
               }
               else {
                   Py_INCREF(Py_None);
                   PUSH(Py_None);
               }
               PyErr_Fetch(&exc, &val, &tb);
               /* Make the raw exception data
                  available to the handler,
                  so a program can emulate the
                  Python main loop. */
               PyErr_NormalizeException(
                   &exc, &val, &tb);
               if (tb != NULL)
                   PyException_SetTraceback(val, tb);
               else
                   PyException_SetTraceback(val, Py_None);
               Py_INCREF(exc);
               exc_info->exc_type = exc;
               Py_INCREF(val);
               exc_info->exc_value = val;
               exc_info->exc_traceback = tb;
               if (tb == NULL)
                   tb = Py_None;
               Py_INCREF(tb);
               PUSH(tb);
               PUSH(val);
               PUSH(exc);
               why = WHY_NOT;
               JUMPTO(handler);
               break;
           }
           if (b->b_type == SETUP_FINALLY) {
               if (why & (WHY_RETURN | WHY_CONTINUE))
                   PUSH(retval);
               PUSH(PyLong_FromLong((long)why));
               why = WHY_NOT;
               JUMPTO(b->b_handler);
               break;
           }
       } /* unwind stack */

       /* End the loop if we still have an error (or return) */

       if (why != WHY_NOT)
           break;

       assert(!PyErr_Occurred());

   } /* main loop */

   assert(why != WHY_YIELD);
   /* Pop remaining stack entries. */
   while (!EMPTY()) {
       PyObject *o = POP();
       Py_XDECREF(o);
   }

   if (why != WHY_RETURN)
       retval = NULL;

   assert((retval != NULL) ^ (PyErr_Occurred() != NULL));
  • 在这里,虚拟机首先从当前PyFrameObject对象中的f_blockstack弹出一个PyTryBlock来。
  • 接着虚拟机通过PyErr_Fetch获取当前线程状态对象中存储的最新异常对象和traceback对象。
ceval.c

void
PyErr_Fetch(PyObject **p_type, PyObject **p_value, PyObject **p_traceback)
{
   PyThreadState *tstate = PyThreadState_GET();

   *p_type = tstate->curexc_type;
   *p_value = tstate->curexc_value;
   *p_traceback = tstate->curexc_traceback;

   tstate->curexc_type = NULL;
   tstate->curexc_value = NULL;
   tstate->curexc_traceback = NULL;
}
  • 随后,虚拟机将tb、val和exc压入到运行时栈中,并将why设置为WHY_NOT。
  • 最后通过JUMPTO(b->b_handler)将要执行的吓一跳指令设置为异常处理代码编译后所得到的第一条字节码指令。
ceval.c

        TARGET(DUP_TOP) {
            PyObject *top = TOP();
            Py_INCREF(top);
            PUSH(top);
            FAST_DISPATCH();
        }
ceval.c

        TARGET(COMPARE_OP) {
            PyObject *right = POP();
            PyObject *left = TOP();
            PyObject *res = cmp_outcome(oparg, left, right);
            Py_DECREF(left);
            Py_DECREF(right);
            SET_TOP(res);
            if (res == NULL)
                goto error;
            PREDICT(POP_JUMP_IF_FALSE);
            PREDICT(POP_JUMP_IF_TRUE);
            DISPATCH();
        }
ceval.c

            if (opcode == SETUP_FINALLY ||
                opcode == SETUP_WITH ||
                opcode == BEFORE_ASYNC_WITH ||
                opcode == YIELD_FROM) {
                /* Few cases where we skip running signal handlers and other
                   pending calls:
                   - If we're about to enter the 'with:'. It will prevent
                     emitting a resource warning in the common idiom
                     'with open(path) as file:'.
                   - If we're about to enter the 'async with:'.
                   - If we're about to enter the 'try:' of a try/finally (not
                     *very* useful, but might help in some cases and it's
                     traditional)
                   - If we're resuming a chain of nested 'yield from' or
                     'await' calls, then each frame is parked with YIELD_FROM
                     as its next opcode. If the user hit control-C we want to
                     wait until we've reached the innermost frame before
                     running the signal handler and raising KeyboardInterrupt
                     (see bpo-30039).
                */
                goto fast_next_opcode;
            }
            if (_Py_atomic_load_relaxed(
                        &_PyRuntime.ceval.pending.calls_to_do))
            {
                if (Py_MakePendingCalls() < 0)
                    goto error;
            }
            if (_Py_atomic_load_relaxed(
                        &_PyRuntime.ceval.gil_drop_request))
            {
                /* Give another thread a chance */
                if (PyThreadState_Swap(NULL) != tstate)
                    Py_FatalError("ceval: tstate mix-up");
                drop_gil(tstate);

                /* Other threads may run now */

                take_gil(tstate);

                /* Check if we should make a quick exit. */
                if (_Py_IsFinalizing() &&
                    !_Py_CURRENTLY_FINALIZING(tstate))
                {
                    drop_gil(tstate);
                    PyThread_exit_thread();
                }

                if (PyThreadState_Swap(tstate) != NULL)
                    Py_FatalError("ceval: orphan tstate");
            }
            /* Check for asynchronous exceptions. */
            if (tstate->async_exc != NULL) {
                PyObject *exc = tstate->async_exc;
                tstate->async_exc = NULL;
                UNSIGNAL_ASYNC_EXC();
                PyErr_SetNone(exc);
                Py_DECREF(exc);
                goto error;
            }
        }
  • 从上面的代码的结尾处,重新跳转到error设置了why。
ceval.c

        PREDICTED(POP_BLOCK);
        TARGET(POP_BLOCK) {
            PyTryBlock *b = PyFrame_BlockPop(f);
            UNWIND_BLOCK(b);
            DISPATCH();
        }
上一篇 下一篇

猜你喜欢

热点阅读