Kaydet (Commit) 8043cf86 authored tarafından Antoine Pitrou's avatar Antoine Pitrou

Issue #4604: Some objects of the I/O library could still be used after

having been closed (for instance, a read() call could return some
previously buffered data). Patch by Dmitry Vasiliev.
üst e7bd8684
...@@ -340,6 +340,7 @@ class IOBase(metaclass=abc.ABCMeta): ...@@ -340,6 +340,7 @@ class IOBase(metaclass=abc.ABCMeta):
def tell(self) -> int: def tell(self) -> int:
"""Return current stream position.""" """Return current stream position."""
self._checkClosed()
return self.seek(0, 1) return self.seek(0, 1)
def truncate(self, pos: int = None) -> int: def truncate(self, pos: int = None) -> int:
...@@ -358,6 +359,8 @@ class IOBase(metaclass=abc.ABCMeta): ...@@ -358,6 +359,8 @@ class IOBase(metaclass=abc.ABCMeta):
This is not implemented for read-only and non-blocking streams. This is not implemented for read-only and non-blocking streams.
""" """
# XXX Should this return the number of bytes written??? # XXX Should this return the number of bytes written???
if self.__closed:
raise ValueError("I/O operation on closed file.")
__closed = False __closed = False
...@@ -530,6 +533,7 @@ class IOBase(metaclass=abc.ABCMeta): ...@@ -530,6 +533,7 @@ class IOBase(metaclass=abc.ABCMeta):
lines will be read if the total size (in bytes/characters) of all lines will be read if the total size (in bytes/characters) of all
lines so far exceeds hint. lines so far exceeds hint.
""" """
self._checkClosed()
if hint is None or hint <= 0: if hint is None or hint <= 0:
return list(self) return list(self)
n = 0 n = 0
...@@ -567,6 +571,7 @@ class RawIOBase(IOBase): ...@@ -567,6 +571,7 @@ class RawIOBase(IOBase):
Returns an empty bytes object on EOF, or None if the object is Returns an empty bytes object on EOF, or None if the object is
set not to block and has no data to read. set not to block and has no data to read.
""" """
self._checkClosed()
if n is None: if n is None:
n = -1 n = -1
if n < 0: if n < 0:
...@@ -578,6 +583,7 @@ class RawIOBase(IOBase): ...@@ -578,6 +583,7 @@ class RawIOBase(IOBase):
def readall(self): def readall(self):
"""Read until EOF, using multiple read() call.""" """Read until EOF, using multiple read() call."""
self._checkClosed()
res = bytearray() res = bytearray()
while True: while True:
data = self.read(DEFAULT_BUFFER_SIZE) data = self.read(DEFAULT_BUFFER_SIZE)
...@@ -673,6 +679,7 @@ class BufferedIOBase(IOBase): ...@@ -673,6 +679,7 @@ class BufferedIOBase(IOBase):
data at the moment. data at the moment.
""" """
# XXX This ought to work with anything that supports the buffer API # XXX This ought to work with anything that supports the buffer API
self._checkClosed()
data = self.read(len(b)) data = self.read(len(b))
n = len(data) n = len(data)
try: try:
...@@ -787,13 +794,11 @@ class _BytesIO(BufferedIOBase): ...@@ -787,13 +794,11 @@ class _BytesIO(BufferedIOBase):
def getvalue(self): def getvalue(self):
"""Return the bytes value (contents) of the buffer """Return the bytes value (contents) of the buffer
""" """
if self.closed: self._checkClosed()
raise ValueError("getvalue on closed file")
return bytes(self._buffer) return bytes(self._buffer)
def read(self, n=None): def read(self, n=None):
if self.closed: self._checkClosed()
raise ValueError("read from closed file")
if n is None: if n is None:
n = -1 n = -1
if n < 0: if n < 0:
...@@ -811,8 +816,7 @@ class _BytesIO(BufferedIOBase): ...@@ -811,8 +816,7 @@ class _BytesIO(BufferedIOBase):
return self.read(n) return self.read(n)
def write(self, b): def write(self, b):
if self.closed: self._checkClosed()
raise ValueError("write to closed file")
if isinstance(b, str): if isinstance(b, str):
raise TypeError("can't write str to binary stream") raise TypeError("can't write str to binary stream")
n = len(b) n = len(b)
...@@ -829,8 +833,7 @@ class _BytesIO(BufferedIOBase): ...@@ -829,8 +833,7 @@ class _BytesIO(BufferedIOBase):
return n return n
def seek(self, pos, whence=0): def seek(self, pos, whence=0):
if self.closed: self._checkClosed()
raise ValueError("seek on closed file")
try: try:
pos = pos.__index__() pos = pos.__index__()
except AttributeError as err: except AttributeError as err:
...@@ -848,13 +851,11 @@ class _BytesIO(BufferedIOBase): ...@@ -848,13 +851,11 @@ class _BytesIO(BufferedIOBase):
return self._pos return self._pos
def tell(self): def tell(self):
if self.closed: self._checkClosed()
raise ValueError("tell on closed file")
return self._pos return self._pos
def truncate(self, pos=None): def truncate(self, pos=None):
if self.closed: self._checkClosed()
raise ValueError("truncate on closed file")
if pos is None: if pos is None:
pos = self._pos pos = self._pos
elif pos < 0: elif pos < 0:
...@@ -914,6 +915,7 @@ class BufferedReader(_BufferedIOMixin): ...@@ -914,6 +915,7 @@ class BufferedReader(_BufferedIOMixin):
mode. If n is negative, read until EOF or until read() would mode. If n is negative, read until EOF or until read() would
block. block.
""" """
self._checkClosed()
with self._read_lock: with self._read_lock:
return self._read_unlocked(n) return self._read_unlocked(n)
...@@ -970,6 +972,7 @@ class BufferedReader(_BufferedIOMixin): ...@@ -970,6 +972,7 @@ class BufferedReader(_BufferedIOMixin):
do at most one raw read to satisfy it. We never return more do at most one raw read to satisfy it. We never return more
than self.buffer_size. than self.buffer_size.
""" """
self._checkClosed()
with self._read_lock: with self._read_lock:
return self._peek_unlocked(n) return self._peek_unlocked(n)
...@@ -988,6 +991,7 @@ class BufferedReader(_BufferedIOMixin): ...@@ -988,6 +991,7 @@ class BufferedReader(_BufferedIOMixin):
"""Reads up to n bytes, with at most one read() system call.""" """Reads up to n bytes, with at most one read() system call."""
# Returns up to n bytes. If at least one byte is buffered, we # Returns up to n bytes. If at least one byte is buffered, we
# only return buffered bytes. Otherwise, we do one raw read. # only return buffered bytes. Otherwise, we do one raw read.
self._checkClosed()
if n <= 0: if n <= 0:
return b"" return b""
with self._read_lock: with self._read_lock:
...@@ -996,9 +1000,11 @@ class BufferedReader(_BufferedIOMixin): ...@@ -996,9 +1000,11 @@ class BufferedReader(_BufferedIOMixin):
min(n, len(self._read_buf) - self._read_pos)) min(n, len(self._read_buf) - self._read_pos))
def tell(self): def tell(self):
self._checkClosed()
return self.raw.tell() - len(self._read_buf) + self._read_pos return self.raw.tell() - len(self._read_buf) + self._read_pos
def seek(self, pos, whence=0): def seek(self, pos, whence=0):
self._checkClosed()
with self._read_lock: with self._read_lock:
if whence == 1: if whence == 1:
pos -= len(self._read_buf) - self._read_pos pos -= len(self._read_buf) - self._read_pos
...@@ -1029,8 +1035,7 @@ class BufferedWriter(_BufferedIOMixin): ...@@ -1029,8 +1035,7 @@ class BufferedWriter(_BufferedIOMixin):
self._write_lock = Lock() self._write_lock = Lock()
def write(self, b): def write(self, b):
if self.closed: self._checkClosed()
raise ValueError("write to closed file")
if isinstance(b, str): if isinstance(b, str):
raise TypeError("can't write str to binary stream") raise TypeError("can't write str to binary stream")
with self._write_lock: with self._write_lock:
...@@ -1060,6 +1065,7 @@ class BufferedWriter(_BufferedIOMixin): ...@@ -1060,6 +1065,7 @@ class BufferedWriter(_BufferedIOMixin):
return written return written
def truncate(self, pos=None): def truncate(self, pos=None):
self._checkClosed()
with self._write_lock: with self._write_lock:
self._flush_unlocked() self._flush_unlocked()
if pos is None: if pos is None:
...@@ -1067,12 +1073,11 @@ class BufferedWriter(_BufferedIOMixin): ...@@ -1067,12 +1073,11 @@ class BufferedWriter(_BufferedIOMixin):
return self.raw.truncate(pos) return self.raw.truncate(pos)
def flush(self): def flush(self):
self._checkClosed()
with self._write_lock: with self._write_lock:
self._flush_unlocked() self._flush_unlocked()
def _flush_unlocked(self): def _flush_unlocked(self):
if self.closed:
raise ValueError("flush of closed file")
written = 0 written = 0
try: try:
while self._write_buf: while self._write_buf:
...@@ -1086,9 +1091,11 @@ class BufferedWriter(_BufferedIOMixin): ...@@ -1086,9 +1091,11 @@ class BufferedWriter(_BufferedIOMixin):
raise BlockingIOError(e.errno, e.strerror, written) raise BlockingIOError(e.errno, e.strerror, written)
def tell(self): def tell(self):
self._checkClosed()
return self.raw.tell() + len(self._write_buf) return self.raw.tell() + len(self._write_buf)
def seek(self, pos, whence=0): def seek(self, pos, whence=0):
self._checkClosed()
with self._write_lock: with self._write_lock:
self._flush_unlocked() self._flush_unlocked()
return self.raw.seek(pos, whence) return self.raw.seek(pos, whence)
...@@ -1186,6 +1193,7 @@ class BufferedRandom(BufferedWriter, BufferedReader): ...@@ -1186,6 +1193,7 @@ class BufferedRandom(BufferedWriter, BufferedReader):
return pos return pos
def tell(self): def tell(self):
self._checkClosed()
if self._write_buf: if self._write_buf:
return self.raw.tell() + len(self._write_buf) return self.raw.tell() + len(self._write_buf)
else: else:
...@@ -1217,6 +1225,7 @@ class BufferedRandom(BufferedWriter, BufferedReader): ...@@ -1217,6 +1225,7 @@ class BufferedRandom(BufferedWriter, BufferedReader):
return BufferedReader.read1(self, n) return BufferedReader.read1(self, n)
def write(self, b): def write(self, b):
self._checkClosed()
if self._read_buf: if self._read_buf:
# Undo readahead # Undo readahead
with self._read_lock: with self._read_lock:
...@@ -1474,8 +1483,7 @@ class TextIOWrapper(TextIOBase): ...@@ -1474,8 +1483,7 @@ class TextIOWrapper(TextIOBase):
return self.buffer.isatty() return self.buffer.isatty()
def write(self, s: str): def write(self, s: str):
if self.closed: self._checkClosed()
raise ValueError("write to closed file")
if not isinstance(s, str): if not isinstance(s, str):
raise TypeError("can't write %s to text stream" % raise TypeError("can't write %s to text stream" %
s.__class__.__name__) s.__class__.__name__)
...@@ -1583,6 +1591,7 @@ class TextIOWrapper(TextIOBase): ...@@ -1583,6 +1591,7 @@ class TextIOWrapper(TextIOBase):
return position, dec_flags, bytes_to_feed, need_eof, chars_to_skip return position, dec_flags, bytes_to_feed, need_eof, chars_to_skip
def tell(self): def tell(self):
self._checkClosed()
if not self._seekable: if not self._seekable:
raise IOError("underlying stream is not seekable") raise IOError("underlying stream is not seekable")
if not self._telling: if not self._telling:
...@@ -1653,8 +1662,7 @@ class TextIOWrapper(TextIOBase): ...@@ -1653,8 +1662,7 @@ class TextIOWrapper(TextIOBase):
return self.buffer.truncate() return self.buffer.truncate()
def seek(self, cookie, whence=0): def seek(self, cookie, whence=0):
if self.closed: self._checkClosed()
raise ValueError("tell on closed file")
if not self._seekable: if not self._seekable:
raise IOError("underlying stream is not seekable") raise IOError("underlying stream is not seekable")
if whence == 1: # seek relative to current position if whence == 1: # seek relative to current position
...@@ -1712,6 +1720,7 @@ class TextIOWrapper(TextIOBase): ...@@ -1712,6 +1720,7 @@ class TextIOWrapper(TextIOBase):
return cookie return cookie
def read(self, n=None): def read(self, n=None):
self._checkClosed()
if n is None: if n is None:
n = -1 n = -1
decoder = self._decoder or self._get_decoder() decoder = self._decoder or self._get_decoder()
...@@ -1732,6 +1741,7 @@ class TextIOWrapper(TextIOBase): ...@@ -1732,6 +1741,7 @@ class TextIOWrapper(TextIOBase):
return result return result
def __next__(self): def __next__(self):
self._checkClosed()
self._telling = False self._telling = False
line = self.readline() line = self.readline()
if not line: if not line:
...@@ -1741,8 +1751,7 @@ class TextIOWrapper(TextIOBase): ...@@ -1741,8 +1751,7 @@ class TextIOWrapper(TextIOBase):
return line return line
def readline(self, limit=None): def readline(self, limit=None):
if self.closed: self._checkClosed()
raise ValueError("read from closed file")
if limit is None: if limit is None:
limit = -1 limit = -1
...@@ -1963,8 +1972,7 @@ try: ...@@ -1963,8 +1972,7 @@ try:
def getvalue(self) -> str: def getvalue(self) -> str:
"""Retrieve the entire contents of the object.""" """Retrieve the entire contents of the object."""
if self.closed: self._checkClosed()
raise ValueError("read on closed file")
return self._getvalue() return self._getvalue()
def write(self, s: str) -> int: def write(self, s: str) -> int:
...@@ -1972,8 +1980,7 @@ try: ...@@ -1972,8 +1980,7 @@ try:
Returns the number of characters written. Returns the number of characters written.
""" """
if self.closed: self._checkClosed()
raise ValueError("write to closed file")
if not isinstance(s, str): if not isinstance(s, str):
raise TypeError("can't write %s to text stream" % raise TypeError("can't write %s to text stream" %
s.__class__.__name__) s.__class__.__name__)
...@@ -1990,8 +1997,7 @@ try: ...@@ -1990,8 +1997,7 @@ try:
If the argument is negative or omitted, read until EOF If the argument is negative or omitted, read until EOF
is reached. Return an empty string at EOF. is reached. Return an empty string at EOF.
""" """
if self.closed: self._checkClosed()
raise ValueError("read to closed file")
if n is None: if n is None:
n = -1 n = -1
res = self._pending res = self._pending
...@@ -2006,8 +2012,7 @@ try: ...@@ -2006,8 +2012,7 @@ try:
def tell(self) -> int: def tell(self) -> int:
"""Tell the current file position.""" """Tell the current file position."""
if self.closed: self._checkClosed()
raise ValueError("tell from closed file")
if self._pending: if self._pending:
return self._tell() - len(self._pending) return self._tell() - len(self._pending)
else: else:
...@@ -2022,8 +2027,7 @@ try: ...@@ -2022,8 +2027,7 @@ try:
2 End of stream - pos must be 0. 2 End of stream - pos must be 0.
Returns the new absolute position. Returns the new absolute position.
""" """
if self.closed: self._checkClosed()
raise ValueError("seek from closed file")
self._pending = "" self._pending = ""
return self._seek(pos, whence) return self._seek(pos, whence)
...@@ -2034,14 +2038,12 @@ try: ...@@ -2034,14 +2038,12 @@ try:
returned by tell(). Imply an absolute seek to pos. returned by tell(). Imply an absolute seek to pos.
Returns the new absolute position. Returns the new absolute position.
""" """
if self.closed: self._checkClosed()
raise ValueError("truncate from closed file")
self._pending = "" self._pending = ""
return self._truncate(pos) return self._truncate(pos)
def readline(self, limit: int = None) -> str: def readline(self, limit: int = None) -> str:
if self.closed: self._checkClosed()
raise ValueError("read from closed file")
if limit is None: if limit is None:
limit = -1 limit = -1
if limit >= 0: if limit >= 0:
......
...@@ -1324,6 +1324,45 @@ class MiscIOTest(unittest.TestCase): ...@@ -1324,6 +1324,45 @@ class MiscIOTest(unittest.TestCase):
f.close() f.close()
g.close() g.close()
def test_io_after_close(self):
for kwargs in [
{"mode": "w"},
{"mode": "wb"},
{"mode": "w", "buffering": 1},
{"mode": "w", "buffering": 2},
{"mode": "wb", "buffering": 0},
{"mode": "r"},
{"mode": "rb"},
{"mode": "r", "buffering": 1},
{"mode": "r", "buffering": 2},
{"mode": "rb", "buffering": 0},
{"mode": "w+"},
{"mode": "w+b"},
{"mode": "w+", "buffering": 1},
{"mode": "w+", "buffering": 2},
{"mode": "w+b", "buffering": 0},
]:
f = io.open(support.TESTFN, **kwargs)
f.close()
self.assertRaises(ValueError, f.flush)
self.assertRaises(ValueError, f.fileno)
self.assertRaises(ValueError, f.isatty)
self.assertRaises(ValueError, f.__iter__)
if hasattr(f, "peek"):
self.assertRaises(ValueError, f.peek, 1)
self.assertRaises(ValueError, f.read)
if hasattr(f, "read1"):
self.assertRaises(ValueError, f.read1, 1024)
if hasattr(f, "readinto"):
self.assertRaises(ValueError, f.readinto, bytearray(1024))
self.assertRaises(ValueError, f.readline)
self.assertRaises(ValueError, f.readlines)
self.assertRaises(ValueError, f.seek, 0)
self.assertRaises(ValueError, f.tell)
self.assertRaises(ValueError, f.truncate)
self.assertRaises(ValueError, f.write, "")
self.assertRaises(ValueError, f.writelines, [])
def test_main(): def test_main():
support.run_unittest(IOTest, BytesIOTest, StringIOTest, support.run_unittest(IOTest, BytesIOTest, StringIOTest,
......
...@@ -12,6 +12,10 @@ What's New in Python 3.1 alpha 0 ...@@ -12,6 +12,10 @@ What's New in Python 3.1 alpha 0
Core and Builtins Core and Builtins
----------------- -----------------
- Issue #4604: Some objects of the I/O library could still be used after
having been closed (for instance, a read() call could return some
previously buffered data). Patch by Dmitry Vasiliev.
- Issue #4705: Fix the -u ("unbuffered binary stdout and stderr") command-line - Issue #4705: Fix the -u ("unbuffered binary stdout and stderr") command-line
flag to work properly. Furthermore, when specifying -u, the text stdout flag to work properly. Furthermore, when specifying -u, the text stdout
and stderr streams have line-by-line buffering enabled (the default being and stderr streams have line-by-line buffering enabled (the default being
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment