Skip to content

Commit

Permalink
Merge pull request #17775 from charris/backport-17598
Browse files Browse the repository at this point in the history
BUG: Fixed file handle leak in array_tofile.
  • Loading branch information
charris committed Nov 15, 2020
2 parents 85c7f3e + 6a834f1 commit 760449c
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 18 deletions.
53 changes: 35 additions & 18 deletions numpy/core/src/multiarray/methods.c
Expand Up @@ -583,6 +583,28 @@ array_tostring(PyArrayObject *self, PyObject *args, PyObject *kwds)
return PyArray_ToString(self, order);
}

/* Like PyArray_ToFile but takes the file as a python object */
static int
PyArray_ToFileObject(PyArrayObject *self, PyObject *file, char *sep, char *format)
{
npy_off_t orig_pos = 0;
FILE *fd = npy_PyFile_Dup2(file, "wb", &orig_pos);

if (fd == NULL) {
return -1;
}

int write_ret = PyArray_ToFile(self, fd, sep, format);
PyObject *err_type, *err_value, *err_traceback;
PyErr_Fetch(&err_type, &err_value, &err_traceback);
int close_ret = npy_PyFile_DupClose2(file, fd, orig_pos);
npy_PyErr_ChainExceptions(err_type, err_value, err_traceback);

if (write_ret || close_ret) {
return -1;
}
return 0;
}

/* This should grow an order= keyword to be consistent
*/
Expand All @@ -592,10 +614,8 @@ array_tofile(PyArrayObject *self, PyObject *args, PyObject *kwds)
{
int own;
PyObject *file;
FILE *fd;
char *sep = "";
char *format = "";
npy_off_t orig_pos = 0;
static char *kwlist[] = {"file", "sep", "format", NULL};

if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|ss:tofile", kwlist,
Expand All @@ -620,25 +640,22 @@ array_tofile(PyArrayObject *self, PyObject *args, PyObject *kwds)
own = 0;
}

fd = npy_PyFile_Dup2(file, "wb", &orig_pos);
if (fd == NULL) {
goto fail;
}
if (PyArray_ToFile(self, fd, sep, format) < 0) {
goto fail;
}
if (npy_PyFile_DupClose2(file, fd, orig_pos) < 0) {
goto fail;
}
if (own && npy_PyFile_CloseFile(file) < 0) {
goto fail;
int file_ret = PyArray_ToFileObject(self, file, sep, format);
int close_ret = 0;

if (own) {
PyObject *err_type, *err_value, *err_traceback;
PyErr_Fetch(&err_type, &err_value, &err_traceback);
close_ret = npy_PyFile_CloseFile(file);
npy_PyErr_ChainExceptions(err_type, err_value, err_traceback);
}
Py_DECREF(file);
Py_RETURN_NONE;

fail:
Py_DECREF(file);
return NULL;

if (file_ret || close_ret) {
return NULL;
}
Py_RETURN_NONE;
}

static PyObject *
Expand Down
11 changes: 11 additions & 0 deletions numpy/core/tests/test_multiarray.py
Expand Up @@ -4985,6 +4985,17 @@ def test_tofile_format(self):
s = f.read()
assert_equal(s, '1.51,2.00,3.51,4.00')

def test_tofile_cleanup(self):
x = np.zeros((10), dtype=object)
with open(self.filename, 'wb') as f:
assert_raises(IOError, lambda: x.tofile(f, sep=''))
# Dup-ed file handle should be closed or remove will fail on Windows OS
os.remove(self.filename)

# Also make sure that we close the Python handle
assert_raises(IOError, lambda: x.tofile(self.filename))
os.remove(self.filename)

def test_locale(self):
with CommaDecimalPointLocale():
self.test_numbers()
Expand Down

0 comments on commit 760449c

Please sign in to comment.