Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions internal/wasm/sdk/test/e2e/exceptions.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# Exception Format is <test name>: <reason>
"data/toplevel integer": "https://github.com/open-policy-agent/opa/issues/3711"
"data/nested integer": "https://github.com/open-policy-agent/opa/issues/3711"
"strings/format_int: bignum exact (>64-bit integer, all bases + negative)": "WASM cannot represent integers larger than 64 bits (see https://github.com/open-policy-agent/opa/issues/3711); this change fixes the Go topdown builtin."
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
cases:
- note: "strings/format_int: bignum exact (>64-bit integer, all bases + negative)"
query: data.generated.p = x
modules:
- |
package generated

p := result if {
result := {
"b16": format_int(18446744073709551617, 16),
"b10": format_int(18446744073709551617, 10),
"b8": format_int(18446744073709551617, 8),
"b2": format_int(18446744073709551617, 2),
"neg16": format_int(-18446744073709551617, 16),
"neg10": format_int(-18446744073709551617, 10),
"frac16": format_int(15.9, 16),
"fracneg16": format_int(-15.9, 16),
}
}
data: {}
want_result:
- x:
b16: "10000000000000001"
b10: "18446744073709551617"
b8: "2000000000000000000001"
b2: "10000000000000000000000000000000000000000000000000000000000000001"
neg16: "-10000000000000001"
neg10: "-18446744073709551617"
frac16: "f"
fracneg16: "-f"
14 changes: 14 additions & 0 deletions v1/topdown/strings.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,11 +129,14 @@ func builtinFormatInt(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Ter
}

var format string
var radix int
switch base {
case ast.Number("2"):
format = "%b"
radix = 2
case ast.Number("8"):
format = "%o"
radix = 8
case ast.Number("10"):
// Fast path: for numbers whose decimal string is already interned (e.g.
// "0"–"100"), we can skip strconv.ParseInt entirely.
Expand All @@ -144,12 +147,23 @@ func builtinFormatInt(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Ter
return iter(ast.InternedIntegerString(i))
}
format = "%d"
radix = 10
case ast.Number("16"):
format = "%x"
radix = 16
default:
return builtins.NewOperandEnumErr(2, "2", "8", "10", "16")
}

// For integer inputs, format the exact big.Int. Routing integers through a
// float (as the fractional path below does) loses precision for values that
// need more than a float64's 53-bit mantissa, e.g. 18446744073709551617.
if i, ok := new(big.Int).SetString(string(input), 10); ok {
return iter(ast.InternedTerm(i.Text(radix)))
}

// Fractional inputs (e.g. 15.9) are truncated toward zero, matching the
// historical behaviour: format_int(15.9, 16) == "f", format_int(-15.9, 16) == "-f".
f := builtins.NumberToFloat(input)
i, _ := f.Int(nil)

Expand Down
Loading