From 8d8acc7be7a47da1f2d4009010e55b2673f43dd1 Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Thu, 23 Apr 2026 10:54:49 -0400 Subject: [PATCH] Fix GH-21742: SplFileObject::fgets() throws at EOF in eof/fgets loop The SplFileObject iterator-desync fix in 08dad097025 made spl_filesystem_file_read_ex throw "Cannot read from file" on the NULL-buffer path. SplFileObject::fgets() now throws inside the documented while (!$spl->eof()) $spl->fgets() idiom, because eof() returns false until a read attempt returns zero bytes. Keep the stricter semantics for next(), seek(), current(), fscanf(). Narrow fgets() to silent=true and return empty string on FAILURE, restoring the PHP-8.5 contract. Fixes GH-21742 --- ext/spl/spl_directory.c | 6 +++- ext/spl/tests/SplFileObject/gh21742.phpt | 35 ++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 ext/spl/tests/SplFileObject/gh21742.phpt diff --git a/ext/spl/spl_directory.c b/ext/spl/spl_directory.c index 1468cec6ccf3..89af25dd9d35 100644 --- a/ext/spl/spl_directory.c +++ b/ext/spl/spl_directory.c @@ -2099,7 +2099,11 @@ PHP_METHOD(SplFileObject, fgets) spl_filesystem_file_free_line(intern); intern->u.file.current_line_num++; } else { - if (spl_filesystem_file_read_ex(intern, /* silent */ false, /* line_add */ 1, /* csv */ false) == FAILURE) { + if (spl_filesystem_file_read_ex(intern, /* silent */ true, /* line_add */ 1, /* csv */ false) == FAILURE) { + if (php_stream_eof(intern->u.file.stream)) { + RETURN_EMPTY_STRING(); + } + spl_filesystem_file_cannot_read(intern); RETURN_THROWS(); } RETVAL_STR_COPY(intern->u.file.current_line); diff --git a/ext/spl/tests/SplFileObject/gh21742.phpt b/ext/spl/tests/SplFileObject/gh21742.phpt new file mode 100644 index 000000000000..7ea60b53915a --- /dev/null +++ b/ext/spl/tests/SplFileObject/gh21742.phpt @@ -0,0 +1,35 @@ +--TEST-- +GH-21742 (SplFileObject::fgets() throws at EOF in while (!$spl->eof()) loop) +--FILE-- +eof()) { + echo $spl->fgets(); +} +echo "clean exit\n"; + +$empty = tempnam(sys_get_temp_dir(), 'spl'); +file_put_contents($empty, ''); +$spl2 = new SplFileObject($empty, 'r'); +$iter = 0; +while (!$spl2->eof()) { + $iter++; + $spl2->fgets(); + if ($iter > 3) break; +} +echo "empty-file iters=$iter\n"; + +unlink($file); +unlink($empty); +?> +--EXPECT-- +Line 0 +Line 1 +Line 2 +Line 3 +Line 4 +clean exit +empty-file iters=1