Skip to content

Commit 63c4284

Browse files
martischgriesemer
authored andcommitted
strconv: check bitsize range in ParseInt and ParseUint
Return an error when a bitSize below 0 or above 64 is specified. Move bitSize 0 handling in ParseInt after the call to ParseUint to avoid a spill. AMD64: name old time/op new time/op delta Atoi 28.9ns ± 6% 27.4ns ± 6% -5.21% (p=0.002 n=20+20) AtoiNeg 24.6ns ± 2% 23.1ns ± 1% -6.04% (p=0.000 n=19+18) Atoi64 38.8ns ± 1% 38.0ns ± 1% -2.03% (p=0.000 n=17+20) Atoi64Neg 35.5ns ± 1% 34.3ns ± 1% -3.42% (p=0.000 n=19+20) Updates golang#21275 Change-Id: I70f0e4a16fa003f7ea929ca4ef56bd1a4181660b Reviewed-on: https://go-review.googlesource.com/55139 Reviewed-by: Robert Griesemer <[email protected]> Run-TryBot: Robert Griesemer <[email protected]>
1 parent 4c0bba1 commit 63c4284

3 files changed

Lines changed: 113 additions & 12 deletions

File tree

src/strconv/atoi.go

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ func baseError(fn, str string, base int) *NumError {
3535
return &NumError{fn, str, errors.New("invalid base " + Itoa(base))}
3636
}
3737

38+
func bitSizeError(fn, str string, bitSize int) *NumError {
39+
return &NumError{fn, str, errors.New("invalid bit size " + Itoa(bitSize))}
40+
}
41+
3842
const intSize = 32 << (^uint(0) >> 63)
3943

