Sanel Zukan created BCEL-373:
--------------------------------

             Summary: Add support for GraalVM
                 Key: BCEL-373
                 URL: https://issues.apache.org/jira/browse/BCEL-373
             Project: Commons BCEL
          Issue Type: Bug
          Components: Main
    Affects Versions: 6.10.0
            Reporter: Sanel Zukan


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 [email protected]/java.util.Objects.requireNonNull(Objects.java:208)
        at 
[email protected]/sun.nio.fs.UnixFileSystem.getPath(UnixFileSystem.java:263)
        at [email protected]/java.nio.file.Path.of(Path.java:147)
        at [email protected]/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




--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to