Skip to content

Commit c9131b6

Browse files
authored
[3.6] bpo-29110: Fix file object leak in aifc.open (#310)
(cherry picked from commit 03f68b6) (GH-162) (cherry picked from commit 5dc33ee) (GH-293)
1 parent 6b81003 commit c9131b6

File tree

3 files changed

+45
-13
lines changed

3 files changed

+45
-13
lines changed

Lib/aifc.py

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,8 @@ class Aifc_read:
303303
# _ssnd_chunk -- instantiation of a chunk class for the SSND chunk
304304
# _framesize -- size of one frame in the file
305305

306+
_file = None # Set here since __del__ checks it
307+
306308
def initfp(self, file):
307309
self._version = 0
308310
self._convert = None
@@ -344,9 +346,15 @@ def initfp(self, file):
344346

345347
def __init__(self, f):
346348
if isinstance(f, str):
347-
f = builtins.open(f, 'rb')
348-
# else, assume it is an open file object already
349-
self.initfp(f)
349+
file_object = builtins.open(f, 'rb')
350+
try:
351+
self.initfp(file_object)
352+
except:
353+
file_object.close()
354+
raise
355+
else:
356+
# assume it is an open file object already
357+
self.initfp(f)
350358

351359
def __enter__(self):
352360
return self
@@ -541,18 +549,23 @@ class Aifc_write:
541549
# _datalength -- the size of the audio samples written to the header
542550
# _datawritten -- the size of the audio samples actually written
543551

552+
_file = None # Set here since __del__ checks it
553+
544554
def __init__(self, f):
545555
if isinstance(f, str):
546-
filename = f
547-
f = builtins.open(f, 'wb')
548-
else:
549-
# else, assume it is an open file object already
550-
filename = '???'
551-
self.initfp(f)
552-
if filename[-5:] == '.aiff':
553-
self._aifc = 0
556+
file_object = builtins.open(f, 'wb')
557+
try:
558+
self.initfp(file_object)
559+
except:
560+
file_object.close()
561+
raise
562+
563+
# treat .aiff file extensions as non-compressed audio
564+
if f.endswith('.aiff'):
565+
self._aifc = 0
554566
else:
555-
self._aifc = 1
567+
# assume it is an open file object already
568+
self.initfp(f)
556569

557570
def initfp(self, file):
558571
self._file = file

Lib/test/test_aifc.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
from test.support import findfile, TESTFN, unlink
1+
from test.support import check_no_resource_warning, findfile, TESTFN, unlink
22
import unittest
3+
from unittest import mock
34
from test import audiotests
45
from audioop import byteswap
56
import io
@@ -149,6 +150,21 @@ def test_skipunknown(self):
149150
#This file contains chunk types aifc doesn't recognize.
150151
self.f = aifc.open(findfile('Sine-1000Hz-300ms.aif'))
151152

153+
def test_close_opened_files_on_error(self):
154+
non_aifc_file = findfile('pluck-pcm8.wav', subdir='audiodata')
155+
with check_no_resource_warning(self):
156+
with self.assertRaises(aifc.Error):
157+
# Try opening a non-AIFC file, with the expectation that
158+
# `aifc.open` will fail (without raising a ResourceWarning)
159+
self.f = aifc.open(non_aifc_file, 'rb')
160+
161+
# Aifc_write.initfp() won't raise in normal case. But some errors
162+
# (e.g. MemoryError, KeyboardInterrupt, etc..) can happen.
163+
with mock.patch.object(aifc.Aifc_write, 'initfp',
164+
side_effect=RuntimeError):
165+
with self.assertRaises(RuntimeError):
166+
self.fout = aifc.open(TESTFN, 'wb')
167+
152168
def test_params_added(self):
153169
f = self.f = aifc.open(TESTFN, 'wb')
154170
f.aiff()

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ Library
7878
- bpo-29532: Altering a kwarg dictionary passed to functools.partial()
7979
no longer affects a partial object after creation.
8080

81+
- bpo-29110: Fix file object leak in aifc.open() when file is given as a
82+
filesystem path and is not in valid AIFF format. Patch by Anthony Zhang.
83+
8184
- bpo-22807: Add uuid.SafeUUID and uuid.UUID.is_safe to relay information from
8285
the platform about whether generated UUIDs are generated with a
8386
multiprocessing safe method.

0 commit comments

Comments
 (0)