Skip to content

Commit 06f2873

Browse files
authored
fix: switch to using big.Int in version parsing & comparing to support really large numbers (#155)
1 parent 7cbc516 commit 06f2873

File tree

5 files changed

+103
-67
lines changed

5 files changed

+103
-67
lines changed

pkg/semantic/compare.go

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package semantic
22

33
import (
4+
"math/big"
45
"regexp"
5-
"strconv"
66
)
77

88
func maxInt(x, y int) int {
@@ -13,23 +13,11 @@ func maxInt(x, y int) int {
1313
return x
1414
}
1515

16-
func compareInt(a int, b int) int {
17-
if a == b {
18-
return 0
19-
}
20-
21-
if a < b {
22-
return -1
23-
}
24-
25-
return +1
26-
}
27-
2816
func compareComponents(a Components, b Components) int {
2917
numberOfComponents := maxInt(len(a), len(b))
3018

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

3422
if diff != 0 {
3523
return diff
@@ -39,16 +27,18 @@ func compareComponents(a Components, b Components) int {
3927
return 0
4028
}
4129

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

4533
results := matcher.FindStringSubmatch(str)
4634

4735
if results == nil {
48-
return 0
36+
return big.NewInt(0)
4937
}
5038

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

5343
return r
5444
}
@@ -64,7 +54,7 @@ func compareBuilds(a string, b string) int {
6454
av := tryExtractNumber(a)
6555
bv := tryExtractNumber(b)
6656

67-
return compareInt(av, bv)
57+
return av.Cmp(bv)
6858
}
6959

7060
// Compare returns an integer comparing two versions according to

pkg/semantic/compare_test.go

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package semantic_test
22

33
import (
44
"github.com/g-rath/osv-detector/pkg/semantic"
5+
"math/big"
56
"testing"
67
)
78

@@ -42,7 +43,13 @@ func expectCompareResult(
4243
}
4344

4445
func buildlessVersion(build string, components ...int) semantic.Version {
45-
return semantic.Version{Components: components, Build: build}
46+
comps := make([]*big.Int, 0, len(components))
47+
48+
for _, i := range components {
49+
comps = append(comps, big.NewInt(int64(i)))
50+
}
51+
52+
return semantic.Version{Components: comps, Build: build}
4653
}
4754

4855
func TestVersion_Compare_BasicEqual(t *testing.T) {
@@ -426,32 +433,57 @@ func TestVersion_Compare_BasicWithLeadingV(t *testing.T) {
426433
t.Parallel()
427434

428435
expectCompareResult(t,
429-
semantic.Version{LeadingV: false, Components: []int{1}, Build: ""},
430-
semantic.Version{LeadingV: false, Components: []int{1}, Build: ""},
436+
semantic.Version{LeadingV: false, Components: []*big.Int{big.NewInt(1)}, Build: ""},
437+
semantic.Version{LeadingV: false, Components: []*big.Int{big.NewInt(1)}, Build: ""},
431438
0,
432439
)
433440

434441
expectCompareResult(t,
435-
semantic.Version{LeadingV: true, Components: []int{1}, Build: ""},
436-
semantic.Version{LeadingV: false, Components: []int{1}, Build: ""},
442+
semantic.Version{LeadingV: true, Components: []*big.Int{big.NewInt(1)}, Build: ""},
443+
semantic.Version{LeadingV: false, Components: []*big.Int{big.NewInt(1)}, Build: ""},
437444
0,
438445
)
439446

440447
expectCompareResult(t,
441-
semantic.Version{LeadingV: false, Components: []int{1}, Build: ""},
442-
semantic.Version{LeadingV: true, Components: []int{1}, Build: ""},
448+
semantic.Version{LeadingV: false, Components: []*big.Int{big.NewInt(1)}, Build: ""},
449+
semantic.Version{LeadingV: true, Components: []*big.Int{big.NewInt(1)}, Build: ""},
443450
0,
444451
)
445452

446453
expectCompareResult(t,
447-
semantic.Version{LeadingV: true, Components: []int{1}, Build: ""},
448-
semantic.Version{LeadingV: true, Components: []int{1}, Build: ""},
454+
semantic.Version{LeadingV: true, Components: []*big.Int{big.NewInt(1)}, Build: ""},
455+
semantic.Version{LeadingV: true, Components: []*big.Int{big.NewInt(1)}, Build: ""},
449456
0,
450457
)
451458

452459
expectCompareResult(t,
453-
semantic.Version{LeadingV: true, Components: []int{2}, Build: ""},
454-
semantic.Version{LeadingV: true, Components: []int{1}, Build: ""},
460+
semantic.Version{LeadingV: true, Components: []*big.Int{big.NewInt(2)}, Build: ""},
461+
semantic.Version{LeadingV: true, Components: []*big.Int{big.NewInt(1)}, Build: ""},
455462
1,
456463
)
457464
}
465+
466+
func TestVersion_Compare_BasicWithBigComponents(t *testing.T) {
467+
t.Parallel()
468+
469+
big1, _ := new(big.Int).SetString("9999999999999999999999999999999999999999999999999999999999999999", 10)
470+
big2, _ := new(big.Int).SetString("9999999999999999999999999999999999999999999999999999999999999998", 10)
471+
472+
expectCompareResult(t,
473+
semantic.Version{Components: []*big.Int{big1}},
474+
semantic.Version{Components: []*big.Int{big1}},
475+
0,
476+
)
477+
478+
expectCompareResult(t,
479+
semantic.Version{Components: []*big.Int{big1}},
480+
semantic.Version{Components: []*big.Int{big2}},
481+
1,
482+
)
483+
484+
expectCompareResult(t,
485+
semantic.Version{Components: []*big.Int{big1, big1, big1, big2}},
486+
semantic.Version{Components: []*big.Int{big1, big1, big1, big1}},
487+
-1,
488+
)
489+
}

pkg/semantic/parse.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
package semantic
22

33
import (
4+
"math/big"
45
"regexp"
5-
"strconv"
66
"strings"
77
)
88

99
func Parse(line string) Version {
10-
var components []int
10+
var components []*big.Int
1111

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

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

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

7272
components = append(components, v)
7373
currentCom = ""

0 commit comments

Comments
 (0)