Skip to content

Commit

Permalink
BUG: Fortify object buffers against included NULLs
Browse files Browse the repository at this point in the history
While NumPy tends to not actively create object buffers initialized
only with NULL (rather than filled with None), at least older versions
of NumPy did do that.  And NumPy guards against this.

This guards against embedded NULLs in object buffers interpreting
a NULL as None (and anticipating a NULL value also when setting
the buffer for reference count purposes).

Closes cythongh-4858
  • Loading branch information
seberg committed Jun 23, 2022
1 parent 579fcbc commit 1af7ccd
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 7 deletions.
17 changes: 10 additions & 7 deletions Cython/Compiler/ExprNodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -4578,17 +4578,17 @@ def generate_buffer_setitem_code(self, rhs, code, op=""):
buffer_entry, ptrexpr = self.buffer_lookup_code(code)

if self.buffer_type.dtype.is_pyobject:
# Must manage refcounts. Decref what is already there
# and incref what we put in.
# Must manage refcounts. XDecref what is already there
# and incref what we put in (NumPy allows there to be NULL)
ptr = code.funcstate.allocate_temp(buffer_entry.buf_ptr_type,
manage_ref=False)
rhs_code = rhs.result()
code.putln("%s = %s;" % (ptr, ptrexpr))
code.put_gotref("*%s" % ptr, self.buffer_type.dtype)
code.putln("__Pyx_INCREF(%s); __Pyx_DECREF(*%s);" % (
code.put_xgotref("*%s" % ptr, self.buffer_type.dtype)
code.putln("__Pyx_INCREF(%s); __Pyx_XDECREF(*%s);" % (
rhs_code, ptr))
code.putln("*%s %s= %s;" % (ptr, op, rhs_code))
code.put_giveref("*%s" % ptr, self.buffer_type.dtype)
code.put_xgiveref("*%s" % ptr, self.buffer_type.dtype)
code.funcstate.release_temp(ptr)
else:
# Simple case
Expand All @@ -4609,8 +4609,11 @@ def generate_result_code(self, code):
# is_temp is True, so must pull out value and incref it.
# NOTE: object temporary results for nodes are declared
# as PyObject *, so we need a cast
code.putln("%s = (PyObject *) *%s;" % (self.result(), self.buffer_ptr_code))
code.putln("__Pyx_INCREF((PyObject*)%s);" % self.result())
res = self.result()
code.putln("%s = (PyObject *) *%s;" % (res, self.buffer_ptr_code))
# NumPy does (occasionally) allow NULL to denote None.
code.putln("if (%s == NULL) %s = Py_None;" % (res, res))
code.putln("__Pyx_INCREF((PyObject*)%s);" % res)

def free_subexpr_temps(self, code):
for temp in self.index_temps:
Expand Down
45 changes: 45 additions & 0 deletions tests/buffers/bufaccess.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -1004,6 +1004,51 @@ def assign_to_object(object[object] buf, int idx, obj):
"""
buf[idx] = obj

@testcase
def check_object_nulled_1d(MockBuffer[object, ndim=1] buf, int idx, obj):
"""
See comments on printbuf_object above.
>>> a = object()
>>> rc1 = get_refcount(a)
>>> A = ObjectMockBuffer(None, [a, a])
>>> check_object_nulled_1d(A, 0, a)
>>> decref(a) # new reference "added" to A
>>> check_object_nulled_1d(A, 1, a)
>>> decref(a)
>>> A = ObjectMockBuffer(None, [a, a, a, a], strides=(2,))
>>> check_object_nulled_1d(A, 0, a) # only 0 due to stride
>>> decref(a)
>>> get_refcount(a) == rc1
True
"""
cdef void **data = <void **>buf.buffer
data[idx] = NULL
res = buf[idx] # takes None
buf[idx] = obj
return res

@testcase
def check_object_nulled_2d(MockBuffer[object, ndim=2] buf, int idx1, int idx2, obj):
"""
See comments on printbuf_object above.
>>> a = object()
>>> rc1 = get_refcount(a)
>>> A = ObjectMockBuffer(None, [a, a, a, a], shape=(2, 2))
>>> check_object_nulled_2d(A, 0, 0, a)
>>> decref(a) # new reference "added" to A
>>> check_object_nulled_2d(A, 1, 1, a)
>>> decref(a)
>>> get_refcount(a) == rc1
True
"""
cdef void **data = <void **>buf.buffer
data[idx1 + 2*idx2] = NULL
res = buf[idx1, idx2] # takes None
buf[idx1, idx2] = obj
return res

@testcase
def assign_temporary_to_object(object[object] buf):
"""
Expand Down

0 comments on commit 1af7ccd

Please sign in to comment.