Author: Matti Picus <matti.pi...@gmail.com> Branch: unicode-utf8 Changeset: r95273:249385a41d6e Date: 2018-11-04 11:05 -0500 http://bitbucket.org/pypy/pypy/changeset/249385a41d6e/
Log: merge default into branch diff too long, truncating to 2000 out of 2058 lines diff --git a/lib-python/2.7/shutil.py b/lib-python/2.7/shutil.py --- a/lib-python/2.7/shutil.py +++ b/lib-python/2.7/shutil.py @@ -396,17 +396,21 @@ return archive_name -def _call_external_zip(base_dir, zip_filename, verbose=False, dry_run=False): +def _call_external_zip(base_dir, zip_filename, verbose, dry_run, logger): # XXX see if we want to keep an external call here if verbose: zipoptions = "-r" else: zipoptions = "-rq" - from distutils.errors import DistutilsExecError - from distutils.spawn import spawn + cmd = ["zip", zipoptions, zip_filename, base_dir] + if logger is not None: + logger.info(' '.join(cmd)) + if dry_run: + return + import subprocess try: - spawn(["zip", zipoptions, zip_filename, base_dir], dry_run=dry_run) - except DistutilsExecError: + subprocess.check_call(cmd) + except subprocess.CalledProcessError: # XXX really should distinguish between "couldn't find # external 'zip' command" and "zip failed". raise ExecError, \ @@ -440,7 +444,7 @@ zipfile = None if zipfile is None: - _call_external_zip(base_dir, zip_filename, verbose, dry_run) + _call_external_zip(base_dir, zip_filename, verbose, dry_run, logger) else: if logger is not None: logger.info("creating '%s' and adding '%s' to it", diff --git a/lib-python/2.7/test/test_inspect.py b/lib-python/2.7/test/test_inspect.py --- a/lib-python/2.7/test/test_inspect.py +++ b/lib-python/2.7/test/test_inspect.py @@ -45,6 +45,9 @@ git = mod.StupidGit() +class ExampleClassWithSlot(object): + __slots__ = 'myslot' + class IsTestBase(unittest.TestCase): predicates = set([inspect.isbuiltin, inspect.isclass, inspect.iscode, inspect.isframe, inspect.isfunction, inspect.ismethod, @@ -96,7 +99,11 @@ else: self.assertFalse(inspect.isgetsetdescriptor(type(tb.tb_frame).f_locals)) if hasattr(types, 'MemberDescriptorType'): - self.istest(inspect.ismemberdescriptor, 'type(lambda: None).func_globals') + # App-level slots are member descriptors on both PyPy and + # CPython, but the various built-in attributes are all + # getsetdescriptors on PyPy. So check ismemberdescriptor() + # with an app-level slot. + self.istest(inspect.ismemberdescriptor, 'ExampleClassWithSlot.myslot') else: self.assertFalse(inspect.ismemberdescriptor(type(lambda: None).func_globals)) diff --git a/lib-python/2.7/threading.py b/lib-python/2.7/threading.py --- a/lib-python/2.7/threading.py +++ b/lib-python/2.7/threading.py @@ -36,6 +36,10 @@ _allocate_lock = thread.allocate_lock _get_ident = thread.get_ident ThreadError = thread.error +try: + _CRLock = thread.RLock +except AttributeError: + _CRLock = None del thread @@ -120,7 +124,9 @@ acquired it. """ - return _RLock(*args, **kwargs) + if _CRLock is None or args or kwargs: + return _PyRLock(*args, **kwargs) + return _CRLock(_active) class _RLock(_Verbose): """A reentrant lock must be released by the thread that acquired it. Once a @@ -238,6 +244,8 @@ def _is_owned(self): return self.__owner == _get_ident() +_PyRLock = _RLock + def Condition(*args, **kwargs): """Factory function that returns a new condition variable object. diff --git a/lib_pypy/_ctypes/function.py b/lib_pypy/_ctypes/function.py --- a/lib_pypy/_ctypes/function.py +++ b/lib_pypy/_ctypes/function.py @@ -486,6 +486,8 @@ return cobj, cobj._to_ffi_param(), type(cobj) def _convert_args_for_callback(self, argtypes, args): + from _ctypes.structure import StructOrUnion + # assert len(argtypes) == len(args) newargs = [] for argtype, arg in zip(argtypes, args): @@ -495,6 +497,10 @@ param = param._get_buffer_value() elif self._is_primitive(argtype): param = param.value + elif isinstance(param, StructOrUnion): # not a *pointer* to struct + newparam = StructOrUnion.__new__(type(param)) + param._copy_to(newparam._buffer.buffer) + param = newparam newargs.append(param) return newargs diff --git a/lib_pypy/cffi/setuptools_ext.py b/lib_pypy/cffi/setuptools_ext.py --- a/lib_pypy/cffi/setuptools_ext.py +++ b/lib_pypy/cffi/setuptools_ext.py @@ -162,6 +162,17 @@ module_path = module_name.split('.') module_path[-1] += '.py' generate_mod(os.path.join(self.build_lib, *module_path)) + def get_source_files(self): + # This is called from 'setup.py sdist' only. Exclude + # the generate .py module in this case. + saved_py_modules = self.py_modules + try: + if saved_py_modules: + self.py_modules = [m for m in saved_py_modules + if m != module_name] + return base_class.get_source_files(self) + finally: + self.py_modules = saved_py_modules dist.cmdclass['build_py'] = build_py_make_mod # distutils and setuptools have no notion I could find of a @@ -171,6 +182,7 @@ # the module. So we add it here, which gives a few apparently # harmless warnings about not finding the file outside the # build directory. + # Then we need to hack more in get_source_files(); see above. if dist.py_modules is None: dist.py_modules = [] dist.py_modules.append(module_name) diff --git a/lib_pypy/pyrepl/unix_console.py b/lib_pypy/pyrepl/unix_console.py --- a/lib_pypy/pyrepl/unix_console.py +++ b/lib_pypy/pyrepl/unix_console.py @@ -26,6 +26,12 @@ from pyrepl.fancy_termios import tcgetattr, tcsetattr from pyrepl.console import Console, Event from pyrepl import unix_eventqueue +try: + from __pypy__ import pyos_inputhook +except ImportError: + def pyos_inputhook(): + pass + class InvalidTerminal(RuntimeError): pass @@ -70,8 +76,8 @@ pass def register(self, fd, flag): self.fd = fd - def poll(self, timeout=None): - r,w,e = select.select([self.fd],[],[],timeout) + def poll(self): # note: a 'timeout' argument would be *milliseconds* + r,w,e = select.select([self.fd],[],[]) return r POLLIN = getattr(select, "POLLIN", None) @@ -416,6 +422,7 @@ def get_event(self, block=1): while self.event_queue.empty(): while 1: # All hail Unix! + pyos_inputhook() try: self.push_char(os.read(self.input_fd, 1)) except (IOError, OSError), err: 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 @@ -36,7 +36,22 @@ Small refactorings in the Python parser. +.. branch: fix-readme-typo + +.. branch: avoid_shell_injection_in_shutil + .. branch: unicode-utf8-re .. branch: utf8-io Utf8 handling for unicode + +Backport CPython fix for possible shell injection issue in `distutils.spawn`, +https://bugs.python.org/issue34540 + +.. branch: cffi_dlopen_unicode + +Enable use of unicode file names in `dlopen` + +.. branch: rlock-in-rpython + +Backport CPython fix for `thread.RLock` diff --git a/pypy/module/__builtin__/functional.py b/pypy/module/__builtin__/functional.py --- a/pypy/module/__builtin__/functional.py +++ b/pypy/module/__builtin__/functional.py @@ -136,89 +136,89 @@ max_jitdriver = jit.JitDriver(name='max', greens=['has_key', 'has_item', 'w_type'], reds='auto') -def make_min_max(unroll): - @specialize.arg(2) - def min_max_impl(space, args, implementation_of): - if implementation_of == "max": - compare = space.gt - jitdriver = max_jitdriver +@specialize.arg(3) +def min_max_sequence(space, w_sequence, w_key, implementation_of): + if implementation_of == "max": + compare = space.gt + jitdriver = max_jitdriver + else: + compare = space.lt + jitdriver = min_jitdriver + w_iter = space.iter(w_sequence) + w_type = space.type(w_iter) + has_key = w_key is not None + has_item = False + w_max_item = None + w_max_val = None + while True: + jitdriver.jit_merge_point(has_key=has_key, has_item=has_item, + w_type=w_type) + try: + w_item = space.next(w_iter) + except OperationError as e: + if not e.match(space, space.w_StopIteration): + raise + break + if has_key: + w_compare_with = space.call_function(w_key, w_item) else: - compare = space.lt - jitdriver = min_jitdriver - any_kwds = bool(args.keywords) - args_w = args.arguments_w - if len(args_w) > 1: - if unroll and len(args_w) == 2 and not any_kwds: - # a fast path for the common case, useful for interpreted - # mode and to reduce the length of the jit trace - w0, w1 = args_w - if space.is_true(compare(w1, w0)): - return w1 - else: - return w0 - w_sequence = space.newtuple(args_w) - elif len(args_w): - w_sequence = args_w[0] + w_compare_with = w_item + if (not has_item or + space.is_true(compare(w_compare_with, w_max_val))): + has_item = True + w_max_item = w_item + w_max_val = w_compare_with + if w_max_item is None: + raise oefmt(space.w_ValueError, "arg is an empty sequence") + return w_max_item + +@specialize.arg(3) +@jit.look_inside_iff(lambda space, args_w, w_key, implementation_of: + jit.loop_unrolling_heuristic(args_w, len(args_w), 3)) +def min_max_multiple_args(space, args_w, w_key, implementation_of): + # case of multiple arguments (at least two). We unroll it if there + # are 2 or 3 arguments. + if implementation_of == "max": + compare = space.gt + else: + compare = space.lt + w_max_item = args_w[0] + if w_key is not None: + w_max_val = space.call_function(w_key, w_max_item) + else: + w_max_val = w_max_item + for i in range(1, len(args_w)): + w_item = args_w[i] + if w_key is not None: + w_compare_with = space.call_function(w_key, w_item) else: - raise oefmt(space.w_TypeError, - "%s() expects at least one argument", - implementation_of) - w_key = None - if any_kwds: - kwds = args.keywords - if kwds[0] == "key" and len(kwds) == 1: - w_key = args.keywords_w[0] - else: - raise oefmt(space.w_TypeError, - "%s() got unexpected keyword argument", - implementation_of) - - w_iter = space.iter(w_sequence) - w_type = space.type(w_iter) - has_key = w_key is not None - has_item = False - w_max_item = None - w_max_val = None - while True: - if not unroll: - jitdriver.jit_merge_point(has_key=has_key, has_item=has_item, w_type=w_type) - try: - w_item = space.next(w_iter) - except OperationError as e: - if not e.match(space, space.w_StopIteration): - raise - break - if has_key: - w_compare_with = space.call_function(w_key, w_item) - else: - w_compare_with = w_item - if not has_item or \ - space.is_true(compare(w_compare_with, w_max_val)): - has_item = True - w_max_item = w_item - w_max_val = w_compare_with - if w_max_item is None: - raise oefmt(space.w_ValueError, "arg is an empty sequence") - return w_max_item - if unroll: - min_max_impl = jit.unroll_safe(min_max_impl) - return min_max_impl - -min_max_unroll = make_min_max(True) -min_max_normal = make_min_max(False) + w_compare_with = w_item + if space.is_true(compare(w_compare_with, w_max_val)): + w_max_item = w_item + w_max_val = w_compare_with + return w_max_item @specialize.arg(2) def min_max(space, args, implementation_of): - # the 'normal' version includes a JIT merge point, which will make a - # new loop (from the interpreter or from another JIT loop). If we - # give exactly two arguments to the call to max(), or a JIT virtual - # list of arguments, then we pick the 'unroll' version with no JIT - # merge point. - if jit.isvirtual(args.arguments_w) or len(args.arguments_w) == 2: - return min_max_unroll(space, args, implementation_of) + w_key = None + if bool(args.keywords): + kwds = args.keywords + if kwds[0] == "key" and len(kwds) == 1: + w_key = args.keywords_w[0] + else: + raise oefmt(space.w_TypeError, + "%s() got unexpected keyword argument", + implementation_of) + # + args_w = args.arguments_w + if len(args_w) > 1: + return min_max_multiple_args(space, args_w, w_key, implementation_of) + elif len(args_w): + return min_max_sequence(space, args_w[0], w_key, implementation_of) else: - return min_max_normal(space, args, implementation_of) -min_max._always_inline = True + raise oefmt(space.w_TypeError, + "%s() expects at least one argument", + implementation_of) def max(space, __args__): """max(iterable[, key=func]) -> value diff --git a/pypy/module/__builtin__/test/test_functional.py b/pypy/module/__builtin__/test/test_functional.py --- a/pypy/module/__builtin__/test/test_functional.py +++ b/pypy/module/__builtin__/test/test_functional.py @@ -353,6 +353,10 @@ assert type(max(1.0, 1L, 1)) is float assert type(max(1L, 1, 1.0)) is long + def test_max_list_and_key(self): + assert max(["100", "50", "30", "-200"], key=int) == "100" + assert max("100", "50", "30", "-200", key=int) == "100" + try: from hypothesis import given, strategies, example 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 @@ -109,6 +109,7 @@ '_promote' : 'interp_magic._promote', 'side_effects_ok' : 'interp_magic.side_effects_ok', 'stack_almost_full' : 'interp_magic.stack_almost_full', + 'pyos_inputhook' : 'interp_magic.pyos_inputhook', } if sys.platform == 'win32': interpleveldefs['get_console_cp'] = 'interp_magic.get_console_cp' 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 @@ -211,3 +211,13 @@ def revdb_stop(space): from pypy.interpreter.reverse_debugging import stop_point stop_point() + +def pyos_inputhook(space): + """Call PyOS_InputHook() from the CPython C API.""" + if not space.config.objspace.usemodules.cpyext: + return + w_modules = space.sys.get('modules') + if space.finditem_str(w_modules, 'cpyext') is None: + return # cpyext not imported yet, ignore + from pypy.module.cpyext.api import invoke_pyos_inputhook + invoke_pyos_inputhook(space) 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 @@ -1,31 +1,24 @@ from rpython.rtyper.lltypesystem import lltype, llmemory, rffi from rpython.rlib.objectmodel import specialize, we_are_translated -from rpython.rlib.rdynload import DLLHANDLE, dlopen, dlsym, dlclose, DLOpenError +from rpython.rlib.rdynload import DLLHANDLE, dlsym, dlclose from pypy.interpreter.error import oefmt -from pypy.module._rawffi.interp_rawffi import wrap_dlopenerror from pypy.module._cffi_backend.parse_c_type import ( _CFFI_OPCODE_T, GLOBAL_S, CDL_INTCONST_S, STRUCT_UNION_S, FIELD_S, ENUM_S, TYPENAME_S, ll_set_cdl_realize_global_int) from pypy.module._cffi_backend.realize_c_type import getop from pypy.module._cffi_backend.lib_obj import W_LibObject -from pypy.module._cffi_backend import cffi_opcode, cffi1_module - +from pypy.module._cffi_backend import cffi_opcode, cffi1_module, misc class W_DlOpenLibObject(W_LibObject): - def __init__(self, ffi, filename, flags): - with rffi.scoped_str2charp(filename) as ll_libname: - if filename is None: - filename = "<None>" - try: - handle = dlopen(ll_libname, flags) - except DLOpenError as e: - raise wrap_dlopenerror(ffi.space, e, filename) - W_LibObject.__init__(self, ffi, filename) + def __init__(self, ffi, w_filename, flags): + space = ffi.space + fname, handle = misc.dlopen_w(space, w_filename, flags) + W_LibObject.__init__(self, ffi, fname) self.libhandle = handle - self.register_finalizer(ffi.space) + self.register_finalizer(space) def _finalize_(self): h = self.libhandle diff --git a/pypy/module/_cffi_backend/embedding.py b/pypy/module/_cffi_backend/embedding.py --- a/pypy/module/_cffi_backend/embedding.py +++ b/pypy/module/_cffi_backend/embedding.py @@ -95,7 +95,9 @@ if os.name == 'nt': do_includes = r""" +#ifndef _WIN32_WINNT #define _WIN32_WINNT 0x0501 +#endif #include <windows.h> static void _cffi_init(void); 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 @@ -572,8 +572,8 @@ return self.ffi_type(w_arg, ACCEPT_STRING | ACCEPT_CDATA) - @unwrap_spec(filename="fsencode_or_none", flags=int) - def descr_dlopen(self, filename, flags=0): + @unwrap_spec(flags=int) + def descr_dlopen(self, w_filename, flags=0): """\ Load and return a dynamic library identified by 'name'. The standard C library can be loaded by passing None. @@ -584,7 +584,7 @@ first access.""" # from pypy.module._cffi_backend import cdlopen - return cdlopen.W_DlOpenLibObject(self, filename, flags) + return cdlopen.W_DlOpenLibObject(self, w_filename, flags) def descr_dlclose(self, w_lib): diff --git a/pypy/module/_cffi_backend/libraryobj.py b/pypy/module/_cffi_backend/libraryobj.py --- a/pypy/module/_cffi_backend/libraryobj.py +++ b/pypy/module/_cffi_backend/libraryobj.py @@ -4,28 +4,21 @@ from pypy.interpreter.error import oefmt from pypy.interpreter.gateway import interp2app, unwrap_spec from pypy.interpreter.typedef import TypeDef -from pypy.module._rawffi.interp_rawffi import wrap_dlopenerror from rpython.rtyper.lltypesystem import rffi -from rpython.rlib.rdynload import DLLHANDLE, dlopen, dlsym, dlclose, DLOpenError +from rpython.rlib.rdynload import DLLHANDLE, dlsym, dlclose from pypy.module._cffi_backend.cdataobj import W_CData from pypy.module._cffi_backend.ctypeobj import W_CType +from pypy.module._cffi_backend import misc class W_Library(W_Root): _immutable_ = True - def __init__(self, space, filename, flags): + def __init__(self, space, w_filename, flags): self.space = space - with rffi.scoped_str2charp(filename) as ll_libname: - if filename is None: - filename = "<None>" - try: - self.handle = dlopen(ll_libname, flags) - except DLOpenError as e: - raise wrap_dlopenerror(space, e, filename) - self.name = filename + self.name, self.handle = misc.dlopen_w(space, w_filename, flags) self.register_finalizer(space) def _finalize_(self): @@ -104,7 +97,7 @@ W_Library.typedef.acceptable_as_base_class = False -@unwrap_spec(filename="fsencode_or_none", flags=int) -def load_library(space, filename, flags=0): - lib = W_Library(space, filename, flags) +@unwrap_spec(flags=int) +def load_library(space, w_filename, flags=0): + lib = W_Library(space, w_filename, flags) return lib diff --git a/pypy/module/_cffi_backend/misc.py b/pypy/module/_cffi_backend/misc.py --- a/pypy/module/_cffi_backend/misc.py +++ b/pypy/module/_cffi_backend/misc.py @@ -1,14 +1,23 @@ from __future__ import with_statement +import sys from pypy.interpreter.error import OperationError, oefmt +from pypy.module._rawffi.interp_rawffi import wrap_dlopenerror from rpython.rlib import jit from rpython.rlib.objectmodel import specialize, we_are_translated from rpython.rlib.rarithmetic import r_uint, r_ulonglong from rpython.rlib.unroll import unrolling_iterable +from rpython.rlib.rdynload import dlopen, DLOpenError from rpython.rtyper.lltypesystem import lltype, llmemory, rffi from rpython.translator.tool.cbuild import ExternalCompilationInfo +if sys.platform == 'win32': + from rpython.rlib.rdynload import dlopenU + WIN32 = True +else: + WIN32 = False + # ____________________________________________________________ @@ -383,3 +392,28 @@ ptr = rffi.cast(rffi.FLOATP, source) for i in range(len(float_list)): float_list[i] = rffi.cast(lltype.Float, ptr[i]) + +# ____________________________________________________________ + +def dlopen_w(space, w_filename, flags): + if WIN32 and space.isinstance_w(w_filename, space.w_unicode): + fname = space.text_w(space.repr(w_filename)) + unicode_name = space.unicode_w(w_filename) + with rffi.scoped_unicode2wcharp(unicode_name) as ll_libname: + try: + handle = dlopenU(ll_libname, flags) + except DLOpenError as e: + raise wrap_dlopenerror(space, e, fname) + else: + if space.is_none(w_filename): + fname = None + else: + fname = space.fsencode_w(w_filename) + with rffi.scoped_str2charp(fname) as ll_libname: + if fname is None: + fname = "<None>" + try: + handle = dlopen(ll_libname, flags) + except DLOpenError as e: + raise wrap_dlopenerror(space, e, fname) + return fname, handle 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 @@ -3957,6 +3957,7 @@ z3 = cast(BVoidP, 0) z4 = cast(BUCharP, 0) with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") newp(new_pointer_type(BIntP), z1) # warn assert len(w) == 1 newp(new_pointer_type(BVoidP), z1) # fine diff --git a/pypy/module/_cffi_backend/test/test_re_python.py b/pypy/module/_cffi_backend/test/test_re_python.py --- a/pypy/module/_cffi_backend/test/test_re_python.py +++ b/pypy/module/_cffi_backend/test/test_re_python.py @@ -1,8 +1,13 @@ import py +import sys, shutil, os from rpython.tool.udir import udir from pypy.interpreter.gateway import interp2app from pypy.module._cffi_backend.newtype import _clean_cache +if sys.platform == 'win32': + WIN32 = True +else: + WIN32 = False class AppTestRecompilerPython: spaceconfig = dict(usemodules=['_cffi_backend']) @@ -40,6 +45,18 @@ 'globalconst42', 'globalconsthello']) outputfilename = ffiplatform.compile(str(tmpdir), ext) cls.w_extmod = space.wrap(outputfilename) + if WIN32: + unicode_name = u'load\u03betest.dll' + else: + unicode_name = u'load_caf\xe9' + os.path.splitext(outputfilename)[1] + try: + unicode_name.encode(sys.getfilesystemencoding()) + except UnicodeEncodeError: + unicode_name = None # skip test_dlopen_unicode + if unicode_name is not None: + outputfileUname = os.path.join(unicode(udir), unicode_name) + shutil.copyfile(outputfilename, outputfileUname) + cls.w_extmodU = space.wrap(outputfileUname) #mod.tmpdir = tmpdir # ffi = FFI() @@ -108,6 +125,15 @@ assert lib.add42(-10) == 32 assert type(lib.add42) is _cffi_backend.FFI.CData + def test_dlopen_unicode(self): + if not getattr(self, 'extmodU', None): + skip("no unicode file name") + import _cffi_backend + self.fix_path() + from re_python_pysrc import ffi + lib = ffi.dlopen(self.extmodU) + assert lib.add42(-10) == 32 + def test_dlclose(self): import _cffi_backend self.fix_path() diff --git a/pypy/module/_codecs/test/test_codecs.py b/pypy/module/_codecs/test/test_codecs.py --- a/pypy/module/_codecs/test/test_codecs.py +++ b/pypy/module/_codecs/test/test_codecs.py @@ -603,13 +603,13 @@ def handler_unicodeinternal(exc): if not isinstance(exc, UnicodeDecodeError): raise TypeError("don't know how to handle %r" % exc) - return (u"\x01", 4) + return (u"\x01", 5) codecs.register_error("test.hui", handler_unicodeinternal) res = "\x00\x00\x00\x00\x00".decode("unicode-internal", "test.hui") if sys.maxunicode > 65535: assert res == u"\u0000\u0001" # UCS4 build else: - assert res == u"\x00\x00\x01\x00\x00" # UCS2 build + assert res == u"\x00\x00\x01" # UCS2 build def handler1(exc): if not isinstance(exc, UnicodeEncodeError) \ @@ -621,6 +621,26 @@ assert b"\\u3042\u3xxx".decode("unicode-escape", "test.handler1") == \ u"\u3042[<92><117><51>]xxx" + def test_unicode_internal_error_handler_infinite_loop(self): + import codecs + class MyException(Exception): + pass + seen = [0] + def handler_unicodeinternal(exc): + if not isinstance(exc, UnicodeDecodeError): + raise TypeError("don't know how to handle %r" % exc) + seen[0] += 1 + if seen[0] == 20: # stop the 20th time this is called + raise MyException + return (u"\x01", 4) # 4 < len(input), so will try and fail again + codecs.register_error("test.inf", handler_unicodeinternal) + try: + "\x00\x00\x00\x00\x00".decode("unicode-internal", "test.inf") + except MyException: + pass + else: + raise AssertionError("should have gone into infinite loop") + def test_encode_error_bad_handler(self): import codecs codecs.register_error("test.bad_handler", lambda e: (repl, 1)) diff --git a/pypy/module/_cppyy/capi/loadable_capi.py b/pypy/module/_cppyy/capi/loadable_capi.py --- a/pypy/module/_cppyy/capi/loadable_capi.py +++ b/pypy/module/_cppyy/capi/loadable_capi.py @@ -308,10 +308,10 @@ dldflags = rdynload.RTLD_LOCAL | rdynload.RTLD_LAZY if os.environ.get('CPPYY_BACKEND_LIBRARY'): libname = os.environ['CPPYY_BACKEND_LIBRARY'] - state.backend = W_Library(space, libname, dldflags) + state.backend = W_Library(space, space.newtext(libname), dldflags) else: # try usual lookups - state.backend = W_Library(space, backend_library, dldflags) + state.backend = W_Library(space, space.newtext(backend_library), dldflags) if state.backend: # fix constants 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 @@ -92,8 +92,11 @@ if sys.platform == 'win32': dash = '_' + WIN32 = True else: dash = '' + WIN32 = False + def fclose(fp): try: @@ -603,10 +606,11 @@ 'PyObject_CallMethod', 'PyObject_CallFunctionObjArgs', 'PyObject_CallMethodObjArgs', '_PyObject_CallFunction_SizeT', '_PyObject_CallMethod_SizeT', - 'PyObject_GetBuffer', 'PyBuffer_Release', - 'PyBuffer_FromMemory', 'PyBuffer_FromReadWriteMemory', 'PyBuffer_FromObject', - 'PyBuffer_FromReadWriteObject', 'PyBuffer_New', 'PyBuffer_Type', '_Py_get_buffer_type', - '_Py_setfilesystemdefaultencoding', + 'PyObject_DelItemString', 'PyObject_GetBuffer', 'PyBuffer_Release', + + 'PyBuffer_FromMemory', 'PyBuffer_FromReadWriteMemory', + 'PyBuffer_FromObject', 'PyBuffer_FromReadWriteObject', 'PyBuffer_New', + 'PyBuffer_Type', '_Py_get_buffer_type', '_Py_setfilesystemdefaultencoding', 'PyCObject_FromVoidPtr', 'PyCObject_FromVoidPtrAndDesc', 'PyCObject_AsVoidPtr', 'PyCObject_GetDesc', 'PyCObject_Import', 'PyCObject_SetVoidPtr', @@ -640,6 +644,7 @@ 'Py_FrozenFlag', 'Py_TabcheckFlag', 'Py_UnicodeFlag', 'Py_IgnoreEnvironmentFlag', 'Py_DivisionWarningFlag', 'Py_DontWriteBytecodeFlag', 'Py_NoUserSiteDirectory', '_Py_QnewFlag', 'Py_Py3kWarningFlag', 'Py_HashRandomizationFlag', '_Py_PackageContext', + 'PyOS_InputHook', '_PyTraceMalloc_Track', '_PyTraceMalloc_Untrack', 'PyMem_Malloc', 'PyObject_Free', 'PyObject_GC_Del', 'PyType_GenericAlloc', '_PyObject_New', '_PyObject_NewVar', @@ -1176,6 +1181,10 @@ state.C._PyPy_object_dealloc = rffi.llexternal( '_PyPy_object_dealloc', [PyObject], lltype.Void, compilation_info=eci, _nowrapper=True) + FUNCPTR = lltype.Ptr(lltype.FuncType([], rffi.INT)) + state.C.get_pyos_inputhook = rffi.llexternal( + '_PyPy_get_PyOS_InputHook', [], FUNCPTR, + compilation_info=eci, _nowrapper=True) def init_function(func): @@ -1299,7 +1308,7 @@ # if do tuple_attach of the prebuilt empty tuple, we need to call # _PyPy_Malloc) builder.attach_all(space) - + setup_init_functions(eci, prefix) return modulename.new(ext='') @@ -1536,7 +1545,6 @@ if sys.platform == 'win32': get_pythonapi_source = ''' - #include <windows.h> RPY_EXTERN HANDLE pypy_get_pythonapi_handle() { MEMORY_BASIC_INFORMATION mi; @@ -1550,6 +1558,9 @@ } ''' separate_module_sources.append(get_pythonapi_source) + kwds['post_include_bits'] = ['#include <windows.h>', + 'RPY_EXTERN HANDLE pypy_get_pythonapi_handle();', + ] eci = ExternalCompilationInfo( include_dirs=include_dirs, @@ -1655,7 +1666,11 @@ try: ll_libname = rffi.str2charp(path) try: - dll = rdynload.dlopen(ll_libname, space.sys.dlopenflags) + if WIN32: + # Allow other DLLs in the same directory with "path" + dll = rdynload.dlopenex(ll_libname) + else: + dll = rdynload.dlopen(ll_libname, space.sys.dlopenflags) finally: lltype.free(ll_libname, flavor='raw') except rdynload.DLOpenError as e: @@ -1716,6 +1731,12 @@ w_mod = state.fixup_extension(name, path) return w_mod +def invoke_pyos_inputhook(space): + state = space.fromcache(State) + c_inputhook = state.C.get_pyos_inputhook() + if c_inputhook: + generic_cpy_call(space, c_inputhook) + @specialize.ll() def generic_cpy_call(space, func, *args): FT = lltype.typeOf(func).TO diff --git a/pypy/module/cpyext/eval.py b/pypy/module/cpyext/eval.py --- a/pypy/module/cpyext/eval.py +++ b/pypy/module/cpyext/eval.py @@ -9,6 +9,7 @@ from pypy.module.cpyext.pyobject import PyObject from pypy.module.cpyext.pyerrors import PyErr_SetFromErrno from pypy.module.cpyext.funcobject import PyCodeObject +from pypy.module.cpyext.frameobject import PyFrameObject from pypy.module.__builtin__ import compiling PyCompilerFlags = cpython_struct( @@ -58,6 +59,11 @@ return None return caller.get_w_globals() # borrowed ref +@cpython_api([], PyFrameObject, error=CANNOT_FAIL, result_borrowed=True) +def PyEval_GetFrame(space): + caller = space.getexecutioncontext().gettopframe_nohidden() + return caller # borrowed ref, may be null + @cpython_api([PyCodeObject, PyObject, PyObject], PyObject) def PyEval_EvalCode(space, w_code, w_globals, w_locals): """This is a simplified interface to PyEval_EvalCodeEx(), with just diff --git a/pypy/module/cpyext/include/abstract.h b/pypy/module/cpyext/include/abstract.h --- a/pypy/module/cpyext/include/abstract.h +++ b/pypy/module/cpyext/include/abstract.h @@ -4,6 +4,15 @@ extern "C" { #endif + PyAPI_FUNC(int) PyObject_DelItemString(PyObject *o, char *key); + + /* + Remove the mapping for object, key, from the object *o. + Returns -1 on failure. This is equivalent to + the Python statement: del o[key]. + */ + + /* new buffer API */ #define PyObject_CheckBuffer(obj) \ @@ -28,6 +37,27 @@ /* Releases a Py_buffer obtained from getbuffer ParseTuple's s*. */ +/* Mapping protocol:*/ + + /* implemented as a macro: + + int PyMapping_DelItemString(PyObject *o, char *key); + + Remove the mapping for object, key, from the object *o. + Returns -1 on failure. This is equivalent to + the Python statement: del o[key]. + */ +#define PyMapping_DelItemString(O,K) PyObject_DelItemString((O),(K)) + + /* implemented as a macro: + + int PyMapping_DelItem(PyObject *o, PyObject *key); + + Remove the mapping for object, key, from the object *o. + Returns -1 on failure. This is equivalent to + the Python statement: del o[key]. + */ +#define PyMapping_DelItem(O,K) PyObject_DelItem((O),(K)) #ifdef __cplusplus } diff --git a/pypy/module/cpyext/include/pythonrun.h b/pypy/module/cpyext/include/pythonrun.h --- a/pypy/module/cpyext/include/pythonrun.h +++ b/pypy/module/cpyext/include/pythonrun.h @@ -47,6 +47,11 @@ #define Py_CompileString(str, filename, start) Py_CompileStringFlags(str, filename, start, NULL) +/* Stuff with no proper home (yet) */ +PyAPI_DATA(int) (*PyOS_InputHook)(void); +typedef int (*_pypy_pyos_inputhook)(void); +PyAPI_FUNC(_pypy_pyos_inputhook) _PyPy_get_PyOS_InputHook(void); + #ifdef __cplusplus } #endif diff --git a/pypy/module/cpyext/src/abstract.c b/pypy/module/cpyext/src/abstract.c --- a/pypy/module/cpyext/src/abstract.c +++ b/pypy/module/cpyext/src/abstract.c @@ -23,6 +23,23 @@ /* Operations on any object */ int +PyObject_DelItemString(PyObject *o, char *key) +{ + PyObject *okey; + int ret; + + if (o == NULL || key == NULL) { + null_error(); + return -1; + } + okey = PyString_FromString(key); + if (okey == NULL) + return -1; + ret = PyObject_DelItem(o, okey); + Py_DECREF(okey); + return ret; +} +int PyObject_CheckReadBuffer(PyObject *obj) { PyBufferProcs *pb = obj->ob_type->tp_as_buffer; @@ -101,6 +118,20 @@ return 0; } +/* Buffer C-API for Python 3.0 */ + +int +PyObject_GetBuffer(PyObject *obj, Py_buffer *view, int flags) +{ + if (!PyObject_CheckBuffer(obj)) { + PyErr_Format(PyExc_TypeError, + "'%100s' does not have the buffer interface", + Py_TYPE(obj)->tp_name); + return -1; + } + return (*(obj->ob_type->tp_as_buffer->bf_getbuffer))(obj, view, flags); +} + void* PyBuffer_GetPointer(Py_buffer *view, Py_ssize_t *indices) { @@ -116,6 +147,7 @@ return (void*)pointer; } + void _Py_add_one_to_index_F(int nd, Py_ssize_t *index, const Py_ssize_t *shape) { @@ -258,19 +290,6 @@ -/* Buffer C-API for Python 3.0 */ - -int -PyObject_GetBuffer(PyObject *obj, Py_buffer *view, int flags) -{ - if (!PyObject_CheckBuffer(obj)) { - PyErr_Format(PyExc_TypeError, - "'%100s' does not have the buffer interface", - Py_TYPE(obj)->tp_name); - return -1; - } - return (*(obj->ob_type->tp_as_buffer->bf_getbuffer))(obj, view, flags); -} void PyBuffer_Release(Py_buffer *view) @@ -428,6 +447,7 @@ return retval; } + static PyObject * objargs_mktuple(va_list va) { diff --git a/pypy/module/cpyext/src/missing.c b/pypy/module/cpyext/src/missing.c --- a/pypy/module/cpyext/src/missing.c +++ b/pypy/module/cpyext/src/missing.c @@ -31,3 +31,7 @@ void _Py_setfilesystemdefaultencoding(const char *enc) { Py_FileSystemDefaultEncoding = enc; } +int (*PyOS_InputHook)(void) = 0; /* only ever filled in by C extensions */ +PyAPI_FUNC(_pypy_pyos_inputhook) _PyPy_get_PyOS_InputHook(void) { + return PyOS_InputHook; +} diff --git a/pypy/module/cpyext/stubs-find-implemented.py b/pypy/module/cpyext/stubs-find-implemented.py new file mode 100644 --- /dev/null +++ b/pypy/module/cpyext/stubs-find-implemented.py @@ -0,0 +1,21 @@ +import re +import os + + +for line in open('stubs.py'): + if not line.strip(): + continue + if line.startswith(' '): + continue + if line.startswith('#'): + continue + if line.startswith('@cpython_api'): + continue + if line.endswith(' = rffi.VOIDP\n'): + continue + + #print line.rstrip() + m = re.match(r"def ([\w\d_]+)[(]", line) + assert m, line + funcname = m.group(1) + os.system('grep -w %s [a-r]*.py s[a-s]*.py str*.py stubsa*.py sy*.py [t-z]*.py' % funcname) diff --git a/pypy/module/cpyext/stubs.py b/pypy/module/cpyext/stubs.py --- a/pypy/module/cpyext/stubs.py +++ b/pypy/module/cpyext/stubs.py @@ -1,28 +1,11 @@ -from pypy.module.cpyext.api import ( - cpython_api, PyObject, PyObjectP, CANNOT_FAIL - ) -from pypy.module.cpyext.complexobject import Py_complex_ptr as Py_complex -from rpython.rtyper.lltypesystem import rffi, lltype +#----this file is not imported, only here for reference---- -# we don't really care -PyTypeObjectPtr = rffi.VOIDP -Py_ssize_t = rffi.SSIZE_T -PyMethodDef = rffi.VOIDP -PyGetSetDef = rffi.VOIDP -PyMemberDef = rffi.VOIDP -va_list = rffi.VOIDP -wrapperbase = rffi.VOIDP -FILE = rffi.VOIDP -PyFileObject = rffi.VOIDP -PyCodeObject = rffi.VOIDP -PyFrameObject = rffi.VOIDP -_inittab = rffi.VOIDP -PyThreadState = rffi.VOIDP -PyInterpreterState = rffi.VOIDP -Py_UNICODE = lltype.UniChar -PyCompilerFlags = rffi.VOIDP -_node = rffi.VOIDP -Py_tracefunc = rffi.VOIDP +#from pypy.module.cpyext.api import ( +# cpython_api, PyObject, PyObjectP, CANNOT_FAIL +# ) +#from pypy.module.cpyext.complexobject import Py_complex_ptr as Py_complex +#from rpython.rtyper.lltypesystem import rffi, lltype + @cpython_api([rffi.CCHARP], Py_ssize_t, error=CANNOT_FAIL) def PyBuffer_SizeFromFormat(space, format): @@ -254,39 +237,6 @@ instead.""" raise NotImplementedError -@cpython_api([rffi.DOUBLE, lltype.Char, rffi.INT_real, rffi.INT_real, rffi.INTP], rffi.CCHARP) -def PyOS_double_to_string(space, val, format_code, precision, flags, ptype): - """Convert a double val to a string using supplied - format_code, precision, and flags. - - format_code must be one of 'e', 'E', 'f', 'F', - 'g', 'G' or 'r'. For 'r', the supplied precision - must be 0 and is ignored. The 'r' format code specifies the - standard repr() format. - - flags can be zero or more of the values Py_DTSF_SIGN, - Py_DTSF_ADD_DOT_0, or Py_DTSF_ALT, or-ed together: - - Py_DTSF_SIGN means to always precede the returned string with a sign - character, even if val is non-negative. - - Py_DTSF_ADD_DOT_0 means to ensure that the returned string will not look - like an integer. - - Py_DTSF_ALT means to apply "alternate" formatting rules. See the - documentation for the PyOS_snprintf() '#' specifier for - details. - - If ptype is non-NULL, then the value it points to will be set to one of - Py_DTST_FINITE, Py_DTST_INFINITE, or Py_DTST_NAN, signifying that - val is a finite number, an infinite number, or not a number, respectively. - - The return value is a pointer to buffer with the converted string or - NULL if the conversion failed. The caller is responsible for freeing the - returned string by calling PyMem_Free(). - """ - raise NotImplementedError - @cpython_api([rffi.CCHARP], rffi.DOUBLE, error=CANNOT_FAIL) def PyOS_ascii_atof(space, nptr): """Convert a string to a double in a locale-independent way. @@ -310,24 +260,6 @@ """ raise NotImplementedError -@cpython_api([PyObject], rffi.INT_real, error=CANNOT_FAIL) -def PyTZInfo_Check(space, ob): - """Return true if ob is of type PyDateTime_TZInfoType or a subtype of - PyDateTime_TZInfoType. ob must not be NULL. - """ - raise NotImplementedError - -@cpython_api([PyObject], rffi.INT_real, error=CANNOT_FAIL) -def PyTZInfo_CheckExact(space, ob): - """Return true if ob is of type PyDateTime_TZInfoType. ob must not be - NULL. - """ - raise NotImplementedError - -@cpython_api([PyTypeObjectPtr, PyGetSetDef], PyObject) -def PyDescr_NewGetSet(space, type, getset): - raise NotImplementedError - @cpython_api([PyTypeObjectPtr, PyMemberDef], PyObject) def PyDescr_NewMember(space, type, meth): raise NotImplementedError @@ -1206,14 +1138,6 @@ """ raise NotImplementedError -@cpython_api([PyObject], rffi.ULONGLONG, error=-1) -def PyInt_AsUnsignedLongLongMask(space, io): - """Will first attempt to cast the object to a PyIntObject or - PyLongObject, if it is not already one, and then return its value as - unsigned long long, without checking for overflow. - """ - raise NotImplementedError - @cpython_api([], rffi.INT_real, error=CANNOT_FAIL) def PyInt_ClearFreeList(space): """Clear the integer free list. Return the number of items that could not @@ -1233,18 +1157,6 @@ """ raise NotImplementedError -@cpython_api([PyObject, rffi.CCHARP], rffi.INT_real, error=-1) -def PyMapping_DelItemString(space, o, key): - """Remove the mapping for object key from the object o. Return -1 on - failure. This is equivalent to the Python statement del o[key].""" - raise NotImplementedError - -@cpython_api([PyObject, PyObject], rffi.INT_real, error=-1) -def PyMapping_DelItem(space, o, key): - """Remove the mapping for object key from the object o. Return -1 on - failure. This is equivalent to the Python statement del o[key].""" - raise NotImplementedError - @cpython_api([lltype.Signed, FILE, rffi.INT_real], lltype.Void) def PyMarshal_WriteLongToFile(space, value, file, version): """Marshal a long integer, value, to file. This will only write @@ -1345,13 +1257,6 @@ for PyObject_Str().""" raise NotImplementedError -@cpython_api([], PyFrameObject) -def PyEval_GetFrame(space): - """Return the current thread state's frame, which is NULL if no frame is - currently executing.""" - borrow_from() - raise NotImplementedError - @cpython_api([PyFrameObject], rffi.INT_real, error=CANNOT_FAIL) def PyFrame_GetLineNumber(space, frame): """Return the line number that frame is currently executing.""" diff --git a/pypy/module/cpyext/test/test_eval.py b/pypy/module/cpyext/test/test_eval.py --- a/pypy/module/cpyext/test/test_eval.py +++ b/pypy/module/cpyext/test/test_eval.py @@ -420,3 +420,15 @@ except StopIteration: pass assert out == [0, 1, 2, 3, 4] + + def test_getframe(self): + import sys + module = self.import_extension('foo', [ + ("getframe1", "METH_NOARGS", + """ + PyFrameObject *x = PyEval_GetFrame(); + Py_INCREF(x); + return (PyObject *)x; + """),], prologue="#include <frameobject.h>\n") + res = module.getframe1() + assert res is sys._getframe(0) diff --git a/pypy/module/cpyext/test/test_misc.py b/pypy/module/cpyext/test/test_misc.py new file mode 100644 --- /dev/null +++ b/pypy/module/cpyext/test/test_misc.py @@ -0,0 +1,35 @@ +from pypy.module.cpyext.test.test_cpyext import AppTestCpythonExtensionBase + + +class AppTestMisc(AppTestCpythonExtensionBase): + + def test_pyos_inputhook(self): + module = self.import_extension('foo', [ + ("set_pyos_inputhook", "METH_NOARGS", + ''' + PyOS_InputHook = &my_callback; + Py_RETURN_NONE; + '''), + ("fetch_value", "METH_NOARGS", + ''' + return PyInt_FromLong(my_flag); + '''), + ], prologue=''' + static long my_flag = 0; + static int my_callback(void) { return ++my_flag; } + ''') + + try: + import __pypy__ + except ImportError: + skip("only runs on top of pypy") + assert module.fetch_value() == 0 + __pypy__.pyos_inputhook() + assert module.fetch_value() == 0 + module.set_pyos_inputhook() # <= set + assert module.fetch_value() == 0 + __pypy__.pyos_inputhook() + assert module.fetch_value() == 1 + __pypy__.pyos_inputhook() + assert module.fetch_value() == 2 + assert module.fetch_value() == 2 diff --git a/pypy/module/pwd/interp_pwd.py b/pypy/module/pwd/interp_pwd.py --- a/pypy/module/pwd/interp_pwd.py +++ b/pypy/module/pwd/interp_pwd.py @@ -37,7 +37,8 @@ passwd_p = lltype.Ptr(config['passwd']) def external(name, args, result, **kwargs): - return rffi.llexternal(name, args, result, compilation_info=eci, **kwargs) + return rffi.llexternal(name, args, result, compilation_info=eci, + releasegil=False, **kwargs) c_getpwuid = external("getpwuid", [uid_t], passwd_p) c_getpwnam = external("getpwnam", [rffi.CCHARP], passwd_p) diff --git a/pypy/module/pypyjit/test_pypy_c/test_min_max.py b/pypy/module/pypyjit/test_pypy_c/test_min_max.py --- a/pypy/module/pypyjit/test_pypy_c/test_min_max.py +++ b/pypy/module/pypyjit/test_pypy_c/test_min_max.py @@ -30,37 +30,42 @@ sa = 0 while i < 30000: lst = range(i % 1000 + 2) - sa += max(*lst) # ID: max + sa += max(*lst) # ID: callmax i += 1 return sa log = self.run(main, []) assert log.result == main() loop, = log.loops_by_filename(self.filepath) - assert loop.match(""" + assert loop.match_by_id('callmax', """ ... - p76 = call_assembler_r(_, _, _, _, descr=...) + p76 = call_may_force_r(_, _, _, _, descr=...) ... """) - loop2 = log.loops[0] - loop2.match(''' - ... - label(..., descr=...) - ... - label(..., descr=...) - guard_not_invalidated? - i17 = int_ge(i11, i7) - guard_false(i17, descr=...) - p18 = getarrayitem_gc_r(p5, i11, descr=...) - i19 = int_add(i11, 1) - setfield_gc(p2, i19, descr=...) - guard_nonnull_class(p18, ConstClass(W_IntObject), descr=...) - i20 = getfield_gc_i(p18, descr=...) - i21 = int_gt(i20, i14) - guard_true(i21, descr=...) - jump(..., descr=...) - ''') - # XXX could be "guard_class(p18)" instead; we lost somewhere - # the information that it cannot be null. + + #----- the following logic used to check the content of the assembly + #----- generated for the loop in max(), but now we no longer produce + #----- any custom assembly in this case. It used to say + #----- 'call_assembler_r' above, and now it says 'call_may_force_r'. + #loop2 = log.loops[0] + #loop2.match(''' + #... + #label(..., descr=...) + #... + #label(..., descr=...) + #guard_not_invalidated? + #i17 = int_ge(i11, i7) + #guard_false(i17, descr=...) + #p18 = getarrayitem_gc_r(p5, i11, descr=...) + #i19 = int_add(i11, 1) + #setfield_gc(p2, i19, descr=...) + #guard_nonnull_class(p18, ConstClass(W_IntObject), descr=...) + #i20 = getfield_gc_i(p18, descr=...) + #i21 = int_gt(i20, i14) + #guard_true(i21, descr=...) + #jump(..., descr=...) + #''') + ## XXX could be "guard_class(p18)" instead; we lost somewhere + ## the information that it cannot be null. def test_iter_max(self): def main(): diff --git a/pypy/module/sys/initpath.py b/pypy/module/sys/initpath.py --- a/pypy/module/sys/initpath.py +++ b/pypy/module/sys/initpath.py @@ -183,7 +183,9 @@ if os.name == 'nt': _source_code = r""" +#ifndef _WIN32_WINNT #define _WIN32_WINNT 0x0501 +#endif #include <windows.h> #include <stdio.h> diff --git a/pypy/module/test_lib_pypy/cffi_tests/cffi0/backend_tests.py b/pypy/module/test_lib_pypy/cffi_tests/cffi0/backend_tests.py --- a/pypy/module/test_lib_pypy/cffi_tests/cffi0/backend_tests.py +++ b/pypy/module/test_lib_pypy/cffi_tests/cffi0/backend_tests.py @@ -1387,6 +1387,7 @@ ffi = FFI(backend=self.Backend()) ffi.cdef("enum foo;") with warnings.catch_warnings(record=True) as log: + warnings.simplefilter("always") n = ffi.cast("enum foo", -1) assert int(n) == 0xffffffff assert str(log[0].message) == ( diff --git a/pypy/module/test_lib_pypy/cffi_tests/cffi0/test_function.py b/pypy/module/test_lib_pypy/cffi_tests/cffi0/test_function.py --- a/pypy/module/test_lib_pypy/cffi_tests/cffi0/test_function.py +++ b/pypy/module/test_lib_pypy/cffi_tests/cffi0/test_function.py @@ -46,14 +46,14 @@ assert x != math.sin(1.23) # rounding effects assert abs(x - math.sin(1.23)) < 1E-6 - def test_sin_no_return_value(self): + def test_lround_no_return_value(self): # check that 'void'-returning functions work too ffi = FFI(backend=self.Backend()) ffi.cdef(""" - void sin(double x); + void lround(double x); """) m = ffi.dlopen(lib_m) - x = m.sin(1.23) + x = m.lround(1.23) assert x is None def test_dlopen_filename(self): diff --git a/pypy/module/test_lib_pypy/ctypes_tests/test_callbacks.py b/pypy/module/test_lib_pypy/ctypes_tests/test_callbacks.py --- a/pypy/module/test_lib_pypy/ctypes_tests/test_callbacks.py +++ b/pypy/module/test_lib_pypy/ctypes_tests/test_callbacks.py @@ -160,15 +160,17 @@ proto = CFUNCTYPE(c_int, RECT) def callback(point): + point.left *= -1 return point.left+point.top+point.right+point.bottom cbp = proto(callback) - rect = RECT(1000,100,10,1) + rect = RECT(-1000,100,10,1) res = cbp(rect) assert res == 1111 + assert rect.left == -1000 # must not have been changed! def test_callback_from_c_with_struct_argument(self): import conftest diff --git a/pypy/module/thread/__init__.py b/pypy/module/thread/__init__.py --- a/pypy/module/thread/__init__.py +++ b/pypy/module/thread/__init__.py @@ -18,6 +18,7 @@ 'allocate_lock': 'os_lock.allocate_lock', 'allocate': 'os_lock.allocate_lock', # obsolete synonym 'LockType': 'os_lock.Lock', + 'RLock': 'os_lock.W_RLock', # pypy only, issue #2905 '_local': 'os_local.Local', 'error': 'space.fromcache(error.Cache).w_error', } diff --git a/pypy/module/thread/os_lock.py b/pypy/module/thread/os_lock.py --- a/pypy/module/thread/os_lock.py +++ b/pypy/module/thread/os_lock.py @@ -8,8 +8,8 @@ from pypy.interpreter.baseobjspace import W_Root from pypy.interpreter.gateway import interp2app, unwrap_spec from pypy.interpreter.typedef import TypeDef, make_weakref_descr -from pypy.interpreter.error import oefmt -from rpython.rlib.rarithmetic import r_longlong, ovfcheck_float_to_longlong +from pypy.interpreter.error import OperationError, oefmt +from rpython.rlib.rarithmetic import r_longlong, ovfcheck, ovfcheck_float_to_longlong RPY_LOCK_FAILURE, RPY_LOCK_ACQUIRED, RPY_LOCK_INTR = range(3) @@ -53,6 +53,12 @@ break return result +def try_release(space, lock): + try: + lock.release() + except rthread.error: + raise wrap_thread_error(space, "release unlocked lock") + class Lock(W_Root): "A box around an interp-level lock object." @@ -97,10 +103,7 @@ """Release the lock, allowing another thread that is blocked waiting for the lock to acquire the lock. The lock must be in the locked state, but it needn't be locked by the same thread that unlocks it.""" - try: - self.lock.release() - except rthread.error: - raise wrap_thread_error(space, "release unlocked lock") + try_release(space, self.lock) def descr_lock_locked(self, space): """Return whether the lock is in the locked state.""" @@ -162,3 +165,149 @@ """Create a new lock object. (allocate() is an obsolete synonym.) See LockType.__doc__ for information about locks.""" return Lock(space) + +class W_RLock(W_Root): + # Does not exist in CPython 2.x. Back-ported from PyPy3. See issue #2905 + + def __init__(self, space, w_active=None): + self.rlock_count = 0 + self.rlock_owner = 0 + self.w_active = w_active # dictionary 'threading._active' + try: + self.lock = rthread.allocate_lock() + except rthread.error: + raise wrap_thread_error(space, "cannot allocate lock") + + def descr__new__(space, w_subtype, w_active=None): + self = space.allocate_instance(W_RLock, w_subtype) + W_RLock.__init__(self, space, w_active) + return self + + def descr__repr__(self, space): + w_type = space.type(self) + classname = w_type.name + if self.rlock_owner == 0: + owner = "None" + else: + owner = str(self.rlock_owner) + if self.w_active is not None: + try: + w_owner = space.getitem(self.w_active, + space.newint(self.rlock_owner)) + w_name = space.getattr(w_owner, space.newtext('name')) + owner = space.text_w(space.repr(w_name)) + except OperationError as e: + if e.async(space): + raise + return space.newtext("<%s owner=%s count=%d>" % ( + classname, owner, self.rlock_count)) + + @unwrap_spec(blocking=int) + def acquire_w(self, space, blocking=1): + """Acquire a lock, blocking or non-blocking. + + When invoked without arguments: if this thread already owns the lock, + increment the recursion level by one, and return immediately. Otherwise, + if another thread owns the lock, block until the lock is unlocked. Once + the lock is unlocked (not owned by any thread), then grab ownership, set + the recursion level to one, and return. If more than one thread is + blocked waiting until the lock is unlocked, only one at a time will be + able to grab ownership of the lock. There is no return value in this + case. + + When invoked with the blocking argument set to true, do the same thing + as when called without arguments, and return true. + + When invoked with the blocking argument set to false, do not block. If a + call without an argument would block, return false immediately; + otherwise, do the same thing as when called without arguments, and + return true. + + """ + tid = rthread.get_ident() + if tid == self.rlock_owner: + try: + self.rlock_count = ovfcheck(self.rlock_count + 1) + except OverflowError: + raise oefmt(space.w_OverflowError, + "internal lock count overflowed") + return space.w_True + + rc = self.lock.acquire(blocking != 0) + if rc: + self.rlock_owner = tid + self.rlock_count = 1 + return space.newbool(rc) + + def release_w(self, space): + """Release a lock, decrementing the recursion level. + + If after the decrement it is zero, reset the lock to unlocked (not owned + by any thread), and if any other threads are blocked waiting for the + lock to become unlocked, allow exactly one of them to proceed. If after + the decrement the recursion level is still nonzero, the lock remains + locked and owned by the calling thread. + + Only call this method when the calling thread owns the lock. A + RuntimeError is raised if this method is called when the lock is + unlocked. + + There is no return value. + + """ + if self.rlock_owner != rthread.get_ident(): + raise oefmt(space.w_RuntimeError, + "cannot release un-acquired lock") + self.rlock_count -= 1 + if self.rlock_count == 0: + self.rlock_owner = 0 + try_release(space, self.lock) + + def is_owned_w(self, space): + """For internal use by `threading.Condition`.""" + return space.newbool(self.rlock_owner == rthread.get_ident()) + + def acquire_restore_w(self, space, w_count_owner): + """For internal use by `threading.Condition`.""" + # saved_state is the value returned by release_save() + w_count, w_owner = space.unpackiterable(w_count_owner, 2) + count = space.int_w(w_count) + owner = space.int_w(w_owner) + self.lock.acquire(True) + self.rlock_count = count + self.rlock_owner = owner + + def release_save_w(self, space): + """For internal use by `threading.Condition`.""" + if self.rlock_count == 0: + raise oefmt(space.w_RuntimeError, + "cannot release un-acquired lock") + count, self.rlock_count = self.rlock_count, 0 + owner, self.rlock_owner = self.rlock_owner, 0 + try_release(space, self.lock) + return space.newtuple([space.newint(count), space.newint(owner)]) + + def descr__enter__(self, space): + self.acquire_w(space) + return self + + def descr__exit__(self, space, __args__): + self.release_w(space) + + def descr__note(self, space, __args__): + pass # compatibility with the _Verbose base class in Python + +W_RLock.typedef = TypeDef( + "thread.RLock", + __new__ = interp2app(W_RLock.descr__new__.im_func), + acquire = interp2app(W_RLock.acquire_w), + release = interp2app(W_RLock.release_w), + _is_owned = interp2app(W_RLock.is_owned_w), + _acquire_restore = interp2app(W_RLock.acquire_restore_w), + _release_save = interp2app(W_RLock.release_save_w), + __enter__ = interp2app(W_RLock.descr__enter__), + __exit__ = interp2app(W_RLock.descr__exit__), + __weakref__ = make_weakref_descr(W_RLock), + __repr__ = interp2app(W_RLock.descr__repr__), + _note = interp2app(W_RLock.descr__note), + ) diff --git a/pypy/module/thread/test/test_lock.py b/pypy/module/thread/test/test_lock.py --- a/pypy/module/thread/test/test_lock.py +++ b/pypy/module/thread/test/test_lock.py @@ -138,6 +138,62 @@ test_lock_again = AppTestLock.test_lock.im_func +class AppTestRLock(GenericTestThread): + """ + Tests for recursive locks. + """ + def test_reacquire(self): + import thread + lock = thread.RLock() + lock.acquire() + lock.acquire() + lock.release() + lock.acquire() + lock.release() + lock.release() + + def test_release_unacquired(self): + # Cannot release an unacquired lock + import thread + lock = thread.RLock() + raises(RuntimeError, lock.release) + lock.acquire() + lock.acquire() + lock.release() + lock.acquire() + lock.release() + lock.release() + raises(RuntimeError, lock.release) + + def test_release_save(self): + import thread + lock = thread.RLock() + raises(RuntimeError, lock._release_save) + lock.acquire() + state = lock._release_save() + lock._acquire_restore(state) + lock.release() + + def test__is_owned(self): + import thread + lock = thread.RLock() + assert lock._is_owned() is False + lock.acquire() + assert lock._is_owned() is True + lock.acquire() + assert lock._is_owned() is True + lock.release() + assert lock._is_owned() is True + lock.release() + assert lock._is_owned() is False + + def test_context_manager(self): + import thread + lock = thread.RLock() + with lock: + assert lock._is_owned() is True + + class AppTestLockSignals(GenericTestThread): pytestmark = py.test.mark.skipif("os.name != 'posix'") @@ -178,6 +234,10 @@ import thread self.acquire_retries_on_intr(thread.allocate_lock()) + def test_rlock_acquire_retries_on_intr(self): + import thread + self.acquire_retries_on_intr(thread.RLock()) + def w_alarm_interrupt(self, sig, frame): raise KeyboardInterrupt @@ -209,3 +269,20 @@ assert dt < 8.0 finally: signal.signal(signal.SIGALRM, oldalrm) + + +class AppTestLockRepr(GenericTestThread): + + def test_rlock_repr(self): + import thread + class MyThread: + name = "foobar" + actives = {thread.get_ident(): MyThread()} + rlock = thread.RLock(actives) + assert repr(rlock) == "<thread.RLock owner=None count=0>" + rlock.acquire() + rlock.acquire() + assert repr(rlock) == "<thread.RLock owner='foobar' count=2>" + actives.clear() + assert repr(rlock) == "<thread.RLock owner=%d count=2>" % ( + thread.get_ident(),) diff --git a/rpython/rlib/jit.py b/rpython/rlib/jit.py --- a/rpython/rlib/jit.py +++ b/rpython/rlib/jit.py @@ -151,7 +151,7 @@ if getattr(func, '_elidable_function_', False): raise TypeError("it does not make sense for %s to be both elidable and unroll_safe" % func) if not getattr(func, '_jit_look_inside_', True): - raise TypeError("it does not make sense for %s to be both elidable and dont_look_inside" % func) + raise TypeError("it does not make sense for %s to be both unroll_safe and dont_look_inside" % func) func._jit_unroll_safe_ = True return func diff --git a/rpython/rlib/rdynload.py b/rpython/rlib/rdynload.py --- a/rpython/rlib/rdynload.py +++ b/rpython/rlib/rdynload.py @@ -233,6 +233,25 @@ raise DLOpenError(ustr.encode('utf-8')) return res + def dlopenex(name): + res = rwin32.LoadLibraryExA(name) + if not res: + err = rwin32.GetLastError_saved() + ustr = rwin32.FormatErrorW(err) + # DLOpenError unicode msg breaks translation of cpyext create_extension_module + raise DLOpenError(ustr.encode('utf-8')) + return res + + def dlopenU(name, mode=-1): + # mode is unused on windows, but a consistant signature + res = rwin32.LoadLibraryW(name) + if not res: + err = rwin32.GetLastError_saved() + ustr = rwin32.FormatErrorW(err) + # DLOpenError unicode msg breaks translation of cpyext create_extension_module + raise DLOpenError(ustr.encode('utf-8')) + return res + def dlclose(handle): res = rwin32.FreeLibrary(handle) if res: diff --git a/rpython/rlib/rmmap.py b/rpython/rlib/rmmap.py --- a/rpython/rlib/rmmap.py +++ b/rpython/rlib/rmmap.py @@ -835,7 +835,7 @@ # assume -1 and 0 both mean invalid file descriptor # to 'anonymously' map memory. if fileno != -1 and fileno != 0: - fh = rwin32.get_osfhandle(fileno) + fh = rffi.cast(HANDLE, rwin32.get_osfhandle(fileno)) # Win9x appears to need us seeked to zero # SEEK_SET = 0 # libc._lseek(fileno, 0, SEEK_SET) diff --git a/rpython/rlib/rposix.py b/rpython/rlib/rposix.py --- a/rpython/rlib/rposix.py +++ b/rpython/rlib/rposix.py @@ -137,7 +137,10 @@ RPY_EXTERN void exit_suppress_iph(void* handle) {}; #endif ''',] - post_include_bits=['RPY_EXTERN int _PyVerify_fd(int);'] + post_include_bits=['RPY_EXTERN int _PyVerify_fd(int);', + 'RPY_EXTERN void* enter_suppress_iph();', + 'RPY_EXTERN void exit_suppress_iph(void* handle);', + ] else: separate_module_sources = [] post_include_bits = [] @@ -235,7 +238,8 @@ rthread.tlfield_rpy_errno.setraw(_get_errno()) # ^^^ keep fork() up-to-date too, below if _WIN32: - includes = ['io.h', 'sys/utime.h', 'sys/types.h', 'process.h', 'time.h'] + includes = ['io.h', 'sys/utime.h', 'sys/types.h', 'process.h', 'time.h', + 'direct.h'] libraries = [] else: if sys.platform.startswith(('darwin', 'netbsd', 'openbsd')): @@ -730,16 +734,21 @@ length = rwin32.MAX_PATH + 1 traits = _preferred_traits(path) win32traits = make_win32_traits(traits) - with traits.scoped_alloc_buffer(length) as buf: - res = win32traits.GetFullPathName( - traits.as_str0(path), rffi.cast(rwin32.DWORD, length), - buf.raw, lltype.nullptr(win32traits.LPSTRP.TO)) - if res == 0: - raise rwin32.lastSavedWindowsError("_getfullpathname failed") - result = buf.str(intmask(res)) - assert result is not None - result = rstring.assert_str0(result) - return result + while True: # should run the loop body maximum twice + with traits.scoped_alloc_buffer(length) as buf: + res = win32traits.GetFullPathName( + traits.as_str0(path), rffi.cast(rwin32.DWORD, length), + buf.raw, lltype.nullptr(win32traits.LPSTRP.TO)) + res = intmask(res) + if res == 0: + raise rwin32.lastSavedWindowsError("_getfullpathname failed") + if res >= length: + length = res + 1 + continue + result = buf.str(res) + assert result is not None + result = rstring.assert_str0(result) + return result c_getcwd = external(UNDERSCORE_ON_WIN32 + 'getcwd', [rffi.CCHARP, rffi.SIZE_T], rffi.CCHARP, diff --git a/rpython/rlib/runicode.py b/rpython/rlib/runicode.py --- a/rpython/rlib/runicode.py +++ b/rpython/rlib/runicode.py @@ -1777,8 +1777,6 @@ "truncated input", s, pos, size) result.append(res) - if pos > size - unicode_bytes: - break continue t = r_uint(0) h = 0 diff --git a/rpython/rlib/rwin32.py b/rpython/rlib/rwin32.py --- a/rpython/rlib/rwin32.py +++ b/rpython/rlib/rwin32.py @@ -20,7 +20,7 @@ if WIN32: eci = ExternalCompilationInfo( - includes = ['windows.h', 'stdio.h', 'stdlib.h'], + includes = ['windows.h', 'stdio.h', 'stdlib.h', 'io.h'], libraries = ['kernel32'], ) else: @@ -113,6 +113,7 @@ MB_ERR_INVALID_CHARS ERROR_NO_UNICODE_TRANSLATION WC_NO_BEST_FIT_CHARS STD_INPUT_HANDLE STD_OUTPUT_HANDLE STD_ERROR_HANDLE HANDLE_FLAG_INHERIT FILE_TYPE_CHAR + LOAD_WITH_ALTERED_SEARCH_PATH """ from rpython.translator.platform import host_factory static_platform = host_factory() @@ -195,12 +196,28 @@ GetModuleHandle = winexternal('GetModuleHandleA', [rffi.CCHARP], HMODULE) LoadLibrary = winexternal('LoadLibraryA', [rffi.CCHARP], HMODULE, save_err=rffi.RFFI_SAVE_LASTERROR) + def wrap_loadlibraryex(func): + def loadlibrary(name, flags=LOAD_WITH_ALTERED_SEARCH_PATH): + # Requires a full path name with '/' -> '\\' + return func(name, NULL_HANDLE, flags) + return loadlibrary + + _LoadLibraryExA = winexternal('LoadLibraryExA', + [rffi.CCHARP, HANDLE, DWORD], HMODULE, + save_err=rffi.RFFI_SAVE_LASTERROR) + LoadLibraryExA = wrap_loadlibraryex(_LoadLibraryExA) + LoadLibraryW = winexternal('LoadLibraryW', [rffi.CWCHARP], HMODULE, + save_err=rffi.RFFI_SAVE_LASTERROR) + _LoadLibraryExW = winexternal('LoadLibraryExW', + [rffi.CWCHARP, HANDLE, DWORD], HMODULE, + save_err=rffi.RFFI_SAVE_LASTERROR) + LoadLibraryExW = wrap_loadlibraryex(_LoadLibraryExW) GetProcAddress = winexternal('GetProcAddress', [HMODULE, rffi.CCHARP], rffi.VOIDP) FreeLibrary = winexternal('FreeLibrary', [HMODULE], BOOL, releasegil=False) - LocalFree = winexternal('LocalFree', [HLOCAL], DWORD) + LocalFree = winexternal('LocalFree', [HLOCAL], HLOCAL) CloseHandle = winexternal('CloseHandle', [HANDLE], BOOL, releasegil=False, save_err=rffi.RFFI_SAVE_LASTERROR) CloseHandle_no_err = winexternal('CloseHandle', [HANDLE], BOOL, @@ -215,12 +232,12 @@ [DWORD, rffi.VOIDP, DWORD, DWORD, rffi.CWCHARP, DWORD, rffi.VOIDP], DWORD) - _get_osfhandle = rffi.llexternal('_get_osfhandle', [rffi.INT], HANDLE) + _get_osfhandle = rffi.llexternal('_get_osfhandle', [rffi.INT], rffi.INTP) def get_osfhandle(fd): from rpython.rlib.rposix import FdValidator with FdValidator(fd): - handle = _get_osfhandle(fd) + handle = rffi.cast(HANDLE, _get_osfhandle(fd)) if handle == INVALID_HANDLE_VALUE: raise WindowsError(ERROR_INVALID_HANDLE, "Invalid file handle") return handle diff --git a/rpython/rlib/test/loadtest/loadtest0.dll b/rpython/rlib/test/loadtest/loadtest0.dll new file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..9bdcc33a1902f8e989d349c49c2cc08e633aa32b GIT binary patch [cut] diff --git a/rpython/rlib/test/loadtest/loadtest1.dll b/rpython/rlib/test/loadtest/loadtest1.dll new file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..cb83854875c876717371bdf90488eed9c6571f03 GIT binary patch [cut] diff --git a/rpython/rlib/test/test_rposix.py b/rpython/rlib/test/test_rposix.py --- a/rpython/rlib/test/test_rposix.py +++ b/rpython/rlib/test/test_rposix.py @@ -83,6 +83,14 @@ # the most intriguing failure of ntpath.py should not repeat, here: assert not data.endswith(stuff) + @win_only + def test__getfullpathname_long(self): + stuff = "C:" + "\\abcd" * 100 + py.test.raises(WindowsError, rposix.getfullpathname, stuff) + ustuff = u"C:" + u"\\abcd" * 100 + res = rposix.getfullpathname(ustuff) + assert res == ustuff + def test_getcwd(self): assert rposix.getcwd() == os.getcwd() diff --git a/rpython/rlib/test/test_rwin32.py b/rpython/rlib/test/test_rwin32.py --- a/rpython/rlib/test/test_rwin32.py +++ b/rpython/rlib/test/test_rwin32.py @@ -6,6 +6,44 @@ from rpython.rlib import rwin32 from rpython.tool.udir import udir +loadtest_dir = os.path.dirname(__file__) + '/loadtest' +test1 = os.path.abspath(loadtest_dir + '/loadtest1.dll') +test0 = os.path.abspath(loadtest_dir + '/loadtest0.dll') + +if not os.path.exists(test1) or not os.path.exists(test0): + # This is how the files, which are checked into the repo, were created + from rpython.translator.tool.cbuild import ExternalCompilationInfo + from rpython.translator.platform import platform + from rpython.translator import cdir + if not os.path.exists(loadtest_dir): + os.mkdir(loadtest_dir) + c_file = udir.ensure("test_rwin32", dir=1).join("test0.c") + c_file.write(py.code.Source(''' + #include "src/precommondefs.h" + RPY_EXPORTED + int internal_sum(int a, int b) { + return a + b; + } + ''')) + eci = ExternalCompilationInfo(include_dirs=[cdir]) + lib_name = str(platform.compile([c_file], eci, test0[:-4], + standalone=False)) + assert os.path.abspath(lib_name) == os.path.abspath(test0) + + c_file = udir.ensure("test_rwin32", dir=1).join("test1.c") + c_file.write(py.code.Source(''' + #include "src/precommondefs.h" + int internal_sum(int a, int b); + RPY_EXPORTED + int sum(int a, int b) { + return internal_sum(a, b); + } + ''')) + eci = ExternalCompilationInfo(include_dirs=[cdir], + libraries=[loadtest_dir + '/loadtest0']) + lib_name = str(platform.compile([c_file], eci, test1[:-4], + standalone=False, )) + assert os.path.abspath(lib_name) == os.path.abspath(test1) def test_get_osfhandle(): fid = open(str(udir.join('validate_test.txt')), 'w') @@ -28,13 +66,13 @@ "import time;" "time.sleep(10)", ], - ) + ) print proc.pid handle = rwin32.OpenProcess(rwin32.PROCESS_ALL_ACCESS, False, proc.pid) assert rwin32.TerminateProcess(handle, signal.SIGTERM) == 1 rwin32.CloseHandle(handle) assert proc.wait() == signal.SIGTERM - + @py.test.mark.dont_track_allocations('putenv intentionally keeps strings alive') def test_wenviron(): name, value = u'PYPY_TEST_日本', u'foobar日本' @@ -55,3 +93,48 @@ msg = rwin32.FormatErrorW(34) assert type(msg) is unicode assert u'%2' in msg + +def test_loadlibraryA(): + # test0 can be loaded alone, but test1 requires the modified search path + hdll = rwin32.LoadLibrary(test0) + assert hdll + faddr = rwin32.GetProcAddress(hdll, 'internal_sum') + assert faddr + assert rwin32.FreeLibrary(hdll) + + hdll = rwin32.LoadLibrary(test1) + assert not hdll + + assert os.path.exists(test1) + + hdll = rwin32.LoadLibraryExA(test1) + assert hdll + faddr = rwin32.GetProcAddress(hdll, 'sum') + assert faddr + assert rwin32.FreeLibrary(hdll) _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit