jenkins-infra/plugin-health-scoring

Problem running JSR-305 probe

alecharp opened this issue · 2 comments

Description of the bug

In the logs of the application, we can see

2023-10-09T11:10:09.533Z ERROR 1 --- [   scheduling-1] i.j.p.scoring.probes.ProbeEngine         : Couldn't run JSR-305 on exclusive-execution

java.io.UncheckedIOException: java.nio.charset.MalformedInputException: Input length = 1
	at java.base/java.nio.file.FileChannelLinesSpliterator.readLine(Unknown Source) ~[na:na]
	at java.base/java.nio.file.FileChannelLinesSpliterator.forEachRemaining(Unknown Source) ~[na:na]
	at java.base/java.util.stream.AbstractPipeline.copyInto(Unknown Source) ~[na:na]
	at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(Unknown Source) ~[na:na]
	at java.base/java.util.stream.AbstractPipeline.evaluate(Unknown Source) ~[na:na]
	at java.base/java.util.stream.AbstractPipeline.evaluateToArrayNode(Unknown Source) ~[na:na]
	at java.base/java.util.stream.ReferencePipeline.toArray(Unknown Source) ~[na:na]
	at java.base/java.util.stream.ReferencePipeline.toArray(Unknown Source) ~[na:na]
	at java.base/java.util.stream.ReferencePipeline.toList(Unknown Source) ~[na:na]
	at io.jenkins.pluginhealth.scoring.probes.JSR305Probe.getAllImportsInTheFile(JSR305Probe.java:76) ~[plugin-health-scoring-core-3.0.0.jar:na]
	at io.jenkins.pluginhealth.scoring.probes.JSR305Probe.containsImports(JSR305Probe.java:121) ~[plugin-health-scoring-core-3.0.0.jar:na]
	at java.base/java.util.stream.ReferencePipeline$2$1.accept(Unknown Source) ~[na:na]
	at java.base/java.util.stream.ReferencePipeline$3$1.accept(Unknown Source) ~[na:na]
	at java.base/java.util.stream.ReferencePipeline$2$1.accept(Unknown Source) ~[na:na]
	at java.base/java.util.Iterator.forEachRemaining(Unknown Source) ~[na:na]
	at java.base/java.util.Spliterators$IteratorSpliterator.forEachRemaining(Unknown Source) ~[na:na]
	at java.base/java.util.stream.AbstractPipeline.copyInto(Unknown Source) ~[na:na]
	at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(Unknown Source) ~[na:na]
	at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(Unknown Source) ~[na:na]
	at java.base/java.util.stream.AbstractPipeline.evaluate(Unknown Source) ~[na:na]
	at java.base/java.util.stream.ReferencePipeline.collect(Unknown Source) ~[na:na]
	at io.jenkins.pluginhealth.scoring.probes.JSR305Probe.doApply(JSR305Probe.java:42) ~[plugin-health-scoring-core-3.0.0.jar:na]
	at io.jenkins.pluginhealth.scoring.probes.Probe.apply(Probe.java:56) ~[plugin-health-scoring-core-3.0.0.jar:na]
	at io.jenkins.pluginhealth.scoring.probes.ProbeEngine.lambda$runOn$1(ProbeEngine.java:106) ~[app/:na]
	at java.base/java.lang.Iterable.forEach(Unknown Source) ~[na:na]
	at io.jenkins.pluginhealth.scoring.probes.ProbeEngine.runOn(ProbeEngine.java:104) ~[app/:na]
	at io.jenkins.pluginhealth.scoring.probes.ProbeEngine.lambda$run$0(ProbeEngine.java:74) ~[app/:na]
	at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(Unknown Source) ~[na:na]
	at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(Unknown Source) ~[na:na]
	at java.base/java.util.stream.AbstractPipeline.copyInto(Unknown Source) ~[na:na]
	at java.base/java.util.stream.ForEachOps$ForEachTask.compute(Unknown Source) ~[na:na]
	at java.base/java.util.concurrent.CountedCompleter.exec(Unknown Source) ~[na:na]
	at java.base/java.util.concurrent.ForkJoinTask.doExec(Unknown Source) ~[na:na]
	at java.base/java.util.concurrent.ForkJoinTask.invoke(Unknown Source) ~[na:na]
	at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateParallel(Unknown Source) ~[na:na]
	at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateParallel(Unknown Source) ~[na:na]
	at java.base/java.util.stream.AbstractPipeline.evaluate(Unknown Source) ~[na:na]
	at java.base/java.util.stream.ReferencePipeline.forEach(Unknown Source) ~[na:na]
	at java.base/java.util.stream.ReferencePipeline$Head.forEach(Unknown Source) ~[na:na]
	at io.jenkins.pluginhealth.scoring.probes.ProbeEngine.run(ProbeEngine.java:74) ~[app/:na]
	at io.jenkins.pluginhealth.scoring.schedule.ProbeEngineScheduler.run(ProbeEngineScheduler.java:47) ~[app/:na]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) ~[na:na]
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) ~[na:na]
	at java.base/java.lang.reflect.Method.invoke(Unknown Source) ~[na:na]
	at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:84) ~[spring-context-6.0.12.jar:6.0.12]
	at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54) ~[spring-context-6.0.12.jar:6.0.12]
	at org.springframework.scheduling.concurrent.ReschedulingRunnable.run(ReschedulingRunnable.java:96) ~[spring-context-6.0.12.jar:6.0.12]
	at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source) ~[na:na]
	at java.base/java.util.concurrent.FutureTask.run(Unknown Source) ~[na:na]
	at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(Unknown Source) ~[na:na]
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) ~[na:na]
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) ~[na:na]
	at java.base/java.lang.Thread.run(Unknown Source) ~[na:na]
