bytedeco / javacpp

The missing bridge between Java and native C++
Other
4.43k stars 576 forks source link

java.lang.NullPointerException: Cannot invoke "java.util.List.iterator()" because "allInherited" is null #761

Closed opendiff closed 1 month ago

opendiff commented 1 month ago

I'm seeing a bunch of errors when trying to use javacpp.jar directly without a build system on a hello world example. Here's a link to the full example with notes:

In Parser.java when using javacpp.jar directly I've seen crashes due to .size() being zero and then .get(-1) fails. There's also a bug where getInheritedClasses can return null and then for (Class c : allInherited) crashes.

String target = clsTargets.get(clsTargets.size() - 1);
String global = clsGlobals.get(clsGlobals.size() - 1);
List<Class> allInherited = allProperties.getInheritedClasses();

infoMap = new InfoMap();
for (Class c : allInherited) {

https://github.com/bytedeco/javacpp/blob/master/src/main/java/org/bytedeco/javacpp/tools/Parser.java#L4637-L4642

I'd like for someone to show me on the hello world example the proper way to use javacpp.jar to generate the wrapper class and JNI bindings.

java -cp javacpp.jar:build org.bytedeco.javacpp.tools.Builder -Dtarget=org.example.hello -Dglobal=org.example.hello  -Dplatform.includepath=src/main/cpp -Dplatform.linkpath=build/macosx-arm64 org.example.presets.HelloPreset -d src/main/java

Exception in thread "main" java.lang.NullPointerException: Cannot invoke "java.util.List.iterator()" because "allInherited" is null
    at org.bytedeco.javacpp.tools.Parser.parse(Parser.java:4631)
    at org.bytedeco.javacpp.tools.Builder.parse(Builder.java:95)
    at org.bytedeco.javacpp.tools.Builder.build(Builder.java:1095)
    at org.bytedeco.javacpp.tools.Builder.main(Builder.java:1450)

The same javacpp example works fine when using maven and gradle so the bugs seem specific to using javacpp.jar directly.

Here are the steps from the above linked manual example:

Step-by-Step Guide

1. Build the Shared Library

Run the build.sh script to create the shared library (libmyhello.dylib):

#!/bin/bash
set -e

echo "removing build/"
rm -rf "build/"

# hardcode mac for now
PLATFORM="macosx-arm64"
OUTPUT_DIR="build/$PLATFORM"

mkdir -p "$OUTPUT_DIR"

g++ -dynamiclib -o "$OUTPUT_DIR/libmyhello.dylib" ./src/main/cpp/myhello.cpp

2. Compile HelloPreset.java

javac -cp ./javacpp.jar -d build/ src/main/java/org/example/presets/HelloPreset.java

3. Try to generate hello.java and JNI Code

Generate hello.java

java -cp javacpp.jar:build org.bytedeco.javacpp.tools.Builder -Dtarget=org.example.hello -Dglobal=org.example.hello  -Dplatform.includepath=src/main/cpp -Dplatform.linkpath=build/macosx-arm64 org.example.presets.HelloPreset -d src/main/java

Exception in thread "main" java.lang.NullPointerException: Cannot invoke "java.util.List.iterator()" because "allInherited" is null
    at org.bytedeco.javacpp.tools.Parser.parse(Parser.java:4631)
    at org.bytedeco.javacpp.tools.Builder.parse(Builder.java:95)
    at org.bytedeco.javacpp.tools.Builder.build(Builder.java:1095)
    at org.bytedeco.javacpp.tools.Builder.main(Builder.java:1450)

hello.java fails to generate due to a null pointer exception in the parser.

Generate JNI Code

java -cp javacpp.jar:build org.bytedeco.javacpp.tools.Builder  -Dplatform.includepath=src/main/cpp -Dplatform.linkpath=build/macosx-arm64 org.example.presets.HelloPreset -d build/macosx-arm64

The libjniHelloPreset.dylib binary doesn't contain the printHelloWorld2 method

nm -gU build/macosx-arm64/libjniHelloPreset.dylib

libmyhello.dylib does contain the printHelloWorld2 method.

nm -gU build/macosx-arm64/libmyhello.dylib

0000000000003108 T __Z16printHelloWorld2v

4. Compile Everything

javac -cp javacpp.jar:src/main/java -d build/ src/main/java/org/example/*.java

5. Run the Application

java -Djava.library.path=./build/macosx-arm64/ -cp build:javacpp-platform-1.5.10-bin/javacpp.jar org.example.Main

The app runs only if hello.java and libjniHelloPreset.dylib are replaced by versions generated by maven or gradle.

saudet commented 1 month ago

There's a working example using the command line only here: https://github.com/bytedeco/javacpp/wiki/Mapping-Recipes#introduction

opendiff commented 1 month ago

Thanks. The mapping recipes example uses this:

@Properties(
    value = @Platform(
        includepath = {"/path/to/include/"},
        preloadpath = {"/path/to/deps/"},
        linkpath = {"/path/to/lib/"},
        include = {"NativeLibrary.h"},
        preload = {"DependentLib"},
        link = {"NativeLibrary"}
    ),
    target = "NativeLibrary"
)
public class NativeLibraryConfig implements InfoMapper {
    public void map(InfoMap infoMap) {
    }
}

when I follow the format with javacpp.jar on a simple hello world example file the jar either crashes or fails to generate the correct java and JNI code. Even when the JNI lib is generated it doesn't contain the helloworld method. There's no null checking or range checking in the parser where the crashes happen. It seems like the code is broken. The same exact code works fine with gradle/maven + javacpp.

@Properties(target="org.example.hello", value={
  @Platform(include = "myhello.h", link = "myhello")
})
public class HelloPreset implements InfoMapper {
    static {
        Loader.load();
    }

    public void map(InfoMap infoMap) {
    }
}

Are you able to reproduce the crashes and broken generation with the example I shared?

opendiff commented 1 month ago

This fixed the issue. I was trying to generate the JNI from the preset class which was causing the parser crashes. Generating from the hello.java instead works.

// Generate hello.java java -cp javacpp.jar:build org.bytedeco.javacpp.tools.Builder -Dplatform.includepath=src/main/cpp org.example.presets.HelloPreset -d src/main/java

// Generate JNI from hello.java java -cp javacpp.jar:build:src/main/java org.bytedeco.javacpp.tools.Builder -Dplatform.includepath=src/main/cpp -Dplatform.linkpath=build/macosx-arm64 org.example.hello -d build/macosx-arm64