--- /dev/null 2020-03-09 21:45:43.000000000 -0700 +++ new/src/java.base/share/classes/jdk/internal/loader/NativeLibraries.java 2020-03-09 21:45:43.000000000 -0700 @@ -0,0 +1,374 @@ +/* + * Copyright (c) 2020, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package jdk.internal.loader; + +import jdk.internal.ref.CleanerFactory; +import jdk.internal.util.StaticProperty; + +import java.io.File; +import java.io.IOException; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Native libraries are loaded via {@link System#loadLibrary(String)}, + * {@link System#load(String)}, {@link Runtime#loadLibrary(String)} and + * {@link Runtime#load(String)}. They are caller-sensitive. + * + * Each class loader has a NativeLibraries instance to register all of its + * loaded native libraries. System::loadLibrary (and other APIs) only + * allows a native library to be loaded by one class loader, i.e. one + * NativeLibraries instance. Any attempt to load a native library that + * has already been loaded by a class loader with another class loader + * will fail. + */ +public final class NativeLibraries { + + private final Map libraries = new ConcurrentHashMap<>(); + private final ClassLoader loader; + private final Class caller; // may be null. If not null, this is used as + // fromClass as a fast-path. See loadLibrary(String name). + private final boolean searchJavaLibraryPath; + + public NativeLibraries(ClassLoader loader) { + // for null loader, default the caller to this class and + // do not search java.library.path + this(loader, loader != null ? null : NativeLibraries.class, loader != null ? true : false); + } + public NativeLibraries(ClassLoader loader, Class caller, boolean searchJavaLibraryPath) { + if (caller != null && caller.getClassLoader() != loader) { + throw new IllegalArgumentException(caller.getName() + " must be defined by " + loader); + } + this.loader = loader; + this.caller = caller; + this.searchJavaLibraryPath = searchJavaLibraryPath; + } + + /* + * Look up the address of the given symbol name from the native libraries + * loaded in this NativeLibraries instance. + */ + public long lookup(String name) { + if (libraries.isEmpty()) + return 0; + + // the native libraries map may be updated in another thread + // when a native library is being loaded. No symbol will be + // searched from it yet. + for (NativeLibrary lib : libraries.values()) { + long entry = lib.findEntry(name); + if (entry != 0) return entry; + } + return 0; + } + + /* + * Load a native library from the given file. Returns null if file does not exist. + * + * @param fromClass the caller class calling System::loadLibrary + * @param file the path of the native library + * @throws UnsatisfiedLinkError if any error in loading the native library + */ + public NativeLibrary loadLibrary(Class fromClass, File file) { + // Check to see if we're attempting to access a static library + String name = findBuiltinLib(file.getName()); + boolean isBuiltin = (name != null); + if (!isBuiltin) { + name = AccessController.doPrivileged(new PrivilegedAction<>() { + public String run() { + try { + return file.exists() ? file.getCanonicalPath() : null; + } catch (IOException e) { + return null; + } + } + }); + if (name == null) { + return null; + } + } + return loadLibrary(fromClass, name, isBuiltin); + } + + /** + * Returns a NativeLibrary of the given name. + * + * @param fromClass the caller class calling System::loadLibrary + * @param name library name + * @param isBuiltin built-in library + * @throws UnsatisfiedLinkError if the native library has already been loaded + * and registered in another NativeLibraries + */ + private NativeLibrary loadLibrary(Class fromClass, String name, boolean isBuiltin) { + ClassLoader loader = (fromClass == null) ? null : fromClass.getClassLoader(); + if (this.loader != loader) { + throw new InternalError(fromClass.getName() + " not allowed to load library"); + } + + synchronized (loadedLibraryNames) { + // find if this library has already been loaded and registered in this NativeLibraries + NativeLibrary cached = libraries.get(name); + if (cached != null) { + return cached; + } + + // cannot be loaded by other class loaders + if (loadedLibraryNames.contains(name)) { + throw new UnsatisfiedLinkError("Native Library " + name + + " already loaded in another classloader"); + } + + /* + * When a library is being loaded, JNI_OnLoad function can cause + * another loadLibrary invocation that should succeed. + * + * We use a static stack to hold the list of libraries we are + * loading because this can happen only when called by the + * same thread because this block is synchronous. + * + * If there is a pending load operation for the library, we + * immediately return success; otherwise, we raise + * UnsatisfiedLinkError. + */ + for (NativeLibraryImpl lib : nativeLibraryContext) { + if (name.equals(lib.name())) { + if (loader == lib.fromClass.getClassLoader()) { + return lib; + } else { + throw new UnsatisfiedLinkError("Native Library " + + name + " is being loaded in another classloader"); + } + } + } + + NativeLibraryImpl lib = new NativeLibraryImpl(fromClass, name, isBuiltin); + // load the native library + nativeLibraryContext.push(lib); + try { + if (!lib.open()) return null; + } finally { + nativeLibraryContext.pop(); + } + // register the loaded native library + loadedLibraryNames.add(name); + libraries.put(name, lib); + return lib; + } + } + + /** + * Loads a native library from the system library path and java library path. + * + * @param name library name + * + * @throws UnsatisfiedLinkError if the native library has already been loaded + * and registered in another NativeLibraries + */ + public NativeLibrary loadLibrary(String name) { + assert caller != null; + return loadLibrary(caller, name); + } + + /** + * Loads a native library from the system library path and java library path. + * + * @param name library name + * @param fromClass the caller class calling System::loadLibrary + * + * @throws UnsatisfiedLinkError if the native library has already been loaded + * and registered in another NativeLibraries + */ + public NativeLibrary loadLibrary(Class fromClass, String name) { + NativeLibrary lib = findFromPaths(LibraryPaths.SYS_PATHS, fromClass, name); + if (lib == null && searchJavaLibraryPath) { + lib = findFromPaths(LibraryPaths.USER_PATHS, fromClass, name); + } + return lib; + } + + private NativeLibrary findFromPaths(String[] paths, Class fromClass, String name) { + for (String path : paths) { + File libfile = new File(path, System.mapLibraryName(name)); + NativeLibrary nl = loadLibrary(fromClass, libfile); + if (nl != null) { + return nl; + } + libfile = ClassLoaderHelper.mapAlternativeName(libfile); + if (libfile != null) { + nl = loadLibrary(fromClass, libfile); + if (nl != null) { + return nl; + } + } + } + return null; + } + + /** + * NativeLibraryImpl denotes a loaded native library instance. + * Each NativeLibraries contains a map of loaded native libraries in the + * private field {@code libraries}. + * + * Every native library requires a particular version of JNI. This is + * denoted by the private {@code jniVersion} field. This field is set by + * the VM when it loads the library, and used by the VM to pass the correct + * version of JNI to the native methods. + */ + static class NativeLibraryImpl implements NativeLibrary { + // the class from which the library is loaded, also indicates + // the loader this native library belongs. + final Class fromClass; + // the canonicalized name of the native library. + // or static library name + final String name; + // Indicates if the native library is linked into the VM + final boolean isBuiltin; + + // opaque handle to native library, used in native code. + long handle; + // the version of JNI environment the native library requires. + int jniVersion; + + NativeLibraryImpl(Class fromClass, String name, boolean isBuiltin) { + this.fromClass = fromClass; + this.name = name; + this.isBuiltin = isBuiltin; + } + + @Override + public String name() { + return name; + } + + @Override + public long findEntry(String name) { + return findEntry0(this, name); + } + + /* + * Loads the native library and registers for cleanup when its + * associated class loader is unloaded + */ + boolean open() { + if (handle != 0) { + throw new InternalError("Native library " + name + " has been loaded"); + } + + if (!load(this, name, isBuiltin)) return false; + + // register the class loader for cleanup when unloaded + // builtin class loaders are never unloaded + ClassLoader loader = fromClass != null ? fromClass.getClassLoader() : null; + if (loader != null && + loader != ClassLoaders.platformClassLoader() && + loader != ClassLoaders.appClassLoader()) { + CleanerFactory.cleaner().register(loader, new Unloader(name, handle, isBuiltin)); + } + return true; + } + } + + /* + * The run() method will be invoked when this class loader becomes + * phantom reachable to unload the native library. + */ + static class Unloader implements Runnable { + // This represents the context when a native library is unloaded + // and getFromClass() will return null, + static final NativeLibraryImpl UNLOADER = + new NativeLibraryImpl(null, "dummy", false); + + final String name; + final long handle; + final boolean isBuiltin; + + Unloader(String name, long handle, boolean isBuiltin) { + if (handle == 0) { + throw new IllegalArgumentException( + "Invalid handle for native library " + name); + } + + this.name = name; + this.handle = handle; + this.isBuiltin = isBuiltin; + } + + @Override + public void run() { + synchronized (NativeLibraries.loadedLibraryNames) { + /* remove the native library name */ + NativeLibraries.loadedLibraryNames.remove(name); + NativeLibraries.nativeLibraryContext.push(UNLOADER); + try { + unload(name, isBuiltin, handle); + } finally { + NativeLibraries.nativeLibraryContext.pop(); + } + } + } + } + + /* + * Holds system and user library paths derived from the + * {@code java.library.path} and {@code sun.boot.library.path} system + * properties. The system properties are eagerly read at bootstrap, then + * lazily parsed on first use to avoid initialization ordering issues. + */ + static class LibraryPaths { + // The paths searched for libraries + static final String[] SYS_PATHS = ClassLoaderHelper.parsePath(StaticProperty.sunBootLibraryPath()); + static final String[] USER_PATHS = ClassLoaderHelper.parsePath(StaticProperty.javaLibraryPath()); + } + + // All native libraries we've loaded. + // This also serves as the lock to obtain nativeLibraries + // and write to nativeLibraryContext. + private static final Set loadedLibraryNames = new HashSet<>(); + + // native libraries being loaded + private static Deque nativeLibraryContext = new ArrayDeque<>(8); + + // Invoked in the VM to determine the context class in JNI_OnLoad + // and JNI_OnUnload + private static Class getFromClass() { + if (nativeLibraryContext.isEmpty()) { // only default library + return Object.class; + } + return nativeLibraryContext.peek().fromClass; + } + + // JNI FindClass expects the caller class if invoked from JNI_OnLoad + // and JNI_OnUnload is NativeLibrary class + private static native boolean load(NativeLibraryImpl impl, String name, boolean isBuiltin); + private static native void unload(String name, boolean isBuiltin, long handle); + private static native String findBuiltinLib(String name); + private static native long findEntry0(NativeLibraryImpl lib, String name); +}