From 6966b489c83ec5743ca9fe81d76e0d14dc81f5c7 Mon Sep 17 00:00:00 2001 From: James Gardner Date: Thu, 6 Nov 2025 12:56:28 -0600 Subject: [PATCH 1/2] fix: normalize java runtime qualifiers in maven version comparisons Signed-off-by: James Gardner --- grype/version/maven_version.go | 18 ++++++++++++++- grype/version/maven_version_test.go | 36 +++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/grype/version/maven_version.go b/grype/version/maven_version.go index 44b9a613b3c..749b1012b05 100644 --- a/grype/version/maven_version.go +++ b/grype/version/maven_version.go @@ -2,6 +2,7 @@ package version import ( "fmt" + "regexp" mvnv "github.com/masahiro331/go-mvn-version" ) @@ -13,8 +14,23 @@ type mavenVersion struct { obj mvnv.Version } +// stripJavaRuntimeQualifier removes .jreNN or .jdkNN suffixes from version strings. +// These are runtime-specific qualifiers that don't affect version comparison. +// Examples: +// - "12.10.2.jre11" -> "12.10.2" +// - "12.10.2.jdk17" -> "12.10.2" +// - "12.10.2" -> "12.10.2" (no change) +func stripJavaRuntimeQualifier(version string) string { + // Match .jre or .jdk at the end of the version string + re := regexp.MustCompile(`\.(jre|jdk)\d+$`) + return re.ReplaceAllString(version, "") +} + func newMavenVersion(raw string) (mavenVersion, error) { - ver, err := mvnv.NewVersion(raw) + // Strip Java runtime qualifiers (e.g., .jre11, .jdk17) before parsing + 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..65bd75b58df 100644 --- a/grype/version/maven_version_test.go +++ b/grype/version/maven_version_test.go @@ -152,6 +152,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) { From 083f104c63aa6c45600d158ba033b0f9c40ea95c Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Fri, 14 Nov 2025 15:33:43 -0500 Subject: [PATCH 2/2] make case insensitive, add more tests, compile regex as global Signed-off-by: Alex Goodman --- grype/version/maven_version.go | 26 ++++++--- grype/version/maven_version_test.go | 81 +++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+), 6 deletions(-) diff --git a/grype/version/maven_version.go b/grype/version/maven_version.go index 749b1012b05..ff33155f5ce 100644 --- a/grype/version/maven_version.go +++ b/grype/version/maven_version.go @@ -9,6 +9,9 @@ import ( 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 @@ -16,18 +19,29 @@ type mavenVersion struct { // 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" -// - "12.10.2.jdk17" -> "12.10.2" +// - "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 { - // Match .jre or .jdk at the end of the version string - re := regexp.MustCompile(`\.(jre|jdk)\d+$`) - return re.ReplaceAllString(version, "") + return javaRuntimeQualifierPattern.ReplaceAllString(version, "") } func newMavenVersion(raw string) (mavenVersion, error) { - // Strip Java runtime qualifiers (e.g., .jre11, .jdk17) before parsing + // 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) diff --git a/grype/version/maven_version_test.go b/grype/version/maven_version_test.go index 65bd75b58df..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