summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobert Bradshaw <robertwb@gmail.com>2017-07-10 12:08:07 -0700
committerRobert Bradshaw <robertwb@gmail.com>2017-07-10 12:08:07 -0700
commit1f85154c6856b2aeccf4ae95eacd3df8abfd5827 (patch)
tree7260dc8585d3724559f54e483bedbe1df45edee8
parent7603976ec2a2f36efcd858260947d9ddb203c858 (diff)
downloadcython-1f85154c6856b2aeccf4ae95eacd3df8abfd5827.tar.gz
Avoid pickling objects with struct attributes.
Extern structs may only be partially declared.
-rw-r--r--Cython/Compiler/ParseTreeTransforms.py15
-rw-r--r--tests/run/reduce_pickle.pyx52
2 files changed, 52 insertions, 15 deletions
diff --git a/Cython/Compiler/ParseTreeTransforms.py b/Cython/Compiler/ParseTreeTransforms.py
index 3eb83a068..43a991d9e 100644
--- a/Cython/Compiler/ParseTreeTransforms.py
+++ b/Cython/Compiler/ParseTreeTransforms.py
@@ -1603,6 +1603,7 @@ if VALUE is not None:
if node.scope.directives['auto_pickle'] is False: # None means attempt it.
# Old behavior of not doing anything.
return
+ auto_pickle_forced = node.scope.directives['auto_pickle'] is True
all_members = []
cls = node.entry.type
@@ -1627,14 +1628,22 @@ if VALUE is not None:
or not e.type.can_coerce_from_pyobject(env))
]
- if cinit or non_py:
+ structs = [e for e in all_members if e.type.is_struct_or_union]
+
+ if cinit or non_py or (structs and not auto_pickle_forced):
if cinit:
# TODO(robertwb): We could allow this if __cinit__ has no require arguments.
msg = 'no default __reduce__ due to non-trivial __cinit__'
- else:
+ elif non_py:
msg = "%s cannot be converted to a Python object for pickling" % ','.join("self.%s" % e.name for e in non_py)
+ else:
+ # Extern structs may be only partially defined.
+ # TODO(robertwb): Limit the restriction to extern
+ # (and recursively extern-containing) structs.
+ msg = ("Pickling of struct members such as %s must be explicitly requested "
+ "with @auto_pickle(True)" % ','.join("self.%s" % e.name for e in structs))
- if node.scope.directives['auto_pickle'] is True:
+ if auto_pickle_forced:
error(node.pos, msg)
pickle_func = TreeFragment(u"""
diff --git a/tests/run/reduce_pickle.pyx b/tests/run/reduce_pickle.pyx
index 2591206e9..10dd9fdd0 100644
--- a/tests/run/reduce_pickle.pyx
+++ b/tests/run/reduce_pickle.pyx
@@ -167,43 +167,71 @@ cdef class NoMembers(object):
return "NoMembers()"
-cdef struct MyStruct:
- int i
- double x
-
cdef class NoPyMembers(object):
"""
>>> import pickle
>>> pickle.loads(pickle.dumps(NoPyMembers(2, 1.75)))
- NoPyMembers(ii=[2, 4, 8], x=1.75, my_struct=(3, 2.75))
+ NoPyMembers(ii=[2, 4, 8], x=1.75)
"""
cdef int[3] ii
cdef double x
- cdef MyStruct my_struct
def __init__(self, i, x):
self.ii[0] = i
self.ii[1] = i * i
self.ii[2] = i * i * i
self.x = x
- self.my_struct = MyStruct(i+1, x+1)
def __repr__(self):
- return "NoPyMembers(ii=%s, x=%s, my_struct=(%s, %s))" % (
- self.ii, self.x, self.my_struct.i, self.my_struct.x)
+ return "%s(ii=%s, x=%s)" % (type(self).__name__, self.ii, self.x)
class NoPyMembersPySubclass(NoPyMembers):
"""
>>> import pickle
>>> pickle.loads(pickle.dumps(NoPyMembersPySubclass(2, 1.75, 'xyz')))
- NoPyMembersPySubclass(ii=[2, 4, 8], x=1.75, my_struct=(3, 2.75), s='xyz')
+ NoPyMembersPySubclass(ii=[2, 4, 8], x=1.75, s='xyz')
"""
def __init__(self, i, x, s):
super(NoPyMembersPySubclass, self).__init__(i, x)
self.s = s
def __repr__(self):
- return super(NoPyMembersPySubclass, self).__repr__().replace(
- 'NoPyMembers', 'NoPyMembersPySubclass')[:-1] + ', s=%r)' % self.s
+ return (super(NoPyMembersPySubclass, self).__repr__()
+ [:-1] + ', s=%r)' % self.s)
+
+
+cdef struct MyStruct:
+ int i
+ double x
+
+cdef class StructMemberDefault(object):
+ """
+ >>> import pickle
+ >>> s = StructMemberDefault(1, 1.5); s
+ StructMemberDefault(i=1, x=1.5)
+ >>> pickle.dumps(s) # doctest: +ELLIPSIS
+ Traceback (most recent call last):
+ TypeError: ...my_struct...
+ """
+
+ cdef MyStruct my_struct
+
+ def __init__(self, i, x):
+ self.my_struct.i = i
+ self.my_struct.x = x
+
+ def __repr__(self):
+ return "%s(i=%s, x=%s)" % (
+ type(self).__name__, self.my_struct.i, self.my_struct.x)
+
+@cython.auto_pickle(True) # Forced due to the (inherited) struct attribute.
+cdef class StructMemberForcedPickle(StructMemberDefault):
+ """
+ >>> import pickle
+ >>> s = StructMemberForcedPickle(1, 1.5); s
+ StructMemberForcedPickle(i=1, x=1.5)
+ >>> pickle.loads(pickle.dumps(s))
+ StructMemberForcedPickle(i=1, x=1.5)
+ """
cdef _unset = object()