Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 33 additions & 18 deletions ext/spl/spl_directory.c
Original file line number Diff line number Diff line change
Expand Up @@ -1813,21 +1813,24 @@ static zend_result spl_filesystem_file_read_ex(spl_filesystem_object *intern, bo
}

if (!buf) {
intern->u.file.current_line = ZSTR_EMPTY_ALLOC();
} else {
if (!csv && SPL_HAS_FLAG(intern->flags, SPL_FILE_OBJECT_DROP_NEW_LINE)) {
if (line_len > 0 && buf[line_len - 1] == '\n') {
if (!silent) {
spl_filesystem_file_cannot_read(intern);
}
return FAILURE;
}

if (!csv && SPL_HAS_FLAG(intern->flags, SPL_FILE_OBJECT_DROP_NEW_LINE)) {
if (line_len > 0 && buf[line_len - 1] == '\n') {
line_len--;
if (line_len > 0 && buf[line_len - 1] == '\r') {
line_len--;
if (line_len > 0 && buf[line_len - 1] == '\r') {
line_len--;
}
buf[line_len] = '\0';
}
buf[line_len] = '\0';
}

intern->u.file.current_line = zend_string_init(buf, line_len, /* persistent */ false);
efree(buf);
}

intern->u.file.current_line = zend_string_init(buf, line_len, /* persistent */ false);
efree(buf);
intern->u.file.current_line_num += line_add;

return SUCCESS;
Expand Down Expand Up @@ -2093,10 +2096,17 @@ PHP_METHOD(SplFileObject, fgets)

CHECK_SPL_FILE_OBJECT_IS_INITIALIZED(intern);

if (spl_filesystem_file_read_ex(intern, /* silent */ false, /* line_add */ 1, /* csv */ false) == FAILURE) {
RETURN_THROWS();
if (intern->u.file.current_line) {
RETVAL_STR_COPY(intern->u.file.current_line);
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) {
RETURN_THROWS();
}
RETVAL_STR_COPY(intern->u.file.current_line);
spl_filesystem_file_free_line(intern);
}
RETURN_STR_COPY(intern->u.file.current_line);
} /* }}} */

/* {{{ Return current line from file */
Expand Down Expand Up @@ -2142,6 +2152,12 @@ PHP_METHOD(SplFileObject, next)

ZEND_PARSE_PARAMETERS_NONE();

if (!intern->u.file.current_line && Z_ISUNDEF(intern->u.file.current_zval)) {
if (spl_filesystem_file_read_line(ZEND_THIS, intern, true) == FAILURE) {
return;
}
}

spl_filesystem_file_free_line(intern);
if (SPL_HAS_FLAG(intern->flags, SPL_FILE_OBJECT_READ_AHEAD)) {
spl_filesystem_file_read_line(ZEND_THIS, intern, true);
Expand Down Expand Up @@ -2629,7 +2645,7 @@ PHP_METHOD(SplFileObject, seek)

for (i = 0; i < line_pos; i++) {
if (spl_filesystem_file_read_line(ZEND_THIS, intern, true) == FAILURE) {
return;
break;
}
}
if (line_pos > 0 && !SPL_HAS_FLAG(intern->flags, SPL_FILE_OBJECT_READ_AHEAD)) {
Expand All @@ -2648,9 +2664,8 @@ PHP_METHOD(SplFileObject, __toString)

if (!intern->u.file.current_line) {
ZEND_ASSERT(Z_ISUNDEF(intern->u.file.current_zval));
zend_result result = spl_filesystem_file_read_line(ZEND_THIS, intern, false);
if (UNEXPECTED(result != SUCCESS)) {
RETURN_THROWS();
if (spl_filesystem_file_read_line(ZEND_THIS, intern, true) == FAILURE) {
RETURN_EMPTY_STRING();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@ var_dump($s->key());
var_dump($s->valid());
?>
--EXPECT--
int(14)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be 13 ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The .phpt temp file is exactly 12 physical lines (verified via run-tests.php --keep-all). seek(13) reads all 12, the 13th iteration hits EOF and breaks, and the post-loop line_num++ lands at 12. Your own draft in #8644 sets this test to int(12) for the same reason.

int(12)
bool(false)
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@ var_dump($s->key());
var_dump($s->valid());
?>
--EXPECT--
int(13)
int(12)
bool(false)
1 change: 0 additions & 1 deletion ext/spl/tests/SplFileObject/bug81477.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ string(8) "baz,bat
"
string(10) "more,data
"
string(0) ""
--CLEAN--
<?php
@unlink(__DIR__ . '/bug81477.csv');
Expand Down
5 changes: 1 addition & 4 deletions ext/spl/tests/SplFileObject/fgetcsv_blank_file.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,5 @@ $file->rewind();
var_dump($file->fgetcsv());
?>
--EXPECT--
array(1) {
[0]=>
NULL
}
bool(false)
bool(false)
30 changes: 30 additions & 0 deletions ext/spl/tests/SplFileObject/gh8561.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
--TEST--
GH-8561 (SplFileObject: key() and current() unsynchronized after fgets())
--FILE--
<?php
$file = new SplTempFileObject();
for ($i = 0; $i < 5; $i++) {
$file->fwrite("line {$i}" . PHP_EOL);
}

// Case 1: rewind + fgets, then key/current
$file->rewind();
$file->fgets();
echo "After rewind+fgets: key=" . $file->key() . " current=" . trim($file->current()) . "\n";

// Case 2: multiple fgets
$file->rewind();
$file->fgets();
$file->fgets();
echo "After rewind+fgets+fgets: key=" . $file->key() . " current=" . trim($file->current()) . "\n";

// Case 3: current then fgets
$file->rewind();
$file->current();
$file->fgets();
echo "After current+fgets: key=" . $file->key() . " current=" . trim($file->current()) . "\n";
?>
--EXPECT--
After rewind+fgets: key=1 current=line 1
After rewind+fgets+fgets: key=2 current=line 2
After current+fgets: key=1 current=line 1
29 changes: 29 additions & 0 deletions ext/spl/tests/SplFileObject/gh8563.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
--TEST--
GH-8563 (Different results for seek() on SplFileObject and SplTempFileObject)
--FILE--
<?php
$path = __DIR__ . '/gh8563.txt';
$file_01 = new SplFileObject($path, 'w+');
$file_02 = new SplTempFileObject();

for ($i = 0; $i < 5; $i++) {
$file_01->fwrite("line {$i}" . PHP_EOL);
$file_02->fwrite("line {$i}" . PHP_EOL);
}

$file_01->rewind();
$file_02->rewind();

$file_01->seek(10);
$file_02->seek(10);

echo 'SplFileObject: ' . $file_01->key() . "\n";
echo 'SplTempFileObject: ' . $file_02->key() . "\n";
?>
--CLEAN--
<?php
unlink(__DIR__ . '/gh8563.txt');
?>
--EXPECT--
SplFileObject: 5
SplTempFileObject: 5
20 changes: 20 additions & 0 deletions ext/spl/tests/SplFileObject/gh8564.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
--TEST--
GH-8564 (SplFileObject: next() moves to nonexistent indexes)
--FILE--
<?php
$file = new SplTempFileObject();
for ($i = 0; $i < 5; $i++) {
$file->fwrite("line {$i}" . PHP_EOL);
}

$file->rewind();
for ($i = 0; $i < 10; $file->next(), $i++);
echo "next() 10x: key=" . $file->key() . " valid=" . var_export($file->valid(), true) . "\n";

$file->rewind();
$file->seek(10);
echo "seek(10): key=" . $file->key() . " valid=" . var_export($file->valid(), true) . "\n";
?>
--EXPECT--
next() 10x: key=5 valid=false
seek(10): key=5 valid=false
4 changes: 2 additions & 2 deletions ext/spl/tests/gh13685.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ try {
string(14) ""A", "B", "C"
"
string(13) ""D", "E", "F""
Cannot read from file php://temp
string(0) ""
--- Use csv control ---
string(14) ""A", "B", "C"
"
string(13) ""D", "E", "F""
Cannot read from file php://temp
string(0) ""
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think part of the motivation for CSV functions is to ignore the final line if it just an EOL before EOF.
Although throwing an error is not amazing. I wonder if it shouldn't be returning the last line?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Returning the last read line would mean holding a separate "last line" field on the object across free_line() calls, adding state for one boundary case. The failure mode this test guards against is a NULL pointer crash, and returning an empty string from __toString() prevents that. Happy to revisit in a follow-up if you want stronger semantics here.

27 changes: 27 additions & 0 deletions ext/spl/tests/gh8562.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
--TEST--
GH-8562 (SplFileObject::current() returns wrong result after call to next())
--FILE--
<?php
$file = new SplTempFileObject();
for ($i = 0; $i < 5; $i++) {
$file->fwrite("line {$i}" . PHP_EOL);
}

$file->rewind();
$file->next();
echo "After rewind+next: key=" . $file->key() . " current=" . trim($file->current()) . "\n";

$file->rewind();
$file->next();
$file->next();
echo "After rewind+next+next: key=" . $file->key() . " current=" . trim($file->current()) . "\n";

$file->rewind();
$file->current();
$file->next();
echo "After current+next: key=" . $file->key() . " current=" . trim($file->current()) . "\n";
?>
--EXPECT--
After rewind+next: key=1 current=line 1
After rewind+next+next: key=2 current=line 2
After current+next: key=1 current=line 1
Loading