[pypy-svn] r61769 - in pypy/trunk/pypy: lib/app_test/ctypes_tests module/_rawffi module/_rawffi/test rlib

afa at codespeak.net afa at codespeak.net
Thu Feb 12 10:10:58 CET 2009


Author: afa
Date: Thu Feb 12 10:10:58 2009
New Revision: 61769

Added:
   pypy/trunk/pypy/lib/app_test/ctypes_tests/test_win32.py
Modified:
   pypy/trunk/pypy/module/_rawffi/interp_rawffi.py
   pypy/trunk/pypy/module/_rawffi/test/test__rawffi.py
   pypy/trunk/pypy/rlib/libffi.py
Log:
On Windows, when using the STDCALL calling convention, the libffi
library (at least, the version shipped with pypy and cpython) has the
nice property to detect mismatches between the passed arguments and
the ones expected by the function.  This is possible because with
STDCALL, the callee is responsible for cleaning the stack.

Also adapt and add a win32-specific ctypes test file.
(there are 5 more missing files in cpython test suite)


Added: pypy/trunk/pypy/lib/app_test/ctypes_tests/test_win32.py
==============================================================================
--- (empty file)
+++ pypy/trunk/pypy/lib/app_test/ctypes_tests/test_win32.py	Thu Feb 12 10:10:58 2009
@@ -0,0 +1,73 @@
+# Windows specific tests
+
+from ctypes import *
+from ctypes.test import is_resource_enabled
+from support import BaseCTypesTestChecker
+
+import py
+import sys
+
+if sys.platform != "win32":
+    py.test.skip("win32-only tests")
+
+class TestWindows(BaseCTypesTestChecker):
+    def test_callconv_1(self):
+        # Testing stdcall function
+
+        IsWindow = windll.user32.IsWindow
+        # ValueError: Procedure probably called with not enough arguments (4 bytes missing)
+        py.test.raises(ValueError, IsWindow)
+
+        # This one should succeeed...
+        assert IsWindow(0) == 0
+
+        # ValueError: Procedure probably called with too many arguments (8 bytes in excess)
+        py.test.raises(ValueError, IsWindow, 0, 0, 0)
+
+    def test_callconv_2(self):
+        # Calling stdcall function as cdecl
+
+        IsWindow = cdll.user32.IsWindow
+
+        # ValueError: Procedure called with not enough arguments (4 bytes missing)
+        # or wrong calling convention
+        py.test.raises(ValueError, IsWindow, None)
+
+    if is_resource_enabled("SEH"):
+        def test_SEH(self):
+            # Call functions with invalid arguments, and make sure that access violations
+            # are trapped and raise an exception.
+            py.test.raises(WindowsError, windll.kernel32.GetModuleHandleA, 32)
+
+class TestWintypes(BaseCTypesTestChecker):
+
+    def test_COMError(self):
+        import _ctypes
+        from _ctypes import COMError
+        assert COMError.__doc__ == "Raised when a COM method call failed."
+
+        ex = COMError(-1, "text", ("details",))
+        assert ex.hresult == -1
+        assert ex.text == "text"
+        assert ex.details == ("details",)
+        assert (ex.hresult, ex.text, ex.details) == ex[:]
+
+class TestStructures(BaseCTypesTestChecker):
+
+    def test_struct_by_value(self):
+        class POINT(Structure):
+            _fields_ = [("x", c_long),
+                        ("y", c_long)]
+
+        class RECT(Structure):
+            _fields_ = [("left", c_long),
+                        ("top", c_long),
+                        ("right", c_long),
+                        ("bottom", c_long)]
+
+        import conftest
+        dll = CDLL(str(conftest.sofile))
+
+        pt = POINT(10, 10)
+        rect = RECT(0, 0, 20, 20)
+        assert dll.PointInRect(byref(rect), pt) == 1

Modified: pypy/trunk/pypy/module/_rawffi/interp_rawffi.py
==============================================================================
--- pypy/trunk/pypy/module/_rawffi/interp_rawffi.py	(original)
+++ pypy/trunk/pypy/module/_rawffi/interp_rawffi.py	Thu Feb 12 10:10:58 2009
@@ -386,7 +386,7 @@
         from pypy.module._rawffi.structure import W_StructureInstance
         argnum = len(args_w)
         if argnum != len(self.argletters):
