Author: Manuel Jacob <m...@manueljacob.de> Branch: py3.3 Changeset: r78577:95c0538a5119 Date: 2015-07-17 11:51 +0200 http://bitbucket.org/pypy/pypy/changeset/95c0538a5119/
Log: hg merge py3k diff too long, truncating to 2000 out of 4669 lines diff --git a/lib-python/3/test/test_dis.py b/lib-python/3/test/test_dis.py --- a/lib-python/3/test/test_dis.py +++ b/lib-python/3/test/test_dis.py @@ -1,6 +1,6 @@ # Minimal tests for dis module -from test.support import run_unittest, captured_stdout +from test.support import run_unittest, captured_stdout, check_impl_detail import difflib import unittest import sys @@ -253,7 +253,8 @@ def test_disassemble_str(self): self.do_disassembly_test(expr_str, dis_expr_str) self.do_disassembly_test(simple_stmt_str, dis_simple_stmt_str) - self.do_disassembly_test(compound_stmt_str, dis_compound_stmt_str) + if check_impl_detail(): + self.do_disassembly_test(compound_stmt_str, dis_compound_stmt_str) def test_disassemble_bytes(self): self.do_disassembly_test(_f.__code__.co_code, dis_f_co_code) 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 @@ -24,14 +24,14 @@ "_codecs", "atexit", "gc", "_weakref", "marshal", "errno", "imp", "itertools", "math", "cmath", "_sre", "_pickle_support", "operator", "parser", "symbol", "token", "_ast", "_random", "__pypy__", - "_string", "_testing" + "_string", "_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", "_minimal_curses", "thread", "itertools", "pyexpat", "_ssl", "cpyext", "array", 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 @@ -20,5 +20,20 @@ .. branch: run-create_cffi_imports Build cffi import libraries as part of translation by monkey-patching an -aditional task into translation +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 @@ -303,7 +303,12 @@ 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 @@ -342,8 +347,6 @@ driver.default_goal = 'build_cffi_imports' # HACKHACKHACK end - return self.get_entry_point(config) - def jitpolicy(self, driver): from pypy.module.pypyjit.policy import PyPyJitPolicy from pypy.module.pypyjit.hooks import pypy_hooks diff --git a/pypy/interpreter/error.py b/pypy/interpreter/error.py --- a/pypy/interpreter/error.py +++ b/pypy/interpreter/error.py @@ -277,7 +277,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): @@ -233,7 +239,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/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 @@ -530,6 +530,22 @@ del sys.modules['mod'] + def test_pickle_generator_crash(self): + import pickle + + def f(): + yield 0 + + x = f() + next(x) + try: + next(x) + 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 +@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 - - -@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/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 @@ -353,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: @@ -446,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 @@ -187,7 +187,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 @@ -207,14 +207,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: 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) + +# ____________________________________________________________ + +@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 @@ -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,7 @@ 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__': @@ -206,14 +217,18 @@ 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): 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 @@ -3341,6 +3402,29 @@ 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[]", b"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[]", b"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 """) @@ -500,28 +500,33 @@ "int foo(int x) { return x + 32; }") assert lib.foo(10) == 42 - def test_bad_size_of_global_1(self): - ffi, lib = self.prepare( - "short glob;", - "test_bad_size_of_global_1", - "long glob;") - raises(ffi.error, getattr, lib, "glob") - - def test_bad_size_of_global_2(self): - ffi, lib = self.prepare( - "int glob[10];", - "test_bad_size_of_global_2", - "int glob[9];") - e = raises(ffi.error, getattr, lib, "glob") - assert str(e.value) == ("global variable 'glob' should be 40 bytes " - "according to the cdef, but is actually 36") - - def test_unspecified_size_of_global(self): + def test_unspecified_size_of_global_1(self): ffi, lib = self.prepare( "int glob[];", - "test_unspecified_size_of_global", + "test_unspecified_size_of_global_1", "int glob[10];") - lib.glob # does not crash + assert ffi.typeof(lib.glob) == ffi.typeof("int *") + + def test_unspecified_size_of_global_2(self): + ffi, lib = self.prepare( + "int glob[][5];", + "test_unspecified_size_of_global_2", + "int glob[10][5];") + assert ffi.typeof(lib.glob) == ffi.typeof("int(*)[5]") + + def test_unspecified_size_of_global_3(self): + ffi, lib = self.prepare( + "int glob[][...];", + "test_unspecified_size_of_global_3", + "int glob[10][5];") + assert ffi.typeof(lib.glob) == ffi.typeof("int(*)[5]") + + def test_unspecified_size_of_global_4(self): + ffi, lib = self.prepare( + "int glob[...][...];", + "test_unspecified_size_of_global_4", + "int glob[10][5];") + assert ffi.typeof(lib.glob) == ffi.typeof("int[10][5]") def test_include_1(self): ffi1, lib1 = self.prepare( @@ -869,11 +874,22 @@ """) assert lib.almost_forty_two == 42.25 + def test_constant_of_unknown_size(self): + ffi, lib = self.prepare( + "typedef ... opaque_t;" + "const opaque_t CONSTANT;", + 'test_constant_of_unknown_size', + "typedef int opaque_t;" + "const int CONSTANT = 42;") + e = raises(ffi.error, getattr, lib, 'CONSTANT') + assert str(e.value) == ("constant 'CONSTANT' is of " + "type 'opaque_t', whose size is not known") + def test_variable_of_unknown_size(self): ffi, lib = self.prepare(""" typedef ... opaque_t; opaque_t globvar; - """, 'test_constant_of_unknown_size', """ + """, 'test_variable_of_unknown_size', """ typedef char opaque_t[6]; opaque_t globvar = "hello"; """) @@ -1012,3 +1028,51 @@ assert hasattr(lib, '__dict__') assert lib.__all__ == ['MYFOO', 'mybar'] # but not 'myvar' assert lib.__name__ == repr(lib) + + def test_macro_var_callback(self): + ffi, lib = self.prepare( + "int my_value; int *(*get_my_value)(void);", + 'test_macro_var_callback', + "int *(*get_my_value)(void);\n" + "#define my_value (*get_my_value())") + # + values = ffi.new("int[50]") + def it(): + for i in range(50): + yield i + it = it() + # + @ffi.callback("int *(*)(void)") + def get_my_value(): + return values + it.next() + lib.get_my_value = get_my_value + # + values[0] = 41 + assert lib.my_value == 41 # [0] + p = ffi.addressof(lib, 'my_value') # [1] + assert p == values + 1 + assert p[-1] == 41 + assert p[+1] == 0 + lib.my_value = 42 # [2] + assert values[2] == 42 + assert p[-1] == 41 + assert p[+1] == 42 + # + # if get_my_value raises or returns nonsense, the exception is printed + # to stderr like with any callback, but then the C expression 'my_value' + # expand to '*NULL'. We assume here that '&my_value' will return NULL + # without segfaulting, and check for NULL when accessing the variable. + @ffi.callback("int *(*)(void)") + def get_my_value(): + raise LookupError + lib.get_my_value = get_my_value + raises(ffi.error, getattr, lib, 'my_value') + raises(ffi.error, setattr, lib, 'my_value', 50) + raises(ffi.error, ffi.addressof, lib, 'my_value') + @ffi.callback("int *(*)(void)") + def get_my_value(): + return "hello" + lib.get_my_value = get_my_value + raises(ffi.error, getattr, lib, 'my_value') + e = raises(ffi.error, setattr, lib, 'my_value', 50) + assert str(e.value) == "global variable 'my_value' is at address NULL" diff --git a/pypy/module/_cffi_backend/wrapper.py b/pypy/module/_cffi_backend/wrapper.py --- a/pypy/module/_cffi_backend/wrapper.py +++ b/pypy/module/_cffi_backend/wrapper.py @@ -9,6 +9,7 @@ from pypy.module._cffi_backend.ctypeptr import W_CTypePtrOrArray from pypy.module._cffi_backend.ctypefunc import W_CTypeFunc from pypy.module._cffi_backend.ctypestruct import W_CTypeStructOrUnion +from pypy.module._cffi_backend import allocator class W_FunctionWrapper(W_Root): @@ -74,7 +75,8 @@ # then internally allocate the struct and pass a pointer to it as # a first argument. if locs[0] == 'R': - w_result_cdata = ctype.fargs[0].newp(space.w_None) + w_result_cdata = ctype.fargs[0].newp(space.w_None, + allocator.nonzero_allocator) args_w = [w_result_cdata] + args_w prepare_args(space, rawfunctype, args_w, 1) # @@ -116,7 +118,7 @@ # the equivalent of ffi.new() if space.is_w(w_arg, space.w_None): continue - w_arg = farg.newp(w_arg) + w_arg = farg.newp(w_arg, allocator.default_allocator) args_w[i] = w_arg diff --git a/pypy/module/_vmprof/interp_vmprof.py b/pypy/module/_vmprof/interp_vmprof.py --- a/pypy/module/_vmprof/interp_vmprof.py +++ b/pypy/module/_vmprof/interp_vmprof.py @@ -23,11 +23,16 @@ # running make inside the src dir DYNAMIC_VMPROF = False +if sys.platform.startswith('linux'): + libs = ['dl'] +else: + libs = [] + eci_kwds = dict( include_dirs = [SRC], includes = ['vmprof.h', 'trampoline.h'], separate_module_files = [SRC.join('trampoline.vmprof.s')], - libraries = ['dl'], + libraries = libs, post_include_bits=[""" int pypy_vmprof_init(void); diff --git a/pypy/module/_vmprof/src/config.h b/pypy/module/_vmprof/src/config.h --- a/pypy/module/_vmprof/src/config.h +++ b/pypy/module/_vmprof/src/config.h @@ -1,2 +1,6 @@ #define HAVE_SYS_UCONTEXT_H +#if defined(__FreeBSD__) || defined(__APPLE__) +#define PC_FROM_UCONTEXT uc_mcontext.mc_rip +#else #define PC_FROM_UCONTEXT uc_mcontext.gregs[REG_RIP] +#endif diff --git a/pypy/module/_vmprof/src/getpc.h b/pypy/module/_vmprof/src/getpc.h --- a/pypy/module/_vmprof/src/getpc.h +++ b/pypy/module/_vmprof/src/getpc.h @@ -132,7 +132,7 @@ } }; -inline void* GetPC(ucontext_t *signal_ucontext) { +void* GetPC(ucontext_t *signal_ucontext) { // See comment above struct CallUnrollInfo. Only try instruction // flow matching if both eip and esp looks reasonable. const int eip = signal_ucontext->uc_mcontext.gregs[REG_EIP]; @@ -168,7 +168,7 @@ typedef int ucontext_t; #endif -inline void* GetPC(ucontext_t *signal_ucontext) { +void* GetPC(ucontext_t *signal_ucontext) { RAW_LOG(ERROR, "GetPC is not yet implemented on Windows\n"); return NULL; } @@ -178,7 +178,7 @@ // the right value for your system, and add it to the list in // configure.ac (or set it manually in your config.h). #else -inline void* GetPC(ucontext_t *signal_ucontext) { +void* GetPC(ucontext_t *signal_ucontext) { return (void*)signal_ucontext->PC_FROM_UCONTEXT; // defined in config.h } diff --git a/pypy/module/_vmprof/src/vmprof.c b/pypy/module/_vmprof/src/vmprof.c --- a/pypy/module/_vmprof/src/vmprof.c +++ b/pypy/module/_vmprof/src/vmprof.c @@ -33,6 +33,9 @@ //#include <libunwind.h> #include "vmprof.h" +#if defined(__FreeBSD__) || defined(__APPLE__) +#define sighandler_t sig_t +#endif #define _unused(x) ((void)x) @@ -259,13 +262,31 @@ int marker = MARKER_TRAILER; write(profile_file, &marker, 1); +#ifdef __linux__ // copy /proc/PID/maps to the end of the profile file sprintf(buf, "/proc/%d/maps", getpid()); - src = fopen(buf, "r"); + src = fopen(buf, "r"); + if (!src) { + vmprof_error = "error opening proc maps"; + return -1; + } while ((size = fread(buf, 1, BUFSIZ, src))) { write(profile_file, buf, size); } fclose(src); +#else + // freebsd and mac + sprintf(buf, "procstat -v %d", getpid()); + src = popen(buf, "r"); + if (!src) { + vmprof_error = "error calling procstat"; + return -1; + } + while ((size = fread(buf, 1, BUFSIZ, src))) { + write(profile_file, buf, size); + } + pclose(src); +#endif close(profile_file); return 0; } diff --git a/pypy/module/imp/test/test_import.py b/pypy/module/imp/test/test_import.py --- a/pypy/module/imp/test/test_import.py +++ b/pypy/module/imp/test/test_import.py @@ -72,6 +72,14 @@ b = "insubpackage = 1", ) setuppkg("pkg.pkg2", a='', b='') + setuppkg("pkg.withall", + __init__ = "__all__ = ['foobar']", + foobar = "found = 123") + setuppkg("pkg.withoutall", + __init__ = "", + foobar = "found = 123") + setuppkg("pkg.bogusall", + __init__ = "__all__ = 42") setuppkg("pkg_r", inpkg = "import x.y") setuppkg("pkg_r.x", y='') setuppkg("x") @@ -697,6 +705,32 @@ import imp raises(ValueError, imp.load_module, "", "", "", [1, 2, 3, 4]) + def test_import_star_finds_submodules_with___all__(self): + for case in ["not-imported-yet", "already-imported"]: + d = {} + exec("from pkg.withall import *", d) + assert d["foobar"].found == 123 + + def test_import_star_does_not_find_submodules_without___all__(self): + for case in ["not-imported-yet", "already-imported"]: + d = {} + exec("from pkg.withoutall import *", d) + assert "foobar" not in d + import pkg.withoutall.foobar # <- import it here only + for case in ["not-imported-yet", "already-imported"]: + d = {} + exec("from pkg.withoutall import *", d) + assert d["foobar"].found == 123 + + def test_import_star_with_bogus___all__(self): + for case in ["not-imported-yet", "already-imported"]: + try: + exec("from pkg.bogusall import *", {}) + except TypeError: + pass # 'int' object does not support indexing + else: + raise AssertionError("should have failed") + def test_source_encoding(self): import imp import encoded diff --git a/pypy/module/micronumpy/boxes.py b/pypy/module/micronumpy/boxes.py --- a/pypy/module/micronumpy/boxes.py +++ b/pypy/module/micronumpy/boxes.py @@ -196,7 +196,12 @@ "'%T' object is not iterable", self) def descr_str(self, space): - return space.wrap(self.get_dtype(space).itemtype.str_format(self, add_quotes=False)) + tp = self.get_dtype(space).itemtype + return space.wrap(tp.str_format(self, add_quotes=False)) + + def descr_repr(self, space): + tp = self.get_dtype(space).itemtype + return space.wrap(tp.str_format(self, add_quotes=True)) def descr_format(self, space, w_spec): return space.format(self.item(space), w_spec) @@ -607,16 +612,25 @@ return W_StringBox(arr, 0, arr.dtype) class W_UnicodeBox(W_CharacterBox): + def __init__(self, value): + self._value = value + + def convert_to(self, space, dtype): + if dtype.is_unicode(): + return self + elif dtype.is_object(): + return W_ObjectBox(space.wrap(self._value)) + else: + raise oefmt(space.w_NotImplementedError, + "Conversion from unicode not implemented yet") + + def get_dtype(self, space): + from pypy.module.micronumpy.descriptor import new_unicode_dtype + return new_unicode_dtype(space, len(self._value)) + def descr__new__unicode_box(space, w_subtype, w_arg): - raise oefmt(space.w_NotImplementedError, "Unicode is not supported yet") - from pypy.module.micronumpy.descriptor import new_unicode_dtype - arg = space.unicode_w(space.unicode_from_object(w_arg)) _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit