diff --git a/.github/workflows/arch-qemu-nightly.yml b/.github/workflows/arch-qemu-nightly.yml index 3535801c..869f2e94 100644 --- a/.github/workflows/arch-qemu-nightly.yml +++ b/.github/workflows/arch-qemu-nightly.yml @@ -1,7 +1,8 @@ name: arch-qemu-nightly # Scheduled QEMU validation of the four EXOTIC 64-bit targets that GitHub-hosted -# runners cannot run natively: riscv64, loong64, ppc64le, s390x. +# runners cannot run natively: riscv64, loong64, ppc64le, s390x — and of 32-bit +# arm (vetted per PR in ci.yml's arch-386 job, whose 386 suite runs natively). # # These lanes used to run on every PR/push, where qemu-user emulation (~50-100x # slower than native) was the CI bottleneck (~10 min/lane). They have been moved @@ -39,6 +40,8 @@ jobs: - { goarch: loong64, qemu: qemu-loongarch64-static } - { goarch: ppc64le, qemu: qemu-ppc64le-static } - { goarch: s390x, qemu: qemu-s390x-static } + # 32-bit: int is 32 bits. (386 runs natively per PR in ci.yml.) + - { goarch: arm, qemu: qemu-arm-static } steps: - uses: actions/checkout@v7 - uses: actions/setup-go@v7 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f49686ce..8025a52c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -109,6 +109,29 @@ jobs: go-version: 1.27.2 - run: go env GOARCH && go test ./... + # 32-bit gate: int is 32 bits. 386 runs natively on the amd64 runner (the + # kernel executes 32-bit x86 binaries), not under qemu-i386, whose emulation + # loses x87/SSE state across preemption. 32-bit arm is vetted here (a cheap + # compile of every package and test) and run under qemu in the nightly + # (arch-qemu-nightly.yml). The mysql2 tests are 64-bit only: their in-process + # server, dolthub/go-mysql-server, does not compile for 32-bit targets. + arch-386: + name: arch 386 (+ vet arm) + runs-on: ubuntu-latest + env: + CGO_ENABLED: "0" + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-go@v7 + with: + go-version: 1.27.2 + - name: Vet (GOARCH=386, GOARCH=arm) + run: | + GOARCH=386 go vet ./... + GOARCH=arm go vet ./... + - name: Test (GOARCH=386) + run: GOARCH=386 go test -timeout=60m ./... + # WebAssembly (WASI preview 1) gate: the same interpreter compiled for # GOOS=wasip1 GOARCH=wasm and RUN under a real wasm runtime. The database / # search / KV / heavy-network gem backends are compiled out on wasm (their diff --git a/README.md b/README.md index 89209645..898a5af6 100644 --- a/README.md +++ b/README.md @@ -771,12 +771,13 @@ Otherwise: | --- | --- | | **linux, macOS, windows** | the full suite under `-race`. The **100 % coverage gate** is enforced on linux and macOS only — a few POSIX-only paths (opening `/dev/null` as a character device) are unreachable on windows, so its coverage is structurally below 100 % | | **amd64, arm64** | `go test ./...` on native runners | +| **386, arm (32-bit)** | `go vet ./...` for `GOARCH=386` and `GOARCH=arm`, then `go test ./...` for `386`, run natively on the amd64 runner (`arch 386 (+ vet arm)` in `ci.yml`). Ruby semantics do not change with the word size: Integer stays a 64-bit Fixnum promoted to Bignum on every target. The `mysql2` tests are skipped there, because their in-process server, `dolthub/go-mysql-server`, does not compile for 32-bit targets; the binding itself does. One limit is the word size's: a buffered IO's position is a Go `int`, so on 32-bit targets it stops at 2**31-1 | | **wasip1/wasm** | built with `CGO_ENABLED=0 GOOS=wasip1 GOARCH=wasm` and **run** under [wazero](https://wazero.io), a pure-Go runtime — verified here: `wazero run rbgo.wasm -e 'puts (1..10).sum'` prints `55` | | **js/wasm (browser)** | `CGO_ENABLED=0 GOOS=js GOARCH=wasm go build ./...` plus `go vet` over `./cmd/wasm ./cmd/rbgo ./internal/vm`, then the playground module is **run** in node by `web/smoke.mjs` (`wasm (js, browser)` in `ci.yml`). Added with #684, which fixed the three `syscall` constants the `js` port does not define | | **rbgo_core** | `go vet` and the `internal/vm` suite with `-tags rbgo_core`, then the core playground module is built for js/wasm, its size bounded, and **run** in node by `web/smoke.mjs --core` (`rbgo_core (core build)` in `ci.yml`). See [the `rbgo_core` build](#the-rbgo_core-build-the-language-and-the-numeric-stack-no-gems) | **Not on the pull-request path** — validated on a nightly schedule instead: -`riscv64`, `loong64`, `ppc64le` and `s390x`, under QEMU +`riscv64`, `loong64`, `ppc64le`, `s390x` and 32-bit `arm`, under QEMU (`arch-qemu-nightly.yml`) and natively on real hardware (`native-arch.yml`: the GCC Compile Farm, plus an IBM LinuxONE s390x host). @@ -1183,7 +1184,8 @@ SPECDIR=/path/to/ruby-spec CACHE=/path/to/ruby-spec scripts/conformance/rubyspec ### CI layering Per-PR CI is a **fast native gate**: the `-race` + 100 %-coverage suite on -ubuntu/macos/windows plus native `amd64` and `arm64` lanes. Because rbgo is +ubuntu/macos/windows plus native `amd64`, `arm64` and 32-bit `386` lanes (32-bit +`arm` is vetted per PR and run nightly under QEMU). Because rbgo is pure-Go (CGO=0, no assembly), the four exotic 64-bit targets (`riscv64`/`loong64`/`ppc64le`/`s390x`) are validated **off the per-PR critical path** — nightly under QEMU, and on **real hardware via the GCC Compile Farm** diff --git a/go.mod b/go.mod index f4f57c2c..aba183e9 100644 --- a/go.mod +++ b/go.mod @@ -52,7 +52,7 @@ require ( github.com/go-ruby-capistrano/capistrano v0.0.0-20260927173236-3b90af3c6888 github.com/go-ruby-capybara/capybara v0.0.0-20260910084630-0731c9ea8d6d github.com/go-ruby-cgi/cgi v0.0.0-20260916091525-3d328d3ff447 - github.com/go-ruby-chronic/chronic v0.0.0-20260916091614-bd47d560b6f4 + github.com/go-ruby-chronic/chronic v0.1.0 github.com/go-ruby-cmath/cmath v0.0.0-20260916091648-4963ab4145f7 github.com/go-ruby-concurrent-ruby/concurrent-ruby v0.0.0-20260916091840-b820f7a57284 github.com/go-ruby-confd/confd v0.0.0-20260825131031-1ac780d51c57 diff --git a/go.sum b/go.sum index a5917f04..d8cc2f21 100644 --- a/go.sum +++ b/go.sum @@ -377,8 +377,8 @@ github.com/go-ruby-capybara/capybara v0.0.0-20260910084630-0731c9ea8d6d h1:eDH85 github.com/go-ruby-capybara/capybara v0.0.0-20260910084630-0731c9ea8d6d/go.mod h1:igWLNrYKtKkssHOzlXm1YFoPH0Yi6LGazOSA4Obo6T0= github.com/go-ruby-cgi/cgi v0.0.0-20260916091525-3d328d3ff447 h1:o6V4n2Td46cjbCwPN0pZnUVFfHbC648AnYtMQVU9O60= github.com/go-ruby-cgi/cgi v0.0.0-20260916091525-3d328d3ff447/go.mod h1:TAIxiFMAj68dWClzEmDY1vCSNFiPfupD3HRADRXrkZw= -github.com/go-ruby-chronic/chronic v0.0.0-20260916091614-bd47d560b6f4 h1:Z2aDUPl3f6fQIJ4Y+9KCE16p77dAxJBBJjR3j974A5o= -github.com/go-ruby-chronic/chronic v0.0.0-20260916091614-bd47d560b6f4/go.mod h1:6H3V0VdtD8NB/WEt+8+bIM0n78g45H8HDAnWNwJByCM= +github.com/go-ruby-chronic/chronic v0.1.0 h1:3rar6r+rLVFNmyGpTPZvMbjEnnRh9JI2vNI11048A70= +github.com/go-ruby-chronic/chronic v0.1.0/go.mod h1:hhudekjG06iXIHV99juSwG+aOJ+Jv7dNS7tLzEX8ywg= github.com/go-ruby-cmath/cmath v0.0.0-20260916091648-4963ab4145f7 h1:2tUvNHeR8mL8NcJyxTZxaaujvusucfBsnZAQg18D9Zg= github.com/go-ruby-cmath/cmath v0.0.0-20260916091648-4963ab4145f7/go.mod h1:djGUM2WR7AdnZqeRg/OQ4xVzmsl8PAWQKILX2n/foas= github.com/go-ruby-concurrent-ruby/concurrent-ruby v0.0.0-20260916091840-b820f7a57284 h1:hIdCXEwLLYl+NPKBCbI3aFul6ARXfgEczxlA0hIDOkY= diff --git a/internal/vm/actionpack.go b/internal/vm/actionpack.go index 23ecf46f..07f5af2a 100644 --- a/internal/vm/actionpack.go +++ b/internal/vm/actionpack.go @@ -228,7 +228,7 @@ func apStr(v object.Value) string { func apInt(v object.Value) int { switch n := v.(type) { case object.Integer: - return int(n) + return clampInt(n) case object.Float: return int(n) } diff --git a/internal/vm/actionview.go b/internal/vm/actionview.go index db2bccdb..1eb177b6 100644 --- a/internal/vm/actionview.go +++ b/internal/vm/actionview.go @@ -537,7 +537,7 @@ func avNumberOpts(h *object.Hash) []actionview.Option { var o []actionview.Option if v, ok := avHashVal(h, "precision"); ok { if i, ok := v.(object.Integer); ok { - o = append(o, actionview.Precision(int(i))) + o = append(o, actionview.Precision(clampInt(i))) } } if v, ok := avHashVal(h, "significant"); ok { diff --git a/internal/vm/actionview_bind.go b/internal/vm/actionview_bind.go index b337a218..0e857c4d 100644 --- a/internal/vm/actionview_bind.go +++ b/internal/vm/actionview_bind.go @@ -210,7 +210,7 @@ func avValue(v object.Value) any { case object.Bool: return bool(x) case object.Integer: - return int(x) + return clampInt(x) case object.Float: return float64(x) case *object.String: @@ -325,7 +325,7 @@ func avHashStr(h *object.Hash, key, def string) string { func avHashInt(h *object.Hash, key string, def int) int { if v, ok := avHashVal(h, key); ok { if i, ok := v.(object.Integer); ok { - return int(i) + return clampInt(i) } } return def diff --git a/internal/vm/active_ldap_bind.go b/internal/vm/active_ldap_bind.go index be66385e..291e5cd6 100644 --- a/internal/vm/active_ldap_bind.go +++ b/internal/vm/active_ldap_bind.go @@ -167,7 +167,7 @@ func alFindOptions(args []object.Value) activeldap.FindOptions { } } if v, ok := alKw(h, "limit"); ok { - opts.Limit = int(intArg(v)) + opts.Limit = clampInt(intArg(v)) } if v, ok := alKw(h, "attributes"); ok { opts.Attributes = rubyStrList(v) diff --git a/internal/vm/activejob_bind.go b/internal/vm/activejob_bind.go index 17f1ade5..649dbc43 100644 --- a/internal/vm/activejob_bind.go +++ b/internal/vm/activejob_bind.go @@ -369,7 +369,7 @@ func ajRawFromRuby(v object.Value) any { // ajInt coerces an Integer argument to an int (a non-Integer reads as 0). func ajInt(v object.Value) int { if n, ok := v.(object.Integer); ok { - return int(n) + return clampInt(n) } return 0 } diff --git a/internal/vm/activemodel_bind.go b/internal/vm/activemodel_bind.go index 30412635..fdaf2ff4 100644 --- a/internal/vm/activemodel_bind.go +++ b/internal/vm/activemodel_bind.go @@ -354,7 +354,7 @@ func (vm *VM) amBuildOptions(h *object.Hash) activemodel.Options { // amIntPtr reads an Integer option into a *int, or nil. func amIntPtr(v object.Value) *int { if n, ok := v.(object.Integer); ok { - i := int(n) + i := clampInt(n) return &i } return nil @@ -368,11 +368,11 @@ func amIntRange(v object.Value) *activemodel.Range { } lo, _ := r.Lo.(object.Integer) hi, _ := r.Hi.(object.Integer) - max := int(hi) + max := clampInt(hi) if r.Exclusive { max-- } - return &activemodel.Range{Min: int(lo), Max: max} + return &activemodel.Range{Min: clampInt(lo), Max: max} } // amLengthOptions reads a length: options Hash into LengthOptions. diff --git a/internal/vm/activerecord_helpers.go b/internal/vm/activerecord_helpers.go index 8ad3355f..5a28c6ff 100644 --- a/internal/vm/activerecord_helpers.go +++ b/internal/vm/activerecord_helpers.go @@ -76,7 +76,7 @@ func arInt(args []object.Value) int { return 0 } if n, ok := args[0].(object.Integer); ok { - return int(n) + return clampInt(n) } return 0 } @@ -207,7 +207,7 @@ func arLengthOpts(args []object.Value) activerecord.LengthOpts { if !isInt { continue } - i := int(n) + i := clampInt(n) switch arStr(k) { case "minimum": o.Minimum = &i diff --git a/internal/vm/activerecord_schema_bind.go b/internal/vm/activerecord_schema_bind.go index b3ff3239..9e4f62fd 100644 --- a/internal/vm/activerecord_schema_bind.go +++ b/internal/vm/activerecord_schema_bind.go @@ -261,7 +261,7 @@ func arColOpts(h *object.Hash) []activerecord.ColOpt { } if val, ok := arHashGet(h, "limit"); ok { if n, isInt := val.(object.Integer); isInt { - opts = append(opts, activerecord.Limit(int(n))) + opts = append(opts, activerecord.Limit(clampInt(n))) } } return opts diff --git a/internal/vm/activesupport.go b/internal/vm/activesupport.go index 2772383d..07f6382c 100644 --- a/internal/vm/activesupport.go +++ b/internal/vm/activesupport.go @@ -104,10 +104,10 @@ func (vm *VM) registerActiveSupportInflector(as *RClass) { return object.NewString(vm.asInflections.ForeignKey(strArg(args[0]), true)) }) sm("ordinal", func(_ *VM, _ object.Value, args []object.Value, _ *Proc) object.Value { - return object.NewString(inflector.Ordinal(int(intArg(args[0])))) + return object.NewString(inflector.Ordinal(clampInt(intArg(args[0])))) }) sm("ordinalize", func(_ *VM, _ object.Value, args []object.Value, _ *Proc) object.Value { - return object.NewString(inflector.Ordinalize(int(intArg(args[0])))) + return object.NewString(inflector.Ordinalize(clampInt(intArg(args[0])))) }) sm("parameterize", func(_ *VM, _ object.Value, args []object.Value, _ *Proc) object.Value { return object.NewString(inflector.Parameterize(strArg(args[0]), "-", false)) @@ -311,7 +311,7 @@ func (vm *VM) registerASString() { return object.NewString(coreext.Squish(str(self))) }) vm.cString.define("truncate", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { - return object.NewString(coreext.Truncate(str(self), int(intArg(args[0])), "", "")) + return object.NewString(coreext.Truncate(str(self), clampInt(intArg(args[0])), "", "")) }) d("camelize", -1, func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { upper := true @@ -354,14 +354,14 @@ func (vm *VM) registerASArray() { return object.Bool(coreext.ArrayBlank(boxSlice(elems(self)))) }) vm.cArray.define("in_groups", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { - n := int(intArg(args[0])) + n := clampInt(intArg(args[0])) if len(args) > 1 && isFalse(args[1]) { return rubyFromGroups(coreext.InGroupsNoFill(boxSlice(elems(self)), n)) } return rubyFromGroups(coreext.InGroups(boxSlice(elems(self)), n, fillArg(args))) }) vm.cArray.define("in_groups_of", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { - n := int(intArg(args[0])) + n := clampInt(intArg(args[0])) if len(args) > 1 && isFalse(args[1]) { return rubyFromGroups(coreext.InGroupsOfNoFill(boxSlice(elems(self)), n)) } @@ -420,10 +420,10 @@ func (vm *VM) registerASInteger() { d := func(name string, argc int, fn NativeFn) { vm.cInteger.defineArgc(name, argc, fn) } d("ordinalize", 0, func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { - return object.NewString(coreext.Ordinalize(int(self.(object.Integer)))) + return object.NewString(coreext.Ordinalize(clampInt(self.(object.Integer)))) }) d("multiple_of?", 1, func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { - return object.Bool(coreext.MultipleOf(int(self.(object.Integer)), int(intArg(args[0])))) + return object.Bool(coreext.MultipleOf(clampInt(self.(object.Integer)), clampInt(intArg(args[0])))) }) } diff --git a/internal/vm/age.go b/internal/vm/age.go index 88774bcf..5e41916d 100644 --- a/internal/vm/age.go +++ b/internal/vm/age.go @@ -139,7 +139,7 @@ func (vm *VM) registerAgeScrypt(mod *RClass) { } if h := ageOptsHash(args[1:]); h != nil { if v, ok := h.Get(object.Symbol("work_factor")); ok { - r.SetWorkFactor(int(intArg(v))) + r.SetWorkFactor(clampInt(intArg(v))) } } return &AgeScryptRecipient{cls: rc, rcp: r} @@ -155,7 +155,7 @@ func (vm *VM) registerAgeScrypt(mod *RClass) { } if h := ageOptsHash(args[1:]); h != nil { if v, ok := h.Get(object.Symbol("max_work_factor")); ok { - id.SetMaxWorkFactor(int(intArg(v))) + id.SetMaxWorkFactor(clampInt(intArg(v))) } } return &AgeScryptIdentity{cls: ic, id: id} diff --git a/internal/vm/alloc_bounds.go b/internal/vm/alloc_bounds.go index 650e928c..9ec9c217 100644 --- a/internal/vm/alloc_bounds.go +++ b/internal/vm/alloc_bounds.go @@ -3,6 +3,7 @@ package vm import ( "math" "runtime" + "strconv" "strings" "unsafe" @@ -78,6 +79,28 @@ func repeatFitsString(srcLen int, n int64) bool { return n == 0 || strMaxSize/n >= int64(srcLen) } +// hostAllocMax is the largest single allocation, in bytes, rbgo asks the Go +// runtime for on behalf of a Ruby size. Above Go's own maxAlloc, make panics +// with a recoverable "makeslice:" fault, which convertAllocPanic turns into +// NoMemoryError. BELOW it, a request the host cannot satisfy is a fatal +// "out of memory" that nothing can recover. +// +// On a 64-bit target maxAlloc is 2**48 and this bound is set there, so it +// refuses exactly what make would have refused, with the same NoMemoryError. +// On a 32-bit target maxAlloc is about 2**32, the whole address space, so +// everything between ~1 GiB and 4 GiB was the fatal window: "x".center(2**62) +// asked for 4 GiB and killed the process (386 CI). There the bound is 2**30. +const hostAllocMax = int64(1) << (30 + 18*(strconv.IntSize/32-1)) + +// checkAlloc raises NoMemoryError, MRI's answer for a refused allocation, when n +// elements of elemSize bytes exceed hostAllocMax. It divides rather than +// multiplies, so n*elemSize is never formed. +func checkAlloc(n, elemSize int64) { + if n > hostAllocMax/elemSize { + raise("NoMemoryError", "failed to allocate memory") + } +} + // fitsArrayAlloc reports whether n elements stay within ARY_MAX_SIZE. It is the // single-factor form of the test, for a size that is taken from a Ruby value // without being multiplied (Array.new's capacity, Array#fill's new length). @@ -164,5 +187,6 @@ func randomByteCount(v object.Value) int { if n < 0 { raise("ArgumentError", "negative string size (or size too big)") } - return int(n) + checkAlloc(n, 1) + return clampInt(n) } diff --git a/internal/vm/alloc_bounds_internal_test.go b/internal/vm/alloc_bounds_internal_test.go index dd9ad2ae..8165cd5e 100644 --- a/internal/vm/alloc_bounds_internal_test.go +++ b/internal/vm/alloc_bounds_internal_test.go @@ -8,6 +8,7 @@ import ( "bytes" "fmt" "math" + "strconv" "strings" "testing" @@ -55,6 +56,13 @@ func runKeepingError(t *testing.T, src string) (*VM, string, error) { // product is never formed. Expectations measured byte-for-byte against the // installed ruby 4.0.5. func TestRepeatBoundRegimes(t *testing.T) { + // "x" * (2**62) passes MRI's LONG_MAX/n test only because LONG_MAX/2**62 is + // 1 with a 64-bit long. With a 32-bit int the quotient is 0, so the same + // division test (strMaxSize is math.MaxInt) refuses with ArgumentError. + strOneByte := "NoMemoryError: failed to allocate memory\n" + if strconv.IntSize == 32 { + strOneByte = "ArgumentError: argument too big\n" + } cases := []struct{ name, src, want string }{ // --- Array#*: MRI divides ARY_MAX_SIZE, so all of these are ArgumentError. {"ary_over_maxalloc", `begin; [1] * (2**62); rescue Exception=>e; puts "#{e.class}: #{e.message}"; end`, @@ -74,7 +82,7 @@ func TestRepeatBoundRegimes(t *testing.T) { // receiver's LENGTH. This asymmetry is MRI's and is reproduced, not // normalised -- see alloc_bounds.go. {"str_one_byte_is_nomemory", `begin; "x" * (2**62); rescue Exception=>e; puts "#{e.class}: #{e.message}"; end`, - "NoMemoryError: failed to allocate memory\n"}, + strOneByte}, {"str_two_byte_is_argerror", `begin; "ab" * (2**62); rescue Exception=>e; puts "#{e.class}: #{e.message}"; end`, "ArgumentError: argument too big\n"}, {"str_three_byte_is_argerror", `begin; "xyz" * (2**62); rescue Exception=>e; puts "#{e.class}: #{e.message}"; end`, diff --git a/internal/vm/argf.go b/internal/vm/argf.go index 1eef34e7..c5d008d5 100644 --- a/internal/vm/argf.go +++ b/internal/vm/argf.go @@ -451,7 +451,7 @@ func (vm *VM) registerARGF() { return object.IntValue(int64(self(v).lineno)) }) d("lineno=", func(vm *VM, v object.Value, args []object.Value, _ *Proc) object.Value { - self(v).lineno = int(intArg(args[0])) + self(v).lineno = clampInt(intArg(args[0])) return args[0] }) diff --git a/internal/vm/array_fill_test.go b/internal/vm/array_fill_test.go index 246221b3..2a13b85c 100644 --- a/internal/vm/array_fill_test.go +++ b/internal/vm/array_fill_test.go @@ -4,7 +4,10 @@ package vm_test -import "testing" +import ( + "strconv" + "testing" +) // TestArrayFill covers Array#fill in every MRI form: the no-block filler forms // (obj / obj,start / obj,start,length / obj,range) and the block forms, including @@ -62,6 +65,10 @@ func TestArrayFill(t *testing.T) { // TestArrayFillErrors covers Array#fill's error and boundary branches. func TestArrayFillErrors(t *testing.T) { + fill50 := "failed to allocate memory\n" + if strconv.IntSize == 32 { + fill50 = "argument too big\n" + } cases := []struct{ src, want string }{ // Argument-count errors. {`begin; [].fill; rescue ArgumentError; puts "AE"; end`, "AE\n"}, @@ -84,8 +91,10 @@ func TestArrayFillErrors(t *testing.T) { // `NoMemoryError: failed to allocate memory`, and 1<<40 and 1<<35 are // genuinely materialised (both passed 2 GB resident before the probe's cap // stopped them), so the old bound was wrong in both directions. - {`begin; [1, 2, 3].fill(10, 1, 1 << 50); rescue NoMemoryError => e; puts e.message; end`, - "failed to allocate memory\n"}, + // On a 32-bit target ARY_MAX_SIZE is (2**31-1)/8, below 1<<50, so the + // size test refuses first, as MRI's own does with a 32-bit long. + {`begin; [1, 2, 3].fill(10, 1, 1 << 50); rescue NoMemoryError, ArgumentError => e; puts e.message; end`, + fill50}, // A range whose begin is before the start of the array -> RangeError. {`begin; [1, 2, 3].fill("x", -5..-3); rescue RangeError; puts "RE"; end`, "RE\n"}, // A block raising mid-fill leaves the already-filled elements in place. diff --git a/internal/vm/arrow.go b/internal/vm/arrow.go index 31b96115..bc1d909a 100644 --- a/internal/vm/arrow.go +++ b/internal/vm/arrow.go @@ -246,7 +246,7 @@ func (vm *VM) registerArrowSchema(cls *RClass) { s := self(v).s switch k := args[0].(type) { case object.Integer: - i := int(k) + i := clampInt(k) if i < 0 || i >= s.NumFields() { raise("IndexError", "field index %d out of range (%d fields)", i, s.NumFields()) } @@ -285,7 +285,7 @@ func (vm *VM) registerArrowArray(cls *RClass) { d("[]", func(_ *VM, v object.Value, args []object.Value, _ *Proc) object.Value { arrowArity(args, 1, "[]") - val, err := self(v).a.Get(int(arrowInt(args[0]))) + val, err := self(v).a.Get(clampInt(arrowInt(args[0]))) raiseArrowErr(err) return arrowScalarToRuby(val) }) @@ -303,11 +303,11 @@ func (vm *VM) registerArrowArray(cls *RClass) { }) d("null?", func(_ *VM, v object.Value, args []object.Value, _ *Proc) object.Value { arrowArity(args, 1, "null?") - return object.Bool(self(v).a.NullQ(int(arrowInt(args[0])))) + return object.Bool(self(v).a.NullQ(clampInt(arrowInt(args[0])))) }) d("valid?", func(_ *VM, v object.Value, args []object.Value, _ *Proc) object.Value { arrowArity(args, 1, "valid?") - return object.Bool(self(v).a.ValidQ(int(arrowInt(args[0])))) + return object.Bool(self(v).a.ValidQ(clampInt(arrowInt(args[0])))) }) d("value_data_type", func(_ *VM, v object.Value, _ []object.Value, _ *Proc) object.Value { return &ArrowDataType{dt: self(v).a.DataType()} @@ -404,7 +404,7 @@ func (vm *VM) registerArrowRecordBatch(cls *RClass) { }) d("column", func(_ *VM, v object.Value, args []object.Value, _ *Proc) object.Value { arrowArity(args, 1, "column") - a, err := self(v).r.Column(int(arrowInt(args[0]))) + a, err := self(v).r.Column(clampInt(arrowInt(args[0]))) raiseArrowErr(err) return &ArrowArray{a: a} }) @@ -543,7 +543,7 @@ func arrowInt(v object.Value) int64 { func arrowColumnKey(v object.Value) any { switch k := v.(type) { case object.Integer: - return int(k) + return clampInt(k) case *object.String: return k.Str() case object.Symbol: diff --git a/internal/vm/arrow_internal_test.go b/internal/vm/arrow_internal_test.go index ebf7cc0c..c68305b5 100644 --- a/internal/vm/arrow_internal_test.go +++ b/internal/vm/arrow_internal_test.go @@ -42,7 +42,9 @@ func TestArrowScalarToRuby(t *testing.T) { {uint64(64), "64"}, {uint64(math.MaxInt64) + 1, big64.String()}, {uint(65), "65"}, - {uint(math.MaxInt64) + 1, big64.String()}, + // math.MaxUint is past MaxInt64 on 64-bit targets (the Bignum branch) + // and fits a Fixnum on 32-bit ones, where uint(MaxInt64)+1 overflows. + {uint(math.MaxUint), new(big.Int).SetUint64(uint64(math.MaxUint)).String()}, {float32(1.5), "1.5"}, {float64(2.5), "2.5"}, {"hi", "\"hi\""}, diff --git a/internal/vm/async_bind.go b/internal/vm/async_bind.go index 16cf4309..c07a1a13 100644 --- a/internal/vm/async_bind.go +++ b/internal/vm/async_bind.go @@ -157,7 +157,7 @@ func asyncInt(args []object.Value, def int) int { } switch n := args[0].(type) { case object.Integer: - return int(int64(n)) + return clampInt(int64(n)) case object.Float: return int(float64(n)) } diff --git a/internal/vm/augeas_bind.go b/internal/vm/augeas_bind.go index 5dc8d731..81cd3e00 100644 --- a/internal/vm/augeas_bind.go +++ b/internal/vm/augeas_bind.go @@ -26,7 +26,7 @@ func augStrArg(args []object.Value, i int) string { func augIntArg(args []object.Value, i int) int { if i < len(args) { if n, ok := args[i].(object.Integer); ok { - return int(n) + return clampInt(n) } } return 0 diff --git a/internal/vm/bag.go b/internal/vm/bag.go index cb1ccff4..4c4a53b6 100644 --- a/internal/vm/bag.go +++ b/internal/vm/bag.go @@ -362,7 +362,7 @@ func (vm *VM) registerBag() { // An Integer n caps the result; a missing arg or explicit nil keeps all. if len(args) > 0 { if _, isNil := args[0].(object.Nil); !isNil { - n := int(intArg(args[0])) + n := clampInt(intArg(args[0])) if n < 0 { n = 0 } diff --git a/internal/vm/bcrypt_bind.go b/internal/vm/bcrypt_bind.go index 7aa539dc..a5b173ee 100644 --- a/internal/vm/bcrypt_bind.go +++ b/internal/vm/bcrypt_bind.go @@ -96,7 +96,7 @@ func (vm *VM) registerBCryptPassword(mod *RClass) { var opts []bcrypt.CreateOption if h := bcryptOptsHash(args[1:]); h != nil { if v, ok := h.Get(object.Symbol("cost")); ok { - opts = append(opts, bcrypt.WithCost(int(intArg(v)))) + opts = append(opts, bcrypt.WithCost(clampInt(intArg(v)))) } } p, err := bcrypt.CreateString(strArg(args[0]), opts...) @@ -163,7 +163,7 @@ func (vm *VM) registerBCryptEngine(mod *RClass) { native: func(_ *VM, _ object.Value, args []object.Value, _ *Proc) object.Value { cost := bcrypt.DefaultCost if len(args) > 0 { - cost = int(intArg(args[0])) + cost = clampInt(intArg(args[0])) } salt, err := bcrypt.GenerateSalt(cost) if err != nil { diff --git a/internal/vm/benchmark.go b/internal/vm/benchmark.go index e563dfd6..e445b91c 100644 --- a/internal/vm/benchmark.go +++ b/internal/vm/benchmark.go @@ -141,7 +141,7 @@ func (vm *VM) registerBenchmark() { } width := 0 if len(args) > 1 { - width = int(intArg(args[1])) + width = clampInt(intArg(args[1])) } format := "" if len(args) > 2 { @@ -396,7 +396,7 @@ func labelToS(v object.Value) string { // benchWidth returns the leading integer label-width argument, defaulting to 0. func benchWidth(args []object.Value) int { if len(args) > 0 { - return int(intArg(args[0])) + return clampInt(intArg(args[0])) } return 0 } diff --git a/internal/vm/bigdecimal_bind.go b/internal/vm/bigdecimal_bind.go index 312ebe71..f991ce2d 100644 --- a/internal/vm/bigdecimal_bind.go +++ b/internal/vm/bigdecimal_bind.go @@ -189,7 +189,7 @@ func roundFamily(d *bigdecimal.Decimal, args []object.Value, fn func(n int) *big if len(args) == 0 { return decimalToInteger(fn(0)) } - return &BigDecimal{d: fn(int(intArg(args[0])))} + return &BigDecimal{d: fn(clampInt(intArg(args[0])))} } // registerBigDecimal installs Kernel#BigDecimal, the BigDecimal instance methods @@ -218,7 +218,7 @@ func (vm *VM) registerBigDecimal() { if len(args) < 2 { return raise("ArgumentError", "can't omit precision for a Float") } - d, err := bigdecimal.FromFloat(float64(v), int(intArg(args[1]))) + d, err := bigdecimal.FromFloat(float64(v), clampInt(intArg(args[1]))) if err != nil { return raise("ArgumentError", "%s", err.Error()) } @@ -272,7 +272,7 @@ func (vm *VM) registerBigDecimal() { // ordinary method send. The exponent must be an Integer (intArg raises // TypeError otherwise). powFn := func(_ *VM, v object.Value, args []object.Value, _ *Proc) object.Value { - return &BigDecimal{d: self(v).d.Pow(int(intArg(args[0])))} + return &BigDecimal{d: self(v).d.Pow(clampInt(intArg(args[0])))} } d("**", powFn) d("power", powFn) // MRI's named form @@ -284,7 +284,7 @@ func (vm *VM) registerBigDecimal() { d("div", func(_ *VM, v object.Value, args []object.Value, _ *Proc) object.Value { o := bigDecimalArg(args[0]) if len(args) >= 2 { - return &BigDecimal{d: divChecked(self(v).d, o, int(intArg(args[1])))} + return &BigDecimal{d: divChecked(self(v).d, o, clampInt(intArg(args[1])))} } q, err := self(v).d.IDiv(o) if err != nil { @@ -333,7 +333,7 @@ func (vm *VM) registerBigDecimal() { if len(args) == 0 { return decimalToInteger(self(v).d.Round(0, bigdecimal.RoundHalfUp)) } - n := int(intArg(args[0])) + n := clampInt(intArg(args[0])) mode := bigdecimal.RoundHalfUp hasMode := len(args) >= 2 if hasMode { diff --git a/internal/vm/bleve.go b/internal/vm/bleve.go index bfb67af9..aa3b81c8 100644 --- a/internal/vm/bleve.go +++ b/internal/vm/bleve.go @@ -367,7 +367,7 @@ func (vm *VM) registerBleveQuery(cls *RClass) { d("fuzziness", func(_ *VM, v object.Value, args []object.Value, _ *Proc) object.Value { bleveArity(args, 1, "fuzziness") q := self(v) - q.q = q.q.Fuzziness(int(intArg(args[0]))) + q.q = q.q.Fuzziness(clampInt(intArg(args[0]))) return v }) } @@ -437,7 +437,7 @@ func (vm *VM) registerBleveHit(cls *RClass) { func (vm *VM) registerBleveFacet(cls *RClass) { bleveSMethod(cls, "term", func(_ *VM, _ object.Value, args []object.Value, _ *Proc) object.Value { bleveArity(args, 2, "term") - return &BleveFacet{cls: cls, f: libbleve.TermFacet(strArg(args[0]), int(intArg(args[1])))} + return &BleveFacet{cls: cls, f: libbleve.TermFacet(strArg(args[0]), clampInt(intArg(args[1])))} }) self := func(v object.Value) *BleveFacet { return v.(*BleveFacet) } diff --git a/internal/vm/bleve_bind.go b/internal/vm/bleve_bind.go index 48bafef9..c9a4c42f 100644 --- a/internal/vm/bleve_bind.go +++ b/internal/vm/bleve_bind.go @@ -240,10 +240,10 @@ func bleveSearchOpts(rest []object.Value) []libbleve.SearchOption { } var opts []libbleve.SearchOption if v, ok := h.Get(object.Symbol("size")); ok { - opts = append(opts, libbleve.Size(int(intArg(v)))) + opts = append(opts, libbleve.Size(clampInt(intArg(v)))) } if v, ok := h.Get(object.Symbol("from")); ok { - opts = append(opts, libbleve.From(int(intArg(v)))) + opts = append(opts, libbleve.From(clampInt(intArg(v)))) } if v, ok := h.Get(object.Symbol("fields")); ok { opts = append(opts, libbleve.Fields(bleveStringList(v)...)) diff --git a/internal/vm/builder_bind.go b/internal/vm/builder_bind.go index 75c69e8c..d431734e 100644 --- a/internal/vm/builder_bind.go +++ b/internal/vm/builder_bind.go @@ -33,9 +33,9 @@ func builderOptions(args []object.Value) []xmlbuilder.Option { val, _ := h.Get(k) switch builderName(k) { case "indent": - opts = append(opts, xmlbuilder.WithIndent(int(intArg(val)))) + opts = append(opts, xmlbuilder.WithIndent(clampInt(intArg(val)))) case "margin": - opts = append(opts, xmlbuilder.WithMargin(int(intArg(val)))) + opts = append(opts, xmlbuilder.WithMargin(clampInt(intArg(val)))) } } } diff --git a/internal/vm/builtins.go b/internal/vm/builtins.go index e1437d0d..f3958161 100644 --- a/internal/vm/builtins.go +++ b/internal/vm/builtins.go @@ -214,7 +214,7 @@ func (vm *VM) bootstrap() { need = -need - 1 } if len(args) > 0 { - need = int(intArg(args[0])) + need = clampInt(intArg(args[0])) // A lambda enforces its arity, so currying it to an arity it cannot // satisfy — fewer than its required positionals, or more than it will // accept when it has no splat — raises, exactly as calling it would. A @@ -1102,7 +1102,7 @@ func (vm *VM) bootstrap() { if hasBase { // The base itself is coerced with #to_int, and a valid radix is 0 // (auto-detect) or 2..36 — anything else is MRI's "invalid radix". - base = int(vm.toIntCoerce(args[1])) + base = clampInt(vm.toIntCoerce(args[1])) if base != 0 && (base < 2 || base > 36) { return fail("ArgumentError", "invalid radix %d", base) } @@ -1810,7 +1810,7 @@ func (vm *VM) bootstrap() { classCmpOp := func(want func(int) bool) NativeFn { return func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { if c, ok := classCmp(self, args[0]).(object.Integer); ok { - return object.Bool(want(int(c))) + return object.Bool(want(clampInt(c))) } return object.NilV } @@ -2286,7 +2286,7 @@ func (vm *VM) bootstrap() { vm.cString.defineArgc("sum", -1, func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { bits := 16 if len(args) > 0 { - bits = int(toInt(args[0])) + bits = clampInt(toInt(args[0])) } return object.IntValue(stringSum(strOf(self), bits)) }) @@ -2579,7 +2579,7 @@ func (vm *VM) bootstrap() { nChars := utf8.RuneCountInString(s) start := 0 if len(args) > 1 { // optional character offset (negative counts from the end) - start = int(vm.repeatLong(args[1])) + start = clampInt(vm.repeatLong(args[1])) if start < 0 { start += nChars } @@ -2617,7 +2617,7 @@ func (vm *VM) bootstrap() { nChars := utf8.RuneCountInString(s) limit := nChars if len(args) > 1 { // optional character offset (negative counts from the end) - limit = int(vm.repeatLong(args[1])) + limit = clampInt(vm.repeatLong(args[1])) if limit < 0 { limit += nChars } @@ -2692,7 +2692,7 @@ func (vm *VM) bootstrap() { if len(args) > 0 { // The base is coerced through #to_int (MRI's rb_num2long), so a Float // or a #to_int object works and a non-integer raises TypeError. - base = int(vm.integerArg(args[0]).Int64()) + base = clampInt(vm.integerArg(args[0]).Int64()) } return stringToInt(strOf(self), base) }) @@ -2990,7 +2990,7 @@ func (vm *VM) bootstrap() { ins := []rune(insStr) r := []rune(s.Str()) idx := vm.repeatLong(args[0]) - at := int(idx) + at := clampInt(idx) if at < 0 { at += len(r) + 1 } @@ -3211,7 +3211,7 @@ func (vm *VM) bootstrap() { rng = vm.coerceRangeBounds(rng) beg := 0 if _, isNil := rng.Lo.(object.Nil); !isNil { - beg = int(intArg(rng.Lo)) + beg = clampInt(intArg(rng.Lo)) if beg < 0 { if beg += len(a); beg < 0 { raise("RangeError", "%s out of range", rng.Inspect()) @@ -3220,7 +3220,7 @@ func (vm *VM) bootstrap() { } end := len(a) if _, isNil := rng.Hi.(object.Nil); !isNil { - end = int(intArg(rng.Hi)) + end = clampInt(intArg(rng.Hi)) if end < 0 { end += len(a) } @@ -3233,7 +3233,7 @@ func (vm *VM) bootstrap() { } continue } - appendAt(int(vm.repeatLong(idxV))) + appendAt(clampInt(vm.repeatLong(idxV))) } return object.NewArrayFromSlice(out) }) @@ -3372,7 +3372,7 @@ func (vm *VM) bootstrap() { raise("ArgumentError", "wrong number of arguments (given %d, expected 0..1)", len(args)) } if len(args) > 0 { // pop(n) removes and returns the last n as an array - n := int(vm.toIntCoerce(args[0])) + n := clampInt(vm.toIntCoerce(args[0])) if n < 0 { raise("ArgumentError", "negative array size") } @@ -3398,7 +3398,7 @@ func (vm *VM) bootstrap() { raise("ArgumentError", "wrong number of arguments (given %d, expected 0..1)", len(args)) } if len(args) > 0 { // shift(n) removes and returns the first n as an array - n := int(vm.toIntCoerce(args[0])) + n := clampInt(vm.toIntCoerce(args[0])) if n < 0 { raise("ArgumentError", "negative array size") } @@ -3440,7 +3440,7 @@ func (vm *VM) bootstrap() { raise("ArgumentError", "wrong number of arguments (given 0, expected 1+)") } raw := vm.repeatLong(args[0]) - idx := int(raw) + idx := clampInt(raw) ins := args[1:] if len(ins) == 0 { return a @@ -3541,7 +3541,7 @@ func (vm *VM) bootstrap() { if n := len(a.Elems); n > 0 { k := 1 if len(args) > 0 { - k = int(vm.repeatLong(args[0])) + k = clampInt(vm.repeatLong(args[0])) } k = ((k % n) + n) % n a.Elems = append(append([]object.Value{}, a.Elems[k:]...), a.Elems[:k]...) @@ -3705,7 +3705,7 @@ func (vm *VM) bootstrap() { } return a.Elems[vm.drawIndex(rng, n)] } - count := int(vm.repeatLong(pos[0])) + count := clampInt(vm.repeatLong(pos[0])) if count < 0 { raise("ArgumentError", "negative sample number") } @@ -3739,7 +3739,7 @@ func (vm *VM) bootstrap() { if len(args) == 3 { raw := vm.repeatLong(args[0]) start := normIndex(raw, len(a.Elems)) - length := int(vm.repeatLong(args[1])) + length := clampInt(vm.repeatLong(args[1])) if start < 0 { raise("IndexError", "index %d too small for array; minimum: -%d", raw, len(a.Elems)) } @@ -3982,14 +3982,14 @@ func (vm *VM) bootstrap() { vm.cArray.defineArgc("flatten", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { depth := -1 if len(args) > 0 && !object.IsNil(args[0]) { - depth = int(vm.repeatLong(args[0])) // #to_int coercion (a nil depth is unbounded) + depth = clampInt(vm.repeatLong(args[0])) // #to_int coercion (a nil depth is unbounded) } return object.NewArrayFromSlice(vm.flattenDepth(self.(*object.Array).Elems, depth)) }) vm.cArray.defineArgc("flatten!", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { depth := -1 if len(args) > 0 && !object.IsNil(args[0]) { - depth = int(vm.repeatLong(args[0])) // #to_int coercion (a nil depth is unbounded) + depth = clampInt(vm.repeatLong(args[0])) // #to_int coercion (a nil depth is unbounded) } a := self.(*object.Array) vm.checkArrayFrozen(a) @@ -4042,7 +4042,7 @@ func (vm *VM) bootstrap() { if blk == nil { return enumFor(self, "each_slice", args...) } - n := int(intArg(args[0])) + n := clampInt(intArg(args[0])) if n <= 0 { raise("ArgumentError", "invalid slice size") } @@ -4065,7 +4065,7 @@ func (vm *VM) bootstrap() { if blk == nil { return enumFor(self, "each_cons", args...) } - n := int(intArg(args[0])) + n := clampInt(intArg(args[0])) if n <= 0 { raise("ArgumentError", "invalid size") } @@ -4167,7 +4167,7 @@ func (vm *VM) bootstrap() { return object.NewArrayFromSlice(out) }) vm.cArray.defineArgc("combination", 1, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { - k := int(intArg(args[0])) + k := clampInt(intArg(args[0])) elems := self.(*object.Array).Elems var combos []object.Value if k >= 0 && k <= len(elems) { @@ -4211,7 +4211,7 @@ func (vm *VM) bootstrap() { n := len(self.(*object.Array).Elems) kk := n if len(args) > 0 { - kk = int(vm.toIntCoerce(args[0])) + kk = clampInt(vm.toIntCoerce(args[0])) } return object.NormInt(permutationCountBig(n, kk)) }, args...) @@ -4219,7 +4219,7 @@ func (vm *VM) bootstrap() { elems := self.(*object.Array).Elems k := len(elems) if len(args) > 0 { - k = int(vm.toIntCoerce(args[0])) + k = clampInt(vm.toIntCoerce(args[0])) } var perms []object.Value if k >= 0 && k <= len(elems) { @@ -4251,12 +4251,12 @@ func (vm *VM) bootstrap() { return self }) vm.cArray.defineArgc("repeated_combination", 1, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { - k := int(vm.repeatLong(args[0])) + k := clampInt(vm.repeatLong(args[0])) // No block: a lazy Enumerator that re-reads self when iterated (MRI sees // later mutations of the array), with an explicit combinatorial #size. if blk == nil { return enumForSized(self, "repeated_combination", func(vm *VM) object.Value { - kk := int(vm.repeatLong(args[0])) + kk := clampInt(vm.repeatLong(args[0])) if kk < 0 { return object.IntValue(0) } @@ -4296,10 +4296,10 @@ func (vm *VM) bootstrap() { return self }) vm.cArray.defineArgc("repeated_permutation", 1, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { - k := int(vm.repeatLong(args[0])) + k := clampInt(vm.repeatLong(args[0])) if blk == nil { return enumForSized(self, "repeated_permutation", func(vm *VM) object.Value { - kk := int(vm.repeatLong(args[0])) + kk := clampInt(vm.repeatLong(args[0])) if kk < 0 { return object.IntValue(0) } @@ -4370,7 +4370,7 @@ func (vm *VM) bootstrap() { } shift := 1 if len(args) > 0 { - shift = int(vm.repeatLong(args[0])) + shift = clampInt(vm.repeatLong(args[0])) } shift = ((shift % n) + n) % n out := make([]object.Value, n) @@ -4471,7 +4471,7 @@ func (vm *VM) bootstrap() { }) vm.cArray.defineArgc("take", 1, func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { a := self.(*object.Array) - n := int(intArg(args[0])) + n := clampInt(intArg(args[0])) if n < 0 { raise("ArgumentError", "attempt to take negative size") } @@ -4484,7 +4484,7 @@ func (vm *VM) bootstrap() { }) vm.cArray.defineArgc("drop", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { a := self.(*object.Array) - n := int(vm.toIntCoerce(args[0])) + n := clampInt(vm.toIntCoerce(args[0])) if n < 0 { raise("ArgumentError", "attempt to drop negative size") } @@ -4635,14 +4635,14 @@ func (vm *VM) bootstrap() { vm.cArray.defineArgc("min_by", -1, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { a := self.(*object.Array) if blk != nil && len(args) > 0 && !object.IsNil(args[0]) { - return vm.arrayByExtremeN(a, blk, int(coerceInt(vm, args[0])), -1) + return vm.arrayByExtremeN(a, blk, clampInt(coerceInt(vm, args[0])), -1) } return vm.arrayByExtreme(a, blk, "min_by", -1) }) vm.cArray.defineArgc("max_by", -1, func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { a := self.(*object.Array) if blk != nil && len(args) > 0 && !object.IsNil(args[0]) { - return vm.arrayByExtremeN(a, blk, int(coerceInt(vm, args[0])), 1) + return vm.arrayByExtremeN(a, blk, clampInt(coerceInt(vm, args[0])), 1) } return vm.arrayByExtreme(a, blk, "max_by", 1) }) @@ -5498,7 +5498,7 @@ func (vm *VM) bootstrap() { vm.cHash.defineArgc("flatten", -1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { depth := 1 if len(args) > 0 { - depth = int(intArg(args[0])) + depth = clampInt(intArg(args[0])) } h := self.(*object.Hash) pairs := make([]object.Value, 0, len(h.Keys)) @@ -5598,7 +5598,7 @@ func (vm *VM) bootstrap() { } // The count is coerced with #to_int (so a Float truncates and a mock_int // works); a negative count is an error, like Array#first. - n := int(vm.toIntCoerce(args[0])) + n := clampInt(vm.toIntCoerce(args[0])) if n < 0 { raise("ArgumentError", "negative array size") } @@ -5687,7 +5687,7 @@ func (vm *VM) bootstrap() { if object.IsNil(r.Lo) { raise("RangeError", "cannot get the minimum of beginless range") } - n := int(vm.toIntCoerce(args[0])) + n := clampInt(vm.toIntCoerce(args[0])) if n < 0 { raise("ArgumentError", "negative array size") } @@ -5802,7 +5802,7 @@ func (vm *VM) bootstrap() { // full materialised range, so it is bounded only. vm.cRange.defineArgc("take", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { r := self.(*object.Range) - n := int(intArg(args[0])) + n := clampInt(intArg(args[0])) if n < 0 { raise("ArgumentError", "attempt to take negative size") } @@ -5825,7 +5825,7 @@ func (vm *VM) bootstrap() { }) vm.cRange.defineArgc("drop", 1, func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { elems := vm.rangeElemsV(self.(*object.Range)) - n := int(intArg(args[0])) + n := clampInt(intArg(args[0])) if n < 0 { raise("ArgumentError", "attempt to drop negative size") } @@ -5955,7 +5955,7 @@ func (vm *VM) bootstrap() { } } elems := strRangeElems(loS.Str(), r.Hi.(*object.String).Str(), r.Exclusive) - for i := 0; i < len(elems); i += int(n) { + for i := 0; i < len(elems); i += clampInt(n) { vm.callBlock(blk, []object.Value{elems[i]}) } return r @@ -6045,7 +6045,7 @@ func (vm *VM) bootstrap() { // (and `rb_usascii_str_new2("0")` for zero), and bignum.c's rb_big2str does // the same, so every Integer#to_s is US-ASCII — core/integer/to_s_spec // asserts it. rbgo tagged the digits UTF-8. - return object.NewStringBytesEnc([]byte(bigVal(self).Text(int(base))), "US-ASCII") + return object.NewStringBytesEnc([]byte(bigVal(self).Text(clampInt(base))), "US-ASCII") }) // Bitwise / shift operators (arbitrary precision via big.Int, so a left shift // promotes to a Bignum and bitwise ops work on Bignums too). @@ -6824,7 +6824,7 @@ func absSign(a int64) (int64, bool) { func floatRound(f float64, args []object.Value, fn func(float64) float64) object.Value { ndigits := 0 if len(args) > 0 { - ndigits = int(intArg(args[0])) + ndigits = clampInt(intArg(args[0])) } pow := math.Pow(10, float64(ndigits)) r := fn(f*pow) / pow @@ -6866,7 +6866,7 @@ func clampCount(n int64, max int) int { if n > int64(max) { return max } - return int(n) + return clampInt(n) } // arrayIndex normalizes a (possibly negative) index and reports whether it is in @@ -6879,7 +6879,7 @@ func arrayIndex(a *object.Array, i int64) (int, bool) { if i < 0 || i >= n { return 0, false } - return int(i), true + return clampInt(i), true } // arrayArefSpan resolves Array#[]/#slice/#slice! arguments to a location in a. @@ -7018,7 +7018,7 @@ func (vm *VM) drawIndex(rng object.Value, bound int) int { if idx < 0 || idx >= int64(bound) { raise("RangeError", "random number too big %d", idx) } - return int(idx) + return clampInt(idx) } // fisherYates shuffles s in place using rng (nil = the default generator), @@ -7044,7 +7044,7 @@ func (vm *VM) arrayArefSpan(a *object.Array, args []object.Value) (start, length } if len(args) == 2 { // a[start, len] — start and length convert via #to_int. s := normIndex(vm.arrayArefLong(args[0]), len(a.Elems)) - l := int(vm.arrayArefLong(args[1])) + l := clampInt(vm.arrayArefLong(args[1])) if s < 0 || s > len(a.Elems) || l < 0 { return 0, 0, true, false } @@ -7083,7 +7083,7 @@ func (vm *VM) arrayArefLong(v object.Value) int64 { // span extends past the array, MRI raises RangeError. Verified against MRI 4.0.6. func (vm *VM) arrayArefArithSeq(a *object.Array, e *Enumerator) object.Value { n := len(a.Elems) - step := int(vm.repeatLong(e.asStep)) + step := clampInt(vm.repeatLong(e.asStep)) if step == 0 { // A fractional step (e.g. .step(0.5)) truncates to 0; MRI rejects it here. raise("ArgumentError", "slice step cannot be zero") @@ -7883,7 +7883,7 @@ func stringIndexEnc(s string, args []object.Value, binary bool) object.Value { if start < 0 || start > n || length < 0 { return object.NilV } - end := start + int(length) + end := start + clampInt(length) if end > n { end = n } @@ -7930,7 +7930,7 @@ func byteslice(vm *VM, self *object.String, args []object.Value) object.Value { if start < 0 || start > n || length < 0 { return object.NilV } - end := start + int(length) + end := start + clampInt(length) if end > n { end = n } @@ -8049,9 +8049,9 @@ func (vm *VM) arraySpliceAssign(a *object.Array, start, length int, value object // the upper bound; callers range-check). func normIndex(i int64, n int) int { if i < 0 { - return int(i) + n + return clampInt(i) + n } - return int(i) + return clampInt(i) } // affixString coerces a String#delete_prefix/#delete_suffix argument to a @@ -8486,7 +8486,7 @@ func (vm *VM) stringAssignRegexp(s *object.String, re *Regexp, groupArgs []objec vm.lastMatch = m gi := 0 if len(groupArgs) > 0 { - gi = int(vm.repeatLong(groupArgs[0])) + gi = clampInt(vm.repeatLong(groupArgs[0])) ng := md.NGroups() if gi < 0 { gi += ng + 1 @@ -8528,7 +8528,7 @@ func (vm *VM) stringAssignSpan(args []object.Value, n int) (start, length int) { if len(args) == 3 { idx := vm.repeatLong(args[0]) // #to_int is invoked exactly once start = normIndex(idx, n) - length = int(vm.repeatLong(args[1])) + length = clampInt(vm.repeatLong(args[1])) if start < 0 || start > n { raise("IndexError", "index %d out of string", idx) } @@ -8619,7 +8619,7 @@ func (vm *VM) stringSliceBangRegexp(s *object.String, re *Regexp, rest []object. if len(rest) > 0 { // The capture index converts via #to_int; a negative index counts back // from the last group; an out-of-range index yields nil (not IndexError). - gi = int(vm.repeatLong(rest[0])) + gi = clampInt(vm.repeatLong(rest[0])) ng := md.NGroups() if gi < 0 { gi += ng + 1 @@ -8661,7 +8661,7 @@ func (vm *VM) stringSliceBangSubstr(s *object.String, sub string) object.Value { func (vm *VM) sliceSpan(args []object.Value, n int) (start, length int, ok bool) { if len(args) == 2 { start = normIndex(vm.repeatLong(args[0]), n) - length = int(vm.repeatLong(args[1])) + length = clampInt(vm.repeatLong(args[1])) if start < 0 || start > n || length < 0 { return 0, 0, false } @@ -8969,7 +8969,7 @@ func (vm *VM) spaceship(a, b object.Value) int { if !ok { raise("ArgumentError", "comparison of %s with %s failed", vm.classOf(a).name, vm.classOf(b).name) } - return int(n) + return clampInt(n) } // sortSlice stably sorts out in place: by <=> when blk is nil, otherwise using @@ -9008,7 +9008,7 @@ func (vm *VM) blockCmpToZero(r object.Value) int { if !ok { raise("ArgumentError", "comparison of %s with 0 failed", vm.classOf(r).name) } - return int(n) + return clampInt(n) } // arrayByExtreme implements min_by/max_by: the element whose block key is @@ -9266,7 +9266,7 @@ func arrayFill(vm *VM, self object.Value, args []object.Value, blk *Proc) object // start / length form. A nil (or absent) start is 0; a nil (or absent) length // fills through to the current end. if len(rest) >= 1 && !object.IsNil(rest[0]) { - beg = int(vm.toIntCoerce(rest[0])) + beg = clampInt(vm.toIntCoerce(rest[0])) if beg < 0 { if beg += alen; beg < 0 { beg = 0 @@ -9275,7 +9275,11 @@ func arrayFill(vm *VM, self object.Value, args []object.Value, blk *Proc) object } if len(rest) >= 2 && !object.IsNil(rest[1]) { length := vm.fillLength(rest[1]) - end = beg + length + // beg + length can pass math.MaxInt and wrap negative (on a 32-bit target + // at 2**31), which would skip arrayFillRange's size check and fill + // nothing. Each term is first capped just past maxFillSize, which keeps + // the int64 sum exact enough: any end past maxFillSize is refused alike. + end = clampInt(min(int64(beg), maxFillSize+1) + min(int64(length), maxFillSize+1)) } else { end = alen } @@ -9289,7 +9293,7 @@ func (vm *VM) fillLength(v object.Value) int { if _, ok := v.(*object.Bignum); ok { raise("RangeError", "array size too big") } - return int(vm.toIntCoerce(v)) + return clampInt(vm.toIntCoerce(v)) } // fillRangeBounds turns a Range into the exclusive [beg, end) index interval for @@ -9300,7 +9304,7 @@ func (vm *VM) fillRangeBounds(r *object.Range, alen int) (beg, end int) { if object.IsNil(r.Lo) { beg = 0 } else { - beg = int(vm.toIntCoerce(r.Lo)) + beg = clampInt(vm.toIntCoerce(r.Lo)) if beg < 0 { if beg += alen; beg < 0 { raise("RangeError", "%s out of range", r.Inspect()) @@ -9310,7 +9314,7 @@ func (vm *VM) fillRangeBounds(r *object.Range, alen int) (beg, end int) { if object.IsNil(r.Hi) { return beg, alen } - end = int(vm.toIntCoerce(r.Hi)) + end = clampInt(vm.toIntCoerce(r.Hi)) if end < 0 { end += alen } @@ -10740,7 +10744,7 @@ func (vm *VM) digRest(v object.Value, keys []object.Value) object.Value { // string (default " ") to a rune width. Extra padding for center goes right. func (vm *VM) padString(self *object.String, args []object.Value, side byte) (string, string) { s := self.Str() - width := int(vm.repeatLong(args[0])) // #to_int coercion of the width + width := clampInt(vm.repeatLong(args[0])) // #to_int coercion of the width pad := " " enc := self.EncName() if len(args) > 1 { @@ -10772,6 +10776,7 @@ func (vm *VM) padString(self *object.String, args []object.Value, side byte) (st // makePad builds n runes from the (non-empty) pad string, repeating/truncating. func makePad(pad string, n int) string { + checkAlloc(int64(n), 4) // a []rune of n runes := []rune(pad) out := make([]rune, n) for i := 0; i < n; i++ { @@ -11449,9 +11454,15 @@ func (vm *VM) rangeStepByPlus(blk *Proc, r *object.Range, step object.Value) { func shiftInt(a *big.Int, n int64) object.Value { if n >= 0 { + // The result needs n/8 bytes. Checking n/2 instead keeps n within a + // 32-bit uint wherever the check passes (hostAllocMax is 2**30 there), + // so uint(n) cannot wrap: 1 << 2**62 used to shift by 0 on 386. + checkAlloc(n/2, 1) return object.NormInt(new(big.Int).Lsh(a, uint(n))) } - return object.NormInt(new(big.Int).Rsh(a, uint(-n))) + // Past a's bit length every right shift gives the same 0 or -1, so the + // amount is capped there: uint(-n) would wrap on a 32-bit target. + return object.NormInt(new(big.Int).Rsh(a, uint(min(-n, int64(a.BitLen())+1)))) } // integerShift implements Integer#<< (right=false) and Integer#>> (right=true). diff --git a/internal/vm/capistrano.go b/internal/vm/capistrano.go index 0c1a9552..30899e44 100644 --- a/internal/vm/capistrano.go +++ b/internal/vm/capistrano.go @@ -496,7 +496,7 @@ func capCommandResult(h *object.Hash) capistrano.CommandResult { } if v, ok := h.Get(object.SymVal("exit_status")); ok { if n, ok := v.(object.Integer); ok { - res.ExitStatus = int(n) + res.ExitStatus = clampInt(n) } } return res diff --git a/internal/vm/clampint.go b/internal/vm/clampint.go new file mode 100644 index 00000000..0eadc26c --- /dev/null +++ b/internal/vm/clampint.go @@ -0,0 +1,35 @@ +// Copyright (c) the go-embedded-ruby/ruby authors +// +// SPDX-License-Identifier: BSD-3-Clause + +package vm + +import "math" + +// clampInt converts a 64-bit integer (an Integer's Fixnum payload, a count, an +// offset) to a Go int. +// +// On 64-bit targets it is exactly int(x): the bounds are math.MinInt64 and +// math.MaxInt64, which no int64 crosses, so the compiler proves both +// comparisons false and keeps only the conversion. +// +// On 32-bit targets (386, arm) int(x) would wrap silently: [1, 2][2**32] would +// read index 0, and Integer("10", 2**32 + 2) would accept radix 2. clampInt +// saturates instead, to math.MaxInt or math.MinInt, so a value outside int's +// range stays outside every slice's bounds and every validity range, and the +// caller's own check answers what it answers on 64-bit (nil, IndexError, +// ArgumentError, ...). +func clampInt[T ~int64](x T) int { + return int(saturate(int64(x), math.MinInt, math.MaxInt)) +} + +// saturate limits x to [lo, hi]. +func saturate(x, lo, hi int64) int64 { + if x > hi { + return hi + } + if x < lo { + return lo + } + return x +} diff --git a/internal/vm/clampint_internal_test.go b/internal/vm/clampint_internal_test.go new file mode 100644 index 00000000..f0e85197 --- /dev/null +++ b/internal/vm/clampint_internal_test.go @@ -0,0 +1,55 @@ +// Copyright (c) the go-embedded-ruby/ruby authors +// +// SPDX-License-Identifier: BSD-3-Clause + +package vm + +import ( + "math" + "strconv" + "testing" + + "github.com/go-embedded-ruby/ruby/internal/object" +) + +func TestSaturate(t *testing.T) { + for _, c := range []struct{ x, lo, hi, want int64 }{ + {5, 0, 10, 5}, + {-1, 0, 10, 0}, + {11, 0, 10, 10}, + {math.MaxInt64, math.MinInt32, math.MaxInt32, math.MaxInt32}, + {math.MinInt64, math.MinInt32, math.MaxInt32, math.MinInt32}, + {math.MaxInt64, math.MinInt64, math.MaxInt64, math.MaxInt64}, + } { + if got := saturate(c.x, c.lo, c.hi); got != c.want { + t.Errorf("saturate(%d, %d, %d) = %d; want %d", c.x, c.lo, c.hi, got, c.want) + } + } +} + +func TestClampInt(t *testing.T) { + // In range: the value itself, on every target, for int64 and named types. + for _, x := range []int64{0, 1, -1, math.MaxInt32, math.MinInt32} { + if got := clampInt(x); int64(got) != x { + t.Errorf("clampInt(%d) = %d", x, got) + } + if got := clampInt(object.Integer(x)); int64(got) != x { + t.Errorf("clampInt(Integer(%d)) = %d", x, got) + } + } + big, small := int64(1)<<40, -(int64(1) << 40) + if strconv.IntSize == 64 { + // 64-bit: exactly int(x). + if clampInt(big) != int(big) || clampInt(small) != int(small) { + t.Errorf("clampInt is not the identity on a 64-bit target") + } + return + } + // 32-bit: saturates where int(x) would wrap (int(1<<40) is 0 there). + if got := clampInt(big); got != math.MaxInt { + t.Errorf("clampInt(1<<40) = %d; want math.MaxInt", got) + } + if got := clampInt(small); got != math.MinInt { + t.Errorf("clampInt(-1<<40) = %d; want math.MinInt", got) + } +} diff --git a/internal/vm/concurrentruby_bind.go b/internal/vm/concurrentruby_bind.go index 3850934e..0f98b217 100644 --- a/internal/vm/concurrentruby_bind.go +++ b/internal/vm/concurrentruby_bind.go @@ -253,9 +253,9 @@ func (o *ConcurrentBarrier) Truthy() bool { return true } func concurrentInt(v object.Value) int { switch n := v.(type) { case object.Integer: - return int(n) + return clampInt(n) case *object.Bignum: - return int(n.I.Int64()) + return clampInt(n.I.Int64()) } raise("TypeError", "expected an Integer, got %s", v.Inspect()) panic("unreachable") diff --git a/internal/vm/connectionpool_bind.go b/internal/vm/connectionpool_bind.go index acc7eb53..458b9e6a 100644 --- a/internal/vm/connectionpool_bind.go +++ b/internal/vm/connectionpool_bind.go @@ -83,7 +83,7 @@ func (vm *VM) cpNewPool(cls *RClass, args []object.Value, blk *Proc) *RConnectio timeout := 5 * time.Second if h := cpOptions(args); h != nil { if v, ok := h.Get(object.Symbol("size")); ok { - size = int(intArg(v)) + size = clampInt(intArg(v)) } if v, ok := h.Get(object.Symbol("timeout")); ok { timeout = cpDuration(v) diff --git a/internal/vm/converter_impl.go b/internal/vm/converter_impl.go index 443b5627..1f07804c 100644 --- a/internal/vm/converter_impl.go +++ b/internal/vm/converter_impl.go @@ -390,14 +390,14 @@ func (vm *VM) primitiveConvert(c *converterObj, args []object.Value) object.Valu offset := len(dst.Bytes()) if len(pos) > 2 && pos[2] != object.NilV { - offset = int(vm.toIntCoerce(pos[2])) + offset = clampInt(vm.toIntCoerce(pos[2])) } if offset > len(dst.Bytes()) { raise("ArgumentError", "too big destination byte offset") } size := -1 if len(pos) > 3 && pos[3] != object.NilV { - size = int(vm.toIntCoerce(pos[3])) + size = clampInt(vm.toIntCoerce(pos[3])) } partial := false if opts != nil { @@ -611,7 +611,7 @@ func (c *converterObj) invalidMessage(errBytes, readAgain []byte) string { func (vm *VM) converterPutback(c *converterObj, args []object.Value) object.Value { n := len(c.readAgain) if len(args) > 0 && args[0] != object.NilV { - if k := int(vm.toIntCoerce(args[0])); k < n { + if k := clampInt(vm.toIntCoerce(args[0])); k < n { n = k } } diff --git a/internal/vm/csv.go b/internal/vm/csv.go index 054c1027..fdbf4f9f 100644 --- a/internal/vm/csv.go +++ b/internal/vm/csv.go @@ -559,7 +559,7 @@ func (vm *VM) registerCSVRowClass(cls *RClass) { idx := func(vm *VM, v object.Value, args []object.Value, _ *Proc) object.Value { r := rowOf(v) if n, ok := args[0].(object.Integer); ok { - if val, ok := r.At(int(n)); ok { + if val, ok := r.At(clampInt(n)); ok { return vm.csvFieldToRuby(val) } return object.NilV @@ -680,7 +680,7 @@ func (vm *VM) registerCSVTableClass(cls *RClass) { return vm.csvHeadersArray(tblOf(v).Headers) }) d("[]", func(vm *VM, v object.Value, args []object.Value, _ *Proc) object.Value { - if r, ok := tblOf(v).Row(int(intArg(args[0]))); ok { + if r, ok := tblOf(v).Row(clampInt(intArg(args[0]))); ok { return &CSVRow{r: r} } return object.NilV diff --git a/internal/vm/date_bind.go b/internal/vm/date_bind.go index e26959a0..f0773a44 100644 --- a/internal/vm/date_bind.go +++ b/internal/vm/date_bind.go @@ -100,7 +100,7 @@ func dateArg(v object.Value) *Date { // Float is rejected) — the day/month-offset counterpart of intArg. func dateDays(v object.Value) int { if n, ok := v.(object.Integer); ok { - return int(n) + return clampInt(n) } raise("TypeError", "no implicit conversion of %s into Integer", v.Inspect()) return 0 @@ -174,7 +174,7 @@ func floorDivDay(ns int64) int { if ns%dateNsPerDay != 0 && ns < 0 { q-- } - return int(q) + return clampInt(q) } // formatZone renders a UTC offset in seconds as MRI's "+HH:MM" / "-HH:MM" zone @@ -192,13 +192,13 @@ func formatZone(secs int) string { // i of a DateTime class-method's arguments, each defaulting to 0. func dtTimeArgs(args []object.Value, i int) (h, mi, s int) { if len(args) > i { - h = int(intArg(args[i])) + h = clampInt(intArg(args[i])) } if len(args) > i+1 { - mi = int(intArg(args[i+1])) + mi = clampInt(intArg(args[i+1])) } if len(args) > i+2 { - s = int(intArg(args[i+2])) + s = clampInt(intArg(args[i+2])) } return } @@ -234,7 +234,7 @@ func offsetSeconds(v object.Value) int { } return secs case object.Integer: - return int(int64(x) * 86400) + return clampInt(int64(x) * 86400) case object.Float: return int(float64(x) * 86400) case *object.Rational: @@ -354,13 +354,13 @@ func (vm *VM) registerDateConstructors() { newFn := func(_ *VM, _ object.Value, args []object.Value, _ *Proc) object.Value { y, m, d := -4712, 1, 1 if len(args) > 0 { - y = int(intArg(args[0])) + y = clampInt(intArg(args[0])) } if len(args) > 1 { - m = int(intArg(args[1])) + m = clampInt(intArg(args[1])) } if len(args) > 2 { - d = int(intArg(args[2])) + d = clampInt(intArg(args[2])) } return payloadDate(date.NewDate(y, m, d)) } @@ -369,28 +369,28 @@ func (vm *VM) registerDateConstructors() { // Date.jd(n) — the Date for astronomical Julian Day Number n. sm(vm.cDate, "jd", func(_ *VM, _ object.Value, args []object.Value, _ *Proc) object.Value { - return &Date{d: date.DateJD(int(intArgOr(args, 0)))} + return &Date{d: date.DateJD(clampInt(intArgOr(args, 0)))} }) // Date.ordinal(y, yday) — the yday-th day of year y (1-based); an out-of-range // yday raises Date::Error. sm(vm.cDate, "ordinal", func(_ *VM, _ object.Value, args []object.Value, _ *Proc) object.Value { y := -4712 if len(args) > 0 { - y = int(intArg(args[0])) + y = clampInt(intArg(args[0])) } - return payloadDate(date.Ordinal(y, int(intArgOr(args[1:], 1)))) + return payloadDate(date.Ordinal(y, clampInt(intArgOr(args[1:], 1)))) }) // Date.commercial(cwyear, cweek, cwday) — the ISO week-date constructor. sm(vm.cDate, "commercial", func(_ *VM, _ object.Value, args []object.Value, _ *Proc) object.Value { cwy, cw, cwd := -4712, 1, 1 if len(args) > 0 { - cwy = int(intArg(args[0])) + cwy = clampInt(intArg(args[0])) } if len(args) > 1 { - cw = int(intArg(args[1])) + cw = clampInt(intArg(args[1])) } if len(args) > 2 { - cwd = int(intArg(args[2])) + cwd = clampInt(intArg(args[2])) } return payloadDate(date.Commercial(cwy, cw, cwd)) }) @@ -425,22 +425,22 @@ func (vm *VM) registerDateConstructors() { sm(vm.cDateTime, "new", func(_ *VM, _ object.Value, args []object.Value, _ *Proc) object.Value { y, m, d, h, mi, s, off := -4712, 1, 1, 0, 0, 0, 0 if len(args) > 0 { - y = int(intArg(args[0])) + y = clampInt(intArg(args[0])) } if len(args) > 1 { - m = int(intArg(args[1])) + m = clampInt(intArg(args[1])) } if len(args) > 2 { - d = int(intArg(args[2])) + d = clampInt(intArg(args[2])) } if len(args) > 3 { - h = int(intArg(args[3])) + h = clampInt(intArg(args[3])) } if len(args) > 4 { - mi = int(intArg(args[4])) + mi = clampInt(intArg(args[4])) } if len(args) > 5 { - s = int(intArg(args[5])) + s = clampInt(intArg(args[5])) } if len(args) > 6 { off = offsetSeconds(args[6]) @@ -465,25 +465,25 @@ func (vm *VM) registerDateConstructors() { // constructors carrying a wall clock. The calendar day is resolved by the plain // Date constructor, then re-expressed as a DateTime at the given time of day. sm(vm.cDateTime, "jd", func(_ *VM, _ object.Value, args []object.Value, _ *Proc) object.Value { - return dateTimeFromDate(date.DateJD(int(intArgOr(args, 0))), args, 1) + return dateTimeFromDate(date.DateJD(clampInt(intArgOr(args, 0))), args, 1) }) sm(vm.cDateTime, "ordinal", func(_ *VM, _ object.Value, args []object.Value, _ *Proc) object.Value { y := -4712 if len(args) > 0 { - y = int(intArg(args[0])) + y = clampInt(intArg(args[0])) } - return dateTimeFromDate(payloadDate(date.Ordinal(y, int(intArgOr(args[1:], 1)))).d, args, 2) + return dateTimeFromDate(payloadDate(date.Ordinal(y, clampInt(intArgOr(args[1:], 1)))).d, args, 2) }) sm(vm.cDateTime, "commercial", func(_ *VM, _ object.Value, args []object.Value, _ *Proc) object.Value { cwy, cw, cwd := -4712, 1, 1 if len(args) > 0 { - cwy = int(intArg(args[0])) + cwy = clampInt(intArg(args[0])) } if len(args) > 1 { - cw = int(intArg(args[1])) + cw = clampInt(intArg(args[1])) } if len(args) > 2 { - cwd = int(intArg(args[2])) + cwd = clampInt(intArg(args[2])) } return dateTimeFromDate(payloadDate(date.Commercial(cwy, cw, cwd)).d, args, 3) }) @@ -493,7 +493,7 @@ func (vm *VM) registerDateConstructors() { // Julian Day Number; valid_ordinal?(y, yday) and valid_commercial?(cwy, cw, cwd) // — the ordinal and week-date validity, each probing the matching constructor. validCivil := func(_ *VM, _ object.Value, args []object.Value, _ *Proc) object.Value { - _, err := date.NewDate(int(intArg(args[0])), int(intArg(args[1])), int(intArg(args[2]))) + _, err := date.NewDate(clampInt(intArg(args[0])), clampInt(intArg(args[1])), clampInt(intArg(args[2]))) return object.Bool(err == nil) } sm(vm.cDate, "valid_civil?", validCivil) @@ -503,11 +503,11 @@ func (vm *VM) registerDateConstructors() { return object.Bool(ok) }) sm(vm.cDate, "valid_ordinal?", func(_ *VM, _ object.Value, args []object.Value, _ *Proc) object.Value { - _, err := date.Ordinal(int(intArg(args[0])), int(intArg(args[1]))) + _, err := date.Ordinal(clampInt(intArg(args[0])), clampInt(intArg(args[1]))) return object.Bool(err == nil) }) sm(vm.cDate, "valid_commercial?", func(_ *VM, _ object.Value, args []object.Value, _ *Proc) object.Value { - _, err := date.Commercial(int(intArg(args[0])), int(intArg(args[1])), int(intArg(args[2]))) + _, err := date.Commercial(clampInt(intArg(args[0])), clampInt(intArg(args[1])), clampInt(intArg(args[2]))) return object.Bool(err == nil) }) } @@ -652,7 +652,7 @@ func (vm *VM) registerDateAccessors() { if dd.IsDateTime() { loc := stdtime.FixedZone("", dd.Offset()) return &Time{t: stdtime.Date(dd.Year(), stdtime.Month(dd.Month()), dd.Day(), - dd.Hour(), dd.Min(), dd.Sec(), int(dd.SecFractionNanos()), loc)} + dd.Hour(), dd.Min(), dd.Sec(), clampInt(dd.SecFractionNanos()), loc)} } return &Time{t: stdtime.Date(dd.Year(), stdtime.Month(dd.Month()), dd.Day(), 0, 0, 0, 0, stdtime.Local)} }) @@ -762,8 +762,8 @@ func (vm *VM) registerDateFormat() { return object.NewString(dd.Iso8601()) } out := dd.Strftime("%FT%T") - if len(args) > 0 && int(intArg(args[0])) > 0 { - out += "." + fracDigits(int(dd.SecFractionNanos()), int(intArg(args[0]))) + if len(args) > 0 && clampInt(intArg(args[0])) > 0 { + out += "." + fracDigits(clampInt(dd.SecFractionNanos()), clampInt(intArg(args[0]))) } return object.NewString(out + formatZone(dd.Offset())) } diff --git a/internal/vm/devise.go b/internal/vm/devise.go index 58e3714d..48496279 100644 --- a/internal/vm/devise.go +++ b/internal/vm/devise.go @@ -69,7 +69,7 @@ func (vm *VM) registerDeviseErrors(mod *RClass) { // secure_compare helpers and the shared Devise.config accessor and setup block. func (vm *VM) registerDeviseModule(mod *RClass) { mod.smethods["friendly_token"] = &Method{name: "friendly_token", owner: mod, native: func(vm *VM, _ object.Value, args []object.Value, _ *Proc) object.Value { - length := int(intArgOr(args, int64(devise.DefaultConfig().FriendlyTokenLength))) + length := clampInt(intArgOr(args, int64(devise.DefaultConfig().FriendlyTokenLength))) return object.NewString(devise.FriendlyToken(length)) }} mod.smethods["secure_compare"] = &Method{name: "secure_compare", owner: mod, native: func(vm *VM, _ object.Value, args []object.Value, _ *Proc) object.Value { @@ -104,7 +104,7 @@ func (vm *VM) registerDeviseEncryptor(mod *RClass) { } cost := devise.DefaultConfig().Stretches if len(args) > 2 { - cost = int(intArg(args[2])) + cost = clampInt(intArg(args[2])) } h, err := devise.Encryptor{}.Digest(strArg(args[0]), pepper, cost) deviseCheck(err) @@ -204,7 +204,7 @@ func (vm *VM) registerDeviseConfigSettings(cls *RClass) { return object.IntValue(int64(self(v).cfg.Stretches)) }) d("stretches=", func(vm *VM, v object.Value, args []object.Value, _ *Proc) object.Value { - self(v).cfg.Stretches = int(intArg(args[0])) + self(v).cfg.Stretches = clampInt(intArg(args[0])) return args[0] }) d("pepper", func(vm *VM, v object.Value, _ []object.Value, _ *Proc) object.Value { @@ -218,7 +218,7 @@ func (vm *VM) registerDeviseConfigSettings(cls *RClass) { return object.IntValue(int64(self(v).cfg.MaximumAttempts)) }) d("maximum_attempts=", func(vm *VM, v object.Value, args []object.Value, _ *Proc) object.Value { - self(v).cfg.MaximumAttempts = int(intArg(args[0])) + self(v).cfg.MaximumAttempts = clampInt(intArg(args[0])) return args[0] }) d("unlock_strategy=", func(vm *VM, v object.Value, args []object.Value, _ *Proc) object.Value { @@ -250,11 +250,11 @@ func (vm *VM) registerDeviseConfigSettings(cls *RClass) { return args[0] }) d("password_length_min=", func(vm *VM, v object.Value, args []object.Value, _ *Proc) object.Value { - self(v).cfg.PasswordLengthMin = int(intArg(args[0])) + self(v).cfg.PasswordLengthMin = clampInt(intArg(args[0])) return args[0] }) d("password_length_max=", func(vm *VM, v object.Value, args []object.Value, _ *Proc) object.Value { - self(v).cfg.PasswordLengthMax = int(intArg(args[0])) + self(v).cfg.PasswordLengthMax = clampInt(intArg(args[0])) return args[0] }) d("expire_all_remember_me_on_sign_out=", func(vm *VM, v object.Value, args []object.Value, _ *Proc) object.Value { diff --git a/internal/vm/devise_bind.go b/internal/vm/devise_bind.go index 92344fd3..16340a59 100644 --- a/internal/vm/devise_bind.go +++ b/internal/vm/devise_bind.go @@ -123,7 +123,7 @@ func deviseFromRuby(v object.Value) any { case *object.String: return x.Str() case object.Integer: - return int(x) + return clampInt(x) case *Time: return stdtime.Unix(x.t.Unix(), 0).UTC() default: diff --git a/internal/vm/dir.go b/internal/vm/dir.go index f6475629..1dac10e8 100644 --- a/internal/vm/dir.go +++ b/internal/vm/dir.go @@ -65,7 +65,7 @@ func (vm *VM) registerDir() { raise("ArgumentError", "wrong number of arguments (given %d, expected 1..2)", len(pos)) } if len(pos) == 2 { - flags = int(intArg(pos[1])) + flags = clampInt(intArg(pos[1])) } return globResult(vm, globPatternArg(vm, pos[0]), flags, base, sortR, blk) }) @@ -379,13 +379,13 @@ func (vm *VM) registerDirInstance(cDir *RClass) { d("seek", func(_ *VM, v object.Value, args []object.Value, _ *Proc) object.Value { dir := self(v) checkOpen(dir) - dir.pos = int(intArg(args[0])) + dir.pos = clampInt(intArg(args[0])) return dir // seek returns the Dir instance }) d("pos=", func(_ *VM, v object.Value, args []object.Value, _ *Proc) object.Value { dir := self(v) checkOpen(dir) - dir.pos = int(intArg(args[0])) + dir.pos = clampInt(intArg(args[0])) return args[0] // pos= returns its argument }) d("rewind", func(_ *VM, v object.Value, _ []object.Value, _ *Proc) object.Value { @@ -713,7 +713,7 @@ func parseGlobArgs(vm *VM, args []object.Value) (base string, sortR bool, flags sortR = v == object.True } if v, ok := h.Get(object.Symbol("flags")); ok && v != object.NilV { - flags = int(intArg(v)) + flags = clampInt(intArg(v)) } } } diff --git a/internal/vm/embed.go b/internal/vm/embed.go index cfe03b6f..7f855bd6 100644 --- a/internal/vm/embed.go +++ b/internal/vm/embed.go @@ -90,7 +90,7 @@ func (vm *VM) ExitStatus(rerr RubyError) (int, bool) { if !vm.classIsA(rerr.Class, "SystemExit") { return 0, false } - return int(vm.exitStatusOf(rerr.Obj)), true + return clampInt(vm.exitStatusOf(rerr.Obj)), true } // LocalGet reads a local of the binding. @@ -215,7 +215,7 @@ func (vm *VM) CallBlock(p *Proc, args []object.Value) object.Value { return vm.c // ProcArity is Proc#arity. func (vm *VM) ProcArity(p *Proc) int { n, _ := vm.send(p, "arity", nil, nil).(object.Integer) - return int(n) + return clampInt(n) } // InspectValue is Kernel#inspect as p prints it. diff --git a/internal/vm/enumerator.go b/internal/vm/enumerator.go index ffe3431e..65aababc 100644 --- a/internal/vm/enumerator.go +++ b/internal/vm/enumerator.go @@ -359,7 +359,7 @@ func (vm *VM) registerEnumerator() { } return elems[len(elems)-1] } - n := int(intArg(args[0])) + n := clampInt(intArg(args[0])) if n < 0 { raise("ArgumentError", "negative array size") } @@ -643,10 +643,10 @@ func (vm *VM) registerEnumerator() { } return got[0] } - return object.NewArrayFromSlice(vm.enumTake(e, int(intArg(args[0])))) + return object.NewArrayFromSlice(vm.enumTake(e, clampInt(intArg(args[0])))) }) d("take", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { - return object.NewArrayFromSlice(vm.enumTake(self.(*Enumerator), int(intArg(args[0])))) + return object.NewArrayFromSlice(vm.enumTake(self.(*Enumerator), clampInt(intArg(args[0])))) }) // #take_while collects elements while the block is truthy and stops at the // first falsy one. MRI's rb_iter_break makes it terminate even for an unbounded diff --git a/internal/vm/eval.go b/internal/vm/eval.go index 1ae6c9b0..1ae81409 100644 --- a/internal/vm/eval.go +++ b/internal/vm/eval.go @@ -80,7 +80,7 @@ func (vm *VM) evalLoc(fileArg, lineArg object.Value, nilFileAllowed bool) evalLo // TypeError, which is what BasicObject#instance_eval's spec pins. func (vm *VM) evalLineno(v object.Value) int { if i, ok := v.(object.Integer); ok { - return int(i) + return clampInt(i) } // nil is rb_to_int's own arm, with its own wording ("from nil to integer", // not "of nil into Integer"): eval(src, nil, "f", nil) raises it. @@ -90,7 +90,7 @@ func (vm *VM) evalLineno(v object.Value) int { if vm.respondsToDynamic(v, "to_int") { r := vm.send(v, "to_int", nil, nil) if i, ok := r.(object.Integer); ok { - return int(i) + return clampInt(i) } raise("TypeError", "can't convert %s to Integer (%s#to_int gives %s)", classNameOf(v), classNameOf(v), classNameOf(r)) diff --git a/internal/vm/excon_bind.go b/internal/vm/excon_bind.go index 432bcb75..c764a944 100644 --- a/internal/vm/excon_bind.go +++ b/internal/vm/excon_bind.go @@ -85,19 +85,19 @@ func rubyHashToExconOptions(h *object.Hash) excon.Options { o.Idempotent = v.Truthy() } if v, ok := h.Get(object.Symbol("retry_limit")); ok { - o.RetryLimit = int(toInt(v)) + o.RetryLimit = clampInt(toInt(v)) } if v, ok := h.Get(object.Symbol("retry_interval")); ok { - o.RetryInterval = int(toInt(v)) + o.RetryInterval = clampInt(toInt(v)) } if v, ok := h.Get(object.Symbol("read_timeout")); ok { - o.ReadTimeout = int(toInt(v)) + o.ReadTimeout = clampInt(toInt(v)) } if v, ok := h.Get(object.Symbol("write_timeout")); ok { - o.WriteTimeout = int(toInt(v)) + o.WriteTimeout = clampInt(toInt(v)) } if v, ok := h.Get(object.Symbol("connect_timeout")); ok { - o.ConnectTimeout = int(toInt(v)) + o.ConnectTimeout = clampInt(toInt(v)) } if v, ok := h.Get(object.Symbol("user")); ok { o.User = v.ToS() @@ -134,11 +134,11 @@ func exconExpects(v object.Value) []int { if arr, ok := v.(*object.Array); ok { out := make([]int, 0, len(arr.Elems)) for _, e := range arr.Elems { - out = append(out, int(toInt(e))) + out = append(out, clampInt(toInt(e))) } return out } - return []int{int(toInt(v))} + return []int{clampInt(toInt(v))} } // rubyHashToExconQuery builds an ordered library Query from a Ruby Hash, keeping diff --git a/internal/vm/facter_bind.go b/internal/vm/facter_bind.go index 68cb4001..c5853b27 100644 --- a/internal/vm/facter_bind.go +++ b/internal/vm/facter_bind.go @@ -29,7 +29,7 @@ func facterNameArg(args []object.Value) string { // as zero. func facterIntArg(v object.Value) int { if i, ok := v.(object.Integer); ok { - return int(i) + return clampInt(i) } return 0 } diff --git a/internal/vm/factory_bot_bind.go b/internal/vm/factory_bot_bind.go index c65b1a74..7b7fe7fa 100644 --- a/internal/vm/factory_bot_bind.go +++ b/internal/vm/factory_bot_bind.go @@ -216,7 +216,7 @@ func (vm *VM) factoryBotList(args []object.Value, create bool) object.Value { raise("ArgumentError", "wrong number of arguments (given %d, expected 2..)", len(args)) } name := nameArg(args[0]) - n := int(fbInt(args[1])) + n := clampInt(fbInt(args[1])) opts := factoryBotCallOpts(args[2:]) var ( list []any diff --git a/internal/vm/faker_bind.go b/internal/vm/faker_bind.go index 2ccab05e..c1805ac5 100644 --- a/internal/vm/faker_bind.go +++ b/internal/vm/faker_bind.go @@ -122,7 +122,7 @@ func fakerIntArg(args []object.Value, n, def int) int { } switch v := args[n].(type) { case object.Integer: - return int(v) + return clampInt(v) case object.Nil: return def } diff --git a/internal/vm/fastgettext_bind.go b/internal/vm/fastgettext_bind.go index 877b3745..f4975de1 100644 --- a/internal/vm/fastgettext_bind.go +++ b/internal/vm/fastgettext_bind.go @@ -27,7 +27,7 @@ func fgStrArg(args []object.Value) string { // fgIntArg reads an integer count, treating a non-integer as zero. func fgIntArg(v object.Value) int { if n, ok := v.(object.Integer); ok { - return int(n) + return clampInt(n) } return 0 } diff --git a/internal/vm/fft.go b/internal/vm/fft.go index daa47a20..585502fa 100644 --- a/internal/vm/fft.go +++ b/internal/vm/fft.go @@ -104,7 +104,7 @@ func intSlice(v object.Value) []int { a := arrayArg(v) out := make([]int, len(a.Elems)) for i, e := range a.Elems { - out[i] = int(intArg(e)) + out[i] = clampInt(intArg(e)) } return out } @@ -159,15 +159,15 @@ func (vm *VM) registerFFT() { return complexArray(gofft.RFFT(floatSlice(args[0]))) }) def("irfft", 2, 2, func(_ *VM, _ object.Value, args []object.Value, _ *Proc) object.Value { - n := int(intArg(args[1])) + n := clampInt(intArg(args[1])) return floatArray(gofft.IRFFT(complexSlice(args[0]), n)) }) // The bin-frequency helpers take numpy's optional sample spacing d. def("fftfreq", 1, 2, func(_ *VM, _ object.Value, args []object.Value, _ *Proc) object.Value { - return floatArray(gofft.FFTFreq(int(intArg(args[0])), sampleSpacing(args, 1))) + return floatArray(gofft.FFTFreq(clampInt(intArg(args[0])), sampleSpacing(args, 1))) }) def("rfftfreq", 1, 2, func(_ *VM, _ object.Value, args []object.Value, _ *Proc) object.Value { - return floatArray(gofft.RFFTFreq(int(intArg(args[0])), sampleSpacing(args, 1))) + return floatArray(gofft.RFFTFreq(clampInt(intArg(args[0])), sampleSpacing(args, 1))) }) // N-dimensional transforms over flat row-major data plus an explicit shape. @@ -184,13 +184,13 @@ func (vm *VM) registerFFT() { // 2-D transforms over flat row-major data of shape rows×cols. def("fft2", 3, 3, func(_ *VM, _ object.Value, args []object.Value, _ *Proc) object.Value { data := complexSlice(args[0]) - rows, cols := int(intArg(args[1])), int(intArg(args[2])) + rows, cols := clampInt(intArg(args[1])), clampInt(intArg(args[2])) checkShape(len(data), []int{rows, cols}) return complexArray(gofft.FFT2(data, [2]int{rows, cols})) }) def("ifft2", 3, 3, func(_ *VM, _ object.Value, args []object.Value, _ *Proc) object.Value { data := complexSlice(args[0]) - rows, cols := int(intArg(args[1])), int(intArg(args[2])) + rows, cols := clampInt(intArg(args[1])), clampInt(intArg(args[2])) checkShape(len(data), []int{rows, cols}) return complexArray(gofft.IFFT2(data, [2]int{rows, cols})) }) @@ -198,7 +198,7 @@ func (vm *VM) registerFFT() { // Window functions (numpy/scipy conventions): length n → []float64. window := func(name string, f func(int) []float64) { def(name, 1, 1, func(_ *VM, _ object.Value, args []object.Value, _ *Proc) object.Value { - return floatArray(f(int(intArg(args[0])))) + return floatArray(f(clampInt(intArg(args[0])))) }) } window("hann", gofft.Hann) @@ -214,7 +214,7 @@ func (vm *VM) registerFFT() { }) def("spectrogram", 4, 5, func(_ *VM, _ object.Value, args []object.Value, _ *Proc) object.Value { signal := floatSlice(args[0]) - segment, overlap := int(intArg(args[1])), int(intArg(args[2])) + segment, overlap := clampInt(intArg(args[1])), clampInt(intArg(args[2])) win := floatSlice(args[3]) // Validate go-fft's constraints up front so a bad call raises a Ruby // error instead of panicking the VM. diff --git a/internal/vm/file.go b/internal/vm/file.go index 29194a89..23ff1847 100644 --- a/internal/vm/file.go +++ b/internal/vm/file.go @@ -90,7 +90,7 @@ func (vm *VM) registerFile() { if len(args) == 3 { // MRI coerces the flags argument with rb_to_int (NUM2INT), so a // non-Integer that answers #to_int is accepted rather than a TypeError. - flags = int(coerceInt(vm, args[2])) + flags = clampInt(coerceInt(vm, args[2])) } return object.Bool(fnmatch(strArg(args[0]), vm.filePathArg(args[1]), flags)) } @@ -115,7 +115,7 @@ func (vm *VM) registerFile() { } level := 1 if len(args) > 1 { - level = int(coerceInt(vm, args[1])) + level = clampInt(coerceInt(vm, args[1])) if level < 0 { raise("ArgumentError", "negative level: %d", level) } @@ -405,7 +405,7 @@ func (vm *VM) registerFile() { setUmask(cur) // restore: a no-arg umask is a pure read return object.IntValue(int64(cur)) } - return object.IntValue(int64(setUmask(int(vm.num2mode(args[0]))))) + return object.IntValue(int64(setUmask(clampInt(vm.num2mode(args[0]))))) }) // Access predicates: readable?/writable?/executable? for the current effective // user, plus executable_real? — thin File.stat-and-test wrappers that return @@ -1040,7 +1040,7 @@ func chownID(v object.Value) int { if v == object.NilV { return -1 } - return int(intArg(v)) + return clampInt(intArg(v)) } // timeArgUnix marshals a File.utime time argument (a Time, or an Integer/Float diff --git a/internal/vm/graphql_bind.go b/internal/vm/graphql_bind.go index c9c4dd09..77a9d6f3 100644 --- a/internal/vm/graphql_bind.go +++ b/internal/vm/graphql_bind.go @@ -148,7 +148,7 @@ func goFromRuby(v object.Value) interface{} { case object.Bool: return bool(x) case object.Integer: - return int(x) + return clampInt(x) case object.Float: return float64(x) case *object.String: diff --git a/internal/vm/grpc_bind.go b/internal/vm/grpc_bind.go index 047cc365..15e99ffa 100644 --- a/internal/vm/grpc_bind.go +++ b/internal/vm/grpc_bind.go @@ -623,7 +623,7 @@ func grpcDurationOf(v object.Value) time.Duration { // grpcInt coerces a Ruby Integer argument to an int (for a status code). func grpcInt(v object.Value) int { if n, ok := v.(object.Integer); ok { - return int(n) + return clampInt(n) } raise("TypeError", "expected an Integer, got %s", v.Inspect()) return 0 diff --git a/internal/vm/hanami.go b/internal/vm/hanami.go index a2c15570..3dc86b9c 100644 --- a/internal/vm/hanami.go +++ b/internal/vm/hanami.go @@ -191,7 +191,7 @@ func hanamiStr(v object.Value) string { func hanamiInt(v object.Value) int { switch n := v.(type) { case object.Integer: - return int(n) + return clampInt(n) case object.Float: return int(n) } diff --git a/internal/vm/httparty_bind.go b/internal/vm/httparty_bind.go index 282ff5d9..282ad1f1 100644 --- a/internal/vm/httparty_bind.go +++ b/internal/vm/httparty_bind.go @@ -141,7 +141,7 @@ func httpartyParseOptions(args []object.Value) httparty.RequestOptions { } if v, ok := httpartyOpt(h, "timeout"); ok { if i, ok := v.(object.Integer); ok { - opt.Timeout = int(i) + opt.Timeout = clampInt(i) } } if v, ok := httpartyOpt(h, "follow_redirects"); ok { @@ -218,7 +218,7 @@ func (vm *VM) httpartyIndex(resp *httparty.Response, key object.Value) object.Va } case *object.Array: if i, ok := key.(object.Integer); ok { - idx := int(i) + idx := clampInt(i) if idx >= 0 && idx < len(parsed.Elems) { return parsed.Elems[idx] } diff --git a/internal/vm/httprb.go b/internal/vm/httprb.go index b811575e..c22b7d12 100644 --- a/internal/vm/httprb.go +++ b/internal/vm/httprb.go @@ -147,7 +147,7 @@ func httprbApplyChain(base *HTTPrbClient, name string, args []object.Value) obje case "accept": return &HTTPrbClient{base.c.Accept(httprbName(args[0]))} case "timeout": - return &HTTPrbClient{base.c.Timeout(int(toInt(args[0])))} + return &HTTPrbClient{base.c.Timeout(clampInt(toInt(args[0])))} case "follow": return &HTTPrbClient{base.c.Follow()} } diff --git a/internal/vm/i18n_bind.go b/internal/vm/i18n_bind.go index 6536e403..b5605b17 100644 --- a/internal/vm/i18n_bind.go +++ b/internal/vm/i18n_bind.go @@ -80,7 +80,7 @@ func (vm *VM) i18nOptions(args []object.Value, bang bool) *i18n.Options { opts.Scope = i18nScope(val) case "count": if n, ok := val.(object.Integer); ok { - c := int(n) + c := clampInt(n) opts.Count = &c } case "default": @@ -197,7 +197,7 @@ func toI18nData(v object.Value) i18n.Value { case object.Symbol: return string(x) case object.Integer: - return int(x) + return clampInt(x) case object.Float: return float64(x) case object.Bool: @@ -326,7 +326,7 @@ func (t *i18nTemporal) field(name string) int { return 0 } if n, ok := t.vm.send(t.obj, name, nil, nil).(object.Integer); ok { - return int(n) + return clampInt(n) } return 0 } diff --git a/internal/vm/image.go b/internal/vm/image.go index d1e1bb42..07d30324 100644 --- a/internal/vm/image.go +++ b/internal/vm/image.go @@ -54,7 +54,7 @@ func (vm *VM) registerImage() { } s("new", 2, 2, func(_ *VM, _ object.Value, args []object.Value, _ *Proc) object.Value { - w, h := int(intArg(args[0])), int(intArg(args[1])) + w, h := clampInt(intArg(args[0])), clampInt(intArg(args[1])) if w <= 0 || h <= 0 { raise("ArgumentError", "image dimensions must be positive") } @@ -101,7 +101,7 @@ func (vm *VM) registerImage() { return object.IntValue(int64(imgOf(v).Bounds().Dy())) }) d("get", 2, 2, func(_ *VM, v object.Value, args []object.Value, _ *Proc) object.Value { - c := imgOf(v).RGBAAt(int(intArg(args[0])), int(intArg(args[1]))) + c := imgOf(v).RGBAAt(clampInt(intArg(args[0])), clampInt(intArg(args[1]))) return object.NewArray(object.IntValue(int64(c.R)), object.IntValue(int64(c.G)), object.IntValue(int64(c.B)), object.IntValue(int64(c.A))) }) d("set", 5, 6, func(_ *VM, v object.Value, args []object.Value, _ *Proc) object.Value { @@ -109,7 +109,7 @@ func (vm *VM) registerImage() { if len(args) > 5 { a = uint8(intArg(args[5])) } - imgOf(v).SetRGBA(int(intArg(args[0])), int(intArg(args[1])), + imgOf(v).SetRGBA(clampInt(intArg(args[0])), clampInt(intArg(args[1])), color.RGBA{uint8(intArg(args[2])), uint8(intArg(args[3])), uint8(intArg(args[4])), a}) return object.NilV }) @@ -170,16 +170,16 @@ func (vm *VM) registerImage() { return mustImg(goimg.GaussianBlur(imgOf(v), s)) }) d("box_blur", 1, 1, func(_ *VM, v object.Value, args []object.Value, _ *Proc) object.Value { - return mustImg(goimg.BoxBlur(imgOf(v), int(intArg(args[0])))) + return mustImg(goimg.BoxBlur(imgOf(v), clampInt(intArg(args[0])))) }) d("median", 1, 1, func(_ *VM, v object.Value, args []object.Value, _ *Proc) object.Value { - return mustImg(goimg.Median(imgOf(v), int(intArg(args[0])))) + return mustImg(goimg.Median(imgOf(v), clampInt(intArg(args[0])))) }) d("erode", 1, 1, func(_ *VM, v object.Value, args []object.Value, _ *Proc) object.Value { - return mustImg(goimg.Erode(imgOf(v), int(intArg(args[0])))) + return mustImg(goimg.Erode(imgOf(v), clampInt(intArg(args[0])))) }) d("dilate", 1, 1, func(_ *VM, v object.Value, args []object.Value, _ *Proc) object.Value { - return mustImg(goimg.Dilate(imgOf(v), int(intArg(args[0])))) + return mustImg(goimg.Dilate(imgOf(v), clampInt(intArg(args[0])))) }) d("canny", 3, 3, func(_ *VM, v object.Value, args []object.Value, _ *Proc) object.Value { sigma, _ := toFloat(args[0]) @@ -188,11 +188,11 @@ func (vm *VM) registerImage() { return mustImg(goimg.Canny(imgOf(v), sigma, low, high)) }) d("resize", 2, 2, func(_ *VM, v object.Value, args []object.Value, _ *Proc) object.Value { - return mustImg(goimg.Resize(imgOf(v), int(intArg(args[0])), int(intArg(args[1])), goimg.Bilinear)) + return mustImg(goimg.Resize(imgOf(v), clampInt(intArg(args[0])), clampInt(intArg(args[1])), goimg.Bilinear)) }) d("crop", 4, 4, func(_ *VM, v object.Value, args []object.Value, _ *Proc) object.Value { - x, y := int(intArg(args[0])), int(intArg(args[1])) - w, h := int(intArg(args[2])), int(intArg(args[3])) + x, y := clampInt(intArg(args[0])), clampInt(intArg(args[1])) + w, h := clampInt(intArg(args[2])), clampInt(intArg(args[3])) return mustImg(goimg.Crop(imgOf(v), image.Rect(x, y, x+w, y+h))) }) diff --git a/internal/vm/images.go b/internal/vm/images.go index 409fe603..3464f2a0 100644 --- a/internal/vm/images.go +++ b/internal/vm/images.go @@ -114,7 +114,7 @@ func (vm *VM) registerImagesImage(mod *RClass) { if len(args) > 2 { fill = imagesColorArg(args[2]) } - return &ImagesImage{im: images.New(int(intArg(args[0])), int(intArg(args[1])), fill)} + return &ImagesImage{im: images.New(clampInt(intArg(args[0])), clampInt(intArg(args[1])), fill)} }} // Query methods. @@ -136,7 +136,7 @@ func (vm *VM) registerImagesImage(mod *RClass) { }) cls.define("at", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { imagesArity(args, 2) - c, err := imagesSelf(self).im.At(int(intArg(args[0])), int(intArg(args[1]))) + c, err := imagesSelf(self).im.At(clampInt(intArg(args[0])), clampInt(intArg(args[1]))) if err != nil { raise("Images::OutOfBoundsError", "%s", err.Error()) } @@ -173,23 +173,23 @@ func (vm *VM) registerImagesTransforms(cls *RClass) { // Error-returning geometric transforms + format conversion. cls.define("resize", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { imagesArity(args, 2) - return imagesWrap(imagesSelf(self).im.Resize(int(intArg(args[0])), int(intArg(args[1])))) + return imagesWrap(imagesSelf(self).im.Resize(clampInt(intArg(args[0])), clampInt(intArg(args[1])))) }) cls.define("resize_nearest", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { imagesArity(args, 2) - return imagesWrap(imagesSelf(self).im.ResizeNearest(int(intArg(args[0])), int(intArg(args[1])))) + return imagesWrap(imagesSelf(self).im.ResizeNearest(clampInt(intArg(args[0])), clampInt(intArg(args[1])))) }) cls.define("thumbnail", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { imagesArity(args, 2) - return imagesWrap(imagesSelf(self).im.Thumbnail(int(intArg(args[0])), int(intArg(args[1])))) + return imagesWrap(imagesSelf(self).im.Thumbnail(clampInt(intArg(args[0])), clampInt(intArg(args[1])))) }) cls.define("crop", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { imagesArity(args, 4) - return imagesWrap(imagesSelf(self).im.Crop(int(intArg(args[0])), int(intArg(args[1])), int(intArg(args[2])), int(intArg(args[3])))) + return imagesWrap(imagesSelf(self).im.Crop(clampInt(intArg(args[0])), clampInt(intArg(args[1])), clampInt(intArg(args[2])), clampInt(intArg(args[3])))) }) cls.define("rotate", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { imagesArity(args, 1) - return imagesWrap(imagesSelf(self).im.Rotate(int(intArg(args[0])))) + return imagesWrap(imagesSelf(self).im.Rotate(clampInt(intArg(args[0])))) }) cls.define("convert", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { imagesArity(args, 1) @@ -199,7 +199,7 @@ func (vm *VM) registerImagesTransforms(cls *RClass) { // Error-returning scikit-image filters. cls.define("blur", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { imagesArity(args, 1) - return imagesWrap(imagesSelf(self).im.Blur(int(intArg(args[0])))) + return imagesWrap(imagesSelf(self).im.Blur(clampInt(intArg(args[0])))) }) cls.define("gaussian_blur", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { imagesArity(args, 1) @@ -211,7 +211,7 @@ func (vm *VM) registerImagesTransforms(cls *RClass) { }) cls.define("median", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { imagesArity(args, 1) - return imagesWrap(imagesSelf(self).im.Median(int(intArg(args[0])))) + return imagesWrap(imagesSelf(self).im.Median(clampInt(intArg(args[0])))) }) cls.define("canny", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { imagesArity(args, 3) @@ -219,25 +219,25 @@ func (vm *VM) registerImagesTransforms(cls *RClass) { }) cls.define("convolve", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { imagesArity(args, 3) - return imagesWrap(imagesSelf(self).im.Convolve(int(intArg(args[0])), int(intArg(args[1])), imagesFloatSlice(args[2]))) + return imagesWrap(imagesSelf(self).im.Convolve(clampInt(intArg(args[0])), clampInt(intArg(args[1])), imagesFloatSlice(args[2]))) }) // Error-returning morphology. cls.define("erode", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { imagesArity(args, 1) - return imagesWrap(imagesSelf(self).im.Erode(int(intArg(args[0])))) + return imagesWrap(imagesSelf(self).im.Erode(clampInt(intArg(args[0])))) }) cls.define("dilate", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { imagesArity(args, 1) - return imagesWrap(imagesSelf(self).im.Dilate(int(intArg(args[0])))) + return imagesWrap(imagesSelf(self).im.Dilate(clampInt(intArg(args[0])))) }) cls.define("open", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { imagesArity(args, 1) - return imagesWrap(imagesSelf(self).im.Open(int(intArg(args[0])))) + return imagesWrap(imagesSelf(self).im.Open(clampInt(intArg(args[0])))) }) cls.define("close", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { imagesArity(args, 1) - return imagesWrap(imagesSelf(self).im.Close(int(intArg(args[0])))) + return imagesWrap(imagesSelf(self).im.Close(clampInt(intArg(args[0])))) }) // Total (never-erroring) transforms + colour ops. diff --git a/internal/vm/images_canvas.go b/internal/vm/images_canvas.go index bad6d1d9..4181bb23 100644 --- a/internal/vm/images_canvas.go +++ b/internal/vm/images_canvas.go @@ -47,7 +47,7 @@ func (vm *VM) registerImagesCanvas(mod *RClass) { if len(args) > 2 { fill = imagesColorArg(args[2]) } - return &ImagesCanvas{c: canvas.New(int(intArg(args[0])), int(intArg(args[1])), fill)} + return &ImagesCanvas{c: canvas.New(clampInt(intArg(args[0])), clampInt(intArg(args[1])), fill)} }} // Images::Canvas.decode(blob) / .from_blob(blob) read a PNG String blob. @@ -79,14 +79,14 @@ func (vm *VM) registerImagesCanvas(mod *RClass) { // coordinates fall outside the canvas); #[]= / #set / #point writes. at := func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { imagesArity(args, 2) - return object.IntValue(int64(imagesCanvasAt(imagesCanvasSelf(self).c, int(intArg(args[0])), int(intArg(args[1]))))) + return object.IntValue(int64(imagesCanvasAt(imagesCanvasSelf(self).c, clampInt(intArg(args[0])), clampInt(intArg(args[1]))))) } cls.define("[]", at) cls.define("at", at) set := func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { imagesArity(args, 3) - imagesCanvasSet(imagesCanvasSelf(self).c, int(intArg(args[0])), int(intArg(args[1])), imagesColorArg(args[2])) + imagesCanvasSet(imagesCanvasSelf(self).c, clampInt(intArg(args[0])), clampInt(intArg(args[1])), imagesColorArg(args[2])) return args[2] } cls.define("[]=", set) @@ -101,22 +101,22 @@ func (vm *VM) registerImagesCanvas(mod *RClass) { }) cls.define("line", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { imagesArity(args, 5) - imagesCanvasSelf(self).c.Line(int(intArg(args[0])), int(intArg(args[1])), int(intArg(args[2])), int(intArg(args[3])), imagesColorArg(args[4])) + imagesCanvasSelf(self).c.Line(clampInt(intArg(args[0])), clampInt(intArg(args[1])), clampInt(intArg(args[2])), clampInt(intArg(args[3])), imagesColorArg(args[4])) return self }) cls.define("rect", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { imagesArity(args, 6) - imagesCanvasSelf(self).c.Rect(int(intArg(args[0])), int(intArg(args[1])), int(intArg(args[2])), int(intArg(args[3])), imagesColorArg(args[4]), imagesColorArg(args[5])) + imagesCanvasSelf(self).c.Rect(clampInt(intArg(args[0])), clampInt(intArg(args[1])), clampInt(intArg(args[2])), clampInt(intArg(args[3])), imagesColorArg(args[4]), imagesColorArg(args[5])) return self }) cls.define("circle", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { imagesArity(args, 4) - imagesCanvasSelf(self).c.Circle(int(intArg(args[0])), int(intArg(args[1])), int(intArg(args[2])), imagesColorArg(args[3])) + imagesCanvasSelf(self).c.Circle(clampInt(intArg(args[0])), clampInt(intArg(args[1])), clampInt(intArg(args[2])), imagesColorArg(args[3])) return self }) cls.define("compose", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { imagesArity(args, 3) - imagesCanvasSelf(self).c.Compose(imagesCanvasSelf(args[0]).c, int(intArg(args[1])), int(intArg(args[2]))) + imagesCanvasSelf(self).c.Compose(imagesCanvasSelf(args[0]).c, clampInt(intArg(args[1])), clampInt(intArg(args[2]))) return self }) diff --git a/internal/vm/io.go b/internal/vm/io.go index 1dbb26dc..511432fd 100644 --- a/internal/vm/io.go +++ b/internal/vm/io.go @@ -447,10 +447,10 @@ func (vm *VM) registerIO() { var fd int switch fv := pos[0].(type) { case object.Integer: - fd = int(fv) + fd = clampInt(fv) default: if vm.respondsToDynamic(pos[0], "to_int") { - fd = int(vm.repeatLong(pos[0])) + fd = clampInt(vm.repeatLong(pos[0])) } else { raise("TypeError", "no implicit conversion of %s into Integer", classNameOf(pos[0])) } @@ -661,7 +661,7 @@ func (vm *VM) registerIO() { return object.IntValue(int64(len(o.buf))) }) cFile.define("flock", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { - return vm.fileFlock(self.(*IOObj), int(vm.toIntCoerce(args[0]))) + return vm.fileFlock(self.(*IOObj), clampInt(vm.toIntCoerce(args[0]))) }) // IO.read/binread/write/binwrite/foreach/readlines, installed identically on IO // and File (File.readlines/foreach included) now that both classes exist. @@ -726,7 +726,7 @@ func (vm *VM) warnUplevelPrefix(lev int64) string { if lev > int64(len(vm.frameNames)) { return "warning: " } - i := len(vm.frameNames) - 1 - int(lev) + i := len(vm.frameNames) - 1 - clampInt(lev) if i < 0 { return "warning: " } @@ -1991,7 +1991,7 @@ func defStringIORead(cls *RClass) { // IO#seek accepts the :SET/:CUR/:END whence symbols; StringIO#seek // (strio_seek) does not — a Symbol there raises the Integer TypeError. if ioIsStringIO(o) { - whence = int(vm.toIntCoerce(args[1])) + whence = clampInt(vm.toIntCoerce(args[1])) } else { whence = vm.seekWhence(args[1]) } @@ -2029,7 +2029,7 @@ func defStringIORead(cls *RClass) { if o.wrClosed { // a read-only stream cannot be truncated raise("IOError", "not opened for writing") } - n := int(vm.toIntCoerce(args[0])) + n := clampInt(vm.toIntCoerce(args[0])) if n < 0 { raise("Errno::EINVAL", "Invalid argument") } @@ -2093,7 +2093,7 @@ func defStringIORead(cls *RClass) { return vm.ioDecodeRead(o, data) } if lengthGiven { - n := int(vm.toIntCoerce(args[0])) + n := clampInt(vm.toIntCoerce(args[0])) if n < 0 { raise("ArgumentError", "negative length %d given", n) } @@ -2117,7 +2117,7 @@ func defStringIORead(cls *RClass) { o := self.(*IOObj) ioCheckReadable(o) o.pipeRefresh() - n := int(toInt(args[0])) + n := clampInt(toInt(args[0])) if n < 0 { raise("ArgumentError", "negative length %d given", n) } @@ -2287,7 +2287,7 @@ func (vm *VM) seekWhence(v object.Value) int { } raise("TypeError", "no implicit conversion of Symbol into Integer") } - return int(vm.toIntCoerce(v)) + return clampInt(vm.toIntCoerce(v)) } // ioIsStringIO reports whether o is a StringIO (or a subclass), which — unlike a @@ -2590,7 +2590,7 @@ func (vm *VM) resolveGetsArgs(args []object.Value) (sep getsSep, limit int, chom if len(args) > 0 { switch a := args[0].(type) { case object.Integer: - limit = int(a) + limit = clampInt(a) case *object.String: sep = getsSep{s: a.Str(), set: true} default: @@ -3007,7 +3007,7 @@ func (vm *VM) ioOfftArg(v object.Value) int { if _, ok := v.(*object.Bignum); ok { raise("RangeError", "bignum too big to convert into 'long long'") } - return int(vm.toIntCoerce(v)) + return clampInt(vm.toIntCoerce(v)) } // ioCIntArg coerces an argument the way MRI's NUM2INT does for a value stored in @@ -3022,7 +3022,7 @@ func (vm *VM) ioCIntArg(v object.Value) int { if n < -2147483648 || n > 2147483647 { // int32 bounds raise("RangeError", "integer %d too big to convert to 'int'", n) } - return int(n) + return clampInt(n) } // toInt coerces a small Integer position/length argument to int64 (raising for diff --git a/internal/vm/io_buffer.go b/internal/vm/io_buffer.go index e288d900..4ce307bd 100644 --- a/internal/vm/io_buffer.go +++ b/internal/vm/io_buffer.go @@ -525,11 +525,11 @@ func (vm *VM) registerIOBuffer() { data := b.live() off := 0 if len(args) > 0 { - off = int(intArg(args[0])) + off = clampInt(intArg(args[0])) } length := len(data) - off if len(args) > 1 { - length = int(intArg(args[1])) + length = clampInt(intArg(args[1])) } if off < 0 || length < 0 || off+length > len(data) { raise("ArgumentError", "Specified offset+length is bigger than the buffer size!") diff --git a/internal/vm/io_class.go b/internal/vm/io_class.go index af083cf9..aee08ab6 100644 --- a/internal/vm/io_class.go +++ b/internal/vm/io_class.go @@ -30,7 +30,7 @@ func (vm *VM) registerIOClassMethods(cIO, cFile *RClass) { if len(pos) < 1 || len(pos) > 2 { raise("ArgumentError", "wrong number of arguments (given %d, expected 1..2)", len(pos)) } - return vm.ioAdoptFd(cIO, int(vm.repeatLong(pos[0])), pos, opts) + return vm.ioAdoptFd(cIO, clampInt(vm.repeatLong(pos[0])), pos, opts) } cIO.smethods["for_fd"] = &Method{name: "for_fd", owner: cIO, native: forFd} cIO.smethods["new"] = &Method{name: "new", owner: cIO, native: forFd} @@ -91,11 +91,11 @@ func (vm *VM) registerIOClassMethods(cIO, cFile *RClass) { } length, hasLen := -1, false if len(args) >= 3 && !object.IsNil(args[2]) { - length, hasLen = int(intArg(args[2])), true + length, hasLen = clampInt(intArg(args[2])), true } srcOffset, hasOff := -1, false if len(args) >= 4 && !object.IsNil(args[3]) { - srcOffset, hasOff = int(intArg(args[3])), true + srcOffset, hasOff = clampInt(intArg(args[3])), true } // A zero-length copy transfers nothing and touches neither object — MRI // never calls #read on the source nor #write on the destination. @@ -225,10 +225,10 @@ func (vm *VM) ioYieldAndClose(o *IOObj, blk *Proc) object.Value { // the bool says so rather than raising, because the caller has a path to try. func (vm *VM) ioFdArg(v object.Value) (int, bool) { if i, ok := v.(object.Integer); ok { - return int(i), true + return clampInt(i), true } if vm.respondsToDynamic(v, "to_int") { - return int(vm.repeatLong(v)), true + return clampInt(vm.repeatLong(v)), true } return 0, false } diff --git a/internal/vm/io_descriptors.go b/internal/vm/io_descriptors.go index d2c3042a..8a99afd6 100644 --- a/internal/vm/io_descriptors.go +++ b/internal/vm/io_descriptors.go @@ -121,7 +121,7 @@ func defIOReadExtra(cls *RClass) { o := self.(*IOObj) ioCheckReadable(o) o.pipeRefresh() - n := int(intArg(args[0])) + n := clampInt(intArg(args[0])) if n < 0 { raise("ArgumentError", "negative length %d given", n) } @@ -305,8 +305,8 @@ func defIOSeekable(cls *RClass) { // #to_int, then io_setstrbuf coerces the optional buffer with #to_str — // all before the maxlen==0 early return, so a non-String buffer still // raises and a zero-length read leaves the buffer's bytes untouched. - n := int(vm.toIntCoerce(args[0])) - off := int(vm.toIntCoerce(args[1])) + n := clampInt(vm.toIntCoerce(args[0])) + off := clampInt(vm.toIntCoerce(args[1])) var buf *object.String if len(args) > 2 { buf = vm.ioBufferArg(args[2]) @@ -338,7 +338,7 @@ func defIOSeekable(cls *RClass) { // stream is checked, so a missing #to_s surfaces as NoMethodError and a // non-Integer offset as the "into Integer" TypeError. data := []byte(vm.objAsString(args[0])) - off := int(vm.toIntCoerce(args[1])) + off := clampInt(vm.toIntCoerce(args[1])) ioCheckOpen(o) if off < 0 { raise("Errno::EINVAL", "Invalid argument - pwrite") diff --git a/internal/vm/io_nonregular_internal_test.go b/internal/vm/io_nonregular_internal_test.go index d8bc2410..586fb22c 100644 --- a/internal/vm/io_nonregular_internal_test.go +++ b/internal/vm/io_nonregular_internal_test.go @@ -8,6 +8,7 @@ import ( "os" "path/filepath" "runtime" + "strconv" "testing" ) @@ -33,8 +34,14 @@ func TestOpeningSomethingThatIsNotARegularFile(t *testing.T) { // The case that used to take the machine: open, seek far past any real // file, read the position back. - got := eval(t, `f = File.open("/dev/zero"); f.seek(2**33); p f.pos; f.close`) - if want := "8589934592\n"; got != want { + // A buffered stream's position is a Go int, so on a 32-bit target it stops + // at math.MaxInt (a documented limit); 2**30 is "far" there. + far, want := "2**33", "8589934592\n" + if strconv.IntSize == 32 { + far, want = "2**30", "1073741824\n" + } + got := eval(t, `f = File.open("/dev/zero"); f.seek(`+far+`); p f.pos; f.close`) + if got != want { t.Errorf("seeking on /dev/zero gave %q, want %q", got, want) } // Reading sees end-of-file rather than an endless stream of zeros. That is diff --git a/internal/vm/ipaddr.go b/internal/vm/ipaddr.go index 217cf86a..df973aea 100644 --- a/internal/vm/ipaddr.go +++ b/internal/vm/ipaddr.go @@ -256,14 +256,14 @@ func (vm *VM) registerIPAddr() { return object.IntValue(int64(self(v).ip.Prefix())) }) d("prefix=", func(_ *VM, v object.Value, args []object.Value, _ *Proc) object.Value { - _, err := self(v).ip.SetPrefix(int(ipInt(args[0]))) + _, err := self(v).ip.SetPrefix(clampInt(ipInt(args[0]))) raiseIPAddrErr(err) return args[0] }) d("mask", func(_ *VM, v object.Value, args []object.Value, _ *Proc) object.Value { switch x := args[0].(type) { case object.Integer: - return ipOK(self(v).ip.MaskLen(int(x))) + return ipOK(self(v).ip.MaskLen(clampInt(x))) case *object.String: return ipOK(self(v).ip.Mask(string(x.Bytes()))) } diff --git a/internal/vm/json.go b/internal/vm/json.go index 2a43c1d2..8f92b7bd 100644 --- a/internal/vm/json.go +++ b/internal/vm/json.go @@ -122,7 +122,7 @@ func jsonSharedOpts(h *object.Hash) []json.Option { if v, ok := h.Get(object.Symbol("max_nesting")); ok { switch n := v.(type) { case object.Integer: - opts = append(opts, json.WithMaxNesting(int(n))) + opts = append(opts, json.WithMaxNesting(clampInt(n))) case object.Bool: if !bool(n) { // max_nesting: false disables the limit opts = append(opts, json.WithMaxNesting(0)) diff --git a/internal/vm/kaminari.go b/internal/vm/kaminari.go index e1026bcd..2478f099 100644 --- a/internal/vm/kaminari.go +++ b/internal/vm/kaminari.go @@ -96,7 +96,7 @@ func (r *kaminariRubyRelation) Slice(offset, limit int) any { // 0, so a misbehaving stub cannot crash the paginator). func kaminariToInt(v object.Value) int { if n, ok := v.(object.Integer); ok { - return int(n) + return clampInt(n) } return 0 } @@ -108,7 +108,7 @@ func kaminariPageNum(args []object.Value) int { return 1 } if n, ok := args[0].(object.Integer); ok { - return int(n) + return clampInt(n) } return 1 } @@ -121,7 +121,7 @@ func kaminariPerPtr(args []object.Value) *int { return nil } if n, ok := args[0].(object.Integer); ok { - return kaminari.Intp(int(n)) + return kaminari.Intp(clampInt(n)) } return nil } @@ -133,7 +133,7 @@ func kaminariPaddingNum(args []object.Value) int { return 0 } if n, ok := args[0].(object.Integer); ok { - return int(n) + return clampInt(n) } return 0 } @@ -193,13 +193,13 @@ func kaminariArrayOpts(args []object.Value) []kaminari.ArrayOption { } switch arStr(k) { case "total_count": - opts = append(opts, kaminari.WithTotalCount(int(n))) + opts = append(opts, kaminari.WithTotalCount(clampInt(n))) case "limit": - opts = append(opts, kaminari.WithLimit(int(n))) + opts = append(opts, kaminari.WithLimit(clampInt(n))) case "offset": - opts = append(opts, kaminari.WithOffset(int(n))) + opts = append(opts, kaminari.WithOffset(clampInt(n))) case "padding": - opts = append(opts, kaminari.WithPadding(int(n))) + opts = append(opts, kaminari.WithPadding(clampInt(n))) } } return opts diff --git a/internal/vm/kernel_introspect.go b/internal/vm/kernel_introspect.go index 90c4d820..32ada5f0 100644 --- a/internal/vm/kernel_introspect.go +++ b/internal/vm/kernel_introspect.go @@ -965,9 +965,9 @@ func (vm *VM) ExitingSplit(err error) (ExitDisposition, bool) { } switch { case vm.classIsA(rerr.Class, "SystemExit"): - return ExitDisposition{Status: int(vm.exitStatusOf(rerr.Obj))}, true + return ExitDisposition{Status: clampInt(vm.exitStatusOf(rerr.Obj))}, true case vm.classIsA(rerr.Class, "SignalException"): - signo := int(vm.signoOf(rerr.Obj)) + signo := clampInt(vm.signoOf(rerr.Obj)) // A signal number of 0 is Signal.trap(:EXIT)'s slot and cannot kill // anything; treat it as a plain failure rather than re-raising signal 0, // which kill(2) defines as a liveness probe. diff --git a/internal/vm/kramdown_bind.go b/internal/vm/kramdown_bind.go index 20b8038c..c0b5944a 100644 --- a/internal/vm/kramdown_bind.go +++ b/internal/vm/kramdown_bind.go @@ -52,7 +52,7 @@ func kramdownOptions(opt object.Value) *kramdown.Options { o.HardWrap = val.Truthy() case "footnote_nr": if n, ok := val.(object.Integer); ok { - o.FootnoteNr = int(n) + o.FootnoteNr = clampInt(n) } } } diff --git a/internal/vm/lazy.go b/internal/vm/lazy.go index acd096dc..ba235197 100644 --- a/internal/vm/lazy.go +++ b/internal/vm/lazy.go @@ -160,10 +160,10 @@ func (vm *VM) registerLazy() { d("take_while", chain("take_while")) d("drop_while", chain("drop_while")) d("take", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { - return self.(*LazyEnum).with(lazyOp{kind: "take", n: int(intArg(args[0]))}) + return self.(*LazyEnum).with(lazyOp{kind: "take", n: clampInt(intArg(args[0]))}) }) d("drop", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { - return self.(*LazyEnum).with(lazyOp{kind: "drop", n: int(intArg(args[0]))}) + return self.(*LazyEnum).with(lazyOp{kind: "drop", n: clampInt(intArg(args[0]))}) }) // grep / grep_v take a pattern (matched with #===) and an optional block that // maps the elements that pass the filter. @@ -211,7 +211,7 @@ func (vm *VM) registerLazy() { d("with_index", func(vm *VM, self object.Value, args []object.Value, blk *Proc) object.Value { off := 0 if len(args) > 0 && !object.IsNil(args[0]) { // a nil offset means "start at 0" - off = int(coerceInt(vm, args[0])) + off = clampInt(coerceInt(vm, args[0])) } return self.(*LazyEnum).with(lazyOp{kind: "with_index", n: off, blk: blk}) }) @@ -324,7 +324,7 @@ func (vm *VM) registerLazy() { } return got[0] } - return object.NewArrayFromSlice(vm.lazyForce(self.(*LazyEnum), int(intArg(args[0])))) + return object.NewArrayFromSlice(vm.lazyForce(self.(*LazyEnum), clampInt(intArg(args[0])))) }) d("each", func(vm *VM, self object.Value, _ []object.Value, blk *Proc) object.Value { l := self.(*LazyEnum) diff --git a/internal/vm/ldap_bind.go b/internal/vm/ldap_bind.go index 19671e14..daa8787b 100644 --- a/internal/vm/ldap_bind.go +++ b/internal/vm/ldap_bind.go @@ -139,7 +139,7 @@ func ldapStrOpt(h *object.Hash, key, def string) string { // absent. func ldapIntOpt(h *object.Hash, key string, def int) int { if v, ok := h.Get(object.Symbol(key)); ok { - return int(intArg(v)) + return clampInt(intArg(v)) } return def } @@ -262,7 +262,7 @@ func ldapBoolResult(err error) object.Value { func ldapScope(v object.Value) ldap.Scope { switch x := v.(type) { case object.Integer: - return ldap.Scope(int(x)) + return ldap.Scope(clampInt(x)) case object.Symbol: switch string(x) { case "base", "base_object": diff --git a/internal/vm/logger.go b/internal/vm/logger.go index e23d209f..d3590301 100644 --- a/internal/vm/logger.go +++ b/internal/vm/logger.go @@ -219,7 +219,7 @@ func (lo *Logger) armPeriod(now time.Time) { func (lo *Logger) applyShiftAge(v object.Value) { switch x := v.(type) { case object.Integer: - lo.shiftAge = int(x) + lo.shiftAge = clampInt(x) case *object.String: lo.setPeriod(x.Str()) case object.Symbol: @@ -589,7 +589,7 @@ func (lo *Logger) applyKwargs(kw *object.Hash) { func severityArg(v object.Value) any { switch x := v.(type) { case object.Integer: - return int(x) + return clampInt(x) case *object.String: return x.Str() case object.Symbol: diff --git a/internal/vm/marshal.go b/internal/vm/marshal.go index 0c4a9a6d..ac5f0fcb 100644 --- a/internal/vm/marshal.go +++ b/internal/vm/marshal.go @@ -85,11 +85,11 @@ func (vm *VM) registerMarshal() { func marshalDumpLimit(args []object.Value) int { if len(args) >= 2 { if n, ok := args[1].(object.Integer); ok { - return int(n) + return clampInt(n) } if len(args) >= 3 { if n, ok := args[2].(object.Integer); ok { - return int(n) + return clampInt(n) } } } diff --git a/internal/vm/marshal_codec.go b/internal/vm/marshal_codec.go index b1ccb0d4..866d9c9a 100644 --- a/internal/vm/marshal_codec.go +++ b/internal/vm/marshal_codec.go @@ -196,7 +196,7 @@ func (d *mDumper) writeInt(b *big.Int) { if b.IsInt64() { if n := b.Int64(); n >= marshalFixnumMin && n <= marshalFixnumMax { d.buf = append(d.buf, 'i') - d.writeLong(int(n)) + d.writeLong(clampInt(n)) return } } @@ -1225,7 +1225,7 @@ func (r *mReader) applyIvar(base object.Value, name string, val object.Value) { // #zone as nil. case "offset": if off, ok := val.(object.Integer); ok { - b.setFixedZone("", int(off)) + b.setFixedZone("", clampInt(off)) } case "zone": if s, ok := val.(*object.String); ok { diff --git a/internal/vm/math.go b/internal/vm/math.go index a3ed9a6f..39c896ec 100644 --- a/internal/vm/math.go +++ b/internal/vm/math.go @@ -96,7 +96,7 @@ func mathInt32(x int64) int { if x < math.MinInt32 { raise("RangeError", "integer %d too small to convert to 'int'", x) } - return int(x) + return clampInt(x) } // mathBignumRange always raises: a Bignum exceeds a machine long, so MRI's @@ -265,6 +265,12 @@ func (vm *VM) registerMath() { install("ldexp", func(vm *VM, _ object.Value, args []object.Value, _ *Proc) object.Value { frac := mathFloat(vm, args[0]) exp := mathLdexpExp(vm, args[1]) + // math.Ldexp adds the fraction's own exponent and bias to exp in a Go + // int (math/ldexp.go: `exp += e`), which wraps on a 32-bit target when + // exp is near a C int's limits: ldexp(0.5, -2**31) came out Infinity. + // Any |exp| past 2**16 already under/overflows every float64, so + // limiting it there changes no result and leaves room for the sums. + exp = int(saturate(int64(exp), -1<<16, 1<<16)) return object.Float(math.Ldexp(frac, exp)) }) } diff --git a/internal/vm/matrix.go b/internal/vm/matrix.go index 497f7d5f..2814692c 100644 --- a/internal/vm/matrix.go +++ b/internal/vm/matrix.go @@ -342,10 +342,10 @@ func (vm *VM) registerMatrixClass() { if blk == nil { raise("LocalJumpError", "no block given (build)") } - r := int(args[0].(object.Integer)) + r := clampInt(args[0].(object.Integer)) c := r if len(args) > 1 { - c = int(args[1].(object.Integer)) + c = clampInt(args[1].(object.Integer)) } return matOK(libmatrix.Build(r, c, func(i, j int) any { res := vm.callBlock(blk, []object.Value{object.IntValue(int64(i)), object.IntValue(int64(j))}) @@ -354,17 +354,17 @@ func (vm *VM) registerMatrixClass() { }) identFn := func(_ *VM, _ object.Value, args []object.Value, _ *Proc) object.Value { - return &Matrix{m: libmatrix.Identity(int(args[0].(object.Integer)))} + return &Matrix{m: libmatrix.Identity(clampInt(args[0].(object.Integer)))} } sm("identity", identFn) sm("I", identFn) sm("unit", identFn) sm("zero", func(_ *VM, _ object.Value, args []object.Value, _ *Proc) object.Value { - r := int(args[0].(object.Integer)) + r := clampInt(args[0].(object.Integer)) c := r if len(args) > 1 { - c = int(args[1].(object.Integer)) + c = clampInt(args[1].(object.Integer)) } return &Matrix{m: libmatrix.Zero(r, c)} }) @@ -372,7 +372,7 @@ func (vm *VM) registerMatrixClass() { return matOK(libmatrix.Diagonal(argsToCells(args)...)) }) sm("scalar", func(_ *VM, _ object.Value, args []object.Value, _ *Proc) object.Value { - return matOK(libmatrix.Scalar(int(args[0].(object.Integer)), numFromValue(args[1]))) + return matOK(libmatrix.Scalar(clampInt(args[0].(object.Integer)), numFromValue(args[1]))) }) sm("row_vector", func(_ *VM, _ object.Value, args []object.Value, _ *Proc) object.Value { return matOK(libmatrix.RowVector(cellsFromValue(args[0]))) @@ -397,21 +397,21 @@ func (vm *VM) registerMatrixClass() { return object.IntValue(int64(self(v).m.ColumnCount())) }) d("[]", func(_ *VM, v object.Value, args []object.Value, _ *Proc) object.Value { - n, ok := self(v).m.At(int(args[0].(object.Integer)), int(args[1].(object.Integer))) + n, ok := self(v).m.At(clampInt(args[0].(object.Integer)), clampInt(args[1].(object.Integer))) if !ok { return object.NilV } return numToValue(n) }) d("row", func(_ *VM, v object.Value, args []object.Value, _ *Proc) object.Value { - vec, ok := self(v).m.Row(int(args[0].(object.Integer))) + vec, ok := self(v).m.Row(clampInt(args[0].(object.Integer))) if !ok { return object.NilV } return &Vector{v: vec} }) d("column", func(_ *VM, v object.Value, args []object.Value, _ *Proc) object.Value { - vec, ok := self(v).m.Column(int(args[0].(object.Integer))) + vec, ok := self(v).m.Column(clampInt(args[0].(object.Integer))) if !ok { return object.NilV } @@ -435,11 +435,11 @@ func (vm *VM) registerMatrixClass() { }) d("minor", func(_ *VM, v object.Value, args []object.Value, _ *Proc) object.Value { return matOK(self(v).m.Minor( - int(args[0].(object.Integer)), int(args[1].(object.Integer)), - int(args[2].(object.Integer)), int(args[3].(object.Integer)))) + clampInt(args[0].(object.Integer)), clampInt(args[1].(object.Integer)), + clampInt(args[2].(object.Integer)), clampInt(args[3].(object.Integer)))) }) d("first_minor", func(_ *VM, v object.Value, args []object.Value, _ *Proc) object.Value { - return matOK(self(v).m.FirstMinor(int(args[0].(object.Integer)), int(args[1].(object.Integer)))) + return matOK(self(v).m.FirstMinor(clampInt(args[0].(object.Integer)), clampInt(args[1].(object.Integer)))) }) d("to_a", func(_ *VM, v object.Value, _ []object.Value, _ *Proc) object.Value { rows := self(v).m.ToA() @@ -473,7 +473,7 @@ func (vm *VM) registerMatrixClass() { return matOK(self(v).m.DivScalar(numFromValue(args[0]))) }) d("**", func(_ *VM, v object.Value, args []object.Value, _ *Proc) object.Value { - return matOK(self(v).m.Pow(int(args[0].(object.Integer)))) + return matOK(self(v).m.Pow(clampInt(args[0].(object.Integer)))) }) d("-@", func(_ *VM, v object.Value, _ []object.Value, _ *Proc) object.Value { return &Matrix{m: self(v).m.Neg()} @@ -513,7 +513,7 @@ func (vm *VM) registerMatrixClass() { d("round", func(_ *VM, v object.Value, args []object.Value, _ *Proc) object.Value { n := 0 if len(args) > 0 { - n = int(args[0].(object.Integer)) + n = clampInt(args[0].(object.Integer)) } return &Matrix{m: self(v).m.RoundEntries(n)} }) @@ -617,7 +617,7 @@ func (vm *VM) registerVectorClass() { self := func(v object.Value) *Vector { return v.(*Vector) } d("[]", func(_ *VM, v object.Value, args []object.Value, _ *Proc) object.Value { - n, ok := self(v).v.At(int(args[0].(object.Integer))) + n, ok := self(v).v.At(clampInt(args[0].(object.Integer))) if !ok { return object.NilV } diff --git a/internal/vm/method_reflect.go b/internal/vm/method_reflect.go index cc9a84c0..22f6205f 100644 --- a/internal/vm/method_reflect.go +++ b/internal/vm/method_reflect.go @@ -96,7 +96,7 @@ func (vm *VM) registerMethodReflect() { required, total, hasSplat := methodParamInfo(b.m) need := required if len(args) > 0 { - need = int(intArg(args[0])) + need = clampInt(intArg(args[0])) if need < required || (!hasSplat && need > total) { raise("ArgumentError", "wrong number of arguments (given %d, expected %d)", need, required) } diff --git a/internal/vm/minitest_spec.go b/internal/vm/minitest_spec.go index d8e91042..544b86f3 100644 --- a/internal/vm/minitest_spec.go +++ b/internal/vm/minitest_spec.go @@ -299,7 +299,7 @@ func (vm *VM) minitestDescribe(args []object.Value, blk *Proc, super *RClass) ob func (vm *VM) minitestNextSpecSeq(cls *RClass) int { n := 0 if v, ok := getIvar(cls, "@__specs__").(object.Integer); ok { - n = int(v) + n = clampInt(v) } n++ setIvar(cls, "@__specs__", object.IntValue(int64(n))) diff --git a/internal/vm/money.go b/internal/vm/money.go index fb5d3156..6960af28 100644 --- a/internal/vm/money.go +++ b/internal/vm/money.go @@ -161,7 +161,7 @@ func (vm *VM) registerMoneyInstance(cls *RClass) { return moneySlice(moneyOf(self).Allocate(moneyInt64Slice(args[0]))) }) d("split", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { - return moneySlice(moneyOf(self).Split(int(intArg(args[0])))) + return moneySlice(moneyOf(self).Split(clampInt(intArg(args[0])))) }) // Exchange and formatting. diff --git a/internal/vm/mvvm.go b/internal/vm/mvvm.go index aa766e21..5e79fae1 100644 --- a/internal/vm/mvvm.go +++ b/internal/vm/mvvm.go @@ -146,7 +146,7 @@ func mvvmValueToAny(v object.Value) any { case object.Bool: return bool(x) case object.Integer: - return int(x) + return clampInt(x) case object.Float: return float64(x) case *object.String: diff --git a/internal/vm/mysql_bind.go b/internal/vm/mysql_bind.go index 9c75dd38..7a52e993 100644 --- a/internal/vm/mysql_bind.go +++ b/internal/vm/mysql_bind.go @@ -244,7 +244,7 @@ func mysqlConnectOptions(h *object.Hash) (mysql.Options, mysql.QueryOptions) { case "host": opts.Host = v.ToS() case "port": - opts.Port = int(mysqlInt(v)) + opts.Port = clampInt(mysqlInt(v)) case "username", "user": opts.Username = v.ToS() case "password", "pass": diff --git a/internal/vm/mysql_test.go b/internal/vm/mysql_test.go index 9469ce3e..4911e04e 100644 --- a/internal/vm/mysql_test.go +++ b/internal/vm/mysql_test.go @@ -2,7 +2,13 @@ // // SPDX-License-Identifier: BSD-3-Clause -//go:build !wasm && !rbgo_core +//go:build !wasm && !rbgo_core && !386 && !arm && !mips && !mipsle + +// The in-process server these tests run against, dolthub/go-mysql-server +// (v0.20.0, its latest release), does not compile for 32-bit targets: its +// sql/types and sql/rowexec packages assign math.MaxUint32 and larger +// constants to int. The mysql2 binding itself builds and runs there; only +// this server-backed test file is limited to 64-bit targets. package vm_test diff --git a/internal/vm/nats.go b/internal/vm/nats.go index b80bce00..b0cd3c25 100644 --- a/internal/vm/nats.go +++ b/internal/vm/nats.go @@ -185,7 +185,7 @@ func natsServers(v object.Value) []string { // natsInt coerces a Ruby Integer option to an int (0 for a non-Integer). func natsInt(v object.Value) int { if n, ok := v.(object.Integer); ok { - return int(n) + return clampInt(n) } return 0 } diff --git a/internal/vm/ndarray.go b/internal/vm/ndarray.go index 31987eb6..3d657b4c 100644 --- a/internal/vm/ndarray.go +++ b/internal/vm/ndarray.go @@ -95,7 +95,7 @@ func (vm *VM) ndTypeName(v object.Value) string { // ndInt reads an Integer argument (an index, an axis, a dimension). func (vm *VM) ndInt(v object.Value) int { if i, ok := v.(object.Integer); ok { - return int(i) + return clampInt(i) } raise("TypeError", "no implicit conversion of %s into Integer", vm.ndTypeName(v)) return 0 @@ -297,7 +297,7 @@ func (vm *VM) ndSpan(lo, hi object.Value, step int, excl bool, length int) nd.In func (vm *VM) ndIndex(x object.Value, length int) nd.Index { switch r := x.(type) { case object.Integer: - return nd.A(int(r)) + return nd.A(clampInt(r)) case object.Bool: if r { return nd.All() diff --git a/internal/vm/net_ftp.go b/internal/vm/net_ftp.go index 777ebb97..b7d26cc7 100644 --- a/internal/vm/net_ftp.go +++ b/internal/vm/net_ftp.go @@ -136,7 +136,7 @@ func (vm *VM) ftpApplyOpts(f *ftpObj, h *object.Hash, port int) (int, string, bo userGiven, passwdGiven := false, false if v, ok := ftpOptHash(h, "port"); ok { if n, ok := v.(object.Integer); ok { - port = int(n) + port = clampInt(n) } } if v, ok := ftpOptHash(h, "passive"); ok { @@ -235,7 +235,7 @@ func (vm *VM) registerFTPMethods(ftp *RClass) { ftpArity(a, 1, 2, "connect") port := 21 if len(a) >= 2 { - port = int(a[1].(object.Integer)) + port = clampInt(a[1].(object.Integer)) } vm.ftpConnect(self(v), strArg(a[0]), port) return object.NilV diff --git a/internal/vm/net_ftp_bind.go b/internal/vm/net_ftp_bind.go index dca04a72..1ddac309 100644 --- a/internal/vm/net_ftp_bind.go +++ b/internal/vm/net_ftp_bind.go @@ -484,8 +484,8 @@ func ftpBlocksizeArg(args []object.Value, i int) int { if i >= len(args) { return ftpDefaultBlocksize } - if n, ok := args[i].(object.Integer); ok && int(n) > 0 { - return int(n) + if n, ok := args[i].(object.Integer); ok && clampInt(n) > 0 { + return clampInt(n) } return ftpDefaultBlocksize } diff --git a/internal/vm/net_pop_bind.go b/internal/vm/net_pop_bind.go index 37dd88be..ee415bbb 100644 --- a/internal/vm/net_pop_bind.go +++ b/internal/vm/net_pop_bind.go @@ -172,7 +172,7 @@ func popStrAt(args []object.Value, i int) string { func popIntAt(args []object.Value, i, def int) int { if i < len(args) && !object.IsNil(args[i]) { if n, ok := args[i].(object.Integer); ok { - return int(n) + return clampInt(n) } } return def diff --git a/internal/vm/nokogiri.go b/internal/vm/nokogiri.go index 9f19ac93..fb38f6a7 100644 --- a/internal/vm/nokogiri.go +++ b/internal/vm/nokogiri.go @@ -333,7 +333,7 @@ func (vm *VM) registerNokogiriNodeSet(mod *RClass) { if len(args) == 0 { raise("ArgumentError", "wrong number of arguments (given 0, expected 1)") } - i := int(sqlite3IntArg(args[0])) + i := clampInt(sqlite3IntArg(args[0])) if i < 0 { i += self(v).Length() } diff --git a/internal/vm/numeric_edges.go b/internal/vm/numeric_edges.go index 42e8b1d8..f2431ac7 100644 --- a/internal/vm/numeric_edges.go +++ b/internal/vm/numeric_edges.go @@ -48,7 +48,7 @@ func (vm *VM) toRoundInt(a object.Value) int { if int64(v) < math.MinInt32 || int64(v) > math.MaxInt32 { raise("RangeError", "integer %d too big to convert to `int'", int64(v)) } - return int(v) + return clampInt(v) case *object.Bignum: raise("RangeError", "bignum too big to convert into `long'") case object.Float: @@ -371,7 +371,7 @@ func bitAt(n, i *big.Int) *big.Int { } return big.NewInt(0) } - return big.NewInt(int64(n.Bit(int(i.Int64())))) + return big.NewInt(int64(n.Bit(clampInt(i.Int64())))) } // bitSlice computes (n >> i) & ((1 << length) - 1) with Ruby semantics; a diff --git a/internal/vm/oauth2_bind.go b/internal/vm/oauth2_bind.go index 2e3b790b..2af56ec7 100644 --- a/internal/vm/oauth2_bind.go +++ b/internal/vm/oauth2_bind.go @@ -138,7 +138,7 @@ func (vm *VM) registerOAuth2Value(mod *RClass) { if len(args) == 0 { raise("ArgumentError", "wrong number of arguments (given 0, expected 1..3)") } - status := int(intArg(args[0])) + status := clampInt(intArg(args[0])) headers := oauth2.NewMap() if len(args) > 1 { if h, ok := args[1].(*object.Hash); ok { diff --git a/internal/vm/oidc.go b/internal/vm/oidc.go index d5ef135c..787c3e82 100644 --- a/internal/vm/oidc.go +++ b/internal/vm/oidc.go @@ -130,7 +130,7 @@ func (d oidcDoer) Do(req *oidc.HTTPRequest) (*oidc.HTTPResponse, error) { } resp := &oidc.HTTPResponse{Header: map[string]string{}} if v, ok := rh.Get(object.NewString("status")); ok { - resp.Status = int(intArg(v)) + resp.Status = clampInt(intArg(v)) } if v, ok := rh.Get(object.NewString("body")); ok { resp.Body = oidcStr(v) diff --git a/internal/vm/openbao_bind.go b/internal/vm/openbao_bind.go index b89dc4d4..7942a463 100644 --- a/internal/vm/openbao_bind.go +++ b/internal/vm/openbao_bind.go @@ -281,14 +281,14 @@ func vaultIntAt(args []object.Value, i int, def int) int { if object.IsNil(v) { return def } - return int(intArg(v)) + return clampInt(intArg(v)) } // vaultVersions reads a trailing list of version-number arguments starting at i. func vaultVersions(args []object.Value, i int) []int { var out []int for _, a := range args[min(i, len(args)):] { - out = append(out, int(intArg(a))) + out = append(out, clampInt(intArg(a))) } return out } diff --git a/internal/vm/openssl.go b/internal/vm/openssl.go index c34ccfc9..f00b9715 100644 --- a/internal/vm/openssl.go +++ b/internal/vm/openssl.go @@ -404,11 +404,12 @@ func (vm *VM) registerOpenSSLPKI(mod, errRoot *RClass) { ssl.consts["VERIFY_FAIL_IF_NO_PEER_CERT"] = object.IntValue(2) ssl.consts["VERIFY_CLIENT_ONCE"] = object.IntValue(4) // TLS option bit flags Puppet's monkey-patch ORs into DEFAULT_PARAMS. - for k, v := range map[string]int{ + // int64: OP_ALL (0x80000bff) does not fit a 32-bit int. + for k, v := range map[string]int64{ "OP_NO_SSLv2": 0x01000000, "OP_NO_SSLv3": 0x02000000, "OP_NO_TLSv1": 0x04000000, "OP_ALL": 0x80000bff, } { - ssl.consts[k] = object.IntValue(int64(v)) + ssl.consts[k] = object.IntValue(v) } // OpenSSL::SSL::SSLContext is constructible (Puppet reopens it and builds one), diff --git a/internal/vm/opentype.go b/internal/vm/opentype.go index db146606..398e30fb 100644 --- a/internal/vm/opentype.go +++ b/internal/vm/opentype.go @@ -126,7 +126,7 @@ func otValueToAny(v object.Value) any { case object.Bool: return bool(x) case object.Integer: - return int(x) + return clampInt(x) case object.Float: return float64(x) case *object.String: diff --git a/internal/vm/optparse.go b/internal/vm/optparse.go index 5672b7fc..04f0f213 100644 --- a/internal/vm/optparse.go +++ b/internal/vm/optparse.go @@ -100,7 +100,7 @@ func (vm *VM) registerOptionParser() { } if len(args) >= 2 { if n, ok := args[1].(object.Integer); ok { - op.p.SummaryWidth = int(n) + op.p.SummaryWidth = clampInt(n) } } if len(args) >= 3 { @@ -192,7 +192,7 @@ func (vm *VM) registerOptionParser() { return object.IntValue(int64(opOf(v).p.SummaryWidth)) }) d("summary_width=", func(_ *VM, v object.Value, args []object.Value, _ *Proc) object.Value { - opOf(v).p.SummaryWidth = int(intArg(args[0])) + opOf(v).p.SummaryWidth = clampInt(intArg(args[0])) return args[0] }) d("summary_indent", func(_ *VM, v object.Value, _ []object.Value, _ *Proc) object.Value { diff --git a/internal/vm/pack.go b/internal/vm/pack.go index e7ca9650..2d9bcfa0 100644 --- a/internal/vm/pack.go +++ b/internal/vm/pack.go @@ -137,7 +137,7 @@ func (vm *VM) unpackOffset(args []object.Value, byteLen int) int { if n > int64(byteLen) { raise("ArgumentError", "offset outside of string") } - return int(n) + return clampInt(n) } // packFormat extracts the mandatory format argument, which is a String or any diff --git a/internal/vm/pagy.go b/internal/vm/pagy.go index dab9b8e7..a114fe5c 100644 --- a/internal/vm/pagy.go +++ b/internal/vm/pagy.go @@ -110,7 +110,7 @@ func (vm *VM) registerPagyInstance(cls *RClass) { if len(args) == 0 { return pagySeries(p.Series()) } - s, err := p.SeriesSize(int(intArg(args[0]))) + s, err := p.SeriesSize(clampInt(intArg(args[0]))) if err != nil { raise("Pagy::VariableError", "%s", err.Error()) } diff --git a/internal/vm/pagy_bind.go b/internal/vm/pagy_bind.go index accf4fbb..0444b4a3 100644 --- a/internal/vm/pagy_bind.go +++ b/internal/vm/pagy_bind.go @@ -45,17 +45,17 @@ func pagyVarsFromHash(h *object.Hash) pagy.Vars { pagyEachOpt(h, func(key string, val object.Value) { switch key { case "count": - v.Count = int(intArg(val)) + v.Count = clampInt(intArg(val)) case "page": - v.Page = int(intArg(val)) + v.Page = clampInt(intArg(val)) case "items", "limit": - v.Items = int(intArg(val)) + v.Items = clampInt(intArg(val)) case "outset": - v.Outset = int(intArg(val)) + v.Outset = clampInt(intArg(val)) case "size": - v.Size = int(intArg(val)) + v.Size = clampInt(intArg(val)) case "max_pages": - v.MaxPages = int(intArg(val)) + v.MaxPages = clampInt(intArg(val)) case "cycle": v.Cycle = val.Truthy() case "ends": diff --git a/internal/vm/parquet.go b/internal/vm/parquet.go index 6969e9db..8152aeb1 100644 --- a/internal/vm/parquet.go +++ b/internal/vm/parquet.go @@ -163,7 +163,7 @@ func (vm *VM) registerParquetReader(cls *RClass) { }) d("read_row_group", func(_ *VM, v object.Value, args []object.Value, _ *Proc) object.Value { parquetArity(args, 1, "read_row_group") - return parquetTableOK(self(v).r.ReadRowGroup(int(parquetInt(args[0])))) + return parquetTableOK(self(v).r.ReadRowGroup(clampInt(parquetInt(args[0])))) }) nRows := func(_ *VM, v object.Value, _ []object.Value, _ *Proc) object.Value { return object.IntValue(self(v).r.NumRows()) diff --git a/internal/vm/pg.go b/internal/vm/pg.go index ba466666..422f411f 100644 --- a/internal/vm/pg.go +++ b/internal/vm/pg.go @@ -225,7 +225,7 @@ func (vm *VM) registerPGResult(mod *RClass) { // #fname(i) returns column i's name. d("fname", func(vm *VM, v object.Value, args []object.Value, _ *Proc) object.Value { - name, err := self(v).Fname(int(pgIntArg(pgArg0(args)))) + name, err := self(v).Fname(clampInt(pgIntArg(pgArg0(args)))) if err != nil { raise("PG::Error", "%s", err.Error()) } @@ -242,7 +242,7 @@ func (vm *VM) registerPGResult(mod *RClass) { if len(args) < 2 { raise("ArgumentError", "wrong number of arguments (given %d, expected 2)", len(args)) } - val, err := self(v).Getvalue(int(pgIntArg(args[0])), int(pgIntArg(args[1]))) + val, err := self(v).Getvalue(clampInt(pgIntArg(args[0])), clampInt(pgIntArg(args[1]))) if err != nil { raise("PG::Error", "%s", err.Error()) } @@ -254,7 +254,7 @@ func (vm *VM) registerPGResult(mod *RClass) { if len(args) < 2 { raise("ArgumentError", "wrong number of arguments (given %d, expected 2)", len(args)) } - null, err := self(v).Getisnull(int(pgIntArg(args[0])), int(pgIntArg(args[1]))) + null, err := self(v).Getisnull(clampInt(pgIntArg(args[0])), clampInt(pgIntArg(args[1]))) if err != nil { raise("PG::Error", "%s", err.Error()) } @@ -277,7 +277,7 @@ func (vm *VM) registerPGResult(mod *RClass) { // #[](i) / #tuple(i) return row i as a Hash keyed by column name. tupleFn := func(vm *VM, v object.Value, args []object.Value, _ *Proc) object.Value { - t, err := self(v).Tuple(int(pgIntArg(pgArg0(args)))) + t, err := self(v).Tuple(clampInt(pgIntArg(pgArg0(args)))) if err != nil { raise("PG::Error", "%s", err.Error()) } diff --git a/internal/vm/prettyprint.go b/internal/vm/prettyprint.go index 7ead396e..12ad7efb 100644 --- a/internal/vm/prettyprint.go +++ b/internal/vm/prettyprint.go @@ -72,7 +72,7 @@ func (vm *VM) ppText(v object.Value) string { // when given, otherwise the string's character length (MRI's obj.length default). func ppWidth(s string, args []object.Value, idx int) int { if len(args) > idx { - return int(intArg(args[idx])) + return clampInt(intArg(args[idx])) } return utf8.RuneCountInString(s) } @@ -101,7 +101,7 @@ func (vm *VM) ppNewArgs(args []object.Value) (prefix string, maxwidth int, newli } if len(args) > 1 { if _, isNil := args[1].(object.Nil); !isNil { - maxwidth = int(intArg(args[1])) + maxwidth = clampInt(intArg(args[1])) } } if len(args) > 2 { @@ -190,7 +190,7 @@ func (vm *VM) registerPrettyPrint() { raise("LocalJumpError", "no block given (nest)") } p := self(v) - indent := int(intArg(args[0])) + indent := clampInt(intArg(args[0])) p.q.Nest(indent, func() { vm.callBlock(blk, nil) }) return object.NilV }) @@ -205,7 +205,7 @@ func (vm *VM) registerPrettyPrint() { p := self(v) indent := 0 if len(args) > 0 { - indent = int(intArg(args[0])) + indent = clampInt(intArg(args[0])) } openObj, closeObj := "", "" if len(args) > 1 { diff --git a/internal/vm/prime_bind.go b/internal/vm/prime_bind.go index 86787ecc..a19c7212 100644 --- a/internal/vm/prime_bind.go +++ b/internal/vm/prime_bind.go @@ -52,7 +52,7 @@ func (vm *VM) installPrime() { // Prime.take(n) / Prime.first(n) — the first n primes as an Array. takeFn := func(_ *VM, _ object.Value, args []object.Value, _ *Proc) object.Value { - n := int(intArg(args[0])) + n := clampInt(intArg(args[0])) return primesToArray(prime.Take(n)) } sm("take", takeFn) diff --git a/internal/vm/process.go b/internal/vm/process.go index f847aa8b..00f06f25 100644 --- a/internal/vm/process.go +++ b/internal/vm/process.go @@ -227,7 +227,7 @@ func (vm *VM) defineProcessLimits(def func(string, NativeFn)) { if len(args) != 2 { raise("ArgumentError", "wrong number of arguments (given %d, expected 2)", len(args)) } - prio, err := procGetpriority(int(vm.procToInt(args[0])), int(vm.procToInt(args[1]))) + prio, err := procGetpriority(clampInt(vm.procToInt(args[0])), clampInt(vm.procToInt(args[1]))) if err != nil && getpriorityFailed(prio, err) { sysFail(err, "") } @@ -237,7 +237,7 @@ func (vm *VM) defineProcessLimits(def func(string, NativeFn)) { if len(args) != 3 { raise("ArgumentError", "wrong number of arguments (given %d, expected 3)", len(args)) } - err := procSetpriority(int(vm.procToInt(args[0])), int(vm.procToInt(args[1])), int(vm.procToInt(args[2]))) + err := procSetpriority(clampInt(vm.procToInt(args[0])), clampInt(vm.procToInt(args[1])), clampInt(vm.procToInt(args[2]))) if err != nil { sysFail(err, "") } @@ -254,7 +254,7 @@ func (vm *VM) defineProcessGroups(def func(string, NativeFn)) { if len(args) != 1 { raise("ArgumentError", "wrong number of arguments (given %d, expected 1)", len(args)) } - pgid, err := procGetpgid(int(vm.procToInt(args[0]))) + pgid, err := procGetpgid(clampInt(vm.procToInt(args[0]))) if err != nil { sysFail(err, "") } @@ -272,7 +272,7 @@ func (vm *VM) defineProcessGroups(def func(string, NativeFn)) { def("getsid", func(vm *VM, _ object.Value, args []object.Value, _ *Proc) object.Value { pid := 0 if len(args) > 0 && !object.IsNil(args[0]) { - pid = int(vm.procToInt(args[0])) + pid = clampInt(vm.procToInt(args[0])) } sid, err := procGetsid(pid) if err != nil { @@ -285,7 +285,7 @@ func (vm *VM) defineProcessGroups(def func(string, NativeFn)) { if len(args) != 2 { raise("ArgumentError", "wrong number of arguments (given %d, expected 2)", len(args)) } - if err := procSetpgid(int(vm.procToInt(args[0])), int(vm.procToInt(args[1]))); err != nil { + if err := procSetpgid(clampInt(vm.procToInt(args[0])), clampInt(vm.procToInt(args[1]))); err != nil { sysFail(err, "") } return object.IntValue(0) @@ -318,7 +318,7 @@ func (vm *VM) defineProcessKill(def func(string, NativeFn)) { sig := vm.killSignalNumber(args[0]) self := os.Getpid() for _, pv := range args[1:] { - pid, send := int(vm.procToInt(pv)), sig + pid, send := clampInt(vm.procToInt(pv)), sig if sig < 0 { // killpg(pgid, sig) == kill(-pgid, sig) pid, send = -pid, -sig } @@ -374,7 +374,7 @@ func rlimValue(lim uint64) object.Value { func (vm *VM) rlimitResourceType(v object.Value) int { name, named := vm.rlimitName(v) if !named { - return int(vm.procToInt(v)) + return clampInt(vm.procToInt(v)) } if res, ok := rlimitResources[name]; ok { return res @@ -517,7 +517,7 @@ func sysFail(err error, op string) { // restored, which is what MRI prints for both "FOO" and "SIGFOO". func (vm *VM) killSignalNumber(v object.Value) int { if i, ok := v.(object.Integer); ok { - return int(i) + return clampInt(i) } name, named := vm.rlimitName(v) // Symbol / String / #to_str, as signm2signo takes if !named { diff --git a/internal/vm/protobuf.go b/internal/vm/protobuf.go index 73482902..ef0843ca 100644 --- a/internal/vm/protobuf.go +++ b/internal/vm/protobuf.go @@ -164,27 +164,27 @@ func (vm *VM) registerProtobufBuilders(builderCls, msgBuilderCls, oneofBuilderCl msgBuilderCls.define("optional", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { mb := self.(*ProtobufMessageBuilder).mb if tn := pbTypeName(args, 3); tn != "" { - mb.Optional(pbName(args[0]), pbSym(args[1]), int(intArg(args[2])), tn) + mb.Optional(pbName(args[0]), pbSym(args[1]), clampInt(intArg(args[2])), tn) } else { - mb.Optional(pbName(args[0]), pbSym(args[1]), int(intArg(args[2]))) + mb.Optional(pbName(args[0]), pbSym(args[1]), clampInt(intArg(args[2]))) } return object.NilV }) msgBuilderCls.define("repeated", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { mb := self.(*ProtobufMessageBuilder).mb if tn := pbTypeName(args, 3); tn != "" { - mb.Repeated(pbName(args[0]), pbSym(args[1]), int(intArg(args[2])), tn) + mb.Repeated(pbName(args[0]), pbSym(args[1]), clampInt(intArg(args[2])), tn) } else { - mb.Repeated(pbName(args[0]), pbSym(args[1]), int(intArg(args[2]))) + mb.Repeated(pbName(args[0]), pbSym(args[1]), clampInt(intArg(args[2]))) } return object.NilV }) msgBuilderCls.define("map", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { mb := self.(*ProtobufMessageBuilder).mb if tn := pbTypeName(args, 4); tn != "" { - mb.Map(pbName(args[0]), pbSym(args[1]), pbSym(args[2]), int(intArg(args[3])), tn) + mb.Map(pbName(args[0]), pbSym(args[1]), pbSym(args[2]), clampInt(intArg(args[3])), tn) } else { - mb.Map(pbName(args[0]), pbSym(args[1]), pbSym(args[2]), int(intArg(args[3]))) + mb.Map(pbName(args[0]), pbSym(args[1]), pbSym(args[2]), clampInt(intArg(args[3]))) } return object.NilV }) @@ -198,15 +198,15 @@ func (vm *VM) registerProtobufBuilders(builderCls, msgBuilderCls, oneofBuilderCl oneofBuilderCls.define("optional", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { ob := self.(*ProtobufOneofBuilder).ob if tn := pbTypeName(args, 3); tn != "" { - ob.Optional(pbName(args[0]), pbSym(args[1]), int(intArg(args[2])), tn) + ob.Optional(pbName(args[0]), pbSym(args[1]), clampInt(intArg(args[2])), tn) } else { - ob.Optional(pbName(args[0]), pbSym(args[1]), int(intArg(args[2]))) + ob.Optional(pbName(args[0]), pbSym(args[1]), clampInt(intArg(args[2]))) } return object.NilV }) enumBuilderCls.define("value", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { - self.(*ProtobufEnumBuilder).eb.Value(pbSym(args[0]), int(intArg(args[1]))) + self.(*ProtobufEnumBuilder).eb.Value(pbSym(args[0]), clampInt(intArg(args[1]))) return object.NilV }) } @@ -246,7 +246,7 @@ func (vm *VM) registerProtobufDescriptors(descCls, enumDescCls, fieldDescCls *RC return object.IntValue(int64(n)) }) enumDescCls.define("lookup_value", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { - s, ok := self.(*ProtobufEnumDescriptor).ed.LookupValue(int(intArg(args[0]))) + s, ok := self.(*ProtobufEnumDescriptor).ed.LookupValue(clampInt(intArg(args[0]))) if !ok { return object.NilV } @@ -394,10 +394,10 @@ func (vm *VM) registerProtobufRepeated(repeatedCls *RClass) { return v }) repeatedCls.define("[]", func(vm *VM, v object.Value, args []object.Value, _ *Proc) object.Value { - return vm.pbToRuby(self(v).At(int(intArg(args[0])))) + return vm.pbToRuby(self(v).At(clampInt(intArg(args[0])))) }) repeatedCls.define("[]=", func(vm *VM, v object.Value, args []object.Value, _ *Proc) object.Value { - if err := self(v).SetAt(int(intArg(args[0])), vm.rubyToPB(args[1])); err != nil { + if err := self(v).SetAt(clampInt(intArg(args[0])), vm.rubyToPB(args[1])); err != nil { raisePBError(err) } return args[1] diff --git a/internal/vm/puma_bind.go b/internal/vm/puma_bind.go index c569b75d..0efeb4e2 100644 --- a/internal/vm/puma_bind.go +++ b/internal/vm/puma_bind.go @@ -343,10 +343,10 @@ func pumaFirst(args []object.Value, name string) object.Value { // non-Integer (a thread count / port / status must be an Integer). func pumaInt(v object.Value) int { if n, ok := v.(object.Integer); ok { - return int(n) + return clampInt(n) } if n, ok := object.BigOf(v); ok { - return int(n.Int64()) + return clampInt(n.Int64()) } raise("TypeError", "expected an Integer, got %s", v.Inspect()) panic("unreachable") diff --git a/internal/vm/queue.go b/internal/vm/queue.go index 77648ff2..7a7c2ee4 100644 --- a/internal/vm/queue.go +++ b/internal/vm/queue.go @@ -131,7 +131,7 @@ func (vm *VM) registerSizedQueue(cQueue *RClass) { if n <= 0 { raise("ArgumentError", "queue size must be positive") } - return &RQueue{max: int(n), class: classOfReceiver(self, cSized)} + return &RQueue{max: clampInt(n), class: classOfReceiver(self, cSized)} }} cSized.define("max", func(_ *VM, self object.Value, _ []object.Value, _ *Proc) object.Value { return object.IntValue(int64(self.(*RQueue).max)) @@ -142,8 +142,8 @@ func (vm *VM) registerSizedQueue(cQueue *RClass) { raise("ArgumentError", "queue size must be positive") } q := self.(*RQueue) - grew := int(n) > q.max - q.max = int(n) + grew := clampInt(n) > q.max + q.max = clampInt(n) // A larger capacity may unblock waiting pushers; a smaller one never drops // items already enqueued beyond the new maximum. if grew { diff --git a/internal/vm/racc_bind.go b/internal/vm/racc_bind.go index 06e80357..b2a8f735 100644 --- a/internal/vm/racc_bind.go +++ b/internal/vm/racc_bind.go @@ -31,7 +31,7 @@ const raccNilCell = -1 << 30 // raccIntSlice, which maps nil to raccNilCell instead. func raccInt(v object.Value) int { if n, ok := v.(object.Integer); ok { - return int(n) + return clampInt(n) } return 0 } @@ -48,7 +48,7 @@ func raccIntSlice(v object.Value) []int { out := make([]int, len(arr.Elems)) for i, e := range arr.Elems { if n, ok := e.(object.Integer); ok { - out[i] = int(n) + out[i] = clampInt(n) } else { out[i] = raccNilCell } diff --git a/internal/vm/rack_helpers.go b/internal/vm/rack_helpers.go index d0a6561f..40b1733c 100644 --- a/internal/vm/rack_helpers.go +++ b/internal/vm/rack_helpers.go @@ -31,7 +31,7 @@ func rackStr(v object.Value) string { func rackInt(v object.Value, def int) int { switch n := v.(type) { case object.Integer: - return int(n) + return clampInt(n) case *object.String: if i, err := strconv.Atoi(n.Str()); err == nil { return i diff --git a/internal/vm/rbnacl.go b/internal/vm/rbnacl.go index ab9c7c4a..fdcaf9ad 100644 --- a/internal/vm/rbnacl.go +++ b/internal/vm/rbnacl.go @@ -68,7 +68,7 @@ func (vm *VM) registerRbNaClRandom(mod *RClass) { c.isModule = true mod.consts["Random"] = c smethod(c, "random_bytes", func(_ *VM, _ object.Value, args []object.Value, _ *Proc) object.Value { - return naclString(sodium.RandomBytes(int(intArg(args[0])))) + return naclString(sodium.RandomBytes(clampInt(intArg(args[0])))) }) } @@ -294,7 +294,7 @@ func (vm *VM) registerRbNaClPasswordHash(mod *RClass) { smethod(c, "scrypt", func(_ *VM, _ object.Value, args []object.Value, _ *Proc) object.Value { out, err := sodium.Scrypt(naclBytes(args[0]), naclBytes(args[1]), - int(intArg(args[2])), int(intArg(args[3])), int(intArg(args[4])), int(intArg(args[5]))) + clampInt(intArg(args[2])), clampInt(intArg(args[3])), clampInt(intArg(args[4])), clampInt(intArg(args[5]))) if err != nil { return raiseSodiumError(err) } diff --git a/internal/vm/rbnacl_bind.go b/internal/vm/rbnacl_bind.go index acbaa6c7..42395c81 100644 --- a/internal/vm/rbnacl_bind.go +++ b/internal/vm/rbnacl_bind.go @@ -104,7 +104,7 @@ func naclBlake2bOpts(rest []object.Value) sodium.Blake2bOptions { return o } if v, ok := h.Get(object.Symbol("digest_size")); ok { - o.DigestSize = int(intArg(v)) + o.DigestSize = clampInt(intArg(v)) } if v, ok := h.Get(object.Symbol("key")); ok { o.Key = naclBytes(v) diff --git a/internal/vm/redis_bind.go b/internal/vm/redis_bind.go index 49c88d61..3d034b65 100644 --- a/internal/vm/redis_bind.go +++ b/internal/vm/redis_bind.go @@ -239,7 +239,7 @@ func redisOptsFromHash(h *object.Hash, opts *redis.Options) object.Value { conn = v } if v, ok := get("db"); ok { - opts.DB = int(redisInt(v)) + opts.DB = clampInt(redisInt(v)) } if v, ok := get("username"); ok { opts.Username = redisStr(v) @@ -248,7 +248,7 @@ func redisOptsFromHash(h *object.Hash, opts *redis.Options) object.Value { opts.Password = redisStr(v) } if v, ok := get("protocol"); ok { - opts.Protocol = int(redisInt(v)) + opts.Protocol = clampInt(redisInt(v)) } return conn } diff --git a/internal/vm/regexp.go b/internal/vm/regexp.go index 12da2160..ae52d9c8 100644 --- a/internal/vm/regexp.go +++ b/internal/vm/regexp.go @@ -1108,7 +1108,7 @@ func coerceTimeout(v object.Value) object.Value { // option-letter form, which returns false, false). func regexpEncodingBits(v object.Value) (fixed, no bool) { if bits, ok := v.(object.Integer); ok { - return int(bits)&reFixedEncoding != 0, int(bits)&reNoEncoding != 0 + return clampInt(bits)&reFixedEncoding != 0, clampInt(bits)&reNoEncoding != 0 } return false, false } @@ -1124,7 +1124,7 @@ func (vm *VM) regexpOptionFlags(v object.Value) string { case object.Nil: return "" case object.Integer: - return flagsFromBits(int(opt)) + return flagsFromBits(clampInt(opt)) case *object.String: return flagsFromLetters(opt.Str()) case object.Bool: @@ -1226,7 +1226,7 @@ func (vm *VM) regexpMatchP(re *Regexp, args []object.Value) object.Value { } // Search from the cursor with the WHOLE subject visible, as // rb_reg_search does — /\A/.match?("hello", 2) is false, not true. - md, _ := re.searchFrom(vm, subject, charToByte(subject, int(pos))) + md, _ := re.searchFrom(vm, subject, charToByte(subject, clampInt(pos))) return object.Bool(md != nil) } return object.Bool(re.matchString(vm, subject)) @@ -1473,7 +1473,7 @@ func (vm *VM) runMatchFrom(re *Regexp, subject, enc string, pos int64) object.Va vm.lastMatch = object.NilV return object.NilV } - byteOff := charToByte(subject, int(pos)) + byteOff := charToByte(subject, clampInt(pos)) md, base := re.searchFrom(vm, subject, byteOff) if md == nil { vm.lastMatch = object.NilV @@ -1942,7 +1942,7 @@ func (vm *VM) stringSplit(subject, enc string, args []object.Value) object.Value if len(args) >= 2 { // A nil limit is not a default — MRI raises TypeError for split(p, nil) — // so coerceInt handles it (nil has no #to_int) alongside real conversions. - limit = int(coerceInt(vm, args[1])) + limit = clampInt(coerceInt(vm, args[1])) } if splitOnWhitespace(args) { return splitWhitespace(subject, limit, enc) @@ -3089,7 +3089,7 @@ func (vm *VM) installRegexp() { if len(args) == 2 { // md[start, length] all := m.allGroups() start := normIndex(intArg(args[0]), len(all)) - length := int(intArg(args[1])) + length := clampInt(intArg(args[1])) if start < 0 || start > len(all) || length < 0 { return object.NilV } @@ -3179,9 +3179,9 @@ func stringLike(v object.Value) (string, bool) { func (m *MatchData) offset(i int64, end bool) object.Value { var b int if end { - b = m.md.End(int(i)) + b = m.md.End(clampInt(i)) } else { - b = m.md.Begin(int(i)) + b = m.md.Begin(clampInt(i)) } if b < 0 { return object.NilV @@ -3195,9 +3195,9 @@ func (m *MatchData) offset(i int64, end bool) object.Value { func (m *MatchData) byteOffset(i int64, end bool) object.Value { var b int if end { - b = m.md.End(int(i)) + b = m.md.End(clampInt(i)) } else { - b = m.md.Begin(int(i)) + b = m.md.Begin(clampInt(i)) } if b < 0 { return object.NilV @@ -3210,7 +3210,7 @@ func (m *MatchData) byteOffset(i int64, end bool) object.Value { func (m *MatchData) at(key object.Value) object.Value { switch k := key.(type) { case object.Integer: - return m.intGroup(int(k)) + return m.intGroup(clampInt(k)) case *object.String: return m.byName(k.Str()) case object.Symbol: @@ -3288,7 +3288,7 @@ func (m *MatchData) byName(name string) object.Value { func (m *MatchData) indexForKey(vm *VM, key object.Value) int { switch k := key.(type) { case object.Integer: - i := int(k) + i := clampInt(k) if i < 0 || i > m.md.NGroups() { raise("IndexError", "index %d out of matches", i) } @@ -3337,14 +3337,14 @@ func (m *MatchData) rangeValuesAt(r *object.Range) []object.Value { n := m.md.NGroups() + 1 lo := 0 if !object.IsNil(r.Lo) { - lo = int(intArg(r.Lo)) + lo = clampInt(intArg(r.Lo)) if lo < 0 { lo += n } } hi := n - 1 if !object.IsNil(r.Hi) { - hi = int(intArg(r.Hi)) + hi = clampInt(intArg(r.Hi)) if hi < 0 { hi += n } diff --git a/internal/vm/reline_bind.go b/internal/vm/reline_bind.go index 414a61f4..94bef67c 100644 --- a/internal/vm/reline_bind.go +++ b/internal/vm/reline_bind.go @@ -7,7 +7,7 @@ package vm import ( - "strings" + "math" "github.com/go-embedded-ruby/ruby/internal/object" reline "github.com/go-ruby-reline/reline" @@ -46,14 +46,14 @@ func (vm *VM) newRelineHistory(mod *RClass, h *reline.History) *RelineHistory { return self }) def("[]", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { - s, err := self.(*RelineHistory).h.Get(int(intArg(args[0]))) + s, err := self.(*RelineHistory).h.Get(relineIndex(self.(*RelineHistory).h, args[0])) if err != nil { raiseHistoryError(err) } return object.NewString(s) }) def("[]=", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { - if err := self.(*RelineHistory).h.Set(int(intArg(args[0])), strArg(args[1])); err != nil { + if err := self.(*RelineHistory).h.Set(relineIndex(self.(*RelineHistory).h, args[0]), strArg(args[1])); err != nil { raiseHistoryError(err) } return args[1] @@ -68,7 +68,7 @@ func (vm *VM) newRelineHistory(mod *RClass, h *reline.History) *RelineHistory { return self }) def("delete_at", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { - s, err := self.(*RelineHistory).h.DeleteAt(int(intArg(args[0]))) + s, err := self.(*RelineHistory).h.DeleteAt(relineIndex(self.(*RelineHistory).h, args[0])) if err != nil { raiseHistoryError(err) } @@ -97,7 +97,7 @@ func (vm *VM) newRelineHistory(mod *RClass, h *reline.History) *RelineHistory { def("last", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { entries := relineHistEntries(self.(*RelineHistory).h) if len(args) > 0 { - n := int(intArg(args[0])) + n := clampInt(intArg(args[0])) if n > len(entries) { n = len(entries) } @@ -117,6 +117,23 @@ func relineHistSize(_ *VM, self object.Value, _ []object.Value, _ *Proc) object. return object.IntValue(int64(self.(*RelineHistory).h.Size())) } +// relineIndex converts a history index for Get/Set/DeleteAt. The library's own +// check_index range test (index outside a C int => RangeError) compares a Go +// int, which on a 32-bit target can never be outside a C int once converted; +// so the test is made here, on the 64-bit value, after the same negative-index +// adjustment, with the library's message. +func relineIndex(h *reline.History, v object.Value) int { + idx := intArg(v) + adj := idx + if adj < 0 { + adj += int64(h.Size()) + } + if adj < math.MinInt32 || adj > math.MaxInt32 { + raise("RangeError", "integer %d too big to convert to 'int'", adj) + } + return clampInt(idx) +} + // relineHistEntries snapshots every history entry as Ruby Strings, in order. func relineHistEntries(h *reline.History) []object.Value { out := make([]object.Value, 0, h.Size()) @@ -128,12 +145,10 @@ func relineHistEntries(h *reline.History) []object.Value { } // raiseHistoryError surfaces a library HistoryError as the matching MRI -// exception: the overflow message maps to RangeError, an out-of-bounds index to -// IndexError (mirroring Reline::History#check_index). +// exception: an out-of-bounds index is IndexError (mirroring +// Reline::History#check_index). check_index's other answer, RangeError for an +// index outside a C int, never comes from the library here: relineIndex raises +// it first, on the 64-bit value. func raiseHistoryError(err error) { - msg := err.Error() - if strings.HasPrefix(msg, "integer ") { - raise("RangeError", "%s", msg) - } - raise("IndexError", "%s", msg) + raise("IndexError", "%s", err.Error()) } diff --git a/internal/vm/rexml.go b/internal/vm/rexml.go index 38c06951..ddb03c77 100644 --- a/internal/vm/rexml.go +++ b/internal/vm/rexml.go @@ -317,13 +317,13 @@ func rexmlWriteString(d *rx.Document, args []object.Value) string { func rexmlIndentArg(args []object.Value) (int, bool) { if len(args) >= 2 { if n, ok := args[1].(object.Integer); ok { - return int(n), true + return clampInt(n), true } return 0, false } if len(args) == 1 { if n, ok := args[0].(object.Integer); ok { - return int(n), true + return clampInt(n), true } } return 0, false @@ -388,7 +388,7 @@ func (vm *VM) registerREXMLElement(mod *RClass) { e := elem(v) if n, ok := args[0].(object.Integer); ok { // Element#[Integer] is 0-based; the library's ElementAt is 1-based. - ch := e.ElementAt(int(n) + 1) + ch := e.ElementAt(clampInt(n) + 1) if ch == nil { return object.NilV } @@ -458,7 +458,7 @@ func (vm *VM) registerREXMLElements(mod *RClass) { e := owner(v) if n, ok := args[0].(object.Integer); ok { // Elements#[Integer] is 1-based, matching the library's ElementAt. - ch := e.ElementAt(int(n)) + ch := e.ElementAt(clampInt(n)) if ch == nil { return object.NilV } @@ -663,7 +663,7 @@ func (vm *VM) registerREXMLFormatters(mod *RClass) { indent := 2 if len(args) > 0 { if n, ok := args[0].(object.Integer); ok { - indent = int(n) + indent = clampInt(n) } } obj := &RObject{class: pretty, ivars: map[string]object.Value{}} @@ -677,7 +677,7 @@ func (vm *VM) registerREXMLFormatters(mod *RClass) { // ArgumentError, matching MRI's Formatters::Pretty#write. pretty.define("write", func(vm *VM, self object.Value, args []object.Value, _ *Proc) object.Value { // @indentation is always the Integer the constructor stored. - indent := int(self.(*RObject).ivars["@indentation"].(object.Integer)) + indent := clampInt(self.(*RObject).ivars["@indentation"].(object.Integer)) dv, ok := args[0].(*REXMLDocument) if !ok { raise("ArgumentError", "Formatters::Pretty#write expects a REXML::Document") diff --git a/internal/vm/roda_bind.go b/internal/vm/roda_bind.go index a8a65b01..d10d4455 100644 --- a/internal/vm/roda_bind.go +++ b/internal/vm/roda_bind.go @@ -345,7 +345,7 @@ func (vm *VM) registerRodaResponse(cls *RClass) { func rodaInt(v object.Value) int { switch n := v.(type) { case object.Integer: - return int(n) + return clampInt(n) case object.Float: return int(n) } diff --git a/internal/vm/rqrcode.go b/internal/vm/rqrcode.go index 705a7ada..b094a105 100644 --- a/internal/vm/rqrcode.go +++ b/internal/vm/rqrcode.go @@ -87,7 +87,7 @@ func (vm *VM) registerRQRCode() { if len(args) < 2 { raise("ArgumentError", "wrong number of arguments (given %d, expected 2)", len(args)) } - on, err := self.(*RQRCode).q.Checked(int(intArg(args[0])), int(intArg(args[1]))) + on, err := self.(*RQRCode).q.Checked(clampInt(intArg(args[0])), clampInt(intArg(args[1]))) if err != nil { raise("RQRCode::QRCodeRunTimeError", "%s", rqrcodeErrMsg(err)) } diff --git a/internal/vm/rqrcode_bind.go b/internal/vm/rqrcode_bind.go index 4bdc158f..3f785d49 100644 --- a/internal/vm/rqrcode_bind.go +++ b/internal/vm/rqrcode_bind.go @@ -33,9 +33,9 @@ func rqrcodeNew(data string, opt object.Value) *rqrcode.QRCode { case "level": opts = append(opts, rqrcode.WithLevelSymbol(rqrcodeSym(val))) case "size": - opts = append(opts, rqrcode.WithSize(int(intArg(val)))) + opts = append(opts, rqrcode.WithSize(clampInt(intArg(val)))) case "max_size": - opts = append(opts, rqrcode.WithMaxSize(int(intArg(val)))) + opts = append(opts, rqrcode.WithMaxSize(clampInt(intArg(val)))) case "mode": opts = append(opts, rqrcode.WithModeSymbol(rqrcodeSym(val))) } @@ -92,13 +92,13 @@ func rqrcodeAsSVG(q *rqrcode.QRCode, opt *object.Hash) string { rqrcodeEachOpt(opt, func(key string, val object.Value) { switch key { case "offset": - o.Offset = int(intArg(val)) + o.Offset = clampInt(intArg(val)) case "color": o.Color = rqrcodeStr(val) case "fill": o.Fill = rqrcodeStr(val) case "module_size": - o.ModuleSize = int(intArg(val)) + o.ModuleSize = clampInt(intArg(val)) case "shape_rendering": o.ShapeRendering = rqrcodeStr(val) case "use_path": @@ -126,7 +126,7 @@ func rqrcodeAsANSI(q *rqrcode.QRCode, opt *object.Hash) string { case "fill_character": o.FillCharacter = rqrcodeStr(val) case "quiet_zone_size": - o.QuietZoneSize = int(intArg(val)) + o.QuietZoneSize = clampInt(intArg(val)) } }) return q.AsANSI(o) @@ -143,7 +143,7 @@ func rqrcodeToString(q *rqrcode.QRCode, opt *object.Hash) string { case "light": o.Light = rqrcodeStr(val) case "quiet_zone_size": - o.QuietZoneSize = int(intArg(val)) + o.QuietZoneSize = clampInt(intArg(val)) } }) return q.ToString(o) diff --git a/internal/vm/rspec.go b/internal/vm/rspec.go index 27e406ba..ac8ca23d 100644 --- a/internal/vm/rspec.go +++ b/internal/vm/rspec.go @@ -259,7 +259,7 @@ func (vm *VM) registerRSpecMatcherClass(mod *RClass) { if m.chain == nil || m.chain.respTo == nil { raise("NoMethodError", "undefined method `with' for matcher") } - n := int(rspecIntArg(args)) + n := clampInt(rspecIntArg(args)) return &RSpecMatcher{m: rspec.RespondTo(m.chain.respTo.names...).With(n)} }) d("arguments", func(vm *VM, v object.Value, _ []object.Value, _ *Proc) object.Value { return v }) diff --git a/internal/vm/securerandom.go b/internal/vm/securerandom.go index 1d91b4d7..ffa741b2 100644 --- a/internal/vm/securerandom.go +++ b/internal/vm/securerandom.go @@ -99,7 +99,7 @@ func countArg(args []object.Value, def int) int { if _, isNil := args[0].(object.Nil); isNil { return def } - return int(intArg(args[0])) + return clampInt(intArg(args[0])) } // secureRandRead is the crypto/rand.Read entry point behind a seam so tests can diff --git a/internal/vm/sequel.go b/internal/vm/sequel.go index 025ba1e7..09a7c4e8 100644 --- a/internal/vm/sequel.go +++ b/internal/vm/sequel.go @@ -272,12 +272,12 @@ func (vm *VM) registerSequelDataset(mod *RClass) { }) d("limit", func(vm *VM, v object.Value, args []object.Value, _ *Proc) object.Value { if len(args) == 2 { - return chain(v, self(v).ds.LimitOffset(int(pgIntArg(args[0])), int(pgIntArg(args[1])))) + return chain(v, self(v).ds.LimitOffset(clampInt(pgIntArg(args[0])), clampInt(pgIntArg(args[1])))) } - return chain(v, self(v).ds.Limit(int(pgIntArg(pgArg0(args))))) + return chain(v, self(v).ds.Limit(clampInt(pgIntArg(pgArg0(args))))) }) d("offset", func(vm *VM, v object.Value, args []object.Value, _ *Proc) object.Value { - return chain(v, self(v).ds.Offset(int(pgIntArg(pgArg0(args))))) + return chain(v, self(v).ds.Offset(clampInt(pgIntArg(pgArg0(args))))) }) d("join", func(vm *VM, v object.Value, args []object.Value, _ *Proc) object.Value { table, cond := sequelJoinArgs(args) diff --git a/internal/vm/sequel_schema.go b/internal/vm/sequel_schema.go index 425fa786..714ae341 100644 --- a/internal/vm/sequel_schema.go +++ b/internal/vm/sequel_schema.go @@ -179,7 +179,7 @@ func sequelColOpts(h *object.Hash) []sequel.ColOpt { opts = append(opts, sequel.Unique()) } if v, ok := sequelKw(h, "size"); ok { - opts = append(opts, sequel.Size(int(pgIntArg(v)))) + opts = append(opts, sequel.Size(clampInt(pgIntArg(v)))) } if v, ok := sequelKw(h, "text"); ok && v.Truthy() { opts = append(opts, sequel.Text()) diff --git a/internal/vm/sidekiq_bind.go b/internal/vm/sidekiq_bind.go index a4459358..a8a9f664 100644 --- a/internal/vm/sidekiq_bind.go +++ b/internal/vm/sidekiq_bind.go @@ -389,7 +389,7 @@ func retryOption(v object.Value) any { case object.Bool: return bool(n) case object.Integer: - return int(n) + return clampInt(n) } return true } diff --git a/internal/vm/signal.go b/internal/vm/signal.go index c4059b3f..4e688eeb 100644 --- a/internal/vm/signal.go +++ b/internal/vm/signal.go @@ -157,7 +157,7 @@ func (vm *VM) registerSignal() { return raise("ArgumentError", "wrong number of arguments (given %d, expected 1)", len(args)) } n := vm.toIntCoerce(args[0]) - if name, ok := signalNameOf(int(n)); ok { + if name, ok := signalNameOf(clampInt(n)); ok { return object.NewString(name) } return object.NilV @@ -196,7 +196,7 @@ func (vm *VM) registerSignalException() { if len(args) > 2 { return raise("ArgumentError", "wrong number of arguments (given %d, expected 1..2)", len(args)) } - signo = int(n) + signo = clampInt(n) if signo < 0 || signo > sigMaxNumber { return raise("ArgumentError", "invalid signal number (%d)", signo) } @@ -269,7 +269,7 @@ func (vm *VM) trapSignm(v object.Value) int { if n < 0 || int64(n) > int64(sigMaxNumber) { raise("ArgumentError", "invalid signal number (%d)", int64(n)) } - return int(n) + return clampInt(n) } return vm.signm2signo(v, true) } diff --git a/internal/vm/simplecov_bind.go b/internal/vm/simplecov_bind.go index 29d2a220..7481c98d 100644 --- a/internal/vm/simplecov_bind.go +++ b/internal/vm/simplecov_bind.go @@ -119,7 +119,7 @@ func simpleCovLinesToHits(arr *object.Array) []simplecov.Hit { if !ok { raise("TypeError", "coverage line must be an Integer or nil, got %s", e.Inspect()) } - hits[i] = simplecov.Coverable(int(n)) + hits[i] = simplecov.Coverable(clampInt(n)) } return hits } diff --git a/internal/vm/sinatra_helpers.go b/internal/vm/sinatra_helpers.go index a969ddb6..d4837d6b 100644 --- a/internal/vm/sinatra_helpers.go +++ b/internal/vm/sinatra_helpers.go @@ -54,7 +54,7 @@ func sinatraOptPattern(args []object.Value) string { func sinatraInt(v object.Value, def int) int { switch n := v.(type) { case object.Integer: - return int(n) + return clampInt(n) case *object.String: if i, err := strconv.Atoi(n.Str()); err == nil { return i @@ -82,7 +82,7 @@ func sinatraHaltArgs(args []object.Value) []any { for _, a := range args { switch n := a.(type) { case object.Integer: - out = append(out, int(n)) + out = append(out, clampInt(n)) case *object.String: out = append(out, n.Str()) case *object.Array: @@ -132,7 +132,7 @@ func sinatraSettingValue(v object.Value) any { case object.Bool: return bool(n) case object.Integer: - return int(n) + return clampInt(n) case *object.String: return n.Str() case object.Symbol: @@ -152,7 +152,7 @@ func sinatraResult(vm *VM, ret object.Value) any { case *object.String: return n.Str() case object.Integer: - return int(n) + return clampInt(n) case *object.Array: parts := make([]string, len(n.Elems)) for i, el := range n.Elems { diff --git a/internal/vm/socket.go b/internal/vm/socket.go index 1f35e780..a2529d47 100644 --- a/internal/vm/socket.go +++ b/internal/vm/socket.go @@ -353,7 +353,7 @@ func streamRecv(vm *VM, s streamIO, args []object.Value) object.Value { if len(args) == 0 { raise("ArgumentError", "wrong number of arguments (given 0, expected 1..2)") } - n := int(intArg(args[0])) + n := clampInt(intArg(args[0])) if n < 0 { raise("ArgumentError", "negative length %d given", n) } @@ -375,7 +375,7 @@ func streamRead(vm *VM, s streamIO, args []object.Value) object.Value { ioBlock(vm, func() { data, _ = io.ReadAll(s.reader()) }) return object.NewStringBytesEnc(data, "ASCII-8BIT") } - n := int(intArg(args[0])) + n := clampInt(intArg(args[0])) if n < 0 { raise("ArgumentError", "negative length %d given", n) } @@ -415,7 +415,7 @@ func streamReadpartial(vm *VM, s streamIO, args []object.Value) object.Value { if len(args) == 0 { raise("ArgumentError", "wrong number of arguments (given 0, expected 1..2)") } - n := int(intArg(args[0])) + n := clampInt(intArg(args[0])) if n < 0 { raise("ArgumentError", "negative length %d given", n) } diff --git a/internal/vm/socket_addrinfo.go b/internal/vm/socket_addrinfo.go index 35750ad6..4d7577d9 100644 --- a/internal/vm/socket_addrinfo.go +++ b/internal/vm/socket_addrinfo.go @@ -193,7 +193,7 @@ func (vm *VM) registerSocketAddr() { } flags := 0 if len(args) > 1 && !object.IsNil(args[1]) { - flags = int(intArg(args[1])) + flags = clampInt(intArg(args[1])) } host, port := nameinfoTarget(args[0]) return object.NewArray( @@ -209,7 +209,7 @@ func (vm *VM) registerSocketAddr() { if len(args) < 2 { raise("ArgumentError", "wrong number of arguments (given %d, expected 2)", len(args)) } - return object.NewStringBytesEnc(packSockaddrIn(int(intArg(args[0])), strArg(args[1])), "ASCII-8BIT") + return object.NewStringBytesEnc(packSockaddrIn(clampInt(intArg(args[0])), strArg(args[1])), "ASCII-8BIT") }} sock.smethods["pack_sockaddr_in"] = packIn sock.smethods["sockaddr_in"] = &Method{name: "sockaddr_in", owner: sock, native: packIn.native} @@ -249,7 +249,7 @@ func (vm *VM) registerAddrinfo(sock *RClass) { raise("ArgumentError", "wrong number of arguments (given %d, expected 2)", len(args)) } ip, af := resolveOneIP("ip", strArg(args[0])) - return &addrinfo{cls: cls, afamily: af, ip: ip, port: int(intArg(args[1])), socktype: socktype, protocol: protocol} + return &addrinfo{cls: cls, afamily: af, ip: ip, port: clampInt(intArg(args[1])), socktype: socktype, protocol: protocol} } } cls.smethods["tcp"] = &Method{name: "tcp", owner: cls, native: mkQualified(sockStream, ipprotoTCP)} @@ -297,7 +297,7 @@ func (vm *VM) registerAddrinfo(sock *RClass) { a.socktype = socktypeNumber(args[2]) } if len(args) > 3 && !object.IsNil(args[3]) { - a.protocol = int(intArg(args[3])) + a.protocol = clampInt(intArg(args[3])) } return a }} @@ -360,7 +360,7 @@ func resolveAddrinfo(cls *RClass, args []object.Value) []*addrinfo { } protocol := 0 if len(args) > 4 && !object.IsNil(args[4]) { - protocol = int(intArg(args[4])) + protocol = clampInt(intArg(args[4])) } else { switch socktype { case sockStream: @@ -416,7 +416,7 @@ func resolveOneIP(network, host string) (string, int) { func resolvePort(v object.Value, socktype int) int { switch p := v.(type) { case object.Integer: - return int(p) + return clampInt(p) case *object.String: s := p.Str() if n, err := strconv.Atoi(s); err == nil { @@ -454,10 +454,10 @@ func familyNumber(v object.Value) int { switch x := v.(type) { case object.Integer: // Accept both the BSD (30) and Linux (10) AF_INET6 spellings. - if int(x) == 10 { + if clampInt(x) == 10 { return afINET6 } - return int(x) + return clampInt(x) case object.Symbol: return familyFromName(string(x)) case *object.String: @@ -489,7 +489,7 @@ func familyFromName(name string) int { func socktypeNumber(v object.Value) int { switch x := v.(type) { case object.Integer: - return int(x) + return clampInt(x) case object.Symbol: return socktypeFromName(string(x)) case *object.String: @@ -590,7 +590,7 @@ func addrinfoFromSockaddr(cls *RClass, v object.Value) *addrinfo { raise("ArgumentError", "array address must have 4 elements [afamily, port, host, addr]") } af := familyNumber(arr.Elems[0]) - return &addrinfo{cls: cls, afamily: af, ip: strArg(arr.Elems[3]), port: int(intArg(arr.Elems[1]))} + return &addrinfo{cls: cls, afamily: af, ip: strArg(arr.Elems[3]), port: clampInt(intArg(arr.Elems[1]))} } raise("TypeError", "expected a packed sockaddr String or an address Array") return nil @@ -636,7 +636,7 @@ func nameinfoTarget(v object.Value) (string, int) { func nameinfoPort(v object.Value) int { switch p := v.(type) { case object.Integer: - return int(p) + return clampInt(p) case *object.String: if n, err := strconv.Atoi(p.Str()); err == nil { return n diff --git a/internal/vm/socket_raw.go b/internal/vm/socket_raw.go index 22a3b095..f3ab578b 100644 --- a/internal/vm/socket_raw.go +++ b/internal/vm/socket_raw.go @@ -112,7 +112,7 @@ func (vm *VM) registerRawSocket(sock *RClass) { typ := socktypeNumber(args[1]) proto := 0 if len(args) > 2 && !object.IsNil(args[2]) { - proto = int(intArg(args[2])) + proto = clampInt(intArg(args[2])) } if _, ok := rawSocketNetwork(domain, typ); !ok { raise("SocketError", "unsupported domain/type combination") @@ -408,7 +408,7 @@ func rawRecvLen(args []object.Value) int { if len(args) == 0 { raise("ArgumentError", "wrong number of arguments (given 0, expected 1..2)") } - n := int(intArg(args[0])) + n := clampInt(intArg(args[0])) if n < 0 { raise("ArgumentError", "negative length %d given", n) } diff --git a/internal/vm/socket_udp.go b/internal/vm/socket_udp.go index 31d0d0e2..8f61cced 100644 --- a/internal/vm/socket_udp.go +++ b/internal/vm/socket_udp.go @@ -203,7 +203,7 @@ func udpRecvLen(args []object.Value) int { if len(args) == 0 || object.IsNil(args[0]) { return 65536 } - n := int(intArg(args[0])) + n := clampInt(intArg(args[0])) if n <= 0 { raise("ArgumentError", "negative length %d given", n) } diff --git a/internal/vm/spawn.go b/internal/vm/spawn.go index 43fc19b3..d5e4a47a 100644 --- a/internal/vm/spawn.go +++ b/internal/vm/spawn.go @@ -463,7 +463,7 @@ func (vm *VM) runForkBlock(blk *Proc) (code int) { // departure from it: the block IS the child, so the child's exit is // this function returning the status it asked for. if rerr, ok := r.(RubyError); ok && vm.classIsA(rerr.Class, "SystemExit") { - code = int(vm.exitStatusOf(rerr.Obj)) + code = clampInt(vm.exitStatusOf(rerr.Obj)) return } panic(r) @@ -478,7 +478,7 @@ func (vm *VM) runForkBlock(blk *Proc) (code int) { func (vm *VM) recordChild(code int) int64 { vm.childPidSeq++ pid := 100000 + vm.childPidSeq - vm.children = append(vm.children, childStatus{pid: int(pid), code: code}) + vm.children = append(vm.children, childStatus{pid: clampInt(pid), code: code}) return pid } @@ -1076,11 +1076,11 @@ func (vm *VM) reapAny(pid int) (childStatus, bool) { func (vm *VM) waitForChild(args []object.Value) (childStatus, bool) { pid := -1 if len(args) > 0 && !object.IsNil(args[0]) { - pid = int(vm.procToInt(args[0])) + pid = clampInt(vm.procToInt(args[0])) } flags := 0 if len(args) > 1 && !object.IsNil(args[1]) { - flags = int(vm.procToInt(args[1])) + flags = clampInt(vm.procToInt(args[1])) } st, ok := vm.reapAny(pid) if !ok { @@ -1158,7 +1158,7 @@ func (vm *VM) registerProcessWait(mod *RClass, status *RClass) { // waitPidArg reads the optional pid argument of a wait-style call. func waitPidArg(vm *VM, args []object.Value) int { if len(args) > 0 && !object.IsNil(args[0]) { - return int(vm.procToInt(args[0])) + return clampInt(vm.procToInt(args[0])) } return -1 } diff --git a/internal/vm/sqlite3.go b/internal/vm/sqlite3.go index d668fa57..a8f662e6 100644 --- a/internal/vm/sqlite3.go +++ b/internal/vm/sqlite3.go @@ -265,7 +265,7 @@ func (vm *VM) registerSQLite3Database(mod *RClass) { d("busy_timeout=", func(vm *VM, v object.Value, args []object.Value, _ *Proc) object.Value { ms := 0 if len(args) > 0 { - ms = int(sqlite3IntArg(args[0])) + ms = clampInt(sqlite3IntArg(args[0])) } if err := self(v).BusyTimeout(ms); err != nil { raiseSQLite3Error(err) diff --git a/internal/vm/sqlite3_bind.go b/internal/vm/sqlite3_bind.go index e7ab071e..fe1f0480 100644 --- a/internal/vm/sqlite3_bind.go +++ b/internal/vm/sqlite3_bind.go @@ -105,7 +105,7 @@ func sqlite3Mode(args []object.Value) sqlite3.TransactionMode { func sqlite3BindKey(v object.Value) any { switch n := v.(type) { case object.Integer: - return int(n) + return clampInt(n) case object.Symbol: return string(n) case *object.String: diff --git a/internal/vm/string_byteops.go b/internal/vm/string_byteops.go index 480155e1..bc526ef0 100644 --- a/internal/vm/string_byteops.go +++ b/internal/vm/string_byteops.go @@ -85,7 +85,7 @@ func (vm *VM) strByteindex(self *object.String, args []object.Value) object.Valu if len(args) > 1 { // rb_str_byteindex_m reads the offset with NUM2LONG, which accepts an // Integer or anything answering #to_int. - off = int(vm.toIntCoerce(args[1])) + off = clampInt(vm.toIntCoerce(args[1])) if off < 0 { off += n } @@ -128,7 +128,7 @@ func (vm *VM) strByterindex(self *object.String, args []object.Value) object.Val off := n if len(args) > 1 { // rb_str_byterindex_m likewise reads the offset with NUM2LONG. - off = int(vm.toIntCoerce(args[1])) + off = clampInt(vm.toIntCoerce(args[1])) if off < 0 { off += n } @@ -259,7 +259,7 @@ func (vm *VM) strBytesplice(self *object.String, args []object.Value) object.Val // the start and end to fall on character boundaries. func bytespliceIndexLen(self *object.String, idxV, lenV object.Value) (int, int) { n := self.Len() - idx := int(toInt(idxV)) + idx := clampInt(toInt(idxV)) raw := idx if idx < 0 { idx += n @@ -267,7 +267,7 @@ func bytespliceIndexLen(self *object.String, idxV, lenV object.Value) (int, int) if idx < 0 || idx > n { raise("IndexError", "index %d out of string", raw) } - length := int(toInt(lenV)) + length := clampInt(toInt(lenV)) if length < 0 { raise("IndexError", "negative length %d", length) } @@ -310,7 +310,7 @@ func bytespliceSrcRange(src *object.String, r *object.Range) string { func rangeToByteSpan(r *object.Range, n int) (int, int) { beg := 0 if !object.IsNil(r.Lo) { - beg = int(toInt(r.Lo)) + beg = clampInt(toInt(r.Lo)) if beg < 0 { beg += n } @@ -320,7 +320,7 @@ func rangeToByteSpan(r *object.Range, n int) (int, int) { } end := n if !object.IsNil(r.Hi) { - end = int(toInt(r.Hi)) + end = clampInt(toInt(r.Hi)) if end < 0 { end += n } diff --git a/internal/vm/strscan.go b/internal/vm/strscan.go index f3cade8d..de9dbcb3 100644 --- a/internal/vm/strscan.go +++ b/internal/vm/strscan.go @@ -206,7 +206,7 @@ func (vm *VM) registerStringScanner() { sc := ssScannerOf(v) switch k := args[0].(type) { case object.Integer: - text, ok := sc.Group(int(k)) + text, ok := sc.Group(clampInt(k)) if !ok { return object.NilV } @@ -223,7 +223,7 @@ func (vm *VM) registerStringScanner() { // peek(n) returns up to n bytes from the current position without advancing. d("peek", func(_ *VM, v object.Value, args []object.Value, _ *Proc) object.Value { - return object.NewString(ssScannerOf(v).Peek(int(intArg(args[0])))) + return object.NewString(ssScannerOf(v).Peek(clampInt(intArg(args[0])))) }) // pos / charpos report the position; pos= moves it, raising RangeError when @@ -237,7 +237,7 @@ func (vm *VM) registerStringScanner() { return object.IntValue(int64(ssScannerOf(v).CharPos())) }) posSet := func(_ *VM, v object.Value, args []object.Value, _ *Proc) object.Value { - n := int(intArg(args[0])) + n := clampInt(intArg(args[0])) if err := ssScannerOf(v).SetPos(n); err != nil { raise("RangeError", "index out of range") } diff --git a/internal/vm/struct.go b/internal/vm/struct.go index 61ab2055..d1249c7c 100644 --- a/internal/vm/struct.go +++ b/internal/vm/struct.go @@ -269,7 +269,7 @@ func setupStruct(vm *VM) { vals := structValues(self) switch k := a[0].(type) { case object.Integer: - return vals[structIndex(int(k), len(vals))] + return vals[structIndex(clampInt(k), len(vals))] case object.Symbol: return vals[structNameIndex(string(k), names)] case *object.String: @@ -288,7 +288,7 @@ func setupStruct(vm *VM) { names := structDefOf(o.class).names switch k := a[0].(type) { case object.Integer: - o.structVals[structIndex(int(k), len(names))] = a[1] + o.structVals[structIndex(clampInt(k), len(names))] = a[1] case object.Symbol: o.structVals[structNameIndex(string(k), names)] = a[1] case *object.String: @@ -371,7 +371,7 @@ func setupStruct(vm *VM) { if !ok { raise("TypeError", "no implicit conversion of %s into Integer", classNameOf(arg)) } - out = append(out, vals[structIndex(int(i), len(vals))]) + out = append(out, vals[structIndex(clampInt(i), len(vals))]) } return object.NewArrayFromSlice(out) }) @@ -385,7 +385,7 @@ func setupStruct(vm *VM) { var v object.Value switch k := a[0].(type) { case object.Integer: - idx := int(k) + idx := clampInt(k) if idx < 0 { idx += len(vals) } @@ -619,7 +619,7 @@ func structRangeValues(rng *object.Range, vals []object.Value) []object.Value { size := len(vals) lo := 0 if !object.IsNil(rng.Lo) { - lo = int(intArg(rng.Lo)) + lo = clampInt(intArg(rng.Lo)) if lo < 0 { lo += size } @@ -629,7 +629,7 @@ func structRangeValues(rng *object.Range, vals []object.Value) []object.Value { } hi := size // endless range runs to the last member if !object.IsNil(rng.Hi) { - hi = int(intArg(rng.Hi)) + hi = clampInt(intArg(rng.Hi)) if hi < 0 { hi += size } @@ -663,7 +663,7 @@ func structDeconstructIndex(vm *VM, k object.Value, names []string) (int, bool) case *object.String: return structFind(key.Str(), names) case object.Integer: - return structPos(int(key), len(names)) + return structPos(clampInt(key), len(names)) default: if vm.respondsTo(k, "to_int") { iv := vm.send(k, "to_int", nil, nil) @@ -671,7 +671,7 @@ func structDeconstructIndex(vm *VM, k object.Value, names []string) (int, bool) if !ok { raise("TypeError", "can't convert %s into Integer", vm.classOf(k).name) } - return structPos(int(i), len(names)) + return structPos(clampInt(i), len(names)) } raise("TypeError", "no implicit conversion of %s into Integer", classNameOf(k)) return 0, false diff --git a/internal/vm/thor_bind.go b/internal/vm/thor_bind.go index d23ac006..a9c63f18 100644 --- a/internal/vm/thor_bind.go +++ b/internal/vm/thor_bind.go @@ -140,7 +140,7 @@ func thorConfig(h *object.Hash) thor.Config { } if v, ok := thorKw(h, "terminal_width"); ok { if n, ok := v.(object.Integer); ok { - c.TerminalWidth = int(n) + c.TerminalWidth = clampInt(n) } } } @@ -174,7 +174,7 @@ func thorKw(h *object.Hash, key string) (object.Value, bool) { func thorIntArg(args []object.Value, i int) int { if i < len(args) { if n, ok := args[i].(object.Integer); ok { - return int(n) + return clampInt(n) } } return 0 diff --git a/internal/vm/thread.go b/internal/vm/thread.go index f78d8b49..9789a057 100644 --- a/internal/vm/thread.go +++ b/internal/vm/thread.go @@ -773,7 +773,7 @@ func (vm *VM) registerThreadClass() { if !ok { raise("TypeError", "no implicit conversion of %s into Integer", classNameOf(args[0])) } - p := int(n) + p := clampInt(n) if p > 3 { p = 3 } diff --git a/internal/vm/time.go b/internal/vm/time.go index b0934142..8673b063 100644 --- a/internal/vm/time.go +++ b/internal/vm/time.go @@ -447,7 +447,7 @@ func (vm *VM) registerTime() { // require "time" formatters. iso8601 / xmlschema take an optional // fraction_digits count (default 0); rfc2822 / rfc822 / httpdate take none. iso := func(_ *VM, v object.Value, args []object.Value, _ *Proc) object.Value { - return object.NewString(self(v).iso8601Str(int(intArgOr(args, 0)))) + return object.NewString(self(v).iso8601Str(clampInt(intArgOr(args, 0)))) } d("iso8601", iso) rfc2822 := func(_ *VM, v object.Value, _ []object.Value, _ *Proc) object.Value { @@ -879,7 +879,7 @@ func numFloat(v object.Value) float64 { func (vm *VM) timeInt(v object.Value) int { switch n := v.(type) { case object.Integer: - return int(n) + return clampInt(n) case object.Float: return int(n) } @@ -889,7 +889,7 @@ func (vm *VM) timeInt(v object.Value) int { r := vm.send(v, "to_int", nil, nil) switch n := r.(type) { case object.Integer: - return int(n) + return clampInt(n) case *object.Bignum: // Every rbgo Bignum is outside int64, so no calendar field can hold one. raise("RangeError", "bignum too big to convert into 'long'") @@ -987,7 +987,7 @@ func (vm *VM) newTimeOffset(v object.Value) *stdtime.Location { case *object.String: return parseUtcOffset(x.Str()) case object.Integer: - return fixedOffsetLoc(int(x)) + return fixedOffsetLoc(clampInt(x)) case object.Float: return fixedOffsetLoc(int(x)) case *object.Rational: @@ -1003,7 +1003,7 @@ func (vm *VM) newTimeOffset(v object.Value) *stdtime.Location { } if vm.respondsToDynamic(v, "to_int") { if n, ok := vm.send(v, "to_int", nil, nil).(object.Integer); ok { - return fixedOffsetLoc(int(n)) + return fixedOffsetLoc(clampInt(n)) } } if vm.isNumeric(v) && vm.respondsToDynamic(v, "to_r") { @@ -1053,7 +1053,7 @@ func (vm *VM) assembleZoneObjectTime(zone object.Value, wall *Time, utcEpoch, of name = s.Str() } } - t := stdtime.Unix(utcEpoch, ns).In(stdtime.FixedZone(name, int(offset))) + t := stdtime.Unix(utcEpoch, ns).In(stdtime.FixedZone(name, clampInt(offset))) return &Time{t: t, zoneObj: zone} } @@ -1397,24 +1397,24 @@ func (vm *VM) timeMonthPart(pos []object.Value, i, def int) int { func (vm *VM) timeSecParts(v object.Value) (int, int64) { switch n := v.(type) { case object.Integer: - return int(n), 0 + return clampInt(n), 0 case object.Float: return floatSecParts(float64(n)) case *object.Rational: s, ns := ratSecNs(n.R) - return int(s), ns + return clampInt(s), ns case *object.String: return parseRubyInt(n.Str()), 0 } if vm.respondsToDynamic(v, "to_int") { if i, ok := vm.send(v, "to_int", nil, nil).(object.Integer); ok { - return int(i), 0 + return clampInt(i), 0 } } if vm.isNumeric(v) && vm.respondsToDynamic(v, "to_r") { if r, ok := vm.send(v, "to_r", nil, nil).(*object.Rational); ok { s, ns := ratSecNs(r.R) - return int(s), ns + return clampInt(s), ns } } raise("TypeError", "no implicit conversion of %s into Time", v.Inspect()) @@ -1511,7 +1511,7 @@ func (vm *VM) buildTime(pos []object.Value, usecNs int64, usecGiven bool, loc *s checkRange("hour", hour, 0, 24) checkRange("min", min, 0, 59) checkRange("sec", sec, 0, 60) - return &Time{t: stdtime.Date(year, stdtime.Month(month), day, hour, min, sec, int(ns), loc)} + return &Time{t: stdtime.Date(year, stdtime.Month(month), day, hour, min, sec, clampInt(ns), loc)} } // checkRange enforces MRI's two-tier range error: a value below the low bound is diff --git a/internal/vm/timecop.go b/internal/vm/timecop.go index 04ed2b45..e672188b 100644 --- a/internal/vm/timecop.go +++ b/internal/vm/timecop.go @@ -65,7 +65,7 @@ func (vm *VM) timecopInstant(args []object.Value, i int) stdtime.Time { func dateInstant(d *Date) stdtime.Time { loc := stdtime.FixedZone("", d.d.Offset()) return stdtime.Date(d.d.Year(), stdtime.Month(d.d.Month()), d.d.Day(), - d.d.Hour(), d.d.Min(), d.d.Sec(), int(d.d.SecFractionNanos()), loc).UTC() + d.d.Hour(), d.d.Min(), d.d.Sec(), clampInt(d.d.SecFractionNanos()), loc).UTC() } // timecopFactor reads Timecop.scale's leading factor argument as a float, diff --git a/internal/vm/tracepoint.go b/internal/vm/tracepoint.go index 59e3d6b2..dd4fbcb2 100644 --- a/internal/vm/tracepoint.go +++ b/internal/vm/tracepoint.go @@ -596,7 +596,7 @@ func (vm *VM) tracepointEnableForTarget(tp *tracePoint, target, targetLine objec if tp.events&evLine == 0 { raise("ArgumentError", "target_line is specified, but line event is not specified") } - line = int(vm.toIntCoerce(targetLine)) + line = clampInt(vm.toIntCoerce(targetLine)) } set := map[*bytecode.ISeq]bool{} diff --git a/internal/vm/tui.go b/internal/vm/tui.go index eea99069..7dbe05eb 100644 --- a/internal/vm/tui.go +++ b/internal/vm/tui.go @@ -107,7 +107,7 @@ func tuiValueToAny(v object.Value) any { case object.Bool: return bool(x) case object.Integer: - return int(x) + return clampInt(x) case object.Float: return float64(x) case *object.String: diff --git a/internal/vm/typhoeus.go b/internal/vm/typhoeus.go index 8b597b2b..182c9acb 100644 --- a/internal/vm/typhoeus.go +++ b/internal/vm/typhoeus.go @@ -168,7 +168,7 @@ func (vm *VM) registerTyphoeusHydra(c *RClass) { var opts typhoeus.HydraOptions if h := typhoeusHashAt(args, 0); h != nil { if v, ok := h.Get(object.Symbol("max_concurrency")); ok { - opts.MaxConcurrency = int(toInt(v)) + opts.MaxConcurrency = clampInt(toInt(v)) } } return &TyphoeusHydra{typhoeus.NewHydra(opts)} diff --git a/internal/vm/typhoeus_bind.go b/internal/vm/typhoeus_bind.go index 61686dc5..a5fa28c9 100644 --- a/internal/vm/typhoeus_bind.go +++ b/internal/vm/typhoeus_bind.go @@ -97,7 +97,7 @@ func rubyHashToTyphoeusOptions(h *object.Hash) typhoeus.Options { o.FollowLocation = v.Truthy() } if v, ok := h.Get(object.Symbol("maxredirs")); ok { - o.MaxRedirects = int(toInt(v)) + o.MaxRedirects = clampInt(toInt(v)) } return o } diff --git a/internal/vm/uri.go b/internal/vm/uri.go index 0740691b..a6a9e27a 100644 --- a/internal/vm/uri.go +++ b/internal/vm/uri.go @@ -433,7 +433,7 @@ func uriBuildField(v object.Value) (string, bool) { func uriBuildPort(v object.Value) (int, bool) { switch p := v.(type) { case object.Integer: - return int(p), true + return clampInt(p), true case *object.String: n := 0 for _, c := range p.Str() { diff --git a/internal/vm/webauthn.go b/internal/vm/webauthn.go index bae0c2d5..f8557678 100644 --- a/internal/vm/webauthn.go +++ b/internal/vm/webauthn.go @@ -169,7 +169,7 @@ func (vm *VM) registerWebAuthnRelyingParty(cls, credCls, pkCls, createOptsCls, g rp.Algorithms = waStringArray(v) } if v, ok := waHashGet(h, "timeout"); ok && v.Truthy() { - rp.Timeout = int(intArg(v)) + rp.Timeout = clampInt(intArg(v)) } return &WebAuthnRelyingParty{cls: cls, rp: rp} }) diff --git a/internal/vm/webmock_bind.go b/internal/vm/webmock_bind.go index 4a12d05f..e96f9d47 100644 --- a/internal/vm/webmock_bind.go +++ b/internal/vm/webmock_bind.go @@ -143,7 +143,7 @@ func webmockAssert(args []object.Value, negate bool) object.Value { times = 0 } if t, ok := acmeKwGet(kw, "times").(object.Integer); ok { - times = int(t) + times = clampInt(t) } if err := webmock.Default.AssertRequested(method, uri, times, webmockOptions(kw)...); err != nil { return raise("Minitest::Assertion", "%s", err.Error()) @@ -180,7 +180,7 @@ func webmockOptions(h *object.Hash) []webmock.Option { func webmockStubResponse(h *object.Hash) webmock.StubResponse { var sr webmock.StubResponse if s, ok := acmeKwGet(h, "status").(object.Integer); ok { - sr.Status = int(s) + sr.Status = clampInt(s) } if b, ok := acmeKwGet(h, "body").(*object.String); ok { sr.Body = b.Str() diff --git a/internal/vm/webrick_bind.go b/internal/vm/webrick_bind.go index 93d6448b..ab34a5f5 100644 --- a/internal/vm/webrick_bind.go +++ b/internal/vm/webrick_bind.go @@ -357,10 +357,10 @@ func webrickStr(v object.Value) string { // non-Integer (a port / status / length must be an Integer). func webrickInt(v object.Value) int { if n, ok := v.(object.Integer); ok { - return int(n) + return clampInt(n) } if n, ok := object.BigOf(v); ok { - return int(n.Int64()) + return clampInt(n.Int64()) } raise("TypeError", "expected an Integer, got %s", v.Inspect()) panic("unreachable") diff --git a/internal/vm/widgets.go b/internal/vm/widgets.go index 9c5a74c0..6f845cba 100644 --- a/internal/vm/widgets.go +++ b/internal/vm/widgets.go @@ -91,7 +91,7 @@ func wdgValueToAny(v object.Value) any { case object.Bool: return bool(x) case object.Integer: - return int(x) + return clampInt(x) case object.Float: return float64(x) case *object.String: diff --git a/internal/vm/zlib.go b/internal/vm/zlib.go index 71642176..b3c3b588 100644 --- a/internal/vm/zlib.go +++ b/internal/vm/zlib.go @@ -144,7 +144,7 @@ func (vm *VM) registerZlib() { // Zlib.deflate(data, level = DEFAULT_COMPRESSION) — one-shot compress. deflateOneShot := func(_ *VM, _ object.Value, args []object.Value, _ *Proc) object.Value { - level := int(intAt(args, 1, int64(gozlib.DefaultCompression))) + level := clampInt(intAt(args, 1, int64(gozlib.DefaultCompression))) out, err := gozlib.Deflate(bytesArg(args, 0), level) if err != nil { raiseZlib(err) @@ -164,7 +164,7 @@ func (vm *VM) registerZlib() { // Zlib.gzip(data, level: DEFAULT_COMPRESSION) / Zlib.gunzip(data). modFn("gzip", func(_ *VM, _ object.Value, args []object.Value, _ *Proc) object.Value { - level := int(intAt(args, 1, int64(gozlib.DefaultCompression))) + level := clampInt(intAt(args, 1, int64(gozlib.DefaultCompression))) out, err := gozlib.GzipCompress(bytesArg(args, 0), level) if err != nil { raiseZlib(err) @@ -192,7 +192,7 @@ func (vm *VM) registerZlib() { // initialize(level = DEFAULT_COMPRESSION): an out-of-range level raises // Zlib::StreamError, matching MRI. deflate.define("initialize", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { - level := int(intAt(args, 0, int64(gozlib.DefaultCompression))) + level := clampInt(intAt(args, 0, int64(gozlib.DefaultCompression))) d, err := gozlib.NewDeflaterLevel(level) if err != nil { raiseZlib(err) @@ -214,7 +214,7 @@ func (vm *VM) registerZlib() { // #deflate(data, flush = NO_FLUSH): feed data, returning the bytes produced so // far (any bytes buffered by an earlier #<<, then the new output). deflate.define("deflate", func(_ *VM, self object.Value, args []object.Value, _ *Proc) object.Value { - flush := int(intAt(args, 1, int64(gozlib.NoFlush))) + flush := clampInt(intAt(args, 1, int64(gozlib.NoFlush))) out, err := selfDeflater(self).Deflate(bytesArg(args, 0), flush) if err != nil { raiseZlib(err)