Issue #22195 has been reported by himura467 (Akito Shitara). ---------------------------------------- Bug #22195: `IO::Buffer` read after free silently returns empty data instead of raising https://bugs.ruby-lang.org/issues/22195 * 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, and `#valid?` still returns `true`: ```ruby buffer = IO::Buffer.for("Hello World") buffer.free buffer.valid? # => true 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 as invalidated when it is explicitly freed (`#free` / `rb_io_buffer_free`), so that: * `#valid?` returns `false`. * Any byte access raises `IO::Buffer::InvalidatedError` or `IO::Buffer::AllocationError`. * `#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: `IO::Buffer.new(0)`, `resize(0)` and zero-length slices remain valid and readable. I'm not sure which exception class is preferable: `IO::Buffer::InvalidatedError` is consistent with how invalidated slices already behave and with `#valid?` returning `false`, while `IO::Buffer::AllocationError` restores the pre-3.4 behavior. -- https://bugs.ruby-lang.org/