[pypy-svn] r79728 - in pypy/trunk: lib_pypy/_ctypes pypy/module/test_lib_pypy/ctypes_tests

afa at codespeak.net afa at codespeak.net
Thu Dec 2 00:45:41 CET 2010


Author: afa
Date: Thu Dec  2 00:45:35 2010
New Revision: 79728

Modified:
   pypy/trunk/lib_pypy/_ctypes/array.py
   pypy/trunk/lib_pypy/_ctypes/basics.py
   pypy/trunk/lib_pypy/_ctypes/builtin.py
   pypy/trunk/lib_pypy/_ctypes/function.py
   pypy/trunk/lib_pypy/_ctypes/pointer.py
   pypy/trunk/lib_pypy/_ctypes/primitive.py
   pypy/trunk/lib_pypy/_ctypes/structure.py
   pypy/trunk/pypy/module/test_lib_pypy/ctypes_tests/test_guess_argtypes.py
   pypy/trunk/pypy/module/test_lib_pypy/ctypes_tests/test_keepalive.py
Log:
Merge the most important _ctypes fixes from the fast-forward branch
- better keepalives should avoid some crashes
- sub-classes of Struct should put the base fields first
- respect the "from_param" conversion method

... 10 less failures in the (skipped!) cpython test suite


Modified: pypy/trunk/lib_pypy/_ctypes/array.py
==============================================================================
--- pypy/trunk/lib_pypy/_ctypes/array.py	(original)
+++ pypy/trunk/lib_pypy/_ctypes/array.py	Thu Dec  2 00:45:35 2010
@@ -99,6 +99,9 @@
                 if len(value) > self._length_:
                     raise ValueError("Invalid length")
                 value = self(*value)
+            elif not isinstance(value, self):
+                raise TypeError("expected string or Unicode object, %s found"
+                                % (value.__class__.__name__,))
         else:
             if isinstance(value, tuple):
                 if len(value) > self._length_:
@@ -107,22 +110,43 @@
         return _CDataMeta.from_param(self, value)
 
 def array_get_slice_params(self, index):
-    if index.step is not None:
-        raise TypeError("3 arg slices not supported (for no reason)")
-    start = index.start or 0
-    stop = index.stop or self._length_
-    return start, stop
+    if hasattr(self, '_length_'):
+        start, stop, step = index.indices(self._length_)
+    else:
+        step = index.step
+        if step is None:
+            step = 1
+        start = index.start
+        stop = index.stop
+        if start is None:
+            if step > 0:
+                start = 0
+            else:
+                raise ValueError("slice start is required for step < 0")
+        if stop is None:
+            raise ValueError("slice stop is required")
+
+    return start, stop, step
 
 def array_slice_setitem(self, index, value):
-    start, stop = self._get_slice_params(index)
-    if stop - start != len(value):
+    start, stop, step = self._get_slice_params(index)
+
+    if ((step < 0 and stop >= start) or
+        (step > 0 and start >= stop)):
+        slicelength = 0
+    elif step < 0:
+        slicelength = (stop - start + 1) / step + 1
+    else:
+        slicelength = (stop - start - 1) / step + 1;
+
+    if slicelength != len(value):
         raise ValueError("Can only assign slices of the same length")
-    for i in range(start, stop):
-        self[i] = value[i - start]
+    for i, j in enumerate(range(start, stop, step)):
+        self[j] = value[i]
 
 def array_slice_getitem(self, index):
-    start, stop = self._get_slice_params(index)
-    l = [self[i] for i in range(start, stop)]
+    start, stop, step = self._get_slice_params(index)
+    l = [self[i] for i in range(start, stop, step)]
     letter = getattr(self._type_, '_type_', None)
     if letter == 'c':
         return "".join(l)
@@ -134,8 +158,12 @@
     __metaclass__ = ArrayMeta
     _ffiargshape = 'P'
 
-    def __init__(self, *args):
+    def __new__(cls, *args):
+        self = _CData.__new__(cls, *args)
         self._buffer = self._ffiarray(self._length_, autofree=True)
+        return self
+
+    def __init__(self, *args):
         for i, arg in enumerate(args):
             self[i] = arg
 
@@ -162,9 +190,10 @@
             self._slice_setitem(index, value)
             return
         index = self._fix_index(index)
