diff --git a/Lib/_pyio.py b/Lib/_pyio.py index b04d23a0c2f..d4cfb6e1264 100644 --- a/Lib/_pyio.py +++ b/Lib/_pyio.py @@ -200,38 +200,45 @@ def open(file, mode="r", buffering=-1, encoding=None, errors=None, (appending and "a" or "") + (updating and "+" or ""), closefd, opener=opener) - line_buffering = False - if buffering == 1 or buffering < 0 and raw.isatty(): - buffering = -1 - line_buffering = True - if buffering < 0: - buffering = DEFAULT_BUFFER_SIZE - try: - bs = os.fstat(raw.fileno()).st_blksize - except (OSError, AttributeError): - pass + result = raw + try: + line_buffering = False + if buffering == 1 or buffering < 0 and raw.isatty(): + buffering = -1 + line_buffering = True + if buffering < 0: + buffering = DEFAULT_BUFFER_SIZE + try: + bs = os.fstat(raw.fileno()).st_blksize + except (OSError, AttributeError): + pass + else: + if bs > 1: + buffering = bs + if buffering < 0: + raise ValueError("invalid buffering size") + if buffering == 0: + if binary: + return result + raise ValueError("can't have unbuffered text I/O") + if updating: + buffer = BufferedRandom(raw, buffering) + elif creating or writing or appending: + buffer = BufferedWriter(raw, buffering) + elif reading: + buffer = BufferedReader(raw, buffering) else: - if bs > 1: - buffering = bs - if buffering < 0: - raise ValueError("invalid buffering size") - if buffering == 0: + raise ValueError("unknown mode: %r" % mode) + result = buffer if binary: - return raw - raise ValueError("can't have unbuffered text I/O") - if updating: - buffer = BufferedRandom(raw, buffering) - elif creating or writing or appending: - buffer = BufferedWriter(raw, buffering) - elif reading: - buffer = BufferedReader(raw, buffering) - else: - raise ValueError("unknown mode: %r" % mode) - if binary: - return buffer - text = TextIOWrapper(buffer, encoding, errors, newline, line_buffering) - text.mode = mode - return text + return result + text = TextIOWrapper(buffer, encoding, errors, newline, line_buffering) + result = text + text.mode = mode + return result + except: + result.close() + raise class DocDescriptor: diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py index 3c2c1025d0c..eef100f7eaf 100644 --- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py @@ -653,6 +653,20 @@ class IOTest(unittest.TestCase): fileio.close() f2.readline() + def test_nonbuffered_textio(self): + with warnings.catch_warnings(record=True) as recorded: + with self.assertRaises(ValueError): + self.open(support.TESTFN, 'w', buffering=0) + support.gc_collect() + self.assertEqual(recorded, []) + + def test_invalid_newline(self): + with warnings.catch_warnings(record=True) as recorded: + with self.assertRaises(ValueError): + self.open(support.TESTFN, 'w', newline='invalid') + support.gc_collect() + self.assertEqual(recorded, []) + class CIOTest(IOTest): diff --git a/Misc/NEWS b/Misc/NEWS index 99507251149..28474bb626f 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -92,6 +92,8 @@ Core and Builtins Library ------- +- Issue #21310: Fixed possible resource leak in failed open(). + - Issue #21256: Printout of keyword args should be in deterministic order in a mock function call. This will help to write better doctests. diff --git a/Modules/_io/_iomodule.c b/Modules/_io/_iomodule.c index be0464c4fa3..1dbd563e2c6 100644 --- a/Modules/_io/_iomodule.c +++ b/Modules/_io/_iomodule.c @@ -235,11 +235,12 @@ io_open(PyObject *self, PyObject *args, PyObject *kwds) char rawmode[6], *m; int line_buffering, isatty; - PyObject *raw, *modeobj = NULL, *buffer = NULL, *wrapper = NULL; + PyObject *raw, *modeobj = NULL, *buffer, *wrapper, *result = NULL; _Py_IDENTIFIER(isatty); _Py_IDENTIFIER(fileno); _Py_IDENTIFIER(mode); + _Py_IDENTIFIER(close); if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|sizzziO:open", kwlist, &file, &mode, &buffering, @@ -354,6 +355,7 @@ io_open(PyObject *self, PyObject *args, PyObject *kwds) "OsiO", file, rawmode, closefd, opener); if (raw == NULL) return NULL; + result = raw; modeobj = PyUnicode_FromString(mode); if (modeobj == NULL) @@ -412,7 +414,7 @@ io_open(PyObject *self, PyObject *args, PyObject *kwds) } Py_DECREF(modeobj); - return raw; + return result; } /* wraps into a buffered file */ @@ -433,15 +435,16 @@ io_open(PyObject *self, PyObject *args, PyObject *kwds) buffer = PyObject_CallFunction(Buffered_class, "Oi", raw, buffering); } - Py_CLEAR(raw); if (buffer == NULL) goto error; + result = buffer; + Py_DECREF(raw); /* if binary, returns the buffered file */ if (binary) { Py_DECREF(modeobj); - return buffer; + return result; } /* wraps into a TextIOWrapper */ @@ -450,20 +453,35 @@ io_open(PyObject *self, PyObject *args, PyObject *kwds) buffer, encoding, errors, newline, line_buffering); - Py_CLEAR(buffer); if (wrapper == NULL) goto error; + result = wrapper; + Py_DECREF(buffer); if (_PyObject_SetAttrId(wrapper, &PyId_mode, modeobj) < 0) goto error; Py_DECREF(modeobj); - return wrapper; + return result; error: - Py_XDECREF(raw); + if (result != NULL) { + PyObject *exc, *val, *tb; + PyErr_Fetch(&exc, &val, &tb); + if (_PyObject_CallMethodId(result, &PyId_close, NULL) != NULL) + PyErr_Restore(exc, val, tb); + else { + PyObject *val2; + PyErr_NormalizeException(&exc, &val, &tb); + Py_XDECREF(exc); + Py_XDECREF(tb); + PyErr_Fetch(&exc, &val2, &tb); + PyErr_NormalizeException(&exc, &val2, &tb); + PyException_SetContext(val2, val); + PyErr_Restore(exc, val2, tb); + } + Py_DECREF(result); + } Py_XDECREF(modeobj); - Py_XDECREF(buffer); - Py_XDECREF(wrapper); return NULL; }