The pure-Python pickle._Unpickler mishandles a constructor failure while unpickling old-style instances (the INST and OBJ opcodes). If the class constructor raises TypeError, _instantiate does:
raise TypeError("in constructor for %s: %s" %
(klass.__name__, str(err)), err.__traceback__)
Passing err.__traceback__ as a second positional argument puts the traceback object into the exception's args, so it leaks into str(), and there is no real chaining (__cause__ stays None).
import pickle, io
class Bad:
def __init__(self, *a):
raise TypeError("boom")
u = pickle._Unpickler(io.BytesIO())
try:
u._instantiate(Bad, (1,))
except TypeError as e:
print(e.args) # ('in constructor for Bad: boom', <traceback object ...>)
print(e.__cause__) # None
The C _pickle unpickler does not wrap the error at all, it just lets the original TypeError propagate, so this only affects the pure-Python implementation.
The fix is to chain properly and drop the traceback argument:
raise TypeError("in constructor for %s: %s" %
(klass.__name__, str(err))) from err
This is a leftover from the Python 2 way of carrying a traceback. It used to be sys.exc_info()[2] and gh-102799 mechanically turned that into err.__traceback__ without noticing the value was being passed as a constructor argument.
Found while going through devdanzin's audit of the standard library, item 15: https://gist.github.com/devdanzin/3198710e3c0128fda5e0a7b4e0768e5f
Linked PRs
The pure-Python
pickle._Unpicklermishandles a constructor failure while unpickling old-style instances (the INST and OBJ opcodes). If the class constructor raisesTypeError,_instantiatedoes:Passing
err.__traceback__as a second positional argument puts the traceback object into the exception'sargs, so it leaks intostr(), and there is no real chaining (__cause__staysNone).The C
_pickleunpickler does not wrap the error at all, it just lets the originalTypeErrorpropagate, so this only affects the pure-Python implementation.The fix is to chain properly and drop the traceback argument:
This is a leftover from the Python 2 way of carrying a traceback. It used to be
sys.exc_info()[2]and gh-102799 mechanically turned that intoerr.__traceback__without noticing the value was being passed as a constructor argument.Found while going through devdanzin's audit of the standard library, item 15: https://gist.github.com/devdanzin/3198710e3c0128fda5e0a7b4e0768e5f
Linked PRs
_Unpickler._instantiate#154003