(https://github.com/ruby/stringio/pull/225)
`Arrays.fill(byte[], int, int, byte)` takes **fromIndex, toIndex**, but
`ungetbyteCommon` passes `memset`'s **destination, length**:
```java
if (rest > cl) Arrays.fill(strBytes, len, rest - cl, (byte) 0);
```
straight from `ext/stringio/stringio.c:1181`, `memset(s + len, 0, rest -
cl)`. It also drops the ByteList `begin` offset that the C `s +`
supplies.
Pushing back onto a StringIO positioned more than the pushback length
past the end of its string then throws instead of zero-filling:
```ruby
s = StringIO.new("abc"); s.pos = 5; s.ungetbyte("d")
# JRuby: Java::JavaLang::IllegalArgumentException: fromIndex(3) > toIndex(1)
# CRuby: s.string == "abc\0d", s.pos == 4
```
The two siblings in this file already spell the correct form —
`truncate` (`StringIO.java:1462`) uses `buf.getBegin() + plen,
buf.getBegin() + l`, and `strioExtend` (`:1044-1048`) uses `begin +
olen, begin + pos`. This one line is the odd one out, so the fix reuses
their shape rather than adding anything.
The added expectations extend the existing `test_ungetc_padding` /
`test_ungetbyte_padding`, which stop one short: both start from
`StringIO.new()`, so `len == 0` makes the wrong call accidentally
correct.
<details>
<summary>Verification</summary>
Both backends built from this clone, `$LOADED_FEATURES` printed and
checked on every row.
A differential sweep of 2100 cases (7 base strings x 25 positions x 6
pushback values x `ungetbyte`/`ungetc`), JRuby against the C extension:
| build | rows differing from CRuby |
|---|---|
| current `master` | **132** |
| with the fix | **0** |
Every one of the 132 is the `IllegalArgumentException`.
| row | result |
|---|---|
| the two padding tests on `master` | 2 errors |
| with the fix | 2 tests, 12 assertions, 0 failures, 0 errors |
| revert | 2 errors |
| widen the guard to `rest > 0` | 1 error |
| `fromIndex` one low | 1 failure, 1 error |
| `toIndex` one high | passes — see below |
That last mutant survives, and I checked whether it was a gap in my test
or genuinely equivalent: the extra byte lands at `s + pos` after `pos -=
cl`, which `System.arraycopy(..., strBytes, s + pos, cl)` two lines
later overwrites unconditionally. Sweeping it over the same 2100 cases
gives **0 differences from the fixed build**, so it is an equivalent
mutant rather than an uncovered line, and I did not add a row for it.
Full `test/stringio/test_stringio.rb`: CRuby 3.2 **104 tests, 646
assertions, 0 failures, 0 errors**; JRuby 10.0.6 **104 tests, 643
assertions, 0 failures, 0 errors**.
Every added expectation was run against the C extension first, so it
encodes CRuby behaviour rather than my reading of it.
Two things I want on the record. The `s +` half is not separately
covered — I could not construct a StringIO whose backing ByteList has
`begin != 0` through the public API. It is included because both
siblings and the C both require it; if you would rather keep the change
minimal, the two-index correction alone fixes the crash. And
`ruby/.github`'s SECURITY.md routes vulnerabilities privately: this is
the JVM's own bounds check firing rather than memory unsafety, and I
could not produce any stale-byte disclosure, so I filed publicly — say
the word if you would rather it had gone the other route.
The CI matrix runs `jruby-head`; the closest I could obtain is JRuby
10.0.6. Note `jruby:9.4` cannot compile `ext/java` at master at all
(`Helpers.memchr` signature mismatch), so 10.x is already the floor.
</details>
---
Disclosure: I used Claude (an AI assistant) while preparing this change.
Every result above I ran and verified myself.
https://github.com/ruby/stringio/commit/9d3e502b3a
* Added `Ractor::Port`
* `Ractor::Port#receive` (support multi-threads)
* `Rcator::Port#close`
* `Ractor::Port#closed?`
* Added some methods
* `Ractor#join`
* `Ractor#value`
* `Ractor#monitor`
* `Ractor#unmonitor`
* Removed some methods
* `Ractor#take`
* `Ractor.yield`
* Change the spec
* `Racotr.select`
You can wait for multiple sequences of messages with `Ractor::Port`.
```ruby
ports = 3.times.map{ Ractor::Port.new }
ports.map.with_index do |port, ri|
Ractor.new port,ri do |port, ri|
3.times{|i| port << "r#{ri}-#{i}"}
end
end
p ports.each{|port| pp 3.times.map{port.receive}}
```
In this example, we use 3 ports, and 3 Ractors send messages to them respectively.
We can receive a series of messages from each port.
You can use `Ractor#value` to get the last value of a Ractor's block:
```ruby
result = Ractor.new do
heavy_task()
end.value
```
You can wait for the termination of a Ractor with `Ractor#join` like this:
```ruby
Ractor.new do
some_task()
end.join
```
`#value` and `#join` are similar to `Thread#value` and `Thread#join`.
To implement `#join`, `Ractor#monitor` (and `Ractor#unmonitor`) is introduced.
This commit changes `Ractor.select()` method.
It now only accepts ports or Ractors, and returns when a port receives a message or a Ractor terminates.
We removes `Ractor.yield` and `Ractor#take` because:
* `Ractor::Port` supports most of similar use cases in a simpler manner.
* Removing them significantly simplifies the code.
We also change the internal thread scheduler code (thread_pthread.c):
* During barrier synchronization, we keep the `ractor_sched` lock to avoid deadlocks.
This lock is released by `rb_ractor_sched_barrier_end()`
which is called at the end of operations that require the barrier.
* fix potential deadlock issues by checking interrupts just before setting UBF.
https://bugs.ruby-lang.org/issues/21262
(https://github.com/ruby/stringio/pull/85)
* Fix compile warnings on latest Java.
* Adjust one test for JRuby erroring on bad pos= value.
* Replace hard JVM synchronization with a spin lock.
* Avoid duplicate reads of state fields where possible.
* Remove long-deprecated codepoints, chars, bytes, lines methods.
* Mark fcntl as not implemented
* Split most of the remaining variable-arity methods.
https://github.com/ruby/stringio/commit/31a9d42e6c
(https://github.com/ruby/stringio/pull/77)
Followup of #79
`rb_str_resize()` was changed by https://github.com/ruby/ruby/commit/b0b9f7201acab05c2a3ad92c3043a1f01df3e17f .
```c
rb_str_resize(string, shorter) // clear ENC_CODERANGE in some case
rb_str_resize(string, longer) // does not clear ENC_CODERANGE anymore
```
```c
// rb_str_resize in string.c
if (slen > len && ENC_CODERANGE(str) != ENC_CODERANGE_7BIT) {
ENC_CODERANGE_CLEAR(str);
}
```
I think this change is based on an assumption that appending null bytes
will not change flag `ascii_only?`.
`strio_extend()` will make the string longer if needed, and update the
flags correctly for appending null bytes.
Before `memmove()`, we need to `rb_str_modify()` because updated flags are not
updated for `memmove()`.
https://github.com/ruby/stringio/commit/b31a538576
Both for being closer to real IOs and also because it's a convenient API
in multithreaded scenarios.
Co-authored-by: Jean Boussier <jean.boussier@gmail.com>
Try to mirror IO behavior, where chomp takes out the entire paragraph
separators between entries, but does not chomp a single line separator
at the end of the string.
Partially Fixes [Bug #18768]
https://github.com/ruby/stringio/commit/a83ddbb7f0
Previously, this could result in an infinite loop. Always update
the e pointer in this case, setting w when chomping so the chomped
data is not included in the output.
Fixes [Bug #18769]
https://github.com/ruby/stringio/commit/4bf64d5130