Caused by: java.nio.charset.MalformedInputException: Input length = 1
	at java.base/java.nio.charset.CoderResult.throwException(Unknown Source) ~[na:na]
	at java.base/sun.nio.cs.StreamDecoder.implRead(Unknown Source) ~[na:na]
	at java.base/sun.nio.cs.StreamDecoder.read(Unknown Source) ~[na:na]
	at java.base/java.io.BufferedReader.fill(Unknown Source) ~[na:na]
	at java.base/java.io.BufferedReader.readLine(Unknown Source) ~[na:na]
	at java.base/java.io.BufferedReader.readLine(Unknown Source) ~[na:na]
	... 54 common frames omitted

This requires investigation and potentially a fix.

I discovered that exclusive-execution plugin has source file encoded with us-ascii and iso-8859-1.

$ find . -type f -name "*.java" -exec file -I {} \;
./src/main/java/hudson/plugins/execution/exclusive/util/DebugHelper.java: text/x-java; charset=us-ascii
./src/main/java/hudson/plugins/execution/exclusive/ExclusiveBuildWrapper.java: text/x-java; charset=iso-8859-1

The problem is that the probe is opening the files with UTF-8 charset (

try (Stream<Path> javaFiles = Files.find(scmRepository, Integer.MAX_VALUE, (path, $) -> Files.isRegularFile(path) && path.getFileName().toString().endsWith(".java"))) {
and https://github.com/openjdk/jdk/blob/731fb4eea21ab67d90970d7c6107fb0a4fbee9ec/src/java.base/share/classes/java/nio/file/Files.java#L3437-L3439).

This exception is caught in

private List<String> getAllImportsInTheFile(Path javaFile) {
try (Stream<String> importStatements = Files.lines(javaFile).filter(line -> line.startsWith("import")).map(this::getFullyQualifiedImportName)) {
return importStatements.toList();
} catch (IOException ex) {
LOGGER.error("Could not browse the {} plugin folder during probe.", key(), ex);
}
return List.of();
which means that files that we cannot read because of encoding would trick the probe into believing that they are fine, when they could have invalid imports.

The question is now, should we transform this case into a error or not?

In case of java.io.UncheckedIOException we can show some message like: The file encryption is not UTF-8.

I researched a little about about how to find a file encoding type in Java. but they say identifying encoding types can only be a wild guess.