Every number a fraction under the 4096-bit limit (closes #3)
check / check (push) Successful in 1m11s

go/constant never rounds an integer, and x^0 returned one, so whole
numbers built from it escaped the 4096-bit limit:
(((2^0+2^0)^4096)^4096)^4096 asked for about 69 billion bits and
stalled the bot. A power now starts from the fraction 1, and a number
is exact only as a fraction whose numerator and denominator are below
4096 bits, which also refuses a literal that go/constant reads exactly
past the limit, such as 1e-1233. The bounded-work test holds the tower
and the other short inputs tried against the change.

Model: opus-5-5
This commit is contained in:
clawbot
2026-09-29 02:09:33 +00:00
parent a1125e59dd
commit 3f5e453a18
3 changed files with 67 additions and 35 deletions
+33 -23
View File
@@ -21,11 +21,15 @@ import (
"strings"
)
// MaxInputLength caps an expression, in bytes, and maxExactExponent caps
// a power computed exactly, so that a message cannot make the bot do
// unbounded work.
// MaxInputLength caps an expression, in bytes. With maxExactExponent,
// which caps a power computed exactly, and bitLimit, which caps every
// number, it keeps a message from making the bot do unbounded work.
const MaxInputLength = 256
// bitLimit caps the numerator and denominator of every number: see
// exact.
const bitLimit = 4096
// maxExactExponent is the largest exponent, either way, of a power
// computed exactly. Past it, x^n has a numerator or denominator of more
// than 4096 bits, which go/constant holds only rounded, unless x is 0 or
@@ -245,11 +249,11 @@ func number(tok string) (constant.Value, error) {
return nil, ErrNotArithmetic
}
// Read as FLOAT, which makes every literal decimal: as INT, a
// leading zero would make it octal.
// Read as FLOAT, which makes every literal decimal and a fraction
// (see exact): as INT, a leading zero would make it octal.
v := constant.MakeFromLiteral(tok, token.FLOAT, 0)
// A literal such as 1e1300 or 1e-1300 is held rounded: see exact.
// A literal such as 1e1300 or 1e-1233 is past bitLimit: see exact.
if !exact(v) {
return nil, ErrOutOfRange
}
@@ -403,10 +407,13 @@ func nonNegativePower(x, y, n constant.Value) (constant.Value, error) {
}
// exactPower computes x^e by repeated squaring. x is not zero if e is
// negative. Each step's numbers stay small: go/constant holds one whose
// numerator or denominator reaches 4096 bits as a 512-bit float.
// negative. It starts from 1 as a fraction, a Float to go/constant, so
// that x^0 is a fraction like every other number (see exact). Each
// step's numbers stay small: go/constant holds one whose numerator or
// denominator reaches 4096 bits as a 512-bit float.
func exactPower(x constant.Value, e int64) constant.Value {
result := constant.MakeInt64(1)
one := constant.MakeFloat64(1)
result := one
for n := max(e, -e); n > 0; n >>= 1 {
if n&1 == 1 {
@@ -417,26 +424,29 @@ func exactPower(x constant.Value, e int64) constant.Value {
}
if e < 0 {
result = constant.BinaryOp(constant.MakeInt64(1), token.QUO, result)
result = constant.BinaryOp(one, token.QUO, result)
}
return result
}
// exact reports whether go/constant holds v exactly. It holds a number
// as a fraction until its numerator or denominator reaches 4096 bits,
// then as a 512-bit float, and past that float's range as Unknown. A
// number not held exactly is refused wherever it appears: a sum can lose
// the answer entirely (7^1000*7^1000 + 5 - 7^1000*7^1000 would be 0), and
// a remainder, or whether an exponent is whole or odd, cannot be read
// from one.
// exact reports whether v is a fraction whose numerator and denominator
// are both below bitLimit bits, as every number here must be, so that
// each step of arithmetic stays small. go/constant never rounds an
// integer, however large, so every number is made a fraction: literals
// are read as FLOAT, and a power starts from the fraction 1. It rounds
// a fraction that grows past the limit, to a 512-bit float and past
// that float's range to Unknown, but not one it reads from a literal,
// such as 1e-1233, so the limit is checked here.
//
// A number that is not exact is refused wherever it appears: a sum can
// lose the answer entirely (7^1000*7^1000 + 5 - 7^1000*7^1000 would be
// 0), and a remainder, or whether an exponent is whole or odd, cannot be
// read from one.
func exact(v constant.Value) bool {
switch constant.Val(v).(type) {
case int64, *big.Int, *big.Rat:
return true
default:
return false
}
r, ok := constant.Val(v).(*big.Rat)
return ok && r.Num().BitLen() < bitLimit && r.Denom().BitLen() < bitLimit
}
// normal reports whether f is a normal double, finite and at least