-        if ensure_objects(value) is not None:
-            store_reference(self, index, value._objects)
-        arg = self._type_._CData_value(value)
+        cobj = self._type_.from_param(value)
+        if ensure_objects(cobj) is not None:
+            store_reference(self, index, cobj._objects)
+        arg = cobj._get_buffer_value()
         if self._type_._fficompositesize is None:
             self._buffer[index] = arg
             # something more sophisticated, cannot set field directly
@@ -183,7 +212,7 @@
         return self._length_
 
     def _get_buffer_for_param(self):
-        return CArgObject(self._buffer.byptr())
+        return CArgObject(self, self._buffer.byptr())
 
     def _get_buffer_value(self):
         return self._buffer.buffer

Modified: pypy/trunk/lib_pypy/_ctypes/basics.py
==============================================================================
--- pypy/trunk/lib_pypy/_ctypes/basics.py	(original)
+++ pypy/trunk/lib_pypy/_ctypes/basics.py	Thu Dec  2 00:45:35 2010
@@ -46,21 +46,6 @@
         else:
             return self.from_param(as_parameter)
 
-    def _CData_input(self, value):
-        """Used when data enters into ctypes from user code.  'value' is
-        some user-specified Python object, which is converted into a _rawffi
-        array of length 1 containing the same value according to the
-        type 'self'.
-        """
-        cobj = self.from_param(value)
-        return cobj, cobj._get_buffer_for_param()
-
-    def _CData_value(self, value):
-        cobj = self.from_param(value)
-        # we don't care here if this stuff will live afterwards, as we're
-        # interested only in value anyway
-        return cobj._get_buffer_value()
-
     def _CData_output(self, resbuffer, base=None, index=-1):
         #assert isinstance(resbuffer, _rawffi.ArrayInstance)
         """Used when data exits ctypes and goes into user code.
@@ -92,13 +77,20 @@
     """ simple wrapper around buffer, just for the case of freeing
     it afterwards
     """
-    def __init__(self, buffer):
+    def __init__(self, obj, buffer):
+        self._obj = obj
         self._buffer = buffer
 
     def __del__(self):
         self._buffer.free()
         self._buffer = None
 
+    def __repr__(self):
+        return repr(self._obj)
+
+    def __eq__(self, other):
+        return self._obj == other
+
 class _CData(object):
     """ The most basic object for all ctypes types
     """
@@ -128,7 +120,7 @@
         return buffer(self._buffer)
 
     def _get_b_base(self):
-        return self._objects
+        return self._base
     _b_base_ = property(_get_b_base)
     _b_needsfree_ = False
 

Modified: pypy/trunk/lib_pypy/_ctypes/builtin.py
==============================================================================
--- pypy/trunk/lib_pypy/_ctypes/builtin.py	(original)
+++ pypy/trunk/lib_pypy/_ctypes/builtin.py	Thu Dec  2 00:45:35 2010
@@ -11,7 +11,8 @@
 def _string_at(addr, lgt):
     # address here can be almost anything
     import ctypes
-    arg = ctypes.c_void_p._CData_value(addr)
+    cobj = ctypes.c_void_p.from_param(addr)
+    arg = cobj._get_buffer_value()
     return _rawffi.charp2rawstring(arg, lgt)
 
 def set_conversion_mode(encoding, errors):
@@ -22,7 +23,8 @@
 
 def _wstring_at(addr, lgt):
     import ctypes
-    arg = ctypes.c_void_p._CData_value(addr)
+    cobj = ctypes.c_void_p.from_param(addr)
+    arg = cobj._get_buffer_value()
     # XXX purely applevel
     if lgt == -1:
         lgt = sys.maxint

Modified: pypy/trunk/lib_pypy/_ctypes/function.py
==============================================================================
--- pypy/trunk/lib_pypy/_ctypes/function.py	(original)
+++ pypy/trunk/lib_pypy/_ctypes/function.py	Thu Dec  2 00:45:35 2010
@@ -35,6 +35,7 @@
 
     _argtypes_ = None
     _restype_ = None
+    _errcheck_ = None
     _flags_ = 0
     _ffiargshape = 'P'
     _ffishape = 'P'
@@ -53,7 +54,15 @@
         return self._argtypes_
     def _setargtypes(self, argtypes):
         self._ptr = None
-        self._argtypes_ = argtypes    
+        if argtypes is None:
+            self._argtypes_ = None
+        else:
+            for i, argtype in enumerate(argtypes):
+                if not hasattr(argtype, 'from_param'):
+                    raise TypeError(
+                        "item %d in _argtypes_ has no from_param method" % (
+                            i + 1,))
+            self._argtypes_ = argtypes
     argtypes = property(_getargtypes, _setargtypes)
 
     def _getrestype(self):
@@ -72,9 +81,24 @@
         del self._restype_
     restype = property(_getrestype, _setrestype, _delrestype)
 
+    def _geterrcheck(self):
+        return getattr(self, '_errcheck_', None)
+    def _seterrcheck(self, errcheck):
+        if not callable(errcheck):
+            raise TypeError("The errcheck attribute must be callable")
+        self._errcheck_ = errcheck
+    def _delerrcheck(self):
+        try:
+            del self._errcheck_
+        except AttributeError:
+            pass
+    errcheck = property(_geterrcheck, _seterrcheck, _delerrcheck)
+
     def _ffishapes(self, args, restype):
         argtypes = [arg._ffiargshape for arg in args]
         if restype is not None:
+            if not isinstance(restype, _CDataMeta):
+                raise TypeError("invalid result type for callback function")
             restype = restype._ffiargshape
         else:
             restype = 'O' # void
@@ -140,6 +164,7 @@
     
     def __call__(self, *args):
         if self.callable is not None:
+            args = args[:len(self._argtypes_)]
             try:
                 res = self.callable(*args)
             except:
@@ -162,13 +187,29 @@
             thisarg = None
             
         if argtypes is None:
-            argtypes = self._guess_argtypes(args)
-        argtypes, argsandobjs = self._wrap_args(argtypes, args)
+            argtypes = []
+        args = self._convert_args(argtypes, args)
+        argtypes = [type(arg) for arg in args]
         
         restype = self._restype_
         funcptr = self._getfuncptr(argtypes, restype, thisarg)
-        resbuffer = funcptr(*[arg._buffer for _, arg in argsandobjs])
-        return self._build_result(restype, resbuffer, argtypes, argsandobjs)
+        resbuffer = funcptr(*[arg._get_buffer_for_param()._buffer
+                              for arg in args])
+        result = self._build_result(restype, resbuffer, argtypes, args)
+
+        # The 'errcheck' protocol
+        if self._errcheck_:
+            v = self._errcheck_(result, self, args)
+            # If the errcheck funtion failed, let it throw
+            # If the errcheck function returned callargs unchanged,
+            # continue normal processing.
+            # If the errcheck function returned something else,
+            # use that as result.
+            if v is not args:
+                result = v
+
+        return result
+
 
     def _getfuncptr(self, argtypes, restype, thisarg=None):
         if self._ptr is not None and argtypes is self._argtypes_:
@@ -212,31 +253,33 @@
             raise
 
     @staticmethod
-    def _guess_argtypes(args):
+    def _conv_param(argtype, arg, index):
         from ctypes import c_char_p, c_wchar_p, c_void_p, c_int
-        res = []
-        for arg in args:
-            if hasattr(arg, '_as_parameter_'):
-                arg = arg._as_parameter_
-            if isinstance(arg, str):
-                res.append(c_char_p)
-            elif isinstance(arg, unicode):
-                res.append(c_wchar_p)
-            elif isinstance(arg, _CData):
-                res.append(type(arg))
-            elif arg is None:
-                res.append(c_void_p)
-            #elif arg == 0:
-            #    res.append(c_void_p)
-            elif isinstance(arg, (int, long)):
-                res.append(c_int)
-            else:
-                raise TypeError("Don't know how to handle %s" % (arg,))
-        return res
+        if argtype is not None:
+            arg = argtype.from_param(arg)
+        if hasattr(arg, '_as_parameter_'):
+            arg = arg._as_parameter_
+
+        if isinstance(arg, _CData):
+            # The usual case when argtype is defined
+            cobj = arg
+        elif isinstance(arg, str):
+            cobj = c_char_p(arg)
+        elif isinstance(arg, unicode):
+            cobj = c_wchar_p(arg)
+        elif arg is None:
+            cobj = c_void_p()
+        elif isinstance(arg, (int, long)):
+            cobj = c_int(arg)
+        else:
+            raise TypeError("Don't know how to handle %s" % (arg,))
 
-    def _wrap_args(self, argtypes, args):
+        return cobj
+
+    def _convert_args(self, argtypes, args):
         wrapped_args = []
         consumed = 0
+
         for i, argtype in enumerate(argtypes):
             defaultvalue = None
             if i > 0 and self._paramflags is not None:
@@ -263,7 +306,7 @@
                     val = defaultvalue
                     if val is None:
                         val = 0
-                    wrapped = argtype._CData_input(val)
+                    wrapped = self._conv_param(argtype, val, consumed)
                     wrapped_args.append(wrapped)
                     continue
                 else:
@@ -278,23 +321,22 @@
                 raise TypeError("Not enough arguments")
 
             try:
-                wrapped = argtype._CData_input(arg)
-            except (UnicodeError, TypeError), e:
+                wrapped = self._conv_param(argtype, arg, consumed)
+            except (UnicodeError, TypeError, ValueError), e:
                 raise ArgumentError(str(e))
             wrapped_args.append(wrapped)
             consumed += 1
 
         if len(wrapped_args) < len(args):
             extra = args[len(wrapped_args):]
-            extra_types = self._guess_argtypes(extra)
-            for arg, argtype in zip(extra, extra_types):
+            argtypes = list(argtypes)
+            for i, arg in enumerate(extra):
                 try:
-                    wrapped = argtype._CData_input(arg)
-                except (UnicodeError, TypeError), e:
+                    wrapped = self._conv_param(None, arg, i)
+                except (UnicodeError, TypeError, ValueError), e:
                     raise ArgumentError(str(e))
                 wrapped_args.append(wrapped)
-            argtypes = list(argtypes) + extra_types
-        return argtypes, wrapped_args
+        return wrapped_args
 
     def _build_result(self, restype, resbuffer, argtypes, argsandobjs):
         """Build the function result:
