summaryrefslogtreecommitdiff
path: root/Objects/exceptions.c
diff options
context:
space:
mode:
Diffstat (limited to 'Objects/exceptions.c')
-rw-r--r--Objects/exceptions.c423
1 files changed, 375 insertions, 48 deletions
diff --git a/Objects/exceptions.c b/Objects/exceptions.c
index 6b04700621..873fbc61bd 100644
--- a/Objects/exceptions.c
+++ b/Objects/exceptions.c
@@ -16,9 +16,6 @@ PyObject *PyExc_IOError = NULL;
#ifdef MS_WINDOWS
PyObject *PyExc_WindowsError = NULL;
#endif
-#ifdef __VMS
-PyObject *PyExc_VMSError = NULL;
-#endif
/* The dict map from errno codes to OSError subclasses */
static PyObject *errnomap = NULL;
@@ -121,11 +118,11 @@ BaseException_str(PyBaseExceptionObject *self)
static PyObject *
BaseException_repr(PyBaseExceptionObject *self)
{
- char *name;
- char *dot;
+ const char *name;
+ const char *dot;
- name = (char *)Py_TYPE(self)->tp_name;
- dot = strrchr(name, '.');
+ name = Py_TYPE(self)->tp_name;
+ dot = (const char *) strrchr(name, '.');
if (dot != NULL) name = dot+1;
return PyUnicode_FromFormat("%s%R", name, self->args);
@@ -727,13 +724,17 @@ ComplexExtendsException(PyExc_Exception, ImportError,
* we hack args so that it only contains two items. This also
* means we need our own __str__() which prints out the filename
* when it was supplied.
+ *
+ * (If a function has two filenames, such as rename(), symlink(),
+ * or copy(), PyErr_SetFromErrnoWithFilenameObjects() is called,
+ * which allows passing in a second filename.)
*/
/* This function doesn't cleanup on error, the caller should */
static int
oserror_parse_args(PyObject **p_args,
PyObject **myerrno, PyObject **strerror,
- PyObject **filename
+ PyObject **filename, PyObject **filename2
#ifdef MS_WINDOWS
, PyObject **winerror
#endif
@@ -741,14 +742,23 @@ oserror_parse_args(PyObject **p_args,
{
Py_ssize_t nargs;
PyObject *args = *p_args;
+#ifndef MS_WINDOWS
+ /*
+ * ignored on non-Windows platforms,
+ * but parsed so OSError has a consistent signature
+ */
+ PyObject *_winerror = NULL;
+ PyObject **winerror = &_winerror;
+#endif /* MS_WINDOWS */
nargs = PyTuple_GET_SIZE(args);
-#ifdef MS_WINDOWS
- if (nargs >= 2 && nargs <= 4) {
- if (!PyArg_UnpackTuple(args, "OSError", 2, 4,
- myerrno, strerror, filename, winerror))
+ if (nargs >= 2 && nargs <= 5) {
+ if (!PyArg_UnpackTuple(args, "OSError", 2, 5,
+ myerrno, strerror,
+ filename, winerror, filename2))
return -1;
+#ifdef MS_WINDOWS
if (*winerror && PyLong_Check(*winerror)) {
long errcode, winerrcode;
PyObject *newargs;
@@ -780,14 +790,8 @@ oserror_parse_args(PyObject **p_args,
Py_DECREF(args);
args = *p_args = newargs;
}
+#endif /* MS_WINDOWS */
}
-#else
- if (nargs >= 2 && nargs <= 3) {
- if (!PyArg_UnpackTuple(args, "OSError", 2, 3,
- myerrno, strerror, filename))
- return -1;
- }
-#endif
return 0;
}
@@ -795,7 +799,7 @@ oserror_parse_args(PyObject **p_args,
static int
oserror_init(PyOSErrorObject *self, PyObject **p_args,
PyObject *myerrno, PyObject *strerror,
- PyObject *filename
+ PyObject *filename, PyObject *filename2
#ifdef MS_WINDOWS
, PyObject *winerror
#endif
@@ -819,9 +823,14 @@ oserror_init(PyOSErrorObject *self, PyObject **p_args,
Py_INCREF(filename);
self->filename = filename;
- if (nargs >= 2 && nargs <= 3) {
- /* filename is removed from the args tuple (for compatibility
- purposes, see test_exceptions.py) */
+ if (filename2 && filename2 != Py_None) {
+ Py_INCREF(filename2);
+ self->filename2 = filename2;
+ }
+
+ if (nargs >= 2 && nargs <= 5) {
+ /* filename, filename2, and winerror are removed from the args tuple
+ (for compatibility purposes, see test_exceptions.py) */
PyObject *subslice = PyTuple_GetSlice(args, 0, 2);
if (!subslice)
return -1;
@@ -845,7 +854,7 @@ oserror_init(PyOSErrorObject *self, PyObject **p_args,
/* Steals the reference to args */
Py_CLEAR(self->args);
self->args = args;
- args = NULL;
+ *p_args = args = NULL;
return 0;
}
@@ -880,17 +889,20 @@ static PyObject *
OSError_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
PyOSErrorObject *self = NULL;
- PyObject *myerrno = NULL, *strerror = NULL, *filename = NULL;
+ PyObject *myerrno = NULL, *strerror = NULL;
+ PyObject *filename = NULL, *filename2 = NULL;
#ifdef MS_WINDOWS
PyObject *winerror = NULL;
#endif
+ Py_INCREF(args);
+
if (!oserror_use_init(type)) {
if (!_PyArg_NoKeywords(type->tp_name, kwds))
- return NULL;
+ goto error;
- Py_INCREF(args);
- if (oserror_parse_args(&args, &myerrno, &strerror, &filename
+ if (oserror_parse_args(&args, &myerrno, &strerror,
+ &filename, &filename2
#ifdef MS_WINDOWS
, &winerror
#endif
@@ -919,7 +931,7 @@ OSError_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
self->written = -1;
if (!oserror_use_init(type)) {
- if (oserror_init(self, &args, myerrno, strerror, filename
+ if (oserror_init(self, &args, myerrno, strerror, filename, filename2
#ifdef MS_WINDOWS
, winerror
#endif
@@ -932,6 +944,7 @@ OSError_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
goto error;
}
+ Py_XDECREF(args);
return (PyObject *) self;
error:
@@ -943,7 +956,8 @@ error:
static int
OSError_init(PyOSErrorObject *self, PyObject *args, PyObject *kwds)
{
- PyObject *myerrno = NULL, *strerror = NULL, *filename = NULL;
+ PyObject *myerrno = NULL, *strerror = NULL;
+ PyObject *filename = NULL, *filename2 = NULL;
#ifdef MS_WINDOWS
PyObject *winerror = NULL;
#endif
@@ -956,14 +970,14 @@ OSError_init(PyOSErrorObject *self, PyObject *args, PyObject *kwds)
return -1;
Py_INCREF(args);
- if (oserror_parse_args(&args, &myerrno, &strerror, &filename
+ if (oserror_parse_args(&args, &myerrno, &strerror, &filename, &filename2
#ifdef MS_WINDOWS
, &winerror
#endif
))
goto error;
- if (oserror_init(self, &args, myerrno, strerror, filename
+ if (oserror_init(self, &args, myerrno, strerror, filename, filename2
#ifdef MS_WINDOWS
, winerror
#endif
@@ -983,6 +997,7 @@ OSError_clear(PyOSErrorObject *self)
Py_CLEAR(self->myerrno);
Py_CLEAR(self->strerror);
Py_CLEAR(self->filename);
+ Py_CLEAR(self->filename2);
#ifdef MS_WINDOWS
Py_CLEAR(self->winerror);
#endif
@@ -1004,6 +1019,7 @@ OSError_traverse(PyOSErrorObject *self, visitproc visit,
Py_VISIT(self->myerrno);
Py_VISIT(self->strerror);
Py_VISIT(self->filename);
+ Py_VISIT(self->filename2);
#ifdef MS_WINDOWS
Py_VISIT(self->winerror);
#endif
@@ -1013,23 +1029,42 @@ OSError_traverse(PyOSErrorObject *self, visitproc visit,
static PyObject *
OSError_str(PyOSErrorObject *self)
{
+#define OR_NONE(x) ((x)?(x):Py_None)
#ifdef MS_WINDOWS
/* If available, winerror has the priority over myerrno */
- if (self->winerror && self->filename)
- return PyUnicode_FromFormat("[WinError %S] %S: %R",
- self->winerror ? self->winerror: Py_None,
- self->strerror ? self->strerror: Py_None,
- self->filename);
+ if (self->winerror && self->filename) {
+ if (self->filename2) {
+ return PyUnicode_FromFormat("[WinError %S] %S: %R -> %R",
+ OR_NONE(self->winerror),
+ OR_NONE(self->strerror),
+ self->filename,
+ self->filename2);
+ } else {
+ return PyUnicode_FromFormat("[WinError %S] %S: %R",
+ OR_NONE(self->winerror),
+ OR_NONE(self->strerror),
+ self->filename);
+ }
+ }
if (self->winerror && self->strerror)
return PyUnicode_FromFormat("[WinError %S] %S",
self->winerror ? self->winerror: Py_None,
self->strerror ? self->strerror: Py_None);
#endif
- if (self->filename)
- return PyUnicode_FromFormat("[Errno %S] %S: %R",
- self->myerrno ? self->myerrno: Py_None,
- self->strerror ? self->strerror: Py_None,
- self->filename);
+ if (self->filename) {
+ if (self->filename2) {
+ return PyUnicode_FromFormat("[Errno %S] %S: %R -> %R",
+ OR_NONE(self->myerrno),
+ OR_NONE(self->strerror),
+ self->filename,
+ self->filename2);
+ } else {
+ return PyUnicode_FromFormat("[Errno %S] %S: %R",
+ OR_NONE(self->myerrno),
+ OR_NONE(self->strerror),
+ self->filename);
+ }
+ }
if (self->myerrno && self->strerror)
return PyUnicode_FromFormat("[Errno %S] %S",
self->myerrno ? self->myerrno: Py_None,
@@ -1046,7 +1081,8 @@ OSError_reduce(PyOSErrorObject *self)
/* self->args is only the first two real arguments if there was a
* file name given to OSError. */
if (PyTuple_GET_SIZE(args) == 2 && self->filename) {
- args = PyTuple_New(3);
+ Py_ssize_t size = self->filename2 ? 5 : 3;
+ args = PyTuple_New(size);
if (!args)
return NULL;
@@ -1060,6 +1096,20 @@ OSError_reduce(PyOSErrorObject *self)
Py_INCREF(self->filename);
PyTuple_SET_ITEM(args, 2, self->filename);
+
+ if (self->filename2) {
+ /*
+ * This tuple is essentially used as OSError(*args).
+ * So, to recreate filename2, we need to pass in
+ * winerror as well.
+ */
+ Py_INCREF(Py_None);
+ PyTuple_SET_ITEM(args, 3, Py_None);
+
+ /* filename2 */
+ Py_INCREF(self->filename2);
+ PyTuple_SET_ITEM(args, 4, self->filename2);
+ }
} else
Py_INCREF(args);
@@ -1099,6 +1149,8 @@ static PyMemberDef OSError_members[] = {
PyDoc_STR("exception strerror")},
{"filename", T_OBJECT, offsetof(PyOSErrorObject, filename), 0,
PyDoc_STR("exception filename")},
+ {"filename2", T_OBJECT, offsetof(PyOSErrorObject, filename2), 0,
+ PyDoc_STR("second exception filename")},
#ifdef MS_WINDOWS
{"winerror", T_OBJECT, offsetof(PyOSErrorObject, winerror), 0,
PyDoc_STR("Win32 exception code")},
@@ -1202,6 +1254,9 @@ SimpleExtendsException(PyExc_Exception, AttributeError,
* SyntaxError extends Exception
*/
+/* Helper function to customise error message for some syntax errors */
+static int _report_missing_parentheses(PySyntaxErrorObject *self);
+
static int
SyntaxError_init(PySyntaxErrorObject *self, PyObject *args, PyObject *kwds)
{
@@ -1246,6 +1301,13 @@ SyntaxError_init(PySyntaxErrorObject *self, PyObject *args, PyObject *kwds)
Py_INCREF(self->text);
Py_DECREF(info);
+
+ /* Issue #21669: Custom error for 'print' & 'exec' as statements */
+ if (self->text && PyUnicode_Check(self->text)) {
+ if (_report_missing_parentheses(self) < 0) {
+ return -1;
+ }
+ }
}
return 0;
}
@@ -1785,6 +1847,10 @@ UnicodeEncodeError_str(PyObject *self)
PyObject *reason_str = NULL;
PyObject *encoding_str = NULL;
+ if (!uself->object)
+ /* Not properly initialized. */
+ return PyUnicode_FromString("");
+
/* Get reason and encoding as strings, which they might not be if
they've been modified after we were contructed. */
reason_str = PyObject_Str(uself->reason);
@@ -1903,6 +1969,10 @@ UnicodeDecodeError_str(PyObject *self)
PyObject *reason_str = NULL;
PyObject *encoding_str = NULL;
+ if (!uself->object)
+ /* Not properly initialized. */
+ return PyUnicode_FromString("");
+
/* Get reason and encoding as strings, which they might not be if
they've been modified after we were contructed. */
reason_str = PyObject_Str(uself->reason);
@@ -1997,6 +2067,10 @@ UnicodeTranslateError_str(PyObject *self)
PyObject *result = NULL;
PyObject *reason_str = NULL;
+ if (!uself->object)
+ /* Not properly initialized. */
+ return PyUnicode_FromString("");
+
/* Get reason as a string, which it might not be if it's been
modified after we were contructed. */
reason_str = PyObject_Str(uself->reason);
@@ -2060,7 +2134,7 @@ _PyUnicodeTranslateError_Create(
PyObject *object,
Py_ssize_t start, Py_ssize_t end, const char *reason)
{
- return PyObject_CallFunction(PyExc_UnicodeTranslateError, "Ons",
+ return PyObject_CallFunction(PyExc_UnicodeTranslateError, "Onns",
object, start, end, reason);
}
@@ -2327,7 +2401,7 @@ PyObject *PyExc_RecursionErrorInst = NULL;
}
#ifdef MS_WINDOWS
-#include <Winsock2.h>
+#include <winsock2.h>
/* The following constants were added to errno.h in VS2010 but have
preferred WSA equivalents. */
#undef EADDRINUSE
@@ -2471,9 +2545,6 @@ _PyExc_Init(PyObject *bltinmod)
#ifdef MS_WINDOWS
INIT_ALIAS(WindowsError, OSError)
#endif
-#ifdef __VMS
- INIT_ALIAS(VMSError, OSError)
-#endif
POST_INIT(EOFError)
POST_INIT(RuntimeError)
POST_INIT(NotImplementedError)
@@ -2591,3 +2662,259 @@ _PyExc_Fini(void)
free_preallocated_memerrors();
Py_CLEAR(errnomap);
}
+
+/* Helper to do the equivalent of "raise X from Y" in C, but always using
+ * the current exception rather than passing one in.
+ *
+ * We currently limit this to *only* exceptions that use the BaseException
+ * tp_init and tp_new methods, since we can be reasonably sure we can wrap
+ * those correctly without losing data and without losing backwards
+ * compatibility.
+ *
+ * We also aim to rule out *all* exceptions that might be storing additional
+ * state, whether by having a size difference relative to BaseException,
+ * additional arguments passed in during construction or by having a
+ * non-empty instance dict.
+ *
+ * We need to be very careful with what we wrap, since changing types to
+ * a broader exception type would be backwards incompatible for
+ * existing codecs, and with different init or new method implementations
+ * may either not support instantiation with PyErr_Format or lose
+ * information when instantiated that way.
+ *
+ * XXX (ncoghlan): This could be made more comprehensive by exploiting the
+ * fact that exceptions are expected to support pickling. If more builtin
+ * exceptions (e.g. AttributeError) start to be converted to rich
+ * exceptions with additional attributes, that's probably a better approach
+ * to pursue over adding special cases for particular stateful subclasses.
+ *
+ * Returns a borrowed reference to the new exception (if any), NULL if the
+ * existing exception was left in place.
+ */
+PyObject *
+_PyErr_TrySetFromCause(const char *format, ...)
+{
+ PyObject* msg_prefix;
+ PyObject *exc, *val, *tb;
+ PyTypeObject *caught_type;
+ PyObject **dictptr;
+ PyObject *instance_args;
+ Py_ssize_t num_args, caught_type_size, base_exc_size;
+ PyObject *new_exc, *new_val, *new_tb;
+ va_list vargs;
+ int same_basic_size;
+
+ PyErr_Fetch(&exc, &val, &tb);
+ caught_type = (PyTypeObject *)exc;
+ /* Ensure type info indicates no extra state is stored at the C level
+ * and that the type can be reinstantiated using PyErr_Format
+ */
+ caught_type_size = caught_type->tp_basicsize;
+ base_exc_size = _PyExc_BaseException.tp_basicsize;
+ same_basic_size = (
+ caught_type_size == base_exc_size ||
+ (PyType_SUPPORTS_WEAKREFS(caught_type) &&
+ (caught_type_size == base_exc_size + sizeof(PyObject *))
+ )
+ );
+ if (caught_type->tp_init != (initproc)BaseException_init ||
+ caught_type->tp_new != BaseException_new ||
+ !same_basic_size ||
+ caught_type->tp_itemsize != _PyExc_BaseException.tp_itemsize) {
+ /* We can't be sure we can wrap this safely, since it may contain
+ * more state than just the exception type. Accordingly, we just
+ * leave it alone.
+ */
+ PyErr_Restore(exc, val, tb);
+ return NULL;
+ }
+
+ /* Check the args are empty or contain a single string */
+ PyErr_NormalizeException(&exc, &val, &tb);
+ instance_args = ((PyBaseExceptionObject *)val)->args;
+ num_args = PyTuple_GET_SIZE(instance_args);
+ if (num_args > 1 ||
+ (num_args == 1 &&
+ !PyUnicode_CheckExact(PyTuple_GET_ITEM(instance_args, 0)))) {
+ /* More than 1 arg, or the one arg we do have isn't a string
+ */
+ PyErr_Restore(exc, val, tb);
+ return NULL;
+ }
+
+ /* Ensure the instance dict is also empty */
+ dictptr = _PyObject_GetDictPtr(val);
+ if (dictptr != NULL && *dictptr != NULL &&
+ PyObject_Length(*dictptr) > 0) {
+ /* While we could potentially copy a non-empty instance dictionary
+ * to the replacement exception, for now we take the more
+ * conservative path of leaving exceptions with attributes set
+ * alone.
+ */
+ PyErr_Restore(exc, val, tb);
+ return NULL;
+ }
+
+ /* For exceptions that we can wrap safely, we chain the original
+ * exception to a new one of the exact same type with an
+ * error message that mentions the additional details and the
+ * original exception.
+ *
+ * It would be nice to wrap OSError and various other exception
+ * types as well, but that's quite a bit trickier due to the extra
+ * state potentially stored on OSError instances.
+ */
+ /* Ensure the traceback is set correctly on the existing exception */
+ if (tb != NULL) {
+ PyException_SetTraceback(val, tb);
+ Py_DECREF(tb);
+ }
+
+#ifdef HAVE_STDARG_PROTOTYPES
+ va_start(vargs, format);
+#else
+ va_start(vargs);
+#endif
+ msg_prefix = PyUnicode_FromFormatV(format, vargs);
+ va_end(vargs);
+ if (msg_prefix == NULL) {
+ Py_DECREF(exc);
+ Py_DECREF(val);
+ return NULL;
+ }
+
+ PyErr_Format(exc, "%U (%s: %S)",
+ msg_prefix, Py_TYPE(val)->tp_name, val);
+ Py_DECREF(exc);
+ Py_DECREF(msg_prefix);
+ PyErr_Fetch(&new_exc, &new_val, &new_tb);
+ PyErr_NormalizeException(&new_exc, &new_val, &new_tb);
+ PyException_SetCause(new_val, val);
+ PyErr_Restore(new_exc, new_val, new_tb);
+ return new_val;
+}
+
+
+/* To help with migration from Python 2, SyntaxError.__init__ applies some
+ * heuristics to try to report a more meaningful exception when print and
+ * exec are used like statements.
+ *
+ * The heuristics are currently expected to detect the following cases:
+ * - top level statement
+ * - statement in a nested suite
+ * - trailing section of a one line complex statement
+ *
+ * They're currently known not to trigger:
+ * - after a semi-colon
+ *
+ * The error message can be a bit odd in cases where the "arguments" are
+ * completely illegal syntactically, but that isn't worth the hassle of
+ * fixing.
+ *
+ * We also can't do anything about cases that are legal Python 3 syntax
+ * but mean something entirely different from what they did in Python 2
+ * (omitting the arguments entirely, printing items preceded by a unary plus
+ * or minus, using the stream redirection syntax).
+ */
+
+static int
+_check_for_legacy_statements(PySyntaxErrorObject *self, Py_ssize_t start)
+{
+ /* Return values:
+ * -1: an error occurred
+ * 0: nothing happened
+ * 1: the check triggered & the error message was changed
+ */
+ static PyObject *print_prefix = NULL;
+ static PyObject *exec_prefix = NULL;
+ Py_ssize_t text_len = PyUnicode_GET_LENGTH(self->text);
+ int kind = PyUnicode_KIND(self->text);
+ void *data = PyUnicode_DATA(self->text);
+
+ /* Ignore leading whitespace */
+ while (start < text_len) {
+ Py_UCS4 ch = PyUnicode_READ(kind, data, start);
+ if (!Py_UNICODE_ISSPACE(ch))
+ break;
+ start++;
+ }
+ /* Checking against an empty or whitespace-only part of the string */
+ if (start == text_len) {
+ return 0;
+ }
+
+ /* Check for legacy print statements */
+ if (print_prefix == NULL) {
+ print_prefix = PyUnicode_InternFromString("print ");
+ if (print_prefix == NULL) {
+ return -1;
+ }
+ }
+ if (PyUnicode_Tailmatch(self->text, print_prefix,
+ start, text_len, -1)) {
+ Py_CLEAR(self->msg);
+ self->msg = PyUnicode_FromString(
+ "Missing parentheses in call to 'print'");
+ return 1;
+ }
+
+ /* Check for legacy exec statements */
+ if (exec_prefix == NULL) {
+ exec_prefix = PyUnicode_InternFromString("exec ");
+ if (exec_prefix == NULL) {
+ return -1;
+ }
+ }
+ if (PyUnicode_Tailmatch(self->text, exec_prefix,
+ start, text_len, -1)) {
+ Py_CLEAR(self->msg);
+ self->msg = PyUnicode_FromString(
+ "Missing parentheses in call to 'exec'");
+ return 1;
+ }
+ /* Fall back to the default error message */
+ return 0;
+}
+
+static int
+_report_missing_parentheses(PySyntaxErrorObject *self)
+{
+ Py_UCS4 left_paren = 40;
+ Py_ssize_t left_paren_index;
+ Py_ssize_t text_len = PyUnicode_GET_LENGTH(self->text);
+ int legacy_check_result = 0;
+
+ /* Skip entirely if there is an opening parenthesis */
+ left_paren_index = PyUnicode_FindChar(self->text, left_paren,
+ 0, text_len, 1);
+ if (left_paren_index < -1) {
+ return -1;
+ }
+ if (left_paren_index != -1) {
+ /* Use default error message for any line with an opening paren */
+ return 0;
+ }
+ /* Handle the simple statement case */
+ legacy_check_result = _check_for_legacy_statements(self, 0);
+ if (legacy_check_result < 0) {
+ return -1;
+
+ }
+ if (legacy_check_result == 0) {
+ /* Handle the one-line complex statement case */
+ Py_UCS4 colon = 58;
+ Py_ssize_t colon_index;
+ colon_index = PyUnicode_FindChar(self->text, colon,
+ 0, text_len, 1);
+ if (colon_index < -1) {
+ return -1;
+ }
+ if (colon_index >= 0 && colon_index < text_len) {
+ /* Check again, starting from just after the colon */
+ if (_check_for_legacy_statements(self, colon_index+1) < 0) {
+ return -1;
+ }
+ }
+ }
+ return 0;
+}