Repository navigation
Description
The leak #777 closed for allocation faults is still open for missing arguments
callNative (internal/vm/object_model.go:1558) converts a Go runtime fault into
a rescuable Ruby ArgumentError so that, as its comment says, "arbitrary input
from an embedding host — the wasm playground REPL, say — can never crash the
process". That net is right and should stay.
What it still does is hand the Go panic text to Ruby code as the exception
message. The same comment records this, for the allocation case:
Reporting it as ArgumentError also leaked the Go text verbatim into a Ruby
message, which is how"x".ljust(2**62)came to raise
ArgumentError: ljust: runtime error: makeslice: len out of rangewhere MRI
4.0.5 raisesNoMemoryError: failed to allocate memory(issue #777).
#777 fixed the allocation class: isAllocFault recognises makeslice:,
makemap: and growslice: and raises NoMemoryError with MRI's own literal.
The index-out-of-range class — a binding reading args[0] that was never
passed — was explicitly left as "the pre-existing behaviour (callNative's
ArgumentError)", and it is still there.
Measured, on main (3a3fb81), darwin/arm64
Eight calls with a missing or extra argument, in one run.
⛔ The rbgo column is measured; the MRI column is not. No MRI is invocable on
this machine, so the right-hand side is MRI's documented arity rendered in its
standard message form, not a reading. It should be re-measured against the
oracle before any of it is used as an expected value in a test — the point of
the table is the left column, where every row was produced by running the
command.
| call | rbgo (measured) | MRI 4.0 (documented arity, NOT measured here) |
|---|---|---|
"x".center() |
ArgumentError: center: runtime error: index out of range [0] with length 0 |
ArgumentError: wrong number of arguments (given 0, expected 1..2) |
1.divmod() |
ArgumentError: divmod: runtime error: index out of range [0] with length 0 |
ArgumentError: wrong number of arguments (given 0, expected 1) |
"a".sub() |
ArgumentError: sub: runtime error: index out of range [0] with length 0 |
ArgumentError: wrong number of arguments (given 0, expected 1..2) |
Integer() |
ArgumentError: Integer: runtime error: index out of range [0] with length 0 |
ArgumentError: wrong number of arguments (given 0, expected 1..2) |
Math.cbrt |
ArgumentError: cbrt: runtime error: index out of range [0] with length 0 |
ArgumentError: wrong number of arguments (given 0, expected 1) |
[1].fetch() |
ArgumentError: wrong number of arguments (given 0, expected 1..2) ✅ |
same |
{}.fetch() |
ArgumentError: wrong number of arguments (given 0, expected 1..2) ✅ |
same |
[1,2].each_slice() |
ArgumentError: wrong number of arguments (given 0, expected 1) ✅ |
same |
So some natives declare their arity and some do not, and the ones that do not
fall through to the net. Five of the eight probed are wrong; three are right.
This is a sample I picked, not a census — the real count needs a sweep.
One more shape, wrong in the other direction — and this row needs no reference
to be a defect, because answering an over-supplied call with a value rather than
an error is wrong against any Ruby:
| [1,2].first(1,2,3) | no error at all, returns a value | an ArgumentError |
The three ✅ rows are the useful control: they prove the correct message is
reachable through the same dispatch, so the five wrong ones are not a limitation
of the architecture.
Why it matters, stated without inflation
It is not an information leak today. The text names a Go method, says
"runtime error", and gives an index and a length both derived from the call
([0] with length 0) — nothing secret. The problem is the mechanism, which is
exactly the one #777 showed can carry internals outward: that issue's leak
exposed Go's allocator wording for a size the program chose.
The concrete harm now is smaller and certain:
- The message is wrong, where this project's whole discipline is MRI's exact
messages. TheMath::DomainErrorandTypeErrorstrings next door are
measured against MRI character by character. - It is not stable. "index out of range [0] with length 0" is Go's wording,
and a Ruby program that rescues and matches on it is matching on our build and
our Go version. The project already guards against that shape of coupling in
convertAllocPanic, which warns that if Go renames its prefixes the
classification degrades and a test says so. - It reports a bad call as a runtime fault, so the backstop that exists to
keep a host alive is also the thing answering an ordinary arity mistake.
Suggested direction
The net stays; what reaches it changes. Natives should declare their arity, as
Array#fetch, Hash#fetch and Enumerable#each_slice evidently already do, so
callNative's recover becomes what its comment says it is — a backstop for
genuine faults rather than the normal path for a missing argument.
If that is too broad for one change, the ordering that worked for #777 applies:
pick the class with no behavioural ambiguity first. Nobody depends on
"index out of range [0] with length 0", so replacing it with MRI's wording
cannot break a program that was relying on the old answer — unlike the
extra-argument half (#847), where a call that silently succeeded today would
start raising.
Math is the instance that led here and is filed separately as #847;
#846 has the two new 4.0 functions, which check their own arity for this reason
and pin MRI's wording in a test so they cannot drift into the leak.
🤖 Generated with Claude Code
Activity