| /* | |
| * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| */ | |
| import org.testng.annotations.Test; | |
| import java.io.IOException; | |
| import java.lang.foreign.*; | |
| import java.lang.foreign.Arena; | |
| import java.lang.invoke.MethodHandle; | |
| import java.net.URI; | |
| import java.net.URISyntaxException; | |
| import java.nio.file.FileSystem; | |
| import java.nio.file.FileSystems; | |
| import java.nio.file.Path; | |
| import java.util.HashMap; | |
| import java.util.concurrent.ExecutorService; | |
| import java.util.concurrent.Executors; | |
| import java.util.concurrent.TimeUnit; | |
| import static org.testng.Assert.*; | |
| /* | |
| * @test id=specialized | |
| * @run testng/othervm/native | |
| * -Djdk.internal.foreign.DowncallLinker.USE_SPEC=true | |
| * --enable-native-access=ALL-UNNAMED | |
| * LibraryLookupTest | |
| */ | |
| /* | |
| * @test id=interpreted | |
| * @run testng/othervm/native | |
| * -Djdk.internal.foreign.DowncallLinker.USE_SPEC=false | |
| * --enable-native-access=ALL-UNNAMED | |
| * LibraryLookupTest | |
| */ | |
| public class LibraryLookupTest { | |
| static final Path JAVA_LIBRARY_PATH = Path.of(System.getProperty("java.library.path")); | |
| static final MethodHandle INC = Linker.nativeLinker().downcallHandle(FunctionDescriptor.ofVoid()); | |
| static final Path LIB_PATH = JAVA_LIBRARY_PATH.resolve(System.mapLibraryName("LibraryLookup")); | |
| @Test | |
| void testLoadLibraryConfined() { | |
| try (Arena arena0 = Arena.ofConfined()) { | |
| callFunc(loadLibrary(arena0)); | |
| try (Arena arena1 = Arena.ofConfined()) { | |
| callFunc(loadLibrary(arena1)); | |
| try (Arena arena2 = Arena.ofConfined()) { | |
| callFunc(loadLibrary(arena2)); | |
| } | |
| } | |
| } | |
| } | |
| @Test(expectedExceptions = IllegalStateException.class) | |
| void testLoadLibraryConfinedClosed() { | |
| MemorySegment addr; | |
| try (Arena arena = Arena.ofConfined()) { | |
| addr = loadLibrary(arena); | |
| } | |
| callFunc(addr); | |
| } | |
| @Test(expectedExceptions = IllegalArgumentException.class) | |
| void testLoadLibraryBadName() { | |
| try (Arena arena = Arena.ofConfined()) { | |
| SymbolLookup.libraryLookup(LIB_PATH.toString() + "\u0000", arena); | |
| } | |
| } | |
| @Test | |
| void testLoadLibraryBadLookupName() { | |
| try (Arena arena = Arena.ofConfined()) { | |
| SymbolLookup lookup = SymbolLookup.libraryLookup(LIB_PATH, arena); | |
| assertTrue(lookup.find("inc\u0000foobar").isEmpty()); | |
| } | |
| } | |
| @Test | |
| void testLoadLibraryNonDefaultFileSystem() throws URISyntaxException, IOException { | |
| try (FileSystem customFs = fsFromJarOfClass(org.testng.annotations.Test.class)) { | |
| try (Arena arena = Arena.ofConfined()) { | |
| Path p = customFs.getPath("."); | |
| try { | |
| SymbolLookup.libraryLookup(p, arena); | |
| fail("Did not throw IAE"); | |
| } catch (IllegalArgumentException iae) { | |
| assertTrue(iae.getMessage().contains("not in default file system")); | |
| } | |
| } | |
| } | |
| } | |
| private static FileSystem fsFromJarOfClass(Class<?> clazz) throws URISyntaxException, IOException { | |
| String name = clazz.getName(); | |
| final int lastDot = name.lastIndexOf('.'); | |
| if (lastDot != -1) { | |
| name = name.substring(lastDot + 1); | |
| } | |
| URI uri = clazz.getResource(name + ".class").toURI(); | |
| if (uri.getScheme().equals("jar")) { | |
| final String[] parts = uri.toString().split("!"); | |
| if (parts.length == 2) { | |
| return FileSystems.newFileSystem(URI.create(parts[0]), new HashMap<>()); | |
| } | |
| } | |
| throw new AssertionError("Unable to create file system from " + clazz); | |
| } | |
| private static MemorySegment loadLibrary(Arena session) { | |
| SymbolLookup lib = SymbolLookup.libraryLookup(LIB_PATH, session); | |
| MemorySegment addr = lib.find("inc").get(); | |
| assertEquals(addr.scope(), session.scope()); | |
| return addr; | |
| } | |
| private static void callFunc(MemorySegment addr) { | |
| try { | |
| INC.invokeExact(addr); | |
| } catch (IllegalStateException ex) { | |
| throw ex; | |
| } catch (Throwable ex) { | |
| throw new AssertionError(ex); | |
| } | |
| } | |
| static final int ITERATIONS = 100; | |
| static final int MAX_EXECUTOR_WAIT_SECONDS = 20; | |
| static final int NUM_ACCESSORS = Math.min(10, Runtime.getRuntime().availableProcessors()); | |
| @Test(expectedExceptions = IllegalArgumentException.class) | |
| void testBadLibraryLookupName() { | |
| SymbolLookup.libraryLookup("nonExistent", Arena.global()); | |
| } | |
| @Test(expectedExceptions = IllegalArgumentException.class) | |
| void testBadLibraryLookupPath() { | |
| SymbolLookup.libraryLookup(Path.of("nonExistent"), Arena.global()); | |
| } | |
| @Test | |
| void testLoadLibraryShared() throws Throwable { | |
| ExecutorService accessExecutor = Executors.newCachedThreadPool(); | |
| for (int i = 0; i < NUM_ACCESSORS ; i++) { | |
| accessExecutor.execute(new LibraryLoadAndAccess()); | |
| } | |
| accessExecutor.shutdown(); | |
| assertTrue(accessExecutor.awaitTermination(MAX_EXECUTOR_WAIT_SECONDS, TimeUnit.SECONDS)); | |
| } | |
| static class LibraryLoadAndAccess implements Runnable { | |
| @Override | |
| public void run() { | |
| for (int i = 0 ; i < ITERATIONS ; i++) { | |
| try (Arena arena = Arena.ofConfined()) { | |
| callFunc(loadLibrary(arena)); | |
| } | |
| } | |
| } | |
| } | |
| @Test | |
| void testLoadLibrarySharedClosed() throws Throwable { | |
| Arena arena = Arena.ofShared(); | |
| MemorySegment addr = loadLibrary(arena); | |
| ExecutorService accessExecutor = Executors.newCachedThreadPool(); | |
| for (int i = 0; i < NUM_ACCESSORS ; i++) { | |
| accessExecutor.execute(new LibraryAccess(addr)); | |
| } | |
| while (true) { | |
| try { | |
| arena.close(); | |
| break; | |
| } catch (IllegalStateException ex) { | |
| // wait for addressable parameter to be released | |
| Thread.onSpinWait(); | |
| } | |
| } | |
| accessExecutor.shutdown(); | |
| assertTrue(accessExecutor.awaitTermination(MAX_EXECUTOR_WAIT_SECONDS, TimeUnit.SECONDS)); | |
| } | |
| static class LibraryAccess implements Runnable { | |
| final MemorySegment addr; | |
| LibraryAccess(MemorySegment addr) { | |
| this.addr = addr; | |
| } | |
| @Override | |
| public void run() { | |
| for (int i = 0 ; i < ITERATIONS ; i++) { | |
| try { | |
| callFunc(addr); | |
| } catch (IllegalStateException ex) { | |
| // library closed | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| } |