@@ -307,7 +349,7 @@
         if self._com_index:
             if resbuffer[0] & 0x80000000:
                 raise get_com_error(resbuffer[0],
-                                    self._com_iid, argsandobjs[0][0])
+                                    self._com_iid, argsandobjs[0])
             else:
                 retval = int(resbuffer[0])
         elif restype is not None:
@@ -326,8 +368,8 @@
 
         results = []
         if self._paramflags:
-            for argtype, (obj, _), paramflag in zip(argtypes[1:], argsandobjs[1:],
-                                                    self._paramflags):
+            for argtype, obj, paramflag in zip(argtypes[1:], argsandobjs[1:],
+                                               self._paramflags):
                 if len(paramflag) == 2:
                     idlflag, name = paramflag
                 elif len(paramflag) == 3:

Modified: pypy/trunk/lib_pypy/_ctypes/pointer.py
==============================================================================
--- pypy/trunk/lib_pypy/_ctypes/pointer.py	(original)
+++ pypy/trunk/lib_pypy/_ctypes/pointer.py	Thu Dec  2 00:45:35 2010
@@ -1,7 +1,8 @@
 
 import _rawffi
 from _ctypes.basics import _CData, _CDataMeta, cdata_from_address
-from _ctypes.basics import sizeof, byref, keepalive_key
+from _ctypes.basics import keepalive_key, store_reference, ensure_objects
+from _ctypes.basics import sizeof, byref
 from _ctypes.array import Array, array_get_slice_params, array_slice_getitem,\
      array_slice_setitem
 
