diff options
author | Robert Bradshaw <robertwb@gmail.com> | 2017-07-10 12:08:07 -0700 |
---|---|---|
committer | Robert Bradshaw <robertwb@gmail.com> | 2017-07-10 12:08:07 -0700 |
commit | 1f85154c6856b2aeccf4ae95eacd3df8abfd5827 (patch) | |
tree | 7260dc8585d3724559f54e483bedbe1df45edee8 | |
parent | 7603976ec2a2f36efcd858260947d9ddb203c858 (diff) | |
download | cython-1f85154c6856b2aeccf4ae95eacd3df8abfd5827.tar.gz |
Avoid pickling objects with struct attributes.
Extern structs may only be partially declared.
-rw-r--r-- | Cython/Compiler/ParseTreeTransforms.py | 15 | ||||
-rw-r--r-- | tests/run/reduce_pickle.pyx | 52 |
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() |