Repository navigation
Array.new's size conversion, and ~ expansion's HOME rule (closes #901) - #908
tannevaled wants to merge 1 commit into
Conversation
Both items of #901, plus a third divergence the second one exposed. ## 1. Array.new(2**63) raises RangeError, not ArgumentError Array.new(10**40) # MRI: RangeError: bignum too big to convert into 'long' # rbgo: ArgumentError: array size too big rb_ary_new2 takes a long, so NUM2LONG runs BEFORE any size test; ary_new's ArgumentError is only for a value that converted and is still out of range. The class matters: `rescue RangeError` could not catch it. The boundary is exactly LONG_MAX, measured on 4.0.7: 2**62 ArgumentError: array size too big 2**63-1 ArgumentError: array size too big 2**63 RangeError: bignum too big to convert into 'long' 10**40 RangeError Array#first already had the identical *object.Bignum check with the right class 200 lines below (builtins.go:3331). One idiom, applied in one of the two places that needed it. ⛔ An existing row PINNED the divergence: array_residuals_wave10_test.go asserted ArgumentError for Array.new(10**40), under a comment reading "Verified against MRI Ruby 4.0". It is moved with its citation, and both sides of the boundary are now asserted so the row says WHERE the line is. ## 2. expand_path("~") answers when HOME is unset ENV.delete("HOME"); File.expand_path("~") # MRI: "/Users/…" (the passwd entry for the current uid) # rbgo: ArgumentError: couldn't find HOME environment -- expanding '~' dirHomeStr (dir.go) ALREADY encoded MRI's rule for Dir.home — $HOME, then getpwuid, raise only when neither answers. expand_path kept its own HOME-only copy, so Dir.home was right and expand_path was wrong for the same input. The fix is a call to the existing helper. ## 3. And HOME="" is a VALUE, not an absence (found by fixing 2) The first version of this change sent an EMPTY HOME to the passwd database too, and the repository's own TestFileExpandPathHomeGuards caught it. MRI's rb_default_home_dir branches on `if (!dir)` — getenv returning NULL — so only an ABSENT HOME reaches the fallback. Measured on 4.0.7: HOME Dir.home File.expand_path("~") "" "" ArgumentError: non-absolute home "relative/home" "relative/home" ArgumentError: non-absolute home unset passwd home passwd home So Dir.home answers getenv VERBATIM — empty or relative, unchecked — and expand_path is the one that demands an absolute path. ⛔ The seam could not express that rule: osUserHomeDir wrapped os.UserHomeDir, which reports an error for unset AND empty alike. It is now osLookupHome, with LookupEnv's (value, set) shape. The two seam tests moved with it rather than being relaxed, and a third row asserts that a SET HOME never reaches the passwd stub. ⛔ That also fixes a divergence nobody had filed: Dir.home with HOME="" answered the passwd home where MRI answers "". It is the same helper and the same distinction as item 2, so it could not be separated out. ⛔ TestFileExpandPathHomeGuards passed BEFORE this change for the wrong reason — right class, wrong message ("couldn't find HOME environment" where MRI says "non-absolute home"). The new test asserts the messages. ## Measured cmd/oracle against /opt/homebrew/opt/ruby@4.0/bin/ruby (ruby 4.0.7, +PRISM): six probes, all "2 engines AGREED, byte for byte". ⛔ The new test mutates the PROCESS environment (ENV["HOME"] in Ruby is setenv, and runSrc builds a VM in this process), so it pins and restores HOME with t.Setenv: a row leaving HOME empty would hand that to every later test in the package. Closes #901 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The corpus sweep: 0 moved, and that is the expected answerClean on every channel: Why
|
covered by core,language? |
|
|---|---|
Array.new(2**63) → RangeError |
no — nothing in core/array/ mentions either message |
expand_path("~") with unset HOME |
no |
expand_path("~") with empty HOME |
no |
expand_path("~") with a relative HOME |
yes — core/file/expand_path_spec.rb:260 |
Both defects were found by probing MRI with rubydiff/cmd/oracle, not by a failing example, so no example can move when they are fixed. The sweep's job here was the other one — proving the blast radius is zero for a helper (dirHomeStr) that Dir.home, Dir.chdir and every ~ expansion go through.
⭐ And the corpus did supply one control I did not have to write
core/file/expand_path_spec.rb control: pass=27 fail=0 treatment: pass=27 fail=0
That file asserts ENV["HOME"] = "non-absolute" → ArgumentError, 'non-absolute home' — the one branch of the three that this change deliberately leaves alone. It passed before and passes after, which is exactly the row I would have had to invent to show the relative-HOME path is untouched.
⭐ Incidentally it also guards the hazard this PR's own test had to handle: its after :each restores ENV["HOME"]. Upstream treats a spec that mutates HOME as something to clean up after, for the same reason t.Setenv is in the new test — a leaked HOME is handed to everything that runs next.
🤖 Generated with Claude Code
Closes #901 — both items — plus a third divergence that fixing the second one exposed.
1.
Array.new(2**63)raisesRangeError, notArgumentErrorrb_ary_new2takes along, soNUM2LONGruns before any size test;ary_new'sArgumentErroris only for a value that converted and is still out of range. The class is the point —rescue RangeErrorcould not catch it.The boundary is exactly
LONG_MAX, measured on 4.0.7 rather than reasoned from the width of along:2**62ArgumentError: array size too big2**63-1ArgumentError: array size too big2**63RangeError10**40RangeErrorArray#firstalready had the identical*object.Bignumcheck with the right class 200 lines below (builtins.go:3331). One idiom, applied in one of the two places that needed it.⛔ An existing row pinned the divergence.
array_residuals_wave10_test.goassertedArgumentErrorforArray.new(10**40)— under a comment reading "Verified against MRI Ruby 4.0". I found it by measuring the boundary, not by trusting the test. It is moved with its citation, and both sides of the boundary are now asserted so the row says where the line is.2.
expand_path("~")answers whenHOMEis unsetdirHomeStr(dir.go) already encoded MRI'srb_default_home_dirforDir.home—$HOME, thengetpwuid, raise only when neither answers.expand_pathkept its ownHOME-only copy, soDir.homewas right andexpand_pathwas wrong for the same input. The fix is a call to the existing helper.#901 said the passwd fallback was a guess and had to be established first. It is:
Etc.getpwuid(Process.uid).diris byte-identical to what MRI returns, andHOMEis re-read on every call ("/first/home"then"/second/home"answer differently in one process), which is the control that makes the fallback attributable rather than a value captured at startup.3. And
HOME=""is a VALUE, not an absence — found by fixing 2My first version sent an empty
HOMEto the passwd database as well, and the repository's ownTestFileExpandPathHomeGuardscaught it. MRI branches onif (!dir)—getenvreturning NULL — so only an absentHOMEreaches the fallback:HOMEDir.homeFile.expand_path("~")""""ArgumentError: non-absolute home"relative/home""relative/home"ArgumentError: non-absolute homeDir.homeanswersgetenvverbatim — empty or relative, unchecked — andexpand_pathis the one that demands an absolute path.⛔ The seam could not express that rule.
osUserHomeDirwrappedos.UserHomeDir, which reports an error for unset and empty alike, so the two were folded together. It is nowosLookupHome, withLookupEnv's(value, set)shape. The two seam tests moved with it rather than being relaxed, and a third row asserts that a setHOMEnever reaches the passwd stub.⛔ This also fixes a divergence nobody had filed:
Dir.homewithHOME=""answered the passwd home where MRI answers"". Same helper, same distinction as item 2 — it could not be separated out, so it is here and said out loud rather than slipped in.⛔
TestFileExpandPathHomeGuardspassed before this change for the wrong reason — right class, wrong message (couldn't find HOME environmentwhere MRI saysnon-absolute home). It only checked the class. The new test asserts the messages.Measured
cmd/oracleagainst/opt/homebrew/opt/ruby@4.0/bin/ruby(ruby 4.0.7 (2026-09-15 revision 229531a6cf) +PRISM [arm64-darwin25]): six probes, every one2 engines AGREED, byte for byte.⛔ The new test mutates the process environment.
ENV["HOME"] = …in Ruby issetenv, andrunSrcbuilds a VM inside the test process, so a row leavingHOMEempty or deleted would hand that to every test running after it in the same package. It pins and restoresHOMEwitht.Setenv, which also makes the test refuse to run in parallel — correct for something that writes a global.Closes #901
🤖 Generated with Claude Code