@@ -99,7 +100,10 @@
         return self._type_._CData_output(self._subarray(index), self, index)
 
     def __setitem__(self, index, value):
-        self._subarray(index)[0] = self._type_._CData_value(value)
+        cobj = self._type_.from_param(value)
+        if ensure_objects(cobj) is not None:
+            store_reference(self, index, cobj._objects)
+        self._subarray(index)[0] = cobj._get_buffer_value()
 
     def __nonzero__(self):
         return self._buffer[0] != 0
@@ -110,29 +114,32 @@
     if not (isinstance(tp, _CDataMeta) and tp._is_pointer_like()):
         raise TypeError("cast() argument 2 must be a pointer type, not %s"
                         % (tp,))
-    if isinstance(obj, Array):
-        ptr = tp.__new__(tp)
-        ptr._buffer = tp._ffiarray(1, autofree=True)
-        ptr._buffer[0] = obj._buffer
-        return ptr
     if isinstance(obj, (int, long)):
         result = tp()
         result._buffer[0] = obj
         return result
-    if obj is None:
+    elif obj is None:
         result = tp()
         return result
-    if not (isinstance(obj, _CData) and type(obj)._is_pointer_like()):
+    elif isinstance(obj, Array):
+        ptr = tp.__new__(tp)
+        ptr._buffer = tp._ffiarray(1, autofree=True)
+        ptr._buffer[0] = obj._buffer
+        result = ptr
+    elif not (isinstance(obj, _CData) and type(obj)._is_pointer_like()):
         raise TypeError("cast() argument 1 must be a pointer, not %s"
                         % (type(obj),))
