// Exception related primitive operations // // These are registered in mypyc.primitives.exc_ops. #include #include "CPy.h" void CPy_Raise(PyObject *exc) { if (PyObject_IsInstance(exc, (PyObject *)&PyType_Type)) { PyObject *obj = PyObject_CallNoArgs(exc); if (!obj) return; PyErr_SetObject(exc, obj); Py_DECREF(obj); } else { PyErr_SetObject((PyObject *)Py_TYPE(exc), exc); } } void CPy_Reraise(void) { PyObject *p_type, *p_value, *p_traceback; PyErr_GetExcInfo(&p_type, &p_value, &p_traceback); PyErr_Restore(p_type, p_value, p_traceback); } void CPyErr_SetObjectAndTraceback(PyObject *type, PyObject *value, PyObject *traceback) { if (!PyType_Check(type) && value == Py_None) { // The first argument must be an exception instance value = type; type = (PyObject *)Py_TYPE(value); } // Set the value and traceback of an error. Because calling // PyErr_Restore takes away a reference to each object passed in // as an argument, we manually increase the reference count of // each argument before calling it. Py_INCREF(type); Py_INCREF(value); Py_INCREF(traceback); PyErr_Restore(type, value, traceback); } tuple_T3OOO CPy_CatchError(void) { // We need to return the existing sys.exc_info() information, so // that it can be restored when we finish handling the error we // are catching now. Grab that triple and convert NULL values to // the ExcDummy object in order to simplify refcount handling in // generated code. tuple_T3OOO ret; PyErr_GetExcInfo(&ret.f0, &ret.f1, &ret.f2); _CPy_ToDummy(&ret.f0); _CPy_ToDummy(&ret.f1); _CPy_ToDummy(&ret.f2); if (!PyErr_Occurred()) { PyErr_SetString(PyExc_RuntimeError, "CPy_CatchError called with no error!"); } // Retrieve the error info and normalize it so that it looks like // what python code needs it to be. PyObject *type, *value, *traceback; PyErr_Fetch(&type, &value, &traceback); // Could we avoid always normalizing? PyErr_NormalizeException(&type, &value, &traceback); if (traceback != NULL) { PyException_SetTraceback(value, traceback); } // Indicate that we are now handling this exception by stashing it // in sys.exc_info(). mypyc routines that need access to the // exception will read it out of there. PyErr_SetExcInfo(type, value, traceback); // Clear the error indicator, since the exception isn't // propagating anymore. PyErr_Clear(); return ret; } void CPy_RestoreExcInfo(tuple_T3OOO info) { PyErr_SetExcInfo(_CPy_FromDummy(info.f0), _CPy_FromDummy(info.f1), _CPy_FromDummy(info.f2)); } bool CPy_ExceptionMatches(PyObject *type) { return PyErr_GivenExceptionMatches((PyObject *)Py_TYPE(CPy_ExcState()->exc_value), type); } PyObject *CPy_GetExcValue(void) { PyObject *exc = CPy_ExcState()->exc_value; Py_INCREF(exc); return exc; } static inline void _CPy_ToNone(PyObject **p) { if (*p == NULL) { Py_INCREF(Py_None); *p = Py_None; } } void _CPy_GetExcInfo(PyObject **p_type, PyObject **p_value, PyObject **p_traceback) { PyErr_GetExcInfo(p_type, p_value, p_traceback); _CPy_ToNone(p_type); _CPy_ToNone(p_value); _CPy_ToNone(p_traceback); } tuple_T3OOO CPy_GetExcInfo(void) { tuple_T3OOO ret; _CPy_GetExcInfo(&ret.f0, &ret.f1, &ret.f2); return ret; } void CPyError_OutOfMemory(void) { fprintf(stderr, "fatal: out of memory\n"); fflush(stderr); abort(); } // Construct a nicely formatted type name based on __module__ and __name__. static PyObject *CPy_GetTypeName(PyObject *type) { PyObject *module = NULL, *name = NULL; PyObject *full = NULL; module = PyObject_GetAttrString(type, "__module__"); if (!module || !PyUnicode_Check(module)) { goto out; } name = PyObject_GetAttrString(type, "__qualname__"); if (!name || !PyUnicode_Check(name)) { goto out; } if (PyUnicode_CompareWithASCIIString(module, "builtins") == 0) { Py_INCREF(name); full = name; } else { full = PyUnicode_FromFormat("%U.%U", module, name); } out: Py_XDECREF(module); Py_XDECREF(name); return full; } // Get the type of a value as a string, expanding tuples to include // all the element types. static PyObject *CPy_FormatTypeName(PyObject *value) { if (Py_IsNone(value)) { return PyUnicode_FromString("None"); } if (!PyTuple_CheckExact(value)) { return CPy_GetTypeName((PyObject *)Py_TYPE(value)); } if (PyTuple_GET_SIZE(value) > 10) { return PyUnicode_FromFormat("tuple[<%d items>]", PyTuple_GET_SIZE(value)); } // Most of the logic is all for tuples, which is the only interesting case PyObject *output = PyUnicode_FromString("tuple["); if (!output) { return NULL; } /* This is quadratic but if that ever matters something is really weird. */ int i; for (i = 0; i < PyTuple_GET_SIZE(value); i++) { PyObject *s = CPy_FormatTypeName(PyTuple_GET_ITEM(value, i)); if (!s) { Py_DECREF(output); return NULL; } PyObject *next = PyUnicode_FromFormat("%U%U%s", output, s, i + 1 == PyTuple_GET_SIZE(value) ? "]" : ", "); Py_DECREF(output); Py_DECREF(s); if (!next) { return NULL; } output = next; } return output; } CPy_NOINLINE void CPy_TypeError(const char *expected, PyObject *value) { PyObject *out = CPy_FormatTypeName(value); if (out) { PyErr_Format(PyExc_TypeError, "%s object expected; got %U", expected, out); Py_DECREF(out); } else { PyErr_Format(PyExc_TypeError, "%s object expected; and errored formatting real type!", expected); } } // The PyFrameObject type definition (struct _frame) has been moved // to the internal C API: to the pycore_frame.h header file. // https://github.com/python/cpython/pull/31530 #if PY_VERSION_HEX >= 0x030b00a6 #include "internal/pycore_frame.h" #endif // This function is basically exactly the same with _PyTraceback_Add // which is available in all the versions we support. // We're continuing to use this because we'll probably optimize this later. void CPy_AddTraceback(const char *filename, const char *funcname, int line, PyObject *globals) { PyObject *exc, *val, *tb; PyThreadState *thread_state = PyThreadState_GET(); PyFrameObject *frame_obj; // We need to save off the exception state because in 3.8, // PyFrame_New fails if there is an error set and it fails to look // up builtins in the globals. (_PyTraceback_Add documents that it // needs to do it because it decodes the filename according to the // FS encoding, which could have a decoder in Python. We don't do // that so *that* doesn't apply to us.) PyErr_Fetch(&exc, &val, &tb); PyCodeObject *code_obj = PyCode_NewEmpty(filename, funcname, line); if (code_obj == NULL) { goto error; } frame_obj = PyFrame_New(thread_state, code_obj, globals, 0); if (frame_obj == NULL) { Py_DECREF(code_obj); goto error; } frame_obj->f_lineno = line; PyErr_Restore(exc, val, tb); PyTraceBack_Here(frame_obj); Py_DECREF(code_obj); Py_DECREF(frame_obj); return; error: #if CPY_3_12_FEATURES _PyErr_ChainExceptions1(exc); #else _PyErr_ChainExceptions(exc, val, tb); #endif } CPy_NOINLINE void CPy_TypeErrorTraceback(const char *filename, const char *funcname, int line, PyObject *globals, const char *expected, PyObject *value) { CPy_TypeError(expected, value); CPy_AddTraceback(filename, funcname, line, globals); } void CPy_AttributeError(const char *filename, const char *funcname, const char *classname, const char *attrname, int line, PyObject *globals) { char buf[500]; snprintf(buf, sizeof(buf), "attribute '%.200s' of '%.200s' undefined", attrname, classname); PyErr_SetString(PyExc_AttributeError, buf); CPy_AddTraceback(filename, funcname, line, globals); }