Skip to content

Commit 8043cf8

Browse files
committed
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.
1 parent e7bd868 commit 8043cf8

File tree

3 files changed

+81
-36
lines changed

3 files changed

+81
-36
lines changed

Lib/io.py

+38-36
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,7 @@ def seek(self, pos: int, whence: int = 0) -> int:
340340

341341
def tell(self) -> int:
342342
"""Return current stream position."""
343+
self._checkClosed()
343344
return self.seek(0, 1)
344345

345346
def truncate(self, pos: int = None) -> int:
@@ -358,6 +359,8 @@ def flush(self) -> None:
358359
This is not implemented for read-only and non-blocking streams.
359360
"""
360361
# XXX Should this return the number of bytes written???
362+
if self.__closed:
363+
raise ValueError("I/O operation on closed file.")
361364

362365
__closed = False
363366

@@ -530,6 +533,7 @@ def readlines(self, hint=None):
530533
lines will be read if the total size (in bytes/characters) of all
531534
lines so far exceeds hint.
532535
"""
536+
self._checkClosed()
533537
if hint is None or hint <= 0:
534538
return list(self)
535539
n = 0
@@ -567,6 +571,7 @@ def read(self, n: int = -1) -> bytes:
567571
Returns an empty bytes object on EOF, or None if the object is
568572
set not to block and has no data to read.
569573
"""
574+
self._checkClosed()
570575
if n is None:
571576
n = -1
572577
if n < 0:
@@ -578,6 +583,7 @@ def read(self, n: int = -1) -> bytes:
578583

579584
def readall(self):
580585
"""Read until EOF, using multiple read() call."""
586+
self._checkClosed()
581587
res = bytearray()
582588
while True:
583589
data = self.read(DEFAULT_BUFFER_SIZE)
@@ -673,6 +679,7 @@ def readinto(self, b: bytearray) -> int:
673679
data at the moment.
674680
"""
675681
# XXX This ought to work with anything that supports the buffer API
682+
self._checkClosed()
676683
data = self.read(len(b))
677684
n = len(data)
678685
try:
@@ -787,13 +794,11 @@ def __init__(self, initial_bytes=None):
787794
def getvalue(self):
788795
"""Return the bytes value (contents) of the buffer
789796
"""
790-
if self.closed:
791-
raise ValueError("getvalue on closed file")
797+
self._checkClosed()
792798
return bytes(self._buffer)
793799

794800
def read(self, n=None):
795-
if self.closed:
796-
raise ValueError("read from closed file")
801+
self._checkClosed()
797802
if n is None:
798803
n = -1
799804
if n < 0:
@@ -811,8 +816,7 @@ def read1(self, n):
811816
return self.read(n)
812817

813818
def write(self, b):
814-
if self.closed:
815-
raise ValueError("write to closed file")
819+
self._checkClosed()
816820
if isinstance(b, str):
817821
raise TypeError("can't write str to binary stream")
818822
n = len(b)
@@ -829,8 +833,7 @@ def write(self, b):
829833
return n
830834

831835
def seek(self, pos, whence=0):
832-
if self.closed:
833-
raise ValueError("seek on closed file")
836+
self._checkClosed()
834837
try:
835838
pos = pos.__index__()
836839
except AttributeError as err:
@@ -848,13 +851,11 @@ def seek(self, pos, whence=0):
848851
return self._pos
849852

850853
def tell(self):
851-
if self.closed:
852-
raise ValueError("tell on closed file")
854+
self._checkClosed()
853855
return self._pos
854856

855857
def truncate(self, pos=None):
856-
if self.closed:
857-
raise ValueError("truncate on closed file")
858+
self._checkClosed()
858859
if pos is None:
859860
pos = self._pos
860861
elif pos < 0:
@@ -914,6 +915,7 @@ def read(self, n=None):
914915
mode. If n is negative, read until EOF or until read() would
915916
block.
916917
"""
918+
self._checkClosed()
917919
with self._read_lock:
918920
return self._read_unlocked(n)
919921

@@ -970,6 +972,7 @@ def peek(self, n=0):
970972
do at most one raw read to satisfy it. We never return more
971973
than self.buffer_size.
972974
"""
975+
self._checkClosed()
973976
with self._read_lock:
974977
return self._peek_unlocked(n)
975978

@@ -988,6 +991,7 @@ def read1(self, n):
988991
"""Reads up to n bytes, with at most one read() system call."""
989992
# Returns up to n bytes. If at least one byte is buffered, we
990993
# only return buffered bytes. Otherwise, we do one raw read.
994+
self._checkClosed()
991995
if n <= 0:
992996
return b""
993997
with self._read_lock:
@@ -996,9 +1000,11 @@ def read1(self, n):
9961000
min(n, len(self._read_buf) - self._read_pos))
9971001

9981002
def tell(self):
1003+
self._checkClosed()
9991004
return self.raw.tell() - len(self._read_buf) + self._read_pos
10001005