4044
// IntSize is the size in bits of an int or uint value.
@@ -46,10 +50,6 @@ const maxUint64 = (1<<64 - 1)
4650
func ParseUint(s string, base int, bitSize int) (uint64, error) {
4751
const fnParseUint = "ParseUint"
4852

49-
if bitSize == 0 {
50-
bitSize = int(IntSize)
51-
}
52-
5353
if len(s) == 0 {
5454
return 0, syntaxError(fnParseUint, s)
5555
}
@@ -79,6 +79,12 @@ func ParseUint(s string, base int, bitSize int) (uint64, error) {
7979
return 0, baseError(fnParseUint, s0, base)
8080
}
8181

82+
if bitSize == 0 {
83+
bitSize = int(IntSize)
84+
} else if bitSize < 0 || bitSize > 64 {
85+
return 0, bitSizeError(fnParseUint, s0, bitSize)
86+
}
87+
8288
// Cutoff is the smallest number such that cutoff*base > maxUint64.
8389
// Use compile-time constants for common cases.
8490
var cutoff uint64
@@ -128,14 +134,17 @@ func ParseUint(s string, base int, bitSize int) (uint64, error) {
128134
return n, nil
129135
}
130136

131-
// ParseInt interprets a string s in the given base (2 to 36) and
132-
// returns the corresponding value i. If base == 0, the base is
133-
// implied by the string's prefix: base 16 for "0x", base 8 for
134-
// "0", and base 10 otherwise.
137+
// ParseInt interprets a string s in the given base (0, 2 to 36) and
138+
// bit size (0 to 64) and returns the corresponding value i.
139+
//
140+
// If base == 0, the base is implied by the string's prefix:
141+
// base 16 for "0x", base 8 for "0", and base 10 otherwise.
142+
// For bases 1, below 0 or above 36 an error is returned.
135143
//
136144
// The bitSize argument specifies the integer type
137145
// that the result must fit into. Bit sizes 0, 8, 16, 32, and 64
138146
// correspond to int, int8, int16, int32, and int64.
147+
// For a bitSize below 0 or above 64 an error is returned.
139148
//
140149
// The errors that ParseInt returns have concrete type *NumError
141150
// and include err.Num = s. If s is empty or contains invalid
@@ -147,10 +156,6 @@ func ParseUint(s string, base int, bitSize int) (uint64, error) {
147156
func ParseInt(s string, base int, bitSize int) (i int64, err error) {
148157
const fnParseInt = "ParseInt"
149158

150-
if bitSize == 0 {
151-
bitSize = int(IntSize)
152-
}
153-
154159
// Empty string bad.
155160
if len(s) == 0 {
156161
return 0, syntaxError(fnParseInt, s)
@@ -174,6 +179,11 @@ func ParseInt(s string, base int, bitSize int) (i int64, err error) {
174179
err.(*NumError).Num = s0
175180
return 0, err
176181
}
182+
183+
if bitSize == 0 {
184+
bitSize = int(IntSize)
185+
}
186+
177187
cutoff := uint64(1 << uint(bitSize-1))
178188
if !neg && un >= cutoff {
179189
return int64(cutoff - 1), rangeError(fnParseInt, s0)

src/strconv/atoi_test.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,87 @@ func TestParseInt(t *testing.T) {
354354
}
355355
}
356356

357+
func bitSizeErrStub(name string, bitSize int) error {
358+
return BitSizeError(name, "0", bitSize)
359+
}
360+
361+
func baseErrStub(name string, base int) error {
362+
return BaseError(name, "0", base)
363+
}
364+
365+
func noErrStub(name string, arg int) error {
366+
return nil
367+
}
368+
369+
type parseErrorTest struct {
370+
arg int
371+
errStub func(name string, arg int) error
372+
}
373+
374+
var parseBitSizeTests = []parseErrorTest{
375+
{-1, bitSizeErrStub},
376+
{0, noErrStub},
377+
{64, noErrStub},
378+
{65, bitSizeErrStub},
379+
}
380+
381+
var parseBaseTests = []parseErrorTest{
382+
{-1, baseErrStub},
383+
{0, noErrStub},
384+
{1, baseErrStub},
385+
{2, noErrStub},
386+
{36, noErrStub},
387+
{37, baseErrStub},
388+
}
389+
390+
func TestParseIntBitSize(t *testing.T) {
391+
for i := range parseBitSizeTests {
392+
test := &parseBitSizeTests[i]
393+
testErr := test.errStub("ParseInt", test.arg)
394+
_, err := ParseInt("0", 0, test.arg)
395+
if !reflect.DeepEqual(testErr, err) {
396+
t.Errorf("ParseInt(\"0\", 0, %v) = 0, %v want 0, %v",
397+
test.arg, err, testErr)
398+
}
399+
}
400+
}
401+
402+
func TestParseUintBitSize(t *testing.T) {
403+
for i := range parseBitSizeTests {
404+
test := &parseBitSizeTests[i]
405+
testErr := test.errStub("ParseUint", test.arg)
406+
_, err := ParseUint("0", 0, test.arg)
407+
if !reflect.DeepEqual(testErr, err) {
408+
t.Errorf("ParseUint(\"0\", 0, %v) = 0, %v want 0, %v",
409+
test.arg, err, testErr)
410+
}
411+
}
412+
}
413+
414+
func TestParseIntBase(t *testing.T) {
415+
for i := range parseBaseTests {
416+
test := &parseBaseTests[i]
417+
testErr := test.errStub("ParseInt", test.arg)
418+
_, err := ParseInt("0", test.arg, 0)
419+
if !reflect.DeepEqual(testErr, err) {
420+
t.Errorf("ParseInt(\"0\", %v, 0) = 0, %v want 0, %v",
421+
test.arg, err, testErr)
422+
}
423+
}
424+
}
425+
426+
func TestParseUintBase(t *testing.T) {
427+
for i := range parseBaseTests {
428+
test := &parseBaseTests[i]
429+
testErr := test.errStub("ParseUint", test.arg)
430+
_, err := ParseUint("0", test.arg, 0)
431+
if !reflect.DeepEqual(testErr, err) {
432+
t.Errorf("ParseUint(\"0\", %v, 0) = 0, %v want 0, %v",
433+
test.arg, err, testErr)
434+
}
435+
}
436+
}
437+
357438
func TestNumError(t *testing.T) {
358439
for _, test := range numErrorTests {
359440
err := &NumError{

src/strconv/export_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Copyright 2017 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package strconv
6+
7+
var (
8+
BitSizeError = bitSizeError
9+
BaseError = baseError
10+
)

0 commit comments

Comments
 (0)