mirror of
https://github.com/tailscale/tailscale.git
synced 2025-02-27 10:47:35 +00:00
version: don't allocate parsing unsupported versions, empty strings
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
parent
f11a8928a6
commit
1e6d8a1043
@ -5,7 +5,6 @@
|
|||||||
package version
|
package version
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -68,8 +67,8 @@ type parsed struct {
|
|||||||
|
|
||||||
func parse(version string) (parsed, bool) {
|
func parse(version string) (parsed, bool) {
|
||||||
if strings.HasPrefix(version, "date.") {
|
if strings.HasPrefix(version, "date.") {
|
||||||
stamp, err := strconv.Atoi(version[5:])
|
stamp, ok := atoi(version[5:])
|
||||||
if err != nil {
|
if !ok {
|
||||||
return parsed{}, false
|
return parsed{}, false
|
||||||
}
|
}
|
||||||
return parsed{Datestamp: stamp}, true
|
return parsed{Datestamp: stamp}, true
|
||||||
@ -77,8 +76,8 @@ func parse(version string) (parsed, bool) {
|
|||||||
|
|
||||||
var ret parsed
|
var ret parsed
|
||||||
|
|
||||||
major, rest, err := splitNumericPrefix(version)
|
major, rest, ok := splitNumericPrefix(version)
|
||||||
if err != nil {
|
if !ok {
|
||||||
return parsed{}, false
|
return parsed{}, false
|
||||||
}
|
}
|
||||||
ret.Major = major
|
ret.Major = major
|
||||||
@ -86,8 +85,8 @@ func parse(version string) (parsed, bool) {
|
|||||||
return ret, true
|
return ret, true
|
||||||
}
|
}
|
||||||
|
|
||||||
ret.Minor, rest, err = splitNumericPrefix(rest[1:])
|
ret.Minor, rest, ok = splitNumericPrefix(rest[1:])
|
||||||
if err != nil {
|
if !ok {
|
||||||
return parsed{}, false
|
return parsed{}, false
|
||||||
}
|
}
|
||||||
if len(rest) == 0 {
|
if len(rest) == 0 {
|
||||||
@ -96,8 +95,8 @@ func parse(version string) (parsed, bool) {
|
|||||||
|
|
||||||
// Optional patch version, if the next separator is a dot.
|
// Optional patch version, if the next separator is a dot.
|
||||||
if rest[0] == '.' {
|
if rest[0] == '.' {
|
||||||
ret.Patch, rest, err = splitNumericPrefix(rest[1:])
|
ret.Patch, rest, ok = splitNumericPrefix(rest[1:])
|
||||||
if err != nil {
|
if !ok {
|
||||||
return parsed{}, false
|
return parsed{}, false
|
||||||
}
|
}
|
||||||
if len(rest) == 0 {
|
if len(rest) == 0 {
|
||||||
@ -112,29 +111,84 @@ func parse(version string) (parsed, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var trailer string
|
var trailer string
|
||||||
ret.ExtraCommits, trailer, err = splitNumericPrefix(rest[1:])
|
ret.ExtraCommits, trailer, ok = splitNumericPrefix(rest[1:])
|
||||||
if err != nil || (len(trailer) > 0 && trailer[0] != '-') {
|
if !ok || (len(trailer) > 0 && trailer[0] != '-') {
|
||||||
// rest was probably the string trailer, ignore it.
|
// rest was probably the string trailer, ignore it.
|
||||||
ret.ExtraCommits = 0
|
ret.ExtraCommits = 0
|
||||||
}
|
}
|
||||||
return ret, true
|
return ret, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func splitNumericPrefix(s string) (int, string, error) {
|
func splitNumericPrefix(s string) (n int, rest string, ok bool) {
|
||||||
for i, r := range s {
|
for i, r := range s {
|
||||||
if r >= '0' && r <= '9' {
|
if r >= '0' && r <= '9' {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
ret, err := strconv.Atoi(s[:i])
|
ret, ok := atoi(s[:i])
|
||||||
if err != nil {
|
if !ok {
|
||||||
return 0, "", err
|
return 0, "", false
|
||||||
}
|
}
|
||||||
return ret, s[i:], nil
|
return ret, s[i:], true
|
||||||
}
|
}
|
||||||
|
|
||||||
ret, err := strconv.Atoi(s)
|
ret, ok := atoi(s)
|
||||||
if err != nil {
|
if !ok {
|
||||||
return 0, "", err
|
return 0, "", false
|
||||||
}
|
}
|
||||||
return ret, "", nil
|
return ret, "", true
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
maxUint = ^uint(0)
|
||||||
|
maxInt = int(maxUint >> 1)
|
||||||
|
)
|
||||||
|
|
||||||
|
// atoi parses an int from a string s.
|
||||||
|
// The bool result reports whether s is a number
|
||||||
|
// representable by a value of type int.
|
||||||
|
//
|
||||||
|
// From Go's runtime/string.go.
|
||||||
|
func atoi(s string) (int, bool) {
|
||||||
|
if s == "" {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
neg := false
|
||||||
|
if s[0] == '-' {
|
||||||
|
neg = true
|
||||||
|
s = s[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
un := uint(0)
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
c := s[i]
|
||||||
|
if c < '0' || c > '9' {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
if un > maxUint/10 {
|
||||||
|
// overflow
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
un *= 10
|
||||||
|
un1 := un + uint(c) - '0'
|
||||||
|
if un1 < un {
|
||||||
|
// overflow
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
un = un1
|
||||||
|
}
|
||||||
|
|
||||||
|
if !neg && un > uint(maxInt) {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
if neg && un > uint(maxInt)+1 {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
n := int(un)
|
||||||
|
if neg {
|
||||||
|
n = -n
|
||||||
|
}
|
||||||
|
|
||||||
|
return n, true
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,7 @@ func TestParse(t *testing.T) {
|
|||||||
{"date.20200612", parsed{Datestamp: 20200612}, true},
|
{"date.20200612", parsed{Datestamp: 20200612}, true},
|
||||||
{"borkbork", parsed{}, false},
|
{"borkbork", parsed{}, false},
|
||||||
{"1a.2.3", parsed{}, false},
|
{"1a.2.3", parsed{}, false},
|
||||||
|
{"", parsed{}, false},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
@ -38,6 +39,12 @@ func TestParse(t *testing.T) {
|
|||||||
if diff := cmp.Diff(gotParsed, test.parsed); diff != "" {
|
if diff := cmp.Diff(gotParsed, test.parsed); diff != "" {
|
||||||
t.Errorf("parse(%q) diff (-got+want):\n%s", test.version, diff)
|
t.Errorf("parse(%q) diff (-got+want):\n%s", test.version, diff)
|
||||||
}
|
}
|
||||||
|
n := int(testing.AllocsPerRun(1000, func() {
|
||||||
|
gotParsed, got = parse(test.version)
|
||||||
|
}))
|
||||||
|
if n != 0 {
|
||||||
|
t.Errorf("parse(%q) allocs = %d; want 0", test.version, n)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user