Skip to content

Commit 5462bda

Browse files
authored
fix(javareach): maven validation and correct manifest class name parsing (#1708)
Fixes a bunch of Java readability issues: - remove `com/` from stdlib prefixes - exit program with errors if running against a non maven JAR - handle wrapped class names in the manifest file - replace some non-fatel error messages with debug messages to reduce noise
1 parent 2c0af94 commit 5462bda

File tree

3 files changed

+42
-7
lines changed

3 files changed

+42
-7
lines changed

experimental/javareach/cmd/reachable/main.go

+24-5
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package main
33
import (
44
"archive/zip"
55
"bufio"
6+
"errors"
67
"flag"
78
"fmt"
89
"io"
@@ -20,6 +21,16 @@ import (
2021
"github.com/google/osv-scanner/experimental/javareach"
2122
)
2223

24+
const MetaDirPath = "META-INF"
25+
26+
var (
27+
ManifestFilePath = filepath.Join(MetaDirPath, "MANIFEST.MF")
28+
MavenDepDirPath = filepath.Join(MetaDirPath, "maven")
29+
ServiceDirPath = filepath.Join(MetaDirPath, "services")
30+
31+
ErrMavenDependencyNotFound = errors.New(MavenDepDirPath + " directory not found")
32+
)
33+
2334
// Usage:
2435
//
2536
// go run ./cmd/reachable path/to/file.jar
@@ -99,6 +110,14 @@ func enumerateReachabilityForJar(jarPath string) error {
99110
return err
100111
}
101112

113+
// Reachability analysis is limited to Maven-built JARs for now.
114+
// Check for the existence of the Maven metadata directory.
115+
_, err = os.Stat(filepath.Join(tmpDir, MavenDepDirPath))
116+
if err != nil {
117+
slog.Error("reachability analysis is only supported for JARs built with Maven.")
118+
return ErrMavenDependencyNotFound
119+
}
120+
102121
// Build .class -> Maven group ID:artifact ID mappings.
103122
// TODO: Handle BOOT-INF and loading .jar dependencies from there.
104123
classFinder, err := javareach.NewDefaultPackageFinder(allDeps, tmpDir)
@@ -107,7 +126,7 @@ func enumerateReachabilityForJar(jarPath string) error {
107126
}
108127

109128
// Extract the main entrypoint.
110-
manifest, err := os.Open(filepath.Join(tmpDir, "META-INF/MANIFEST.MF"))
129+
manifest, err := os.Open(filepath.Join(tmpDir, ManifestFilePath))
111130
if err != nil {
112131
return err
113132
}
@@ -129,7 +148,7 @@ func enumerateReachabilityForJar(jarPath string) error {
129148

130149
// Look inside META-INF/services, which is used by
131150
// https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html
132-
servicesDir := filepath.Join(tmpDir, "META-INF/services")
151+
servicesDir := filepath.Join(tmpDir, ServiceDirPath)
133152
var optionalRootClasses []string
134153
if _, err := os.Stat(servicesDir); err == nil {
135154
var entries []string
@@ -194,7 +213,7 @@ func enumerateReachabilityForJar(jarPath string) error {
194213
for _, class := range result.Classes {
195214
deps, err := classFinder.Find(class)
196215
if err != nil {
197-
slog.Error("Failed to find dep mapping", "class", class, "error", err)
216+
slog.Debug("Failed to find dep mapping", "class", class, "error", err)
198217
continue
199218
}
200219

@@ -211,7 +230,7 @@ func enumerateReachabilityForJar(jarPath string) error {
211230
slog.Info("Found use of dynamic code loading", "class", class)
212231
deps, err := classFinder.Find(class)
213232
if err != nil {
214-
slog.Error("Failed to find dep mapping", "class", class, "error", err)
233+
slog.Debug("Failed to find dep mapping", "class", class, "error", err)
215234
continue
216235
}
217236
for _, dep := range deps {
@@ -222,7 +241,7 @@ func enumerateReachabilityForJar(jarPath string) error {
222241
slog.Info("Found use of dependency injection", "class", class)
223242
deps, err := classFinder.Find(class)
224243
if err != nil {
225-
slog.Error("Failed to find dep mapping", "class", class, "error", err)
244+
slog.Debug("Failed to find dep mapping", "class", class, "error", err)
226245
continue
227246
}
228247
for _, dep := range deps {

experimental/javareach/jar.go

+18-1
Original file line numberDiff line numberDiff line change
@@ -314,11 +314,28 @@ func GetMainClasses(manifest io.Reader) ([]string, error) {
314314
scanner := bufio.NewScanner(manifest)
315315

316316
var classes []string
317+
var lines []string
318+
319+
// Read all lines into memory for easier processing.
317320
for scanner.Scan() {
318-
line := strings.TrimSpace(scanner.Text())
321+
lines = append(lines, scanner.Text())
322+
}
323+
324+
for i := 0; i < len(lines); i++ {
325+
line := strings.TrimSpace(lines[i])
319326
for _, marker := range markers {
320327
if strings.HasPrefix(line, marker) {
321328
class := strings.TrimSpace(strings.TrimPrefix(line, marker))
329+
// Handle wrapped lines. Class names exceeding line length limits
330+
// may be split across multiple lines, starting with a space.
331+
for index := i + 1; index < len(lines); index++ {
332+
nextLine := lines[index]
333+
if strings.HasPrefix(nextLine, " ") {
334+
class += strings.TrimSpace(nextLine)
335+
} else {
336+
break
337+
}
338+
}
322339
classes = append(classes, strings.ReplaceAll(class, ".", "/"))
323340
}
324341
}

experimental/javareach/javaclass.go

-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ var (
2424
}
2525

2626
StandardLibraryPrefixes = []string{
27-
"com/",
2827
"java/",
2928
"javax/",
3029
"jdk/",

0 commit comments

Comments
 (0)