Author: Armin Rigo <ar...@tunes.org> Branch: cffi-callback-onerror Changeset: r78432:088ea8f70900 Date: 2015-07-04 23:19 +0200 http://bitbucket.org/pypy/pypy/changeset/088ea8f70900/
Log: hg merge default diff too long, truncating to 2000 out of 4366 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 @@ -328,6 +328,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 @@ -429,6 +436,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/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/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/whatsnew-head.rst b/pypy/doc/whatsnew-head.rst --- a/pypy/doc/whatsnew-head.rst +++ b/pypy/doc/whatsnew-head.rst @@ -11,3 +11,14 @@ .. 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 +aditional task into translation + 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 @@ -298,6 +298,44 @@ wrapstr = 'space.wrap(%r)' % (options) pypy.module.sys.Module.interpleveldefs['pypy_translation_info'] = wrapstr + # 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 + return self.get_entry_point(config) def jitpolicy(self, driver): diff --git a/pypy/interpreter/app_main.py b/pypy/interpreter/app_main.py --- a/pypy/interpreter/app_main.py +++ b/pypy/interpreter/app_main.py @@ -40,6 +40,11 @@ PYPYLOG: If set to a non-empty value, enable logging. """ +try: + from __pypy__ import get_hidden_tb, hidden_applevel +except ImportError: + get_hidden_tb = lambda: sys.exc_info()[2] + hidden_applevel = lambda f: f import sys DEBUG = False # dump exceptions before calling the except hook @@ -63,6 +68,7 @@ exitcode = 1 raise SystemExit(exitcode) +@hidden_applevel def run_toplevel(f, *fargs, **fkwds): """Calls f() and handles all OperationErrors. Intended use is to run the main program or one interactive statement. @@ -87,13 +93,13 @@ except SystemExit as e: handle_sys_exit(e) - except: - display_exception() + except BaseException as e: + display_exception(e) return False return True # success -def display_exception(): - etype, evalue, etraceback = sys.exc_info() +def display_exception(e): + etype, evalue, etraceback = type(e), e, get_hidden_tb() try: # extra debugging info in case the code below goes very wrong if DEBUG and hasattr(sys, 'stderr'): @@ -119,11 +125,11 @@ hook(etype, evalue, etraceback) return # done - except: + except BaseException as e: try: stderr = sys.stderr print >> stderr, 'Error calling sys.excepthook:' - originalexcepthook(*sys.exc_info()) + originalexcepthook(type(e), e, e.__traceback__) print >> stderr print >> stderr, 'Original exception was:' except: @@ -509,6 +515,7 @@ return options +@hidden_applevel def run_command_line(interactive, inspect, run_command, @@ -597,6 +604,7 @@ # Put '' on sys.path sys.path.insert(0, '') + @hidden_applevel def run_it(): exec run_command in mainmodule.__dict__ success = run_toplevel(run_it) @@ -634,6 +642,7 @@ print >> sys.stderr, "Could not open PYTHONSTARTUP" print >> sys.stderr, "IOError:", e else: + @hidden_applevel def run_it(): co_python_startup = compile(startup, python_startup, @@ -650,6 +659,7 @@ inspect = True else: # If not interactive, just read and execute stdin normally. + @hidden_applevel def run_it(): co_stdin = compile(sys.stdin.read(), '<stdin>', 'exec', PyCF_ACCEPT_NULL_BYTES) @@ -689,7 +699,7 @@ except SystemExit as e: status = e.code if inspect_requested(): - display_exception() + display_exception(e) else: status = not success @@ -743,6 +753,7 @@ # This is important for py3k sys.executable = executable +@hidden_applevel def entry_point(executable, argv): # note that before calling setup_bootstrap_path, we are limited because we # cannot import stdlib modules. In particular, we cannot use unicode diff --git a/pypy/interpreter/executioncontext.py b/pypy/interpreter/executioncontext.py --- a/pypy/interpreter/executioncontext.py +++ b/pypy/interpreter/executioncontext.py @@ -1,6 +1,7 @@ import sys from pypy.interpreter.error import OperationError, get_cleared_operation_error from rpython.rlib.unroll import unrolling_iterable +from rpython.rlib.objectmodel import specialize from rpython.rlib import jit TICK_COUNTER_STEP = 100 @@ -214,13 +215,21 @@ self._trace(frame, 'exception', None, operationerr) #operationerr.print_detailed_traceback(self.space) - def sys_exc_info(self): # attn: the result is not the wrapped sys.exc_info() !!! + @specialize.arg(1) + def sys_exc_info(self, for_hidden=False): """Implements sys.exc_info(). - Return an OperationError instance or None.""" + Return an OperationError instance or None. + + Ignores exceptions within hidden frames unless for_hidden=True + is specified. + + # NOTE: the result is not the wrapped sys.exc_info() !!! + + """ frame = self.gettopframe() while frame: if frame.last_exception is not None: - if (not frame.hide() or + if ((for_hidden or not frame.hide()) or frame.last_exception is get_cleared_operation_error(self.space)): return frame.last_exception 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/__pypy__/__init__.py b/pypy/module/__pypy__/__init__.py --- a/pypy/module/__pypy__/__init__.py +++ b/pypy/module/__pypy__/__init__.py @@ -71,6 +71,8 @@ 'debug_print_once' : 'interp_debug.debug_print_once', 'debug_flush' : 'interp_debug.debug_flush', 'builtinify' : 'interp_magic.builtinify', + 'hidden_applevel' : 'interp_magic.hidden_applevel', + 'get_hidden_tb' : 'interp_magic.get_hidden_tb', 'lookup_special' : 'interp_magic.lookup_special', 'do_what_I_mean' : 'interp_magic.do_what_I_mean', 'validate_fd' : 'interp_magic.validate_fd', diff --git a/pypy/module/__pypy__/interp_magic.py b/pypy/module/__pypy__/interp_magic.py --- a/pypy/module/__pypy__/interp_magic.py +++ b/pypy/module/__pypy__/interp_magic.py @@ -59,6 +59,20 @@ bltn = BuiltinFunction(func) return space.wrap(bltn) +def hidden_applevel(space, w_func): + """Decorator that hides a function's frame from app-level""" + from pypy.interpreter.function import Function + func = space.interp_w(Function, w_func) + func.getcode().hidden_applevel = True + return w_func + +def get_hidden_tb(space): + """Return the traceback of the current exception being handled by a + frame hidden from applevel. + """ + operr = space.getexecutioncontext().sys_exc_info(for_hidden=True) + return space.w_None if operr is None else space.wrap(operr.get_traceback()) + @unwrap_spec(meth=str) def lookup_special(space, w_obj, meth): """Lookup up a special method on an object.""" diff --git a/pypy/module/__pypy__/test/test_special.py b/pypy/module/__pypy__/test/test_special.py --- a/pypy/module/__pypy__/test/test_special.py +++ b/pypy/module/__pypy__/test/test_special.py @@ -27,6 +27,52 @@ assert A.a is not A.__dict__['a'] assert A.b is A.__dict__['b'] + def test_hidden_applevel(self): + import __pypy__ + import sys + + @__pypy__.hidden_applevel + def sneak(): (lambda: 1/0)() + try: + sneak() + except ZeroDivisionError as e: + tb = sys.exc_info()[2] + assert tb.tb_frame == sys._getframe() + assert tb.tb_next.tb_frame.f_code.co_name == '<lambda>' + else: + assert False, 'Expected ZeroDivisionError' + + def test_hidden_applevel_frames(self): + import __pypy__ + import sys + + @__pypy__.hidden_applevel + def test_hidden(): + assert sys._getframe().f_code.co_name != 'test_hidden' + def e(): 1/0 + try: e() + except ZeroDivisionError as e: + assert sys.exc_info() == (None, None, None) + else: assert False + return 2 + assert test_hidden() == 2 + + def test_get_hidden_tb(self): + import __pypy__ + import sys + + @__pypy__.hidden_applevel + def test_hidden_with_tb(): + def not_hidden(): 1/0 + try: not_hidden() + except ZeroDivisionError as e: + assert sys.exc_info() == (None, None, None) + tb = __pypy__.get_hidden_tb() + assert tb.tb_frame.f_code.co_name == 'not_hidden' + return True + else: return False + assert test_hidden_with_tb() + def test_lookup_special(self): from __pypy__ import lookup_special class X(object): 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): @@ -37,6 +37,7 @@ 'from_handle': 'handle.from_handle', '_get_types': 'func._get_types', 'from_buffer': 'func.from_buffer', + 'gcp': 'func.gcp', 'string': 'func.string', 'buffer': 'cbuffer.buffer', 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/cglob.py b/pypy/module/_cffi_backend/cglob.py --- a/pypy/module/_cffi_backend/cglob.py +++ b/pypy/module/_cffi_backend/cglob.py @@ -2,23 +2,38 @@ 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 + +FNPTR = rffi.CCallback([], rffi.VOIDP) class W_GlobSupport(W_Root): - def __init__(self, space, w_ctype, ptr): + _immutable_fields_ = ['w_ctype', 'ptr', 'fetch_addr'] + + def __init__(self, space, w_ctype, ptr=lltype.nullptr(rffi.CCHARP.TO), + fetch_addr=lltype.nullptr(rffi.VOIDP.TO)): self.space = space self.w_ctype = w_ctype self.ptr = ptr + self.fetch_addr = rffi.cast(FNPTR, fetch_addr) + + def fetch_global_var_addr(self): + if self.ptr: + return self.ptr + result = self.fetch_addr() + return rffi.cast(rffi.CCHARP, 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 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/ctypeprim.py b/pypy/module/_cffi_backend/ctypeprim.py --- a/pypy/module/_cffi_backend/ctypeprim.py +++ b/pypy/module/_cffi_backend/ctypeprim.py @@ -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: 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 @@ -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/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 @@ -542,13 +542,18 @@ @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) +def W_FFIObject___new__(space, w_subtype, __args__): + return make_plain_ffi_object(space, w_subtype) + def make_CData(space): return space.gettypefor(W_CData) 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 @@ -105,3 +105,18 @@ "raw address on PyPy", w_x) # return cdataobj.W_CDataFromBuffer(space, _cdata, w_ctype, buf, w_x) + +# ____________________________________________________________ + +class ConstantFFI: + ffi1 = None + def _cleanup_(self): + self.ffi1 = None +constant_ffi = ConstantFFI() + +@unwrap_spec(w_cdata=cdataobj.W_CData) +def gcp(space, w_cdata, w_destructor): + if constant_ffi.ffi1 is None: + from pypy.module._cffi_backend import ffi_obj + constant_ffi.ffi1 = ffi_obj.make_plain_ffi_object(space) + return constant_ffi.ffi1.descr_gc(w_cdata, 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, 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, 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 @@ -2099,8 +2099,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 +3332,15 @@ 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_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 @@ -4,6 +4,8 @@ spaceconfig = dict(usemodules=('_cffi_backend', 'array')) def teardown_method(self, meth): + from pypy.module._cffi_backend.func import constant_ffi + constant_ffi._cleanup_() _clean_cache(self.space) def test_ffi_new(self): @@ -234,9 +236,10 @@ assert p1[0] == 123 seen.append(1) ffi.gc(p, destructor=destructor) # instantly forgotten + _cffi1_backend.gcp(p, destructor=destructor) for i in range(5): if seen: break import gc gc.collect() - assert seen == [1] + assert seen == [1, 1] 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__ == { + 'ff': lib.ff, + 'aa': aaobj, + 'my_constant': -45} + lib.__dict__['ff'] = "??" + assert lib.ff(10) == 15 def test_verify_opaque_struct(self): ffi, lib = self.prepare( @@ -491,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( @@ -819,6 +833,22 @@ assert isinstance(addr, ffi.CData) assert ffi.typeof(addr) == ffi.typeof("long(*)(long)") + def test_address_of_function_with_struct(self): + ffi, lib = self.prepare( + "struct foo_s { int x; }; long myfunc(struct foo_s);", + "test_addressof_function_with_struct", """ + struct foo_s { int x; }; + char myfunc(struct foo_s input) { return (char)(input.x + 42); } + """) + s = ffi.new("struct foo_s *", [5])[0] + assert lib.myfunc(s) == 47 + assert not isinstance(lib.myfunc, ffi.CData) + assert ffi.typeof(lib.myfunc) == ffi.typeof("long(*)(struct foo_s)") + addr = ffi.addressof(lib, 'myfunc') + assert addr(s) == 47 + assert isinstance(addr, ffi.CData) + assert ffi.typeof(addr) == ffi.typeof("long(*)(struct foo_s)") + def test_issue198(self): ffi, lib = self.prepare(""" typedef struct{...;} opaque_t; @@ -844,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"; """) @@ -984,5 +1025,35 @@ assert sys.modules['_CFFI_test_import_from_lib.lib'] is lib from _CFFI_test_import_from_lib.lib import MYFOO assert MYFOO == 42 - assert not hasattr(lib, '__dict__') + 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 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 @@ -19,12 +19,20 @@ wrapper is callable, and the arguments it expects and returns are directly the struct/union. Calling ffi.typeof(wrapper) also returns the original struct/union signature. + + This class cannot be used for variadic functions. """ _immutable_ = True common_doc_str = 'direct call to the C function of the same name' - def __init__(self, space, fnptr, directfnptr, ctype, - locs, rawfunctype, fnname, modulename): + def __init__(self, space, fnptr, directfnptr, + rawfunctype, fnname, modulename): + # everything related to the type of the function is accessed + # as immutable attributes of the 'rawfunctype' object, which + # is a W_RawFuncType. This gives us an obvious thing to + # promote in order to do the call. + ctype = rawfunctype.nostruct_ctype + locs = rawfunctype.nostruct_locs assert isinstance(ctype, W_CTypeFunc) assert ctype.cif_descr is not None # not for '...' functions assert locs is None or len(ctype.fargs) == len(locs) @@ -32,83 +40,86 @@ self.space = space self.fnptr = fnptr self.directfnptr = directfnptr - self.ctype = ctype - self.locs = locs self.rawfunctype = rawfunctype self.fnname = fnname self.modulename = modulename - self.nargs_expected = len(ctype.fargs) - (locs is not None and - locs[0] == 'R') def typeof(self, ffi): return self.rawfunctype.unwrap_as_fnptr(ffi) - @jit.unroll_safe - def _prepare(self, args_w, start_index): - # replaces struct/union arguments with ptr-to-struct/union arguments + def descr_call(self, args_w): space = self.space - locs = self.locs - fargs = self.ctype.fargs - for i in range(start_index, len(locs)): - if locs[i] != 'A': - continue - w_arg = args_w[i] - farg = fargs[i] # <ptr to struct/union> - assert isinstance(farg, W_CTypePtrOrArray) - if isinstance(w_arg, W_CData) and w_arg.ctype is farg.ctitem: - # fast way: we are given a W_CData "struct", so just make - # a new W_CData "ptr-to-struct" which points to the same - # raw memory. We use unsafe_escaping_ptr(), so we have to - # make sure the original 'w_arg' stays alive; the easiest - # is to build an instance of W_CDataPtrToStructOrUnion. - w_arg = W_CDataPtrToStructOrUnion( - space, w_arg.unsafe_escaping_ptr(), farg, w_arg) - else: - # slow way: build a new "ptr to struct" W_CData by calling - # the equivalent of ffi.new() - if space.is_w(w_arg, space.w_None): - continue - w_arg = farg.newp(w_arg) - args_w[i] = w_arg - - def descr_call(self, args_w): - if len(args_w) != self.nargs_expected: - space = self.space - if self.nargs_expected == 0: + rawfunctype = jit.promote(self.rawfunctype) + ctype = rawfunctype.nostruct_ctype + locs = rawfunctype.nostruct_locs + nargs_expected = rawfunctype.nostruct_nargs + # + if len(args_w) != nargs_expected: + if nargs_expected == 0: raise oefmt(space.w_TypeError, "%s() takes no arguments (%d given)", self.fnname, len(args_w)) - elif self.nargs_expected == 1: + elif nargs_expected == 1: raise oefmt(space.w_TypeError, "%s() takes exactly one argument (%d given)", self.fnname, len(args_w)) else: raise oefmt(space.w_TypeError, "%s() takes exactly %d arguments (%d given)", - self.fnname, self.nargs_expected, len(args_w)) + self.fnname, nargs_expected, len(args_w)) # - if self.locs is not None: + if locs is not None: # This case is if there are structs as arguments or return values. # If the result we want to present to the user is "returns struct", # then internally allocate the struct and pass a pointer to it as # a first argument. - if self.locs[0] == 'R': - w_result_cdata = self.ctype.fargs[0].newp(self.space.w_None) + if locs[0] == 'R': + w_result_cdata = ctype.fargs[0].newp(space.w_None) args_w = [w_result_cdata] + args_w - self._prepare(args_w, 1) - self.ctype._call(self.fnptr, args_w) # returns w_None + prepare_args(space, rawfunctype, args_w, 1) + # + ctype._call(self.fnptr, args_w) # returns w_None + # assert isinstance(w_result_cdata, W_CDataPtrToStructOrUnion) return w_result_cdata.structobj else: args_w = args_w[:] - self._prepare(args_w, 0) + prepare_args(space, rawfunctype, args_w, 0) # - return self.ctype._call(self.fnptr, args_w) + return ctype._call(self.fnptr, args_w) def descr_repr(self, space): return space.wrap("<FFIFunctionWrapper for %s()>" % (self.fnname,)) +@jit.unroll_safe +def prepare_args(space, rawfunctype, args_w, start_index): + # replaces struct/union arguments with ptr-to-struct/union arguments + locs = rawfunctype.nostruct_locs + fargs = rawfunctype.nostruct_ctype.fargs + for i in range(start_index, len(locs)): + if locs[i] != 'A': + continue + w_arg = args_w[i] + farg = fargs[i] # <ptr to struct/union> + assert isinstance(farg, W_CTypePtrOrArray) + if isinstance(w_arg, W_CData) and w_arg.ctype is farg.ctitem: + # fast way: we are given a W_CData "struct", so just make + # a new W_CData "ptr-to-struct" which points to the same + # raw memory. We use unsafe_escaping_ptr(), so we have to + # make sure the original 'w_arg' stays alive; the easiest + # is to build an instance of W_CDataPtrToStructOrUnion. + w_arg = W_CDataPtrToStructOrUnion( + space, w_arg.unsafe_escaping_ptr(), farg, w_arg) + else: + # slow way: build a new "ptr to struct" W_CData by calling + # the equivalent of ffi.new() + if space.is_w(w_arg, space.w_None): + continue + w_arg = farg.newp(w_arg) + args_w[i] = w_arg + + W_FunctionWrapper.typedef = TypeDef( 'FFIFunctionWrapper', __repr__ = interp2app(W_FunctionWrapper.descr_repr), diff --git a/pypy/module/_file/interp_file.py b/pypy/module/_file/interp_file.py --- a/pypy/module/_file/interp_file.py +++ b/pypy/module/_file/interp_file.py @@ -613,7 +613,7 @@ # ____________________________________________________________ def wrap_list_of_str(space, lst): - return space.newlist([space.wrap(s) for s in lst]) + return space.newlist_bytes(lst) class FileState: def __init__(self, space): diff --git a/pypy/module/_io/interp_textio.py b/pypy/module/_io/interp_textio.py --- a/pypy/module/_io/interp_textio.py +++ b/pypy/module/_io/interp_textio.py @@ -600,6 +600,7 @@ def read_w(self, space, w_size=None): self._check_attached(space) + self._check_closed(space) if not self.w_decoder: raise OperationError(space.w_IOError, space.wrap("not readable")) @@ -641,6 +642,7 @@ def readline_w(self, space, w_limit=None): self._check_attached(space) + self._check_closed(space) self._writeflush(space) limit = convert_size(space, w_limit) @@ -736,7 +738,7 @@ def write_w(self, space, w_text): self._check_attached(space) - # self._check_closed(space) + self._check_closed(space) if not self.w_encoder: raise OperationError(space.w_IOError, space.wrap("not writable")) diff --git a/pypy/module/_io/test/test_io.py b/pypy/module/_io/test/test_io.py --- a/pypy/module/_io/test/test_io.py +++ b/pypy/module/_io/test/test_io.py @@ -391,3 +391,57 @@ f.seek(1, 0) f.read(buffer_size * 2) assert f.tell() == 1 + buffer_size * 2 + + +class AppTestIoAferClose: + spaceconfig = dict(usemodules=['_io']) + + def setup_class(cls): + tmpfile = udir.join('tmpfile').ensure() + cls.w_tmpfile = cls.space.wrap(str(tmpfile)) + + def test_io_after_close(self): + import _io + for kwargs in [ + {"mode": "w"}, + {"mode": "wb"}, + {"mode": "w", "buffering": 1}, + {"mode": "w", "buffering": 2}, + {"mode": "wb", "buffering": 0}, + {"mode": "r"}, + {"mode": "rb"}, + {"mode": "r", "buffering": 1}, + {"mode": "r", "buffering": 2}, + {"mode": "rb", "buffering": 0}, + {"mode": "w+"}, + {"mode": "w+b"}, + {"mode": "w+", "buffering": 1}, + {"mode": "w+", "buffering": 2}, + {"mode": "w+b", "buffering": 0}, + ]: + print kwargs + if "b" not in kwargs["mode"]: + kwargs["encoding"] = "ascii" + f = _io.open(self.tmpfile, **kwargs) + f.close() + raises(ValueError, f.flush) + raises(ValueError, f.fileno) + raises(ValueError, f.isatty) + raises(ValueError, f.__iter__) + if hasattr(f, "peek"): + raises(ValueError, f.peek, 1) + raises(ValueError, f.read) + if hasattr(f, "read1"): + raises(ValueError, f.read1, 1024) + if hasattr(f, "readall"): + raises(ValueError, f.readall) + if hasattr(f, "readinto"): + raises(ValueError, f.readinto, bytearray(1024)) + raises(ValueError, f.readline) + raises(ValueError, f.readlines) + raises(ValueError, f.seek, 0) + raises(ValueError, f.tell) + raises(ValueError, f.truncate) + raises(ValueError, f.write, b"" if "b" in kwargs['mode'] else u"") + raises(ValueError, f.writelines, []) + raises(ValueError, next, f) diff --git a/pypy/module/_rawffi/callback.py b/pypy/module/_rawffi/callback.py --- a/pypy/module/_rawffi/callback.py +++ b/pypy/module/_rawffi/callback.py @@ -27,8 +27,10 @@ callback_ptr = global_counter.get(userdata.addarg) w_callable = callback_ptr.w_callable argtypes = callback_ptr.argtypes + must_leave = False space = callback_ptr.space try: + must_leave = space.threadlocals.try_enter_thread(space) args_w = [None] * len(argtypes) for i in range(len(argtypes)): argtype = argtypes[i] @@ -50,6 +52,8 @@ resshape = letter2tp(space, callback_ptr.result) for i in range(resshape.size): ll_res[i] = '\x00' + if must_leave: + space.threadlocals.leave_thread(space) class W_CallbackPtr(W_DataInstance): @@ -75,6 +79,14 @@ if tracker.DO_TRACING: addr = rffi.cast(lltype.Signed, self.ll_callback.ll_closure) tracker.trace_allocation(addr, self) + # + # We must setup the GIL here, in case the callback is invoked in + # some other non-Pythonic thread. This is the same as ctypes on + # CPython (but only when creating a callback; on CPython it occurs + # as soon as we import _ctypes) + if space.config.translation.thread: + from pypy.module.thread.os_thread import setup_threads + setup_threads(space) def free(self): if tracker.DO_TRACING: diff --git a/pypy/module/_socket/__init__.py b/pypy/module/_socket/__init__.py --- a/pypy/module/_socket/__init__.py +++ b/pypy/module/_socket/__init__.py @@ -18,6 +18,10 @@ from rpython.rlib.rsocket import rsocket_startup rsocket_startup() + def shutdown(self, space): + from pypy.module._socket.interp_socket import close_all_sockets + close_all_sockets(space) + def buildloaders(cls): from rpython.rlib import rsocket for name in """ diff --git a/pypy/module/_socket/interp_func.py b/pypy/module/_socket/interp_func.py --- a/pypy/module/_socket/interp_func.py +++ b/pypy/module/_socket/interp_func.py @@ -142,7 +142,7 @@ sock = rsocket.fromfd(fd, family, type, proto) except SocketError, e: raise converted_error(space, e) - return space.wrap(W_Socket(sock)) + return space.wrap(W_Socket(space, sock)) @unwrap_spec(family=int, type=int, proto=int) def socketpair(space, family=rsocket.socketpair_default_family, @@ -160,8 +160,8 @@ except SocketError, e: raise converted_error(space, e) return space.newtuple([ - space.wrap(W_Socket(sock1)), - space.wrap(W_Socket(sock2)) + space.wrap(W_Socket(space, sock1)), + space.wrap(W_Socket(space, sock2)) ]) # The following 4 functions refuse all negative numbers, like CPython 2.6. diff --git a/pypy/module/_socket/interp_socket.py b/pypy/module/_socket/interp_socket.py --- a/pypy/module/_socket/interp_socket.py +++ b/pypy/module/_socket/interp_socket.py @@ -1,4 +1,5 @@ -from rpython.rlib import rsocket +import sys +from rpython.rlib import rsocket, rweaklist from rpython.rlib.rarithmetic import intmask from rpython.rlib.rsocket import ( RSocket, AF_INET, SOCK_STREAM, SocketError, SocketErrorWithErrno, @@ -153,8 +154,9 @@ class W_Socket(W_Root): - def __init__(self, sock): + def __init__(self, space, sock): self.sock = sock + register_socket(space, sock) def get_type_w(self, space): return space.wrap(self.sock.type) @@ -183,7 +185,7 @@ fd, addr = self.sock.accept() sock = rsocket.make_socket( fd, self.sock.family, self.sock.type, self.sock.proto) - return space.newtuple([space.wrap(W_Socket(sock)), + return space.newtuple([space.wrap(W_Socket(space, sock)), addr_as_object(addr, sock.fd, space)]) except SocketError as e: raise converted_error(space, e) @@ -248,7 +250,7 @@ def dup_w(self, space): try: sock = self.sock.dup() - return W_Socket(sock) + return W_Socket(space, sock) except SocketError as e: raise converted_error(space, e) @@ -592,10 +594,50 @@ sock = RSocket(family, type, proto) except SocketError as e: raise converted_error(space, e) - W_Socket.__init__(self, sock) + W_Socket.__init__(self, space, sock) return space.wrap(self) descr_socket_new = interp2app(newsocket) + +# ____________________________________________________________ +# Automatic shutdown()/close() + +# On some systems, the C library does not guarantee that when the program +# finishes, all data sent so far is really sent even if the socket is not +# explicitly closed. This behavior has been observed on Windows but not +# on Linux, so far. +NEED_EXPLICIT_CLOSE = (sys.platform == 'win32') + +class OpenRSockets(rweaklist.RWeakListMixin): + pass +class OpenRSocketsState: + def __init__(self, space): + self.openrsockets = OpenRSockets() + self.openrsockets.initialize() + +def getopenrsockets(space): + if NEED_EXPLICIT_CLOSE and space.config.translation.rweakref: + return space.fromcache(OpenRSocketsState).openrsockets + else: + return None + +def register_socket(space, socket): + openrsockets = getopenrsockets(space) + if openrsockets is not None: + openrsockets.add_handle(socket) + +def close_all_sockets(space): + openrsockets = getopenrsockets(space) + if openrsockets is not None: + for sock_wref in openrsockets.get_all_handles(): + sock = sock_wref() + if sock is not None: + try: + sock.close() + except SocketError: + pass + + # ____________________________________________________________ # Error handling diff --git a/pypy/module/_socket/test/test_sock_app.py b/pypy/module/_socket/test/test_sock_app.py --- a/pypy/module/_socket/test/test_sock_app.py +++ b/pypy/module/_socket/test/test_sock_app.py @@ -309,10 +309,16 @@ class AppTestSocket: + spaceconfig = dict(usemodules=['_socket', '_weakref', 'struct']) + def setup_class(cls): cls.space = space cls.w_udir = space.wrap(str(udir)) + def teardown_class(cls): + if not cls.runappdirect: + cls.space.sys.getmodule('_socket').shutdown(cls.space) + def test_module(self): import _socket assert _socket.socket.__name__ == 'socket' @@ -614,6 +620,12 @@ finally: os.chdir(oldcwd) + def test_automatic_shutdown(self): + # doesn't really test anything, but at least should not explode + # in close_all_sockets() + import _socket + self.foo = _socket.socket() + class AppTestPacket: def setup_class(cls): diff --git a/pypy/module/_ssl/interp_ssl.py b/pypy/module/_ssl/interp_ssl.py --- a/pypy/module/_ssl/interp_ssl.py +++ b/pypy/module/_ssl/interp_ssl.py @@ -136,7 +136,7 @@ def __init__(self, ctx, protos): self.protos = protos self.buf, self.pinned, self.is_raw = rffi.get_nonmovingbuffer(protos) - NPN_STORAGE.set(r_uint(rffi.cast(rffi.UINT, self.buf)), self) + NPN_STORAGE.set(rffi.cast(lltype.Unsigned, self.buf), self) # set both server and client callbacks, because the context # can be used to create both types of sockets @@ -151,7 +151,7 @@ @staticmethod def advertiseNPN_cb(s, data_ptr, len_ptr, args): - npn = NPN_STORAGE.get(r_uint(rffi.cast(rffi.UINT, args))) + npn = NPN_STORAGE.get(rffi.cast(lltype.Unsigned, args)) if npn and npn.protos: data_ptr[0] = npn.buf len_ptr[0] = rffi.cast(rffi.UINT, len(npn.protos)) @@ -163,7 +163,7 @@ @staticmethod def selectNPN_cb(s, out_ptr, outlen_ptr, server, server_len, args): - npn = NPN_STORAGE.get(r_uint(rffi.cast(rffi.UINT, args))) + npn = NPN_STORAGE.get(rffi.cast(lltype.Unsigned, args)) if npn and npn.protos: client = npn.buf client_len = len(npn.protos) @@ -182,7 +182,7 @@ def __init__(self, ctx, protos): self.protos = protos self.buf, self.pinned, self.is_raw = rffi.get_nonmovingbuffer(protos) - ALPN_STORAGE.set(r_uint(rffi.cast(rffi.UINT, self.buf)), self) + ALPN_STORAGE.set(rffi.cast(lltype.Unsigned, self.buf), self) with rffi.scoped_str2charp(protos) as protos_buf: if libssl_SSL_CTX_set_alpn_protos( @@ -197,7 +197,7 @@ @staticmethod def selectALPN_cb(s, out_ptr, outlen_ptr, client, client_len, args): - alpn = ALPN_STORAGE.get(r_uint(rffi.cast(rffi.UINT, args))) + alpn = ALPN_STORAGE.get(rffi.cast(lltype.Unsigned, args)) if alpn and alpn.protos: server = alpn.buf server_len = len(alpn.protos) 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 @@ -26,7 +26,7 @@ eci_kwds = dict( include_dirs = [SRC], includes = ['vmprof.h', 'trampoline.h'], - separate_module_files = [SRC.join('trampoline.asmgcc.s')], + separate_module_files = [SRC.join('trampoline.vmprof.s')], libraries = ['dl'], post_include_bits=[""" diff --git a/pypy/module/_vmprof/src/trampoline.asmgcc.s b/pypy/module/_vmprof/src/trampoline.vmprof.s rename from pypy/module/_vmprof/src/trampoline.asmgcc.s rename to pypy/module/_vmprof/src/trampoline.vmprof.s --- a/pypy/module/_vmprof/src/trampoline.asmgcc.s +++ b/pypy/module/_vmprof/src/trampoline.vmprof.s @@ -1,7 +1,6 @@ // NOTE: you need to use TABs, not spaces! .text - .p2align 4,,-1 .globl pypy_execute_frame_trampoline .type pypy_execute_frame_trampoline, @function pypy_execute_frame_trampoline: 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 @@ -305,7 +305,6 @@ static int remove_sigprof_timer(void) { static struct itimerval timer; - last_period_usec = 0; timer.it_interval.tv_sec = 0; timer.it_interval.tv_usec = 0; timer.it_value.tv_sec = 0; @@ -317,11 +316,15 @@ } static void atfork_disable_timer(void) { - remove_sigprof_timer(); + if (last_period_usec) { + remove_sigprof_timer(); + } } static void atfork_enable_timer(void) { - install_sigprof_timer(last_period_usec); + if (last_period_usec) { + install_sigprof_timer(last_period_usec); + } } static int install_pthread_atfork_hooks(void) { @@ -412,6 +415,7 @@ if (remove_sigprof_timer() == -1) { return -1; } + last_period_usec = 0; if (remove_sigprof_handler() == -1) { return -1; } diff --git a/pypy/module/cpyext/buffer.py b/pypy/module/cpyext/buffer.py --- a/pypy/module/cpyext/buffer.py +++ b/pypy/module/cpyext/buffer.py @@ -38,4 +38,4 @@ 'C') or Fortran-style (fortran is 'F') contiguous or either one (fortran is 'A'). Return 0 otherwise.""" # PyPy only supports contiguous Py_buffers for now. - return space.wrap(1) + return 1 diff --git a/pypy/module/cpyext/test/test_version.py b/pypy/module/cpyext/test/test_version.py --- a/pypy/module/cpyext/test/test_version.py +++ b/pypy/module/cpyext/test/test_version.py @@ -16,7 +16,7 @@ } """ module = self.import_module(name='foo', init=init) - assert module.py_version == sys.version[:5] + assert module.py_version == '%d.%d.%d' % sys.version_info[:3] assert module.py_major_version == sys.version_info.major assert module.py_minor_version == sys.version_info.minor assert module.py_micro_version == sys.version_info.micro diff --git a/pypy/module/imp/importing.py b/pypy/module/imp/importing.py --- a/pypy/module/imp/importing.py +++ b/pypy/module/imp/importing.py @@ -349,6 +349,11 @@ w_all = try_getattr(space, w_mod, space.wrap('__all__')) if w_all is not None: fromlist_w = space.fixedview(w_all) + else: + fromlist_w = [] + # "from x import *" with x already imported and no x.__all__ + # always succeeds without doing more imports. It will + # just copy everything from x.__dict__ as it is now. for w_name in fromlist_w: if try_getattr(space, w_mod, w_name) is None: return None @@ -389,6 +394,8 @@ w_all = try_getattr(space, w_mod, w('__all__')) if w_all is not None: fromlist_w = space.fixedview(w_all) + else: + fromlist_w = [] for w_name in fromlist_w: if try_getattr(space, w_mod, w_name) is None: load_part(space, w_path, prefix, space.str0_w(w_name), 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 @@ -66,6 +66,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") setuppkg("x", y='') @@ -677,6 +685,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 *" in d + assert d["foobar"].found == 123 _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit