159 Commits
Author SHA1 Message Date
Kazuki Yamaguchi b10b8048d1 io_buffer.c: use rb_off_t consistently
Fix an rb_off_t value being implicitly converted to off_t and then back
to rb_off_t.
2026-10-13 18:39:35 +09:00
Hiroshi SHIBATAandClaude Opus 5.5 ee3d6c987b Use rb_len_t for the lengths and indices left in long
These still held String and Array lengths and indices in long, or cast them to long. rb_len_t is still long everywhere, so the generated code does not change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 16:45:25 +09:00
Hiroshi SHIBATAandClaude Opus 5 454c9df847 Use rb_len_t to iterate over Array elements
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-09 14:00:15 +09:00
Nobuyoshi Nakada c86bd1776d Cast to enum
```
../src/io_buffer.c(609): warning C5286: implicit conversion from enum type '<unnamed-enum-RB_IO_BUFFER_HEXDUMP_DEFAULT_WIDTH>' to enum type 'rb_io_buffer_flags'; use an explicit cast to silence this warning
```
2026-10-08 23:02:16 +09:00
Samuel Williams 5b6f593531 Introduce (internal) IO::Buffer::Storage and Slice. (#19186) 2026-10-03 10:46:24 +13:00
Samuel Williams 3c01c256ce Add IO::Buffer#advance. (#19122) 2026-10-02 14:58:52 +13:00
Samuel Williams 5f5d65517e Retain immediate IO::Buffer slice sources. (#19091) 2026-09-30 10:22:00 +13:00
Samuel Williams 1692691913 Make IO::Buffer slices offset-based so they survive source relocation. (#19090) 2026-09-28 19:16:19 +13:00
Samuel Williams c568687e45 Treat empty IO::Buffer ranges as non-overlapping. (#19088) 2026-09-28 17:13:38 +13:00
Samuel Williams 4d56c955c4 Revalidate IO::Buffer byte access after Ruby callbacks. (#19086)
Treat encoding coercion, `to_io`, and scheduler capability checks as
callouts that can change the buffer before byte access begins.

- Copy `get_string` bytes through the existing locked-for-reading helper
  after encoding coercion, without retaining metadata pointers across it.
- Obtain the writable buffer after `to_io` conversion in `read` and `pread`.
- Reacquire and validate the buffer before native I/O fallback when the
  scheduler does not handle `read`, `write`, `pread`, or `pwrite`.

Add regression coverage using the existing buffer and slice APIs. Scheduler
probes shrink a slice without freeing its underlying storage, making stale
length handling observable without accessing released memory. Also cover
encoding callbacks and frozen-state changes during I/O coercion.

This is an independent correctness extraction from #18911. It introduces
no new classes, slice representation, or public API.
2026-09-28 15:07:20 +13:00
Nobuyoshi Nakada ec2eaf3f07 Fix shorten-64-to-32 warnings
`size_t` may be less than `off_t`
2026-09-26 19:11:32 +09:00
Peter Zhu 21017bde76 Fix race condition crash in IO::Buffer#set_string
For strings larger than or equal to IO_BUFFER_BLOCKING_SIZE (which is 1KB),
IO::Buffer#set_string will perform the memmove without GVL. In this time,
another thread can resize the IO::Buffer object. In that case, the buffer
may be freed and thus cause an use-after-free.

For example, the following script crashes:

    buf = IO::Buffer.new(64 * 1024 * 1024)
    src = "x" * (64 * 1024 * 1024)

    stop = false

    writer = Thread.new do
      until stop
        begin
          buf.set_string(src)
        rescue ArgumentError
        end
      end
    end

    resizer = Thread.new do
      until stop
        buf.resize(8)
        buf.resize(64 * 1024 * 1024)
      end
    end

    sleep 2
    stop = true
    writer.join
    resizer.join
    puts "done"
2026-09-25 08:06:44 +09:00
Peter Zhu b61da996af [Bug #22336] Fix use-after-free in IO::Buffer#get_string. (#18919) 2026-09-19 16:34:06 +12:00
Matt Valentine-House 1c37a5d0ba [Bug #22301] Check IO Buffer is writeable before writing 2026-09-11 11:52:30 +01:00
Luke Gruber c329f36bea Add and use rb_gc_update_moved(VALUE *obj) which conditionally assigns
Using the `container->obj = rb_gc_location(container->obj)` pattern dirties pages unconditionally.
If we're in a forked process, this is bad for CoW. If the object hasn't moved, don't reassign.
2026-09-08 21:43:48 -04:00
Nobuyoshi Nakada 6f57a72f53 Fix enum-compare warning
With gcc 15.2.0 on aarch64 Ubuntu 26.4:

```console
io_buffer.c: In function ‘io_buffer_flags_for_new’:
io_buffer.c:978:20: warning: comparison between ‘enum rb_io_buffer_flags’ and ‘enum <anonymous>’ [-Wenum-compare]
  978 |     if (allocation == RB_IO_BUFFER_ALLOCATION_FLAGS) {
      |                    ^~
```
2026-08-25 22:15:27 +09:00
Samuel Williams a06bf2f8f7 Lock IO::Buffer coercion callbacks. (#18490) 2026-08-25 21:50:16 +12:00
Samuel Williams 753ace46fe Lock IO::Buffer allocations during copy. (#18489) 2026-08-25 19:50:12 +12:00
Samuel Williams b9bcae995d Preserve IO::Buffer read-only state when resizing. (#18487) 2026-08-25 04:55:09 +00:00
Samuel Williams 44da9d7384 Make IO::Buffer I/O operations single-transfer and composable. (#18483) 2026-08-25 14:46:49 +12:00
Sutou Kouhei 177ead8d16 Make IO::Buffer MemoryView ready (#17965)
`null?` buffer isn't MemoryView available.

Not `null?` buffer is MemoryView available. It can be exported as a
1-dimensional contiguous array of bytes.

Users can't change the underlying memory of the exporting IO::Buffer
while exported MemoryView is using.
2026-08-25 05:50:39 +09:00
Samuel Williams 53bc085645 Expose IO::Buffer mapping alignment. (#18474)
[Feature #21700]
2026-08-24 12:50:55 +00:00
Samuel Williams 5ebbf33a28 Respect frozen IO::Buffer lifecycle state. (#18473)
[Bug #22219]
2026-08-24 11:50:02 +00:00
Samuel Williams d3bf4529e3 Validate IO::Buffer constructor flags. (#18470)
[Bug #21672]
2026-08-24 11:32:04 +00:00
Samuel Williams 281c2d4c19 Document IO::Buffer#free empty state. (#18471)
[Bug #22195]
2026-08-24 11:12:39 +00:00
Samuel Williams cc706f3956 Clarify IO::Buffer slice validity documentation. (#18469) 2026-08-24 23:00:56 +12:00
Samuel Williams 09fdc3bed9 Avoid unsafe zero-length IO::Buffer operations. (#18465)
- All `null?` buffers will result in zero-length operations.
2026-08-24 09:06:28 +00:00
Samuel Williams 4c978a5be5 Make empty IO::Buffer slices valid. (#18461) 2026-08-24 04:46:19 +00:00
Samuel Williams 4d5ae42629 Document dynamic IO::Buffer slice validity. (#18459) 2026-08-24 04:04:52 +00:00
Samuel Williams 99312bd9ff Resize IO::Buffer slices without reallocating. (#18446)
Resize IO::Buffer slices as views
2026-08-24 07:47:46 +12:00
Samuel Williams 2e2b54648c Normalize empty IO::Buffer state. (#18443) 2026-08-23 12:14:43 +12:00
Samuel Williams 0c25658d36 Hide rb_io_buffer_readonly_p. (#18431) 2026-08-22 09:10:42 +00:00
Samuel Williams 85d68c3e1a Fix IO::Buffer#resize(0) for mapped buffers and slices. (#18432)
[Bug #22234]
2026-08-22 06:42:55 +00:00
Samuel Williams c5a2c145c7 Make IO::Buffer locks reference counted. (#18429)
Share each lock count across a buffer and its slices so independent native operations retain the backing allocation until all users release it. Always acquire a lock for blocking I/O and protect each/each_byte callbacks from invalidating their backing pointers.
2026-08-22 16:43:18 +12:00
Samuel Williams 81cefb5c9d Make IO::Buffer slices retain their root buffer. (#18430) 2026-08-22 14:33:28 +12:00
Hiroshi SHIBATA d4ce236636 [DOC] Use IO::Buffer.for in the IO::Buffer#transfer example
IO::Buffer.new takes a size as its first argument, so passing a string
raises TypeError. Adjust the sample output to the flags the buffer
created by IO::Buffer.for actually shows.
2026-08-05 19:38:31 +09:00
Shizuo Fujita 9b76442b62 [DOC] Fix return value documentation of IO::Buffer#<=> and #hexdump (#18089)
The call-seq of IO::Buffer#<=> says it returns "true or false", but
rb_io_buffer_compare always returns an integer: -1 or 1 when the sizes
differ, and the result of memcmp as is when the sizes are equal. Since
the value comes straight from memcmp it is not normalized to -1, 0 or 1,
so only its sign is meaningful.

    p(IO::Buffer.for("abc") <=> IO::Buffer.for("abc")) # => 0
    p(IO::Buffer.for("abc") <=> IO::Buffer.for("ab"))  # => 1
    p(IO::Buffer.for("abc") <=> IO::Buffer.for("abz")) # => -23

IO::Buffer#hexdump returns nil when the buffer does not reference any
memory, that is, when #null? returns true, but this was not documented.

    buffer = IO::Buffer.new(4)
    buffer.free
    p buffer.hexdump # => nil
2026-07-28 21:03:59 +09:00
Shizuo Fujita 624075c838 [DOC] Fix the buffer size in the IO::Buffer#& example (#18087)
The example shows the result as `+4`, but the operation returns a buffer of
the same size as the source, which is 10 bytes here:

    $ ruby -e 'p((IO::Buffer.for("1234567890") & IO::Buffer.for("\xFF\x00\x00\xFF")).size)'
    10

The rest of the example is already correct. The hex dump and the ASCII column
show all 10 bytes, and the description right above says "Generate a new buffer
the same size as the source", so the example contradicts both. The size of the
mask is 4, which is likely where the number came from.

The neighbouring examples for #|, #^ and #~ all show `+10` and need no change.
2026-07-28 20:11:46 +09:00
Shizuo Fujita fef2234a7b [DOC] Fix the return value of IO::Buffer#set_value in its example (#18078)
The documentation for `IO::Buffer#set_value` shows:

    buffer.set_value(:U8, 1, 111)
    # => 1

but it actually returns 2, the offset just after the written value:

    $ ruby -e 'p IO::Buffer.new(8).set_value(:U8, 1, 111)'
    2

`io_buffer_set_value` passes `&offset` to `rb_io_buffer_set_value`, which
advances it by the size of the written value, and then returns the advanced
offset:

    static VALUE
    io_buffer_set_value(VALUE self, VALUE type, VALUE _offset, VALUE value)
    {
        struct rb_io_buffer *buffer = get_io_buffer_for_writing(self);
        size_t offset = io_buffer_extract_offset(_offset);
        rb_io_buffer_set_value(buffer, type, &offset, value);
        return SIZET2NUM(offset);
    }

This changed in 025b8701c0 (#6434, released in
3.2.0); before that the method returned the given offset, so the example was
correct at the time it was written. Checked with all-ruby: 3.1 returns 1 and
3.2 through 4.0 return 2.

Also state the return value explicitly for both `set_value` and `set_values`.
The call-seq says `-> offset`, which reads as if the given offset were
returned. `set_values` returns the offset after the last written value in the
same way (3 for the example in its documentation).
2026-07-27 19:49:40 +09:00
Shizuo Fujita e09a12b116 [DOC] Clarify when IO::Buffer#readonly? is true (#18062)
The documentation says "Frozen strings and read-only files create
read-only buffers", but this omits the most common case: a buffer
created by IO::Buffer.for without a block is always read-only, even
when the source string is not frozen.

This is because the no-block form of IO::Buffer.for uses an internal
frozen copy of the string as the buffer source (see rb_io_buffer_type_for,
which passes RB_IO_BUFFER_READONLY unconditionally), and its call-seq is
already documented as `IO::Buffer.for(string) -> readonly io_buffer`.

    p IO::Buffer.for("test").readonly?        # => true  (not frozen)
    p IO::Buffer.for("test") {|b| b.readonly? } # => false (block form)
    p IO::Buffer.new(4).readonly?             # => false
2026-07-25 19:40:41 +09:00
Samuel Williams 1619cf7cea io_buffer: add safe callback wrappers for String views. (#17473) 2026-06-25 14:28:09 +12:00
John HawthornandLuke Gruber daffaaee73 Mark most TypedData types as THREAD_SAFE_FREE
Set RUBY_TYPED_THREAD_SAFE_FREE on TypedData types whose dfree function
is trivially safe, and only frees its own memory

Types not yet marked as THREAD_SAFE_FREE:
- id2ref_tbl_type: sets the process-global id2ref_tbl to NULL.
- mutex_data_type: unlinks itself from another thread's keeping_mutexes list.
- autoload_data_type: deletes nodes from a shared intrusive list.
- autoload_const_type: deletes itself from a shared list.
- rb_cont_data_type: mutates the shared fiber pool and the global first_jit_cont list.
- rb_fiber_data_type: delegates to cont_free.
- FiberPoolDataType: mutates shared fiber pool free-lists.
- ractor_data_type: reconfigures global VM event-hook flags and call caches.
- exported_object_registry: frees a global table under the VM lock.
- rb_box_data_type (Box::Entry): unlinks classext from other live class and module objects.
- box_ext_cleanup_type: dereferences another String object and calls unlink.
- monitor_data_type: uses the default free, so the flag has no effect.

Co-authored-by: Luke Gruber <luke.gruber@shopify.com>
2026-06-23 13:30:26 -07:00
Sutou Kouhei 2b5ec2eecf IO::Buffer.map: Show actual values in error messages
This is for easy to find what was wrong.
2026-06-11 18:15:40 +09:00
Nobuyoshi Nakada 8ebc075c4b IO::Buffer: Remove ensure from set_value and set_values 2026-06-09 18:40:30 +09:00
Nobuyoshi Nakada e475350a7c IO::Buffer: Add the helper function get_io_buffer 2026-06-09 18:40:30 +09:00
Nobuyoshi Nakada 09e76c6cb7 IO::Buffer: Avoid inadvertent ID creation 2026-06-08 18:32:46 +09:00
Nobuyoshi Nakada 773e0c3a0f IO::Buffer: Validate the buffer after type argument conversion 2026-06-08 18:32:46 +09:00
Nobuyoshi Nakada 2c8002d583 IO::Buffer: Validate the buffer after argument conversion 2026-06-08 18:32:46 +09:00
Nobuyoshi Nakada 4bd3e14fc2 IO::Buffer: Validate the mask argument of bit operations 2026-06-07 17:40:30 +09:00
himura467 3373fcc2de Fix UAF in IO::Buffer#~ when self is an invalidated slice
`io_buffer_not` accessed `buffer->base` directly without validating that the buffer was still live. A slice whose parent had been freed retained its stale base pointer, so calling `~` on it caused a UAF.

Use `io_buffer_get_bytes_for_reading`, which raises `IO::Buffer::InvalidatedError` before any memory access if the buffer has been invalidated.
2026-05-20 06:57:01 +09:00