diff --git a/grype/version/maven_version.go b/grype/version/maven_version.go index 44b9a613b3c..ff33155f5ce 100644 --- a/grype/version/maven_version.go +++ b/grype/version/maven_version.go @@ -2,19 +2,49 @@ package version import ( "fmt" + "regexp" mvnv "github.com/masahiro331/go-mvn-version" ) var _ Comparator = (*mavenVersion)(nil) +// javaRuntimeQualifierPattern matches .jreNN or .jdkNN suffixes (case-insensitive) at the end of version strings +var javaRuntimeQualifierPattern = regexp.MustCompile(`(?i)\.(jre|jdk)\d+$`) + type mavenVersion struct { raw string obj mvnv.Version } +// stripJavaRuntimeQualifier removes .jreNN or .jdkNN suffixes from version strings. +// These are runtime-specific qualifiers that don't affect version comparison. +// +// The pattern matches 'jre' or 'jdk' (case-insensitive) followed by one or more digits +// at the END of the version string only. This means: +// - Case-insensitive: Both .jre11 and .JRE11 will be stripped +// - Requires digits: .jre or .jdk without numbers will NOT be stripped +// - End-anchored: .jre11-SNAPSHOT or .jdk17.beta will NOT be stripped +// +// Examples: +// - "12.10.2.jre11" -> "12.10.2" (stripped) +// - "12.10.2.JRE11" -> "12.10.2" (stripped) +// - "12.10.2.jdk17" -> "12.10.2" (stripped) +// - "12.10.2.JDK17" -> "12.10.2" (stripped) +// - "12.10.2" -> "12.10.2" (no change) +// - "12.10.2.jre" -> "12.10.2.jre" (no digits, not stripped) +// - "12.10.2.jre11-SNAPSHOT" -> "12.10.2.jre11-SNAPSHOT" (not at end, not stripped) +func stripJavaRuntimeQualifier(version string) string { + return javaRuntimeQualifierPattern.ReplaceAllString(version, "") +} + func newMavenVersion(raw string) (mavenVersion, error) { - ver, err := mvnv.NewVersion(raw) + // strip Java runtime qualifiers (e.g., .jre11, .jdk17) before parsing to ensure + // versions like "12.10.2" and "12.10.2.jre11" are treated as equivalent for comparison. + // The original raw version is preserved for display purposes. + normalized := stripJavaRuntimeQualifier(raw) + + ver, err := mvnv.NewVersion(normalized) if err != nil { return mavenVersion{}, fmt.Errorf("could not generate new java version from: %s; %w", raw, err) } diff --git a/grype/version/maven_version_test.go b/grype/version/maven_version_test.go index 970a07e29b8..44592da9c53 100644 --- a/grype/version/maven_version_test.go +++ b/grype/version/maven_version_test.go @@ -8,6 +8,87 @@ import ( "github.com/stretchr/testify/require" ) +func TestStripJavaRuntimeQualifier(t *testing.T) { + tests := []struct { + name string + input string + want string + }{ + { + name: "version with jre11", + input: "12.10.2.jre11", + want: "12.10.2", + }, + { + name: "version with jdk17", + input: "12.10.2.jdk17", + want: "12.10.2", + }, + { + name: "version with uppercase JRE11", + input: "12.10.2.JRE11", + want: "12.10.2", + }, + { + name: "version with uppercase JDK17", + input: "12.10.2.JDK17", + want: "12.10.2", + }, + { + name: "version with mixed case Jre11", + input: "12.10.2.Jre11", + want: "12.10.2", + }, + { + name: "version without qualifier", + input: "12.10.2", + want: "12.10.2", + }, + { + name: "version with jre but no digits", + input: "12.10.2.jre", + want: "12.10.2.jre", + }, + { + name: "version with jdk but no digits", + input: "12.10.2.jdk", + want: "12.10.2.jdk", + }, + { + name: "version with jre0 (zero)", + input: "12.10.2.jre0", + want: "12.10.2", + }, + { + name: "version with jdk999 (large number)", + input: "12.10.2.jdk999", + want: "12.10.2", + }, + { + name: "version with jre11 followed by SNAPSHOT", + input: "12.10.2.jre11-SNAPSHOT", + want: "12.10.2.jre11-SNAPSHOT", + }, + { + name: "version with jdk17 followed by beta", + input: "12.10.2.jdk17.beta", + want: "12.10.2.jdk17.beta", + }, + { + name: "version with JRE uppercase followed by SNAPSHOT", + input: "12.10.2.JRE11-SNAPSHOT", + want: "12.10.2.JRE11-SNAPSHOT", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := stripJavaRuntimeQualifier(tt.input) + require.Equal(t, tt.want, got) + }) + } +} + func TestMavenVersion_Constraint(t *testing.T) { tests := []testCase{ // range expressions @@ -152,6 +233,42 @@ func TestMavenVersion_Compare(t *testing.T) { v2: "5.2.25", want: 0, }, + // JRE/JDK qualifier tests (GitHub issue: JRE version matching) + { + v1: "12.10.2", + v2: "12.10.2.jre11", + want: 0, + }, + { + v1: "12.10.2.jre11", + v2: "12.10.2", + want: 0, + }, + { + v1: "12.10.2.jdk17", + v2: "12.10.2", + want: 0, + }, + { + v1: "12.10.2.jre11", + v2: "12.10.2.jdk17", + want: 0, + }, + { + v1: "12.10.1", + v2: "12.10.2.jre11", + want: -1, + }, + { + v1: "12.10.2.jre11", + v2: "12.10.1", + want: 1, + }, + { + v1: "1.2.3.jre8", + v2: "1.2.4.jre8", + want: -1, + }, } for _, tt := range tests { t.Run(tt.v1+" vs "+tt.v2, func(t *testing.T) {