Skip to content

Commit

Permalink
Deserialize by using __new__ whenever possible
Browse files Browse the repository at this point in the history
Using of the class constructor and passing it the args values is not always
correct, the custom parameters could be different to the args actually recorded
by the built-in Exception class. Try to use the __new__ to create class instance
and initialize the args attribute afterwards to prevent calling constructor with
wrong arguments. Other instance attributes are initialized in a standard way by
pickle module, so this way the reconstruction of the exception is complete.

Subclasses of OSError are known exceptions to this, because they initialize
read-only attributes from the constructor argument values, so usage of the
__new__ factory method leads to lost values of those read-only attributes.

Fixes: ionelmc#65

Signed-off-by: Oldřich Jedlička <oldium.pro@gmail.com>
  • Loading branch information
oldium committed Feb 19, 2024
1 parent 1e5a2e6 commit 72a17fd
Showing 1 changed file with 24 additions and 1 deletion.
25 changes: 24 additions & 1 deletion src/tblib/pickling_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,19 @@ def pickle_traceback(tb, *, get_locals=None):
)


def unpickle_exception_with_new(func, args, cause, tb, context, suppress_context, notes):
inst = func.__new__(func)
if args is not None:
inst.args = args
inst.__cause__ = cause
inst.__traceback__ = tb
inst.__context__ = context
inst.__suppress_context__ = suppress_context
if notes is not None:
inst.__notes__ = notes
return inst


# Note: Older versions of tblib will generate pickle archives that call unpickle_exception() with
# fewer arguments. We assign default values to some of the arguments to support this.
def unpickle_exception(func, args, cause, tb, context=None, suppress_context=False, notes=None):
Expand Down Expand Up @@ -49,8 +62,18 @@ def pickle_exception(obj):
assert isinstance(rv, tuple)
assert len(rv) >= 2

# Use __new__ whenever there is no customization by __reduce__ and
# __reduce_ex__. Also OSError and descendants are known to require using
# a constructor, otherwise they do not set the errno, strerror and other
# attributes.
use_new = (
obj.__class__.__reduce__ is BaseException.__reduce__
and obj.__class__.__reduce_ex__ is BaseException.__reduce_ex__
and not isinstance(obj, OSError)
)

return (
unpickle_exception,
unpickle_exception_with_new if use_new else unpickle_exception,
rv[:2]
+ (
obj.__cause__,
Expand Down

0 comments on commit 72a17fd

Please sign in to comment.