[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