10011006
def seek(self, pos, whence=0):
1007+
self._checkClosed()
10021008
with self._read_lock:
10031009
if whence == 1:
10041010
pos -= len(self._read_buf) - self._read_pos
@@ -1029,8 +1035,7 @@ def __init__(self, raw,
10291035
self._write_lock = Lock()
10301036

10311037
def write(self, b):
1032-
if self.closed:
1033-
raise ValueError("write to closed file")
1038+
self._checkClosed()
10341039
if isinstance(b, str):
10351040
raise TypeError("can't write str to binary stream")
10361041
with self._write_lock:
@@ -1060,19 +1065,19 @@ def write(self, b):
10601065
return written
10611066

10621067
def truncate(self, pos=None):
1068+
self._checkClosed()
10631069
with self._write_lock:
10641070
self._flush_unlocked()
10651071
if pos is None:
10661072
pos = self.raw.tell()
10671073
return self.raw.truncate(pos)
10681074

10691075
def flush(self):
1076+
self._checkClosed()
10701077
with self._write_lock:
10711078
self._flush_unlocked()
10721079

10731080
def _flush_unlocked(self):
1074-
if self.closed:
1075-
raise ValueError("flush of closed file")
10761081
written = 0
10771082
try:
10781083
while self._write_buf:
@@ -1086,9 +1091,11 @@ def _flush_unlocked(self):
10861091
raise BlockingIOError(e.errno, e.strerror, written)
10871092

10881093
def tell(self):
1094+
self._checkClosed()
10891095
return self.raw.tell() + len(self._write_buf)
10901096

10911097
def seek(self, pos, whence=0):
1098+
self._checkClosed()
10921099
with self._write_lock:
10931100
self._flush_unlocked()
10941101
return self.raw.seek(pos, whence)
@@ -1186,6 +1193,7 @@ def seek(self, pos, whence=0):
11861193
return pos
11871194

11881195
def tell(self):
1196+
self._checkClosed()
11891197
if self._write_buf:
11901198
return self.raw.tell() + len(self._write_buf)
11911199
else:
@@ -1217,6 +1225,7 @@ def read1(self, n):
12171225
return BufferedReader.read1(self, n)
12181226

12191227
def write(self, b):
1228+
self._checkClosed()
12201229
if self._read_buf:
12211230
# Undo readahead
12221231
with self._read_lock:
@@ -1474,8 +1483,7 @@ def isatty(self):
14741483
return self.buffer.isatty()
14751484

14761485
def write(self, s: str):
1477-
if self.closed:
1478-
raise ValueError("write to closed file")
1486+
self._checkClosed()
14791487
if not isinstance(s, str):
14801488
raise TypeError("can't write %s to text stream" %
14811489
s.__class__.__name__)
@@ -1583,6 +1591,7 @@ def _unpack_cookie(self, bigint):
15831591
return position, dec_flags, bytes_to_feed, need_eof, chars_to_skip
15841592

15851593
def tell(self):
1594+
self._checkClosed()
15861595
if not self._seekable:
15871596
raise IOError("underlying stream is not seekable")
15881597
if not self._telling:
@@ -1653,8 +1662,7 @@ def truncate(self, pos=None):
16531662
return self.buffer.truncate()
16541663

16551664
def seek(self, cookie, whence=0):
1656-
if self.closed:
1657-
raise ValueError("tell on closed file")
1665+
self._checkClosed()
16581666
if not self._seekable:
16591667
raise IOError("underlying stream is not seekable")
16601668
if whence == 1: # seek relative to current position
@@ -1712,6 +1720,7 @@ def seek(self, cookie, whence=0):
17121720
return cookie
17131721

17141722
def read(self, n=None):
1723+
self._checkClosed()
17151724
if n is None:
17161725
n = -1
17171726
decoder = self._decoder or self._get_decoder()
@@ -1732,6 +1741,7 @@ def read(self, n=None):
17321741
return result
17331742

17341743
def __next__(self):
1744+
self._checkClosed()
17351745
self._telling = False
17361746
line = self.readline()
17371747
if not line:
@@ -1741,8 +1751,7 @@ def __next__(self):
17411751
return line
17421752

17431753
def readline(self, limit=None):
1744-
if self.closed:
1745-
raise ValueError("read from closed file")
1754+
self._checkClosed()
17461755
if limit is None:
17471756
limit = -1
17481757

@@ -1963,17 +1972,15 @@ def seekable(self):
19631972

19641973
def getvalue(self) -> str:
19651974
"""Retrieve the entire contents of the object."""
1966-
if self.closed:
1967-
raise ValueError("read on closed file")
1975+
self._checkClosed()
19681976
return self._getvalue()
19691977

19701978
def write(self, s: str) -> int:
19711979
"""Write string s to file.
19721980
19731981
Returns the number of characters written.
19741982
"""
1975-
if self.closed:
1976-
raise ValueError("write to closed file")
1983+
self._checkClosed()
19771984
if not isinstance(s, str):
19781985
raise TypeError("can't write %s to text stream" %
19791986
s.__class__.__name__)
@@ -1990,8 +1997,7 @@ def read(self, n: int = None) -> str:
19901997
If the argument is negative or omitted, read until EOF
19911998
is reached. Return an empty string at EOF.
19921999
"""
1993-
if self.closed:
1994-
raise ValueError("read to closed file")
2000+
self._checkClosed()
19952001
if n is None:
19962002
n = -1
19972003
res = self._pending
@@ -2006,8 +2012,7 @@ def read(self, n: int = None) -> str:
20062012

20072013
def tell(self) -> int:
20082014
"""Tell the current file position."""
2009-
if self.closed:
2010-
raise ValueError("tell from closed file")
2015+
self._checkClosed()
20112016
if self._pending:
20122017
return self._tell() - len(self._pending)
20132018
else:
@@ -2022,8 +2027,7 @@ def seek(self, pos: int = None, whence: int = 0) -> int:
20222027
2 End of stream - pos must be 0.
20232028
Returns the new absolute position.
20242029
"""
2025-
if self.closed:
2026-
raise ValueError("seek from closed file")
2030+
self._checkClosed()
20272031
self._pending = ""
20282032
return self._seek(pos, whence)
20292033

@@ -2034,14 +2038,12 @@ def truncate(self, pos: int = None) -> int:
20342038
returned by tell(). Imply an absolute seek to pos.
20352039
Returns the new absolute position.
20362040
"""
2037-
if self.closed:
2038-
raise ValueError("truncate from closed file")
2041+
self._checkClosed()
20392042
self._pending = ""
20402043
return self._truncate(pos)
20412044

20422045
def readline(self, limit: int = None) -> str:
2043-
if self.closed:
2044-
raise ValueError("read from closed file")
2046+
self._checkClosed()
20452047
if limit is None:
20462048
limit = -1
20472049
if limit >= 0:

Lib/test/test_io.py

+39
Original file line numberDiff line numberDiff line change
@@ -1324,6 +1324,45 @@ def test_attributes(self):
13241324
f.close()
13251325
g.close()
13261326

1327+
def test_io_after_close(self):
1328+
for kwargs in [
1329+
{"mode": "w"},
1330+
{"mode": "wb"},
1331+
{"mode": "w", "buffering": 1},
1332+
{"mode": "w", "buffering": 2},
1333+
{"mode": "wb", "buffering": 0},
1334+
{"mode": "r"},
1335+
{"mode": "rb"},
1336+
{"mode": "r", "buffering": 1},
1337+
{"mode": "r", "buffering": 2},
1338+
{"mode": "rb", "buffering": 0},
1339+
{"mode": "w+"},
1340+
{"mode": "w+b"},
1341+
{"mode": "w+", "buffering": 1},
1342+
{"mode": "w+", "buffering": 2},
1343+
{"mode": "w+b", "buffering": 0},
1344+
]:
1345+
f = io.open(support.TESTFN, **kwargs)
1346+
f.close()
1347+
self.assertRaises(ValueError, f.flush)
1348+
self.assertRaises(ValueError, f.fileno)
1349+
self.assertRaises(ValueError, f.isatty)
1350+
self.assertRaises(ValueError, f.__iter__)
1351+
if hasattr(f, "peek"):
1352+
self.assertRaises(ValueError, f.peek, 1)
1353+
self.assertRaises(ValueError, f.read)
1354+
if hasattr(f, "read1"):
1355+
self.assertRaises(ValueError, f.read1, 1024)
1356+
if hasattr(f, "readinto"):
1357+
self.assertRaises(ValueError, f.readinto, bytearray(1024))
1358+
self.assertRaises(ValueError, f.readline)
1359+
self.assertRaises(ValueError, f.readlines)
1360+
self.assertRaises(ValueError, f.seek, 0)
1361+
self.assertRaises(ValueError, f.tell)
1362+
self.assertRaises(ValueError, f.truncate)
1363+
self.assertRaises(ValueError, f.write, "")
1364+
self.assertRaises(ValueError, f.writelines, [])
1365+
13271366

13281367
def test_main():
13291368
support.run_unittest(IOTest, BytesIOTest, StringIOTest,

Misc/NEWS

+4
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ What's New in Python 3.1 alpha 0
1212
Core and Builtins
1313
-----------------
1414

15+
- Issue #4604: Some objects of the I/O library could still be used after
16+
having been closed (for instance, a read() call could return some
17+
previously buffered data). Patch by Dmitry Vasiliev.
18+
1519
- Issue #4705: Fix the -u ("unbuffered binary stdout and stderr") command-line
1620
flag to work properly. Furthermore, when specifying -u, the text stdout
1721
and stderr streams have line-by-line buffering enabled (the default being

0 commit comments

Comments
 (0)