-    result = tp()
+    else:
+        result = tp()
+        result._buffer[0] = obj._buffer[0]
 
     # The casted objects '_objects' member:
-    # It must certainly contain the source objects one.
+    # From now on, both objects will use the same dictionary
+    # It must certainly contain the source objects
     # It must contain the source object itself.
     if obj._ensure_objects() is not None:
-        result._objects = {keepalive_key(0): obj._objects,
-                           keepalive_key(1): obj}
+        result._objects = obj._objects
+        if isinstance(obj._objects, dict):
+            result._objects[id(obj)] =  obj
 
-    result._buffer[0] = obj._buffer[0]
     return result

Modified: pypy/trunk/lib_pypy/_ctypes/primitive.py
==============================================================================
--- pypy/trunk/lib_pypy/_ctypes/primitive.py	(original)
+++ pypy/trunk/lib_pypy/_ctypes/primitive.py	Thu Dec  2 00:45:35 2010
@@ -132,8 +132,8 @@
                                              ConvMode.errors)
                     #self._objects = value
                     array = _rawffi.Array('c')(len(value)+1, value)
+                    self._objects = CArgObject(value, array)
                     value = array.buffer
-                    self._objects = {'0': CArgObject(array)}
                 elif value is None:
                     value = 0
                 self._buffer[0] = value
@@ -155,8 +155,8 @@
                                              ConvMode.errors)
                     #self._objects = value
                     array = _rawffi.Array('u')(len(value)+1, value)
+                    self._objects = CArgObject(value, array)
                     value = array.buffer
-                    self._objects = {'0': CArgObject(array)}
                 elif value is None:
                     value = 0
                 self._buffer[0] = value
@@ -174,8 +174,8 @@
             def _setvalue(self, value):
                 if isinstance(value, str):
                     array = _rawffi.Array('c')(len(value)+1, value)
+                    self._objects = CArgObject(value, array)
                     value = array.buffer
-                    self._objects = {'0': CArgObject(array)}
                 elif value is None:
                     value = 0
                 self._buffer[0] = value
@@ -271,7 +271,9 @@
 
     def _CData_output(self, resbuffer, base=None, index=-1):
         output = super(SimpleType, self)._CData_output(resbuffer, base, index)
-        return output.value
+        if self.__bases__[0] is _SimpleCData:
+            return output.value
+        return output
     
     def _sizeofinstances(self):
         return _rawffi.sizeof(self._type_)
@@ -286,8 +288,12 @@
     __metaclass__ = SimpleType
     _type_ = 'i'
 
-    def __init__(self, value=DEFAULT_VALUE):
+    def __new__(cls, *args, **kwds):
+        self = _CData.__new__(cls, *args, **kwds)
         self._buffer = self._ffiarray(1, autofree=True)
+        return self
+
+    def __init__(self, value=DEFAULT_VALUE):
         if value is not DEFAULT_VALUE:
             self.value = value
 
@@ -312,7 +318,11 @@
         return self.value
 
     def __repr__(self):
-        return "%s(%r)" % (type(self).__name__, self.value)
+        if type(self).__bases__[0] is _SimpleCData:
+            return "%s(%r)" % (type(self).__name__, self.value)
+        else:
+            return "<%s object at 0x%x>" % (type(self).__name__,
+                                            id(self))
 
     def __nonzero__(self):
         return self._buffer[0] not in (0, '\x00')

