Author: Devin Jeanpierre <jeanpierr...@gmail.com> Branch: cpyext-old-buffers Changeset: r84531:546354142cce Date: 2016-05-20 08:30 -0700 http://bitbucket.org/pypy/pypy/changeset/546354142cce/
Log: Expose memory leaked by cpyext's buffer -> PyBufferObject to RPython. This will let me avoid copying it again inside the implementation of bf_getreadbuffer etc. diff --git a/pypy/module/cpyext/bufferobject.py b/pypy/module/cpyext/bufferobject.py --- a/pypy/module/cpyext/bufferobject.py +++ b/pypy/module/cpyext/bufferobject.py @@ -1,4 +1,4 @@ -from rpython.rlib.buffer import StringBuffer, SubBuffer +from rpython.rlib.buffer import Buffer, StringBuffer, SubBuffer from rpython.rtyper.lltypesystem import rffi, lltype from pypy.interpreter.error import oefmt from pypy.module.cpyext.api import ( @@ -9,6 +9,54 @@ from pypy.objspace.std.bufferobject import W_Buffer +class LeakedBuffer(Buffer): + __slots__ = ['buf','ptr'] + _immutable_ = True + + def __init__(self, buffer): + if not buffer.readonly: + raise ValueError("Can only leak a copy of a readonly buffer.") + self.buf = buffer + self.readonly = True + self.ptr = rffi.cast(rffi.VOIDP, rffi.str2charp(self.buf.as_str())) + + def getlength(self): + return self.buf.getlength() + + def as_str(self): + return self.buf.as_str() + + def as_str_and_offset_maybe(self): + return self.buf.as_str_and_offset_maybe() + + def getitem(self, index): + return self.buf.getitem(index) + + def getslice(self, start, stop, step, size): + return self.buf.getslice(start, stop, step, size) + + def setitem(self, index, char): + return self.buf.setitem(index) + + def setslice(self, start, string): + return self.buf.setslice(start, string) + + def get_raw_address(self): + return self.ptr + + +def leak_stringbuffer(buf): + if isinstance(buf, StringBuffer): + return LeakedBuffer(buf) + elif isinstance(buf, SubBuffer): + leaked = leak_stringbuffer(buf.buffer) + if leaked is None: + return leaked + return SubBuffer(leaked, buf.offset, buf.size) + else: + return None + + PyBufferObjectStruct = lltype.ForwardReference() PyBufferObject = lltype.Ptr(PyBufferObjectStruct) PyBufferObjectFields = PyObjectFields + ( @@ -43,17 +91,19 @@ assert isinstance(w_obj, W_Buffer) buf = w_obj.buf + w_obj.buf = buf = leak_stringbuffer(buf) or buf + # Now, if it was backed by a StringBuffer, it is now a LeakedBuffer. + # We deliberately copy the string so that we can have a pointer to it, + # and we make it accessible in the buffer through get_raw_address(), so that + # we can reuse it elsewhere in the C API. + if isinstance(buf, SubBuffer): py_buf.c_b_offset = buf.offset buf = buf.buffer - # If buf already allocated a fixed buffer, use it, and keep a - # reference to buf. - # Otherwise, b_base stays NULL, and we own the b_ptr. - - if isinstance(buf, StringBuffer): + if isinstance(buf, LeakedBuffer): py_buf.c_b_base = lltype.nullptr(PyObject.TO) - py_buf.c_b_ptr = rffi.cast(rffi.VOIDP, rffi.str2charp(buf.value)) + py_buf.c_b_ptr = buf.get_raw_address() py_buf.c_b_size = buf.getlength() elif isinstance(buf, ArrayBuffer): w_base = buf.array diff --git a/pypy/module/cpyext/test/test_bufferobject.py b/pypy/module/cpyext/test/test_bufferobject.py --- a/pypy/module/cpyext/test/test_bufferobject.py +++ b/pypy/module/cpyext/test/test_bufferobject.py @@ -1,6 +1,8 @@ +from rpython.rlib.buffer import StringBuffer, SubBuffer from rpython.rtyper.lltypesystem import rffi, lltype from pypy.module.cpyext.test.test_api import BaseApiTest from pypy.module.cpyext.test.test_cpyext import AppTestCpythonExtensionBase +from pypy.module.cpyext.bufferobject import leak_stringbuffer from pypy.module.cpyext.api import PyObject from pypy.module.cpyext.pyobject import Py_DecRef @@ -63,4 +65,34 @@ a = array.array('c', 'text') b = buffer(a) assert module.roundtrip(b) == 'text' - + + +def test_leaked_buffer(): + s = 'hello world' + buf = leak_stringbuffer(StringBuffer(s)) + assert buf.getitem(4) == 'o' + assert buf.getitem(4) == buf[4] + assert buf.getlength() == 11 + assert buf.getlength() == len(buf) + assert buf.getslice(1, 6, 1, 5) == 'ello ' + assert buf.getslice(1, 6, 1, 5) == buf[1:6] + assert buf.getslice(1, 6, 2, 3) == 'el ' + assert buf.as_str() == 'hello world' + assert s == rffi.charp2str(buf.get_raw_address()) + rffi.free_charp(buf.get_raw_address()) + + +def test_leaked_subbuffer(): + s = 'hello world' + buf = leak_stringbuffer(SubBuffer(StringBuffer(s), 1, 10)) + assert buf.getitem(4) == ' ' + assert buf.getitem(4) == buf[4] + assert buf.getlength() == 10 + assert buf.getlength() == len(buf) + assert buf.getslice(1, 6, 1, 5) == 'llo w' + assert buf.getslice(1, 6, 1, 5) == buf[1:6] + assert buf.getslice(1, 6, 2, 3) == 'low' + assert buf.as_str() == 'ello world' + assert s[1:] == rffi.charp2str(buf.get_raw_address()) + rffi.free_charp(buf.buffer.get_raw_address()) + _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit