func (e *NumError) Unwrap() error { return e.Err }
+// cloneString returns a string copy of x.
+//
+// All ParseXXX functions allow the input string to escape to the error value.
+// This hurts strconv.ParseXXX(string(b)) calls where b is []byte since
+// the conversion from []byte must allocate a string on the heap.
+// If we assume errors are infrequent, then we can avoid escaping the input
+// back to the output by copying it first. This allows the compiler to call
+// strconv.ParseXXX without a heap allocation for most []byte to string
+// conversions, since it can now prove that the string cannot escape Parse.
+//
+// TODO: Use strings.Clone instead? However, we cannot depend on "strings"
+// since it incurs a transitive dependency on "unicode".
+// Either move strings.Clone to an internal/bytealg or make the
+// "strings" to "unicode" dependency lighter (see https://go.dev/issue/54098).
+func cloneString(x string) string { return string([]byte(x)) }
+
func syntaxError(fn, str string) *NumError {
- return &NumError{fn, str, ErrSyntax}
+ return &NumError{fn, cloneString(str), ErrSyntax}
}
func rangeError(fn, str string) *NumError {
- return &NumError{fn, str, ErrRange}
+ return &NumError{fn, cloneString(str), ErrRange}
}
func baseError(fn, str string, base int) *NumError {
- return &NumError{fn, str, errors.New("invalid base " + Itoa(base))}
+ return &NumError{fn, cloneString(str), errors.New("invalid base " + Itoa(base))}
}
func bitSizeError(fn, str string, bitSize int) *NumError {
- return &NumError{fn, str, errors.New("invalid bit size " + Itoa(bitSize))}
+ return &NumError{fn, cloneString(str), errors.New("invalid bit size " + Itoa(bitSize))}
}
const intSize = 32 << (^uint(0) >> 63)
un, err = ParseUint(s, base, bitSize)
if err != nil && err.(*NumError).Err != ErrRange {
err.(*NumError).Func = fnParseInt
- err.(*NumError).Num = s0
+ err.(*NumError).Num = cloneString(s0)
return 0, err
}
if s[0] == '-' || s[0] == '+' {
s = s[1:]
if len(s) < 1 {
- return 0, &NumError{fnAtoi, s0, ErrSyntax}
+ return 0, syntaxError(fnAtoi, s0)
}
}
for _, ch := range []byte(s) {
ch -= '0'
if ch > 9 {
- return 0, &NumError{fnAtoi, s0, ErrSyntax}
+ return 0, syntaxError(fnAtoi, s0)
}
n = n*10 + int(ch)
}
}
}
+// Sink makes sure the compiler cannot optimize away the benchmarks.
+var Sink struct {
+ Bool bool
+ Int int
+ Int64 int64
+ Uint64 uint64
+ Float64 float64
+ Complex128 complex128
+ Error error
+ Bytes []byte
+}
+
+func TestAllocationsFromBytes(t *testing.T) {
+ const runsPerTest = 100
+ bytes := struct{ Bool, Number, String, Buffer []byte }{
+ Bool: []byte("false"),
+ Number: []byte("123456789"),
+ String: []byte("hello, world!"),
+ Buffer: make([]byte, 1024),
+ }
+
+ checkNoAllocs := func(f func()) func(t *testing.T) {
+ return func(t *testing.T) {
+ t.Helper()
+ if allocs := testing.AllocsPerRun(runsPerTest, f); allocs != 0 {
+ t.Errorf("got %v allocs, want 0 allocs", allocs)
+ }
+ }
+ }
+
+ t.Run("Atoi", checkNoAllocs(func() {
+ Sink.Int, Sink.Error = Atoi(string(bytes.Number))
+ }))
+ t.Run("ParseBool", checkNoAllocs(func() {
+ Sink.Bool, Sink.Error = ParseBool(string(bytes.Bool))
+ }))
+ t.Run("ParseInt", checkNoAllocs(func() {
+ Sink.Int64, Sink.Error = ParseInt(string(bytes.Number), 10, 64)
+ }))
+ t.Run("ParseUint", checkNoAllocs(func() {
+ Sink.Uint64, Sink.Error = ParseUint(string(bytes.Number), 10, 64)
+ }))
+ t.Run("ParseFloat", checkNoAllocs(func() {
+ Sink.Float64, Sink.Error = ParseFloat(string(bytes.Number), 64)
+ }))
+ t.Run("ParseComplex", checkNoAllocs(func() {
+ Sink.Complex128, Sink.Error = ParseComplex(string(bytes.Number), 128)
+ }))
+ t.Run("CanBackquote", checkNoAllocs(func() {
+ Sink.Bool = CanBackquote(string(bytes.String))
+ }))
+ t.Run("AppendQuote", checkNoAllocs(func() {
+ Sink.Bytes = AppendQuote(bytes.Buffer[:0], string(bytes.String))
+ }))
+ t.Run("AppendQuoteToASCII", checkNoAllocs(func() {
+ Sink.Bytes = AppendQuoteToASCII(bytes.Buffer[:0], string(bytes.String))
+ }))
+ t.Run("AppendQuoteToGraphic", checkNoAllocs(func() {
+ Sink.Bytes = AppendQuoteToGraphic(bytes.Buffer[:0], string(bytes.String))
+ }))
+}
+
func TestErrorPrefixes(t *testing.T) {
_, errInt := Atoi("INVALID")
_, errBool := ParseBool("INVALID")