-            msg = "Wrong number of argument: expected %d, got %d" % (
+            msg = "Wrong number of arguments: expected %d, got %d" % (
                 len(self.argletters), argnum)
             raise OperationError(space.w_TypeError, space.wrap(msg))
         args_ll = []
@@ -419,13 +419,17 @@
                         raise OperationError(space.w_TypeError, space.wrap(msg))
             args_ll.append(arg.ll_buffer)
             # XXX we could avoid the intermediate list args_ll
-        if self.resshape is not None:
-            result = self.resshape.allocate(space, 1, autofree=True)
-            self.ptr.call(args_ll, result.ll_buffer)
-            return space.wrap(result)
-        else:
-            self.ptr.call(args_ll, lltype.nullptr(rffi.VOIDP.TO))
-            return space.w_None
+
+        try:
+            if self.resshape is not None:
+                result = self.resshape.allocate(space, 1, autofree=True)
+                self.ptr.call(args_ll, result.ll_buffer)
+                return space.wrap(result)
+            else:
+                self.ptr.call(args_ll, lltype.nullptr(rffi.VOIDP.TO))
+                return space.w_None
+        except StackCheckError, e:
+            raise OperationError(space.w_ValueError, space.wrap(e.message))
     call.unwrap_spec = ['self', ObjSpace, 'args_w']
 
 def descr_new_funcptr(space, w_tp, addr, w_args, w_res, flags=FUNCFLAG_CDECL):

Modified: pypy/trunk/pypy/module/_rawffi/test/test__rawffi.py
==============================================================================
--- pypy/trunk/pypy/module/_rawffi/test/test__rawffi.py	(original)
+++ pypy/trunk/pypy/module/_rawffi/test/test__rawffi.py	Thu Feb 12 10:10:58 2009
@@ -752,8 +752,35 @@
         raises(_rawffi.SegfaultException, a.__getitem__, 3)
         raises(_rawffi.SegfaultException, a.__setitem__, 3, 3)
 
-    def test_struct_byvalue(self):
+    def test_stackcheck(self):
+        if not self.iswin32:
+            skip("win32 specific")
+
+        # Even if the call corresponds to the specified signature,
+        # the STDCALL calling convention may detect some errors
         import _rawffi
+        lib = _rawffi.CDLL('kernel32')
+
+        f = lib.ptr('SetLastError', [], 'i')
+        try:
+            f()
+        except ValueError, e:
+            assert "Procedure called with not enough arguments" in e.message
+        else:
+            assert 0, "Did not raise"
+
+        f = lib.ptr('GetLastError', ['i'], None,
+                    flags=_rawffi.FUNCFLAG_STDCALL)
+        arg = _rawffi.Array('i')(1)
+        arg[0] = 1
+        try:
+            f(arg)
+        except ValueError, e:
+            assert "Procedure called with too many arguments" in e.message
+        else:
+            assert 0, "Did not raise"
+
+    def test_struct_byvalue(self):
         if self.isx86_64:
             skip("Segfaults on x86_64 because small structures "
                  "may be passed in registers and "

Modified: pypy/trunk/pypy/rlib/libffi.py
==============================================================================
--- pypy/trunk/pypy/rlib/libffi.py	(original)
+++ pypy/trunk/pypy/rlib/libffi.py	Thu Feb 12 10:10:58 2009
@@ -251,6 +251,9 @@
     def dlsym_byordinal(handle, index):
         # Never called
         raise KeyError(index)
+
+    def check_fficall_result(result, flags):
+        pass # No check
     
     libc_name = ctypes.util.find_library('c')
 
@@ -285,6 +288,27 @@
         # XXX rffi.cast here...
         return res
     
+    def check_fficall_result(result, flags):
+        if result == 0:
+            return
+        # if win64:
+        #     raises ValueError("ffi_call failed with code %d" % (result,))
+        if result < 0:
+            if flags & FUNCFLAG_CDECL:
+                raise StackCheckError(
+                    "Procedure called with not enough arguments"
+                    " (%d bytes missing)"
+                    " or wrong calling convention" % (-result,))
+            else:
+                raise StackCheckError(
+                    "Procedure called with not enough arguments "
+                    " (%d bytes missing) " % (-result,))
+        else:
+            raise StackCheckError(
+                "Procedure called with too many "
+                "arguments (%d bytes in excess) " % (result,))
+
+    
     FormatError = rwin32.FormatError
     LoadLibrary = rwin32.LoadLibrary
 
@@ -309,8 +333,12 @@
 
 c_ffi_prep_cif = external('ffi_prep_cif', [FFI_CIFP, rffi.USHORT, rffi.UINT,
                                            FFI_TYPE_P, FFI_TYPE_PP], rffi.INT)
+if _WIN32:
+    c_ffi_call_return_type = rffi.INT
+else:
+    c_ffi_call_return_type = lltype.Void
 c_ffi_call = external('ffi_call', [FFI_CIFP, rffi.VOIDP, rffi.VOIDP,
-                                   VOIDPP], lltype.Void)
+                                   VOIDPP], c_ffi_call_return_type)
 CALLBACK_TP = rffi.CCallback([FFI_CIFP, rffi.VOIDP, rffi.VOIDPP, rffi.VOIDP],
                              lltype.Void)
 c_ffi_prep_closure = external('ffi_prep_closure', [FFI_CLOSUREP, FFI_CIFP,
@@ -362,6 +390,10 @@
     userdata = rffi.cast(USERDATA_P, ll_userdata)
     userdata.callback(ll_args, ll_res, userdata)
 
+class StackCheckError(ValueError):
+    def __init__(self, message):
+        self.message = message
+
 CHUNK = 4096
 CLOSURES = rffi.CArrayPtr(FFI_CLOSUREP.TO)
 
@@ -403,6 +435,7 @@
         self.name = name
         self.argtypes = argtypes
         self.restype = restype
+        self.flags = flags
         argnum = len(argtypes)
         self.ll_argtypes = lltype.malloc(FFI_TYPE_PP.TO, argnum, flavor='raw')
         for i in range(argnum):
@@ -481,8 +514,9 @@
         for i in range(len(args_ll)):
             assert args_ll[i] # none should be NULL
             ll_args[i] = args_ll[i]
-        c_ffi_call(self.ll_cif, self.funcsym, ll_result, ll_args)
+        ffires = c_ffi_call(self.ll_cif, self.funcsym, ll_result, ll_args)
         lltype.free(ll_args, flavor='raw')
+        check_fficall_result(ffires, self.flags)
 
 
 class FuncPtr(AbstractFuncPtr):
@@ -508,7 +542,7 @@
 
     def push_arg(self, value):
         if self.pushed_args == self.argnum:
-            raise TypeError("Too much arguments, eats %d, pushed %d" %
+            raise TypeError("Too many arguments, eats %d, pushed %d" %
                             (self.argnum, self.argnum + 1))
         if not we_are_translated():
             TP = lltype.typeOf(value)
@@ -536,15 +570,16 @@
 
     def call(self, RES_TP):
         self._check_args()
-        c_ffi_call(self.ll_cif, self.funcsym,
-                   rffi.cast(rffi.VOIDP, self.ll_result),
-                   rffi.cast(VOIDPP, self.ll_args))
+        ffires = c_ffi_call(self.ll_cif, self.funcsym,
+                            rffi.cast(rffi.VOIDP, self.ll_result),
+                            rffi.cast(VOIDPP, self.ll_args))
         if RES_TP is not lltype.Void:
             TP = lltype.Ptr(rffi.CArray(RES_TP))
             res = rffi.cast(TP, self.ll_result)[0]
         else:
             res = None
         self._clean_args()
+        check_fficall_result(ffires, self.flags)
         return res
     call._annspecialcase_ = 'specialize:arg(1)'
 



More information about the Pypy-commit mailing list