anchore/grype
Version comparators
Active contributors: Alex Goodman, Will Murphy, Christopher Phillips
Purpose
Different ecosystems have wildly different version semantics: 1.2.3-alpha is a pre-release in semver but a post-release in Debian. RPM has a numeric epoch. Python distinguishes 1.0.0.dev1 from 1.0.0a1. APK has its own modifier semantics. The version package centralizes all of these comparators behind a single Comparator interface.
Directory layout
grype/version/
├── version.go # Version + Format
├── format.go # Format enum and detection
├── comparator.go # Comparator interface
├── constraint.go # Constraint
├── range.go # Range + range_expression
├── range_expression.go
├── set.go # SetOperator
├── operator.go # Operator (=, >, <, >=, <=, !=)
├── error.go
├── deprecated.go
│
├── apk_version.go # Alpine apk
├── deb_version.go # Debian dpkg
├── rpm_version.go # RPM
├── semantic_version.go # SemVer (npm, cargo, ...)
├── gem_version.go # Ruby gems
├── maven_version.go # Maven
├── pep440_version.go # Python PEP 440
├── golang_version.go # Go modules (semver-ish + pseudo-versions)
├── jvm_version.go # JVM (Oracle versioning, e.g. 1.8.0_362)
├── pacman_version.go # Arch pacman
├── portage_version.go # Gentoo portage
├── kb_version.go # Microsoft KB articles
├── bitnami_version.go # Bitnami packaging
├── fuzzy_version.go # last-resort fuzzy comparison
└── combined_constraint.go # AND/OR compositionThe Comparator interface
grype/version/comparator.go:
type Comparator interface {
Compare(other *Version) (int, error)
}Each *_version.go file defines a struct implementing this interface, with New<Format>Version(raw) constructors and validation. The Version value (in version.go) holds the raw string plus a Format, and lazily constructs the appropriate comparator.
Constraint evaluation
Constraint (constraint.go) is the matching primitive: given a version string and a list of constraint expressions like >= 1.2.3, < 1.3.0, it returns whether the version satisfies all constraints.
Constraints come in several shapes:
generic_constraint.go— operator + version, e.g.>= 1.2.3. The default for most formats.combined_constraint.go— AND/OR composition of sub-constraints.range.go/range_expression.go— interval syntax shared by Java/Maven and others.fuzzy_constraint.go— best-effort comparison when no specific format is known.kb_constraint.go— Microsoft KB-specific (numeric KB id ordering).
The matcher pipeline asks the version package: "does package p's version satisfy this affected-version constraint?" If yes, it's a vulnerability hit; otherwise no.
Format detection
format.go::FormatFromPkgURL and friends convert from PURL and Syft package types to a Format value. The selected format determines which comparator and constraint parser is used. version_format.go in grype/pkg/ is the higher-level call that chooses the right format from a pkg.Package.
Why so many?
Version comparison is one of the highest-leverage correctness concerns in a scanner. Each ecosystem hides corner cases:
- APK suffix sorting is different from semver pre-release sorting.
- Debian has the upstream-version + debian-revision split with
~for pre-releases. - RPM has a leading numeric epoch separated by
:. - Python PEP 440 has post-releases, dev-releases, and local-version segments (
+local). - Maven ranges include hard
[/]and soft(/)boundaries with their own ordering rules. - Gem RubyGems uses dot-separated comparators that are semver-similar but not quite.
- Go modules have semver +
+incompatible+ pseudo-versions likev0.0.0-20190101000000-abcdef.
The corresponding test files are extensive — deb_version_test.go is 17 KLoC, rpm_version_test.go is 14 KLoC, all encoding subtle edge cases. The wisdom of past CVE triage lives in those tables.
External libraries used
Wherever possible, comparators wrap a battle-tested upstream library:
knqyf263/go-apk-version— APK.knqyf263/go-deb-version— Debian.aquasecurity/go-pep440-version— PEP 440.bitnami/go-version— Bitnami.masahiro331/go-mvn-version— Maven.Masterminds/semver/v3— semver.anchore/go-version— generalized fork.pandatix/go-cvss— used downstream (CVSS, not strictly versions).
Key abstractions
| Symbol | Where | Description |
|---|---|---|
version.Version |
grype/version/version.go |
Raw + Format. |
version.Format |
grype/version/format.go |
Enum of supported formats. |
version.Comparator |
grype/version/comparator.go |
The compare-two-versions interface. |
version.Constraint |
grype/version/constraint.go |
Constraint expression evaluator. |
version.New<Fmt>Version(raw) |
per *_version.go |
Constructor for a format-specific version. |
Key source files
| File | Purpose |
|---|---|
grype/version/version.go |
Public Version type. |
grype/version/comparator.go |
Comparator interface. |
grype/version/constraint.go |
Constraint evaluation. |
grype/version/format.go |
Format enum + detection. |
grype/version/<ecosystem>_version.go |
Per-ecosystem comparator. |
Entry points for modification
- Bug in version comparison — fix in the relevant
*_version.goand add a row to its_test.gotable. Almost every comparator regression has been driven by table-row additions. - New ecosystem — add a
<ecosystem>_version.goand<ecosystem>_constraint.go(or reusegeneric_constraint), add aFormatenum value, and wire it inversion_format.go(ingrype/pkg/) andformat.go. - Constraint syntax change — modify
constraint.goand the relevant*_constraint.go. Fuzzy and Maven are the trickiest.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.