8000 if __notes__ is not sequence, print repr(__notes__). If note is not a… · python/cpython@614378e · GitHub
[go: up one dir, main page]

Skip to content

Commit 614378e

Browse files
committed
if __notes__ is not sequence, print repr(__notes__). If note is not a string, print str(note)
1 parent dcc93ef commit 614378e

File tree

3 files changed

+78
-16
lines changed

3 files changed

+78
-16
lines changed

Lib/test/test_traceback.py

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1323,7 +1323,7 @@ def test_syntax_error_various_offsets(self):
13231323
self.assertEqual(exp, err)
13241324

13251325
def test_exception_with_note(self):
1326-
e = ValueError(42)
1326+
e = ValueError(123)
13271327
vanilla = self.get_report(e)
13281328

13291329
e.add_note('My Note')
@@ -1340,13 +1340,40 @@ def test_exception_with_note(self):
13401340
del e.__notes__
13411341
self.assertEqual(self.get_report(e), vanilla)
13421342

1343-
# non-sequence __notes__ is ignored
1344-
e.__notes__ = 42
1345-
self.assertEqual(self.get_report(e), vanilla)
1343+
def test_exception_with_invalid_notes(self):
1344+
e = ValueError(123)
1345+
vanilla = self.get_report(e)
1346+
1347+
# non-sequence __notes__
1348+
class BadThing:
1349+
def __str__(self):
1350+
return 'bad str'
1351+
1352+
def __repr__(self):
1353+
return 'bad repr'
1354+
1355+
# unprintable, non-sequence __notes__
1356+
class Unprintable:
1357+
def __repr__(self):
1358+
raise ValueError('bad value')
1359+
1360+
e.__notes__ = BadThing()
1361+
notes_repr = 'bad repr'
1362+
self.assertEqual(self.get_report(e), vanilla + notes_repr)
1363+
1364+
e.__notes__ = Unprintable()
1365+
err_msg = '<__notes__ repr() failed>'
1366+
self.assertEqual(self.get_report(e), vanilla + err_msg)
1367+
1368+
# non-string item in the __notes__ sequence
1369+
e.__notes__ = [BadThing(), 'Final Note']
1370+
bad_note = 'bad str'
1371+
self.assertEqual(self.get_report(e), vanilla + bad_note + '\nFinal Note\n')
13461372

1347-
# non-string items in the __notes__ sequence are ignored
1348-
e.__notes__ = [42, 'Final Note']
1349-
self.assertEqual(self.get_report(e), vanilla + 'Final Note\n')
1373+
# unprintable, non-string item in the __notes__ sequence
1374+
e.__notes__ = [Unprintable(), 'Final Note']
1375+
err_msg = '<note str() failed>'
1376+
self.assertEqual(self.get_report(e), vanilla + err_msg + '\nFinal Note\n')
13501377

13511378
def test_exception_with_note_with_multiple_notes(self):
13521379
e = ValueError(42)

Lib/traceback.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -163,18 +163,18 @@ def format_exception_only(exc, /, value=_sentinel):
163163
# -- not official API but folk probably use these two functions.
164164

165165
def _format_final_exc_line(etype, value):
166-
valuestr = _some_str(value)
166+
valuestr = _safe_string(value, 'exception')
167167
if value is None or not valuestr:
168168
line = "%s\n" % etype
169169
else:
170170
line = "%s: %s\n" % (etype, valuestr)
171171
return line
172172

173-
def _some_str(value):
173+
def _safe_string(value, what, func=str):
174174
try:
175-
return str(value)
175+
return func(value)
176176
except:
177-
return '<exception str() failed>'
177+
return f'<{what} {func.__name__}() failed>'
178178

179179
# --
180180

@@ -688,7 +688,7 @@ def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None,
688688
self.exc_type = exc_type
689689
# Capture now to permit freeing resources: only complication is in the
690690
# unofficial API _format_final_exc_line
691-
self._str = _some_str(exc_value)
691+
self._str = _safe_string(exc_value, 'exception')
692692
self.__notes__ = getattr(exc_value, '__notes__', None)
693693

694694
if exc_type and issubclass(exc_type, SyntaxError):
@@ -824,8 +824,11 @@ def format_exception_only(self):
824824
yield from self._format_syntax_error(stype)
825825
if isinstance(self.__notes__, collections.abc.Sequence):
826826
for note in self.__notes__:
827-
if isinstance(note, str):
828-
yield from [l + '\n' for l in note.split('\n')]
827+
if not isinstance(note, str):
828+
note = _safe_string(note, 'note')
829+
yield from [l + '\n' for l in note.split('\n')]
830+
elif self.__notes__ is not None:
831+
yield _safe_string(self.__notes__, '__notes__', func=repr)
829832

830833
def _format_syntax_error(self, stype):
831834
"""Format SyntaxError exceptions (internal helper)."""

Python/pythonrun.c

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1145,8 +1145,21 @@ print_exception_notes(struct exception_print_context *ctx, PyObject *value)
11451145
return -1;
11461146
}
11471147
if (!PySequence_Check(notes)) {
1148+
int res = 0;
1149+
if (write_indented_margin(ctx, f) < 0) {
1150+
res = -1;
1151+
}
1152+
PyObject *s = PyObject_Repr(notes);
1153+
if (s == NULL) {
1154+
PyErr_Clear();
1155+
res = PyFile_WriteString("<__notes__ repr() failed>", f);
1156+
}
1157+
else {
1158+
res = PyFile_WriteObject(s, f, Py_PRINT_RAW);
1159+
Py_DECREF(s);
1160+
}
11481161
Py_DECREF(notes);
1149-
return 0;
1162+
return res;
11501163
}
11511164
Py_ssize_t num_notes = PySequence_Length(notes);
11521165
PyObject *lines = NULL;
@@ -1176,8 +1189,27 @@ print_exception_notes(struct exception_print_context *ctx, PyObject *value)
11761189
Py_CLEAR(lines);
11771190
}
11781191
else {
1179-
/* Ignore notes which are not strings */
1192+
int res = 0;
1193+
if (write_indented_margin(ctx, f) < 0) {
1194+
res = -1;
1195+
}
1196+
PyObject *s = PyObject_Str(note);
1197+
if (s == NULL) {
1198+
PyErr_Clear();
1199+
res = PyFile_WriteString("<note str() failed>", f);
1200+
}
1201+
else {
1202+
res = PyFile_WriteObject(s, f, Py_PRINT_RAW);
1203+
Py_DECREF(s);
1204+
}
11801205
Py_DECREF(note);
1206+
if (res < 0) {
1207+
goto error;
1208+
}
1209+
if (PyFile_WriteString("\n", f) < 0) {
1210+
goto error;
1211+
}
1212+
11811213
}
11821214
}
11831215

0 commit comments

Comments
 (0)
0