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.
This commit is contained in:
Antoine Pitrou 2009-01-09 19:54:29 +00:00
parent e7bd868429
commit 8043cf868c
3 changed files with 81 additions and 36 deletions

View File

@ -340,6 +340,7 @@ def seek(self, pos: int, whence: int = 0) -> int:
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 @@ def flush(self) -> None:
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 @@ def readlines(self, hint=None):
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 @@ def read(self, n: int = -1) -> bytes:
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 @@ def read(self, n: int = -1) -> bytes:
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 @@ def readinto(self, b: bytearray) -> int:
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 @@ def __init__(self, initial_bytes=None):
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 @@ def read1(self, n):
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 @@ def write(self, b):
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 @@ def seek(self, pos, whence=0):
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 @@ def read(self, n=None):
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 @@ def peek(self, n=0):
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 @@ def read1(self, n):
"""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 @@ def read1(self, n):
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 @@ def __init__(self, raw,
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 @@ def write(self, b):
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 @@ def truncate(self, pos=None):
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 @@ def _flush_unlocked(self):
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 @@ def seek(self, pos, whence=0):
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 @@ def read1(self, n):
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 @@ def isatty(self):
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 @@ def _unpack_cookie(self, bigint):
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 @@ def truncate(self, pos=None):
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 @@ def seek(self, cookie, whence=0):
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 @@ def read(self, n=None):
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 @@ def __next__(self):
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 @@ def seekable(self):
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 @@ def write(self, s: str) -> int:
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 @@ def read(self, n: int = None) -> str:
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 @@ def read(self, n: int = None) -> str:
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 @@ def seek(self, pos: int = None, whence: int = 0) -> int:
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 @@ def truncate(self, pos: int = None) -> int:
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:

View File

@ -1324,6 +1324,45 @@ def test_attributes(self):
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,

View File

@ -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