From a0649ba357a57ed35055cb7661fe1009642d8d19 Mon Sep 17 00:00:00 2001 From: Ulisses Lima Date: Mon, 31 Jul 2023 12:57:13 -0300 Subject: [PATCH] BAEL-4165 - Custom DLL Load - Fixing the "java.lang.UnsatisfiedLinkError" Error (#14393) * first draft * review 1 * review 1 - test setup --- ...ung_unsatisfiedlink_JniUnsatisfiedLink.cpp | 13 +++ ...ldung_unsatisfiedlink_JniUnsatisfiedLink.h | 21 ++++ .../cpp/unsatisfiedlink/generateNativeLib.sh | 22 ++++ .../unsatisfiedlink/JniUnsatisfiedLink.java | 15 +++ .../main/resources/unsatisfiedlink/jni.policy | 3 + .../JniUnsatisfiedLinkManualTest.java | 105 ++++++++++++++++++ 6 files changed, 179 insertions(+) create mode 100644 java-native/src/main/cpp/unsatisfiedlink/com_baeldung_unsatisfiedlink_JniUnsatisfiedLink.cpp create mode 100644 java-native/src/main/cpp/unsatisfiedlink/com_baeldung_unsatisfiedlink_JniUnsatisfiedLink.h create mode 100755 java-native/src/main/cpp/unsatisfiedlink/generateNativeLib.sh create mode 100644 java-native/src/main/java/com/baeldung/unsatisfiedlink/JniUnsatisfiedLink.java create mode 100644 java-native/src/main/resources/unsatisfiedlink/jni.policy create mode 100644 java-native/src/test/java/com/baeldung/unsatisfiedlink/JniUnsatisfiedLinkManualTest.java diff --git a/java-native/src/main/cpp/unsatisfiedlink/com_baeldung_unsatisfiedlink_JniUnsatisfiedLink.cpp b/java-native/src/main/cpp/unsatisfiedlink/com_baeldung_unsatisfiedlink_JniUnsatisfiedLink.cpp new file mode 100644 index 0000000000..0349ea09ee --- /dev/null +++ b/java-native/src/main/cpp/unsatisfiedlink/com_baeldung_unsatisfiedlink_JniUnsatisfiedLink.cpp @@ -0,0 +1,13 @@ +#include "com_baeldung_unsatisfiedlink_JniUnsatisfiedLink.h" +#include + +/* + * Class: com_baeldung_unsatisfiedlink_JniUnsatisfiedLink + * Method: test + * Signature: ()Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_com_baeldung_unsatisfiedlink_JniUnsatisfiedLink_test (JNIEnv* env, jobject thisObject) { + std::string test = "Test OK"; + std::cout << test << std::endl; + return env->NewStringUTF(test.c_str()); +} diff --git a/java-native/src/main/cpp/unsatisfiedlink/com_baeldung_unsatisfiedlink_JniUnsatisfiedLink.h b/java-native/src/main/cpp/unsatisfiedlink/com_baeldung_unsatisfiedlink_JniUnsatisfiedLink.h new file mode 100644 index 0000000000..4d4169ebe9 --- /dev/null +++ b/java-native/src/main/cpp/unsatisfiedlink/com_baeldung_unsatisfiedlink_JniUnsatisfiedLink.h @@ -0,0 +1,21 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class com_baeldung_unsatisfiedlink_JniUnsatisfiedLink */ + +#ifndef _Included_com_baeldung_unsatisfiedlink_JniUnsatisfiedLink +#define _Included_com_baeldung_unsatisfiedlink_JniUnsatisfiedLink +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: com_baeldung_unsatisfiedlink_JniUnsatisfiedLink + * Method: test + * Signature: ()V + */ +JNIEXPORT jstring JNICALL Java_com_baeldung_unsatisfiedlink_JniUnsatisfiedLink_test + (JNIEnv *, jobject); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/java-native/src/main/cpp/unsatisfiedlink/generateNativeLib.sh b/java-native/src/main/cpp/unsatisfiedlink/generateNativeLib.sh new file mode 100755 index 0000000000..20249a0f76 --- /dev/null +++ b/java-native/src/main/cpp/unsatisfiedlink/generateNativeLib.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# Create the header with javac -h . ClassName.java +# Remember to set your JAVA_HOME env var +# Don't forget to set java.library.path to point to the folder where you have the lib you're loading. +MYSELF="$(readlink -f "$0")" +MYDIR="${MYSELF%/*}" + +cd "$MYDIR" +class_name=com_baeldung_unsatisfiedlink_JniUnsatisfiedLink +lib_name=test + +g++ -c -fPIC -I"${JAVA_HOME}/include" -I"${JAVA_HOME}/include/linux" ${class_name}.cpp -o ${class_name}.o +g++ -shared -fPIC -o lib${lib_name}.so ${class_name}.o -lc + +# 32-bit version +g++ -m32 -c -fPIC -I"${JAVA_HOME}/include" -I"${JAVA_HOME}/include/linux" ${class_name}.cpp -o ${class_name}32.o +g++ -m32 -shared -fPIC -o lib${lib_name}32.so ${class_name}32.o -lc + +# dummy version +touch ${class_name}-dummy.o + +ls lib* diff --git a/java-native/src/main/java/com/baeldung/unsatisfiedlink/JniUnsatisfiedLink.java b/java-native/src/main/java/com/baeldung/unsatisfiedlink/JniUnsatisfiedLink.java new file mode 100644 index 0000000000..bd65c25110 --- /dev/null +++ b/java-native/src/main/java/com/baeldung/unsatisfiedlink/JniUnsatisfiedLink.java @@ -0,0 +1,15 @@ +package com.baeldung.unsatisfiedlink; + +public class JniUnsatisfiedLink { + + public static final String LIB_NAME = "test"; + + public static void main(String[] args) { + System.loadLibrary(LIB_NAME); + new JniUnsatisfiedLink().test(); + } + + public native String test(); + + public native String nonexistentDllMethod(); +} diff --git a/java-native/src/main/resources/unsatisfiedlink/jni.policy b/java-native/src/main/resources/unsatisfiedlink/jni.policy new file mode 100644 index 0000000000..ba08a4ecc7 --- /dev/null +++ b/java-native/src/main/resources/unsatisfiedlink/jni.policy @@ -0,0 +1,3 @@ +grant { + permission java.lang.RuntimePermission "loadLibrary.test"; +}; \ No newline at end of file diff --git a/java-native/src/test/java/com/baeldung/unsatisfiedlink/JniUnsatisfiedLinkManualTest.java b/java-native/src/test/java/com/baeldung/unsatisfiedlink/JniUnsatisfiedLinkManualTest.java new file mode 100644 index 0000000000..709c018762 --- /dev/null +++ b/java-native/src/test/java/com/baeldung/unsatisfiedlink/JniUnsatisfiedLinkManualTest.java @@ -0,0 +1,105 @@ +package com.baeldung.unsatisfiedlink; + +import static com.baeldung.unsatisfiedlink.JniUnsatisfiedLink.LIB_NAME; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +public class JniUnsatisfiedLinkManualTest { + + static String osLibPrefix = ""; + static String osLibSuffix = ""; + + @BeforeAll + static void setup() { + String osName = System.getProperty("os.name") + .toLowerCase(); + + if (osName.contains("win")) { + osLibSuffix = ".dll"; + } else if (osName.contains("nix") || osName.contains("nux") || osName.contains("mac")) { + osLibPrefix = "lib"; + osLibSuffix = ".so"; + if (osName.contains("mac")) { + osLibPrefix = "lib"; + osLibSuffix = ".dylib"; + } + } else { + throw new UnsupportedOperationException("Unsupported operating system: " + osName); + } + } + + @Test + public void whenCorrectLibName_thenLibLoaded() { + assertDoesNotThrow(() -> { + System.loadLibrary(LIB_NAME); + new JniUnsatisfiedLink().test(); + }); + } + + @Test + public void whenIncorrectLibName_thenLibNotFound() { + String libName = osLibPrefix + LIB_NAME + osLibSuffix; + + Error error = assertThrows(UnsatisfiedLinkError.class, () -> System.loadLibrary(libName)); + + assertEquals(String.format("no %s in java.library.path", libName), error.getMessage()); + } + + @Test + public void whenLoadLibraryContainsPathSeparator_thenErrorThrown() { + String libName = "/" + LIB_NAME; + + Error error = assertThrows(UnsatisfiedLinkError.class, () -> System.loadLibrary(libName)); + + assertEquals(String.format("Directory separator should not appear in library name: %s", libName), error.getMessage()); + } + + @Test + public void whenLoadWithoutAbsolutePath_thenErrorThrown() { + String libName = LIB_NAME; + + Error error = assertThrows(UnsatisfiedLinkError.class, () -> System.load(libName)); + + assertEquals(String.format("Expecting an absolute path of the library: %s", libName), error.getMessage()); + } + + @Test + public void whenUnlinkedMethod_thenErrorThrown() { + System.loadLibrary(LIB_NAME); + + Error error = assertThrows(UnsatisfiedLinkError.class, () -> new JniUnsatisfiedLink().nonexistentDllMethod()); + + assertTrue(error.getMessage() + .contains("JniUnsatisfiedLink.nonexistentDllMethod")); + } + + @Test + public void whenIncompatibleArchitecture_thenErrorThrown() { + Error error = assertThrows(UnsatisfiedLinkError.class, () -> System.loadLibrary(LIB_NAME + "32")); + + assertTrue(error.getMessage() + .contains("wrong ELF class: ELFCLASS32")); + } + + @Test + public void whenCorruptedFile_thenErrorThrown() { + String libPath = System.getProperty("java.library.path"); + assertNotNull(libPath); + + String dummyLib = LIB_NAME + "-dummy"; + assertTrue(new File(libPath, osLibPrefix + dummyLib + osLibSuffix).isFile()); + + Error error = assertThrows(UnsatisfiedLinkError.class, () -> System.loadLibrary(dummyLib)); + + assertTrue(error.getMessage() + .contains("file too short")); + } +}