Skip to content

Commit

Permalink
fix: switch to using big.Int in version parsing & comparing to supp…
Browse files Browse the repository at this point in the history
…ort really large numbers (#155)
  • Loading branch information
G-Rath authored Sep 26, 2022
1 parent 7cbc516 commit 06f2873
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 67 deletions.
26 changes: 8 additions & 18 deletions pkg/semantic/compare.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package semantic

import (
"math/big"
"regexp"
"strconv"
)

func maxInt(x, y int) int {
Expand All @@ -13,23 +13,11 @@ func maxInt(x, y int) int {
return x
}

func compareInt(a int, b int) int {
if a == b {
return 0
}

if a < b {
return -1
}

return +1
}

func compareComponents(a Components, b Components) int {
numberOfComponents := maxInt(len(a), len(b))

for i := 0; i < numberOfComponents; i++ {
diff := compareInt(a.Fetch(i), b.Fetch(i))
diff := a.Fetch(i).Cmp(b.Fetch(i))

if diff != 0 {
return diff
Expand All @@ -39,16 +27,18 @@ func compareComponents(a Components, b Components) int {
return 0
}

func tryExtractNumber(str string) int {
func tryExtractNumber(str string) *big.Int {
matcher := regexp.MustCompile(`[a-zA-Z.-]+(\d+)`)

results := matcher.FindStringSubmatch(str)

if results == nil {
return 0
return big.NewInt(0)
}

r, _ := strconv.Atoi(results[1])
// it should not be possible for this to not be a number,
// because we select only numbers above in our regexp
r, _ := new(big.Int).SetString(results[1], 10)

return r
}
Expand All @@ -64,7 +54,7 @@ func compareBuilds(a string, b string) int {
av := tryExtractNumber(a)
bv := tryExtractNumber(b)

return compareInt(av, bv)
return av.Cmp(bv)
}

// Compare returns an integer comparing two versions according to
Expand Down
54 changes: 43 additions & 11 deletions pkg/semantic/compare_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package semantic_test

import (
"github.com/g-rath/osv-detector/pkg/semantic"
"math/big"
"testing"
)

Expand Down Expand Up @@ -42,7 +43,13 @@ func expectCompareResult(
}

func buildlessVersion(build string, components ...int) semantic.Version {
return semantic.Version{Components: components, Build: build}
comps := make([]*big.Int, 0, len(components))

for _, i := range components {
comps = append(comps, big.NewInt(int64(i)))
}

return semantic.Version{Components: comps, Build: build}
}

func TestVersion_Compare_BasicEqual(t *testing.T) {
Expand Down Expand Up @@ -426,32 +433,57 @@ func TestVersion_Compare_BasicWithLeadingV(t *testing.T) {
t.Parallel()

expectCompareResult(t,
semantic.Version{LeadingV: false, Components: []int{1}, Build: ""},
semantic.Version{LeadingV: false, Components: []int{1}, Build: ""},
semantic.Version{LeadingV: false, Components: []*big.Int{big.NewInt(1)}, Build: ""},
semantic.Version{LeadingV: false, Components: []*big.Int{big.NewInt(1)}, Build: ""},
0,
)

expectCompareResult(t,
semantic.Version{LeadingV: true, Components: []int{1}, Build: ""},
semantic.Version{LeadingV: false, Components: []int{1}, Build: ""},
semantic.Version{LeadingV: true, Components: []*big.Int{big.NewInt(1)}, Build: ""},
semantic.Version{LeadingV: false, Components: []*big.Int{big.NewInt(1)}, Build: ""},
0,
)

expectCompareResult(t,
semantic.Version{LeadingV: false, Components: []int{1}, Build: ""},
semantic.Version{LeadingV: true, Components: []int{1}, Build: ""},
semantic.Version{LeadingV: false, Components: []*big.Int{big.NewInt(1)}, Build: ""},
semantic.Version{LeadingV: true, Components: []*big.Int{big.NewInt(1)}, Build: ""},
0,
)

expectCompareResult(t,
semantic.Version{LeadingV: true, Components: []int{1}, Build: ""},
semantic.Version{LeadingV: true, Components: []int{1}, Build: ""},
semantic.Version{LeadingV: true, Components: []*big.Int{big.NewInt(1)}, Build: ""},
semantic.Version{LeadingV: true, Components: []*big.Int{big.NewInt(1)}, Build: ""},
0,
)

expectCompareResult(t,
semantic.Version{LeadingV: true, Components: []int{2}, Build: ""},
semantic.Version{LeadingV: true, Components: []int{1}, Build: ""},
semantic.Version{LeadingV: true, Components: []*big.Int{big.NewInt(2)}, Build: ""},
semantic.Version{LeadingV: true, Components: []*big.Int{big.NewInt(1)}, Build: ""},
1,
)
}

func TestVersion_Compare_BasicWithBigComponents(t *testing.T) {
t.Parallel()

big1, _ := new(big.Int).SetString("9999999999999999999999999999999999999999999999999999999999999999", 10)
big2, _ := new(big.Int).SetString("9999999999999999999999999999999999999999999999999999999999999998", 10)

expectCompareResult(t,
semantic.Version{Components: []*big.Int{big1}},
semantic.Version{Components: []*big.Int{big1}},
0,
)

expectCompareResult(t,
semantic.Version{Components: []*big.Int{big1}},
semantic.Version{Components: []*big.Int{big2}},
1,
)

expectCompareResult(t,
semantic.Version{Components: []*big.Int{big1, big1, big1, big2}},
semantic.Version{Components: []*big.Int{big1, big1, big1, big1}},
-1,
)
}
8 changes: 4 additions & 4 deletions pkg/semantic/parse.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package semantic

import (
"math/big"
"regexp"
"strconv"
"strings"
)

func Parse(line string) Version {
var components []int
var components []*big.Int

numberReg := regexp.MustCompile(`\d`)

Expand Down Expand Up @@ -43,7 +43,7 @@ func Parse(line string) Version {
// either way, we will be terminating the current component being
// parsed (if any), so let's do that first
if currentCom != "" {
v, _ := strconv.Atoi(currentCom)
v, _ := new(big.Int).SetString(currentCom, 10)

components = append(components, v)
currentCom = ""
Expand All @@ -67,7 +67,7 @@ func Parse(line string) Version {
// if we looped over everything without finding a build string,
// then what we were currently parsing is actually a component
if !foundBuild && currentCom != "" {
v, _ := strconv.Atoi(currentCom)
v, _ := new(big.Int).SetString(currentCom, 10)

components = append(components, v)
currentCom = ""
Expand Down
Loading

0 comments on commit 06f2873

Please sign in to comment.