Skip to content

Commit 41c2374

Browse files
miss-islingtoniritkatrielambv
authored
[3.9] bpo-45083: Include the exception class qualname when formatting an exception (GH-28119) (GH-28135)
* bpo-45083: Include the exception class qualname when formatting an exception (GH-28119) Co-authored-by: Erlend Egeberg Aasland <erlend.aasland@innova.no> (cherry picked from commit b4b6342) Co-authored-by: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> Co-authored-by: Łukasz Langa <lukasz@langa.pl>
1 parent 98eb408 commit 41c2374

File tree

7 files changed

+79
-36
lines changed

7 files changed

+79
-36
lines changed

Include/internal/pycore_object.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ extern "C" {
1515
PyAPI_FUNC(int) _PyType_CheckConsistency(PyTypeObject *type);
1616
PyAPI_FUNC(int) _PyDict_CheckConsistency(PyObject *mp, int check_content);
1717

18+
/* Only private in Python 3.10 and 3.9.8+; public in 3.11 */
19+
extern PyObject *_PyType_GetQualName(PyTypeObject *type);
20+
1821
/* Tell the GC to track this object.
1922
*
2023
* NB: While the object is tracked by the collector, it must be safe to call the

Lib/test/test_sys.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1005,6 +1005,20 @@ def __del__(self):
10051005
self.assertIn("del is broken", report)
10061006
self.assertTrue(report.endswith("\n"))
10071007

1008+
def test_original_unraisablehook_exception_qualname(self):
1009+
class A:
1010+
class B:
1011+
class X(Exception):
1012+
pass
1013+
1014+
with test.support.captured_stderr() as stderr, \
1015+
test.support.swap_attr(sys, 'unraisablehook',
1016+
sys.__unraisablehook__):
1017+
expected = self.write_unraisable_exc(
1018+
A.B.X(), "msg", "obj");
1019+
report = stderr.getvalue()
1020+
testName = 'test_original_unraisablehook_exception_qualname'
1021+
self.assertIn(f"{testName}.<locals>.A.B.X", report)
10081022

10091023
def test_original_unraisablehook_wrong_type(self):
10101024
exc = ValueError(42)

Lib/test/test_traceback.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -759,6 +759,19 @@ def test_syntax_error_various_offsets(self):
759759
exp = "\n".join(expected)
760760
self.assertEqual(exp, err)
761761

762+
def test_format_exception_only_qualname(self):
763+
class A:
764+
class B:
765+
class X(Exception):
766+
def __str__(self):
767+
return "I am X"
768+
pass
769+
err = self.get_report(A.B.X())
770+
str_value = 'I am X'
771+
str_name = '.'.join([A.B.X.__module__, A.B.X.__qualname__])
772+
exp = "%s: %s\n" % (str_name, str_value)
773+
self.assertEqual(exp, err)
774+
762775

763776
class PyExcReportingTests(BaseExceptionReportingTests, unittest.TestCase):
764777
#
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
When the interpreter renders an exception, its name now has a complete qualname. Previously only the class name was concatenated to the module name, which sometimes resulted in an incorrect full name being displayed.
2+
3+
(This issue impacted only the C code exception rendering, the :mod:`traceback` module was using qualname already).

Objects/typeobject.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3119,6 +3119,14 @@ PyType_FromSpec(PyType_Spec *spec)
31193119
return PyType_FromSpecWithBases(spec, NULL);
31203120
}
31213121

3122+
/* private in 3.10 and 3.9.8+; public in 3.11 */
3123+
PyObject *
3124+
_PyType_GetQualName(PyTypeObject *type)
3125+
{
3126+
return type_qualname(type, NULL);
3127+
}
3128+
3129+
31223130
void *
31233131
PyType_GetSlot(PyTypeObject *type, int slot)
31243132
{

Python/errors.c

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
#include "Python.h"
55
#include "pycore_initconfig.h"
6+
#include "pycore_object.h" // _PyType_GetQualName
67
#include "pycore_pyerrors.h"
78
#include "pycore_pystate.h" // _PyThreadState_GET()
89
#include "pycore_sysmodule.h"
@@ -1321,46 +1322,45 @@ write_unraisable_exc_file(PyThreadState *tstate, PyObject *exc_type,
13211322
}
13221323

13231324
assert(PyExceptionClass_Check(exc_type));
1324-
const char *className = PyExceptionClass_Name(exc_type);
1325-
if (className != NULL) {
1326-
const char *dot = strrchr(className, '.');
1327-
if (dot != NULL) {
1328-
className = dot+1;
1329-
}
1330-
}
13311325

1332-
PyObject *moduleName = _PyObject_GetAttrId(exc_type, &PyId___module__);
1333-
if (moduleName == NULL || !PyUnicode_Check(moduleName)) {
1334-
Py_XDECREF(moduleName);
1326+
PyObject *modulename = _PyObject_GetAttrId(exc_type, &PyId___module__);
1327+
if (modulename == NULL || !PyUnicode_Check(modulename)) {
1328+
Py_XDECREF(modulename);
13351329
_PyErr_Clear(tstate);
13361330
if (PyFile_WriteString("<unknown>", file) < 0) {
13371331
return -1;
13381332
}
13391333
}
13401334
else {
1341-
if (!_PyUnicode_EqualToASCIIId(moduleName, &PyId_builtins)) {
1342-
if (PyFile_WriteObject(moduleName, file, Py_PRINT_RAW) < 0) {
1343-
Py_DECREF(moduleName);
1335+
if (!_PyUnicode_EqualToASCIIId(modulename, &PyId_builtins)) {
1336+
if (PyFile_WriteObject(modulename, file, Py_PRINT_RAW) < 0) {
1337+
Py_DECREF(modulename);
13441338
return -1;
13451339
}
1346-
Py_DECREF(moduleName);
1340+
Py_DECREF(modulename);
13471341
if (PyFile_WriteString(".", file) < 0) {
13481342
return -1;
13491343
}
13501344
}
13511345
else {
1352-
Py_DECREF(moduleName);
1346+
Py_DECREF(modulename);
13531347
}
13541348
}
1355-
if (className == NULL) {
1349+
1350+
PyObject *qualname = _PyType_GetQualName((PyTypeObject *)exc_type);
1351+
if (qualname == NULL || !PyUnicode_Check(qualname)) {
1352+
Py_XDECREF(qualname);
1353+
_PyErr_Clear(tstate);
13561354
if (PyFile_WriteString("<unknown>", file) < 0) {
13571355
return -1;
13581356
}
13591357
}
13601358
else {
1361-
if (PyFile_WriteString(className, file) < 0) {
1359+
if (PyFile_WriteObject(qualname, file, Py_PRINT_RAW) < 0) {
1360+
Py_DECREF(qualname);
13621361
return -1;
13631362
}
1363+
Py_DECREF(qualname);
13641364
}
13651365

13661366
if (exc_value && exc_value != Py_None) {

Python/pythonrun.c

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
#undef Yield /* undefine macro conflicting with <winbase.h> */
1515

1616
#include "pycore_interp.h" // PyInterpreterState.importlib
17-
#include "pycore_object.h" // _PyDebug_PrintTotalRefs()
17+
#include "pycore_object.h" // _PyDebug_PrintTotalRefs(),
18+
// _PyType_GetQualName()
1819
#include "pycore_pyerrors.h" // _PyErr_Fetch
1920
#include "pycore_pylifecycle.h" // _Py_UnhandledKeyboardInterrupt
2021
#include "pycore_pystate.h" // _PyInterpreterState_GET()
@@ -892,36 +893,37 @@ print_exception(PyObject *f, PyObject *value)
892893
/* Don't do anything else */
893894
}
894895
else {
895-
PyObject* moduleName;
896-
const char *className;
896+
PyObject* modulename;
897+
897898
_Py_IDENTIFIER(__module__);
898899
assert(PyExceptionClass_Check(type));
899-
className = PyExceptionClass_Name(type);
900-
if (className != NULL) {
901-
const char *dot = strrchr(className, '.');
902-
if (dot != NULL)
903-
className = dot+1;
904-
}
905900

906-
moduleName = _PyObject_GetAttrId(type, &PyId___module__);
907-
if (moduleName == NULL || !PyUnicode_Check(moduleName))
901+
modulename = _PyObject_GetAttrId(type, &PyId___module__);
902+
if (modulename == NULL || !PyUnicode_Check(modulename))
908903
{
909-
Py_XDECREF(moduleName);
904+
Py_XDECREF(modulename);
905+
PyErr_Clear();
910906
err = PyFile_WriteString("<unknown>", f);
911907
}
912908
else {
913-
if (!_PyUnicode_EqualToASCIIId(moduleName, &PyId_builtins))
909+
if (!_PyUnicode_EqualToASCIIId(modulename, &PyId_builtins))
914910
{
915-
err = PyFile_WriteObject(moduleName, f, Py_PRINT_RAW);
911+
err = PyFile_WriteObject(modulename, f, Py_PRINT_RAW);
916912
err += PyFile_WriteString(".", f);
917913
}
918-
Py_DECREF(moduleName);
914+
Py_DECREF(modulename);
919915
}
920916
if (err == 0) {
921-
if (className == NULL)
922-
err = PyFile_WriteString("<unknown>", f);
923-
else
924-
err = PyFile_WriteString(className, f);
917+
PyObject* qualname = _PyType_GetQualName((PyTypeObject *)type);
918+
if (qualname == NULL || !PyUnicode_Check(qualname)) {
919+
Py_XDECREF(qualname);
920+
PyErr_Clear();
921+
err = PyFile_WriteString("<unknown>", f);
922+
}
923+
else {
924+
err = PyFile_WriteObject(qualname, f, Py_PRINT_RAW);
925+
Py_DECREF(qualname);
926+
}
925927
}
926928
}
927929
if (err == 0 && (value != Py_None)) {

0 commit comments

Comments
 (0)