You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Traceback (most recent call last):
File "Tools/pybench/pybench.py", line 954, in <module>
PyBenchCmdline()
File "/home/antoine/py3k/__svn__/Tools/pybench/CommandLine.py", line
349, in __init__
rc =self.main()
File "Tools/pybench/pybench.py", line 888, in main
bench = pickle.load(f)
File "/home/antoine/py3k/__svn__/Lib/pickle.py", line 1335, in loadreturn Unpickler(file, encoding=encoding, errors=errors).load()
_pickle.UnpicklingError: bad pickle data
If I disable _pickle, I get a more meaningful trace:
File "Tools/pybench/pybench.py", line 954, in <module>
PyBenchCmdline()
File "/Users/sasha/Work/python-svn/py3k-commit/Tools/pybench/CommandLine.py", line 349, in __init__
rc = self.main()
File "Tools/pybench/pybench.py", line 888, in main
bench = pickle.load(f)
File "/Users/sasha/Work/python-svn/py3k-commit/Lib/pickle.py", line 1321, in load
encoding=encoding, errors=errors).load()
File "/Users/sasha/Work/python-svn/py3k-commit/Lib/pickle.py", line 830, in load
dispatchkey[0]
File "/Users/sasha/Work/python-svn/py3k-commit/Lib/pickle.py", line 1055, in load_inst
klass = self.find_class(module, name)
File "/Users/sasha/Work/python-svn/py3k-commit/Lib/pickle.py", line 1115, in find_class
__import__(module, level=0)
File "/Users/sasha/Work/python-svn/py3k-commit/Tools/pybench/Unicode.py", line 17
s = unicode(u''.join(map(str,range(100))))
^
SyntaxError: invalid syntax
Apparently, pybench/Unicode.py has never been converted to py3k. This is likely not the only problem:
>>> from pybench import*
>>> import pickle
>>> pickle.load(open('27.bench'))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Users/sasha/Work/python-svn/py3k-commit/Lib/pickle.py", line 1321, in load
encoding=encoding, errors=errors).load()
File "/Users/sasha/Work/python-svn/py3k-commit/Lib/pickle.py", line 829, in load
assert isinstance(key, bytes_types)
AssertionError
This looks like the infamous bytes vs string problem ...
The bytes/string issu was a red herring: with pickle.load(open('27.bench', 'b')), I get the same stack trace as from command line pybench invocation.
>>> pickle.load(open('27.bench', 'rb'))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Users/sasha/Work/python-svn/py3k-commit/Lib/pickle.py", line 1321, in load
encoding=encoding, errors=errors).load()
File "/Users/sasha/Work/python-svn/py3k-commit/Lib/pickle.py", line 830, in load
dispatch[key[0]](self)
File "/Users/sasha/Work/python-svn/py3k-commit/Lib/pickle.py", line 1055, in load_inst
klass = self.find_class(module, name)
File "/Users/sasha/Work/python-svn/py3k-commit/Lib/pickle.py", line 1115, in find_class
__import__(module, level=0)
File "/Users/sasha/Work/python-svn/py3k-commit/Tools/pybench/Unicode.py", line 17
s = unicode(u''.join(map(str,range(100))))
^
SyntaxError: invalid syntax
Hmm. It looks like another pickle vs. _pickle issue. Attached patch is a result of 2to3 applied to Tools/pybench (and a minor manual fix for pickle import) with _pickle import disabled.
I have found bpo-1634499 which deals with running pybench. I suggest that we reopen that issue and move discussion of bpo-5180.diff there.
I really like the idea to keep single source for 2.x and 3.x pybench, but we need to add some machinery to run 2.x pybench under python3.x transparently.
Marc-Andre,
are you taking over the pybench or the _pickle issue?
As promised, here is the fix (bpo-5180-pickle.diff) for the "unpickle-bug.py" issue. Unfortunately, it looks like more bugs remain. 27.bench is still not loadable.
My patch attempts to emulate 2.x PyInstance_NewRaw with a call to
tp_alloc.
This is certainly the wrong thing to do. You could at least try PyBaseObject_Type.tp_new (see object_new() in typeobject.c), but even this is not necessarily right (for example if the class is derived from an extension type defining its own tp_new).
So, IMO, the right thing to do would be to choose the first base type that isn't a Python-defined class and use its tp_new:
staticbase=subtype;
while (staticbase&& (staticbase->tp_flags&Py_TPFLAGS_HEAPTYPE))
staticbase=staticbase->tp_base;
(these two lines are from tp_new_wrapper() in typeobject.c)
That way you choose the right constructor function, yet don't call the Python-defined __new__ function. That's the only reasonable way of doing it I can imagine. It also follows the following behaviour:
>>> classC(int):
... def__new__(cls, *a):
... print"__new__", a
... returnint.__new__(cls, *a)
...
>>> C(5)
__new__ (5,)
5
>>> s = pickle.dumps(C(5))
__new__ (5,)
>>> x = pickle.loads(s)
>>> x
5
As you can see, int.__new__ has been called on unpickling but not C.__new__ (no print).
> My patch attempts to emulate 2.x PyInstance_NewRaw with a call to
> tp_alloc.
This is certainly the wrong thing to do. You could at least try
PyBaseObject_Type.tp_new (see object_new() in typeobject.c), but
even this is not necessarily right (for example if the
class is derived from an extension type defining its own tp_new).
Well, this is not so obviously wrong as you make it sound. If you look closer at object_new(), you will see that in sane situations it reduces to type->tp_alloc(type, 0) call. Remember, this is called in very special circumstances: when unpickling old style class instances from a 2.x generated pickle. No arguments are available for tp_new call, so excess_args check does not apply. The other check is for an ABC, but the situation where an old-style class becomes an ABC in 2.x to 3.x transition is hardly sane. If such sanity check is necessary, I would rather check the flag explicitly in _pickle.c rather than having to generate dummy args and kwds to satisfy tp_new signature.
So, IMO, the right thing to do would be to choose the first base
type that isn't a Python-defined class and use its tp_new:
(these two lines are from tp_new_wrapper() in typeobject.c)
This looks like overengineering to me. I think 3.x should simply refuse to unpickle old style class instances into anything that is not subclassed in python directly from object. It would be helpful at this point if you could provide a test case where tp_alloc logic does not work. Regardless of what algorithm we ultimately choose, I would like to include such test in regrtest.
PS: Note that in my patch I've eliminated one control path that was calling instantiate(..) erroneously, but I have not reviewed the code carefully enough to eliminate the possibility that instantiate(..) would be called to instantiate new-style class instances.
PPS: What is you opinion on the pickle.py trick of monkey patching __class__ on an instance of an empty class? Do you think this can be fooled?
Well, this is not so obviously wrong as you make it sound. If you
look closer at object_new(), you will see that in sane situations it
reduces to type->tp_alloc(type, 0) call.
Today, yes. But tomorrow it may entail additional operations.
If such sanity check is necessary, I would rather check the flag
explicitly in _pickle.c rather than having to generate dummy args and
kwds to satisfy tp_new signature.
I find it better to "generate dummy args and kwds to satisfy tp_new
signature". I see no point in a partial inlining of an internal function
when we could call it instead.
> So, IMO, the right thing to do would be to choose the first base
> type that isn't a Python-defined class and use its tp_new:
>
> staticbase = subtype;
> while (staticbase && (staticbase->tp_flags & Py_TPFLAGS_HEAPTYPE))
> staticbase = staticbase->tp_base;
>
> (these two lines are from tp_new_wrapper() in typeobject.c)
>
This looks like overengineering to me. I think 3.x should simply
refuse to unpickle old style class instances into anything that is not
subclassed in python directly from object.
Right, but it would lead to almost the same code... Since you have to
find the first non-Python base type and check that it is "object".
It would be helpful at this point if you could provide a test case
where tp_alloc logic does not work.
I'll try to find one.
PPS: What is you opinion on the pickle.py trick of monkey patching
__class__ on an instance of an empty class? Do you think this can be
fooled?
I think I have found a better solution. Since we are dealing with classic classes, they should not define __new__. If in porting to 3.x, users introduce __new__ that requires arguments, they will not be able to unpickle 2.x pickles no matter what we do.
Therefore, I propose to replace _EmptyClass trick in pickle.py with a call to klass.__new__:
value = \_EmptyClass()
value.\_\_class__ = klass
+ value = klass.__new__(klass)
and do the same in _pickle.c's instantiate:
r=PyObject_CallMethod(cls, "__new__", "O", cls);
Note that I am not even calling tp_new here directly, so all the hoops in tp_new_wrapper get jumped through.
The advantage of this scheme is that python and C code will work exactly the same and users that have corner cases for which the scheme does not work will be able to figure out what is going on by looking at the python code.
I have fixed some style nits in your patch. It would be nice to have tests for the different control paths in instantiate(). But, I realize that would be a bit annoying.
I am not sure your change form PyObject_Size(args) to Py_SIZE(args) is correct. As far as I can tell, args come from pickle machine stack without any type checks. The equivalent code in 2.x cPickle uses PyObject_Size and checks for errors. I'll try to come up with a test case for the error situation, but it is a bit tricky because pickle should be produced in 2.x in order for this function to be used.
The args argument is always a tuple created with Pdata_poptuple(). You can add an explicit type check. If this check fails a RuntimeError should be raised, because this would indicate a programming error in pickle.
Activity
pitrou commentedon Feb 7, 2009
The pickle was generated by pybench. You can try to display it by running:
./python Tools/pybench/pybench.py --debug -s 27.bench
It fails with the following traceback:
pitrou commentedon Feb 7, 2009
Here is the pickle file.
abalkin commentedon Jun 27, 2010
If I disable _pickle, I get a more meaningful trace:
File "Tools/pybench/pybench.py", line 954, in <module>
PyBenchCmdline()
File "/Users/sasha/Work/python-svn/py3k-commit/Tools/pybench/CommandLine.py", line 349, in __init__
rc = self.main()
File "Tools/pybench/pybench.py", line 888, in main
bench = pickle.load(f)
File "/Users/sasha/Work/python-svn/py3k-commit/Lib/pickle.py", line 1321, in load
encoding=encoding, errors=errors).load()
File "/Users/sasha/Work/python-svn/py3k-commit/Lib/pickle.py", line 830, in load
dispatchkey[0]
File "/Users/sasha/Work/python-svn/py3k-commit/Lib/pickle.py", line 1055, in load_inst
klass = self.find_class(module, name)
File "/Users/sasha/Work/python-svn/py3k-commit/Lib/pickle.py", line 1115, in find_class
__import__(module, level=0)
File "/Users/sasha/Work/python-svn/py3k-commit/Tools/pybench/Unicode.py", line 17
s = unicode(u''.join(map(str,range(100))))
^
SyntaxError: invalid syntax
Apparently, pybench/Unicode.py has never been converted to py3k. This is likely not the only problem:
This looks like the infamous bytes vs string problem ...
abalkin commentedon Jun 27, 2010
The bytes/string issu was a red herring: with pickle.load(open('27.bench', 'b')), I get the same stack trace as from command line pybench invocation.
abalkin commentedon Jun 27, 2010
Hmm. It looks like another pickle vs. _pickle issue. Attached patch is a result of 2to3 applied to Tools/pybench (and a minor manual fix for pickle import) with _pickle import disabled.
With the patch applied,
runs fine and produces results identical to those for a similar run from the trunk.
Looks like it is time to fire up gdb.
malemburg commentedon Jun 27, 2010
Please keep the Python3 version of pybench compatible to Python 2.6 and 2.7.
abalkin commentedon Jun 27, 2010
I have found bpo-1634499 which deals with running pybench. I suggest that we reopen that issue and move discussion of bpo-5180.diff there.
I really like the idea to keep single source for 2.x and 3.x pybench, but we need to add some machinery to run 2.x pybench under python3.x transparently.
Marc-Andre,
are you taking over the pybench or the _pickle issue?
abalkin commentedon Jun 28, 2010
It looks like I was able to get to the root of the problem. I am attaching two files that demonstrate the issue:
==> pickle-bug.py <==
import pickle
import sys
class Bug:
pass
bug = Bug()
f = open(sys.argv[1], 'w')
pickle.Pickler(f, protocol=0).dump(bug)
f.close()
==> unpickle-bug.py <==
import pickle
import sys
class Bug:
pass
bug = pickle._Unpickler(open(sys.argv[1], 'rb')).load() # works
print(bug)
bug = pickle.Unpickler(open(sys.argv[1], 'rb')).load() # doesn't
print(bug)
The problematic pickle is really small, so I hope I'll report that I have a fix soon.
15: p PUT 0
18: ( MARK
19: d DICT (MARK at 18)
20: p PUT 1
23: b BUILD
24: . STOP
highest protocol among opcodes = 0
abalkin commentedon Jun 28, 2010
As promised, here is the fix (bpo-5180-pickle.diff) for the "unpickle-bug.py" issue. Unfortunately, it looks like more bugs remain. 27.bench is still not loadable.
abalkin commentedon Jun 28, 2010
The remaining bug is a bit harder. If you try to run unpickle-bug-2.py on the same pickle, you get
The problem is acknowledged in the source code of instantiate function in _pickle.c:
Clearly, this is wrong because pickle.py way succeeds where _pickle does not.
PS: I did not mean to unassign MAL, but I think this is more of alexandre.vassalotti's issue.
4 remaining items
avassalotti commentedon Jun 28, 2010
Thank you for the nice investigative work!
I will try my best to review this patch by next week.
pitrou commentedon Jun 28, 2010
This is certainly the wrong thing to do. You could at least try PyBaseObject_Type.tp_new (see object_new() in typeobject.c), but even this is not necessarily right (for example if the class is derived from an extension type defining its own tp_new).
So, IMO, the right thing to do would be to choose the first base type that isn't a Python-defined class and use its tp_new:
(these two lines are from tp_new_wrapper() in typeobject.c)
That way you choose the right constructor function, yet don't call the Python-defined __new__ function. That's the only reasonable way of doing it I can imagine. It also follows the following behaviour:
As you can see, int.__new__ has been called on unpickling but not C.__new__ (no print).
pitrou commentedon Jun 28, 2010
(and to follow on my example, I'd point that the unpickled instance is still an instance of the right class:
>>> type(x) <class '__main__.C'>)
abalkin commentedon Jun 29, 2010
On Mon, Jun 28, 2010 at 7:32 PM, Antoine Pitrou <report@bugs.python.org> wrote:
Well, this is not so obviously wrong as you make it sound. If you look closer at object_new(), you will see that in sane situations it reduces to type->tp_alloc(type, 0) call. Remember, this is called in very special circumstances: when unpickling old style class instances from a 2.x generated pickle. No arguments are available for tp_new call, so excess_args check does not apply. The other check is for an ABC, but the situation where an old-style class becomes an ABC in 2.x to 3.x transition is hardly sane. If such sanity check is necessary, I would rather check the flag explicitly in _pickle.c rather than having to generate dummy args and kwds to satisfy tp_new signature.
This looks like overengineering to me. I think 3.x should simply refuse to unpickle old style class instances into anything that is not subclassed in python directly from object. It would be helpful at this point if you could provide a test case where tp_alloc logic does not work. Regardless of what algorithm we ultimately choose, I would like to include such test in regrtest.
PS: Note that in my patch I've eliminated one control path that was calling instantiate(..) erroneously, but I have not reviewed the code carefully enough to eliminate the possibility that instantiate(..) would be called to instantiate new-style class instances.
PPS: What is you opinion on the pickle.py trick of monkey patching __class__ on an instance of an empty class? Do you think this can be fooled?
pitrou commentedon Jun 29, 2010
Today, yes. But tomorrow it may entail additional operations.
I find it better to "generate dummy args and kwds to satisfy tp_new
signature". I see no point in a partial inlining of an internal function
when we could call it instead.
Right, but it would lead to almost the same code... Since you have to
find the first non-Python base type and check that it is "object".
I'll try to find one.
I don't think so.
abalkin commentedon Jul 15, 2010
Antoine,
I think I have found a better solution. Since we are dealing with classic classes, they should not define __new__. If in porting to 3.x, users introduce __new__ that requires arguments, they will not be able to unpickle 2.x pickles no matter what we do.
Therefore, I propose to replace _EmptyClass trick in pickle.py with a call to klass.__new__:
+ value = klass.__new__(klass)
and do the same in _pickle.c's instantiate:
Note that I am not even calling tp_new here directly, so all the hoops in tp_new_wrapper get jumped through.
The advantage of this scheme is that python and C code will work exactly the same and users that have corner cases for which the scheme does not work will be able to figure out what is going on by looking at the python code.
Attaching issue5180a.diff
abalkin commentedon Jul 17, 2010
Antoine said on IRC that he is ok with the latest approach. Does anyone want to review the patch before it goes in?
avassalotti commentedon Jul 17, 2010
I have fixed some style nits in your patch. It would be nice to have tests for the different control paths in instantiate(). But, I realize that would be a bit annoying.
Apart from that, the patch looks good.
abalkin commentedon Jul 17, 2010
Alexandre,
I am not sure your change form PyObject_Size(args) to Py_SIZE(args) is correct. As far as I can tell, args come from pickle machine stack without any type checks. The equivalent code in 2.x cPickle uses PyObject_Size and checks for errors. I'll try to come up with a test case for the error situation, but it is a bit tricky because pickle should be produced in 2.x in order for this function to be used.
avassalotti commentedon Jul 17, 2010
The args argument is always a tuple created with Pdata_poptuple(). You can add an explicit type check. If this check fails a RuntimeError should be raised, because this would indicate a programming error in pickle.
abalkin commentedon Jul 17, 2010
Committed issue5180b.diff with minor additions in r82937 (r82938 for 3.1):
Added an assert that args is a tuple in instantiate() with an explanation why it is true.
Added a test for the other code branch instantiate. (Not all conditions are tested, but I think this is now good enough.)
The explanation of INST and OBJ opcodes in pickletools is now out of date, so I am adding bpo-9267 as a superseder.