Content-Length: 14556 | pFad | http://github.com/python/cpython/pull/113034.diff
thub.com
diff --git a/Include/internal/pycore_crossinterp.h b/Include/internal/pycore_crossinterp.h
index ce95979f8d343b..414e32b5155f62 100644
--- a/Include/internal/pycore_crossinterp.h
+++ b/Include/internal/pycore_crossinterp.h
@@ -188,6 +188,8 @@ typedef struct _excinfo {
const char *module;
} type;
const char *msg;
+ const char *pickled;
+ Py_ssize_t pickled_len;
} _PyXI_excinfo;
diff --git a/Lib/test/support/interpreters/__init__.py b/Lib/test/support/interpreters/__init__.py
index 9cd1c3de0274d2..d619bea3e32f5d 100644
--- a/Lib/test/support/interpreters/__init__.py
+++ b/Lib/test/support/interpreters/__init__.py
@@ -34,17 +34,36 @@ def __getattr__(name):
raise AttributeError(name)
+_EXEC_FAILURE_STR = """
+{superstr}
+
+Uncaught in the interpreter:
+
+{formatted}
+""".strip()
+
class ExecFailure(RuntimeError):
def __init__(self, excinfo):
msg = excinfo.formatted
if not msg:
- if excinfo.type and snapshot.msg:
- msg = f'{snapshot.type.__name__}: {snapshot.msg}'
+ if excinfo.type and excinfo.msg:
+ msg = f'{excinfo.type.__name__}: {excinfo.msg}'
else:
- msg = snapshot.type.__name__ or snapshot.msg
+ msg = excinfo.type.__name__ or excinfo.msg
super().__init__(msg)
- self.snapshot = excinfo
+ self.excinfo = excinfo
+
+ def __str__(self):
+ try:
+ formatted = ''.join(self.excinfo.tbexc.format()).rstrip()
+ except Exception:
+ return super().__str__()
+ else:
+ return _EXEC_FAILURE_STR.format(
+ superstr=super().__str__(),
+ formatted=formatted,
+ )
def create():
diff --git a/Lib/test/test_interpreters/test_api.py b/Lib/test/test_interpreters/test_api.py
index b702338c3de1ad..aefd326977095f 100644
--- a/Lib/test/test_interpreters/test_api.py
+++ b/Lib/test/test_interpreters/test_api.py
@@ -525,6 +525,54 @@ def test_failure(self):
with self.assertRaises(interpreters.ExecFailure):
interp.exec_sync('raise Exception')
+ def test_display_preserved_exception(self):
+ tempdir = self.temp_dir()
+ modfile = self.make_module('spam', tempdir, text="""
+ def ham():
+ raise RuntimeError('uh-oh!')
+
+ def eggs():
+ ham()
+ """)
+ scriptfile = self.make_script('script.py', tempdir, text="""
+ from test.support import interpreters
+
+ def script():
+ import spam
+ spam.eggs()
+
+ interp = interpreters.create()
+ interp.exec_sync(script)
+ """)
+
+ stdout, stderr = self.assert_python_failure(scriptfile)
+ self.maxDiff = None
+ interpmod_line, = (l for l in stderr.splitlines() if ' exec_sync' in l)
+ # File "{interpreters.__file__}", line 179, in exec_sync
+ self.assertEqual(stderr, dedent(f"""\
+ Traceback (most recent call last):
+ File "{scriptfile}", line 9, in
+ interp.exec_sync(script)
+ ~~~~~~~~~~~~~~~~^^^^^^^^
+ {interpmod_line.strip()}
+ raise ExecFailure(excinfo)
+ test.support.interpreters.ExecFailure: RuntimeError: uh-oh!
+
+ Uncaught in the interpreter:
+
+ Traceback (most recent call last):
+ File "{scriptfile}", line 6, in script
+ spam.eggs()
+ ~~~~~~~~~^^
+ File "{modfile}", line 6, in eggs
+ ham()
+ ~~~^^
+ File "{modfile}", line 3, in ham
+ raise RuntimeError('uh-oh!')
+ RuntimeError: uh-oh!
+ """))
+ self.assertEqual(stdout, '')
+
def test_in_thread(self):
interp = interpreters.create()
script, file = _captured_script('print("it worked!", end="")')
diff --git a/Lib/test/test_interpreters/utils.py b/Lib/test/test_interpreters/utils.py
index 11b6f126dff0f4..3a37ed09dd8943 100644
--- a/Lib/test/test_interpreters/utils.py
+++ b/Lib/test/test_interpreters/utils.py
@@ -1,9 +1,16 @@
import contextlib
import os
+import os.path
+import subprocess
+import sys
+import tempfile
import threading
from textwrap import dedent
import unittest
+from test import support
+from test.support import os_helper
+
from test.support import interpreters
@@ -71,5 +78,70 @@ def ensure_closed(fd):
self.addCleanup(lambda: ensure_closed(w))
return r, w
+ def temp_dir(self):
+ tempdir = tempfile.mkdtemp()
+ tempdir = os.path.realpath(tempdir)
+ self.addCleanup(lambda: os_helper.rmtree(tempdir))
+ return tempdir
+
+ def make_script(self, filename, dirname=None, text=None):
+ if text:
+ text = dedent(text)
+ if dirname is None:
+ dirname = self.temp_dir()
+ filename = os.path.join(dirname, filename)
+
+ os.makedirs(os.path.dirname(filename), exist_ok=True)
+ with open(filename, 'w', encoding='utf-8') as outfile:
+ outfile.write(text or '')
+ return filename
+
+ def make_module(self, name, pathentry=None, text=None):
+ if text:
+ text = dedent(text)
+ if pathentry is None:
+ pathentry = self.temp_dir()
+ else:
+ os.makedirs(pathentry, exist_ok=True)
+ *subnames, basename = name.split('.')
+
+ dirname = pathentry
+ for subname in subnames:
+ dirname = os.path.join(dirname, subname)
+ if os.path.isdir(dirname):
+ pass
+ elif os.path.exists(dirname):
+ raise Exception(dirname)
+ else:
+ os.mkdir(dirname)
+ initfile = os.path.join(dirname, '__init__.py')
+ if not os.path.exists(initfile):
+ with open(initfile, 'w'):
+ pass
+ filename = os.path.join(dirname, basename + '.py')
+
+ with open(filename, 'w', encoding='utf-8') as outfile:
+ outfile.write(text or '')
+ return filename
+
+ @support.requires_subprocess()
+ def run_python(self, *argv):
+ proc = subprocess.run(
+ [sys.executable, *argv],
+ capture_output=True,
+ text=True,
+ )
+ return proc.returncode, proc.stdout, proc.stderr
+
+ def assert_python_ok(self, *argv):
+ exitcode, stdout, stderr = self.run_python(*argv)
+ self.assertNotEqual(exitcode, 1)
+ return stdout, stderr
+
+ def assert_python_failure(self, *argv):
+ exitcode, stdout, stderr = self.run_python(*argv)
+ self.assertNotEqual(exitcode, 0)
+ return stdout, stderr
+
def tearDown(self):
clean_up_interpreters()
diff --git a/Python/crossinterp.c b/Python/crossinterp.c
index a31b5ef4613dbd..edd61cf99f3f52 100644
--- a/Python/crossinterp.c
+++ b/Python/crossinterp.c
@@ -944,6 +944,26 @@ _xidregistry_fini(struct _xidregistry *registry)
/* convenience utilities */
/*************************/
+static const char *
+_copy_raw_string(const char *str, Py_ssize_t len)
+{
+ size_t size = len + 1;
+ if (len <= 0) {
+ size = strlen(str) + 1;
+ }
+ char *copied = PyMem_RawMalloc(size);
+ if (copied == NULL) {
+ return NULL;
+ }
+ if (len <= 0) {
+ strcpy(copied, str);
+ }
+ else {
+ memcpy(copied, str, size);
+ }
+ return copied;
+}
+
static const char *
_copy_string_obj_raw(PyObject *strobj)
{
@@ -961,6 +981,80 @@ _copy_string_obj_raw(PyObject *strobj)
return copied;
}
+
+static int
+_pickle_object(PyObject *obj, const char **p_pickled, Py_ssize_t *p_len)
+{
+ assert(!PyErr_Occurred());
+ PyObject *picklemod = PyImport_ImportModule("_pickle");
+ if (picklemod == NULL) {
+ PyErr_Clear();
+ picklemod = PyImport_ImportModule("pickle");
+ if (picklemod == NULL) {
+ return -1;
+ }
+ }
+ PyObject *dumps = PyObject_GetAttrString(picklemod, "dumps");
+ Py_DECREF(picklemod);
+ if (dumps == NULL) {
+ return -1;
+ }
+ PyObject *pickledobj = PyObject_CallOneArg(dumps, obj);
+ Py_DECREF(dumps);
+ if (pickledobj == NULL) {
+ return -1;
+ }
+
+ char *pickled = NULL;
+ Py_ssize_t len = 0;
+ if (PyBytes_AsStringAndSize(pickledobj, &pickled, &len) < 0) {
+ Py_DECREF(pickledobj);
+ return -1;
+ }
+ const char *copied = _copy_raw_string(pickled, len);
+ Py_DECREF(pickledobj);
+ if (copied == NULL) {
+ return -1;
+ }
+
+ *p_pickled = copied;
+ *p_len = len;
+ return 0;
+}
+
+static int
+_unpickle_object(const char *pickled, Py_ssize_t size, PyObject **p_obj)
+{
+ assert(!PyErr_Occurred());
+ PyObject *picklemod = PyImport_ImportModule("_pickle");
+ if (picklemod == NULL) {
+ PyErr_Clear();
+ picklemod = PyImport_ImportModule("pickle");
+ if (picklemod == NULL) {
+ return -1;
+ }
+ }
+ PyObject *loads = PyObject_GetAttrString(picklemod, "loads");
+ Py_DECREF(picklemod);
+ if (loads == NULL) {
+ return -1;
+ }
+ PyObject *pickledobj = PyBytes_FromStringAndSize(pickled, size);
+ if (pickledobj == NULL) {
+ Py_DECREF(loads);
+ return -1;
+ }
+ PyObject *obj = PyObject_CallOneArg(loads, pickledobj);
+ Py_DECREF(loads);
+ Py_DECREF(pickledobj);
+ if (obj == NULL) {
+ return -1;
+ }
+ *p_obj = obj;
+ return 0;
+}
+
+
static int
_release_xid_data(_PyCrossInterpreterData *data, int rawfree)
{
@@ -1094,6 +1188,9 @@ _PyXI_excinfo_Clear(_PyXI_excinfo *info)
if (info->msg != NULL) {
PyMem_RawFree((void *)info->msg);
}
+ if (info->pickled != NULL) {
+ PyMem_RawFree((void *)info->pickled);
+ }
*info = (_PyXI_excinfo){{NULL}};
}
@@ -1129,6 +1226,63 @@ _PyXI_excinfo_format(_PyXI_excinfo *info)
}
}
+static int
+_convert_exc_to_TracebackException(PyObject *exc, PyObject **p_tbexc)
+{
+ PyObject *args = NULL;
+ PyObject *kwargs = NULL;
+ PyObject *create = NULL;
+
+ // This is inspired by _PyErr_Display().
+ PyObject *tbmod = PyImport_ImportModule("traceback");
+ if (tbmod == NULL) {
+ return -1;
+ }
+ PyObject *tbexc_type = PyObject_GetAttrString(tbmod, "TracebackException");
+ Py_DECREF(tbmod);
+ if (tbexc_type == NULL) {
+ return -1;
+ }
+ create = PyObject_GetAttrString(tbexc_type, "from_exception");
+ Py_DECREF(tbexc_type);
+ if (create == NULL) {
+ return -1;
+ }
+
+ args = PyTuple_Pack(1, exc);
+ if (args == NULL) {
+ goto error;
+ }
+
+ kwargs = PyDict_New();
+ if (kwargs == NULL) {
+ goto error;
+ }
+ if (PyDict_SetItemString(kwargs, "save_exc_type", Py_False) < 0) {
+ goto error;
+ }
+ if (PyDict_SetItemString(kwargs, "lookup_lines", Py_False) < 0) {
+ goto error;
+ }
+
+ PyObject *tbexc = PyObject_Call(create, args, kwargs);
+ Py_DECREF(args);
+ Py_DECREF(kwargs);
+ Py_DECREF(create);
+ if (tbexc == NULL) {
+ goto error;
+ }
+
+ *p_tbexc = tbexc;
+ return 0;
+
+error:
+ Py_XDECREF(args);
+ Py_XDECREF(kwargs);
+ Py_XDECREF(create);
+ return -1;
+}
+
static const char *
_PyXI_excinfo_InitFromException(_PyXI_excinfo *info, PyObject *exc)
{
@@ -1158,6 +1312,24 @@ _PyXI_excinfo_InitFromException(_PyXI_excinfo *info, PyObject *exc)
goto error;
}
+ // Pickle a traceback.TracebackException.
+ PyObject *tbexc = NULL;
+ if (_convert_exc_to_TracebackException(exc, &tbexc) < 0) {
+#ifdef Py_DEBUG
+ PyErr_FormatUnraisable("Exception ignored while creating TracebackException");
+#endif
+ PyErr_Clear();
+ }
+ else {
+ if (_pickle_object(tbexc, &info->pickled, &info->pickled_len) < 0) {
+#ifdef Py_DEBUG
+ PyErr_FormatUnraisable("Exception ignored while pickling TracebackException");
+#endif
+ PyErr_Clear();
+ }
+ Py_DECREF(tbexc);
+ }
+
return NULL;
error:
@@ -1169,9 +1341,28 @@ _PyXI_excinfo_InitFromException(_PyXI_excinfo *info, PyObject *exc)
static void
_PyXI_excinfo_Apply(_PyXI_excinfo *info, PyObject *exctype)
{
+ PyObject *tbexc = NULL;
+ if (info->pickled != NULL) {
+ if (_unpickle_object(info->pickled, info->pickled_len, &tbexc) < 0) {
+ PyErr_Clear();
+ }
+ }
+
PyObject *formatted = _PyXI_excinfo_format(info);
PyErr_SetObject(exctype, formatted);
Py_DECREF(formatted);
+
+ if (tbexc != NULL) {
+ PyObject *exc = PyErr_GetRaisedException();
+ if (PyObject_SetAttrString(exc, "_tbexc", tbexc) < 0) {
+#ifdef Py_DEBUG
+ PyErr_FormatUnraisable("Exception ignored when setting _tbexc");
+#endif
+ PyErr_Clear();
+ }
+ Py_DECREF(tbexc);
+ PyErr_SetRaisedException(exc);
+ }
}
static PyObject *
@@ -1277,6 +1468,20 @@ _PyXI_excinfo_AsObject(_PyXI_excinfo *info)
goto error;
}
+ if (info->pickled != NULL) {
+ PyObject *tbexc = NULL;
+ if (_unpickle_object(info->pickled, info->pickled_len, &tbexc) < 0) {
+ PyErr_Clear();
+ }
+ else {
+ res = PyObject_SetAttrString(ns, "tbexc", tbexc);
+ Py_DECREF(tbexc);
+ if (res < 0) {
+ goto error;
+ }
+ }
+ }
+
return ns;
error:
@@ -1983,6 +2188,7 @@ _capture_current_exception(_PyXI_session *session)
}
else {
failure = _PyXI_InitError(err, excval, _PyXI_ERR_UNCAUGHT_EXCEPTION);
+ Py_DECREF(excval);
if (failure == NULL && override != NULL) {
err->code = errcode;
}
@@ -1997,18 +2203,6 @@ _capture_current_exception(_PyXI_session *session)
err = NULL;
}
- // a temporary hack (famous last words)
- if (excval != NULL) {
- // XXX Store the traceback info (or rendered traceback) on
- // _PyXI_excinfo, attach it to the exception when applied,
- // and teach PyErr_Display() to print it.
-#ifdef Py_DEBUG
- // XXX Drop this once _Py_excinfo picks up the slack.
- PyErr_Display(NULL, excval, NULL);
-#endif
- Py_DECREF(excval);
- }
-
// Finished!
assert(!PyErr_Occurred());
session->error = err;
--- a PPN by Garber Painting Akron. With Image Size Reduction included!Fetched URL: http://github.com/python/cpython/pull/113034.diff
Alternative Proxies:
Alternative Proxy
pFad Proxy
pFad v3 Proxy
pFad v4 Proxy