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

Reply via email to