mirror of https://github.com/python/cpython.git
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:
parent
e7bd868429
commit
8043cf868c
74
Lib/io.py
74
Lib/io.py
|
@ -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:
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue