Commit 558530c
authored
Test that a coerced TYPE_CONST_STRING argument stays in place (#213)
## Problem
`TYPE_CONST_STRING` stores a pointer into a Ruby String:
```c
case TYPE_CONST_STRING:
...
else { dst->pointer = rb_string_value_cstr(src); }
```
A String argument is safe. `argv` holds it and keeps it in place.
A `to_str` object is not. `rb_string_value_cstr` writes the coerced
String back into `src`. `argv` still holds the original object, not the
String. The next loop turn overwrites `src`. After that, only
`converted_args` holds the String.
The pointer must stay valid until `ffi_call` returns. `ffi_call` runs
under `rb_thread_call_without_gvl`, so another thread can compact the
heap during the call. `converted_args` must keep the String **in
place**, not only alive.
Master does this. `converted_args` sits in the `ALLOCV` buffer. The GC
scans that buffer conservatively, and a conservative scan pins the
object.
## Gap
No test covers this.
Before #205, `converted_args` was a Ruby Array. The GC may move an
Array's elements, so the String moved while the pointer stayed, and the
C function read the vacated slot. #205 replaced the Array with the
buffer and fixed the fault as a side effect. Nothing records that the
buffer is required.
## Change
1. `test_call_const_string_from_to_str_after_compaction` in
`test/fiddle/test_function.rb`.
2. A comment at `converted_args` in `ext/fiddle/function.c` stating the
requirement.
The test calls `strcspn` as `[TYPE_CONST_STRING, TYPE_VOIDP] ->
TYPE_SIZE_T`. Arg 0 is a `to_str` object returning a fresh 100-byte
String. Arg 1 is a `to_ptr` object, which makes Fiddle call
`Pointer.[]`; that allocation lets the GC run after the pointer is
stored.
The subject holds no `"Z"`; the reject set is `"Z"`. A correct call
returns 100. A stale pointer returns 0, because a vacated slot holds
zero bytes.
Three details are load-bearing. Remove any one and the test passes on a
faulty build; each has a comment:
- Churn is the subject's byte size, half of it dropped. The compactor
only evacuates non-full pages.
- `to_ptr` returns a **new** `Pointer` each time. An existing `Pointer`
gives Fiddle nothing to allocate.
- The intact-case call runs **after** the loop. A call before it
prepares the CIF, and that preparation is part of what opens the window.
## Result
| tree | arm64-darwin, ruby 3.4.10 | aarch64-linux, ruby 3.4.7 |
|---|---|---|
| `d389bbf` (before #205) | fail | fail |
| master | pass | pass |
Three runs per tree on darwin, same result each time. Full suite on
master with this change: 242 tests, 668 assertions, 0 failures, 1 error,
3 omissions. The error is `test_nogvl_poll` needing `envutil`; it fails
the same way without this change.
Environment: ruby 3.4.10 [arm64-darwin23]; ruby 3.4.7 [aarch64-linux],
glibc 2.36, libffi 3.4.4.
## Note
Released versions still have the fault. I measured 1.1.6 and 1.1.8 on
both platforms: a two-argument call of this shape returns the wrong
result on every iteration under `GC.stress` with `GC.auto_compact`.
Master is correct. You may want a release.
I report this as latent. The trigger is the declared argument type plus
a `to_str` object in place of a String. In one application that loads
fiddle at boot, all 227 bundled gems put every `Fiddle::Function.new`
behind a Windows-only branch.1 parent 8dc7c05 commit 558530c
2 files changed
Lines changed: 87 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
304 | 304 | | |
305 | 305 | | |
306 | 306 | | |
307 | | - | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
308 | 319 | | |
309 | 320 | | |
310 | 321 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
179 | 179 | | |
180 | 180 | | |
181 | 181 | | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
182 | 257 | | |
183 | 258 | | |
184 | 259 | | |
| |||
0 commit comments