Skip to content

Commit 76ac2de

Browse files
authored
gh-154002: Do not wrap a constructor TypeError in pickle._Unpickler (GH-154003)
It passed the traceback as the second argument to a new TypeError, so that the traceback object ended up in args and the original error was not chained. The original error now propagates, as in the C implementation.
1 parent 89e3aae commit 76ac2de

4 files changed

Lines changed: 26 additions & 5 deletions

File tree

Lib/pickle.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1623,11 +1623,7 @@ def load_dict(self):
16231623
def _instantiate(self, klass, args):
16241624
if (args or not isinstance(klass, type) or
16251625
hasattr(klass, "__getinitargs__")):
1626-
try:
1627-
value = klass(*args)
1628-
except TypeError as err:
1629-
raise TypeError("in constructor for %s: %s" %
1630-
(klass.__name__, str(err)), err.__traceback__)
1626+
value = klass(*args)
16311627
else:
16321628
value = klass.__new__(klass)
16331629
self.append(value)

Lib/test/picklecommon.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,20 @@ class E(C):
1717
def __getinitargs__(self):
1818
return ()
1919

20+
# For test_load_bad_constructor
21+
class BadConstructor:
22+
def __init__(self, *args):
23+
raise TypeError("bad constructor")
24+
2025
import __main__
2126
__main__.C = C
2227
C.__module__ = "__main__"
2328
__main__.D = D
2429
D.__module__ = "__main__"
2530
__main__.E = E
2631
E.__module__ = "__main__"
32+
__main__.BadConstructor = BadConstructor
33+
BadConstructor.__module__ = "__main__"
2734

2835
# Simple mutable object.
2936
class Object(object):

Lib/test/pickletester.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -846,6 +846,20 @@ def test_load_classic_instance(self):
846846
b'q\x00oq\x01}q\x02b.').replace(b'X', xname)
847847
self.assert_is_copy(X(*args), self.loads(pickle2))
848848

849+
def test_load_bad_constructor(self):
850+
# gh-154002: a TypeError raised by an old-style instance constructor
851+
# during INST/OBJ unpickling propagates unchanged. The pure-Python
852+
# unpickler used to replace it with one that carried the traceback
853+
# object in its args.
854+
# 0: ( MARK
855+
# 1: I INT 1
856+
# 4: i INST '__main__ BadConstructor' (MARK at 0)
857+
# 28: . STOP
858+
data = b'(I1\ni__main__\nBadConstructor\n.'
859+
with self.assertRaises(TypeError) as cm:
860+
self.loads(data)
861+
self.assertEqual(cm.exception.args, ("bad constructor",))
862+
849863
def test_maxint64(self):
850864
maxint64 = (1 << 63) - 1
851865
data = b'I' + str(maxint64).encode("ascii") + b'\n.'
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
The pure-Python :mod:`pickle` unpickler no longer replaces a :exc:`TypeError`
2+
raised by an old-style instance constructor with a new one carrying the
3+
traceback object in its ``args``. The original error now propagates, as it
4+
already did in the C implementation.

0 commit comments

Comments
 (0)