[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