Repository navigation
Conversation
This is the proposed replacement API for the SplFileObject CSV methods deprecated in PHP 8.6, intended to be submitted as an RFC. It is based on the girgias/csv extension 0.6.0 by Gina Peter Banyard (BSD-3-Clause, https://gitlab.com/Girgias/csv-php-extension), extended with the file/stream handling that the upstream extension left as future work. The extension provides, in the Csv\ namespace: array_to_row(), row_to_array(), collection_to_buffer(), buffer_to_collection(), buffer_to_collection_lax() and the lazily iterating LazyLaxCollection (all as upstream), plus two new stream-based APIs: - Csv\collection_to_file(): writes an iterable of rows through the streams layer one row at a time, so generator-backed collections of any size are written in constant memory; open, short-write, flush and close failures all throw. - Csv\LazyLaxCollection::createFromFile(): holds a private stream and reads it in chunks through the stream's read buffer (the php_stream_get_line() pattern, so partial reads e.g. from FIFOs are processed as they arrive). Row boundaries are found by an enclosure-aware scanner; a dialect-specific lookahead (0 for the default dialect) guards against tokens split across read boundaries, and the sliding window is compacted so memory use is proportional to the longest row, not the file. The stream is detached from EG(regular_list), making the object its sole owner. Compared to upstream, the port is master-only (pre-8.4 compatibility shims removed), the arginfo is regenerated, and five upstream bugs found during the port and review are fixed: - createFromBuffer() stored the borrowed $buffer without addref (double-free with non-interned strings) - current_row was destroyed twice via the rewind/next sequence (use-after-free after breaking out of foreach and iterating again) - the iterable foreach helper looped forever on arrays with holes - a doubled multibyte enclosure was consumed one byte at a time, corrupting parser state (round-trip failure) - error exits from collection loops leaked the active iterator Tests: 77 phpt (55 ported from upstream, 22 new, covering the stream APIs, chunk-boundary dialect edge cases, stream lifecycle and the upstream bug regressions). Co-authored-by: Gina Peter Banyard <girgias@php.net>
- Add test for `LazyLaxCollection::createFromFile()` to ensure retained stream resource references are properly handled and released in various scenarios. - Add test for `collection_to_file()` to verify safe handling of retained resource references during stream operations. - Update CSV extension to detach streams more robustly by converting resource references to closed states, preventing early freeing when userland accesses them.
|
I built the branch at 38e463a and went through csv.c with a few probes; two things I'd consider blockers, then smaller ones. Use-after-free with a generator. class S { function __toString(): string { $GLOBALS['g']->next(); return "s"; } }
function rows() { yield [new S, 'b']; yield ['c', 'd']; }
$g = rows();
Csv\collection_to_buffer($g);valgrind ( Quadratic rescan in Smaller:
Probes and numbers: https://github.com/ArtUkrainskiy/php-src-bench/tree/main/reports/csv-pr-24199 |
- Add tests for `LazyLaxCollection::createFromFile()` and `createFromBuffer()`, including nested iteration, large fields, retained stream references, and compliance with RFC 4180. - Add tests for `collection_to_file()` and `collection_to_buffer()` verifying edge cases such as references, resource lifecycle, and strict field-matching. - Improve testing coverage of `row_to_array()` with stricter compliance for newline handling and large enclosures. - Enhance CSV extension to detect overlapping enclosures, align internal stream lifecycle with userland behavior, and prevent premature resource release during iteration.
|
Thanks a lot for the thorough review and the reproducer scripts — they were extremely helpful! I've reproduced and addressed all the reported issues in 121c02f, with dedicated regression tests for each. Full results, benchmarks, reproducer scripts, and ASan logs are available here: https://github.com/damek24/php-csv-rfc/tree/main/reports/csv-pr-24199-fixes Correctness and memory safetyUse-after-free: Confirmed under ASan before the fix (same trace: read at Quadratic rescanning: The stream scanner now preserves its position and enclosure state across reads, resuming from the last point where parsing decisions were final. Only potential token matches at the read boundary need reconsideration. This eliminates the quadratic rescanning behavior:
Other fixes
The strictness changes also exposed an ambiguity with self-overlapping multibyte enclosures ( PerformanceI also optimized the parser's hot path using a per-dialect byte lookup table, bulk scanning, and direct string creation from contiguous input slices instead of byte-by-byte appending. This improved buffer parsing performance by approximately 3.5×. On my i9-12900K, using your
The writers remain unchanged and are approximately 10–20% slower than The complete benchmark methodology and before/after measurements are included in the linked report. Thanks again for putting together such a detailed and reproducible review, and nice work on #24207! |
|
Re-ran everything on 121c02f: the use-after-free is gone under valgrind on both reproducers, the 32 MB quoted field reads in 0.06 s and the unterminated one raises in 0.01 s, and every smaller item behaves as you describe. The new parser passes a round-trip fuzz (4,000 random rows with NUL, CR/LF, One thing remains, and it predates the port (girgias/csv 0.6.0 behaves the same): dialects whose tokens overlap with field content don't round-trip, because the writer doesn't enclose a field when the token appears across the field/delimiter boundary: Csv\row_to_array(Csv\array_to_row(['-', 'z'], '--', 'aa', "\r\n"), '--', 'aa', "\r\n");
// "---z\r\n" -> ["", "-z"]
Csv\row_to_array(Csv\array_to_row(['x', 'x'], 'xy', 'xyx', "\n"), 'xy', 'xyx', "\n");
// "xxyx\n" -> ValueError: Enclosure sequence is used in a non escaped fieldIn the fuzz, Fuzz and the minimal-case search: https://github.com/ArtUkrainskiy/php-src-bench/tree/main/reports/csv-pr-24199 |
Summary
This draft PR proposes adding a new CSV extension (
ext/csv) to PHP core.The extension is based on Gina Peter Banyard's
girgias/csvproject (BSD-3-Clause) and extends it with file and stream handling.Features
Implementation
Csv\namespaceRFC
https://wiki.php.net/rfc/csv_extension
Status: Draft - this PR is not intended for merging until the RFC has been discussed and accepted.
Attribution
Based on
girgias/csvby Gina Peter Banyard and contributors, licensed under BSD-3-Clause.Parts of the implementation were developed with LLM assistance. I have reviewed the code and take full responsibility for the contribution.
Feedback on API design, implementation details, and portability is welcome.