Details
-
Bug
-
Status: Open
-
Major
-
Resolution: Unresolved
-
6.10.0
-
None
-
None
Description
I'm working on a small tool called "jarbloat" (written in Clojure but should work on any JVM language that produces jar files) [1] for inspecting JAR files. I'm using BCEL to analyze class dependencies and package names. Although I have alternative code for computing package names based on paths, sadly, I don't have any alternative for fetching class dependencies outside of BCEL.
Now, jarbloat works fine when compiled usual way (uberjar/fatjar), but when I compile it with GraalVM, BCEL code will throw NullPointerException:
java.lang.ExceptionInInitializerError at org.apache.bcel.util.SyntheticRepository.getInstance(SyntheticRepository.java:38) at org.apache.bcel.classfile.JavaClass.<init>(JavaClass.java:145) at org.apache.bcel.classfile.ClassParser.parse(ClassParser.java:179) at jarbloat.class_analyzer.BCELAnalyzer.load_class(class_analyzer.clj:64) at jarbloat.analyzer$analyze_entry_deps.invokeStatic(analyzer.clj:128) at jarbloat.analyzer$analyze_entry_deps.invoke(analyzer.clj:118) at jarbloat.analyzer$analyze_jar$fn__706.invoke(analyzer.clj:156) at clojure.core$map$fn__4785.invoke(core.clj:2646) at clojure.lang.LazySeq.sval(LazySeq.java:40) at clojure.lang.LazySeq.seq(LazySeq.java:49) at clojure.lang.RT.seq(RT.java:521) at clojure.core$seq__4357.invokeStatic(core.clj:137) at clojure.core$filter$fn__4812.invoke(core.clj:2700) at clojure.lang.LazySeq.sval(LazySeq.java:40) at clojure.lang.LazySeq.seq(LazySeq.java:56) at clojure.lang.RT.seq(RT.java:521) at clojure.core$seq__4357.invokeStatic(core.clj:137) at jarbloat.printer$do_print_dot.invokeStatic(printer.clj:13) at jarbloat.analyzer$analyze_jar.invokeStatic(analyzer.clj:160) at jarbloat.core$handle_args.invokeStatic(core.clj:91) at jarbloat.core$_main.invokeStatic(core.clj:105) at jarbloat.core$_main.doInvoke(core.clj:99) at clojure.lang.RestFn.applyTo(RestFn.java:137) at jarbloat.core.main(Unknown Source) Caused by: java.lang.NullPointerException at java.base@17.0.12/java.util.Objects.requireNonNull(Objects.java:208) at java.base@17.0.12/sun.nio.fs.UnixFileSystem.getPath(UnixFileSystem.java:263) at java.base@17.0.12/java.nio.file.Path.of(Path.java:147) at java.base@17.0.12/java.nio.file.Paths.get(Paths.java:69) at org.apache.bcel.util.ClassPath.getClassPath(ClassPath.java:481) at org.apache.bcel.util.ClassPath.<clinit>(ClassPath.java:443)
This happens when I set GraalVM's native-image to use --initialize-at-run-time=org.apache.bcel.util.ClassPath argument. Without it, GraalVM will complain with the following error:
Error: Detected a ZipFile object in the image heap. A ZipFile object contains pointers to unmanaged C memory and file descriptors, and these resources are no longer available at image runtime. To see how this object got instantiated use --trace-object-instantiation=java.util.zip.ZipFile. The object was probably created by a class initializer and is reachable from a static field. You can request class initialization at image runtime by using the option --initialize-at-run-time=<class-name>. Or you can write your own initialization methods and call them explicitly from your main entry point. Trace: Object was reached by reading field org.apache.bcel.util.ClassPath$AbstractZip.zipFile of constant org.apache.bcel.util.ClassPath$Module@3956260a: /opt/graalvm-jdk-17.0.12+8.1/jmods/java.se.jmod indexing into array java.lang.Object[]@761be02f: [Ljava.lang.Object;@761be02f reading field java.util.ArrayList.elementData of constant java.util.ArrayList@3eb61e52: [[/modules/jdk.internal.vm.compiler, /modules/com.oracle.graal.graal_enterprise,... reading field org.apache.bcel.util.ClassPath.paths of constant org.apache.bcel.util.ClassPath@2f5aa92f: /opt/graalvm-jdk-17.0.12+8.1/lib/modules:/opt/graalvm-jdk-17.0.12+8.1/jmods/java... scanning root org.apache.bcel.util.ClassPath@2f5aa92f: /opt/graalvm-jdk-17.0.12+8.1/lib/modules:/opt/graalvm-jdk-17.0.12+8.1/jmods/java... embedded in org.apache.bcel.util.SyntheticRepository.getInstance(SyntheticRepository.java:38) parsing method org.apache.bcel.util.SyntheticRepository.getInstance(SyntheticRepository.java:38) reachable via the parsing context at static root method.(Unknown Source)
After some digging through the code, I found that BCEL has some variables initialized when the class is initialized (e.g. [2], [3], and [4]). I'm getting the impression that the problem starts in ClassPath.getClassPath() [5]. Some values, like SystemProperties.getJavaHome() [6], will be null on GraalVM.
I'm not very familiar with BCEL internals, but after some rough checking things, maybe making this [2] repository initialization lazy could solve and allowing repository to be null, could solve the issue. Any thoughts?
Tested with these GraalVM native-image versions:
$ /opt/graalvm/bin/native-image --version GraalVM 22.0.0.2 Java 17 CE (Java Version 17.0.2+8-jvmci-22.0-b05) $ /opt/graalvm-jdk-17.0.12+8.1/bin/native-image --version native-image 17.0.12 2024-07-16 GraalVM Runtime Environment Oracle GraalVM 17.0.12+8.1 (build 17.0.12+8-LTS-jvmci-23.0-b41) Substrate VM Oracle GraalVM 17.0.12+8.1 (build 17.0.12+8-LTS, serial gc, compressed references)
[1] https://github.com/sanel/jarbloat
[2] https://github.com/apache/commons-bcel/blob/master/src/main/java/org/apache/bcel/classfile/JavaClass.java#L145
[3] https://github.com/apache/commons-bcel/blob/master/src/main/java/org/apache/bcel/util/SyntheticRepository.java#L38
[4] https://github.com/apache/commons-bcel/blob/master/src/main/java/org/apache/bcel/util/ClassPath.java#L443
[5] https://github.com/apache/commons-bcel/blob/master/src/main/java/org/apache/bcel/util/ClassPath.java#L470
[6] https://github.com/apache/commons-bcel/blob/master/src/main/java/org/apache/bcel/util/ClassPath.java#L481