[Patches] two more fix for extended call syntax
Jeremy Hylton
jeremy@cnri.reston.va.us
Wed, 29 Mar 2000 16:23:49 -0500 (EST)
I was feeling paranoid today, after Barry's bug report last night, and
wrote some more obscure tests cases for the new call syntax. I found
two more problems that need fixing, and have patches below. The
second problem & patch are a little questionable, so I'm running them
through the patches list. If they look ok, I can check them in.
Bug #1: A user-defined class implements the sequence protocol
incorrectly, but raises an IndexError for index i where i is less
than the value returned by __length__. The current code creates
a tuple from the instance, then happily indexes off the end of the
tuple. The fix is to never give the instance a chance to lie;
convert to a tuple first, then see how long the tuple is.
Bug #2: The use of old-style keyword arguments along with **dict
modifies dict, adding entries for each of the explicit keywords.
The solution I've taken is to copy the **dict if there are
old-style keyword arguments. I'm not happy about copying a
potentially large dictionary, but the case seems to obscure to
optimize. I also discovered that there was no Python C API call
to copy a dictionary, so I added a PyDict_Copy method by stealing
the body of dict_copy.
Jeremy
=== cd /home/jhylton/python/src/Include/
=== /depot/gnu/plat/bin/cvs diff -c dictobject.h
Index: dictobject.h
===================================================================
RCS file: /projects/cvsroot/python/dist/src/Include/dictobject.h,v
retrieving revision 2.15
diff -c -r2.15 dictobject.h
*** dictobject.h 1998/12/04 18:47:57 2.15
--- dictobject.h 2000/03/29 19:57:15
***************
*** 52,57 ****
--- 52,59 ----
extern DL_IMPORT(PyObject *) PyDict_Values Py_PROTO((PyObject *mp));
extern DL_IMPORT(PyObject *) PyDict_Items Py_PROTO((PyObject *mp));
extern DL_IMPORT(int) PyDict_Size Py_PROTO((PyObject *mp));
+ extern DL_IMPORT(PyObject *) PyDict_Copy Py_PROTO((PyObject *mp));
+
extern DL_IMPORT(PyObject *) PyDict_GetItemString Py_PROTO((PyObject *dp, char *key));
extern DL_IMPORT(int) PyDict_SetItemString Py_PROTO((PyObject *dp, char *key, PyObject *item));
=== Exit status: 1
=== cd /home/jhylton/python/src/Lib/test/
=== /depot/gnu/plat/bin/cvs diff -c test_extcall.py
Index: test_extcall.py
===================================================================
RCS file: /projects/cvsroot/python/dist/src/Lib/test/test_extcall.py,v
retrieving revision 1.2
diff -c -r1.2 test_extcall.py
*** test_extcall.py 2000/03/28 23:53:18 1.2
--- test_extcall.py 2000/03/29 20:25:56
***************
*** 46,51 ****
--- 46,95 ----
g(1, 2)
g(1, 2, 3)
g(1, 2, 3, *(4, 5))
+ class Nothing: pass
+ try:
+ g(*Nothing())
+ except AttributeError, attr:
+ assert attr[0] == '__len__'
+ else:
+ print "should raise AttributeError: __len__"
+
+ class Nothing:
+ def __len__(self):
+ return 5
+ try:
+ g(*Nothing())
+ except AttributeError, attr:
+ assert attr[0] == '__getitem__'
+ else:
+ print "should raise AttributeError: __getitem__"
+
+ class Nothing:
+ def __len__(self):
+ return 5
+ def __getitem__(self, i):
+ if i < 3:
+ return i
+ else:
+ raise IndexError, i
+ g(*Nothing())
+
+ # make sure the function call doesn't stomp on the dictionary?
+ d = {'a': 1, 'b': 2, 'c': 3}
+ d2 = d.copy()
+ assert d == d2
+ g(1, d=4, **d)
+ print d
+ print d2
+ assert d == d2, "function call modified dictionary"
+
+ # what about willful misconduct?
+ def saboteur(**kw):
+ kw['x'] = locals()
+ d = {}
+ saboteur(a=1, **d)
+ assert d == {}
+
try:
g(1, 2, 3, **{'x':4, 'y':5})
except TypeError, err:
=== Exit status: 1
=== cd /home/jhylton/python/src/Objects/
=== /depot/gnu/plat/bin/cvs diff -c dictobject.c
Index: dictobject.c
===================================================================
RCS file: /projects/cvsroot/python/dist/src/Objects/dictobject.c,v
retrieving revision 2.50
diff -c -r2.50 dictobject.c
*** dictobject.c 2000/03/13 16:01:29 2.50
--- dictobject.c 2000/03/29 20:17:24
***************
*** 738,748 ****
register dictobject *mp;
PyObject *args;
{
register int i;
dictobject *copy;
dictentry *entry;
! if (!PyArg_Parse(args, ""))
return NULL;
copy = (dictobject *)PyDict_New();
if (copy == NULL)
return NULL;
--- 738,760 ----
register dictobject *mp;
PyObject *args;
{
+ if (!PyArg_Parse(args, ""))
+ return NULL;
+ return PyDict_Copy((PyObject*)mp);
+ }
+
+ PyObject *
+ PyDict_Copy(o)
+ PyObject *o;
+ {
+ register dictobject *mp;
register int i;
dictobject *copy;
dictentry *entry;
!
! if (!PyDict_Check(o))
return NULL;
+ mp = (dictobject *)o;
copy = (dictobject *)PyDict_New();
if (copy == NULL)
return NULL;
=== Exit status: 1
=== cd /home/jhylton/python/src/Python/
=== /depot/gnu/plat/bin/cvs diff -c ceval.c
Index: ceval.c
===================================================================
RCS file: /projects/cvsroot/python/dist/src/Python/ceval.c,v
retrieving revision 2.171
diff -c -r2.171 ceval.c
*** ceval.c 2000/03/29 18:36:49 2.171
--- ceval.c 2000/03/29 20:24:22
***************
*** 1635,1641 ****
x = NULL;
break;
}
! nstar = PySequence_Length(stararg);
if (nstar < 0) {
x = NULL;
break;
--- 1635,1652 ----
x = NULL;
break;
}
! /* Convert abstract sequence to concrete tuple */
! if (!PyTuple_Check(stararg)) {
! PyObject *t = NULL;
! t = PySequence_Tuple(stararg);
! if (t == NULL) {
! x = NULL;
! break;
! }
! Py_DECREF(stararg);
! stararg = t;
! }
! nstar = PyTuple_GET_SIZE(stararg);
if (nstar < 0) {
x = NULL;
break;
***************
*** 1649,1654 ****
--- 1660,1674 ----
break;
}
}
+ else {
+ PyObject *d = PyDict_Copy(kwdict);
+ if (d == NULL) {
+ x = NULL;
+ break;
+ }
+ Py_DECREF(kwdict);
+ kwdict = d;
+ }
err = 0;
while (--nk >= 0) {
PyObject *value = POP();
***************
*** 1678,1695 ****
break;
}
if (stararg) {
- PyObject *t = NULL;
int i;
- if (!PyTuple_Check(stararg)) {
- /* must be sequence to pass earlier test */
- t = PySequence_Tuple(stararg);
- if (t == NULL) {
- x = NULL;
- break;
- }
- Py_DECREF(stararg);
- stararg = t;
- }
for (i = 0; i < nstar; i++) {
PyObject *a = PyTuple_GET_ITEM(stararg, i);
Py_INCREF(a);
--- 1698,1704 ----
=== Exit status: 1