Author: Ronan Lamy <ronan.l...@gmail.com> Branch: PyBuffer Changeset: r91048:2fbdc25dcb1b Date: 2017-04-13 00:49 +0100 http://bitbucket.org/pypy/pypy/changeset/2fbdc25dcb1b/
Log: hg merge py3.5 diff too long, truncating to 2000 out of 51546 lines diff --git a/lib-python/3/distutils/ccompiler.py b/lib-python/3/distutils/ccompiler.py --- a/lib-python/3/distutils/ccompiler.py +++ b/lib-python/3/distutils/ccompiler.py @@ -959,7 +959,9 @@ # is assumed to be in the 'distutils' package.) compiler_class = { 'unix': ('unixccompiler', 'UnixCCompiler', "standard UNIX-style compiler"), - 'msvc': ('_msvccompiler', 'MSVCCompiler', + # PyPy change: On win32 PyPy is translated with the + # "msvc9compiler", force the same here. + 'msvc': ('msvc9compiler', 'MSVCCompiler', "Microsoft Visual C++"), 'cygwin': ('cygwinccompiler', 'CygwinCCompiler', "Cygwin port of GNU C Compiler for Win32"), diff --git a/lib-python/3/distutils/sysconfig_pypy.py b/lib-python/3/distutils/sysconfig_pypy.py --- a/lib-python/3/distutils/sysconfig_pypy.py +++ b/lib-python/3/distutils/sysconfig_pypy.py @@ -66,12 +66,12 @@ so_ext = [s[0] for s in imp.get_suffixes() if s[2] == imp.C_EXTENSION][0] g = {} - g['CC'] = "gcc -pthread" - g['CXX'] = "g++ -pthread" + g['CC'] = "cc -pthread" + g['CXX'] = "c++ -pthread" g['OPT'] = "-DNDEBUG -O2" g['CFLAGS'] = "-DNDEBUG -O2" g['CCSHARED'] = "-fPIC" - g['LDSHARED'] = "gcc -pthread -shared" + g['LDSHARED'] = "cc -pthread -shared" g['EXT_SUFFIX'] = so_ext g['SHLIB_SUFFIX'] = so_ext g['SO'] = so_ext # deprecated in Python 3, for backward compatibility diff --git a/lib-python/3/pathlib.py b/lib-python/3/pathlib.py --- a/lib-python/3/pathlib.py +++ b/lib-python/3/pathlib.py @@ -1209,23 +1209,19 @@ def mkdir(self, mode=0o777, parents=False, exist_ok=False): if self._closed: self._raise_closed() - if not parents: - try: - self._accessor.mkdir(self, mode) - except FileExistsError: - if not exist_ok or not self.is_dir(): - raise - else: - try: - self._accessor.mkdir(self, mode) - except FileExistsError: - if not exist_ok or not self.is_dir(): - raise - except OSError as e: - if e.errno != ENOENT: - raise - self.parent.mkdir(parents=True) - self._accessor.mkdir(self, mode) + # NOTE: version from CPython 3.7 plus fix at issue #29694 + try: + self._accessor.mkdir(self, mode) + except FileNotFoundError: + if not parents or self.parent == self: + raise + self.parent.mkdir(parents=True, exist_ok=True) + self.mkdir(mode, parents=False, exist_ok=exist_ok) + except OSError: + # Cannot rely on checking for EEXIST, since the operating system + # could give priority to other errors like EACCES or EROFS + if not exist_ok or not self.is_dir(): + raise def chmod(self, mode): """ diff --git a/lib-python/3/sre_compile.py b/lib-python/3/sre_compile.py --- a/lib-python/3/sre_compile.py +++ b/lib-python/3/sre_compile.py @@ -378,7 +378,11 @@ def _bytes_to_codes(b): # Convert block indices to word array - a = memoryview(b).cast('I') + if _sre.CODESIZE == 2: + code = 'H' + else: + code = 'I' + a = memoryview(b).cast(code) assert a.itemsize == _sre.CODESIZE assert len(a) * a.itemsize == len(b) return a.tolist() diff --git a/lib-python/3/test/_test_multiprocessing.py b/lib-python/3/test/_test_multiprocessing.py --- a/lib-python/3/test/_test_multiprocessing.py +++ b/lib-python/3/test/_test_multiprocessing.py @@ -2147,7 +2147,8 @@ # Because we are using xmlrpclib for serialization instead of # pickle this will cause a serialization error. - self.assertRaises(Exception, queue.put, time.sleep) + # Changed on PyPy: passing functions to xmlrpc is broken + #self.assertRaises(Exception, queue.put, time.sleep) # Make queue finalizer run before the server is stopped del queue diff --git a/lib-python/3/test/test_pathlib.py b/lib-python/3/test/test_pathlib.py --- a/lib-python/3/test/test_pathlib.py +++ b/lib-python/3/test/test_pathlib.py @@ -8,6 +8,7 @@ import stat import tempfile import unittest +from unittest import mock from test import support TESTFN = support.TESTFN @@ -1751,6 +1752,35 @@ p.mkdir(exist_ok=True) self.assertEqual(cm.exception.errno, errno.EEXIST) + def test_mkdir_concurrent_parent_creation(self): + for pattern_num in range(32): + p = self.cls(BASE, 'dirCPC%d' % pattern_num) + self.assertFalse(p.exists()) + + def my_mkdir(path, mode=0o777): + path = str(path) + # Emulate another process that would create the directory + # just before we try to create it ourselves. We do it + # in all possible pattern combinations, assuming that this + # function is called at most 5 times (dirCPC/dir1/dir2, + # dirCPC/dir1, dirCPC, dirCPC/dir1, cirCPC/dir1/dir2). + if pattern.pop(): + os.mkdir(path, mode) # from another process + concurrently_created.add(path) + os.mkdir(path, mode) # our real call + + pattern = [bool(pattern_num & (1 << n)) for n in range(5)] + concurrently_created = set() + p12 = p / 'dir1' / 'dir2' + try: + with mock.patch("pathlib._normal_accessor.mkdir", my_mkdir): + p12.mkdir(parents=True, exist_ok=False) + except FileExistsError: + self.assertIn(str(p12), concurrently_created) + else: + self.assertNotIn(str(p12), concurrently_created) + self.assertTrue(p.exists()) + @with_symlinks def test_symlink_to(self): P = self.cls(BASE) diff --git a/lib-python/3/test/test_xml_etree.py b/lib-python/3/test/test_xml_etree.py --- a/lib-python/3/test/test_xml_etree.py +++ b/lib-python/3/test/test_xml_etree.py @@ -1878,6 +1878,17 @@ with self.assertRaises(RuntimeError): repr(e) # Should not crash + def test_bad_find_returns_none(self): + # This behavior is the one we get historically when the C + # extension module is enabled. With the Python version, it + # raised a TypeError instead. There are projects out there + # that depend on the non-raising behavior, of course. + e = ET.Element('foo') + assert e.find('') is None + assert e.findall('') == [] + assert e.findtext('') is None + assert e.findtext('', default="default.") == "default." + class MutatingElementPath(str): def __new__(cls, elem, *args): self = str.__new__(cls, *args) diff --git a/lib-python/3/xml/etree/ElementTree.py b/lib-python/3/xml/etree/ElementTree.py --- a/lib-python/3/xml/etree/ElementTree.py +++ b/lib-python/3/xml/etree/ElementTree.py @@ -294,7 +294,14 @@ Return the first matching element, or None if no element was found. """ - return ElementPath.find(self, path, namespaces) + # Used to be: return ElementPath.find(self, path, namespaces) + # but there are projects out there that rely on it getting None + # instead of an internal TypeError. This is what the C version + # of this class does. + result = ElementPath.iterfind(self, path, namespaces) + if result is None: + return None + return next(result, None) def findtext(self, path, default=None, namespaces=None): """Find text for first matching element by tag name or path. @@ -308,7 +315,17 @@ content, the empty string is returned. """ - return ElementPath.findtext(self, path, default, namespaces) + # Used to be: + # return ElementPath.findtext(self, path, default, namespaces) + # See find(). + result = ElementPath.iterfind(self, path, namespaces) + if result is None: + return default + try: + elem = next(result) + return elem.text or "" + except StopIteration: + return default def findall(self, path, namespaces=None): """Find all matching subelements by tag name or path. @@ -319,7 +336,12 @@ Returns list containing all matching elements in document order. """ - return ElementPath.findall(self, path, namespaces) + # Used to be: return ElementPath.findall(self, path, namespaces) + # See find(). + result = ElementPath.iterfind(self, path, namespaces) + if result is None: + return [] + return list(result) def iterfind(self, path, namespaces=None): """Find all matching subelements by tag name or path. diff --git a/lib-python/conftest.py b/lib-python/conftest.py --- a/lib-python/conftest.py +++ b/lib-python/conftest.py @@ -597,7 +597,7 @@ py.test.skip("%s module not included in %s" % (mod, execpath)) - cmd = "%s %s %s" % (execpath, regrrun, fspath.purebasename) + cmd = "%s -m test -v %s" % (execpath, fspath.purebasename) # add watchdog for timing out cmd = "%s %s %s %s" % (python, watchdog_script, TIMEOUT, cmd) else: diff --git a/lib_pypy/_audioop_build.py b/lib_pypy/_audioop_build.py --- a/lib_pypy/_audioop_build.py +++ b/lib_pypy/_audioop_build.py @@ -656,4 +656,4 @@ ffi.set_source("_audioop_cffi", C_SOURCE) if __name__ == "__main__": - ffi.compile() + ffi.compile(verbose=2) diff --git a/lib_pypy/_decimal_build.py b/lib_pypy/_decimal_build.py --- a/lib_pypy/_decimal_build.py +++ b/lib_pypy/_decimal_build.py @@ -226,6 +226,16 @@ _libdir = os.path.join(os.path.dirname(__file__), '_libmpdec') ffi.set_source('_decimal_cffi', """ +#ifdef _MSC_VER + #if defined(_WIN64) + typedef __int64 LONG_PTR; + #else + typedef long LONG_PTR; + #endif + typedef LONG_PTR ssize_t; +#else + #define HAVE_STDINT_H +#endif #include "mpdecimal.h" #define MPD_Float_operation MPD_Not_implemented @@ -266,7 +276,6 @@ include_dirs=[_libdir], extra_compile_args=[ "-DANSI", - "-DHAVE_STDINT_H", "-DHAVE_INTTYPES_H", "-DCONFIG_64" if sys.maxsize > 1 << 32 else "-DCONFIG_32", ], diff --git a/lib_pypy/_pypy_testcapi.py b/lib_pypy/_pypy_testcapi.py --- a/lib_pypy/_pypy_testcapi.py +++ b/lib_pypy/_pypy_testcapi.py @@ -61,13 +61,12 @@ # set link options output_filename = modulename + _get_c_extension_suffix() if sys.platform == 'win32': - # XXX pyconfig.h uses a pragma to link to the import library, - # which is currently python3.lib - library = os.path.join(thisdir, '..', 'libs', 'python32') + libname = 'python{0[0]}{0[1]}'.format(sys.version_info) + library = os.path.join(thisdir, '..', 'lib', libname) if not os.path.exists(library + '.lib'): # For a local translation or nightly build - library = os.path.join(thisdir, '..', 'pypy', 'goal', 'python32') - assert os.path.exists(library + '.lib'),'Could not find import library "%s"' % library + library = os.path.join(thisdir, '..', 'pypy', 'goal', libname) + assert os.path.exists(library + '.lib'), 'Could not find import library "%s"' % library libraries = [library, 'oleaut32'] extra_ldargs = ['/MANIFEST', # needed for VC10 '/EXPORT:PyInit_' + modulename] diff --git a/lib_pypy/_sqlite3.py b/lib_pypy/_sqlite3.py --- a/lib_pypy/_sqlite3.py +++ b/lib_pypy/_sqlite3.py @@ -32,10 +32,11 @@ import threading try: - from __pypy__ import newlist_hint + from __pypy__ import newlist_hint, add_memory_pressure except ImportError: assert '__pypy__' not in sys.builtin_module_names newlist_hint = lambda sizehint: [] + add_memory_pressure = lambda size: None if sys.version_info[0] >= 3: StandardError = Exception @@ -152,6 +153,9 @@ check_same_thread=True, factory=None, cached_statements=100, uri=0): factory = Connection if not factory else factory + # an sqlite3 db seems to be around 100 KiB at least (doesn't matter if + # backed by :memory: or a file) + add_memory_pressure(100 * 1024) return factory(database, timeout, detect_types, isolation_level, check_same_thread, factory, cached_statements, uri) 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 @@ -563,7 +563,7 @@ if sys.platform == "win32": # we need 'libpypy-c.lib'. Current distributions of # pypy (>= 4.1) contain it as 'libs/python27.lib'. - pythonlib = "python27" + pythonlib = "python{0[0]}{0[1]}".format(sys.version_info) if hasattr(sys, 'prefix'): ensure('library_dirs', os.path.join(sys.prefix, 'libs')) else: diff --git a/pypy/config/pypyoption.py b/pypy/config/pypyoption.py --- a/pypy/config/pypyoption.py +++ b/pypy/config/pypyoption.py @@ -72,8 +72,6 @@ if "cppyy" in working_modules: working_modules.remove("cppyy") # not tested on win32 - if "faulthandler" in working_modules: - working_modules.remove("faulthandler") # missing details # The _locale module is needed by site.py on Windows default_modules.add("_locale") diff --git a/pypy/doc/build.rst b/pypy/doc/build.rst --- a/pypy/doc/build.rst +++ b/pypy/doc/build.rst @@ -79,6 +79,9 @@ _ssl libssl +_vmprof + libunwind (optional, loaded dynamically at runtime) + Make sure to have these libraries (with development headers) installed before building PyPy, otherwise the resulting binary will not contain these modules. Furthermore, the following libraries should be present @@ -185,7 +188,7 @@ :: cd pypy/tool/release - ./package.py pypy-VER-PLATFORM + ./package.py --archive-name=pypy-VER-PLATFORM This creates a clean and prepared hierarchy, as well as a ``.tar.bz2`` with the same content; both are found by default in 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 @@ -364,6 +364,24 @@ .. __: https://bitbucket.org/pypy/pypy/issue/1974/different-behaviour-for-collections-of +Performance Differences +------------------------- + +CPython has an optimization that can make repeated string concatenation not +quadratic. For example, this kind of code runs in O(n) time:: + + s = '' + for string in mylist: + s += string + +In PyPy, this code will always have quadratic complexity. Note also, that the +CPython optimization is brittle and can break by having slight variations in +your code anyway. So you should anyway replace the code with:: + + parts = [] + for string in mylist: + parts.append(string) + s = "".join(parts) Miscellaneous ------------- diff --git a/pypy/doc/install.rst b/pypy/doc/install.rst --- a/pypy/doc/install.rst +++ b/pypy/doc/install.rst @@ -57,6 +57,7 @@ .. code-block:: console $ ./pypy-xxx/bin/pypy -m ensurepip + $ ./pypy-xxx/bin/pip install -U pip wheel # to upgrade to the latest versions $ ./pypy-xxx/bin/pip install pygments # for example Third party libraries will be installed in ``pypy-xxx/site-packages``, and @@ -77,7 +78,17 @@ # from the mercurial checkout $ virtualenv -p /path/to/pypy/pypy/translator/goal/pypy-c my-pypy-env -Note that bin/python is now a symlink to bin/pypy. + # in any case activate it + $ source my-pypy-env/bin/activate + +Note that my-pypy-env/bin/python is now a symlink to my-pypy-env/bin/pypy +so you should be able to run pypy simply by typing:: + + $ python + +You should still upgrade pip and wheel to the latest versions via:: + + $ my-pypy-env/bin/pip install -U pip wheel .. _pip: http://pypi.python.org/pypi/pip .. _ensurepip: https://docs.python.org/2.7/library/ensurepip.html 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 @@ -2,7 +2,22 @@ What's new in PyPy2.7 5.8+ ========================== -.. this is a revision shortly after release-pypy2.7-v5.7 +.. this is a revision shortly after release-pypy2.7-v5.7.0 .. startrev: 44f31f6dd39f +Add cpyext interfaces for ``PyModule_New`` +Correctly handle `dict.pop`` where the ``pop`` +key is not the same type as the ``dict``'s and ``pop`` +is called with a default (will be part of release 5.7.1) + +.. branch: issue2522 + +Fix missing tp_new on w_object called through multiple inheritance +(will be part of release 5.7.1) + +.. branch: lstrip_to_empty_string + +.. branch: vmprof-native + +PyPy support to profile native frames in vmprof. diff --git a/pypy/doc/whatsnew-pypy3-head.rst b/pypy/doc/whatsnew-pypy3-head.rst --- a/pypy/doc/whatsnew-pypy3-head.rst +++ b/pypy/doc/whatsnew-pypy3-head.rst @@ -5,3 +5,16 @@ .. this is the revision after release-pypy3.3-5.7.x was branched .. startrev: afbf09453369 +.. branch: mtest +Use "<python> -m test" to run the CPython test suite, as documented by CPython, +instead of our outdated regrverbose.py script. + +.. branch: win32-faulthandler + +Enable the 'faulthandler' module on Windows; +this unblocks the Python test suite. + +.. branch: superjumbo + +Implement posix.posix_fallocate() and posix.posix_fadvise() + diff --git a/pypy/goal/targetpypystandalone.py b/pypy/goal/targetpypystandalone.py --- a/pypy/goal/targetpypystandalone.py +++ b/pypy/goal/targetpypystandalone.py @@ -13,6 +13,7 @@ from pypy import pypydir from rpython.rlib import rthread from pypy.module.thread import os_thread +from pypy.module.sys.version import CPYTHON_VERSION thisdir = py.path.local(__file__).dirpath() @@ -285,7 +286,8 @@ if sys.platform == 'win32': libdir = thisdir.join('..', '..', 'libs') libdir.ensure(dir=1) - config.translation.libname = str(libdir.join('python27.lib')) + pythonlib = "python{0[0]}{0[1]}.lib".format(CPYTHON_VERSION) + config.translation.libname = str(libdir.join(pythonlib)) if config.translation.thread: config.objspace.usemodules.thread = True diff --git a/pypy/interpreter/error.py b/pypy/interpreter/error.py --- a/pypy/interpreter/error.py +++ b/pypy/interpreter/error.py @@ -9,6 +9,7 @@ from rpython.rlib.objectmodel import we_are_translated, specialize from rpython.rlib.objectmodel import dont_inline from rpython.rlib import rstack, rstackovf +from rpython.rlib import rwin32 from pypy.interpreter import debug @@ -568,33 +569,6 @@ # 31: ANSI color code "red" ansi_print(text, esc="31", file=file, newline=newline) -try: - WindowsError -except NameError: - _WINDOWS = False -else: - _WINDOWS = True - - def wrap_windowserror(space, e, w_filename=None): - XXX # WindowsError no longer exists in Py3.5 - # instead, OSError has a kwarg winerror that overrides - # any errno supplied - from rpython.rlib import rwin32 - - winerror = e.winerror - try: - msg = rwin32.FormatError(winerror) - except ValueError: - msg = 'Windows Error %d' % winerror - exc = space.w_WindowsError - if w_filename is not None: - w_error = space.call_function(exc, space.newint(winerror), - space.newtext(msg), w_filename) - else: - w_error = space.call_function(exc, space.newint(winerror), - space.newtext(msg)) - return OperationError(exc, w_error) - @specialize.arg(3, 6) def wrap_oserror2(space, e, w_filename=None, exception_name='w_OSError', w_exception_class=None, w_filename2=None, eintr_retry=False): @@ -612,9 +586,6 @@ """ assert isinstance(e, OSError) - if _WINDOWS and isinstance(e, WindowsError): - return wrap_windowserror(space, e, w_filename) - if w_exception_class is None: w_exc = getattr(space, exception_name) else: @@ -631,28 +602,37 @@ def _wrap_oserror2_impl(space, e, w_filename, w_filename2, w_exc, eintr_retry): # move the common logic in its own function, instead of having it # duplicated 4 times in all 4 specialized versions of wrap_oserror2() - errno = e.errno - if errno == EINTR: - space.getexecutioncontext().checksignals() - if eintr_retry: - return None + if rwin32.WIN32 and isinstance(e, WindowsError): + winerror = e.winerror + try: + msg = rwin32.FormatError(winerror) + except ValueError: + msg = 'Windows Error %d' % winerror + w_errno = space.w_None + w_winerror = space.newint(winerror) + w_msg = space.newtext(msg) + else: + errno = e.errno + if errno == EINTR: + space.getexecutioncontext().checksignals() + if eintr_retry: + return None - try: - msg = strerror(errno) - except ValueError: - msg = u'error %d' % errno - if w_filename is not None: - if w_filename2 is not None: - w_error = space.call_function(w_exc, space.newint(errno), - space.newunicode(msg), w_filename, - space.w_None, w_filename2) - else: - w_error = space.call_function(w_exc, space.newint(errno), - space.newunicode(msg), w_filename) - else: - w_error = space.call_function(w_exc, space.newint(errno), - space.newunicode(msg)) + try: + msg = strerror(errno) + except ValueError: + msg = u'error %d' % errno + w_errno = space.newint(errno) + w_winerror = space.w_None + w_msg = space.newunicode(msg) + + if w_filename is None: + w_filename = space.w_None + if w_filename2 is None: + w_filename2 = space.w_None + w_error = space.call_function(w_exc, w_errno, w_msg, w_filename, + w_winerror, w_filename2) operror = OperationError(w_exc, w_error) if eintr_retry: raise operror diff --git a/pypy/interpreter/pyparser/future.py b/pypy/interpreter/pyparser/future.py --- a/pypy/interpreter/pyparser/future.py +++ b/pypy/interpreter/pyparser/future.py @@ -80,6 +80,7 @@ from pypy.interpreter.pyparser import pygram it = TokenIterator(tokens) result = 0 + last_position = (0, 0) # # The only things that can precede a future statement are another # future statement and a doc string (only one). This is a very @@ -94,6 +95,11 @@ it.skip_name("__future__") and it.skip_name("import")): it.skip(pygram.tokens.LPAR) # optionally + # return in 'last_position' any line-column pair that points + # somewhere inside the last __future__ import statement + # (at the start would be fine too, but it's easier to grab a + # random position inside) + last_position = (it.tok[2], it.tok[3]) result |= future_flags.get_compiler_feature(it.next_feature_name()) while it.skip(pygram.tokens.COMMA): result |= future_flags.get_compiler_feature(it.next_feature_name()) @@ -104,5 +110,4 @@ # remove the flags that were specified but are anyway mandatory result &= ~future_flags.mandatory_flags - position = (it.tok[2], it.tok[3]) - return result, position + return result, last_position diff --git a/pypy/interpreter/pyparser/test/test_future.py b/pypy/interpreter/pyparser/test/test_future.py --- a/pypy/interpreter/pyparser/test/test_future.py +++ b/pypy/interpreter/pyparser/test/test_future.py @@ -2,10 +2,9 @@ from pypy.interpreter.pyparser import future, pytokenizer from pypy.tool import stdlib___future__ as fut -def run(s, expected_last_future=None): +def run(s, expected_last_future=(0, 0)): source_lines = s.splitlines(True) tokens = pytokenizer.generate_tokens(source_lines, 0) - expected_last_future = expected_last_future or tokens[-1][2:4] # flags, last_future_import = future.add_future_flags( future.futureFlags_3_5, tokens) @@ -14,7 +13,7 @@ def test_docstring(): s = '"Docstring\\" "\nfrom __future__ import division\n' - f = run(s) + f = run(s, (2, 24)) assert f == 0 def test_comment(): @@ -45,152 +44,152 @@ def test_from(): s = 'from __future__ import division\n' - f = run(s) + f = run(s, (1, 24)) assert f == 0 def test_froms(): s = 'from __future__ import division, generators, with_statement\n' - f = run(s) + f = run(s, (1, 24)) assert f == 0 def test_from_as(): s = 'from __future__ import division as b\n' - f = run(s) + f = run(s, (1, 24)) assert f == 0 def test_froms_as(): s = 'from __future__ import division as b, generators as c\n' - f = run(s) + f = run(s, (1, 24)) assert f == 0 def test_from_paren(): s = 'from __future__ import (division)\n' - f = run(s) + f = run(s, (1, 25)) assert f == 0 def test_froms_paren(): s = 'from __future__ import (division, generators)\n' - f = run(s) + f = run(s, (1, 25)) assert f == 0 def test_froms_paren_as(): s = 'from __future__ import (division as b, generators,)\n' - f = run(s) + f = run(s, (1, 25)) assert f == 0 def test_paren_with_newline(): s = 'from __future__ import (division,\nabsolute_import)\n' - f = run(s) + f = run(s, (1, 24)) assert f == 0 def test_paren_with_newline_2(): s = 'from __future__ import (\ndivision,\nabsolute_import)\n' - f = run(s) + f = run(s, (2, 0)) assert f == 0 def test_multiline(): s = '"abc" #def\n #ghi\nfrom __future__ import (division as b, generators,)\nfrom __future__ import with_statement\n' - f = run(s) + f = run(s, (4, 23)) assert f == 0 def test_windows_style_lineendings(): s = '"abc" #def\r\n #ghi\r\nfrom __future__ import (division as b, generators,)\r\nfrom __future__ import with_statement\r\n' - f = run(s) + f = run(s, (4, 23)) assert f == 0 def test_mac_style_lineendings(): s = '"abc" #def\r #ghi\rfrom __future__ import (division as b, generators,)\rfrom __future__ import with_statement\r' - f = run(s) + f = run(s, (4, 23)) assert f == 0 def test_semicolon(): s = '"abc" #def\n #ghi\nfrom __future__ import (division as b, generators,); from __future__ import with_statement\n' - f = run(s) + f = run(s, (3, 78)) assert f == 0 def test_semicolon_2(): s = 'from __future__ import division; from foo import bar' - f = run(s, expected_last_future=(1, 39)) + f = run(s, expected_last_future=(1, 24)) assert f == 0 def test_full_chain(): s = '"abc" #def\n #ghi\nfrom __future__ import (division as b, generators,); from __future__ import with_statement\n' - f = run(s) + f = run(s, (3, 78)) assert f == 0 def test_intervening_code(): s = 'from __future__ import (division as b, generators,)\nfrom sys import modules\nfrom __future__ import with_statement\n' - f = run(s, expected_last_future=(2, 5)) + f = run(s, expected_last_future=(1, 25)) assert f == 0 def test_nonexisting(): s = 'from __future__ import non_existing_feature\n' - f = run(s) + f = run(s, (1, 24)) assert f == 0 def test_nonexisting_2(): s = 'from __future__ import non_existing_feature, with_statement\n' - f = run(s) + f = run(s, (1, 24)) assert f == 0 def test_from_import_abs_import(): s = 'from __future__ import absolute_import\n' - f = run(s) + f = run(s, (1, 24)) assert f == 0 def test_raw_doc(): s = 'r"Doc"\nfrom __future__ import with_statement\n' - f = run(s) + f = run(s, (2, 23)) assert f == 0 def test_unicode_doc(): s = 'u"Doc"\nfrom __future__ import with_statement\n' - f = run(s) + f = run(s, (2, 23)) assert f == 0 def test_raw_unicode_doc(): s = 'ru"Doc"\nfrom __future__ import with_statement\n' - f = run(s) + f = run(s, (2, 23)) assert f == 0 def test_continuation_line(): s = "\\\nfrom __future__ import with_statement\n" - f = run(s) + f = run(s, (2, 23)) assert f == 0 def test_continuation_lines(): s = "\\\n \t\\\nfrom __future__ import with_statement\n" - f = run(s) + f = run(s, (3, 23)) assert f == 0 def test_lots_of_continuation_lines(): s = "\\\n\\\n\\\n\\\n\\\n\\\n\nfrom __future__ import with_statement\n" - f = run(s) + f = run(s, (8, 23)) assert f == 0 def test_continuation_lines_raise(): s = " \\\n \t\\\nfrom __future__ import with_statement\n" - f = run(s, expected_last_future=(1, 0)) + f = run(s) assert f == 0 # because of the INDENT def test_continuation_lines_in_docstring_single_quoted(): s = '"\\\n\\\n\\\n\\\n\\\n\\\n"\nfrom __future__ import division\n' - f = run(s) + f = run(s, (8, 24)) assert f == 0 def test_continuation_lines_in_docstring_triple_quoted(): s = '"""\\\n\\\n\\\n\\\n\\\n\\\n"""\nfrom __future__ import division\n' - f = run(s) + f = run(s, (8, 24)) assert f == 0 def test_blank_lines(): s = ('\n\t\n\nfrom __future__ import with_statement' ' \n \n \nfrom __future__ import division') - f = run(s) + f = run(s, (7, 23)) assert f == 0 def test_dummy_semicolons(): s = ('from __future__ import division;\n' 'from __future__ import with_statement;') - f = run(s) + f = run(s, (2, 23)) assert f == 0 diff --git a/pypy/interpreter/test/test_syntax.py b/pypy/interpreter/test/test_syntax.py --- a/pypy/interpreter/test/test_syntax.py +++ b/pypy/interpreter/test/test_syntax.py @@ -308,6 +308,15 @@ assert isinstance(ns["c"], bytes) assert isinstance(ns["d"], bytes) + def test_both_futures_with_semicolon(self): + # Issue #2526: a corner case which crashes only if the file + # contains *nothing more* than two __future__ imports separated + # by a semicolon. + s = """ +from __future__ import unicode_literals; from __future__ import print_function +""" + exec s in {} + class AppTestComprehensions: 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 @@ -6,6 +6,8 @@ class BuildersModule(MixedModule): + """ Module containing string and unicode builders """ + appleveldefs = {} interpleveldefs = { @@ -34,6 +36,8 @@ class IntOpModule(MixedModule): + """ Module for integer operations that have two-complement overflow + behaviour instead of overflowing to longs """ appleveldefs = {} interpleveldefs = { 'int_add': 'interp_intop.int_add', @@ -55,6 +59,8 @@ class Module(MixedModule): + """ PyPy specific "magic" functions. A lot of them are experimental and + subject to change, many are internal. """ appleveldefs = { } 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 @@ -106,13 +106,15 @@ raise wrap_oserror(space, e) @unwrap_spec(sizehint=int) -def resizelist_hint(space, w_iterable, sizehint): - if not isinstance(w_iterable, W_ListObject): +def resizelist_hint(space, w_list, sizehint): + """ Reallocate the underlying storage of the argument list to sizehint """ + if not isinstance(w_list, W_ListObject): raise oefmt(space.w_TypeError, "arg 1 must be a 'list'") - w_iterable._resize_hint(sizehint) + w_list._resize_hint(sizehint) @unwrap_spec(sizehint=int) def newlist_hint(space, sizehint): + """ Create a new empty list that has an underlying storage of length sizehint """ return space.newlist_hint(sizehint) @unwrap_spec(debug=int) @@ -125,6 +127,9 @@ @unwrap_spec(estimate=int) def add_memory_pressure(estimate): + """ Add memory pressure of estimate bytes. Useful when calling a C function + that internally allocates a big chunk of memory. This instructs the GC to + garbage collect sooner than it would otherwise.""" rgc.add_memory_pressure(estimate) @unwrap_spec(w_frame=PyFrame) 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 @@ -1471,8 +1471,13 @@ with self.StdErrCapture(fd=True) as f: res = lib.bar(4, 5) assert res == 0 - assert f.getvalue() == ( + assert f.getvalue() in ( + # If the underlying cffi is <= 1.9 "extern \"Python\": function bar() called, but no code was attached " + "to it yet with @ffi.def_extern(). Returning 0.\n", + # If the underlying cffi is >= 1.10 + "extern \"Python\": function _CFFI_test_extern_python_1.bar() " + "called, but no code was attached " "to it yet with @ffi.def_extern(). Returning 0.\n") @ffi.def_extern("bar") diff --git a/pypy/module/_multiprocessing/interp_win32.py b/pypy/module/_multiprocessing/interp_win32.py --- a/pypy/module/_multiprocessing/interp_win32.py +++ b/pypy/module/_multiprocessing/interp_win32.py @@ -4,10 +4,9 @@ from rpython.rtyper.tool import rffi_platform from rpython.translator.tool.cbuild import ExternalCompilationInfo -from pypy.interpreter.error import oefmt, wrap_windowserror +from pypy.interpreter.error import oefmt, wrap_oserror from pypy.interpreter.function import StaticMethod from pypy.interpreter.gateway import interp2app, unwrap_spec -from pypy.module._multiprocessing.interp_connection import w_handle CONSTANTS = """ PIPE_ACCESS_INBOUND PIPE_ACCESS_DUPLEX @@ -35,6 +34,9 @@ def handle_w(space, w_handle): return rffi.cast(rwin32.HANDLE, space.int_w(w_handle)) +def w_handle(space, handle): + return space.newint(rffi.cast(rffi.INTPTR_T, handle)) + _CreateNamedPipe = rwin32.winexternal( 'CreateNamedPipeA', [ rwin32.LPCSTR, @@ -106,7 +108,7 @@ def CloseHandle(space, w_handle): handle = handle_w(space, w_handle) if not rwin32.CloseHandle(handle): - raise wrap_windowserror(space, rwin32.lastSavedWindowsError()) + raise wrap_oserror(space, rwin32.lastSavedWindowsError()) def GetLastError(space): return space.newint(rwin32.GetLastError_saved()) @@ -126,7 +128,7 @@ outputsize, inputsize, timeout, rffi.NULL) if handle == rwin32.INVALID_HANDLE_VALUE: - raise wrap_windowserror(space, rwin32.lastSavedWindowsError()) + raise wrap_oserror(space, rwin32.lastSavedWindowsError()) return w_handle(space, handle) @@ -136,7 +138,7 @@ if overlapped: raise oefmt(space.w_NotImplementedError, "expected a NULL pointer") if not _ConnectNamedPipe(handle, rffi.NULL): - raise wrap_windowserror(space, rwin32.lastSavedWindowsError()) + raise wrap_oserror(space, rwin32.lastSavedWindowsError()) def SetNamedPipeHandleState(space, w_handle, w_pipemode, w_maxinstances, w_timeout): @@ -156,7 +158,7 @@ statep[2] = rffi.ptradd(state, 2) if not _SetNamedPipeHandleState(handle, statep[0], statep[1], statep[2]): - raise wrap_windowserror(space, rwin32.lastSavedWindowsError()) + raise wrap_oserror(space, rwin32.lastSavedWindowsError()) finally: lltype.free(state, flavor='raw') lltype.free(statep, flavor='raw') @@ -165,7 +167,7 @@ def WaitNamedPipe(space, name, timeout): # Careful: zero means "default value specified by CreateNamedPipe()" if not _WaitNamedPipe(name, timeout): - raise wrap_windowserror(space, rwin32.lastSavedWindowsError()) + raise wrap_oserror(space, rwin32.lastSavedWindowsError()) @unwrap_spec(filename='fsencode', access=r_uint, share=r_uint, disposition=r_uint, flags=r_uint) @@ -180,7 +182,7 @@ disposition, flags, rwin32.NULL_HANDLE) if handle == rwin32.INVALID_HANDLE_VALUE: - raise wrap_windowserror(space, rwin32.lastSavedWindowsError()) + raise wrap_oserror(space, rwin32.lastSavedWindowsError()) return w_handle(space, handle) 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 @@ -50,8 +50,8 @@ return OperationError(w_VMProfError, space.newtext(e.msg)) -@unwrap_spec(fileno=int, period=float) -def enable(space, fileno, period): +@unwrap_spec(fileno=int, period=float, memory=int, lines=int, native=int) +def enable(space, fileno, period, memory, lines, native): """Enable vmprof. Writes go to the given 'fileno', a file descriptor opened for writing. *The file descriptor must remain open at least until disable() is called.* @@ -65,7 +65,7 @@ # "with vmprof will crash"), # space.w_RuntimeWarning) try: - rvmprof.enable(fileno, period) + rvmprof.enable(fileno, period, memory, native) except rvmprof.VMProfError as e: raise VMProfError(space, e) diff --git a/pypy/module/_vmprof/test/test__vmprof.py b/pypy/module/_vmprof/test/test__vmprof.py --- a/pypy/module/_vmprof/test/test__vmprof.py +++ b/pypy/module/_vmprof/test/test__vmprof.py @@ -24,10 +24,11 @@ i += 5 * WORD # header assert s[i ] == 5 # MARKER_HEADER assert s[i + 1] == 0 # 0 - assert s[i + 2] == 2 # VERSION_THREAD_ID - assert s[i + 3] == 4 # len('pypy') - assert s[i + 4: i + 8] == b'pypy' - i += 8 + assert s[i + 2] == 6 # VERSION_TIMESTAMP + assert s[i + 3] == 8 # PROFILE_RPYTHON + assert s[i + 4] == 4 # len('pypy') + assert s[i + 5: i + 9] == b'pypy' + i += 9 while i < len(s): if s[i] == 3: break @@ -41,6 +42,17 @@ _, size = struct.unpack("ll", s[i:i + 2 * WORD]) count += 1 i += 2 * WORD + size + elif s[i] == '\x06': + print(s[i:i+24]) + i += 1+8+8+8 + elif s[i] == '\x07': + i += 1 + # skip string + size, = struct.unpack("l", s[i:i + WORD]) + i += WORD+size + # skip string + size, = struct.unpack("l", s[i:i + WORD]) + i += WORD+size else: raise AssertionError(s[i]) return count @@ -48,7 +60,7 @@ import _vmprof gc.collect() # try to make the weakref list deterministic gc.collect() # by freeing all dead code objects - _vmprof.enable(tmpfileno, 0.01) + _vmprof.enable(tmpfileno, 0.01, 0, 0, 0) _vmprof.disable() s = open(self.tmpfilename, 'rb').read() no_of_codes = count(s) @@ -64,7 +76,7 @@ gc.collect() gc.collect() - _vmprof.enable(tmpfileno2, 0.01) + _vmprof.enable(tmpfileno2, 0.01, 0, 0, 0) exec_("""def foo2(): pass @@ -79,9 +91,9 @@ def test_enable_ovf(self): import _vmprof - raises(_vmprof.VMProfError, _vmprof.enable, 2, 0) - raises(_vmprof.VMProfError, _vmprof.enable, 2, -2.5) - raises(_vmprof.VMProfError, _vmprof.enable, 2, 1e300) - raises(_vmprof.VMProfError, _vmprof.enable, 2, 1e300 * 1e300) + raises(_vmprof.VMProfError, _vmprof.enable, 2, 0, 0, 0, 0) + raises(_vmprof.VMProfError, _vmprof.enable, 2, -2.5, 0, 0, 0) + raises(_vmprof.VMProfError, _vmprof.enable, 2, 1e300, 0, 0, 0) + raises(_vmprof.VMProfError, _vmprof.enable, 2, 1e300 * 1e300, 0, 0, 0) NaN = (1e300*1e300) / (1e300*1e300) - raises(_vmprof.VMProfError, _vmprof.enable, 2, NaN) + raises(_vmprof.VMProfError, _vmprof.enable, 2, NaN, 0, 0, 0) diff --git a/pypy/module/_vmprof/test/test_direct.py b/pypy/module/_vmprof/test/test_direct.py --- a/pypy/module/_vmprof/test/test_direct.py +++ b/pypy/module/_vmprof/test/test_direct.py @@ -43,7 +43,7 @@ } -""" + open(str(srcdir.join("vmprof_get_custom_offset.h"))).read(), include_dirs=[str(srcdir)]) +""" + open(str(srcdir.join("shared/vmprof_get_custom_offset.h"))).read(), include_dirs=[str(srcdir)]) class TestDirect(object): def test_infrastructure(self): diff --git a/pypy/module/_winreg/interp_winreg.py b/pypy/module/_winreg/interp_winreg.py --- a/pypy/module/_winreg/interp_winreg.py +++ b/pypy/module/_winreg/interp_winreg.py @@ -2,7 +2,7 @@ from pypy.interpreter.baseobjspace import W_Root, BufferInterfaceNotFound from pypy.interpreter.gateway import interp2app, unwrap_spec from pypy.interpreter.typedef import TypeDef, GetSetProperty -from pypy.interpreter.error import OperationError, oefmt, wrap_windowserror +from pypy.interpreter.error import OperationError, oefmt, wrap_oserror from rpython.rtyper.lltypesystem import rffi, lltype from rpython.rlib import rwinreg, rwin32 from rpython.rlib.rarithmetic import r_uint, intmask @@ -110,7 +110,7 @@ elif isinstance(w_hkey, W_HKEY): return w_hkey.hkey elif space.isinstance_w(w_hkey, space.w_int): - if space.is_true(space.lt(w_hkey, space.wrap(0))): + if space.is_true(space.lt(w_hkey, space.newint(0))): return rffi.cast(rwinreg.HKEY, space.int_w(w_hkey)) return rffi.cast(rwinreg.HKEY, space.uint_w(w_hkey)) else: @@ -702,7 +702,7 @@ try: return space.newunicode(rwinreg.ExpandEnvironmentStrings(source)) except WindowsError as e: - raise wrap_windowserror(space, e) + raise wrap_oserror(space, e) def DisableReflectionKey(space, w_key): """Disables registry reflection for 32-bit processes running on a 64-bit diff --git a/pypy/module/cpyext/__init__.py b/pypy/module/cpyext/__init__.py --- a/pypy/module/cpyext/__init__.py +++ b/pypy/module/cpyext/__init__.py @@ -4,7 +4,6 @@ class Module(MixedModule): interpleveldefs = { - 'load_module': 'api.load_extension_module', } appleveldefs = { diff --git a/pypy/module/cpyext/api.py b/pypy/module/cpyext/api.py --- a/pypy/module/cpyext/api.py +++ b/pypy/module/cpyext/api.py @@ -565,7 +565,7 @@ 'PyUnicode_FromFormat', 'PyUnicode_FromFormatV', 'PyUnicode_AsWideCharString', 'PyUnicode_GetSize', 'PyUnicode_GetLength', 'PyModule_AddObject', 'PyModule_AddIntConstant', 'PyModule_AddStringConstant', - 'PyModule_GetDef', + 'PyModule_GetDef', 'PyModuleDef_Init', 'Py_BuildValue', 'Py_VaBuildValue', 'PyTuple_Pack', '_PyArg_Parse_SizeT', '_PyArg_ParseTuple_SizeT', '_PyArg_ParseTupleAndKeywords_SizeT', '_PyArg_VaParse_SizeT', @@ -651,7 +651,7 @@ global all_exceptions all_exceptions = list(exceptions.Module.interpleveldefs) for exc_name in all_exceptions: - if exc_name in ('EnvironmentError', 'IOError'): + if exc_name in ('EnvironmentError', 'IOError', 'WindowsError'): # FIXME: aliases of OSError cause a clash of names via # export_struct continue @@ -1489,8 +1489,7 @@ copy_header_files(cts, trunk_include, use_micronumpy) -@unwrap_spec(path='fsencode', name='text') -def load_extension_module(space, path, name): +def create_extension_module(space, w_spec): # note: this is used both to load CPython-API-style C extension # modules (cpyext) and to load CFFI-style extension modules # (_cffi_backend). Any of the two can be disabled at translation @@ -1498,6 +1497,9 @@ # order of things here. from rpython.rlib import rdynload + name = space.text_w(space.getattr(w_spec, space.newtext("name"))) + path = space.text_w(space.getattr(w_spec, space.newtext("origin"))) + if os.sep not in path: path = os.curdir + os.sep + path # force a '/' in the path basename = name.split('.')[-1] @@ -1535,7 +1537,7 @@ except KeyError: pass else: - return load_cpyext_module(space, name, path, dll, initptr) + return create_cpyext_module(space, w_spec, name, path, dll, initptr) if look_for is not None: look_for += ' or ' + also_look_for else: @@ -1549,8 +1551,9 @@ initfunctype = lltype.Ptr(lltype.FuncType([], PyObject)) -def load_cpyext_module(space, name, path, dll, initptr): +def create_cpyext_module(space, w_spec, name, path, dll, initptr): from rpython.rlib import rdynload + from pypy.module.cpyext.pyobject import get_w_obj_and_decref space.getbuiltinmodule("cpyext") # mandatory to init cpyext state = space.fromcache(State) @@ -1562,25 +1565,54 @@ state.package_context = name, path try: initfunc = rffi.cast(initfunctype, initptr) - w_mod = generic_cpy_call(space, initfunc) + initret = generic_cpy_call_dont_convert_result(space, initfunc) state.check_and_raise_exception() + if not initret.c_ob_type: + raise oefmt(space.w_SystemError, + "init function of %s returned uninitialized object", + name) + # This should probably compare by identity with PyModuleDef_Type from + # modsupport.c, but I didn't find a way to do that. + tp_name_nonconst = rffi.cast(rffi.CCHARP, initret.c_ob_type.c_tp_name) + if rffi.charp2str(tp_name_nonconst) == "moduledef": + from pypy.module.cpyext.modsupport import \ + create_module_from_def_and_spec + return create_module_from_def_and_spec(space, initret, w_spec, + name) finally: state.package_context = old_context + w_mod = get_w_obj_and_decref(space, initret) state.fixup_extension(w_mod, name, path) return w_mod +def exec_extension_module(space, w_mod): + from pypy.module.cpyext.modsupport import exec_def + if not space.config.objspace.usemodules.cpyext: + return + if not isinstance(w_mod, Module): + return + space.getbuiltinmodule("cpyext") + mod_as_pyobj = rawrefcount.from_obj(PyObject, w_mod) + if mod_as_pyobj: + return exec_def(space, w_mod, mod_as_pyobj) + @specialize.ll() def generic_cpy_call(space, func, *args): FT = lltype.typeOf(func).TO - return make_generic_cpy_call(FT, False)(space, func, *args) + return make_generic_cpy_call(FT, False, True)(space, func, *args) @specialize.ll() def generic_cpy_call_expect_null(space, func, *args): FT = lltype.typeOf(func).TO - return make_generic_cpy_call(FT, True)(space, func, *args) + return make_generic_cpy_call(FT, True, True)(space, func, *args) + +@specialize.ll() +def generic_cpy_call_dont_convert_result(space, func, *args): + FT = lltype.typeOf(func).TO + return make_generic_cpy_call(FT, False, False)(space, func, *args) @specialize.memo() -def make_generic_cpy_call(FT, expect_null): +def make_generic_cpy_call(FT, expect_null, convert_result): from pypy.module.cpyext.pyobject import make_ref, from_ref from pypy.module.cpyext.pyobject import is_pyobj, as_pyobj from pypy.module.cpyext.pyobject import get_w_obj_and_decref @@ -1634,8 +1666,9 @@ keepalive_until_here(*keepalives) if is_PyObject(RESULT_TYPE): - if not is_pyobj(result): + if not convert_result or not is_pyobj(result): ret = result + has_result = bool(ret) else: # The object reference returned from a C function # that is called from Python must be an owned reference @@ -1644,10 +1677,10 @@ ret = get_w_obj_and_decref(space, result) else: ret = None + has_result = ret is not None # Check for exception consistency has_error = PyErr_Occurred(space) is not None - has_result = ret is not None if has_error and has_result: raise oefmt(space.w_SystemError, "An exception was set, but function returned a " diff --git a/pypy/module/cpyext/include/modsupport.h b/pypy/module/cpyext/include/modsupport.h --- a/pypy/module/cpyext/include/modsupport.h +++ b/pypy/module/cpyext/include/modsupport.h @@ -87,9 +87,9 @@ #ifdef _WIN32 /* explicitly export since PyAPI_FUNC is usually dllimport */ #ifdef __cplusplus -#define PyMODINIT_FUNC extern "C" __declspec(dllexport) void +#define PyMODINIT_FUNC extern "C" __declspec(dllexport) PyObject* #else -#define PyMODINIT_FUNC __declspec(dllexport) void +#define PyMODINIT_FUNC __declspec(dllexport) PyObject* #endif #else #ifdef __cplusplus diff --git a/pypy/module/cpyext/include/moduleobject.h b/pypy/module/cpyext/include/moduleobject.h --- a/pypy/module/cpyext/include/moduleobject.h +++ b/pypy/module/cpyext/include/moduleobject.h @@ -8,6 +8,8 @@ #include "cpyext_moduleobject.h" +PyAPI_FUNC(PyObject *) PyModuleDef_Init(struct PyModuleDef*); + #ifdef __cplusplus } #endif diff --git a/pypy/module/cpyext/modsupport.py b/pypy/module/cpyext/modsupport.py --- a/pypy/module/cpyext/modsupport.py +++ b/pypy/module/cpyext/modsupport.py @@ -1,7 +1,7 @@ from rpython.rtyper.lltypesystem import rffi, lltype from pypy.module.cpyext.api import ( cpython_api, METH_STATIC, METH_CLASS, METH_COEXIST, CANNOT_FAIL, cts, - parse_dir, bootstrap_function) + parse_dir, bootstrap_function, generic_cpy_call) from pypy.module.cpyext.pyobject import PyObject, as_pyobj, make_typedescr from pypy.interpreter.module import Module from pypy.module.cpyext.methodobject import ( @@ -14,6 +14,7 @@ cts.parse_header(parse_dir / 'cpyext_moduleobject.h') PyModuleDef = cts.gettype('PyModuleDef *') PyModuleObject = cts.gettype('PyModuleObject *') +PyModuleDef_Slot = cts.gettype('PyModuleDef_Slot') @bootstrap_function def init_moduleobject(space): @@ -64,6 +65,89 @@ return w_mod +createfunctype = lltype.Ptr(lltype.FuncType([PyObject, PyModuleDef], PyObject)) +execfunctype = lltype.Ptr(lltype.FuncType([PyObject], rffi.INT_real)) + + +def create_module_from_def_and_spec(space, moddef, w_spec, name): + moddef = rffi.cast(PyModuleDef, moddef) + if moddef.c_m_size < 0: + raise oefmt(space.w_SystemError, + "module %s: m_size may not be negative for multi-phase " + "initialization", name) + createf = lltype.nullptr(rffi.VOIDP.TO) + has_execution_slots = False + cur_slot = rffi.cast(rffi.CArrayPtr(PyModuleDef_Slot), moddef.c_m_slots) + if cur_slot: + while True: + slot = rffi.cast(lltype.Signed, cur_slot[0].c_slot) + if slot == 0: + break + elif slot == 1: + if createf: + raise oefmt(space.w_SystemError, + "module %s has multiple create slots", name) + createf = cur_slot[0].c_value + elif slot < 0 or slot > 2: + raise oefmt(space.w_SystemError, + "module %s uses unknown slot ID %d", name, slot) + else: + has_execution_slots = True + cur_slot = rffi.ptradd(cur_slot, 1) + if createf: + createf = rffi.cast(createfunctype, createf) + w_mod = generic_cpy_call(space, createf, w_spec, moddef) + else: + w_mod = Module(space, space.newtext(name)) + if isinstance(w_mod, Module): + mod = rffi.cast(PyModuleObject, as_pyobj(space, w_mod)) + #mod.c_md_state = None + mod.c_md_def = moddef + else: + if moddef.c_m_size > 0 or moddef.c_m_traverse or moddef.c_m_clear or \ + moddef.c_m_free: + raise oefmt(space.w_SystemError, + "module %s is not a module object, but requests " + "module state", name) + if has_execution_slots: + raise oefmt(space.w_SystemError, + "module %s specifies execution slots, but did not " + "create a ModuleType instance", name) + dict_w = {} + convert_method_defs(space, dict_w, moddef.c_m_methods, None, w_mod, name) + for key, w_value in dict_w.items(): + space.setattr(w_mod, space.newtext(key), w_value) + if moddef.c_m_doc: + doc = rffi.charp2str(rffi.cast(rffi.CCHARP, moddef.c_m_doc)) + space.setattr(w_mod, space.newtext('__doc__'), space.newtext(doc)) + return w_mod + + +def exec_def(space, w_mod, mod_as_pyobj): + from pypy.module.cpyext.pyerrors import PyErr_Occurred + mod = rffi.cast(PyModuleObject, mod_as_pyobj) + moddef = mod.c_md_def + cur_slot = rffi.cast(rffi.CArrayPtr(PyModuleDef_Slot), moddef.c_m_slots) + while cur_slot and rffi.cast(lltype.Signed, cur_slot[0].c_slot): + if rffi.cast(lltype.Signed, cur_slot[0].c_slot) == 2: + execf = rffi.cast(execfunctype, cur_slot[0].c_value) + res = generic_cpy_call(space, execf, w_mod) + has_error = PyErr_Occurred(space) is not None + if rffi.cast(lltype.Signed, res): + if has_error: + state = space.fromcache(State) + state.check_and_raise_exception() + else: + raise oefmt(space.w_SystemError, + "execution of module %S failed without " + "setting an exception", w_mod.w_name) + if has_error: + raise oefmt(space.w_SystemError, + "execution of module %S raised unreported " + "exception", w_mod.w_name) + cur_slot = rffi.ptradd(cur_slot, 1) + + def convert_method_defs(space, dict_w, methods, w_type, w_self=None, name=None): w_name = space.newtext_or_none(name) methods = rffi.cast(rffi.CArrayPtr(PyMethodDef), methods) diff --git a/pypy/module/cpyext/slotdefs.py b/pypy/module/cpyext/slotdefs.py --- a/pypy/module/cpyext/slotdefs.py +++ b/pypy/module/cpyext/slotdefs.py @@ -4,10 +4,10 @@ from rpython.rtyper.lltypesystem import rffi, lltype from rpython.rlib.rarithmetic import widen -from rpython.rlib import rgc # Force registration of gc.collect +from rpython.rlib import rgc # Force registration of gc.collect from pypy.module.cpyext.api import ( slot_function, generic_cpy_call, PyObject, Py_ssize_t, - pypy_decl, Py_buffer, Py_bufferP, PyTypeObjectPtr, cts) + Py_buffer, Py_bufferP, PyTypeObjectPtr, cts) from pypy.module.cpyext.typeobjectdefs import ( unaryfunc, ternaryfunc, binaryfunc, getattrfunc, getattrofunc, setattrofunc, lenfunc, ssizeargfunc, inquiry, @@ -794,6 +794,16 @@ return 0 return buff_w +missing_wrappers = ['wrap_indexargfunc', 'wrap_del'] +for name in missing_wrappers: + assert name not in globals() + def missing_wrapper(space, w_self, w_args, func): + print "cpyext: missing slot wrapper " + name + raise NotImplementedError("Slot wrapper " + name) + missing_wrapper.__name__ = name + globals()[name] = missing_wrapper + + PyWrapperFlag_KEYWORDS = 1 class TypeSlot: @@ -811,7 +821,7 @@ if WRAPPER is None: wrapper = None else: - wrapper = globals().get(WRAPPER, Ellipsis) + wrapper = globals()[WRAPPER] # irregular interface, because of tp_getattr/tp_getattro confusion if NAME == "__getattr__": @@ -825,17 +835,9 @@ function = getattr(userslot, FUNCTION or '!missing', None) assert FLAGS == 0 or FLAGS == PyWrapperFlag_KEYWORDS if FLAGS: - if wrapper is Ellipsis: - @func_renamer(WRAPPER) - def wrapper(space, w_self, w_args, func, w_kwds): - raise NotImplementedError("Wrapper for slot " + NAME) wrapper1 = None wrapper2 = wrapper else: - if wrapper is Ellipsis: - @func_renamer(WRAPPER) - def wrapper(space, w_self, w_args, func): - raise NotImplementedError("Wrapper for slot " + NAME) wrapper1 = wrapper wrapper2 = None return TypeSlot(NAME, SLOT, function, wrapper1, wrapper2, DOC) diff --git a/pypy/module/cpyext/src/modsupport.c b/pypy/module/cpyext/src/modsupport.c --- a/pypy/module/cpyext/src/modsupport.c +++ b/pypy/module/cpyext/src/modsupport.c @@ -602,3 +602,26 @@ } return ((PyModuleObject *)m)->md_def; } + +PyTypeObject PyModuleDef_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, 0) + "moduledef", /* tp_name */ + sizeof(struct PyModuleDef), /* tp_size */ + 0, /* tp_itemsize */ +}; + +static Py_ssize_t max_module_number; + +PyObject* +PyModuleDef_Init(struct PyModuleDef* def) +{ + if (PyType_Ready(&PyModuleDef_Type) < 0) + return NULL; + if (def->m_base.m_index == 0) { + max_module_number++; + Py_REFCNT(def) = 1; + Py_TYPE(def) = &PyModuleDef_Type; + def->m_base.m_index = max_module_number; + } + return (PyObject*)def; +} diff --git a/pypy/module/cpyext/test/buffer_test.c b/pypy/module/cpyext/test/buffer_test.c --- a/pypy/module/cpyext/test/buffer_test.c +++ b/pypy/module/cpyext/test/buffer_test.c @@ -344,7 +344,6 @@ #endif if (m == NULL) INITERROR; - PyMyArrayType.tp_new = PyType_GenericNew; if (PyType_Ready(&PyMyArrayType) < 0) INITERROR; Py_INCREF(&PyMyArrayType); diff --git a/pypy/module/cpyext/test/comparisons.c b/pypy/module/cpyext/test/comparisons.c --- a/pypy/module/cpyext/test/comparisons.c +++ b/pypy/module/cpyext/test/comparisons.c @@ -68,7 +68,7 @@ 0, /* tp_dictoffset */ 0, /* tp_init */ 0, /* tp_alloc */ - 0, /* tp_new */ + PyType_GenericNew, /* tp_new */ 0 /* tp_free */ }; @@ -87,7 +87,6 @@ { PyObject *m, *d; - CmpType.tp_new = PyType_GenericNew; if (PyType_Ready(&CmpType) < 0) return NULL; m = PyModule_Create(&moduledef); diff --git a/pypy/module/cpyext/test/foo.c b/pypy/module/cpyext/test/foo.c --- a/pypy/module/cpyext/test/foo.c +++ b/pypy/module/cpyext/test/foo.c @@ -36,14 +36,14 @@ Py_ssize_t foo_ssizet; } fooobject; -static PyTypeObject footype; +static PyTypeObject fooType; static fooobject * newfooobject(void) { fooobject *foop; - foop = PyObject_New(fooobject, &footype); + foop = PyObject_New(fooobject, &fooType); if (foop == NULL) return NULL; @@ -160,6 +160,28 @@ return PyObject_GenericSetAttr((PyObject *)self, name, value); } +static PyObject * +new_fooType(PyTypeObject * t, PyObject *args, PyObject *kwds) +{ + PyObject * o; + /* copied from numpy scalartypes.c for inherited classes */ + if (t->tp_bases && (PyTuple_GET_SIZE(t->tp_bases) > 1)) + { + PyTypeObject *sup; + /* We are inheriting from a Python type as well so + give it first dibs on conversion */ + sup = (PyTypeObject *)PyTuple_GET_ITEM(t->tp_bases, 1); + /* Prevent recursion */ + if (new_fooType != sup->tp_new) + { + o = sup->tp_new(t, args, kwds); + return o; + } + } + o = t->tp_alloc(t, 0); + return o; +}; + static PyMemberDef foo_members[] = { {"int_member", T_INT, offsetof(fooobject, foo), 0, "A helpful docstring."}, @@ -194,7 +216,7 @@ PyDoc_STRVAR(foo_doc, "foo is for testing."); -static PyTypeObject footype = { +static PyTypeObject fooType = { PyVarObject_HEAD_INIT(NULL, 0) "foo.foo", /*tp_name*/ sizeof(fooobject), /*tp_size*/ @@ -592,7 +614,7 @@ 0, /*tp_init*/ 0, /*tp_alloc will be set to PyType_GenericAlloc in module init*/ - 0, /*tp_new*/ + PyType_GenericNew, /*tp_new*/ 0, /*tp_free Low-level free-memory routine */ 0, /*tp_is_gc For PyObject_IS_GC */ 0, /*tp_bases*/ @@ -648,6 +670,37 @@ return PyInt_FromLong(tf); } +static PyTypeObject GetType1 = { + PyVarObject_HEAD_INIT(NULL, 0) + "foo.GetType1", /*tp_name*/ + sizeof(PyObject), /*tp_size*/ +}; +static PyTypeObject GetType2 = { + PyVarObject_HEAD_INIT(NULL, 0) + "foo.GetType2", /*tp_name*/ + sizeof(PyObject), /*tp_size*/ +}; +static PyObject *gettype1, *gettype2; + +static PyObject *gettype1_getattr(PyObject *self, char *name) +{ + char buf[200]; + strcpy(buf, "getattr:"); + strcat(buf, name); + return PyBytes_FromString(buf); +} +static PyObject *gettype2_getattro(PyObject *self, PyObject *name) +{ + char buf[200]; + PyObject* temp; + temp = PyUnicode_AsASCIIString(name); + if (temp == NULL) return NULL; + strcpy(buf, "getattro:"); + strcat(buf, PyBytes_AS_STRING(temp)); + return PyBytes_FromString(buf); +} + + /* List of functions exported by this module */ static PyMethodDef foo_functions[] = { @@ -706,13 +759,14 @@ if (module == NULL) INITERROR; - footype.tp_new = PyType_GenericNew; - UnicodeSubtype.tp_base = &PyUnicode_Type; UnicodeSubtype2.tp_base = &UnicodeSubtype; MetaType.tp_base = &PyType_Type; - if (PyType_Ready(&footype) < 0) + fooType.tp_new = &new_fooType; + InitErrType.tp_new = PyType_GenericNew; + + if (PyType_Ready(&fooType) < 0) INITERROR; if (PyType_Ready(&UnicodeSubtype) < 0) INITERROR; @@ -725,8 +779,6 @@ if (PyType_Ready(&SimplePropertyType) < 0) INITERROR; - SimplePropertyType.tp_new = PyType_GenericNew; - InitErrType.tp_new = PyType_GenericNew; Py_TYPE(&CustomType) = &MetaType; if (PyType_Ready(&CustomType) < 0) @@ -744,11 +796,23 @@ if (PyType_Ready(&TupleLike) < 0) INITERROR; + GetType1.tp_flags = Py_TPFLAGS_DEFAULT; + GetType1.tp_getattr = &gettype1_getattr; + if (PyType_Ready(&GetType1) < 0) + INITERROR; + gettype1 = PyObject_New(PyObject, &GetType1); + + GetType2.tp_flags = Py_TPFLAGS_DEFAULT; + GetType2.tp_getattro = &gettype2_getattro; + if (PyType_Ready(&GetType2) < 0) + INITERROR; + gettype2 = PyObject_New(PyObject, &GetType2); + d = PyModule_GetDict(module); if (d == NULL) INITERROR; - if (PyDict_SetItemString(d, "fooType", (PyObject *)&footype) < 0) + if (PyDict_SetItemString(d, "fooType", (PyObject *)&fooType) < 0) INITERROR; if (PyDict_SetItemString(d, "UnicodeSubtype", (PyObject *) &UnicodeSubtype) < 0) INITERROR; @@ -766,6 +830,10 @@ INITERROR; if (PyDict_SetItemString(d, "TupleLike", (PyObject *) &TupleLike) < 0) INITERROR; + if (PyDict_SetItemString(d, "gettype1", gettype1) < 0) + INITERROR; + if (PyDict_SetItemString(d, "gettype2", gettype2) < 0) + INITERROR; #if PY_MAJOR_VERSION >=3 return module; #endif diff --git a/pypy/module/cpyext/test/multiphase.c b/pypy/module/cpyext/test/multiphase.c new file mode 100644 --- /dev/null +++ b/pypy/module/cpyext/test/multiphase.c @@ -0,0 +1,28 @@ +#include "Python.h" + +static struct PyModuleDef multiphase_def; + +static PyObject *check_getdef_same(PyObject *self, PyObject *args) { + return PyBool_FromLong(PyModule_GetDef(self) == &multiphase_def); +} + +static PyMethodDef methods[] = { + {"check_getdef_same", check_getdef_same, METH_NOARGS}, + {NULL} +}; + +static PyModuleDef multiphase_def = { + PyModuleDef_HEAD_INIT, /* m_base */ + "multiphase", /* m_name */ + "example docstring", /* m_doc */ + 0, /* m_size */ + methods, /* m_methods */ + NULL, /* m_slots */ + NULL, /* m_traverse */ + NULL, /* m_clear */ + NULL, /* m_free */ +}; + +PyMODINIT_FUNC PyInit_multiphase(void) { + return PyModuleDef_Init(&multiphase_def); +} diff --git a/pypy/module/cpyext/test/test_classobject.py b/pypy/module/cpyext/test/test_classobject.py --- a/pypy/module/cpyext/test/test_classobject.py +++ b/pypy/module/cpyext/test/test_classobject.py @@ -27,15 +27,3 @@ InstanceMethod.testmethod.attribute = "test" assert testfunction.attribute == "test" raises(AttributeError, setattr, inst.testmethod, "attribute", "test") - - def test_pyclass_new_no_bases(self): - module = self.import_extension('foo', [ - ("new_foo", "METH_O", - """ - return PyClass_New(NULL, PyDict_New(), args); - """)]) - FooClass = module.new_foo("FooClass") - class Cls1: - pass - assert type(FooClass) is type(Cls1) - assert FooClass.__bases__ == Cls1.__bases__ diff --git a/pypy/module/cpyext/test/test_cpyext.py b/pypy/module/cpyext/test/test_cpyext.py --- a/pypy/module/cpyext/test/test_cpyext.py +++ b/pypy/module/cpyext/test/test_cpyext.py @@ -40,9 +40,11 @@ def load_module(self, mod, name): space = self.space - api.load_extension_module(space, mod, name) - return space.getitem( - space.sys.get('modules'), space.wrap(name)) + w_path = space.newtext(mod) + w_name = space.newtext(name) + return space.appexec([w_name, w_path], '''(name, path): + import imp + return imp.load_dynamic(name, path)''') def get_cpyext_info(space): diff --git a/pypy/module/cpyext/test/test_memoryobject.py b/pypy/module/cpyext/test/test_memoryobject.py --- a/pypy/module/cpyext/test/test_memoryobject.py +++ b/pypy/module/cpyext/test/test_memoryobject.py @@ -187,6 +187,10 @@ " on too long format string" finally: warnings.resetwarnings() + # calling get_buffer_info on x creates a memory leak, + # which is detected as an error at test teardown: + # Exception TypeError: "'NoneType' object is not callable" + # in <bound method ConcreteArray.__del__ ...> ignored def test_releasebuffer(self): if not self.runappdirect: diff --git a/pypy/module/cpyext/test/test_module.py b/pypy/module/cpyext/test/test_module.py --- a/pypy/module/cpyext/test/test_module.py +++ b/pypy/module/cpyext/test/test_module.py @@ -25,12 +25,85 @@ module = self.import_extension('foo', [ ("check_getdef_same", "METH_NOARGS", """ - return PyBool_FromLong(PyModule_GetDef(mod_global) == &moduledef); + return PyBool_FromLong(PyModule_GetDef(self) == &moduledef); """ )], prologue=""" static struct PyModuleDef moduledef; - static PyObject *mod_global; - """, more_init=""" - mod_global = mod; """) assert module.check_getdef_same() + + +class AppTestMultiPhase(AppTestCpythonExtensionBase): + def test_basic(self): + from types import ModuleType + module = self.import_module(name='multiphase') + assert isinstance(module, ModuleType) + assert module.__name__ == 'multiphase' + assert module.__doc__ == "example docstring" + + def test_getdef(self): + from types import ModuleType + module = self.import_module(name='multiphase') + assert module.check_getdef_same() + + def test_slots(self): + from types import ModuleType + body = """ + static PyModuleDef multiphase_def; + + static PyObject* multiphase_create(PyObject *spec, PyModuleDef *def) { + PyObject *module = PyModule_New("altname"); + PyObject_SetAttrString(module, "create_spec", spec); + PyObject_SetAttrString(module, "create_def_eq", + PyBool_FromLong(def == &multiphase_def)); + return module; + } + + static int multiphase_exec(PyObject* module) { + Py_INCREF(Py_True); + PyObject_SetAttrString(module, "exec_called", Py_True); + return 0; + } + + static PyModuleDef_Slot multiphase_slots[] = { + {Py_mod_create, multiphase_create}, + {Py_mod_exec, multiphase_exec}, + {0, NULL} + }; + + static PyModuleDef multiphase_def = { + PyModuleDef_HEAD_INIT, /* m_base */ + "multiphase", /* m_name */ + "example docstring", /* m_doc */ + 0, /* m_size */ + NULL, /* m_methods */ + multiphase_slots, /* m_slots */ + NULL, /* m_traverse */ + NULL, /* m_clear */ + NULL, /* m_free */ + }; + """ + init = """ + return PyModuleDef_Init(&multiphase_def); + """ + module = self.import_module(name='multiphase', body=body, init=init) + assert module.create_spec + assert module.create_spec is module.__spec__ + assert module.create_def_eq + assert module.exec_called + + def test_forget_init(self): + from types import ModuleType + body = """ + static PyModuleDef multiphase_def = { + PyModuleDef_HEAD_INIT, /* m_base */ + "multiphase", /* m_name */ + "example docstring", /* m_doc */ + 0, /* m_size */ + }; + """ + init = """ + return (PyObject *) &multiphase_def; + """ + raises(SystemError, self.import_module, name='multiphase', body=body, + init=init) diff --git a/pypy/module/cpyext/test/test_typeobject.py b/pypy/module/cpyext/test/test_typeobject.py --- a/pypy/module/cpyext/test/test_typeobject.py +++ b/pypy/module/cpyext/test/test_typeobject.py @@ -16,12 +16,12 @@ assert 'foo' in sys.modules assert "copy" in dir(module.fooType) obj = module.new() - print(obj.foo) + #print(obj.foo) assert obj.foo == 42 - print("Obj has type", type(obj)) + #print("Obj has type", type(obj)) assert type(obj) is module.fooType - print("type of obj has type", type(type(obj))) - print("type of type of obj has type", type(type(type(obj)))) + #print("type of obj has type", type(type(obj))) + #print("type of type of obj has type", type(type(type(obj)))) assert module.fooType.__doc__ == "foo is for testing." def test_typeobject_method_descriptor(self): @@ -949,6 +949,8 @@ pass class foo(f2, f1): pass + + x = foo() assert bar.__base__ is f2 # On cpython, the size changes. if '__pypy__' in sys.builtin_module_names: @@ -1159,8 +1161,8 @@ ((PyHeapTypeObject*)Base2)->ht_name = dummyname; ((PyHeapTypeObject*)Base12)->ht_name = dummyname; } - #endif - #endif + #endif + #endif Base1->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HEAPTYPE; Base2->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HEAPTYPE; Base12->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HEAPTYPE; @@ -1198,24 +1200,13 @@ bases = module.foo(C) assert bases == (A, B) - def test_multiple_inheritance_old_style_base(self): - module = self.import_extension('foo', [ - ("foo", "METH_O", - ''' - PyTypeObject *tp; - tp = (PyTypeObject*)args; - Py_INCREF(tp->tp_bases); - return tp->tp_bases; - ''' - )]) - # used to segfault after some iterations - for i in range(11): - print i - class A(object): - pass - class B: - pass - class C(A, B): - pass - bases = module.foo(C) - assert bases == (A, B) + def test_getattr_getattro(self): + module = self.import_module(name='foo') + assert module.gettype2.dcba == b'getattro:dcba' + assert (type(module.gettype2).__getattribute__(module.gettype2, 'dcBA') + == b'getattro:dcBA') + assert module.gettype1.abcd == b'getattr:abcd' + # GetType1 objects have a __getattribute__ method, but this + # doesn't call tp_getattr at all, also on CPython + raises(AttributeError, type(module.gettype1).__getattribute__, + module.gettype1, 'dcBA') diff --git a/pypy/module/cpyext/typeobject.py b/pypy/module/cpyext/typeobject.py --- a/pypy/module/cpyext/typeobject.py +++ b/pypy/module/cpyext/typeobject.py @@ -682,10 +682,6 @@ if pto.c_tp_itemsize < pto.c_tp_base.c_tp_itemsize: pto.c_tp_itemsize = pto.c_tp_base.c_tp_itemsize - if space.is_w(w_type, space.w_object): - # will be filled later on with the correct value - # may not be 0 - pto.c_tp_new = cts.cast('newfunc', 1) update_all_slots(space, w_type, pto) if not pto.c_tp_new: base_object_pyo = make_ref(space, space.w_object) diff --git a/pypy/module/exceptions/__init__.py b/pypy/module/exceptions/__init__.py _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit