Issue #22195 has been updated by himura467 (Akito Shitara). Even with the proposal withdrawn, I think one thing here still needs fixing. `IO::Buffer.new(0)` and `IO::Buffer.new(0).slice(0, 0)` are both `{NULL, 0}`, a valid empty span by your description, but they do not behave the same. On master (4.1.0dev, 37325e9f7a): ```ruby IO::Buffer.new(0).get_string # => "" IO::Buffer.for("").slice(0, 0).get_string # => "" IO::Buffer.new.slice(0, 0).get_string # => "" IO::Buffer.new(0).slice(0, 0).get_string # => IO::Buffer::InvalidatedError IO::Buffer.new(0).slice(0, 0).valid? # => false ``` `io_buffer_initialize` returns early when base and size are both zero, so the root keeps `base == NULL`, and `io_buffer_validate_slice` rejects any slice whose source has a NULL base, without looking at the length. The slice is therefore invalid as soon as it is created, although nothing was freed or transferred and no pointer is ever dereferenced. #18805 asked that a buffer with size 0 always return an empty string, and #19542 asked for `dup`, `<=>`, `each` and `get_values` to work on empty buffers. All of them work on `IO::Buffer.new(0)` and raise on `IO::Buffer.new(0).slice(0, 0)`. The fix in GH-9532 went into `io_buffer_get_bytes_for_reading` and `io_buffer_get_bytes_for_writing`, which a slice of an unallocated buffer never reaches: validation rejects it first. ---------------------------------------- Bug #22195: IO::Buffer read after free silently returns empty data instead of raising https://bugs.ruby-lang.org/issues/22195#change-118625 * Author: himura467 (Akito Shitara) * Status: Open * Backport: 3.3: UNKNOWN, 3.4: UNKNOWN, 4.0: UNKNOWN ---------------------------------------- Reading from an `IO::Buffer` after calling `#free` does not raise. It behaves like an empty buffer: ```ruby buffer = IO::Buffer.for("Hello World") buffer.free buffer.get_string # => "" (no error) ``` ## Why this looks like an unintended regression Originally (https://github.com/ruby/ruby/commit/e30920354f, Ruby 3.1–3.3), accessing a freed buffer raised `IO::Buffer::AllocationError` ("The buffer is not allocated!"). https://github.com/ruby/ruby/commit/c5cf4d4e12 made zero-length buffer operations succeed instead of raising, for [Bug #19542] and [Bug #18805]. Since a freed buffer also has `base == NULL, size == 0`, it was caught in the same code path and stopped raising too. Neither ticket mentions freed buffers, so this appears to be an accidental side effect rather than a decision. Note that the rdoc of `#free` still promises that access after free raises. ## Proposal How about marking a buffer internally as freed when its memory is released, so that: * Any byte access raises `IO::Buffer::AllocationError` again, restoring the pre-3.4 documented behavior. * `#resize` re-allocates and clears the mark, as `#free`'s rdoc promises ("You can resize a freed buffer to re-allocate it"). * The zero-length buffer semantics from https://github.com/ruby/ruby/pull/9532 are preserved. -- https://bugs.ruby-lang.org/