Commit 90159e19 authored by Stefan Behnel's avatar Stefan Behnel

For the auto-pickle checksum, allow MD5 again since it was used by Cython 0.x...

For the auto-pickle checksum, allow MD5 again since it was used by Cython 0.x pickles. Otherwise switch to SHA-256 since that should be safe enough to stay available in Python's hashlib module for another while.

Closes https://github.com/cython/cython/issues/4680
parent 33fea7bf
...@@ -2036,7 +2036,21 @@ if VALUE is not None: ...@@ -2036,7 +2036,21 @@ if VALUE is not None:
e.type.create_to_py_utility_code(env) e.type.create_to_py_utility_code(env)
e.type.create_from_py_utility_code(env) e.type.create_from_py_utility_code(env)
all_members_names = sorted([e.name for e in all_members]) all_members_names = sorted([e.name for e in all_members])
checksum = '0x%s' % hashlib.sha1(' '.join(all_members_names).encode('utf-8')).hexdigest()[:7]
# Cython 0.x used MD5 for the checksum, which a few Python installations remove for security reasons.
# SHA-256 should be ok for years to come, but early Cython 3.0 alpha releases used SHA-1,
# which may not be.
checksum_algos = [hashlib.sha256, hashlib.sha1]
try:
checksum_algos.append(hashlib.md5)
except AttributeError:
pass
member_names_string = ' '.join(all_members_names).encode('utf-8')
checksums = [
'0x' + mkchecksum(member_names_string).hexdigest()[:7]
for mkchecksum in checksum_algos
]
unpickle_func_name = '__pyx_unpickle_%s' % node.punycode_class_name unpickle_func_name = '__pyx_unpickle_%s' % node.punycode_class_name
# TODO(robertwb): Move the state into the third argument # TODO(robertwb): Move the state into the third argument
...@@ -2045,9 +2059,9 @@ if VALUE is not None: ...@@ -2045,9 +2059,9 @@ if VALUE is not None:
def %(unpickle_func_name)s(__pyx_type, long __pyx_checksum, __pyx_state): def %(unpickle_func_name)s(__pyx_type, long __pyx_checksum, __pyx_state):
cdef object __pyx_PickleError cdef object __pyx_PickleError
cdef object __pyx_result cdef object __pyx_result
if __pyx_checksum != %(checksum)s: if __pyx_checksum not in %(checksums)s:
from pickle import PickleError as __pyx_PickleError from pickle import PickleError as __pyx_PickleError
raise __pyx_PickleError, "Incompatible checksums (%%s vs %(checksum)s = (%(members)s))" %% __pyx_checksum raise __pyx_PickleError, "Incompatible checksums (%%s vs %(checksums)s = (%(members)s))" %% __pyx_checksum
__pyx_result = %(class_name)s.__new__(__pyx_type) __pyx_result = %(class_name)s.__new__(__pyx_type)
if __pyx_state is not None: if __pyx_state is not None:
%(unpickle_func_name)s__set_state(<%(class_name)s> __pyx_result, __pyx_state) %(unpickle_func_name)s__set_state(<%(class_name)s> __pyx_result, __pyx_state)
...@@ -2059,7 +2073,7 @@ if VALUE is not None: ...@@ -2059,7 +2073,7 @@ if VALUE is not None:
__pyx_result.__dict__.update(__pyx_state[%(num_members)d]) __pyx_result.__dict__.update(__pyx_state[%(num_members)d])
""" % { """ % {
'unpickle_func_name': unpickle_func_name, 'unpickle_func_name': unpickle_func_name,
'checksum': checksum, 'checksums': "(%s)" % ', '.join(checksums),
'members': ', '.join(all_members_names), 'members': ', '.join(all_members_names),
'class_name': node.class_name, 'class_name': node.class_name,
'assignments': '; '.join( 'assignments': '; '.join(
...@@ -2092,7 +2106,7 @@ if VALUE is not None: ...@@ -2092,7 +2106,7 @@ if VALUE is not None:
%(unpickle_func_name)s__set_state(self, __pyx_state) %(unpickle_func_name)s__set_state(self, __pyx_state)
""" % { """ % {
'unpickle_func_name': unpickle_func_name, 'unpickle_func_name': unpickle_func_name,
'checksum': checksum, 'checksum': checksums[0],
'members': ', '.join('self.%s' % v for v in all_members_names) + (',' if len(all_members_names) == 1 else ''), 'members': ', '.join('self.%s' % v for v in all_members_names) + (',' if len(all_members_names) == 1 else ''),
# Even better, we could check PyType_IS_GC. # Even better, we could check PyType_IS_GC.
'any_notnone_members' : ' or '.join(['self.%s is not None' % e.name for e in all_members if e.type.is_pyobject] or ['False']), 'any_notnone_members' : ' or '.join(['self.%s is not None' % e.name for e in all_members if e.type.is_pyobject] or ['False']),
......
...@@ -62,6 +62,9 @@ cdef class B: ...@@ -62,6 +62,9 @@ cdef class B:
def __reduce__(self): def __reduce__(self):
return makeObj, (type(self), {'x': self.x, 'y': self.y}) return makeObj, (type(self), {'x': self.x, 'y': self.y})
def __eq__(self, other):
return isinstance(other, B) and (<B>other).x == self.x and (<B>other).y == self.y
def makeObj(obj_type, kwds): def makeObj(obj_type, kwds):
return obj_type(**kwds) return obj_type(**kwds)
...@@ -304,3 +307,24 @@ if sys.version_info[:2] >= (3, 5): ...@@ -304,3 +307,24 @@ if sys.version_info[:2] >= (3, 5):
""" """
def my_method(self, x): def my_method(self, x):
return x return x
# Pickled with Cython 0.29.28 (using MD5 for the checksum).
OLD_MD5_PICKLE = b'''\
\x80\x04\x95:\x00\x00\x00\x00\x00\x00\x00\x8c\rreduce_pickle\
\x94\x8c\x07makeObj\x94\x93\x94h\x00\x8c\x01B\x94\x93\x94}\
\x94(\x8c\x01x\x94K%\x8c\x01y\x94M\x85\x01u\x86\x94R\x94.\
'''
try:
from hashlib import md5
except ImportError:
pass
else:
def unpickle_old_0_29_28():
"""
>>> import pickle
>>> b = pickle.loads(OLD_MD5_PICKLE)
>>> b == B(x=37, y=389) or b
True
"""
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment