Skip to content

Commit 9491d18

Browse files
[3.12] gh-122400: Handle ValueError in filecmp (GH-122401) (GH-122442)
(cherry picked from commit 3a9b2aa) Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
1 parent 6b4abde commit 9491d18

File tree

3 files changed

+42
-4
lines changed

3 files changed

+42
-4
lines changed

Lib/filecmp.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -160,12 +160,14 @@ def phase2(self): # Distinguish files, directories, funnies
160160
ok = True
161161
try:
162162
a_stat = os.stat(a_path)
163-
except OSError:
163+
except (OSError, ValueError):
164+
# See https://github.com/python/cpython/issues/122400
165+
# for the rationale for protecting against ValueError.
164166
# print('Can\'t stat', a_path, ':', why.args[1])
165167
ok = False
166168
try:
167169
b_stat = os.stat(b_path)
168-
except OSError:
170+
except (OSError, ValueError):
169171
# print('Can\'t stat', b_path, ':', why.args[1])
170172
ok = False
171173

@@ -280,12 +282,12 @@ def cmpfiles(a, b, common, shallow=True):
280282
# Return:
281283
# 0 for equal
282284
# 1 for different
283-
# 2 for funny cases (can't stat, etc.)
285+
# 2 for funny cases (can't stat, NUL bytes, etc.)
284286
#
285287
def _cmp(a, b, sh, abs=abs, cmp=cmp):
286288
try:
287289
return not abs(cmp(a, b, sh))
288-
except OSError:
290+
except (OSError, ValueError):
289291
return 2
290292

291293

Lib/test/test_filecmp.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,39 @@ def test_cmpfiles(self):
111111
(['file'], ['file2'], []),
112112
"Comparing mismatched directories fails")
113113

114+
def test_cmpfiles_invalid_names(self):
115+
# See https://github.com/python/cpython/issues/122400.
116+
for file, desc in [
117+
('\x00', 'NUL bytes filename'),
118+
(__file__ + '\x00', 'filename with embedded NUL bytes'),
119+
("\uD834\uDD1E.py", 'surrogate codes (MUSICAL SYMBOL G CLEF)'),
120+
('a' * 1_000_000, 'very long filename'),
121+
]:
122+
for other_dir in [self.dir, self.dir_same, self.dir_diff]:
123+
with self.subTest(f'cmpfiles: {desc}', other_dir=other_dir):
124+
res = filecmp.cmpfiles(self.dir, other_dir, [file])
125+
self.assertTupleEqual(res, ([], [], [file]))
126+
127+
def test_dircmp_invalid_names(self):
128+
for bad_dir, desc in [
129+
('\x00', 'NUL bytes dirname'),
130+
(f'Top{os.sep}Mid\x00', 'dirname with embedded NUL bytes'),
131+
("\uD834\uDD1E", 'surrogate codes (MUSICAL SYMBOL G CLEF)'),
132+
('a' * 1_000_000, 'very long dirname'),
133+
]:
134+
d1 = filecmp.dircmp(self.dir, bad_dir)
135+
d2 = filecmp.dircmp(bad_dir, self.dir)
136+
for target in [
137+
# attributes where os.listdir() raises OSError or ValueError
138+
'left_list', 'right_list',
139+
'left_only', 'right_only', 'common',
140+
]:
141+
with self.subTest(f'dircmp(ok, bad): {desc}', target=target):
142+
with self.assertRaises((OSError, ValueError)):
143+
getattr(d1, target)
144+
with self.subTest(f'dircmp(bad, ok): {desc}', target=target):
145+
with self.assertRaises((OSError, ValueError)):
146+
getattr(d2, target)
114147

115148
def _assert_lists(self, actual, expected):
116149
"""Assert that two lists are equal, up to ordering."""
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Handle :exc:`ValueError`\s raised by :func:`os.stat` in
2+
:class:`filecmp.dircmp` and :func:`filecmp.cmpfiles`.
3+
Patch by Bénédikt Tran.

0 commit comments

Comments
 (0)