Modified: pypy/trunk/lib_pypy/_ctypes/structure.py
==============================================================================
--- pypy/trunk/lib_pypy/_ctypes/structure.py	(original)
+++ pypy/trunk/lib_pypy/_ctypes/structure.py	Thu Dec  2 00:45:35 2010
@@ -34,9 +34,11 @@
         if not isinstance(tp, _CDataMeta):
             raise TypeError("Expected CData subclass, got %s" % (tp,))
     import ctypes
-    all_fields = _fields_[:]
-    for cls in inspect.getmro(superclass):
-        all_fields += getattr(cls, '_fields_', [])
+    all_fields = []
+    for cls in reversed(inspect.getmro(superclass)):
+        # The first field comes from the most base class
+        all_fields.extend(getattr(cls, '_fields_', []))
+    all_fields.extend(_fields_)
     names = [name for name, ctype in all_fields]
     rawfields = [(name, ctype._ffishape)
                  for name, ctype in all_fields]
@@ -168,7 +170,7 @@
 
     def __init__(self, *args, **kwds):
         if len(args) > len(self._names):
-            raise TypeError("too many arguments")
+            raise TypeError("too many initializers")
         for name, arg in zip(self._names, args):
             if name in kwds:
                 raise TypeError("duplicate value for argument %r" % (

Modified: pypy/trunk/pypy/module/test_lib_pypy/ctypes_tests/test_guess_argtypes.py
==============================================================================
--- pypy/trunk/pypy/module/test_lib_pypy/ctypes_tests/test_guess_argtypes.py	(original)
+++ pypy/trunk/pypy/module/test_lib_pypy/ctypes_tests/test_guess_argtypes.py	Thu Dec  2 00:45:35 2010
@@ -11,21 +11,23 @@
         py.test.skip("pypy white-box test")
     from _ctypes.function import CFuncPtr
 
-    guess = CFuncPtr._guess_argtypes
+    def guess(value):
+        cobj = CFuncPtr._conv_param(None, value, 0)
+        return type(cobj)
 
-    assert guess([13]) == [c_int]
-    assert guess([0]) == [c_int]
-    assert guess(['xca']) == [c_char_p]
-    assert guess([None]) == [c_void_p]
-    assert guess([c_int(3)]) == [c_int]
-    assert guess([u'xca']) == [c_wchar_p]
+    assert guess(13) == c_int
+    assert guess(0) == c_int
+    assert guess('xca') == c_char_p
+    assert guess(None) == c_void_p
+    assert guess(c_int(3)) == c_int
+    assert guess(u'xca') == c_wchar_p
 
     class Stuff:
         pass
     s = Stuff()
     s._as_parameter_ = None
     
-    assert guess([s]) == [c_void_p]
+    assert guess(s) == c_void_p
 
 def test_guess_unicode():
     if not hasattr(sys, 'pypy_translation_info') and sys.platform != 'win32':

Modified: pypy/trunk/pypy/module/test_lib_pypy/ctypes_tests/test_keepalive.py
==============================================================================
--- pypy/trunk/pypy/module/test_lib_pypy/ctypes_tests/test_keepalive.py	(original)
+++ pypy/trunk/pypy/module/test_lib_pypy/ctypes_tests/test_keepalive.py	Thu Dec  2 00:45:35 2010
@@ -99,7 +99,7 @@
     def test_primitive(self):
         if not hasattr(sys, 'pypy_translation_info'):
             py.test.skip("pypy white-box test")
-        assert c_char_p("abc")._objects['0']._buffer[0] == "a"
+        assert c_char_p("abc")._objects._buffer[0] == "a"
         assert c_int(3)._objects is None
 
     def test_pointer_to_pointer(self):
@@ -123,7 +123,7 @@
             pass
         cf = CFUNCTYPE(c_int, c_int)(f)
         p1 = cast(cf, c_void_p)
-        assert p1._objects == {'1': cf, '0': {'0': cf}}
+        assert p1._objects == {id(cf): cf, '0': cf}
 
     def test_array_of_struct_with_pointer(self):
         class S(Structure):
@@ -221,7 +221,7 @@
         import gc; gc.collect()
         print 'x =', repr(x)
         assert x.value == 'hellohello'
-        assert x._objects.keys() == ['0']
+        assert x._objects == 'hellohello'
         #
         class datum(Structure):
             _fields_ = [



More information about the Pypy-commit mailing list