diff options
author | Irit Katriel <1055913+iritkatriel@users.noreply.github.com> | 2021-12-14 16:48:15 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-12-14 16:48:15 (GMT) |
commit | d60457a6673cf0263213c2f2be02c633ec2e2038 (patch) | |
tree | 04461db9079cf30a98c5a4070098f795275aa910 /Python/ceval.c | |
parent | 850aefc2c651110a784cd5478af9774b1f6287a3 (diff) | |
download | cpython-d60457a6673cf0263213c2f2be02c633ec2e2038.zip cpython-d60457a6673cf0263213c2f2be02c633ec2e2038.tar.gz cpython-d60457a6673cf0263213c2f2be02c633ec2e2038.tar.bz2 |
bpo-45292: [PEP-654] add except* (GH-29581)
Diffstat (limited to 'Python/ceval.c')
-rw-r--r-- | Python/ceval.c | 355 |
1 files changed, 347 insertions, 8 deletions
diff --git a/Python/ceval.c b/Python/ceval.c index 4f5ccf5..fb19f78 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -37,6 +37,7 @@ #include "structmember.h" // struct PyMemberDef, T_OFFSET_EX #include <ctype.h> +#include <stdbool.h> #ifdef Py_DEBUG /* For debugging the interpreter: */ @@ -95,6 +96,7 @@ static void format_exc_check_arg(PyThreadState *, PyObject *, const char *, PyOb static void format_exc_unbound(PyThreadState *tstate, PyCodeObject *co, int oparg); static int check_args_iterable(PyThreadState *, PyObject *func, PyObject *vararg); static int check_except_type_valid(PyThreadState *tstate, PyObject* right); +static int check_except_star_type_valid(PyThreadState *tstate, PyObject* right); static void format_kwargs_error(PyThreadState *, PyObject *func, PyObject *kwargs); static void format_awaitable_error(PyThreadState *, PyTypeObject *, int, int); static int get_exception_handler(PyCodeObject *, int, int*, int*, int*); @@ -1090,6 +1092,11 @@ fail: static int do_raise(PyThreadState *tstate, PyObject *exc, PyObject *cause); +static PyObject *do_reraise_star(PyObject *excs, PyObject *orig); +static int exception_group_match( + PyObject *exc_type, PyObject* exc_value, PyObject *match_type, + PyObject **match, PyObject **rest); + static int unpack_iterable(PyThreadState *, PyObject *, int, int, PyObject **); #ifdef Py_DEBUG @@ -2727,6 +2734,7 @@ check_eval_breaker: type = exc_info->exc_type; value = exc_info->exc_value; traceback = exc_info->exc_traceback; + exc_info->exc_type = POP(); exc_info->exc_value = POP(); exc_info->exc_traceback = POP(); @@ -2791,6 +2799,36 @@ check_eval_breaker: goto exception_unwind; } + TARGET(PREP_RERAISE_STAR) { + PyObject *excs = POP(); + assert(PyList_Check(excs)); + PyObject *orig = POP(); + + PyObject *val = do_reraise_star(excs, orig); + Py_DECREF(excs); + Py_DECREF(orig); + + if (val == NULL) { + goto error; + } + + PyObject *lasti_unused = Py_NewRef(_PyLong_GetZero()); + PUSH(lasti_unused); + if (!Py_IsNone(val)) { + PyObject *tb = PyException_GetTraceback(val); + PUSH(tb ? tb : Py_NewRef(Py_None)); + PUSH(val); + PUSH(Py_NewRef(Py_TYPE(val))); + } + else { + // nothing to reraise + PUSH(Py_NewRef(Py_None)); + PUSH(val); + PUSH(Py_NewRef(Py_None)); + } + DISPATCH(); + } + TARGET(END_ASYNC_FOR) { PyObject *exc = POP(); PyObject *val = POP(); @@ -3922,6 +3960,83 @@ check_eval_breaker: DISPATCH(); } + TARGET(JUMP_IF_NOT_EG_MATCH) { + PyObject *match_type = POP(); + PyObject *exc_type = TOP(); + PyObject *exc_value = SECOND(); + if (check_except_star_type_valid(tstate, match_type) < 0) { + Py_DECREF(match_type); + goto error; + } + + PyObject *match = NULL, *rest = NULL; + int res = exception_group_match(exc_type, exc_value, + match_type, &match, &rest); + Py_DECREF(match_type); + if (res < 0) { + goto error; + } + + if (match == NULL || rest == NULL) { + assert(match == NULL); + assert(rest == NULL); + goto error; + } + + if (Py_IsNone(match)) { + Py_DECREF(match); + Py_XDECREF(rest); + /* no match - jump to target */ + JUMPTO(oparg); + } + else { + + /* Total or partial match - update the stack from + * [tb, val, exc] + * to + * [tb, rest, exc, tb, match, exc] + * (rest can be Py_None) + */ + + + PyObject *type = TOP(); + PyObject *val = SECOND(); + PyObject *tb = THIRD(); + + if (!Py_IsNone(rest)) { + /* tb remains the same */ + SET_TOP(Py_NewRef(Py_TYPE(rest))); + SET_SECOND(Py_NewRef(rest)); + SET_THIRD(Py_NewRef(tb)); + } + else { + SET_TOP(Py_NewRef(Py_None)); + SET_SECOND(Py_NewRef(Py_None)); + SET_THIRD(Py_NewRef(Py_None)); + } + /* Push match */ + + PUSH(Py_NewRef(tb)); + PUSH(Py_NewRef(match)); + PUSH(Py_NewRef(Py_TYPE(match))); + + // set exc_info to the current match + PyErr_SetExcInfo( + Py_NewRef(Py_TYPE(match)), + Py_NewRef(match), + Py_NewRef(tb)); + + Py_DECREF(tb); + Py_DECREF(val); + Py_DECREF(type); + + Py_DECREF(match); + Py_DECREF(rest); + } + + DISPATCH(); + } + TARGET(JUMP_IF_NOT_EXC_MATCH) { PyObject *right = POP(); ASSERT_EXC_TYPE_IS_REDUNDANT(TOP(), SECOND()); @@ -3931,17 +4046,12 @@ check_eval_breaker: Py_DECREF(right); goto error; } + int res = PyErr_GivenExceptionMatches(left, right); Py_DECREF(right); - if (res > 0) { - /* Exception matches -- Do nothing */; - } - else if (res == 0) { + if (res == 0) { JUMPTO(oparg); } - else { - goto error; - } DISPATCH(); } @@ -6127,6 +6237,196 @@ raise_error: return 0; } +/* Logic for matching an exception in an except* clause (too + complicated for inlining). +*/ + +static int +exception_group_match(PyObject *exc_type, PyObject* exc_value, + PyObject *match_type, PyObject **match, PyObject **rest) +{ + if (Py_IsNone(exc_type)) { + assert(Py_IsNone(exc_value)); + *match = Py_NewRef(Py_None); + *rest = Py_NewRef(Py_None); + return 0; + } + assert(PyExceptionClass_Check(exc_type)); + assert(PyExceptionInstance_Check(exc_value)); + + if (PyErr_GivenExceptionMatches(exc_type, match_type)) { + /* Full match of exc itself */ + bool is_eg = _PyBaseExceptionGroup_Check(exc_value); + if (is_eg) { + *match = Py_NewRef(exc_value); + } + else { + /* naked exception - wrap it */ + PyObject *excs = PyTuple_Pack(1, exc_value); + if (excs == NULL) { + return -1; + } + PyObject *wrapped = _PyExc_CreateExceptionGroup("", excs); + Py_DECREF(excs); + if (wrapped == NULL) { + return -1; + } + *match = wrapped; + } + *rest = Py_NewRef(Py_None); + return 0; + } + + /* exc_value does not match match_type. + * Check for partial match if it's an exception group. + */ + if (_PyBaseExceptionGroup_Check(exc_value)) { + PyObject *pair = PyObject_CallMethod(exc_value, "split", "(O)", + match_type); + if (pair == NULL) { + return -1; + } + assert(PyTuple_CheckExact(pair)); + assert(PyTuple_GET_SIZE(pair) == 2); + *match = Py_NewRef(PyTuple_GET_ITEM(pair, 0)); + *rest = Py_NewRef(PyTuple_GET_ITEM(pair, 1)); + Py_DECREF(pair); + return 0; + } + /* no match */ + *match = Py_NewRef(Py_None); + *rest = Py_NewRef(Py_None); + return 0; +} + +/* Logic for the final raise/reraise of a try-except* contruct + (too complicated for inlining). +*/ + +static bool +is_same_exception_metadata(PyObject *exc1, PyObject *exc2) +{ + assert(PyExceptionInstance_Check(exc1)); + assert(PyExceptionInstance_Check(exc2)); + + PyObject *tb1 = PyException_GetTraceback(exc1); + PyObject *ctx1 = PyException_GetContext(exc1); + PyObject *cause1 = PyException_GetCause(exc1); + PyObject *tb2 = PyException_GetTraceback(exc2); + PyObject *ctx2 = PyException_GetContext(exc2); + PyObject *cause2 = PyException_GetCause(exc2); + + bool result = (Py_Is(tb1, tb2) && + Py_Is(ctx1, ctx2) && + Py_Is(cause1, cause2)); + + Py_XDECREF(tb1); + Py_XDECREF(ctx1); + Py_XDECREF(cause1); + Py_XDECREF(tb2); + Py_XDECREF(ctx2); + Py_XDECREF(cause2); + return result; +} + +/* + excs: a list of exceptions to raise/reraise + orig: the original except that was caught + + Calculates an exception group to raise. It contains + all exceptions in excs, where those that were reraised + have same nesting structure as in orig, and those that + were raised (if any) are added as siblings in a new EG. + + Returns NULL and sets an exception on failure. +*/ +static PyObject * +do_reraise_star(PyObject *excs, PyObject *orig) +{ + assert(PyList_Check(excs)); + assert(PyExceptionInstance_Check(orig)); + + Py_ssize_t numexcs = PyList_GET_SIZE(excs); + + if (numexcs == 0) { + return Py_NewRef(Py_None); + } + + if (!_PyBaseExceptionGroup_Check(orig)) { + /* a naked exception was caught and wrapped. Only one except* clause + * could have executed,so there is at most one exception to raise. + */ + + assert(numexcs == 1 || (numexcs == 2 && PyList_GET_ITEM(excs, 1) == Py_None)); + + PyObject *e = PyList_GET_ITEM(excs, 0); + assert(e != NULL); + return Py_NewRef(e); + } + + + PyObject *raised_list = PyList_New(0); + if (raised_list == NULL) { + return NULL; + } + PyObject* reraised_list = PyList_New(0); + if (reraised_list == NULL) { + Py_DECREF(raised_list); + return NULL; + } + + /* Now we are holding refs to raised_list and reraised_list */ + + PyObject *result = NULL; + + /* Split excs into raised and reraised by comparing metadata with orig */ + for (Py_ssize_t i = 0; i < numexcs; i++) { + PyObject *e = PyList_GET_ITEM(excs, i); + assert(e != NULL); + if (Py_IsNone(e)) { + continue; + } + bool is_reraise = is_same_exception_metadata(e, orig); + PyObject *append_list = is_reraise ? reraised_list : raised_list; + if (PyList_Append(append_list, e) < 0) { + goto done; + } + } + + PyObject *reraised_eg = _PyExc_ExceptionGroupProjection(orig, reraised_list); + if (reraised_eg == NULL) { + goto done; + } + + if (!Py_IsNone(reraised_eg)) { + assert(is_same_exception_metadata(reraised_eg, orig)); + } + + Py_ssize_t num_raised = PyList_GET_SIZE(raised_list); + if (num_raised == 0) { + result = reraised_eg; + } + else if (num_raised > 0) { + int res = 0; + if (!Py_IsNone(reraised_eg)) { + res = PyList_Append(raised_list, reraised_eg); + } + Py_DECREF(reraised_eg); + if (res < 0) { + goto done; + } + result = _PyExc_CreateExceptionGroup("", raised_list); + if (result == NULL) { + goto done; + } + } + +done: + Py_XDECREF(raised_list); + Py_XDECREF(reraised_list); + return result; +} + /* Iterate v argcnt times and store the results on the stack (via decreasing sp). Return 1 for success, 0 if error. @@ -7020,10 +7320,12 @@ import_all_from(PyThreadState *tstate, PyObject *locals, PyObject *v) return err; } - #define CANNOT_CATCH_MSG "catching classes that do not inherit from "\ "BaseException is not allowed" +#define CANNOT_EXCEPT_STAR_EG "catching ExceptionGroup with except* "\ + "is not allowed. Use except instead." + static int check_except_type_valid(PyThreadState *tstate, PyObject* right) { @@ -7050,6 +7352,43 @@ check_except_type_valid(PyThreadState *tstate, PyObject* right) } static int +check_except_star_type_valid(PyThreadState *tstate, PyObject* right) +{ + if (check_except_type_valid(tstate, right) < 0) { + return -1; + } + + /* reject except *ExceptionGroup */ + + int is_subclass = 0; + if (PyTuple_Check(right)) { + Py_ssize_t length = PyTuple_GET_SIZE(right); + for (Py_ssize_t i = 0; i < length; i++) { + PyObject *exc = PyTuple_GET_ITEM(right, i); + is_subclass = PyObject_IsSubclass(exc, PyExc_BaseExceptionGroup); + if (is_subclass < 0) { + return -1; + } + if (is_subclass) { + break; + } + } + } + else { + is_subclass = PyObject_IsSubclass(right, PyExc_BaseExceptionGroup); + if (is_subclass < 0) { + return -1; + } + } + if (is_subclass) { + _PyErr_SetString(tstate, PyExc_TypeError, + CANNOT_EXCEPT_STAR_EG); + return -1; + } + return 0; +} + +static int check_args_iterable(PyThreadState *tstate, PyObject *func, PyObject *args) { if (Py_TYPE(args)->tp_iter == NULL && !PySequence_Check(args)) { |