mirror of
https://github.com/python/cpython.git
synced 2025-09-10 18:58:35 +00:00
Issue #6137: The pickle module now translates module names when loading
or dumping pickles with a 2.x-compatible protocol, in order to make data sharing and migration easier. This behaviour can be disabled using the new `fix_imports` optional argument.
This commit is contained in:
parent
751899a59f
commit
d9dfaa9487
8 changed files with 532 additions and 157 deletions
|
@ -141,7 +141,7 @@ an unpickler, then you call the unpickler's :meth:`load` method. The
|
||||||
The :mod:`pickle` module provides the following functions to make the pickling
|
The :mod:`pickle` module provides the following functions to make the pickling
|
||||||
process more convenient:
|
process more convenient:
|
||||||
|
|
||||||
.. function:: dump(obj, file[, protocol])
|
.. function:: dump(obj, file[, protocol, \*, fix_imports=True])
|
||||||
|
|
||||||
Write a pickled representation of *obj* to the open file object *file*. This
|
Write a pickled representation of *obj* to the open file object *file*. This
|
||||||
is equivalent to ``Pickler(file, protocol).dump(obj)``.
|
is equivalent to ``Pickler(file, protocol).dump(obj)``.
|
||||||
|
@ -158,7 +158,11 @@ process more convenient:
|
||||||
argument. It can thus be a file object opened for binary writing, a
|
argument. It can thus be a file object opened for binary writing, a
|
||||||
io.BytesIO instance, or any other custom object that meets this interface.
|
io.BytesIO instance, or any other custom object that meets this interface.
|
||||||
|
|
||||||
.. function:: dumps(obj[, protocol])
|
If *fix_imports* is True and *protocol* is less than 3, pickle will try to
|
||||||
|
map the new Python 3.x names to the old module names used in Python 2.x,
|
||||||
|
so that the pickle data stream is readable with Python 2.x.
|
||||||
|
|
||||||
|
.. function:: dumps(obj[, protocol, \*, fix_imports=True])
|
||||||
|
|
||||||
Return the pickled representation of the object as a :class:`bytes`
|
Return the pickled representation of the object as a :class:`bytes`
|
||||||
object, instead of writing it to a file.
|
object, instead of writing it to a file.
|
||||||
|
@ -171,7 +175,11 @@ process more convenient:
|
||||||
supported. The higher the protocol used, the more recent the version of
|
supported. The higher the protocol used, the more recent the version of
|
||||||
Python needed to read the pickle produced.
|
Python needed to read the pickle produced.
|
||||||
|
|
||||||
.. function:: load(file, [\*, encoding="ASCII", errors="strict"])
|
If *fix_imports* is True and *protocol* is less than 3, pickle will try to
|
||||||
|
map the new Python 3.x names to the old module names used in Python 2.x,
|
||||||
|
so that the pickle data stream is readable with Python 2.x.
|
||||||
|
|
||||||
|
.. function:: load(file, [\*, fix_imports=True, encoding="ASCII", errors="strict"])
|
||||||
|
|
||||||
Read a pickled object representation from the open file object *file* and
|
Read a pickled object representation from the open file object *file* and
|
||||||
return the reconstituted object hierarchy specified therein. This is
|
return the reconstituted object hierarchy specified therein. This is
|
||||||
|
@ -187,11 +195,14 @@ process more convenient:
|
||||||
for reading, a BytesIO object, or any other custom object that meets this
|
for reading, a BytesIO object, or any other custom object that meets this
|
||||||
interface.
|
interface.
|
||||||
|
|
||||||
Optional keyword arguments are encoding and errors, which are used to decode
|
Optional keyword arguments are *fix_imports*, *encoding* and *errors*,
|
||||||
8-bit string instances pickled by Python 2.x. These default to 'ASCII' and
|
which are used to control compatiblity support for pickle stream generated
|
||||||
'strict', respectively.
|
by Python 2.x. If *fix_imports* is True, pickle will try to map the old
|
||||||
|
Python 2.x names to the new names used in Python 3.x. The *encoding* and
|
||||||
|
*errors* tell pickle how to decode 8-bit string instances pickled by Python
|
||||||
|
2.x; these default to 'ASCII' and 'strict', respectively.
|
||||||
|
|
||||||
.. function:: loads(bytes_object, [\*, encoding="ASCII", errors="strict"])
|
.. function:: loads(bytes_object, [\*, fix_imports=True, encoding="ASCII", errors="strict"])
|
||||||
|
|
||||||
Read a pickled object hierarchy from a :class:`bytes` object and return the
|
Read a pickled object hierarchy from a :class:`bytes` object and return the
|
||||||
reconstituted object hierarchy specified therein
|
reconstituted object hierarchy specified therein
|
||||||
|
@ -200,9 +211,12 @@ process more convenient:
|
||||||
argument is needed. Bytes past the pickled object's representation are
|
argument is needed. Bytes past the pickled object's representation are
|
||||||
ignored.
|
ignored.
|
||||||
|
|
||||||
Optional keyword arguments are encoding and errors, which are used to decode
|
Optional keyword arguments are *fix_imports*, *encoding* and *errors*,
|
||||||
8-bit string instances pickled by Python 2.x. These default to 'ASCII' and
|
which are used to control compatiblity support for pickle stream generated
|
||||||
'strict', respectively.
|
by Python 2.x. If *fix_imports* is True, pickle will try to map the old
|
||||||
|
Python 2.x names to the new names used in Python 3.x. The *encoding* and
|
||||||
|
*errors* tell pickle how to decode 8-bit string instances pickled by Python
|
||||||
|
2.x; these default to 'ASCII' and 'strict', respectively.
|
||||||
|
|
||||||
|
|
||||||
The :mod:`pickle` module defines three exceptions:
|
The :mod:`pickle` module defines three exceptions:
|
||||||
|
@ -233,7 +247,7 @@ The :mod:`pickle` module defines three exceptions:
|
||||||
The :mod:`pickle` module exports two classes, :class:`Pickler` and
|
The :mod:`pickle` module exports two classes, :class:`Pickler` and
|
||||||
:class:`Unpickler`:
|
:class:`Unpickler`:
|
||||||
|
|
||||||
.. class:: Pickler(file[, protocol])
|
.. class:: Pickler(file[, protocol, \*, fix_imports=True])
|
||||||
|
|
||||||
This takes a binary file for writing a pickle data stream.
|
This takes a binary file for writing a pickle data stream.
|
||||||
|
|
||||||
|
@ -249,6 +263,10 @@ The :mod:`pickle` module exports two classes, :class:`Pickler` and
|
||||||
argument. It can thus be a file object opened for binary writing, a
|
argument. It can thus be a file object opened for binary writing, a
|
||||||
io.BytesIO instance, or any other custom object that meets this interface.
|
io.BytesIO instance, or any other custom object that meets this interface.
|
||||||
|
|
||||||
|
If *fix_imports* is True and *protocol* is less than 3, pickle will try to
|
||||||
|
map the new Python 3.x names to the old module names used in Python 2.x,
|
||||||
|
so that the pickle data stream is readable with Python 2.x.
|
||||||
|
|
||||||
.. method:: dump(obj)
|
.. method:: dump(obj)
|
||||||
|
|
||||||
Write a pickled representation of *obj* to the open file object given in
|
Write a pickled representation of *obj* to the open file object given in
|
||||||
|
@ -277,7 +295,7 @@ The :mod:`pickle` module exports two classes, :class:`Pickler` and
|
||||||
Use :func:`pickletools.optimize` if you need more compact pickles.
|
Use :func:`pickletools.optimize` if you need more compact pickles.
|
||||||
|
|
||||||
|
|
||||||
.. class:: Unpickler(file, [\*, encoding="ASCII", errors="strict"])
|
.. class:: Unpickler(file, [\*, fix_imports=True, encoding="ASCII", errors="strict"])
|
||||||
|
|
||||||
This takes a binary file for reading a pickle data stream.
|
This takes a binary file for reading a pickle data stream.
|
||||||
|
|
||||||
|
@ -290,9 +308,12 @@ The :mod:`pickle` module exports two classes, :class:`Pickler` and
|
||||||
for reading, a BytesIO object, or any other custom object that meets this
|
for reading, a BytesIO object, or any other custom object that meets this
|
||||||
interface.
|
interface.
|
||||||
|
|
||||||
Optional keyword arguments are encoding and errors, which are used to decode
|
Optional keyword arguments are *fix_imports*, *encoding* and *errors*,
|
||||||
8-bit string instances pickled by Python 2.x. These default to 'ASCII' and
|
which are used to control compatiblity support for pickle stream generated
|
||||||
'strict', respectively.
|
by Python 2.x. If *fix_imports* is True, pickle will try to map the old
|
||||||
|
Python 2.x names to the new names used in Python 3.x. The *encoding* and
|
||||||
|
*errors* tell pickle how to decode 8-bit string instances pickled by Python
|
||||||
|
2.x; these default to 'ASCII' and 'strict', respectively.
|
||||||
|
|
||||||
.. method:: load()
|
.. method:: load()
|
||||||
|
|
||||||
|
|
81
Lib/_compat_pickle.py
Normal file
81
Lib/_compat_pickle.py
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
# This module is used to map the old Python 2 names to the new names used in
|
||||||
|
# Python 3 for the pickle module. This needed to make pickle streams
|
||||||
|
# generated with Python 2 loadable by Python 3.
|
||||||
|
|
||||||
|
# This is a copy of lib2to3.fixes.fix_imports.MAPPING. We cannot import
|
||||||
|
# lib2to3 and use the mapping defined there, because lib2to3 uses pickle.
|
||||||
|
# Thus, this could cause the module to be imported recursively.
|
||||||
|
IMPORT_MAPPING = {
|
||||||
|
'StringIO': 'io',
|
||||||
|
'cStringIO': 'io',
|
||||||
|
'cPickle': 'pickle',
|
||||||
|
'__builtin__' : 'builtins',
|
||||||
|
'copy_reg': 'copyreg',
|
||||||
|
'Queue': 'queue',
|
||||||
|
'SocketServer': 'socketserver',
|
||||||
|
'ConfigParser': 'configparser',
|
||||||
|
'repr': 'reprlib',
|
||||||
|
'FileDialog': 'tkinter.filedialog',
|
||||||
|
'tkFileDialog': 'tkinter.filedialog',
|
||||||
|
'SimpleDialog': 'tkinter.simpledialog',
|
||||||
|
'tkSimpleDialog': 'tkinter.simpledialog',
|
||||||
|
'tkColorChooser': 'tkinter.colorchooser',
|
||||||
|
'tkCommonDialog': 'tkinter.commondialog',
|
||||||
|
'Dialog': 'tkinter.dialog',
|
||||||
|
'Tkdnd': 'tkinter.dnd',
|
||||||
|
'tkFont': 'tkinter.font',
|
||||||
|
'tkMessageBox': 'tkinter.messagebox',
|
||||||
|
'ScrolledText': 'tkinter.scrolledtext',
|
||||||
|
'Tkconstants': 'tkinter.constants',
|
||||||
|
'Tix': 'tkinter.tix',
|
||||||
|
'ttk': 'tkinter.ttk',
|
||||||
|
'Tkinter': 'tkinter',
|
||||||
|
'markupbase': '_markupbase',
|
||||||
|
'_winreg': 'winreg',
|
||||||
|
'thread': '_thread',
|
||||||
|
'dummy_thread': '_dummy_thread',
|
||||||
|
'dbhash': 'dbm.bsd',
|
||||||
|
'dumbdbm': 'dbm.dumb',
|
||||||
|
'dbm': 'dbm.ndbm',
|
||||||
|
'gdbm': 'dbm.gnu',
|
||||||
|
'xmlrpclib': 'xmlrpc.client',
|
||||||
|
'DocXMLRPCServer': 'xmlrpc.server',
|
||||||
|
'SimpleXMLRPCServer': 'xmlrpc.server',
|
||||||
|
'httplib': 'http.client',
|
||||||
|
'htmlentitydefs' : 'html.entities',
|
||||||
|
'HTMLParser' : 'html.parser',
|
||||||
|
'Cookie': 'http.cookies',
|
||||||
|
'cookielib': 'http.cookiejar',
|
||||||
|
'BaseHTTPServer': 'http.server',
|
||||||
|
'SimpleHTTPServer': 'http.server',
|
||||||
|
'CGIHTTPServer': 'http.server',
|
||||||
|
'test.test_support': 'test.support',
|
||||||
|
'commands': 'subprocess',
|
||||||
|
'UserString' : 'collections',
|
||||||
|
'UserList' : 'collections',
|
||||||
|
'urlparse' : 'urllib.parse',
|
||||||
|
'robotparser' : 'urllib.robotparser',
|
||||||
|
'whichdb': 'dbm',
|
||||||
|
'anydbm': 'dbm'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# This contains rename rules that are easy to handle. We ignore the more
|
||||||
|
# complex stuff (e.g. mapping the names in the urllib and types modules).
|
||||||
|
# These rules should be run before import names are fixed.
|
||||||
|
NAME_MAPPING = {
|
||||||
|
('__builtin__', 'xrange'): ('builtins', 'range'),
|
||||||
|
('__builtin__', 'reduce'): ('functools', 'reduce'),
|
||||||
|
('__builtin__', 'intern'): ('sys', 'intern'),
|
||||||
|
('__builtin__', 'unichr'): ('builtins', 'chr'),
|
||||||
|
('__builtin__', 'basestring'): ('builtins', 'str'),
|
||||||
|
('__builtin__', 'long'): ('builtins', 'int'),
|
||||||
|
('itertools', 'izip'): ('builtins', 'zip'),
|
||||||
|
('itertools', 'imap'): ('builtins', 'map'),
|
||||||
|
('itertools', 'ifilter'): ('builtins', 'filter'),
|
||||||
|
('itertools', 'ifilterfalse'): ('itertools', 'filterfalse'),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Same, but for 3.x to 2.x
|
||||||
|
REVERSE_IMPORT_MAPPING = dict((v, k) for (k, v) in IMPORT_MAPPING.items())
|
||||||
|
REVERSE_NAME_MAPPING = dict((v, k) for (k, v) in NAME_MAPPING.items())
|
|
@ -34,6 +34,7 @@ import struct
|
||||||
import re
|
import re
|
||||||
import io
|
import io
|
||||||
import codecs
|
import codecs
|
||||||
|
import _compat_pickle
|
||||||
|
|
||||||
__all__ = ["PickleError", "PicklingError", "UnpicklingError", "Pickler",
|
__all__ = ["PickleError", "PicklingError", "UnpicklingError", "Pickler",
|
||||||
"Unpickler", "dump", "dumps", "load", "loads"]
|
"Unpickler", "dump", "dumps", "load", "loads"]
|
||||||
|
@ -171,12 +172,11 @@ SHORT_BINBYTES = b'C' # " " ; " " " " < 256 bytes
|
||||||
|
|
||||||
__all__.extend([x for x in dir() if re.match("[A-Z][A-Z0-9_]+$",x)])
|
__all__.extend([x for x in dir() if re.match("[A-Z][A-Z0-9_]+$",x)])
|
||||||
|
|
||||||
|
|
||||||
# Pickling machinery
|
# Pickling machinery
|
||||||
|
|
||||||
class _Pickler:
|
class _Pickler:
|
||||||
|
|
||||||
def __init__(self, file, protocol=None):
|
def __init__(self, file, protocol=None, *, fix_imports=True):
|
||||||
"""This takes a binary file for writing a pickle data stream.
|
"""This takes a binary file for writing a pickle data stream.
|
||||||
|
|
||||||
The optional protocol argument tells the pickler to use the
|
The optional protocol argument tells the pickler to use the
|
||||||
|
@ -193,6 +193,10 @@ class _Pickler:
|
||||||
bytes argument. It can thus be a file object opened for binary
|
bytes argument. It can thus be a file object opened for binary
|
||||||
writing, a io.BytesIO instance, or any other custom object that
|
writing, a io.BytesIO instance, or any other custom object that
|
||||||
meets this interface.
|
meets this interface.
|
||||||
|
|
||||||
|
If fix_imports is True and protocol is less than 3, pickle will try to
|
||||||
|
map the new Python 3.x names to the old module names used in Python
|
||||||
|
2.x, so that the pickle data stream is readable with Python 2.x.
|
||||||
"""
|
"""
|
||||||
if protocol is None:
|
if protocol is None:
|
||||||
protocol = DEFAULT_PROTOCOL
|
protocol = DEFAULT_PROTOCOL
|
||||||
|
@ -208,6 +212,7 @@ class _Pickler:
|
||||||
self.proto = int(protocol)
|
self.proto = int(protocol)
|
||||||
self.bin = protocol >= 1
|
self.bin = protocol >= 1
|
||||||
self.fast = 0
|
self.fast = 0
|
||||||
|
self.fix_imports = fix_imports and protocol < 3
|
||||||
|
|
||||||
def clear_memo(self):
|
def clear_memo(self):
|
||||||
"""Clears the pickler's "memo".
|
"""Clears the pickler's "memo".
|
||||||
|
@ -698,6 +703,11 @@ class _Pickler:
|
||||||
write(GLOBAL + bytes(module, "utf-8") + b'\n' +
|
write(GLOBAL + bytes(module, "utf-8") + b'\n' +
|
||||||
bytes(name, "utf-8") + b'\n')
|
bytes(name, "utf-8") + b'\n')
|
||||||
else:
|
else:
|
||||||
|
if self.fix_imports:
|
||||||
|
if (module, name) in _compat_pickle.REVERSE_NAME_MAPPING:
|
||||||
|
module, name = _compat_pickle.REVERSE_NAME_MAPPING[(module, name)]
|
||||||
|
if module in _compat_pickle.REVERSE_IMPORT_MAPPING:
|
||||||
|
module = _compat_pickle.REVERSE_IMPORT_MAPPING[module]
|
||||||
try:
|
try:
|
||||||
write(GLOBAL + bytes(module, "ascii") + b'\n' +
|
write(GLOBAL + bytes(module, "ascii") + b'\n' +
|
||||||
bytes(name, "ascii") + b'\n')
|
bytes(name, "ascii") + b'\n')
|
||||||
|
@ -766,7 +776,8 @@ def whichmodule(func, funcname):
|
||||||
|
|
||||||
class _Unpickler:
|
class _Unpickler:
|
||||||
|
|
||||||
def __init__(self, file, *, encoding="ASCII", errors="strict"):
|
def __init__(self, file, *, fix_imports=True,
|
||||||
|
encoding="ASCII", errors="strict"):
|
||||||
"""This takes a binary file for reading a pickle data stream.
|
"""This takes a binary file for reading a pickle data stream.
|
||||||
|
|
||||||
The protocol version of the pickle is detected automatically, so no
|
The protocol version of the pickle is detected automatically, so no
|
||||||
|
@ -779,15 +790,21 @@ class _Unpickler:
|
||||||
reading, a BytesIO object, or any other custom object that
|
reading, a BytesIO object, or any other custom object that
|
||||||
meets this interface.
|
meets this interface.
|
||||||
|
|
||||||
Optional keyword arguments are encoding and errors, which are
|
Optional keyword arguments are *fix_imports*, *encoding* and *errors*,
|
||||||
used to decode 8-bit string instances pickled by Python 2.x.
|
which are used to control compatiblity support for pickle stream
|
||||||
These default to 'ASCII' and 'strict', respectively.
|
generated by Python 2.x. If *fix_imports* is True, pickle will try to
|
||||||
|
map the old Python 2.x names to the new names used in Python 3.x. The
|
||||||
|
*encoding* and *errors* tell pickle how to decode 8-bit string
|
||||||
|
instances pickled by Python 2.x; these default to 'ASCII' and
|
||||||
|
'strict', respectively.
|
||||||
"""
|
"""
|
||||||
self.readline = file.readline
|
self.readline = file.readline
|
||||||
self.read = file.read
|
self.read = file.read
|
||||||
self.memo = {}
|
self.memo = {}
|
||||||
self.encoding = encoding
|
self.encoding = encoding
|
||||||
self.errors = errors
|
self.errors = errors
|
||||||
|
self.proto = 0
|
||||||
|
self.fix_imports = fix_imports
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
"""Read a pickled object representation from the open file.
|
"""Read a pickled object representation from the open file.
|
||||||
|
@ -838,6 +855,7 @@ class _Unpickler:
|
||||||
proto = ord(self.read(1))
|
proto = ord(self.read(1))
|
||||||
if not 0 <= proto <= HIGHEST_PROTOCOL:
|
if not 0 <= proto <= HIGHEST_PROTOCOL:
|
||||||
raise ValueError("unsupported pickle protocol: %d" % proto)
|
raise ValueError("unsupported pickle protocol: %d" % proto)
|
||||||
|
self.proto = proto
|
||||||
dispatch[PROTO[0]] = load_proto
|
dispatch[PROTO[0]] = load_proto
|
||||||
|
|
||||||
def load_persid(self):
|
def load_persid(self):
|
||||||
|
@ -1088,7 +1106,12 @@ class _Unpickler:
|
||||||
self.append(obj)
|
self.append(obj)
|
||||||
|
|
||||||
def find_class(self, module, name):
|
def find_class(self, module, name):
|
||||||
# Subclasses may override this
|
# Subclasses may override this.
|
||||||
|
if self.proto < 3 and self.fix_imports:
|
||||||
|
if (module, name) in _compat_pickle.NAME_MAPPING:
|
||||||
|
module, name = _compat_pickle.NAME_MAPPING[(module, name)]
|
||||||
|
if module in _compat_pickle.IMPORT_MAPPING:
|
||||||
|
module = _compat_pickle.IMPORT_MAPPING[module]
|
||||||
__import__(module, level=0)
|
__import__(module, level=0)
|
||||||
mod = sys.modules[module]
|
mod = sys.modules[module]
|
||||||
klass = getattr(mod, name)
|
klass = getattr(mod, name)
|
||||||
|
@ -1327,27 +1350,28 @@ except ImportError:
|
||||||
|
|
||||||
# Shorthands
|
# Shorthands
|
||||||
|
|
||||||
def dump(obj, file, protocol=None):
|
def dump(obj, file, protocol=None, *, fix_imports=True):
|
||||||
Pickler(file, protocol).dump(obj)
|
Pickler(file, protocol, fix_imports=fix_imports).dump(obj)
|
||||||
|
|
||||||
def dumps(obj, protocol=None):
|
def dumps(obj, protocol=None, *, fix_imports=True):
|
||||||
f = io.BytesIO()
|
f = io.BytesIO()
|
||||||
Pickler(f, protocol).dump(obj)
|
Pickler(f, protocol, fix_imports=fix_imports).dump(obj)
|
||||||
res = f.getvalue()
|
res = f.getvalue()
|
||||||
assert isinstance(res, bytes_types)
|
assert isinstance(res, bytes_types)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def load(file, *, encoding="ASCII", errors="strict"):
|
def load(file, *, fix_imports=True, encoding="ASCII", errors="strict"):
|
||||||
return Unpickler(file, encoding=encoding, errors=errors).load()
|
return Unpickler(file, fix_imports=fix_imports,
|
||||||
|
encoding=encoding, errors=errors).load()
|
||||||
|
|
||||||
def loads(s, *, encoding="ASCII", errors="strict"):
|
def loads(s, *, fix_imports=True, encoding="ASCII", errors="strict"):
|
||||||
if isinstance(s, str):
|
if isinstance(s, str):
|
||||||
raise TypeError("Can't load pickle from unicode string")
|
raise TypeError("Can't load pickle from unicode string")
|
||||||
file = io.BytesIO(s)
|
file = io.BytesIO(s)
|
||||||
return Unpickler(file, encoding=encoding, errors=errors).load()
|
return Unpickler(file, fix_imports=fix_imports,
|
||||||
|
encoding=encoding, errors=errors).load()
|
||||||
|
|
||||||
# Doctest
|
# Doctest
|
||||||
|
|
||||||
def _test():
|
def _test():
|
||||||
import doctest
|
import doctest
|
||||||
return doctest.testmod()
|
return doctest.testmod()
|
||||||
|
|
|
@ -2066,27 +2066,27 @@ _dis_test = r"""
|
||||||
29: ( MARK
|
29: ( MARK
|
||||||
30: d DICT (MARK at 29)
|
30: d DICT (MARK at 29)
|
||||||
31: p PUT 2
|
31: p PUT 2
|
||||||
34: c GLOBAL 'builtins bytes'
|
34: c GLOBAL '__builtin__ bytes'
|
||||||
50: p PUT 3
|
53: p PUT 3
|
||||||
53: ( MARK
|
56: ( MARK
|
||||||
54: ( MARK
|
57: ( MARK
|
||||||
55: l LIST (MARK at 54)
|
58: l LIST (MARK at 57)
|
||||||
56: p PUT 4
|
59: p PUT 4
|
||||||
59: L LONG 97
|
62: L LONG 97
|
||||||
64: a APPEND
|
67: a APPEND
|
||||||
65: L LONG 98
|
68: L LONG 98
|
||||||
70: a APPEND
|
73: a APPEND
|
||||||
71: L LONG 99
|
74: L LONG 99
|
||||||
76: a APPEND
|
79: a APPEND
|
||||||
77: t TUPLE (MARK at 53)
|
80: t TUPLE (MARK at 56)
|
||||||
78: p PUT 5
|
81: p PUT 5
|
||||||
81: R REDUCE
|
84: R REDUCE
|
||||||
82: p PUT 6
|
85: p PUT 6
|
||||||
85: V UNICODE 'def'
|
88: V UNICODE 'def'
|
||||||
90: p PUT 7
|
93: p PUT 7
|
||||||
93: s SETITEM
|
96: s SETITEM
|
||||||
94: a APPEND
|
97: a APPEND
|
||||||
95: . STOP
|
98: . STOP
|
||||||
highest protocol among opcodes = 0
|
highest protocol among opcodes = 0
|
||||||
|
|
||||||
Try again with a "binary" pickle.
|
Try again with a "binary" pickle.
|
||||||
|
@ -2105,25 +2105,25 @@ Try again with a "binary" pickle.
|
||||||
14: q BINPUT 1
|
14: q BINPUT 1
|
||||||
16: } EMPTY_DICT
|
16: } EMPTY_DICT
|
||||||
17: q BINPUT 2
|
17: q BINPUT 2
|
||||||
19: c GLOBAL 'builtins bytes'
|
19: c GLOBAL '__builtin__ bytes'
|
||||||
35: q BINPUT 3
|
38: q BINPUT 3
|
||||||
37: ( MARK
|
40: ( MARK
|
||||||
38: ] EMPTY_LIST
|
41: ] EMPTY_LIST
|
||||||
39: q BINPUT 4
|
42: q BINPUT 4
|
||||||
41: ( MARK
|
44: ( MARK
|
||||||
42: K BININT1 97
|
45: K BININT1 97
|
||||||
44: K BININT1 98
|
47: K BININT1 98
|
||||||
46: K BININT1 99
|
49: K BININT1 99
|
||||||
48: e APPENDS (MARK at 41)
|
51: e APPENDS (MARK at 44)
|
||||||
49: t TUPLE (MARK at 37)
|
52: t TUPLE (MARK at 40)
|
||||||
50: q BINPUT 5
|
53: q BINPUT 5
|
||||||
52: R REDUCE
|
55: R REDUCE
|
||||||
53: q BINPUT 6
|
56: q BINPUT 6
|
||||||
55: X BINUNICODE 'def'
|
58: X BINUNICODE 'def'
|
||||||
63: q BINPUT 7
|
66: q BINPUT 7
|
||||||
65: s SETITEM
|
68: s SETITEM
|
||||||
66: e APPENDS (MARK at 3)
|
69: e APPENDS (MARK at 3)
|
||||||
67: . STOP
|
70: . STOP
|
||||||
highest protocol among opcodes = 1
|
highest protocol among opcodes = 1
|
||||||
|
|
||||||
Exercise the INST/OBJ/BUILD family.
|
Exercise the INST/OBJ/BUILD family.
|
||||||
|
@ -2141,58 +2141,58 @@ highest protocol among opcodes = 0
|
||||||
0: ( MARK
|
0: ( MARK
|
||||||
1: l LIST (MARK at 0)
|
1: l LIST (MARK at 0)
|
||||||
2: p PUT 0
|
2: p PUT 0
|
||||||
5: c GLOBAL 'copyreg _reconstructor'
|
5: c GLOBAL 'copy_reg _reconstructor'
|
||||||
29: p PUT 1
|
30: p PUT 1
|
||||||
32: ( MARK
|
33: ( MARK
|
||||||
33: c GLOBAL 'pickletools _Example'
|
34: c GLOBAL 'pickletools _Example'
|
||||||
55: p PUT 2
|
56: p PUT 2
|
||||||
58: c GLOBAL 'builtins object'
|
59: c GLOBAL '__builtin__ object'
|
||||||
75: p PUT 3
|
79: p PUT 3
|
||||||
78: N NONE
|
82: N NONE
|
||||||
79: t TUPLE (MARK at 32)
|
83: t TUPLE (MARK at 33)
|
||||||
80: p PUT 4
|
84: p PUT 4
|
||||||
83: R REDUCE
|
87: R REDUCE
|
||||||
84: p PUT 5
|
88: p PUT 5
|
||||||
87: ( MARK
|
91: ( MARK
|
||||||
88: d DICT (MARK at 87)
|
92: d DICT (MARK at 91)
|
||||||
89: p PUT 6
|
93: p PUT 6
|
||||||
92: V UNICODE 'value'
|
96: V UNICODE 'value'
|
||||||
99: p PUT 7
|
103: p PUT 7
|
||||||
102: L LONG 42
|
106: L LONG 42
|
||||||
107: s SETITEM
|
111: s SETITEM
|
||||||
108: b BUILD
|
112: b BUILD
|
||||||
109: a APPEND
|
|
||||||
110: g GET 5
|
|
||||||
113: a APPEND
|
113: a APPEND
|
||||||
114: . STOP
|
114: g GET 5
|
||||||
|
117: a APPEND
|
||||||
|
118: . STOP
|
||||||
highest protocol among opcodes = 0
|
highest protocol among opcodes = 0
|
||||||
|
|
||||||
>>> dis(pickle.dumps(x, 1))
|
>>> dis(pickle.dumps(x, 1))
|
||||||
0: ] EMPTY_LIST
|
0: ] EMPTY_LIST
|
||||||
1: q BINPUT 0
|
1: q BINPUT 0
|
||||||
3: ( MARK
|
3: ( MARK
|
||||||
4: c GLOBAL 'copyreg _reconstructor'
|
4: c GLOBAL 'copy_reg _reconstructor'
|
||||||
28: q BINPUT 1
|
29: q BINPUT 1
|
||||||
30: ( MARK
|
31: ( MARK
|
||||||
31: c GLOBAL 'pickletools _Example'
|
32: c GLOBAL 'pickletools _Example'
|
||||||
53: q BINPUT 2
|
54: q BINPUT 2
|
||||||
55: c GLOBAL 'builtins object'
|
56: c GLOBAL '__builtin__ object'
|
||||||
72: q BINPUT 3
|
76: q BINPUT 3
|
||||||
74: N NONE
|
78: N NONE
|
||||||
75: t TUPLE (MARK at 30)
|
79: t TUPLE (MARK at 31)
|
||||||
76: q BINPUT 4
|
80: q BINPUT 4
|
||||||
78: R REDUCE
|
82: R REDUCE
|
||||||
79: q BINPUT 5
|
83: q BINPUT 5
|
||||||
81: } EMPTY_DICT
|
85: } EMPTY_DICT
|
||||||
82: q BINPUT 6
|
86: q BINPUT 6
|
||||||
84: X BINUNICODE 'value'
|
88: X BINUNICODE 'value'
|
||||||
94: q BINPUT 7
|
98: q BINPUT 7
|
||||||
96: K BININT1 42
|
100: K BININT1 42
|
||||||
98: s SETITEM
|
102: s SETITEM
|
||||||
99: b BUILD
|
103: b BUILD
|
||||||
100: h BINGET 5
|
104: h BINGET 5
|
||||||
102: e APPENDS (MARK at 3)
|
106: e APPENDS (MARK at 3)
|
||||||
103: . STOP
|
107: . STOP
|
||||||
highest protocol among opcodes = 1
|
highest protocol among opcodes = 1
|
||||||
|
|
||||||
Try "the canonical" recursive-object test.
|
Try "the canonical" recursive-object test.
|
||||||
|
|
|
@ -3,6 +3,7 @@ import unittest
|
||||||
import pickle
|
import pickle
|
||||||
import pickletools
|
import pickletools
|
||||||
import copyreg
|
import copyreg
|
||||||
|
from http.cookies import SimpleCookie
|
||||||
|
|
||||||
from test.support import TestFailed, TESTFN, run_with_locale
|
from test.support import TestFailed, TESTFN, run_with_locale
|
||||||
|
|
||||||
|
@ -342,6 +343,24 @@ DATA2_DIS = """\
|
||||||
highest protocol among opcodes = 2
|
highest protocol among opcodes = 2
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# set([1,2]) pickled from 2.x with protocol 2
|
||||||
|
DATA3 = b'\x80\x02c__builtin__\nset\nq\x00]q\x01(K\x01K\x02e\x85q\x02Rq\x03.'
|
||||||
|
|
||||||
|
# xrange(5) pickled from 2.x with protocol 2
|
||||||
|
DATA4 = b'\x80\x02c__builtin__\nxrange\nq\x00K\x00K\x05K\x01\x87q\x01Rq\x02.'
|
||||||
|
|
||||||
|
# a SimpleCookie() object pickled from 2.x with protocol 2
|
||||||
|
DATA5 = (b'\x80\x02cCookie\nSimpleCookie\nq\x00)\x81q\x01U\x03key'
|
||||||
|
b'q\x02cCookie\nMorsel\nq\x03)\x81q\x04(U\x07commentq\x05U'
|
||||||
|
b'\x00q\x06U\x06domainq\x07h\x06U\x06secureq\x08h\x06U\x07'
|
||||||
|
b'expiresq\th\x06U\x07max-ageq\nh\x06U\x07versionq\x0bh\x06U'
|
||||||
|
b'\x04pathq\x0ch\x06U\x08httponlyq\rh\x06u}q\x0e(U\x0b'
|
||||||
|
b'coded_valueq\x0fU\x05valueq\x10h\x10h\x10h\x02h\x02ubs}q\x11b.')
|
||||||
|
|
||||||
|
# set([3]) pickled from 2.x with protocol 2
|
||||||
|
DATA6 = b'\x80\x02c__builtin__\nset\nq\x00]q\x01K\x03a\x85q\x02Rq\x03.'
|
||||||
|
|
||||||
|
|
||||||
def create_data():
|
def create_data():
|
||||||
c = C()
|
c = C()
|
||||||
c.foo = 1
|
c.foo = 1
|
||||||
|
@ -956,6 +975,29 @@ class AbstractPickleTests(unittest.TestCase):
|
||||||
for x_key, y_key in zip(x_keys, y_keys):
|
for x_key, y_key in zip(x_keys, y_keys):
|
||||||
self.assertIs(x_key, y_key)
|
self.assertIs(x_key, y_key)
|
||||||
|
|
||||||
|
def test_unpickle_from_2x(self):
|
||||||
|
# Unpickle non-trivial data from Python 2.x.
|
||||||
|
loaded = self.loads(DATA3)
|
||||||
|
self.assertEqual(loaded, set([1, 2]))
|
||||||
|
loaded = self.loads(DATA4)
|
||||||
|
self.assertEqual(type(loaded), type(range(0)))
|
||||||
|
self.assertEqual(list(loaded), list(range(5)))
|
||||||
|
loaded = self.loads(DATA5)
|
||||||
|
self.assertEqual(type(loaded), SimpleCookie)
|
||||||
|
self.assertEqual(list(loaded.keys()), ["key"])
|
||||||
|
self.assertEqual(loaded["key"].value, "Set-Cookie: key=value")
|
||||||
|
|
||||||
|
def test_pickle_to_2x(self):
|
||||||
|
# Pickle non-trivial data with protocol 2, expecting that it yields
|
||||||
|
# the same result as Python 2.x did.
|
||||||
|
# NOTE: this test is a bit too strong since we can produce different
|
||||||
|
# bytecode that 2.x will still understand.
|
||||||
|
dumped = self.dumps(range(5), 2)
|
||||||
|
self.assertEqual(dumped, DATA4)
|
||||||
|
dumped = self.dumps(set([3]), 2)
|
||||||
|
self.assertEqual(dumped, DATA6)
|
||||||
|
|
||||||
|
|
||||||
# Test classes for reduce_ex
|
# Test classes for reduce_ex
|
||||||
|
|
||||||
class REX_one(object):
|
class REX_one(object):
|
||||||
|
|
|
@ -12,6 +12,9 @@ class OptimizedPickleTests(AbstractPickleTests, AbstractPickleModuleTests):
|
||||||
def loads(self, buf):
|
def loads(self, buf):
|
||||||
return pickle.loads(buf)
|
return pickle.loads(buf)
|
||||||
|
|
||||||
|
# Test relies on precise output of dumps()
|
||||||
|
test_pickle_to_2x = None
|
||||||
|
|
||||||
|
|
||||||
def test_main():
|
def test_main():
|
||||||
support.run_unittest(OptimizedPickleTests)
|
support.run_unittest(OptimizedPickleTests)
|
||||||
|
|
|
@ -21,6 +21,11 @@ Core and Builtins
|
||||||
Library
|
Library
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
- Issue #6137: The pickle module now translates module names when loading
|
||||||
|
or dumping pickles with a 2.x-compatible protocol, in order to make data
|
||||||
|
sharing and migration easier. This behaviour can be disabled using the
|
||||||
|
new `fix_imports` optional argument.
|
||||||
|
|
||||||
- Removed the ipaddr module.
|
- Removed the ipaddr module.
|
||||||
|
|
||||||
- Issue #3613: base64.{encode,decode}string are now called
|
- Issue #3613: base64.{encode,decode}string are now called
|
||||||
|
|
|
@ -103,25 +103,33 @@ enum {
|
||||||
|
|
||||||
/* Exception classes for pickle. These should override the ones defined in
|
/* Exception classes for pickle. These should override the ones defined in
|
||||||
pickle.py, when the C-optimized Pickler and Unpickler are used. */
|
pickle.py, when the C-optimized Pickler and Unpickler are used. */
|
||||||
static PyObject *PickleError;
|
static PyObject *PickleError = NULL;
|
||||||
static PyObject *PicklingError;
|
static PyObject *PicklingError = NULL;
|
||||||
static PyObject *UnpicklingError;
|
static PyObject *UnpicklingError = NULL;
|
||||||
|
|
||||||
/* copyreg.dispatch_table, {type_object: pickling_function} */
|
/* copyreg.dispatch_table, {type_object: pickling_function} */
|
||||||
static PyObject *dispatch_table;
|
static PyObject *dispatch_table = NULL;
|
||||||
/* For EXT[124] opcodes. */
|
/* For EXT[124] opcodes. */
|
||||||
/* copyreg._extension_registry, {(module_name, function_name): code} */
|
/* copyreg._extension_registry, {(module_name, function_name): code} */
|
||||||
static PyObject *extension_registry;
|
static PyObject *extension_registry = NULL;
|
||||||
/* copyreg._inverted_registry, {code: (module_name, function_name)} */
|
/* copyreg._inverted_registry, {code: (module_name, function_name)} */
|
||||||
static PyObject *inverted_registry;
|
static PyObject *inverted_registry = NULL;
|
||||||
/* copyreg._extension_cache, {code: object} */
|
/* copyreg._extension_cache, {code: object} */
|
||||||
static PyObject *extension_cache;
|
static PyObject *extension_cache = NULL;
|
||||||
|
|
||||||
|
/* _compat_pickle.NAME_MAPPING, {(oldmodule, oldname): (newmodule, newname)} */
|
||||||
|
static PyObject *name_mapping_2to3 = NULL;
|
||||||
|
/* _compat_pickle.IMPORT_MAPPING, {oldmodule: newmodule} */
|
||||||
|
static PyObject *import_mapping_2to3 = NULL;
|
||||||
|
/* Same, but with REVERSE_NAME_MAPPING / REVERSE_IMPORT_MAPPING */
|
||||||
|
static PyObject *name_mapping_3to2 = NULL;
|
||||||
|
static PyObject *import_mapping_3to2 = NULL;
|
||||||
|
|
||||||
/* XXX: Are these really nescessary? */
|
/* XXX: Are these really nescessary? */
|
||||||
/* As the name says, an empty tuple. */
|
/* As the name says, an empty tuple. */
|
||||||
static PyObject *empty_tuple;
|
static PyObject *empty_tuple = NULL;
|
||||||
/* For looking up name pairs in copyreg._extension_registry. */
|
/* For looking up name pairs in copyreg._extension_registry. */
|
||||||
static PyObject *two_tuple;
|
static PyObject *two_tuple = NULL;
|
||||||
|
|
||||||
static int
|
static int
|
||||||
stack_underflow(void)
|
stack_underflow(void)
|
||||||
|
@ -315,6 +323,8 @@ typedef struct PicklerObject {
|
||||||
should not be used if with self-referential
|
should not be used if with self-referential
|
||||||
objects. */
|
objects. */
|
||||||
int fast_nesting;
|
int fast_nesting;
|
||||||
|
int fix_imports; /* Indicate whether Pickler should fix
|
||||||
|
the name of globals for Python 2.x. */
|
||||||
PyObject *fast_memo;
|
PyObject *fast_memo;
|
||||||
} PicklerObject;
|
} PicklerObject;
|
||||||
|
|
||||||
|
@ -340,6 +350,9 @@ typedef struct UnpicklerObject {
|
||||||
objects. */
|
objects. */
|
||||||
Py_ssize_t num_marks; /* Number of marks in the mark stack. */
|
Py_ssize_t num_marks; /* Number of marks in the mark stack. */
|
||||||
Py_ssize_t marks_size; /* Current allocated size of the mark stack. */
|
Py_ssize_t marks_size; /* Current allocated size of the mark stack. */
|
||||||
|
int proto; /* Protocol of the pickle loaded. */
|
||||||
|
int fix_imports; /* Indicate whether Unpickler should fix
|
||||||
|
the name of globals pickled by Python 2.x. */
|
||||||
} UnpicklerObject;
|
} UnpicklerObject;
|
||||||
|
|
||||||
/* Forward declarations */
|
/* Forward declarations */
|
||||||
|
@ -1972,6 +1985,63 @@ save_global(PicklerObject *self, PyObject *obj, PyObject *name)
|
||||||
unicode_encoder = PyUnicode_AsASCIIString;
|
unicode_encoder = PyUnicode_AsASCIIString;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* For protocol < 3 and if the user didn't request against doing so,
|
||||||
|
we convert module names to the old 2.x module names. */
|
||||||
|
if (self->fix_imports) {
|
||||||
|
PyObject *key;
|
||||||
|
PyObject *item;
|
||||||
|
|
||||||
|
key = PyTuple_Pack(2, module_name, global_name);
|
||||||
|
if (key == NULL)
|
||||||
|
goto error;
|
||||||
|
item = PyDict_GetItemWithError(name_mapping_3to2, key);
|
||||||
|
Py_DECREF(key);
|
||||||
|
if (item) {
|
||||||
|
if (!PyTuple_Check(item) || PyTuple_GET_SIZE(item) != 2) {
|
||||||
|
PyErr_Format(PyExc_RuntimeError,
|
||||||
|
"_compat_pickle.REVERSE_NAME_MAPPING values "
|
||||||
|
"should be 2-tuples, not %.200s",
|
||||||
|
Py_TYPE(item)->tp_name);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
Py_CLEAR(module_name);
|
||||||
|
Py_CLEAR(global_name);
|
||||||
|
module_name = PyTuple_GET_ITEM(item, 0);
|
||||||
|
global_name = PyTuple_GET_ITEM(item, 1);
|
||||||
|
if (!PyUnicode_Check(module_name) ||
|
||||||
|
!PyUnicode_Check(global_name)) {
|
||||||
|
PyErr_Format(PyExc_RuntimeError,
|
||||||
|
"_compat_pickle.REVERSE_NAME_MAPPING values "
|
||||||
|
"should be pairs of str, not (%.200s, %.200s)",
|
||||||
|
Py_TYPE(module_name)->tp_name,
|
||||||
|
Py_TYPE(global_name)->tp_name);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
Py_INCREF(module_name);
|
||||||
|
Py_INCREF(global_name);
|
||||||
|
}
|
||||||
|
else if (PyErr_Occurred()) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
item = PyDict_GetItemWithError(import_mapping_3to2, module_name);
|
||||||
|
if (item) {
|
||||||
|
if (!PyUnicode_Check(item)) {
|
||||||
|
PyErr_Format(PyExc_RuntimeError,
|
||||||
|
"_compat_pickle.REVERSE_IMPORT_MAPPING values "
|
||||||
|
"should be strings, not %.200s",
|
||||||
|
Py_TYPE(item)->tp_name);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
Py_CLEAR(module_name);
|
||||||
|
module_name = item;
|
||||||
|
Py_INCREF(module_name);
|
||||||
|
}
|
||||||
|
else if (PyErr_Occurred()) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Save the name of the module. */
|
/* Save the name of the module. */
|
||||||
encoded = unicode_encoder(module_name);
|
encoded = unicode_encoder(module_name);
|
||||||
if (encoded == NULL) {
|
if (encoded == NULL) {
|
||||||
|
@ -2608,18 +2678,23 @@ PyDoc_STRVAR(Pickler_doc,
|
||||||
"The file argument must have a write() method that accepts a single\n"
|
"The file argument must have a write() method that accepts a single\n"
|
||||||
"bytes argument. It can thus be a file object opened for binary\n"
|
"bytes argument. It can thus be a file object opened for binary\n"
|
||||||
"writing, a io.BytesIO instance, or any other custom object that\n"
|
"writing, a io.BytesIO instance, or any other custom object that\n"
|
||||||
"meets this interface.\n");
|
"meets this interface.\n"
|
||||||
|
"\n"
|
||||||
|
"If fix_imports is True and protocol is less than 3, pickle will try to\n"
|
||||||
|
"map the new Python 3.x names to the old module names used in Python\n"
|
||||||
|
"2.x, so that the pickle data stream is readable with Python 2.x.\n");
|
||||||
|
|
||||||
static int
|
static int
|
||||||
Pickler_init(PicklerObject *self, PyObject *args, PyObject *kwds)
|
Pickler_init(PicklerObject *self, PyObject *args, PyObject *kwds)
|
||||||
{
|
{
|
||||||
static char *kwlist[] = {"file", "protocol", 0};
|
static char *kwlist[] = {"file", "protocol", "fix_imports", 0};
|
||||||
PyObject *file;
|
PyObject *file;
|
||||||
PyObject *proto_obj = NULL;
|
PyObject *proto_obj = NULL;
|
||||||
long proto = 0;
|
long proto = 0;
|
||||||
|
int fix_imports = 1;
|
||||||
|
|
||||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|O:Pickler",
|
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|Oi:Pickler",
|
||||||
kwlist, &file, &proto_obj))
|
kwlist, &file, &proto_obj, &fix_imports))
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
/* In case of multiple __init__() calls, clear previous content. */
|
/* In case of multiple __init__() calls, clear previous content. */
|
||||||
|
@ -2628,8 +2703,11 @@ Pickler_init(PicklerObject *self, PyObject *args, PyObject *kwds)
|
||||||
|
|
||||||
if (proto_obj == NULL || proto_obj == Py_None)
|
if (proto_obj == NULL || proto_obj == Py_None)
|
||||||
proto = DEFAULT_PROTOCOL;
|
proto = DEFAULT_PROTOCOL;
|
||||||
else
|
else {
|
||||||
proto = PyLong_AsLong(proto_obj);
|
proto = PyLong_AsLong(proto_obj);
|
||||||
|
if (proto == -1 && PyErr_Occurred())
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
if (proto < 0)
|
if (proto < 0)
|
||||||
proto = HIGHEST_PROTOCOL;
|
proto = HIGHEST_PROTOCOL;
|
||||||
|
@ -2639,12 +2717,13 @@ Pickler_init(PicklerObject *self, PyObject *args, PyObject *kwds)
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
self->proto = proto;
|
self->proto = proto;
|
||||||
self->bin = proto > 0;
|
self->bin = proto > 0;
|
||||||
self->arg = NULL;
|
self->arg = NULL;
|
||||||
self->fast = 0;
|
self->fast = 0;
|
||||||
self->fast_nesting = 0;
|
self->fast_nesting = 0;
|
||||||
self->fast_memo = NULL;
|
self->fast_memo = NULL;
|
||||||
|
self->fix_imports = fix_imports && proto < 3;
|
||||||
|
|
||||||
if (!PyObject_HasAttrString(file, "write")) {
|
if (!PyObject_HasAttrString(file, "write")) {
|
||||||
PyErr_SetString(PyExc_TypeError,
|
PyErr_SetString(PyExc_TypeError,
|
||||||
|
@ -4220,8 +4299,10 @@ load_proto(UnpicklerObject *self)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
i = (unsigned char)s[0];
|
i = (unsigned char)s[0];
|
||||||
if (i <= HIGHEST_PROTOCOL)
|
if (i <= HIGHEST_PROTOCOL) {
|
||||||
|
self->proto = i;
|
||||||
return 0;
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
PyErr_Format(PyExc_ValueError, "unsupported pickle protocol: %d", i);
|
PyErr_Format(PyExc_ValueError, "unsupported pickle protocol: %d", i);
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -4383,12 +4464,67 @@ Unpickler_find_class(UnpicklerObject *self, PyObject *args)
|
||||||
&module_name, &global_name))
|
&module_name, &global_name))
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
|
/* Try to map the old names used in Python 2.x to the new ones used in
|
||||||
|
Python 3.x. We do this only with old pickle protocols and when the
|
||||||
|
user has not disabled the feature. */
|
||||||
|
if (self->proto < 3 && self->fix_imports) {
|
||||||
|
PyObject *key;
|
||||||
|
PyObject *item;
|
||||||
|
|
||||||
|
/* Check if the global (i.e., a function or a class) was renamed
|
||||||
|
or moved to another module. */
|
||||||
|
key = PyTuple_Pack(2, module_name, global_name);
|
||||||
|
if (key == NULL)
|
||||||
|
return NULL;
|
||||||
|
item = PyDict_GetItemWithError(name_mapping_2to3, key);
|
||||||
|
Py_DECREF(key);
|
||||||
|
if (item) {
|
||||||
|
if (!PyTuple_Check(item) || PyTuple_GET_SIZE(item) != 2) {
|
||||||
|
PyErr_Format(PyExc_RuntimeError,
|
||||||
|
"_compat_pickle.NAME_MAPPING values should be "
|
||||||
|
"2-tuples, not %.200s", Py_TYPE(item)->tp_name);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
module_name = PyTuple_GET_ITEM(item, 0);
|
||||||
|
global_name = PyTuple_GET_ITEM(item, 1);
|
||||||
|
if (!PyUnicode_Check(module_name) ||
|
||||||
|
!PyUnicode_Check(global_name)) {
|
||||||
|
PyErr_Format(PyExc_RuntimeError,
|
||||||
|
"_compat_pickle.NAME_MAPPING values should be "
|
||||||
|
"pairs of str, not (%.200s, %.200s)",
|
||||||
|
Py_TYPE(module_name)->tp_name,
|
||||||
|
Py_TYPE(global_name)->tp_name);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (PyErr_Occurred()) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check if the module was renamed. */
|
||||||
|
item = PyDict_GetItemWithError(import_mapping_2to3, module_name);
|
||||||
|
if (item) {
|
||||||
|
if (!PyUnicode_Check(item)) {
|
||||||
|
PyErr_Format(PyExc_RuntimeError,
|
||||||
|
"_compat_pickle.IMPORT_MAPPING values should be "
|
||||||
|
"strings, not %.200s", Py_TYPE(item)->tp_name);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
module_name = item;
|
||||||
|
}
|
||||||
|
else if (PyErr_Occurred()) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
modules_dict = PySys_GetObject("modules");
|
modules_dict = PySys_GetObject("modules");
|
||||||
if (modules_dict == NULL)
|
if (modules_dict == NULL)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
module = PyDict_GetItem(modules_dict, module_name);
|
module = PyDict_GetItemWithError(modules_dict, module_name);
|
||||||
if (module == NULL) {
|
if (module == NULL) {
|
||||||
|
if (PyErr_Occurred())
|
||||||
|
return NULL;
|
||||||
module = PyImport_Import(module_name);
|
module = PyImport_Import(module_name);
|
||||||
if (module == NULL)
|
if (module == NULL)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -4477,15 +4613,20 @@ PyDoc_STRVAR(Unpickler_doc,
|
||||||
"reading, a BytesIO object, or any other custom object that\n"
|
"reading, a BytesIO object, or any other custom object that\n"
|
||||||
"meets this interface.\n"
|
"meets this interface.\n"
|
||||||
"\n"
|
"\n"
|
||||||
"Optional keyword arguments are encoding and errors, which are\n"
|
"Optional keyword arguments are *fix_imports*, *encoding* and *errors*,\n"
|
||||||
"used to decode 8-bit string instances pickled by Python 2.x.\n"
|
"which are used to control compatiblity support for pickle stream\n"
|
||||||
"These default to 'ASCII' and 'strict', respectively.\n");
|
"generated by Python 2.x. If *fix_imports* is True, pickle will try to\n"
|
||||||
|
"map the old Python 2.x names to the new names used in Python 3.x. The\n"
|
||||||
|
"*encoding* and *errors* tell pickle how to decode 8-bit string\n"
|
||||||
|
"instances pickled by Python 2.x; these default to 'ASCII' and\n"
|
||||||
|
"'strict', respectively.\n");
|
||||||
|
|
||||||
static int
|
static int
|
||||||
Unpickler_init(UnpicklerObject *self, PyObject *args, PyObject *kwds)
|
Unpickler_init(UnpicklerObject *self, PyObject *args, PyObject *kwds)
|
||||||
{
|
{
|
||||||
static char *kwlist[] = {"file", "encoding", "errors", 0};
|
static char *kwlist[] = {"file", "fix_imports", "encoding", "errors", 0};
|
||||||
PyObject *file;
|
PyObject *file;
|
||||||
|
int fix_imports = 1;
|
||||||
char *encoding = NULL;
|
char *encoding = NULL;
|
||||||
char *errors = NULL;
|
char *errors = NULL;
|
||||||
|
|
||||||
|
@ -4504,8 +4645,8 @@ Unpickler_init(UnpicklerObject *self, PyObject *args, PyObject *kwds)
|
||||||
extra careful in the other Unpickler methods, since a subclass could
|
extra careful in the other Unpickler methods, since a subclass could
|
||||||
forget to call Unpickler.__init__() thus breaking our internal
|
forget to call Unpickler.__init__() thus breaking our internal
|
||||||
invariants. */
|
invariants. */
|
||||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|ss:Unpickler", kwlist,
|
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|iss:Unpickler", kwlist,
|
||||||
&file, &encoding, &errors))
|
&file, &fix_imports, &encoding, &errors))
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
/* In case of multiple __init__() calls, clear previous content. */
|
/* In case of multiple __init__() calls, clear previous content. */
|
||||||
|
@ -4549,6 +4690,8 @@ Unpickler_init(UnpicklerObject *self, PyObject *args, PyObject *kwds)
|
||||||
|
|
||||||
self->last_string = NULL;
|
self->last_string = NULL;
|
||||||
self->arg = NULL;
|
self->arg = NULL;
|
||||||
|
self->proto = 0;
|
||||||
|
self->fix_imports = fix_imports;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -4672,40 +4815,85 @@ static PyTypeObject Unpickler_Type = {
|
||||||
};
|
};
|
||||||
|
|
||||||
static int
|
static int
|
||||||
init_stuff(void)
|
initmodule(void)
|
||||||
{
|
{
|
||||||
PyObject *copyreg;
|
PyObject *copyreg = NULL;
|
||||||
|
PyObject *compat_pickle = NULL;
|
||||||
|
|
||||||
|
/* XXX: We should ensure that the types of the dictionaries imported are
|
||||||
|
exactly PyDict objects. Otherwise, it is possible to crash the pickle
|
||||||
|
since we use the PyDict API directly to access these dictionaries. */
|
||||||
|
|
||||||
copyreg = PyImport_ImportModule("copyreg");
|
copyreg = PyImport_ImportModule("copyreg");
|
||||||
if (!copyreg)
|
if (!copyreg)
|
||||||
return -1;
|
goto error;
|
||||||
|
|
||||||
dispatch_table = PyObject_GetAttrString(copyreg, "dispatch_table");
|
dispatch_table = PyObject_GetAttrString(copyreg, "dispatch_table");
|
||||||
if (!dispatch_table)
|
if (!dispatch_table)
|
||||||
goto error;
|
goto error;
|
||||||
|
|
||||||
extension_registry = \
|
extension_registry = \
|
||||||
PyObject_GetAttrString(copyreg, "_extension_registry");
|
PyObject_GetAttrString(copyreg, "_extension_registry");
|
||||||
if (!extension_registry)
|
if (!extension_registry)
|
||||||
goto error;
|
goto error;
|
||||||
|
|
||||||
inverted_registry = PyObject_GetAttrString(copyreg, "_inverted_registry");
|
inverted_registry = PyObject_GetAttrString(copyreg, "_inverted_registry");
|
||||||
if (!inverted_registry)
|
if (!inverted_registry)
|
||||||
goto error;
|
goto error;
|
||||||
|
|
||||||
extension_cache = PyObject_GetAttrString(copyreg, "_extension_cache");
|
extension_cache = PyObject_GetAttrString(copyreg, "_extension_cache");
|
||||||
if (!extension_cache)
|
if (!extension_cache)
|
||||||
goto error;
|
goto error;
|
||||||
|
Py_CLEAR(copyreg);
|
||||||
|
|
||||||
Py_DECREF(copyreg);
|
/* Load the 2.x -> 3.x stdlib module mapping tables */
|
||||||
|
compat_pickle = PyImport_ImportModule("_compat_pickle");
|
||||||
|
if (!compat_pickle)
|
||||||
|
goto error;
|
||||||
|
name_mapping_2to3 = PyObject_GetAttrString(compat_pickle, "NAME_MAPPING");
|
||||||
|
if (!name_mapping_2to3)
|
||||||
|
goto error;
|
||||||
|
if (!PyDict_CheckExact(name_mapping_2to3)) {
|
||||||
|
PyErr_Format(PyExc_RuntimeError,
|
||||||
|
"_compat_pickle.NAME_MAPPING should be a dict, not %.200s",
|
||||||
|
Py_TYPE(name_mapping_2to3)->tp_name);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
import_mapping_2to3 = PyObject_GetAttrString(compat_pickle,
|
||||||
|
"IMPORT_MAPPING");
|
||||||
|
if (!import_mapping_2to3)
|
||||||
|
goto error;
|
||||||
|
if (!PyDict_CheckExact(import_mapping_2to3)) {
|
||||||
|
PyErr_Format(PyExc_RuntimeError,
|
||||||
|
"_compat_pickle.IMPORT_MAPPING should be a dict, "
|
||||||
|
"not %.200s", Py_TYPE(import_mapping_2to3)->tp_name);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
/* ... and the 3.x -> 2.x mapping tables */
|
||||||
|
name_mapping_3to2 = PyObject_GetAttrString(compat_pickle,
|
||||||
|
"REVERSE_NAME_MAPPING");
|
||||||
|
if (!name_mapping_3to2)
|
||||||
|
goto error;
|
||||||
|
if (!PyDict_CheckExact(name_mapping_3to2)) {
|
||||||
|
PyErr_Format(PyExc_RuntimeError,
|
||||||
|
"_compat_pickle.REVERSE_NAME_MAPPING shouldbe a dict, "
|
||||||
|
"not %.200s", Py_TYPE(name_mapping_3to2)->tp_name);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
import_mapping_3to2 = PyObject_GetAttrString(compat_pickle,
|
||||||
|
"REVERSE_IMPORT_MAPPING");
|
||||||
|
if (!import_mapping_3to2)
|
||||||
|
goto error;
|
||||||
|
if (!PyDict_CheckExact(import_mapping_3to2)) {
|
||||||
|
PyErr_Format(PyExc_RuntimeError,
|
||||||
|
"_compat_pickle.REVERSE_IMPORT_MAPPING should be a dict, "
|
||||||
|
"not %.200s", Py_TYPE(import_mapping_3to2)->tp_name);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
Py_CLEAR(compat_pickle);
|
||||||
|
|
||||||
empty_tuple = PyTuple_New(0);
|
empty_tuple = PyTuple_New(0);
|
||||||
if (empty_tuple == NULL)
|
if (empty_tuple == NULL)
|
||||||
return -1;
|
goto error;
|
||||||
|
|
||||||
two_tuple = PyTuple_New(2);
|
two_tuple = PyTuple_New(2);
|
||||||
if (two_tuple == NULL)
|
if (two_tuple == NULL)
|
||||||
return -1;
|
goto error;
|
||||||
/* We use this temp container with no regard to refcounts, or to
|
/* We use this temp container with no regard to refcounts, or to
|
||||||
* keeping containees alive. Exempt from GC, because we don't
|
* keeping containees alive. Exempt from GC, because we don't
|
||||||
* want anything looking at two_tuple() by magic.
|
* want anything looking at two_tuple() by magic.
|
||||||
|
@ -4715,7 +4903,18 @@ init_stuff(void)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
error:
|
error:
|
||||||
Py_DECREF(copyreg);
|
Py_CLEAR(copyreg);
|
||||||
|
Py_CLEAR(dispatch_table);
|
||||||
|
Py_CLEAR(extension_registry);
|
||||||
|
Py_CLEAR(inverted_registry);
|
||||||
|
Py_CLEAR(extension_cache);
|
||||||
|
Py_CLEAR(compat_pickle);
|
||||||
|
Py_CLEAR(name_mapping_2to3);
|
||||||
|
Py_CLEAR(import_mapping_2to3);
|
||||||
|
Py_CLEAR(name_mapping_3to2);
|
||||||
|
Py_CLEAR(import_mapping_3to2);
|
||||||
|
Py_CLEAR(empty_tuple);
|
||||||
|
Py_CLEAR(two_tuple);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4773,7 +4972,7 @@ PyInit__pickle(void)
|
||||||
if (PyModule_AddObject(m, "UnpicklingError", UnpicklingError) < 0)
|
if (PyModule_AddObject(m, "UnpicklingError", UnpicklingError) < 0)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
if (init_stuff() < 0)
|
if (initmodule() < 0)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
return m;
|
return m;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue