[pypy-commit] pypy dtypes-compatability: merge default into branch
mattip
noreply at buildbot.pypy.org
Fri Jul 10 09:10:23 CEST 2015
Author: mattip <matti.picus at gmail.com>
Branch: dtypes-compatability
Changeset: r78523:29b32820d583
Date: 2015-07-10 10:04 +0300
http://bitbucket.org/pypy/pypy/changeset/29b32820d583/
Log: merge default into branch
diff too long, truncating to 2000 out of 6164 lines
diff --git a/lib-python/2.7/test/test_urllib2.py b/lib-python/2.7/test/test_urllib2.py
--- a/lib-python/2.7/test/test_urllib2.py
+++ b/lib-python/2.7/test/test_urllib2.py
@@ -291,6 +291,7 @@
self.req_headers = []
self.data = None
self.raise_on_endheaders = False
+ self.sock = None
self._tunnel_headers = {}
def __call__(self, host, timeout=socket._GLOBAL_DEFAULT_TIMEOUT):
diff --git a/lib-python/2.7/urllib2.py b/lib-python/2.7/urllib2.py
--- a/lib-python/2.7/urllib2.py
+++ b/lib-python/2.7/urllib2.py
@@ -1200,6 +1200,12 @@
r = h.getresponse(buffering=True)
except TypeError: # buffering kw not supported
r = h.getresponse()
+ # If the server does not send us a 'Connection: close' header,
+ # HTTPConnection assumes the socket should be left open. Manually
+ # mark the socket to be closed when this response object goes away.
+ if h.sock:
+ h.sock.close()
+ h.sock = None
# Pick apart the HTTPResponse object to get the addinfourl
# object initialized properly.
diff --git a/lib_pypy/_tkinter/tclobj.py b/lib_pypy/_tkinter/tclobj.py
--- a/lib_pypy/_tkinter/tclobj.py
+++ b/lib_pypy/_tkinter/tclobj.py
@@ -108,6 +108,8 @@
return value.internalRep.doubleValue
if value.typePtr == typeCache.IntType:
return value.internalRep.longValue
+ if value.typePtr == typeCache.WideIntType:
+ return FromWideIntObj(app, value)
if value.typePtr == typeCache.BigNumType and tklib.HAVE_LIBTOMMATH:
return FromBignumObj(app, value)
if value.typePtr == typeCache.ListType:
diff --git a/lib_pypy/_tkinter/tklib_build.py b/lib_pypy/_tkinter/tklib_build.py
--- a/lib_pypy/_tkinter/tklib_build.py
+++ b/lib_pypy/_tkinter/tklib_build.py
@@ -179,6 +179,7 @@
typedef int... Tcl_WideInt;
int Tcl_GetWideIntFromObj(Tcl_Interp *interp, Tcl_Obj *obj, Tcl_WideInt *value);
+Tcl_Obj *Tcl_NewWideIntObj(Tcl_WideInt value);
""")
if HAVE_LIBTOMMATH:
diff --git a/lib_pypy/cffi.egg-info/PKG-INFO b/lib_pypy/cffi.egg-info/PKG-INFO
--- a/lib_pypy/cffi.egg-info/PKG-INFO
+++ b/lib_pypy/cffi.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
Metadata-Version: 1.1
Name: cffi
-Version: 1.1.2
+Version: 1.2.0
Summary: Foreign Function Interface for Python calling C code.
Home-page: http://cffi.readthedocs.org
Author: Armin Rigo, Maciej Fijalkowski
diff --git a/lib_pypy/cffi/__init__.py b/lib_pypy/cffi/__init__.py
--- a/lib_pypy/cffi/__init__.py
+++ b/lib_pypy/cffi/__init__.py
@@ -4,8 +4,8 @@
from .api import FFI, CDefError, FFIError
from .ffiplatform import VerificationError, VerificationMissing
-__version__ = "1.1.2"
-__version_info__ = (1, 1, 2)
+__version__ = "1.2.0"
+__version_info__ = (1, 2, 0)
# The verifier module file names are based on the CRC32 of a string that
# contains the following version number. It may be older than __version__
diff --git a/lib_pypy/cffi/api.py b/lib_pypy/cffi/api.py
--- a/lib_pypy/cffi/api.py
+++ b/lib_pypy/cffi/api.py
@@ -236,6 +236,30 @@
cdecl = self._typeof(cdecl)
return self._backend.newp(cdecl, init)
+ def new_allocator(self, alloc=None, free=None,
+ should_clear_after_alloc=True):
+ """Return a new allocator, i.e. a function that behaves like ffi.new()
+ but uses the provided low-level 'alloc' and 'free' functions.
+
+ 'alloc' is called with the size as argument. If it returns NULL, a
+ MemoryError is raised. 'free' is called with the result of 'alloc'
+ as argument. Both can be either Python function or directly C
+ functions. If 'free' is None, then no free function is called.
+ If both 'alloc' and 'free' are None, the default is used.
+
+ If 'should_clear_after_alloc' is set to False, then the memory
+ returned by 'alloc' is assumed to be already cleared (or you are
+ fine with garbage); otherwise CFFI will clear it.
+ """
+ compiled_ffi = self._backend.FFI()
+ allocator = compiled_ffi.new_allocator(alloc, free,
+ should_clear_after_alloc)
+ def allocate(cdecl, init=None):
+ if isinstance(cdecl, basestring):
+ cdecl = self._typeof(cdecl)
+ return allocator(cdecl, init)
+ return allocate
+
def cast(self, cdecl, source):
"""Similar to a C cast: returns an instance of the named C
type initialized with the given 'source'. The source is
@@ -286,7 +310,7 @@
"""
return self._backend.from_buffer(self.BCharA, python_buffer)
- def callback(self, cdecl, python_callable=None, error=None):
+ def callback(self, cdecl, python_callable=None, error=None, onerror=None):
"""Return a callback object or a decorator making such a
callback object. 'cdecl' must name a C function pointer type.
The callback invokes the specified 'python_callable' (which may
@@ -298,7 +322,8 @@
if not callable(python_callable):
raise TypeError("the 'python_callable' argument "
"is not callable")
- return self._backend.callback(cdecl, python_callable, error)
+ return self._backend.callback(cdecl, python_callable,
+ error, onerror)
if isinstance(cdecl, basestring):
cdecl = self._typeof(cdecl, consider_function_as_funcptr=True)
if python_callable is None:
@@ -327,6 +352,13 @@
data. Later, when this new cdata object is garbage-collected,
'destructor(old_cdata_object)' will be called.
"""
+ try:
+ gcp = self._backend.gcp
+ except AttributeError:
+ pass
+ else:
+ return gcp(cdata, destructor)
+ #
with self._lock:
try:
gc_weakrefs = self.gc_weakrefs
@@ -428,6 +460,8 @@
raise TypeError("ffi.include() expects an argument that is also of"
" type cffi.FFI, not %r" % (
type(ffi_to_include).__name__,))
+ if ffi_to_include is self:
+ raise ValueError("self.include(self)")
with ffi_to_include._lock:
with self._lock:
self._parser.include(ffi_to_include._parser)
diff --git a/lib_pypy/cffi/backend_ctypes.py b/lib_pypy/cffi/backend_ctypes.py
--- a/lib_pypy/cffi/backend_ctypes.py
+++ b/lib_pypy/cffi/backend_ctypes.py
@@ -989,7 +989,8 @@
def cast(self, BType, source):
return BType._cast_from(source)
- def callback(self, BType, source, error):
+ def callback(self, BType, source, error, onerror):
+ assert onerror is None # XXX not implemented
return BType(source, error)
typeof = type
diff --git a/lib_pypy/cffi/cffi_opcode.py b/lib_pypy/cffi/cffi_opcode.py
--- a/lib_pypy/cffi/cffi_opcode.py
+++ b/lib_pypy/cffi/cffi_opcode.py
@@ -53,6 +53,7 @@
OP_GLOBAL_VAR = 33
OP_DLOPEN_FUNC = 35
OP_DLOPEN_CONST = 37
+OP_GLOBAL_VAR_F = 39
PRIM_VOID = 0
PRIM_BOOL = 1
diff --git a/lib_pypy/cffi/cparser.py b/lib_pypy/cffi/cparser.py
--- a/lib_pypy/cffi/cparser.py
+++ b/lib_pypy/cffi/cparser.py
@@ -633,6 +633,8 @@
def include(self, other):
for name, tp in other._declarations.items():
+ if name.startswith('anonymous $enum_$'):
+ continue # fix for test_anonymous_enum_include
kind = name.split(' ', 1)[0]
if kind in ('struct', 'union', 'enum', 'anonymous'):
self._declare(name, tp, included=True)
diff --git a/lib_pypy/cffi/model.py b/lib_pypy/cffi/model.py
--- a/lib_pypy/cffi/model.py
+++ b/lib_pypy/cffi/model.py
@@ -35,9 +35,6 @@
def is_integer_type(self):
return False
- def sizeof_enabled(self):
- return False
-
def get_cached_btype(self, ffi, finishlist, can_delay=False):
try:
BType = ffi._cached_btypes[self]
@@ -80,8 +77,7 @@
class BasePrimitiveType(BaseType):
- def sizeof_enabled(self):
- return True
+ pass
class PrimitiveType(BasePrimitiveType):
@@ -205,9 +201,6 @@
class FunctionPtrType(BaseFunctionType):
_base_pattern = '(*&)(%s)'
- def sizeof_enabled(self):
- return True
-
def build_backend_type(self, ffi, finishlist):
result = self.result.get_cached_btype(ffi, finishlist)
args = []
@@ -233,9 +226,6 @@
extra = self._base_pattern
self.c_name_with_marker = totype.c_name_with_marker.replace('&', extra)
- def sizeof_enabled(self):
- return True
-
def build_backend_type(self, ffi, finishlist):
BItem = self.totype.get_cached_btype(ffi, finishlist, can_delay=True)
return global_cache(self, ffi, 'new_pointer_type', BItem)
@@ -276,9 +266,6 @@
self.c_name_with_marker = (
self.item.c_name_with_marker.replace('&', brackets))
- def sizeof_enabled(self):
- return self.item.sizeof_enabled() and self.length is not None
-
def resolve_length(self, newlength):
return ArrayType(self.item, newlength)
@@ -433,9 +420,6 @@
from . import ffiplatform
raise ffiplatform.VerificationMissing(self._get_c_name())
- def sizeof_enabled(self):
- return self.fldtypes is not None
-
def build_backend_type(self, ffi, finishlist):
self.check_not_partial()
finishlist.append(self)
@@ -464,9 +448,6 @@
self.baseinttype = baseinttype
self.build_c_name_with_marker()
- def sizeof_enabled(self):
- return True # not strictly true, but external enums are obscure
-
def force_the_name(self, forcename):
StructOrUnionOrEnum.force_the_name(self, forcename)
if self.forcename is None:
diff --git a/lib_pypy/cffi/parse_c_type.h b/lib_pypy/cffi/parse_c_type.h
--- a/lib_pypy/cffi/parse_c_type.h
+++ b/lib_pypy/cffi/parse_c_type.h
@@ -26,6 +26,7 @@
#define _CFFI_OP_GLOBAL_VAR 33
#define _CFFI_OP_DLOPEN_FUNC 35
#define _CFFI_OP_DLOPEN_CONST 37
+#define _CFFI_OP_GLOBAL_VAR_F 39
#define _CFFI_PRIM_VOID 0
#define _CFFI_PRIM_BOOL 1
diff --git a/lib_pypy/cffi/recompiler.py b/lib_pypy/cffi/recompiler.py
--- a/lib_pypy/cffi/recompiler.py
+++ b/lib_pypy/cffi/recompiler.py
@@ -981,10 +981,6 @@
if not self.target_is_python and tp.is_integer_type():
type_op = CffiOp(OP_CONSTANT_INT, -1)
else:
- if not tp.sizeof_enabled():
- raise ffiplatform.VerificationError(
- "constant '%s' is of type '%s', whose size is not known"
- % (name, tp._get_c_name()))
if self.target_is_python:
const_kind = OP_DLOPEN_CONST
else:
@@ -1069,18 +1065,36 @@
self._do_collect_type(self._global_type(tp, name))
def _generate_cpy_variable_decl(self, tp, name):
- pass
+ prnt = self._prnt
+ tp = self._global_type(tp, name)
+ if isinstance(tp, model.ArrayType) and tp.length is None:
+ tp = tp.item
+ ampersand = ''
+ else:
+ ampersand = '&'
+ # This code assumes that casts from "tp *" to "void *" is a
+ # no-op, i.e. a function that returns a "tp *" can be called
+ # as if it returned a "void *". This should be generally true
+ # on any modern machine. The only exception to that rule (on
+ # uncommon architectures, and as far as I can tell) might be
+ # if 'tp' were a function type, but that is not possible here.
+ # (If 'tp' is a function _pointer_ type, then casts from "fn_t
+ # **" to "void *" are again no-ops, as far as I can tell.)
+ prnt('static ' + tp.get_c_name('*_cffi_var_%s(void)' % (name,)))
+ prnt('{')
+ prnt(' return %s(%s);' % (ampersand, name))
+ prnt('}')
+ prnt()
def _generate_cpy_variable_ctx(self, tp, name):
tp = self._global_type(tp, name)
type_index = self._typesdict[tp]
- type_op = CffiOp(OP_GLOBAL_VAR, type_index)
- if tp.sizeof_enabled():
- size = "sizeof(%s)" % (name,)
+ if self.target_is_python:
+ op = OP_GLOBAL_VAR
else:
- size = 0
+ op = OP_GLOBAL_VAR_F
self._lsts["global"].append(
- GlobalExpr(name, '&%s' % name, type_op, size))
+ GlobalExpr(name, '_cffi_var_%s' % name, CffiOp(op, type_index)))
# ----------
# emitting the opcodes for individual types
diff --git a/pypy/config/pypyoption.py b/pypy/config/pypyoption.py
--- a/pypy/config/pypyoption.py
+++ b/pypy/config/pypyoption.py
@@ -23,14 +23,14 @@
default_modules.update([
"_codecs", "gc", "_weakref", "marshal", "errno", "imp", "math", "cmath",
"_sre", "_pickle_support", "operator", "parser", "symbol", "token", "_ast",
- "_io", "_random", "__pypy__", "_testing"
+ "_io", "_random", "__pypy__", "_testing", "time"
])
# --allworkingmodules
working_modules = default_modules.copy()
working_modules.update([
- "_socket", "unicodedata", "mmap", "fcntl", "_locale", "pwd", "time" ,
+ "_socket", "unicodedata", "mmap", "fcntl", "_locale", "pwd",
"select", "zipimport", "_lsprof", "crypt", "signal", "_rawffi", "termios",
"zlib", "bz2", "struct", "_hashlib", "_md5", "_sha", "_minimal_curses",
"cStringIO", "thread", "itertools", "pyexpat", "_ssl", "cpyext", "array",
diff --git a/pypy/doc/cpython_differences.rst b/pypy/doc/cpython_differences.rst
--- a/pypy/doc/cpython_differences.rst
+++ b/pypy/doc/cpython_differences.rst
@@ -135,7 +135,7 @@
Here are some more technical details. This issue affects the precise
time at which ``__del__`` methods are called, which
is not reliable in PyPy (nor Jython nor IronPython). It also means that
-weak references may stay alive for a bit longer than expected. This
+**weak references** may stay alive for a bit longer than expected. This
makes "weak proxies" (as returned by ``weakref.proxy()``) somewhat less
useful: they will appear to stay alive for a bit longer in PyPy, and
suddenly they will really be dead, raising a ``ReferenceError`` on the
@@ -143,6 +143,24 @@
``ReferenceError`` at any place that uses them. (Or, better yet, don't use
``weakref.proxy()`` at all; use ``weakref.ref()``.)
+Note a detail in the `documentation for weakref callbacks`__:
+
+ If callback is provided and not None, *and the returned weakref
+ object is still alive,* the callback will be called when the object
+ is about to be finalized.
+
+There are cases where, due to CPython's refcount semantics, a weakref
+dies immediately before or after the objects it points to (typically
+with some circular reference). If it happens to die just after, then
+the callback will be invoked. In a similar case in PyPy, both the
+object and the weakref will be considered as dead at the same time,
+and the callback will not be invoked. (Issue `#2030`__)
+
+.. __: https://docs.python.org/2/library/weakref.html
+.. __: https://bitbucket.org/pypy/pypy/issue/2030/
+
+---------------------------------
+
There are a few extra implications from the difference in the GC. Most
notably, if an object has a ``__del__``, the ``__del__`` is never called more
than once in PyPy; but CPython will call the same ``__del__`` several times
@@ -321,9 +339,8 @@
Miscellaneous
-------------
-* Hash randomization (``-R``) is ignored in PyPy. As documented in
- http://bugs.python.org/issue14621, some of us believe it has no
- purpose in CPython either.
+* Hash randomization (``-R``) `is ignored in PyPy`_. In CPython
+ before 3.4 it has `little point`_.
* You can't store non-string keys in type objects. For example::
@@ -338,7 +355,8 @@
for about 1400 calls.
* since the implementation of dictionary is different, the exact number
- which ``__hash__`` and ``__eq__`` are called is different. Since CPython
+ of times that ``__hash__`` and ``__eq__`` are called is different.
+ Since CPython
does not give any specific guarantees either, don't rely on it.
* assignment to ``__class__`` is limited to the cases where it
@@ -395,3 +413,12 @@
interactive mode. In a released version, this behaviour is suppressed, but
setting the environment variable PYPY_IRC_TOPIC will bring it back. Note that
downstream package providers have been known to totally disable this feature.
+
+* PyPy's readline module was rewritten from scratch: it is not GNU's
+ readline. It should be mostly compatible, and it adds multiline
+ support (see ``multiline_input()``). On the other hand,
+ ``parse_and_bind()`` calls are ignored (issue `#2072`_).
+
+.. _`is ignored in PyPy`: http://bugs.python.org/issue14621
+.. _`little point`: http://events.ccc.de/congress/2012/Fahrplan/events/5152.en.html
+.. _`#2072`: https://bitbucket.org/pypy/pypy/issue/2072/
diff --git a/pypy/doc/embedding.rst b/pypy/doc/embedding.rst
--- a/pypy/doc/embedding.rst
+++ b/pypy/doc/embedding.rst
@@ -6,15 +6,9 @@
C. It was developed in collaboration with Roberto De Ioris from the `uwsgi`_
project. The `PyPy uwsgi plugin`_ is a good example of using the embedding API.
-**NOTE**: As of 1st of December, PyPy comes with ``--shared`` by default
-on linux, linux64 and windows. We will make it the default on all platforms
-by the time of the next release.
-
-The first thing that you need is to compile PyPy yourself with the option
-``--shared``. We plan to make ``--shared`` the default in the future. Consult
-the `how to compile PyPy`_ doc for details. This will result in ``libpypy.so``
-or ``pypy.dll`` file or something similar, depending on your platform. Consult
-your platform specification for details.
+**NOTE**: You need a PyPy compiled with the option ``--shared``, i.e.
+with a ``libpypy-c.so`` or ``pypy-c.dll`` file. This is the default in
+recent versions of PyPy.
The resulting shared library exports very few functions, however they are
enough to accomplish everything you need, provided you follow a few principles.
diff --git a/pypy/doc/faq.rst b/pypy/doc/faq.rst
--- a/pypy/doc/faq.rst
+++ b/pypy/doc/faq.rst
@@ -70,6 +70,20 @@
.. _`use virtualenv (as documented here)`: getting-started.html#installing-using-virtualenv
+Module xyz does not work in the sandboxed PyPy?
+-----------------------------------------------
+
+You cannot import *any* extension module in a `sandboxed PyPy`_,
+sorry. Even the built-in modules available are very limited.
+Sandboxing in PyPy is a good proof of concept, really safe IMHO, but
+it is only a proof of concept. It seriously requires someone working
+on it. Before this occurs, it can only be used it for "pure Python"
+examples: programs that import mostly nothing (or only pure Python
+modules, recursively).
+
+.. _`sandboxed PyPy`: sandbox.html
+
+
.. _`See below.`:
Do CPython Extension modules work with PyPy?
diff --git a/pypy/doc/sandbox.rst b/pypy/doc/sandbox.rst
--- a/pypy/doc/sandbox.rst
+++ b/pypy/doc/sandbox.rst
@@ -103,12 +103,15 @@
Howto
-----
-In pypy/goal::
+Grab a copy of the pypy repository_. In the directory pypy/goal, run::
../../rpython/bin/rpython -O2 --sandbox targetpypystandalone.py
If you don't have a regular PyPy installed, you should, because it's
-faster to translate, but you can also run ``python translate.py`` instead.
+faster to translate; but you can also run the same line with ``python``
+in front.
+
+.. _repository: https://bitbucket.org/pypy/pypy
To run it, use the tools in the pypy/sandbox directory::
@@ -136,8 +139,6 @@
Not all operations are supported; e.g. if you type os.readlink('...'),
the controller crashes with an exception and the subprocess is killed.
Other operations make the subprocess die directly with a "Fatal RPython
-error". None of this is a security hole; it just means that if you try
-to run some random program, it risks getting killed depending on the
-Python built-in functions it tries to call. This is a matter of the
-sandboxing layer being incomplete so far, but it should not really be
-a problem in practice.
+error". None of this is a security hole. More importantly, *most other
+built-in modules are not enabled. Please read all the warnings in this
+page before complaining about this. Contributions welcome.*
diff --git a/pypy/doc/whatsnew-head.rst b/pypy/doc/whatsnew-head.rst
--- a/pypy/doc/whatsnew-head.rst
+++ b/pypy/doc/whatsnew-head.rst
@@ -11,3 +11,29 @@
.. branch: stdlib-2.7.10
Update stdlib to version 2.7.10
+
+.. branch: issue2062
+
+.. branch: disable-unroll-for-short-loops
+The JIT no longer performs loop unrolling if the loop compiles to too much code.
+
+.. branch: run-create_cffi_imports
+
+Build cffi import libraries as part of translation by monkey-patching an
+additional task into translation
+
+.. branch: int-float-list-strategy
+
+Use a compact strategy for Python lists that mix integers and floats,
+at least if the integers fit inside 32 bits. These lists are now
+stored as an array of floats, like lists that contain only floats; the
+difference is that integers are stored as tagged NaNs. (This should
+have no visible effect! After ``lst = [42, 42.5]``, the value of
+``lst[0]`` is still *not* the float ``42.0`` but the integer ``42``.)
+
+.. branch: cffi-callback-onerror
+.. branch: cffi-new-allocator
+
+.. branch: unicode-dtype
+
+Partial implementation of unicode dtype and unicode scalars.
diff --git a/pypy/goal/targetpypystandalone.py b/pypy/goal/targetpypystandalone.py
--- a/pypy/goal/targetpypystandalone.py
+++ b/pypy/goal/targetpypystandalone.py
@@ -1,6 +1,6 @@
import py
-import os, sys
+import os, sys, subprocess
import pypy
from pypy.interpreter import gateway
@@ -297,8 +297,49 @@
options = make_dict(config)
wrapstr = 'space.wrap(%r)' % (options)
pypy.module.sys.Module.interpleveldefs['pypy_translation_info'] = wrapstr
+ if config.objspace.usemodules._cffi_backend:
+ self.hack_for_cffi_modules(driver)
return self.get_entry_point(config)
+
+ def hack_for_cffi_modules(self, driver):
+ # HACKHACKHACK
+ # ugly hack to modify target goal from compile_c to build_cffi_imports
+ # this should probably get cleaned up and merged with driver.create_exe
+ from rpython.translator.driver import taskdef
+ import types
+
+ class Options(object):
+ pass
+
+
+ def mkexename(name):
+ if sys.platform == 'win32':
+ name = name.new(ext='exe')
+ return name
+
+ @taskdef(['compile_c'], "Create cffi bindings for modules")
+ def task_build_cffi_imports(self):
+ from pypy.tool.build_cffi_imports import create_cffi_import_libraries
+ ''' Use cffi to compile cffi interfaces to modules'''
+ exename = mkexename(driver.compute_exe_name())
+ basedir = exename
+ while not basedir.join('include').exists():
+ _basedir = basedir.dirpath()
+ if _basedir == basedir:
+ raise ValueError('interpreter %s not inside pypy repo',
+ str(exename))
+ basedir = _basedir
+ modules = self.config.objspace.usemodules.getpaths()
+ options = Options()
+ # XXX possibly adapt options using modules
+ failures = create_cffi_import_libraries(exename, options, basedir)
+ # if failures, they were already printed
+ print >> sys.stderr, str(exename),'successfully built, but errors while building the above modules will be ignored'
+ driver.task_build_cffi_imports = types.MethodType(task_build_cffi_imports, driver)
+ driver.tasks['build_cffi_imports'] = driver.task_build_cffi_imports, ['compile_c']
+ driver.default_goal = 'build_cffi_imports'
+ # HACKHACKHACK end
def jitpolicy(self, driver):
from pypy.module.pypyjit.policy import PyPyJitPolicy
diff --git a/pypy/interpreter/error.py b/pypy/interpreter/error.py
--- a/pypy/interpreter/error.py
+++ b/pypy/interpreter/error.py
@@ -252,7 +252,8 @@
w_t, w_v, w_tb],
"""(where, objrepr, extra_line, t, v, tb):
import sys, traceback
- sys.stderr.write('From %s%s:\\n' % (where, objrepr))
+ if where or objrepr:
+ sys.stderr.write('From %s%s:\\n' % (where, objrepr))
if extra_line:
sys.stderr.write(extra_line)
traceback.print_exception(t, v, tb)
diff --git a/pypy/interpreter/generator.py b/pypy/interpreter/generator.py
--- a/pypy/interpreter/generator.py
+++ b/pypy/interpreter/generator.py
@@ -15,7 +15,10 @@
self.running = False
def descr__repr__(self, space):
- code_name = self.pycode.co_name
+ if self.pycode is None:
+ code_name = '<finished>'
+ else:
+ code_name = self.pycode.co_name
addrstring = self.getaddrstring(space)
return space.wrap("<generator object %s at 0x%s>" %
(code_name, addrstring))
@@ -45,6 +48,8 @@
w_framestate, w_running = args_w
if space.is_w(w_framestate, space.w_None):
self.frame = None
+ self.space = space
+ self.pycode = None
else:
frame = instantiate(space.FrameClass) # XXX fish
frame.descr__setstate__(space, w_framestate)
@@ -62,9 +67,10 @@
def send_ex(self, w_arg, operr=None):
pycode = self.pycode
- if jit.we_are_jitted() and should_not_inline(pycode):
- generatorentry_driver.jit_merge_point(gen=self, w_arg=w_arg,
- operr=operr, pycode=pycode)
+ if pycode is not None:
+ if jit.we_are_jitted() and should_not_inline(pycode):
+ generatorentry_driver.jit_merge_point(gen=self, w_arg=w_arg,
+ operr=operr, pycode=pycode)
return self._send_ex(w_arg, operr)
def _send_ex(self, w_arg, operr):
@@ -158,7 +164,10 @@
return self.pycode
def descr__name__(self, space):
- code_name = self.pycode.co_name
+ if self.pycode is None:
+ code_name = '<finished>'
+ else:
+ code_name = self.pycode.co_name
return space.wrap(code_name)
# Results can be either an RPython list of W_Root, or it can be an
diff --git a/pypy/interpreter/pytraceback.py b/pypy/interpreter/pytraceback.py
--- a/pypy/interpreter/pytraceback.py
+++ b/pypy/interpreter/pytraceback.py
@@ -60,7 +60,6 @@
def check_traceback(space, w_tb, msg):
- from pypy.interpreter.typedef import PyTraceback
if w_tb is None or not space.isinstance_w(w_tb, space.gettypeobject(PyTraceback.typedef)):
raise OperationError(space.w_TypeError, space.wrap(msg))
return w_tb
diff --git a/pypy/interpreter/test/test_zzpickle_and_slow.py b/pypy/interpreter/test/test_zzpickle_and_slow.py
--- a/pypy/interpreter/test/test_zzpickle_and_slow.py
+++ b/pypy/interpreter/test/test_zzpickle_and_slow.py
@@ -491,6 +491,22 @@
assert pack.mod is result
+ def test_pickle_generator_crash(self):
+ import pickle
+
+ def f():
+ yield 0
+
+ x = f()
+ x.next()
+ try:
+ x.next()
+ except StopIteration:
+ y = pickle.loads(pickle.dumps(x))
+ assert 'finished' in y.__name__
+ assert 'finished' in repr(y)
+ assert y.gi_code is None
+
class AppTestGeneratorCloning:
def setup_class(cls):
diff --git a/pypy/module/_cffi_backend/__init__.py b/pypy/module/_cffi_backend/__init__.py
--- a/pypy/module/_cffi_backend/__init__.py
+++ b/pypy/module/_cffi_backend/__init__.py
@@ -2,7 +2,7 @@
from pypy.interpreter.mixedmodule import MixedModule
from rpython.rlib import rdynload
-VERSION = "1.1.2"
+VERSION = "1.2.0"
class Module(MixedModule):
diff --git a/pypy/module/_cffi_backend/allocator.py b/pypy/module/_cffi_backend/allocator.py
new file mode 100644
--- /dev/null
+++ b/pypy/module/_cffi_backend/allocator.py
@@ -0,0 +1,86 @@
+from pypy.interpreter.error import oefmt
+from pypy.interpreter.baseobjspace import W_Root
+from pypy.interpreter.typedef import TypeDef
+from pypy.interpreter.gateway import interp2app, unwrap_spec, WrappedDefault
+
+from rpython.rtyper.lltypesystem import lltype, rffi
+
+
+class W_Allocator(W_Root):
+ _immutable_ = True
+
+ def __init__(self, ffi, w_alloc, w_free, should_clear_after_alloc):
+ self.ffi = ffi # may be None
+ self.w_alloc = w_alloc
+ self.w_free = w_free
+ self.should_clear_after_alloc = should_clear_after_alloc
+
+ def allocate(self, space, datasize, ctype, length=-1):
+ from pypy.module._cffi_backend import cdataobj, ctypeptr
+ if self.w_alloc is None:
+ if self.should_clear_after_alloc:
+ ptr = lltype.malloc(rffi.CCHARP.TO, datasize,
+ flavor='raw', zero=True)
+ else:
+ ptr = lltype.malloc(rffi.CCHARP.TO, datasize,
+ flavor='raw', zero=False)
+ return cdataobj.W_CDataNewStd(space, ptr, ctype, length)
+ else:
+ w_raw_cdata = space.call_function(self.w_alloc,
+ space.wrap(datasize))
+ if not isinstance(w_raw_cdata, cdataobj.W_CData):
+ raise oefmt(space.w_TypeError,
+ "alloc() must return a cdata object (got %T)",
+ w_raw_cdata)
+ if not isinstance(w_raw_cdata.ctype, ctypeptr.W_CTypePtrOrArray):
+ raise oefmt(space.w_TypeError,
+ "alloc() must return a cdata pointer, not '%s'",
+ w_raw_cdata.ctype.name)
+ #
+ ptr = w_raw_cdata.unsafe_escaping_ptr()
+ if not ptr:
+ raise oefmt(space.w_MemoryError, "alloc() returned NULL")
+ #
+ if self.should_clear_after_alloc:
+ rffi.c_memset(rffi.cast(rffi.VOIDP, ptr), 0,
+ rffi.cast(rffi.SIZE_T, datasize))
+ #
+ if self.w_free is None:
+ # use this class which does not have a __del__, but still
+ # keeps alive w_raw_cdata
+ res = cdataobj.W_CDataNewNonStdNoFree(space, ptr, ctype, length)
+ else:
+ res = cdataobj.W_CDataNewNonStdFree(space, ptr, ctype, length)
+ res.w_free = self.w_free
+ res.w_raw_cdata = w_raw_cdata
+ return res
+
+ @unwrap_spec(w_init=WrappedDefault(None))
+ def descr_call(self, space, w_arg, w_init):
+ ffi = self.ffi
+ assert ffi is not None
+ w_ctype = ffi.ffi_type(w_arg, ffi.ACCEPT_STRING | ffi.ACCEPT_CTYPE)
+ return w_ctype.newp(w_init, self)
+
+
+W_Allocator.typedef = TypeDef(
+ 'FFIAllocator',
+ __call__ = interp2app(W_Allocator.descr_call),
+ )
+W_Allocator.typedef.acceptable_as_base_class = False
+
+
+def new_allocator(ffi, w_alloc, w_free, should_clear_after_alloc):
+ space = ffi.space
+ if space.is_none(w_alloc):
+ w_alloc = None
+ if space.is_none(w_free):
+ w_free = None
+ if w_alloc is None and w_free is not None:
+ raise oefmt(space.w_TypeError, "cannot pass 'free' without 'alloc'")
+ alloc = W_Allocator(ffi, w_alloc, w_free, bool(should_clear_after_alloc))
+ return space.wrap(alloc)
+
+
+default_allocator = W_Allocator(None, None, None, should_clear_after_alloc=True)
+nonzero_allocator = W_Allocator(None, None, None,should_clear_after_alloc=False)
diff --git a/pypy/module/_cffi_backend/ccallback.py b/pypy/module/_cffi_backend/ccallback.py
--- a/pypy/module/_cffi_backend/ccallback.py
+++ b/pypy/module/_cffi_backend/ccallback.py
@@ -22,8 +22,9 @@
class W_CDataCallback(W_CData):
#_immutable_fields_ = ...
ll_error = lltype.nullptr(rffi.CCHARP.TO)
+ w_onerror = None
- def __init__(self, space, ctype, w_callable, w_error):
+ def __init__(self, space, ctype, w_callable, w_error, w_onerror):
raw_closure = rffi.cast(rffi.CCHARP, clibffi.closureHeap.alloc())
W_CData.__init__(self, space, raw_closure, ctype)
#
@@ -31,6 +32,12 @@
raise oefmt(space.w_TypeError,
"expected a callable object, not %T", w_callable)
self.w_callable = w_callable
+ if not space.is_none(w_onerror):
+ if not space.is_true(space.callable(w_onerror)):
+ raise oefmt(space.w_TypeError,
+ "expected a callable object for 'onerror', not %T",
+ w_onerror)
+ self.w_onerror = w_onerror
#
fresult = self.getfunctype().ctitem
size = fresult.size
@@ -161,6 +168,29 @@
STDERR = 2
+ at jit.dont_look_inside
+def _handle_applevel_exception(space, callback, e, ll_res, extra_line):
+ callback.write_error_return_value(ll_res)
+ if callback.w_onerror is None:
+ callback.print_error(e, extra_line)
+ else:
+ try:
+ e.normalize_exception(space)
+ w_t = e.w_type
+ w_v = e.get_w_value(space)
+ w_tb = space.wrap(e.get_traceback())
+ w_res = space.call_function(callback.w_onerror,
+ w_t, w_v, w_tb)
+ if not space.is_none(w_res):
+ callback.convert_result(ll_res, w_res)
+ except OperationError, e2:
+ # double exception! print a double-traceback...
+ callback.print_error(e, extra_line) # original traceback
+ e2.write_unraisable(space, '', with_traceback=True,
+ extra_line="\nDuring the call to 'onerror', "
+ "another exception occurred:\n\n")
+
+
@jit.jit_callback("CFFI")
def _invoke_callback(ffi_cif, ll_res, ll_args, ll_userdata):
""" Callback specification.
@@ -178,7 +208,7 @@
try:
os.write(STDERR, "SystemError: invoking a callback "
"that was already freed\n")
- except OSError:
+ except:
pass
# In this case, we don't even know how big ll_res is. Let's assume
# it is just a 'ffi_arg', and store 0 there.
@@ -195,9 +225,7 @@
extra_line = "Trying to convert the result back to C:\n"
callback.convert_result(ll_res, w_res)
except OperationError, e:
- # got an app-level exception
- callback.print_error(e, extra_line)
- callback.write_error_return_value(ll_res)
+ _handle_applevel_exception(space, callback, e, ll_res, extra_line)
#
except Exception, e:
# oups! last-level attempt to recover.
@@ -205,7 +233,7 @@
os.write(STDERR, "SystemError: callback raised ")
os.write(STDERR, str(e))
os.write(STDERR, "\n")
- except OSError:
+ except:
pass
callback.write_error_return_value(ll_res)
if must_leave:
diff --git a/pypy/module/_cffi_backend/cdataobj.py b/pypy/module/_cffi_backend/cdataobj.py
--- a/pypy/module/_cffi_backend/cdataobj.py
+++ b/pypy/module/_cffi_backend/cdataobj.py
@@ -363,16 +363,19 @@
def _sizeof(self):
return self.ctype.size
+ def with_gc(self, w_destructor):
+ with self as ptr:
+ return W_CDataGCP(self.space, ptr, self.ctype, self, w_destructor)
+
class W_CDataMem(W_CData):
- """This is the base class used for cdata objects that own and free
- their memory. Used directly by the results of cffi.cast('int', x)
- or other primitive explicitly-casted types. It is further subclassed
- by W_CDataNewOwning."""
+ """This is used only by the results of cffi.cast('int', x)
+ or other primitive explicitly-casted types."""
_attrs_ = []
- def __init__(self, space, size, ctype):
- cdata = lltype.malloc(rffi.CCHARP.TO, size, flavor='raw', zero=True)
+ def __init__(self, space, ctype):
+ cdata = lltype.malloc(rffi.CCHARP.TO, ctype.size, flavor='raw',
+ zero=False)
W_CData.__init__(self, space, cdata, ctype)
@rgc.must_be_light_finalizer
@@ -380,36 +383,65 @@
lltype.free(self._ptr, flavor='raw')
-class W_CDataNewOwning(W_CDataMem):
- """This is the class used for the cata objects created by newp()."""
- _attrs_ = []
+class W_CDataNewOwning(W_CData):
+ """This is the abstract base class used for cdata objects created
+ by newp(). They create and free their own memory according to an
+ allocator."""
+
+ # the 'length' is either >= 0 for arrays, or -1 for pointers.
+ _attrs_ = ['length']
+ _immutable_fields_ = ['length']
+
+ def __init__(self, space, cdata, ctype, length=-1):
+ W_CData.__init__(self, space, cdata, ctype)
+ self.length = length
def _repr_extra(self):
return self._repr_extra_owning()
-
-class W_CDataNewOwningLength(W_CDataNewOwning):
- """Subclass with an explicit length, for allocated instances of
- the C type 'foo[]'."""
- _attrs_ = ['length']
- _immutable_fields_ = ['length']
-
- def __init__(self, space, size, ctype, length):
- W_CDataNewOwning.__init__(self, space, size, ctype)
- self.length = length
-
def _sizeof(self):
- from pypy.module._cffi_backend import ctypearray
ctype = self.ctype
- assert isinstance(ctype, ctypearray.W_CTypeArray)
- return self.length * ctype.ctitem.size
+ if self.length >= 0:
+ from pypy.module._cffi_backend import ctypearray
+ assert isinstance(ctype, ctypearray.W_CTypeArray)
+ return self.length * ctype.ctitem.size
+ else:
+ return ctype.size
def get_array_length(self):
return self.length
+class W_CDataNewStd(W_CDataNewOwning):
+ """Subclass using the standard allocator, lltype.malloc()/lltype.free()"""
+ _attrs_ = []
+
+ @rgc.must_be_light_finalizer
+ def __del__(self):
+ lltype.free(self._ptr, flavor='raw')
+
+
+class W_CDataNewNonStdNoFree(W_CDataNewOwning):
+ """Subclass using a non-standard allocator, no free()"""
+ _attrs_ = ['w_raw_cdata']
+
+class W_CDataNewNonStdFree(W_CDataNewNonStdNoFree):
+ """Subclass using a non-standard allocator, with a free()"""
+ _attrs_ = ['w_free']
+
+ def __del__(self):
+ self.clear_all_weakrefs()
+ self.enqueue_for_destruction(self.space,
+ W_CDataNewNonStdFree.call_destructor,
+ 'destructor of ')
+
+ def call_destructor(self):
+ assert isinstance(self, W_CDataNewNonStdFree)
+ self.space.call_function(self.w_free, self.w_raw_cdata)
+
+
class W_CDataPtrToStructOrUnion(W_CData):
- """This subclass is used for the pointer returned by new('struct foo').
+ """This subclass is used for the pointer returned by new('struct foo *').
It has a strong reference to a W_CDataNewOwning that really owns the
struct, which is the object returned by the app-level expression 'p[0]'.
But it is not itself owning any memory, although its repr says so;
@@ -483,6 +515,26 @@
self.length, self.space.type(self.w_keepalive).name)
+class W_CDataGCP(W_CData):
+ """For ffi.gc()."""
+ _attrs_ = ['w_original_cdata', 'w_destructor']
+ _immutable_fields_ = ['w_original_cdata', 'w_destructor']
+
+ def __init__(self, space, cdata, ctype, w_original_cdata, w_destructor):
+ W_CData.__init__(self, space, cdata, ctype)
+ self.w_original_cdata = w_original_cdata
+ self.w_destructor = w_destructor
+
+ def __del__(self):
+ self.clear_all_weakrefs()
+ self.enqueue_for_destruction(self.space, W_CDataGCP.call_destructor,
+ 'destructor of ')
+
+ def call_destructor(self):
+ assert isinstance(self, W_CDataGCP)
+ self.space.call_function(self.w_destructor, self.w_original_cdata)
+
+
W_CData.typedef = TypeDef(
'_cffi_backend.CData',
__module__ = '_cffi_backend',
diff --git a/pypy/module/_cffi_backend/cdlopen.py b/pypy/module/_cffi_backend/cdlopen.py
--- a/pypy/module/_cffi_backend/cdlopen.py
+++ b/pypy/module/_cffi_backend/cdlopen.py
@@ -36,7 +36,10 @@
self.libname)
try:
cdata = dlsym(self.libhandle, name)
+ found = bool(cdata)
except KeyError:
+ found = False
+ if not found:
raise oefmt(self.ffi.w_FFIError,
"symbol '%s' not found in library '%s'",
name, self.libname)
diff --git a/pypy/module/_cffi_backend/cffi_opcode.py b/pypy/module/_cffi_backend/cffi_opcode.py
--- a/pypy/module/_cffi_backend/cffi_opcode.py
+++ b/pypy/module/_cffi_backend/cffi_opcode.py
@@ -53,6 +53,7 @@
OP_GLOBAL_VAR = 33
OP_DLOPEN_FUNC = 35
OP_DLOPEN_CONST = 37
+OP_GLOBAL_VAR_F = 39
PRIM_VOID = 0
PRIM_BOOL = 1
diff --git a/pypy/module/_cffi_backend/cgc.py b/pypy/module/_cffi_backend/cgc.py
deleted file mode 100644
--- a/pypy/module/_cffi_backend/cgc.py
+++ /dev/null
@@ -1,29 +0,0 @@
-from rpython.rlib import jit
-
-
- at jit.dont_look_inside
-def gc_weakrefs_build(ffi, w_cdata, w_destructor):
- from pypy.module._weakref import interp__weakref
-
- space = ffi.space
- if ffi.w_gc_wref_remove is None:
- ffi.gc_wref_dict = {}
- ffi.w_gc_wref_remove = space.getattr(space.wrap(ffi),
- space.wrap("__gc_wref_remove"))
-
- w_new_cdata = w_cdata.ctype.cast(w_cdata)
- assert w_new_cdata is not w_cdata
-
- w_ref = interp__weakref.make_weakref_with_callback(
- space,
- space.gettypefor(interp__weakref.W_Weakref),
- w_new_cdata,
- ffi.w_gc_wref_remove)
-
- ffi.gc_wref_dict[w_ref] = (w_destructor, w_cdata)
- return w_new_cdata
-
-
-def gc_wref_remove(ffi, w_ref):
- (w_destructor, w_cdata) = ffi.gc_wref_dict.pop(w_ref)
- ffi.space.call_function(w_destructor, w_cdata)
diff --git a/pypy/module/_cffi_backend/cglob.py b/pypy/module/_cffi_backend/cglob.py
--- a/pypy/module/_cffi_backend/cglob.py
+++ b/pypy/module/_cffi_backend/cglob.py
@@ -1,24 +1,66 @@
+from pypy.interpreter.error import oefmt
from pypy.interpreter.baseobjspace import W_Root
from pypy.interpreter.typedef import TypeDef
from pypy.module._cffi_backend.cdataobj import W_CData
from pypy.module._cffi_backend import newtype
+from rpython.rlib.objectmodel import we_are_translated
+from rpython.rtyper.lltypesystem import lltype, rffi
+from rpython.translator.tool.cbuild import ExternalCompilationInfo
class W_GlobSupport(W_Root):
- def __init__(self, space, w_ctype, ptr):
+ _immutable_fields_ = ['w_ctype', 'ptr', 'fetch_addr']
+
+ def __init__(self, space, name, w_ctype, ptr=lltype.nullptr(rffi.CCHARP.TO),
+ fetch_addr=lltype.nullptr(rffi.VOIDP.TO)):
self.space = space
+ self.name = name
self.w_ctype = w_ctype
self.ptr = ptr
+ self.fetch_addr = fetch_addr
+
+ def fetch_global_var_addr(self):
+ if self.ptr:
+ result = self.ptr
+ else:
+ if not we_are_translated():
+ FNPTR = rffi.CCallback([], rffi.VOIDP)
+ fetch_addr = rffi.cast(FNPTR, self.fetch_addr)
+ result = fetch_addr()
+ else:
+ # careful in translated versions: we need to call fetch_addr,
+ # but in a GIL-releasing way. The easiest is to invoke a
+ # llexternal() helper.
+ result = pypy__cffi_fetch_var(self.fetch_addr)
+ result = rffi.cast(rffi.CCHARP, result)
+ if not result:
+ from pypy.module._cffi_backend import ffi_obj
+ ffierror = ffi_obj.get_ffi_error(self.space)
+ raise oefmt(ffierror, "global variable '%s' is at address NULL",
+ self.name)
+ return result
def read_global_var(self):
- return self.w_ctype.convert_to_object(self.ptr)
+ return self.w_ctype.convert_to_object(self.fetch_global_var_addr())
def write_global_var(self, w_newvalue):
- self.w_ctype.convert_from_object(self.ptr, w_newvalue)
+ self.w_ctype.convert_from_object(self.fetch_global_var_addr(),
+ w_newvalue)
def address(self):
w_ctypeptr = newtype.new_pointer_type(self.space, self.w_ctype)
- return W_CData(self.space, self.ptr, w_ctypeptr)
+ return W_CData(self.space, self.fetch_global_var_addr(), w_ctypeptr)
W_GlobSupport.typedef = TypeDef("FFIGlobSupport")
W_GlobSupport.typedef.acceptable_as_base_class = False
+
+
+eci = ExternalCompilationInfo(post_include_bits=["""
+static void *pypy__cffi_fetch_var(void *fetch_addr) {
+ return ((void*(*)(void))fetch_addr)();
+}
+"""])
+
+pypy__cffi_fetch_var = rffi.llexternal(
+ "pypy__cffi_fetch_var", [rffi.VOIDP], rffi.VOIDP,
+ compilation_info=eci)
diff --git a/pypy/module/_cffi_backend/ctypearray.py b/pypy/module/_cffi_backend/ctypearray.py
--- a/pypy/module/_cffi_backend/ctypearray.py
+++ b/pypy/module/_cffi_backend/ctypearray.py
@@ -28,7 +28,7 @@
def _alignof(self):
return self.ctitem.alignof()
- def newp(self, w_init):
+ def newp(self, w_init, allocator):
space = self.space
datasize = self.size
#
@@ -40,12 +40,10 @@
except OverflowError:
raise OperationError(space.w_OverflowError,
space.wrap("array size would overflow a ssize_t"))
- #
- cdata = cdataobj.W_CDataNewOwningLength(space, datasize,
- self, length)
+ else:
+ length = self.length
#
- else:
- cdata = cdataobj.W_CDataNewOwning(space, datasize, self)
+ cdata = allocator.allocate(space, datasize, self, length)
#
if not space.is_w(w_init, space.w_None):
with cdata as ptr:
diff --git a/pypy/module/_cffi_backend/ctypefunc.py b/pypy/module/_cffi_backend/ctypefunc.py
--- a/pypy/module/_cffi_backend/ctypefunc.py
+++ b/pypy/module/_cffi_backend/ctypefunc.py
@@ -143,7 +143,7 @@
@jit.unroll_safe
def _call(self, funcaddr, args_w):
space = self.space
- cif_descr = self.cif_descr
+ cif_descr = self.cif_descr # 'self' should have been promoted here
size = cif_descr.exchange_size
mustfree_max_plus_1 = 0
buffer = lltype.malloc(rffi.CCHARP.TO, size, flavor='raw')
diff --git a/pypy/module/_cffi_backend/ctypeobj.py b/pypy/module/_cffi_backend/ctypeobj.py
--- a/pypy/module/_cffi_backend/ctypeobj.py
+++ b/pypy/module/_cffi_backend/ctypeobj.py
@@ -55,7 +55,7 @@
def pack_list_of_items(self, cdata, w_ob):
return False
- def newp(self, w_init):
+ def newp(self, w_init, allocator):
space = self.space
raise oefmt(space.w_TypeError,
"expected a pointer or array ctype, got '%s'", self.name)
@@ -90,6 +90,16 @@
def _convert_error(self, expected, w_got):
space = self.space
if isinstance(w_got, cdataobj.W_CData):
+ if self.name == w_got.ctype.name:
+ # in case we'd give the error message "initializer for
+ # ctype 'A' must be a pointer to same type, not cdata
+ # 'B'", but with A=B, then give instead a different error
+ # message to try to clear up the confusion
+ return oefmt(space.w_TypeError,
+ "initializer for ctype '%s' appears indeed to "
+ "be '%s', but the types are different (check "
+ "that you are not e.g. mixing up different ffi "
+ "instances)", self.name, w_got.ctype.name)
return oefmt(space.w_TypeError,
"initializer for ctype '%s' must be a %s, not cdata "
"'%s'", self.name, expected, w_got.ctype.name)
diff --git a/pypy/module/_cffi_backend/ctypeprim.py b/pypy/module/_cffi_backend/ctypeprim.py
--- a/pypy/module/_cffi_backend/ctypeprim.py
+++ b/pypy/module/_cffi_backend/ctypeprim.py
@@ -63,7 +63,7 @@
value = self._cast_result(value)
else:
value = self._cast_generic(w_ob)
- w_cdata = cdataobj.W_CDataMem(space, self.size, self)
+ w_cdata = cdataobj.W_CDataMem(space, self)
self.write_raw_integer_data(w_cdata, value)
return w_cdata
@@ -134,8 +134,7 @@
def convert_to_object(self, cdata):
unichardata = rffi.cast(rffi.CWCHARP, cdata)
- s = rffi.wcharpsize2unicode(unichardata, 1)
- return self.space.wrap(s)
+ return self.space.wrap(unichardata[0])
def string(self, cdataobj, maxlen):
with cdataobj as ptr:
@@ -354,7 +353,7 @@
value = self.cast_unicode(w_ob)
else:
value = space.float_w(w_ob)
- w_cdata = cdataobj.W_CDataMem(space, self.size, self)
+ w_cdata = cdataobj.W_CDataMem(space, self)
if not isinstance(self, W_CTypePrimitiveLongDouble):
w_cdata.write_raw_float_data(value)
else:
@@ -447,7 +446,7 @@
return self.space.wrap(value)
def convert_to_object(self, cdata):
- w_cdata = cdataobj.W_CDataMem(self.space, self.size, self)
+ w_cdata = cdataobj.W_CDataMem(self.space, self)
with w_cdata as ptr:
self._copy_longdouble(cdata, ptr)
return w_cdata
diff --git a/pypy/module/_cffi_backend/ctypeptr.py b/pypy/module/_cffi_backend/ctypeptr.py
--- a/pypy/module/_cffi_backend/ctypeptr.py
+++ b/pypy/module/_cffi_backend/ctypeptr.py
@@ -185,7 +185,7 @@
self.is_void_ptr = isinstance(ctitem, ctypevoid.W_CTypeVoid)
W_CTypePtrBase.__init__(self, space, size, extra, 2, ctitem)
- def newp(self, w_init):
+ def newp(self, w_init, allocator):
from pypy.module._cffi_backend.ctypestruct import W_CTypeStructOrUnion
space = self.space
ctitem = self.ctitem
@@ -205,14 +205,14 @@
datasize = ctitem.convert_struct_from_object(
lltype.nullptr(rffi.CCHARP.TO), w_init, datasize)
#
- cdatastruct = cdataobj.W_CDataNewOwning(space, datasize, ctitem)
+ cdatastruct = allocator.allocate(space, datasize, ctitem)
ptr = cdatastruct.unsafe_escaping_ptr()
cdata = cdataobj.W_CDataPtrToStructOrUnion(space, ptr,
self, cdatastruct)
else:
if self.is_char_or_unichar_ptr_or_array():
datasize *= 2 # forcefully add a null character
- cdata = cdataobj.W_CDataNewOwning(space, datasize, self)
+ cdata = allocator.allocate(space, datasize, self)
#
if not space.is_w(w_init, space.w_None):
with cdata as ptr:
@@ -223,9 +223,13 @@
if (isinstance(w_cdata, cdataobj.W_CDataNewOwning) or
isinstance(w_cdata, cdataobj.W_CDataPtrToStructOrUnion)):
if i != 0:
- space = self.space
- raise oefmt(space.w_IndexError,
+ raise oefmt(self.space.w_IndexError,
"cdata '%s' can only be indexed by 0", self.name)
+ else:
+ if not w_cdata.unsafe_escaping_ptr():
+ raise oefmt(self.space.w_RuntimeError,
+ "cannot dereference null pointer from cdata '%s'",
+ self.name)
return self
def _check_slice_index(self, w_cdata, start, stop):
diff --git a/pypy/module/_cffi_backend/ctypestruct.py b/pypy/module/_cffi_backend/ctypestruct.py
--- a/pypy/module/_cffi_backend/ctypestruct.py
+++ b/pypy/module/_cffi_backend/ctypestruct.py
@@ -81,10 +81,9 @@
def copy_and_convert_to_object(self, source):
space = self.space
self.check_complete()
- ob = cdataobj.W_CDataNewOwning(space, self.size, self)
- with ob as target:
- misc._raw_memcopy(source, target, self.size)
- return ob
+ ptr = lltype.malloc(rffi.CCHARP.TO, self.size, flavor='raw', zero=False)
+ misc._raw_memcopy(source, ptr, self.size)
+ return cdataobj.W_CDataNewStd(space, ptr, self)
def typeoffsetof_field(self, fieldname, following):
self.force_lazy_struct()
diff --git a/pypy/module/_cffi_backend/ffi_obj.py b/pypy/module/_cffi_backend/ffi_obj.py
--- a/pypy/module/_cffi_backend/ffi_obj.py
+++ b/pypy/module/_cffi_backend/ffi_obj.py
@@ -10,8 +10,8 @@
from pypy.module._cffi_backend import parse_c_type, realize_c_type
from pypy.module._cffi_backend import newtype, cerrno, ccallback, ctypearray
from pypy.module._cffi_backend import ctypestruct, ctypeptr, handle
-from pypy.module._cffi_backend import cbuffer, func, cgc, wrapper
-from pypy.module._cffi_backend import cffi_opcode
+from pypy.module._cffi_backend import cbuffer, func, wrapper
+from pypy.module._cffi_backend import cffi_opcode, allocator
from pypy.module._cffi_backend.ctypeobj import W_CType
from pypy.module._cffi_backend.cdataobj import W_CData
@@ -44,6 +44,10 @@
class W_FFIObject(W_Root):
+ ACCEPT_STRING = ACCEPT_STRING
+ ACCEPT_CTYPE = ACCEPT_CTYPE
+ ACCEPT_CDATA = ACCEPT_CDATA
+
w_gc_wref_remove = None
@jit.dont_look_inside
@@ -276,8 +280,9 @@
@unwrap_spec(w_python_callable=WrappedDefault(None),
- w_error=WrappedDefault(None))
- def descr_callback(self, w_cdecl, w_python_callable, w_error):
+ w_error=WrappedDefault(None),
+ w_onerror=WrappedDefault(None))
+ def descr_callback(self, w_cdecl, w_python_callable, w_error, w_onerror):
"""\
Return a callback object or a decorator making such a callback object.
'cdecl' must name a C function pointer type. The callback invokes the
@@ -290,14 +295,16 @@
space = self.space
if not space.is_none(w_python_callable):
return ccallback.W_CDataCallback(space, w_ctype,
- w_python_callable, w_error)
+ w_python_callable, w_error,
+ w_onerror)
else:
# decorator mode: returns a single-argument function
- return space.appexec([w_ctype, w_error],
- """(ctype, error):
+ return space.appexec([w_ctype, w_error, w_onerror],
+ """(ctype, error, onerror):
import _cffi_backend
return lambda python_callable: (
- _cffi_backend.callback(ctype, python_callable, error))""")
+ _cffi_backend.callback(ctype, python_callable,
+ error, onerror))""")
def descr_cast(self, w_arg, w_ob):
@@ -341,10 +348,7 @@
Later, when this new cdata object is garbage-collected,
'destructor(old_cdata_object)' will be called."""
#
- return cgc.gc_weakrefs_build(self, w_cdata, w_destructor)
-
- def descr___gc_wref_remove(self, w_ref):
- return cgc.gc_wref_remove(self, w_ref)
+ return w_cdata.with_gc(w_destructor)
@unwrap_spec(replace_with=str)
@@ -411,7 +415,31 @@
pointer to the memory somewhere else, e.g. into another structure."""
#
w_ctype = self.ffi_type(w_arg, ACCEPT_STRING | ACCEPT_CTYPE)
- return w_ctype.newp(w_init)
+ return w_ctype.newp(w_init, allocator.default_allocator)
+
+
+ @unwrap_spec(w_alloc=WrappedDefault(None),
+ w_free=WrappedDefault(None),
+ should_clear_after_alloc=int)
+ def descr_new_allocator(self, w_alloc, w_free,
+ should_clear_after_alloc=1):
+ """\
+Return a new allocator, i.e. a function that behaves like ffi.new()
+but uses the provided low-level 'alloc' and 'free' functions.
+
+'alloc' is called with the size as argument. If it returns NULL, a
+MemoryError is raised. 'free' is called with the result of 'alloc'
+as argument. Both can be either Python function or directly C
+functions. If 'free' is None, then no free function is called.
+If both 'alloc' and 'free' are None, the default is used.
+
+If 'should_clear_after_alloc' is set to False, then the memory
+returned by 'alloc' is assumed to be already cleared (or you are
+fine with garbage); otherwise CFFI will clear it.
+ """
+ #
+ return allocator.new_allocator(self, w_alloc, w_free,
+ should_clear_after_alloc)
def descr_new_handle(self, w_arg):
@@ -539,12 +567,17 @@
@jit.dont_look_inside
-def W_FFIObject___new__(space, w_subtype, __args__):
- r = space.allocate_instance(W_FFIObject, w_subtype)
+def make_plain_ffi_object(space, w_ffitype=None):
+ if w_ffitype is None:
+ w_ffitype = space.gettypefor(W_FFIObject)
+ r = space.allocate_instance(W_FFIObject, w_ffitype)
# get in 'src_ctx' a NULL which translation doesn't consider to be constant
src_ctx = rffi.cast(parse_c_type.PCTX, 0)
r.__init__(space, src_ctx)
- return space.wrap(r)
+ return r
+
+def W_FFIObject___new__(space, w_subtype, __args__):
+ return space.wrap(make_plain_ffi_object(space, w_subtype))
def make_CData(space):
return space.gettypefor(W_CData)
@@ -578,7 +611,6 @@
W_FFIObject.set_errno,
doc=W_FFIObject.doc_errno,
cls=W_FFIObject),
- __gc_wref_remove = interp2app(W_FFIObject.descr___gc_wref_remove),
addressof = interp2app(W_FFIObject.descr_addressof),
alignof = interp2app(W_FFIObject.descr_alignof),
buffer = interp2app(W_FFIObject.descr_buffer),
@@ -592,6 +624,7 @@
getctype = interp2app(W_FFIObject.descr_getctype),
integer_const = interp2app(W_FFIObject.descr_integer_const),
new = interp2app(W_FFIObject.descr_new),
+ new_allocator = interp2app(W_FFIObject.descr_new_allocator),
new_handle = interp2app(W_FFIObject.descr_new_handle),
offsetof = interp2app(W_FFIObject.descr_offsetof),
sizeof = interp2app(W_FFIObject.descr_sizeof),
diff --git a/pypy/module/_cffi_backend/func.py b/pypy/module/_cffi_backend/func.py
--- a/pypy/module/_cffi_backend/func.py
+++ b/pypy/module/_cffi_backend/func.py
@@ -1,13 +1,13 @@
from pypy.interpreter.error import OperationError, oefmt
from pypy.interpreter.gateway import unwrap_spec, WrappedDefault
-from pypy.module._cffi_backend import ctypeobj, cdataobj
+from pypy.module._cffi_backend import ctypeobj, cdataobj, allocator
# ____________________________________________________________
@unwrap_spec(w_ctype=ctypeobj.W_CType, w_init=WrappedDefault(None))
def newp(space, w_ctype, w_init):
- return w_ctype.newp(w_init)
+ return w_ctype.newp(w_init, allocator.default_allocator)
# ____________________________________________________________
@@ -18,9 +18,9 @@
# ____________________________________________________________
@unwrap_spec(w_ctype=ctypeobj.W_CType)
-def callback(space, w_ctype, w_callable, w_error=None):
+def callback(space, w_ctype, w_callable, w_error=None, w_onerror=None):
from pypy.module._cffi_backend.ccallback import W_CDataCallback
- return W_CDataCallback(space, w_ctype, w_callable, w_error)
+ return W_CDataCallback(space, w_ctype, w_callable, w_error, w_onerror)
# ____________________________________________________________
@@ -105,3 +105,9 @@
"raw address on PyPy", w_x)
#
return cdataobj.W_CDataFromBuffer(space, _cdata, w_ctype, buf, w_x)
+
+# ____________________________________________________________
+
+ at unwrap_spec(w_cdata=cdataobj.W_CData)
+def gcp(space, w_cdata, w_destructor):
+ return w_cdata.with_gc(w_destructor)
diff --git a/pypy/module/_cffi_backend/lib_obj.py b/pypy/module/_cffi_backend/lib_obj.py
--- a/pypy/module/_cffi_backend/lib_obj.py
+++ b/pypy/module/_cffi_backend/lib_obj.py
@@ -60,12 +60,12 @@
self.ffi, self.ctx.c_types, getarg(g.c_type_op))
assert isinstance(rawfunctype, realize_c_type.W_RawFuncType)
#
- w_ct, locs = rawfunctype.unwrap_as_nostruct_fnptr(self.ffi)
+ rawfunctype.prepare_nostruct_fnptr(self.ffi)
#
ptr = rffi.cast(rffi.CCHARP, g.c_address)
assert ptr
- return W_FunctionWrapper(self.space, ptr, g.c_size_or_direct_fn, w_ct,
- locs, rawfunctype, fnname, self.libname)
+ return W_FunctionWrapper(self.space, ptr, g.c_size_or_direct_fn,
+ rawfunctype, fnname, self.libname)
@jit.elidable_promote()
def _get_attr_elidable(self, attr):
@@ -102,6 +102,8 @@
#
elif op == cffi_opcode.OP_GLOBAL_VAR:
# A global variable of the exact type specified here
+ # (nowadays, only used by the ABI mode or backend
+ # compatibility; see OP_GLOBAL_F for the API mode
w_ct = realize_c_type.realize_c_type(
self.ffi, self.ctx.c_types, getarg(g.c_type_op))
g_size = rffi.cast(lltype.Signed, g.c_size_or_direct_fn)
@@ -113,7 +115,13 @@
ptr = rffi.cast(rffi.CCHARP, g.c_address)
if not ptr: # for dlopen() style
ptr = self.cdlopen_fetch(attr)
- w_result = cglob.W_GlobSupport(space, w_ct, ptr)
+ w_result = cglob.W_GlobSupport(space, attr, w_ct, ptr=ptr)
+ #
+ elif op == cffi_opcode.OP_GLOBAL_VAR_F:
+ w_ct = realize_c_type.realize_c_type(
+ self.ffi, self.ctx.c_types, getarg(g.c_type_op))
+ w_result = cglob.W_GlobSupport(space, attr, w_ct,
+ fetch_addr=g.c_address)
#
elif (op == cffi_opcode.OP_CONSTANT_INT or
op == cffi_opcode.OP_ENUM):
@@ -131,6 +139,9 @@
realize_c_type.FUNCPTR_FETCH_CHARP,
g.c_address)
if w_ct.size <= 0:
+ raise oefmt(self.ffi.w_FFIError,
+ "constant '%s' is of type '%s', "
+ "whose size is not known", attr, w_ct.name)
raise oefmt(space.w_SystemError,
"constant has no known size")
if not fetch_funcptr: # for dlopen() style
@@ -172,7 +183,11 @@
w_value = self._build_attr(attr)
if w_value is None:
if is_getattr and attr == '__all__':
- return self.dir1(ignore_type=cffi_opcode.OP_GLOBAL_VAR)
+ return self.dir1(ignore_global_vars=True)
+ if is_getattr and attr == '__dict__':
+ return self.full_dict_copy()
+ if is_getattr and attr == '__name__':
+ return self.descr_repr()
raise oefmt(self.space.w_AttributeError,
"cffi library '%s' has no function, constant "
"or global variable named '%s'",
@@ -202,16 +217,31 @@
def descr_dir(self):
return self.dir1()
- def dir1(self, ignore_type=-1):
+ def dir1(self, ignore_global_vars=False):
space = self.space
total = rffi.getintfield(self.ctx, 'c_num_globals')
g = self.ctx.c_globals
names_w = []
for i in range(total):
- if getop(g[i].c_type_op) != ignore_type:
- names_w.append(space.wrap(rffi.charp2str(g[i].c_name)))
+ if ignore_global_vars:
+ op = getop(g[i].c_type_op)
+ if (op == cffi_opcode.OP_GLOBAL_VAR or
+ op == cffi_opcode.OP_GLOBAL_VAR_F):
+ continue
+ names_w.append(space.wrap(rffi.charp2str(g[i].c_name)))
return space.newlist(names_w)
+ def full_dict_copy(self):
+ space = self.space
+ total = rffi.getintfield(self.ctx, 'c_num_globals')
+ g = self.ctx.c_globals
+ w_result = space.newdict()
+ for i in range(total):
+ w_attr = space.wrap(rffi.charp2str(g[i].c_name))
+ w_value = self._get_attr(w_attr)
+ space.setitem(w_result, w_attr, w_value)
+ return w_result
+
def address_of_func_or_global_var(self, varname):
# rebuild a string object from 'varname', to do typechecks and
# to force a unicode back to a plain string
@@ -224,7 +254,8 @@
if isinstance(w_value, W_FunctionWrapper):
# '&func' returns a regular cdata pointer-to-function
if w_value.directfnptr:
- return W_CData(space, w_value.directfnptr, w_value.ctype)
+ ctype = w_value.typeof(self.ffi)
+ return W_CData(space, w_value.directfnptr, ctype)
else:
return w_value # backward compatibility
#
diff --git a/pypy/module/_cffi_backend/realize_c_type.py b/pypy/module/_cffi_backend/realize_c_type.py
--- a/pypy/module/_cffi_backend/realize_c_type.py
+++ b/pypy/module/_cffi_backend/realize_c_type.py
@@ -1,4 +1,5 @@
import sys
+from rpython.rlib import jit
from rpython.rlib.rarithmetic import intmask
from rpython.rlib.objectmodel import specialize
from rpython.rtyper.lltypesystem import lltype, rffi
@@ -135,8 +136,12 @@
class W_RawFuncType(W_Root):
"""Temporary: represents a C function type (not a function pointer)"""
+
+ _immutable_fields_ = ['nostruct_ctype', 'nostruct_locs', 'nostruct_nargs']
_ctfuncptr = None
- _nostruct_ctfuncptr = (None, None)
+ nostruct_ctype = None
+ nostruct_locs = None
+ nostruct_nargs = 0
def __init__(self, opcodes, base_index):
self.opcodes = opcodes
@@ -168,14 +173,16 @@
assert self._ctfuncptr is not None
return self._ctfuncptr
- def unwrap_as_nostruct_fnptr(self, ffi):
- # tweaked version: instead of returning the ctfuncptr corresponding
- # exactly to the OP_FUNCTION ... OP_FUNCTION_END opcodes, return
- # another one in which the struct args are replaced with ptr-to-
- # struct, and a struct return value is replaced with a hidden first
- # arg of type ptr-to-struct. This is how recompiler.py produces
+ @jit.dont_look_inside
+ def prepare_nostruct_fnptr(self, ffi):
+ # tweaked version: instead of returning the ctfuncptr
+ # corresponding exactly to the OP_FUNCTION ... OP_FUNCTION_END
+ # opcodes, this builds in self.nostruct_ctype another one in
+ # which the struct args are replaced with ptr-to- struct, and
+ # a struct return value is replaced with a hidden first arg of
+ # type ptr-to-struct. This is how recompiler.py produces
# trampoline functions for PyPy.
- if self._nostruct_ctfuncptr[0] is None:
+ if self.nostruct_ctype is None:
fargs, fret, ellipsis = self._unpack(ffi)
# 'locs' will be a string of the same length as the final fargs,
# containing 'A' where a struct argument was detected, and 'R'
@@ -198,8 +205,10 @@
locs = None
else:
locs = ''.join(locs)
- self._nostruct_ctfuncptr = (ctfuncptr, locs)
- return self._nostruct_ctfuncptr
+ self.nostruct_ctype = ctfuncptr
+ self.nostruct_locs = locs
+ self.nostruct_nargs = len(ctfuncptr.fargs) - (locs is not None and
+ locs[0] == 'R')
def unexpected_fn_type(self, ffi):
fargs, fret, ellipsis = self._unpack(ffi)
diff --git a/pypy/module/_cffi_backend/src/parse_c_type.c b/pypy/module/_cffi_backend/src/parse_c_type.c
--- a/pypy/module/_cffi_backend/src/parse_c_type.c
+++ b/pypy/module/_cffi_backend/src/parse_c_type.c
@@ -362,7 +362,7 @@
case TOK_INTEGER:
errno = 0;
-#ifndef MS_WIN32
+#ifndef _MSC_VER
if (sizeof(length) > sizeof(unsigned long))
length = strtoull(tok->p, &endptr, 0);
else
diff --git a/pypy/module/_cffi_backend/test/_backend_test_c.py b/pypy/module/_cffi_backend/test/_backend_test_c.py
--- a/pypy/module/_cffi_backend/test/_backend_test_c.py
+++ b/pypy/module/_cffi_backend/test/_backend_test_c.py
@@ -1170,6 +1170,14 @@
BShort = new_primitive_type("short")
BFunc = new_function_type((BShort,), BShort, False)
f = callback(BFunc, Zcb1, -42)
+ #
+ seen = []
+ oops_result = None
+ def oops(*args):
+ seen.append(args)
+ return oops_result
+ ff = callback(BFunc, Zcb1, -42, oops)
+ #
orig_stderr = sys.stderr
orig_getline = linecache.getline
try:
@@ -1195,6 +1203,59 @@
Trying to convert the result back to C:
OverflowError: integer 60000 does not fit 'short'
""")
+ sys.stderr = cStringIO.StringIO()
+ bigvalue = 20000
+ assert len(seen) == 0
+ assert ff(bigvalue) == -42
+ assert sys.stderr.getvalue() == ""
+ assert len(seen) == 1
+ exc, val, tb = seen[0]
+ assert exc is OverflowError
+ assert str(val) == "integer 60000 does not fit 'short'"
+ #
+ sys.stderr = cStringIO.StringIO()
+ bigvalue = 20000
+ del seen[:]
+ oops_result = 81
+ assert ff(bigvalue) == 81
+ oops_result = None
+ assert sys.stderr.getvalue() == ""
+ assert len(seen) == 1
+ exc, val, tb = seen[0]
+ assert exc is OverflowError
+ assert str(val) == "integer 60000 does not fit 'short'"
+ #
+ sys.stderr = cStringIO.StringIO()
+ bigvalue = 20000
+ del seen[:]
+ oops_result = "xy" # not None and not an int!
+ assert ff(bigvalue) == -42
+ oops_result = None
+ assert matches(sys.stderr.getvalue(), """\
+From cffi callback <function$Zcb1 at 0x$>:
+Trying to convert the result back to C:
+OverflowError: integer 60000 does not fit 'short'
+
+During the call to 'onerror', another exception occurred:
+
+TypeError: $integer$
+""")
+ #
+ sys.stderr = cStringIO.StringIO()
+ seen = "not a list" # this makes the oops() function crash
+ assert ff(bigvalue) == -42
+ assert matches(sys.stderr.getvalue(), """\
+From cffi callback <function$Zcb1 at 0x$>:
+Trying to convert the result back to C:
+OverflowError: integer 60000 does not fit 'short'
+
+During the call to 'onerror', another exception occurred:
+
+Traceback (most recent call last):
+ File "$", line $, in oops
+ $
+AttributeError: 'str' object has no attribute 'append'
+""")
finally:
sys.stderr = orig_stderr
linecache.getline = orig_getline
@@ -2099,8 +2160,7 @@
p = cast(BVoidP, 123456)
py.test.raises(TypeError, "p[0]")
p = cast(BVoidP, 0)
- if 'PY_DOT_PY' in globals(): py.test.skip("NULL crashes early on py.py")
- py.test.raises(TypeError, "p[0]")
+ py.test.raises((TypeError, RuntimeError), "p[0]")
def test_iter():
BInt = new_primitive_type("int")
@@ -3333,6 +3393,38 @@
check(4 | 8, "CHB", "GTB")
check(4 | 16, "CHB", "ROB")
+def test_dereference_null_ptr():
+ BInt = new_primitive_type("int")
+ BIntPtr = new_pointer_type(BInt)
+ p = cast(BIntPtr, 0)
+ py.test.raises(RuntimeError, "p[0]")
+ py.test.raises(RuntimeError, "p[0] = 42")
+ py.test.raises(RuntimeError, "p[42]")
+ py.test.raises(RuntimeError, "p[42] = -1")
+
+def test_mixup():
+ BStruct1 = new_struct_type("foo")
+ BStruct2 = new_struct_type("foo") # <= same name as BStruct1
+ BStruct3 = new_struct_type("bar")
+ BStruct1Ptr = new_pointer_type(BStruct1)
+ BStruct2Ptr = new_pointer_type(BStruct2)
+ BStruct3Ptr = new_pointer_type(BStruct3)
+ BStruct1PtrPtr = new_pointer_type(BStruct1Ptr)
+ BStruct2PtrPtr = new_pointer_type(BStruct2Ptr)
+ BStruct3PtrPtr = new_pointer_type(BStruct3Ptr)
+ pp1 = newp(BStruct1PtrPtr)
+ pp2 = newp(BStruct2PtrPtr)
+ pp3 = newp(BStruct3PtrPtr)
+ pp1[0] = pp1[0]
+ e = py.test.raises(TypeError, "pp3[0] = pp1[0]")
+ assert str(e.value).startswith("initializer for ctype 'bar *' must be a ")
+ assert str(e.value).endswith(", not cdata 'foo *'")
+ e = py.test.raises(TypeError, "pp2[0] = pp1[0]")
+ assert str(e.value) == ("initializer for ctype 'foo *' appears indeed to "
+ "be 'foo *', but the types are different (check "
+ "that you are not e.g. mixing up different ffi "
+ "instances)")
+
def test_version():
# this test is here mostly for PyPy
- assert __version__ == "1.1.2"
+ assert __version__ == "1.2.0"
diff --git a/pypy/module/_cffi_backend/test/test_ffi_obj.py b/pypy/module/_cffi_backend/test/test_ffi_obj.py
--- a/pypy/module/_cffi_backend/test/test_ffi_obj.py
+++ b/pypy/module/_cffi_backend/test/test_ffi_obj.py
@@ -114,6 +114,18 @@
assert ffi.callback("int(int)", lambda x: x + "", -66)(10) == -66
assert ffi.callback("int(int)", lambda x: x + "", error=-66)(10) == -66
+ def test_ffi_callback_onerror(self):
+ import _cffi_backend as _cffi1_backend
+ ffi = _cffi1_backend.FFI()
+ seen = []
+ def myerror(exc, val, tb):
+ seen.append(exc)
+ cb = ffi.callback("int(int)", lambda x: x + "", onerror=myerror)
+ assert cb(10) == 0
+ cb = ffi.callback("int(int)", lambda x:int(1E100), -66, onerror=myerror)
+ assert cb(10) == -66
+ assert seen == [TypeError, OverflowError]
+
def test_ffi_callback_decorator(self):
import _cffi_backend as _cffi1_backend
ffi = _cffi1_backend.FFI()
@@ -122,6 +134,37 @@
assert deco(lambda x: x + "")(10) == -66
assert deco(lambda x: x + 42)(10) == 52
+ def test_ffi_callback_onerror(self):
+ import _cffi_backend as _cffi1_backend
+ ffi = _cffi1_backend.FFI()
+ seen = []
+ def oops(*args):
+ seen.append(args)
+
+ @ffi.callback("int(int)", onerror=oops)
+ def fn1(x):
+ return x + ""
+ assert fn1(10) == 0
+
+ @ffi.callback("int(int)", onerror=oops, error=-66)
+ def fn2(x):
+ return x + ""
+ assert fn2(10) == -66
+
+ assert len(seen) == 2
+ exc, val, tb = seen[0]
+ assert exc is TypeError
+ assert isinstance(val, TypeError)
+ assert tb.tb_frame.f_code.co_name == "fn1"
+ exc, val, tb = seen[1]
+ assert exc is TypeError
+ assert isinstance(val, TypeError)
+ assert tb.tb_frame.f_code.co_name == "fn2"
+ del seen[:]
+ #
+ raises(TypeError, ffi.callback, "int(int)",
+ lambda x: x, onerror=42) # <- not callable
+
def test_ffi_getctype(self):
import _cffi_backend as _cffi1_backend
ffi = _cffi1_backend.FFI()
@@ -228,3 +271,99 @@
import gc
gc.collect()
assert seen == [1]
+
+ def test_ffi_new_allocator_1(self):
+ import _cffi_backend as _cffi1_backend
+ ffi = _cffi1_backend.FFI()
+ alloc1 = ffi.new_allocator()
+ alloc2 = ffi.new_allocator(should_clear_after_alloc=False)
+ for retry in range(100):
+ p1 = alloc1("int[10]")
+ p2 = alloc2("int[10]")
+ combination = 0
+ for i in range(10):
+ assert p1[i] == 0
+ combination |= p2[i]
+ p1[i] = -42
+ p2[i] = -43
+ if combination != 0:
+ break
+ del p1, p2
+ import gc; gc.collect()
+ else:
+ raise AssertionError("cannot seem to get an int[10] not "
+ "completely cleared")
+
+ def test_ffi_new_allocator_2(self):
+ import _cffi_backend as _cffi1_backend
+ ffi = _cffi1_backend.FFI()
+ seen = []
+ def myalloc(size):
+ seen.append(size)
+ return ffi.new("char[]", "X" * size)
+ def myfree(raw):
+ seen.append(raw)
+ alloc1 = ffi.new_allocator(myalloc, myfree)
+ alloc2 = ffi.new_allocator(alloc=myalloc, free=myfree,
+ should_clear_after_alloc=False)
+ p1 = alloc1("int[10]")
+ p2 = alloc2("int[]", 10)
+ assert seen == [40, 40]
+ assert ffi.typeof(p1) == ffi.typeof("int[10]")
+ assert ffi.sizeof(p1) == 40
+ assert ffi.typeof(p2) == ffi.typeof("int[]")
+ assert ffi.sizeof(p2) == 40
+ assert p1[5] == 0
+ assert p2[6] == ord('X') * 0x01010101
+ raw1 = ffi.cast("char *", p1)
+ raw2 = ffi.cast("char *", p2)
+ del p1, p2
+ retries = 0
+ while len(seen) != 4:
+ retries += 1
+ assert retries <= 5
+ import gc; gc.collect()
+ assert seen == [40, 40, raw1, raw2]
+ assert repr(seen[2]) == "<cdata 'char[]' owning 41 bytes>"
+ assert repr(seen[3]) == "<cdata 'char[]' owning 41 bytes>"
+
+ def test_ffi_new_allocator_3(self):
+ import _cffi_backend as _cffi1_backend
+ ffi = _cffi1_backend.FFI()
+ seen = []
+ def myalloc(size):
+ seen.append(size)
+ return ffi.new("char[]", "X" * size)
+ alloc1 = ffi.new_allocator(myalloc) # no 'free'
+ p1 = alloc1("int[10]")
+ assert seen == [40]
+ assert ffi.typeof(p1) == ffi.typeof("int[10]")
+ assert ffi.sizeof(p1) == 40
+ assert p1[5] == 0
+
+ def test_ffi_new_allocator_4(self):
+ import _cffi_backend as _cffi1_backend
+ ffi = _cffi1_backend.FFI()
+ raises(TypeError, ffi.new_allocator, free=lambda x: None)
+ #
+ def myalloc2(size):
+ raise LookupError
+ alloc2 = ffi.new_allocator(myalloc2)
+ raises(LookupError, alloc2, "int[5]")
+ #
+ def myalloc3(size):
+ return 42
+ alloc3 = ffi.new_allocator(myalloc3)
+ e = raises(TypeError, alloc3, "int[5]")
+ assert str(e.value) == "alloc() must return a cdata object (got int)"
+ #
+ def myalloc4(size):
+ return ffi.cast("int", 42)
+ alloc4 = ffi.new_allocator(myalloc4)
+ e = raises(TypeError, alloc4, "int[5]")
+ assert str(e.value) == "alloc() must return a cdata pointer, not 'int'"
+ #
+ def myalloc5(size):
+ return ffi.NULL
+ alloc5 = ffi.new_allocator(myalloc5)
+ raises(MemoryError, alloc5, "int[5]")
diff --git a/pypy/module/_cffi_backend/test/test_recompiler.py b/pypy/module/_cffi_backend/test/test_recompiler.py
--- a/pypy/module/_cffi_backend/test/test_recompiler.py
+++ b/pypy/module/_cffi_backend/test/test_recompiler.py
@@ -16,8 +16,8 @@
from cffi import ffiplatform
except ImportError:
py.test.skip("system cffi module not found or older than 1.0.0")
- if cffi.__version_info__ < (1, 0, 4):
- py.test.skip("system cffi module needs to be at least 1.0.4")
+ if cffi.__version_info__ < (1, 2, 0):
+ py.test.skip("system cffi module needs to be at least 1.2.0")
space.appexec([], """():
import _cffi_backend # force it to be initialized
""")
@@ -276,6 +276,15 @@
""")
lib.aa = 5
assert dir(lib) == ['aa', 'ff', 'my_constant']
+ #
+ aaobj = lib.__dict__['aa']
+ assert not isinstance(aaobj, int) # some internal object instead
+ assert lib.__dict__ == {
More information about the pypy-commit
mailing list