Skip to content

Commit b7fb1e2

Browse files
authored
bpo-29110: Fix file object leak in aifc.open (GH-311)
(cherry picked from commit 03f68b6) (GH-162) (cherry picked from commit 5dc33ee) (GH-293)
1 parent 21c697f commit b7fb1e2

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
@@ -294,6 +294,8 @@ class Aifc_read:
294294
# _ssnd_chunk -- instantiation of a chunk class for the SSND chunk
295295
# _framesize -- size of one frame in the file
296296

297+
_file = None # Set here since __del__ checks it
298+
297299
def initfp(self, file):
298300
self._version = 0
299301
self._convert = None
@@ -335,9 +337,15 @@ def initfp(self, file):
335337

336338
def __init__(self, f):
337339
if isinstance(f, str):
338-
f = builtins.open(f, 'rb')
339-
# else, assume it is an open file object already
340-
self.initfp(f)
340+
file_object = builtins.open(f, 'rb')
341+
try:
342+
self.initfp(file_object)
343+
except:
344+
file_object.close()
345+
raise
346+
else:
347+
# assume it is an open file object already
348+
self.initfp(f)
341349

342350
def __enter__(self):
343351
return self
@@ -532,18 +540,23 @@ class Aifc_write:
532540
# _datalength -- the size of the audio samples written to the header
533541
# _datawritten -- the size of the audio samples actually written
534542

543+
_file = None # Set here since __del__ checks it
544+
535545
def __init__(self, f):
536546
if isinstance(f, str):
537-
filename = f
538-
f = builtins.open(f, 'wb')
539-
else:
540-
# else, assume it is an open file object already
541-
filename = '???'
542-
self.initfp(f)
543-
if filename[-5:] == '.aiff':
544-
self._aifc = 0
547+
file_object = builtins.open(f, 'wb')
548+
try:
549+
self.initfp(file_object)
550+
except:
551+
file_object.close()
552+
raise
553+
554+
# treat .aiff file extensions as non-compressed audio
555+
if f.endswith('.aiff'):
556+
self._aifc = 0
545557
else:
546-
self._aifc = 1
558+
# assume it is an open file object already
559+
self.initfp(f)
547560

548561
def initfp(self, file):
549562
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 os
@@ -150,6 +151,21 @@ def test_skipunknown(self):
150151
#This file contains chunk types aifc doesn't recognize.
151152
self.f = aifc.open(findfile('Sine-1000Hz-300ms.aif'))
152153

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

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ Extension Modules
3232
Library
3333
-------
3434

35+
- bpo-29110: Fix file object leak in aifc.open() when file is given as a
36+
filesystem path and is not in valid AIFF format. Patch by Anthony Zhang.
37+
3538
- Issue #28961: Fix unittest.mock._Call helper: don't ignore the name parameter
3639
anymore. Patch written by Jiajun Huang.
3740

0 commit comments

Comments
 (0)