1 /*
  2  * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.  Oracle designates this
  8  * particular file as subject to the "Classpath" exception as provided
  9  * by Oracle in the LICENSE file that accompanied this code.
 10  *
 11  * This code is distributed in the hope that it will be useful, but WITHOUT
 12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 14  * version 2 for more details (a copy is included in the LICENSE file that
 15  * accompanied this code).
 16  *
 17  * You should have received a copy of the GNU General Public License version
 18  * 2 along with this work; if not, write to the Free Software Foundation,
 19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 20  *
 21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 22  * or visit www.oracle.com if you need additional information or have any
 23  * questions.
 24  */
 25 package jdk.internal.loader;
 26 
 27 import jdk.internal.misc.VM;
 28 import jdk.internal.ref.CleanerFactory;
 29 import jdk.internal.util.StaticProperty;
 30 
 31 import java.io.File;
 32 import java.io.IOException;
 33 import java.security.AccessController;
 34 import java.security.PrivilegedAction;
 35 import java.util.ArrayDeque;
 36 import java.util.Deque;
 37 import java.util.function.BiFunction;
 38 import java.util.function.Function;
 39 import java.util.Map;
 40 import java.util.Set;
 41 import java.util.concurrent.ConcurrentHashMap;
 42 import java.util.concurrent.locks.ReentrantLock;
 43 
 44 /**
 45  * Native libraries are loaded via {@link System#loadLibrary(String)},
 46  * {@link System#load(String)}, {@link Runtime#loadLibrary(String)} and
 47  * {@link Runtime#load(String)}.  They are caller-sensitive.
 48  *
 49  * Each class loader has a NativeLibraries instance to register all of its
 50  * loaded native libraries.  System::loadLibrary (and other APIs) only
 51  * allows a native library to be loaded by one class loader, i.e. one
 52  * NativeLibraries instance.  Any attempt to load a native library that
 53  * has already been loaded by a class loader with another class loader
 54  * will fail.
 55  */
 56 public final class NativeLibraries {
 57     private static final boolean loadLibraryOnlyIfPresent = ClassLoaderHelper.loadLibraryOnlyIfPresent();
 58     private final Map<String, NativeLibraryImpl> libraries = new ConcurrentHashMap<>();
 59     private final ClassLoader loader;
 60     // caller, if non-null, is the fromClass parameter for NativeLibraries::loadLibrary
 61     // unless specified
 62     private final Class<?> caller;      // may be null
 63     private final boolean searchJavaLibraryPath;
 64 
 65     /**
 66      * Creates a NativeLibraries instance for loading JNI native libraries
 67      * via for System::loadLibrary use.
 68      *
 69      * 1. Support of auto-unloading.  The loaded native libraries are unloaded
 70      *    when the class loader is reclaimed.
 71      * 2. Support of linking of native method.  See JNI spec.
 72      * 3. Restriction on a native library that can only be loaded by one class loader.
 73      *    Each class loader manages its own set of native libraries.
 74      *    The same JNI native library cannot be loaded into more than one class loader.
 75      *
 76      * This static factory method is intended only for System::loadLibrary use.
 77      *
 78      * @see <a href="${docroot}/specs/jni/invocation.html##library-and-version-management">
 79      *     JNI Specification: Library and Version Management</a>
 80      */
 81     public static NativeLibraries newInstance(ClassLoader loader) {
 82         return new NativeLibraries(loader, loader != null ? null : NativeLibraries.class, loader != null);
 83     }
 84 
 85     private NativeLibraries(ClassLoader loader, Class<?> caller, boolean searchJavaLibraryPath) {
 86         this.loader = loader;
 87         this.caller = caller;
 88         this.searchJavaLibraryPath = searchJavaLibraryPath;
 89     }
 90 
 91     /*
 92      * Find the address of the given symbol name from the native libraries
 93      * loaded in this NativeLibraries instance.
 94      */
 95     public long find(String name) {
 96         if (libraries.isEmpty())
 97             return 0;
 98 
 99         // the native libraries map may be updated in another thread
100         // when a native library is being loaded.  No symbol will be
101         // searched from it yet.
102         for (NativeLibrary lib : libraries.values()) {
103             long entry = lib.find(name);
104             if (entry != 0) return entry;
105         }
106         return 0;
107     }
108 
109     /*
110      * Load a native library from the given file.  Returns null if the given
111      * library is determined to be non-loadable, which is system-dependent.
112      *
113      * @param fromClass the caller class calling System::loadLibrary
114      * @param file the path of the native library
115      * @throws UnsatisfiedLinkError if any error in loading the native library
116      */
117     public NativeLibrary loadLibrary(Class<?> fromClass, File file) {
118         // Check to see if we're attempting to access a static library
119         String name = findBuiltinLib(file.getName());
120         boolean isBuiltin = (name != null);
121         if (!isBuiltin) {
122             try {
123                 if (loadLibraryOnlyIfPresent && !file.exists()) {
124                     return null;
125                 }
126                 name = file.getCanonicalPath();
127             } catch (IOException e) {
128                 return null;
129             }
130         }
131         return loadLibrary(fromClass, name, isBuiltin);
132     }
133 
134     /**
135      * Returns a NativeLibrary of the given name.
136      *
137      * @param fromClass the caller class calling System::loadLibrary
138      * @param name      library name
139      * @param isBuiltin built-in library
140      * @throws UnsatisfiedLinkError if the native library has already been loaded
141      *      and registered in another NativeLibraries
142      */
143     private NativeLibrary loadLibrary(Class<?> fromClass, String name, boolean isBuiltin) {
144         ClassLoader loader = (fromClass == null) ? null : fromClass.getClassLoader();
145         if (this.loader != loader) {
146             throw new InternalError(fromClass.getName() + " not allowed to load library");
147         }
148 
149         acquireNativeLibraryLock(name);
150         try {
151             // find if this library has already been loaded and registered in this NativeLibraries
152             NativeLibrary cached = libraries.get(name);
153             if (cached != null) {
154                 return cached;
155             }
156 
157             // cannot be loaded by other class loaders
158             if (Holder.loadedLibraryNames.contains(name)) {
159                 throw new UnsatisfiedLinkError("Native Library " + name +
160                         " already loaded in another classloader");
161             }
162 
163             /*
164              * When a library is being loaded, JNI_OnLoad function can cause
165              * another loadLibrary invocation that should succeed.
166              *
167              * Each thread maintains its own stack to hold the list of
168              * libraries it is loading.
169              *
170              * If there is a pending load operation for the library, we
171              * immediately return success; if the pending load is from
172              * a different class loader, we raise UnsatisfiedLinkError.
173              */
174             for (NativeLibraryImpl lib : NativeLibraryContext.current()) {
175                 if (name.equals(lib.name())) {
176                     if (loader == lib.fromClass.getClassLoader()) {
177                         return lib;
178                     } else {
179                         throw new UnsatisfiedLinkError("Native Library " +
180                                 name + " is being loaded in another classloader");
181                     }
182                 }
183             }
184 
185             NativeLibraryImpl lib = new NativeLibraryImpl(fromClass, name, isBuiltin);
186             // load the native library
187             NativeLibraryContext.push(lib);
188             try {
189                 if (!lib.open()) {
190                     return null;    // fail to open the native library
191                 }
192                 // auto unloading is only supported for JNI native libraries
193                 // loaded by custom class loaders that can be unloaded.
194                 // built-in class loaders are never unloaded.
195                 boolean autoUnload = !VM.isSystemDomainLoader(loader) && loader != ClassLoaders.appClassLoader();
196                 if (autoUnload) {
197                     // register the loaded native library for auto unloading
198                     // when the class loader is reclaimed, all native libraries
199                     // loaded that class loader will be unloaded.
200                     // The entries in the libraries map are not removed since
201                     // the entire map will be reclaimed altogether.
202                     CleanerFactory.cleaner().register(loader, lib.unloader());
203                 }
204             } finally {
205                 NativeLibraryContext.pop();
206             }
207             // register the loaded native library
208             Holder.loadedLibraryNames.add(name);
209             libraries.put(name, lib);
210             return lib;
211         } finally {
212             releaseNativeLibraryLock(name);
213         }
214     }
215 
216     /**
217      * Loads a native library from the system library path and java library path.
218      *
219      * @param name library name
220      *
221      * @throws UnsatisfiedLinkError if the native library has already been loaded
222      *      and registered in another NativeLibraries
223      */
224     public NativeLibrary loadLibrary(String name) {
225         assert name.indexOf(File.separatorChar) < 0;
226         return loadLibrary(caller, name);
227     }
228 
229     /**
230      * Loads a native library from the system library path and java library path.
231      *
232      * @param name library name
233      * @param fromClass the caller class calling System::loadLibrary
234      *
235      * @throws UnsatisfiedLinkError if the native library has already been loaded
236      *      and registered in another NativeLibraries
237      */
238     public NativeLibrary loadLibrary(Class<?> fromClass, String name) {
239         assert name.indexOf(File.separatorChar) < 0;
240 
241         NativeLibrary lib = findFromPaths(LibraryPaths.SYS_PATHS, fromClass, name);
242         if (lib == null && searchJavaLibraryPath) {
243             lib = findFromPaths(LibraryPaths.USER_PATHS, fromClass, name);
244         }
245         return lib;
246     }
247 
248     // Called at the end of AOTCache assembly phase.
249     public void clear() {
250         libraries.clear();
251     }
252 
253     private NativeLibrary findFromPaths(String[] paths, Class<?> fromClass, String name) {
254         for (String path : paths) {
255             File libfile = new File(path, System.mapLibraryName(name));
256             NativeLibrary nl = loadLibrary(fromClass, libfile);
257             if (nl != null) {
258                 return nl;
259             }
260             libfile = ClassLoaderHelper.mapAlternativeName(libfile);
261             if (libfile != null) {
262                 nl = loadLibrary(fromClass, libfile);
263                 if (nl != null) {
264                     return nl;
265                 }
266             }
267         }
268         return null;
269     }
270 
271     /**
272      * NativeLibraryImpl denotes a loaded native library instance.
273      * Each NativeLibraries contains a map of loaded native libraries in the
274      * private field {@code libraries}.
275      *
276      * Every native library requires a particular version of JNI. This is
277      * denoted by the private {@code jniVersion} field.  This field is set by
278      * the VM when it loads the library, and used by the VM to pass the correct
279      * version of JNI to the native methods.
280      */
281     static class NativeLibraryImpl extends NativeLibrary {
282         // the class from which the library is loaded, also indicates
283         // the loader this native library belongs.
284         final Class<?> fromClass;
285         // the canonicalized name of the native library.
286         // or static library name
287         final String name;
288         // Indicates if the native library is linked into the VM
289         final boolean isBuiltin;
290 
291         // opaque handle to native library, used in native code.
292         long handle;
293         // the version of JNI environment the native library requires.
294         int jniVersion;
295 
296         NativeLibraryImpl(Class<?> fromClass, String name, boolean isBuiltin) {
297             this.fromClass = fromClass;
298             this.name = name;
299             this.isBuiltin = isBuiltin;
300         }
301 
302         @Override
303         public String name() {
304             return name;
305         }
306 
307         @Override
308         public long find(String name) {
309             return findEntry0(handle, name);
310         }
311 
312         /*
313          * Unloader::run method is invoked to unload the native library
314          * when this class loader becomes phantom reachable.
315          */
316         private Runnable unloader() {
317             return new Unloader(name, handle, isBuiltin);
318         }
319 
320         /*
321          * Loads the named native library
322          */
323         boolean open() {
324             if (handle != 0) {
325                 throw new InternalError("Native library " + name + " has been loaded");
326             }
327 
328             return load(this, name, isBuiltin, throwExceptionIfFail());
329         }
330 
331         @SuppressWarnings("removal")
332         private boolean throwExceptionIfFail() {
333             if (loadLibraryOnlyIfPresent) return true;
334 
335             // If the file exists but fails to load, UnsatisfiedLinkException thrown by the VM
336             // will include the error message from dlopen to provide diagnostic information
337             return AccessController.doPrivileged(new PrivilegedAction<>() {
338                 public Boolean run() {
339                     File file = new File(name);
340                     return file.exists();
341                 }
342             });
343         }
344 
345         /*
346          * Close this native library.
347          */
348         void close() {
349             unload(name, isBuiltin, handle);
350         }
351     }
352 
353     /*
354      * The run() method will be invoked when this class loader becomes
355      * phantom reachable to unload the native library.
356      */
357     static class Unloader implements Runnable {
358         // This represents the context when a native library is unloaded
359         // and getFromClass() will return null,
360         static final NativeLibraryImpl UNLOADER =
361                 new NativeLibraryImpl(null, "dummy", false);
362 
363         final String name;
364         final long handle;
365         final boolean isBuiltin;
366 
367         Unloader(String name, long handle, boolean isBuiltin) {
368             if (handle == 0) {
369                 throw new IllegalArgumentException(
370                         "Invalid handle for native library " + name);
371             }
372 
373             this.name = name;
374             this.handle = handle;
375             this.isBuiltin = isBuiltin;
376         }
377 
378         @Override
379         public void run() {
380             acquireNativeLibraryLock(name);
381             try {
382                 /* remove the native library name */
383                 if (!Holder.loadedLibraryNames.remove(name)) {
384                     throw new IllegalStateException(name + " has already been unloaded");
385                 }
386                 NativeLibraryContext.push(UNLOADER);
387                 try {
388                     unload(name, isBuiltin, handle);
389                 } finally {
390                     NativeLibraryContext.pop();
391                 }
392             } finally {
393                 releaseNativeLibraryLock(name);
394             }
395         }
396     }
397 
398     /*
399      * Holds system and user library paths derived from the
400      * {@code java.library.path} and {@code sun.boot.library.path} system
401      * properties. The system properties are eagerly read at bootstrap, then
402      * lazily parsed on first use to avoid initialization ordering issues.
403      */
404     static class LibraryPaths {
405         // The paths searched for libraries
406         static final String[] SYS_PATHS = ClassLoaderHelper.parsePath(StaticProperty.sunBootLibraryPath());
407         static final String[] USER_PATHS = ClassLoaderHelper.parsePath(StaticProperty.javaLibraryPath());
408     }
409 
410     // Holder has the fields that need to be initialized during JVM bootstrap even if
411     // the outer is aot-initialized.
412     static class Holder {
413         // All native libraries we've loaded.
414         private static final Set<String> loadedLibraryNames =
415             ConcurrentHashMap.newKeySet();
416     }
417 
418     // reentrant lock class that allows exact counting (with external synchronization)
419     @SuppressWarnings("serial")
420     private static final class CountedLock extends ReentrantLock {
421 
422         private int counter = 0;
423 
424         public void increment() {
425             if (counter == Integer.MAX_VALUE) {
426                 // prevent overflow
427                 throw new Error("Maximum lock count exceeded");
428             }
429             ++counter;
430         }
431 
432         public void decrement() {
433             --counter;
434         }
435 
436         public int getCounter() {
437             return counter;
438         }
439     }
440 
441     // Maps native library name to the corresponding lock object
442     private static final Map<String, CountedLock> nativeLibraryLockMap =
443             new ConcurrentHashMap<>();
444 
445     private static void acquireNativeLibraryLock(String libraryName) {
446         nativeLibraryLockMap.compute(libraryName,
447             new BiFunction<>() {
448                 public CountedLock apply(String name, CountedLock currentLock) {
449                     if (currentLock == null) {
450                         currentLock = new CountedLock();
451                     }
452                     // safe as compute BiFunction<> is executed atomically
453                     currentLock.increment();
454                     return currentLock;
455                 }
456             }
457         ).lock();
458     }
459 
460     private static void releaseNativeLibraryLock(String libraryName) {
461         CountedLock lock = nativeLibraryLockMap.computeIfPresent(libraryName,
462             new BiFunction<>() {
463                 public CountedLock apply(String name, CountedLock currentLock) {
464                     if (currentLock.getCounter() == 1) {
465                         // unlock and release the object if no other threads are queued
466                         currentLock.unlock();
467                         // remove the element
468                         return null;
469                     } else {
470                         currentLock.decrement();
471                         return currentLock;
472                     }
473                 }
474             }
475         );
476         if (lock != null) {
477             lock.unlock();
478         }
479     }
480 
481     // native libraries being loaded
482     private static final class NativeLibraryContext {
483 
484         // Maps thread object to the native library context stack, maintained by each thread
485         private static Map<Thread, Deque<NativeLibraryImpl>> nativeLibraryThreadContext =
486                 new ConcurrentHashMap<>();
487 
488         // returns a context associated with the current thread
489         private static Deque<NativeLibraryImpl> current() {
490             return nativeLibraryThreadContext.computeIfAbsent(
491                     Thread.currentThread(),
492                     new Function<>() {
493                         public Deque<NativeLibraryImpl> apply(Thread t) {
494                             return new ArrayDeque<>(8);
495                         }
496                     });
497         }
498 
499         private static NativeLibraryImpl peek() {
500             return current().peek();
501         }
502 
503         private static void push(NativeLibraryImpl lib) {
504             current().push(lib);
505         }
506 
507         private static void pop() {
508             // this does not require synchronization since each
509             // thread has its own context
510             Deque<NativeLibraryImpl> libs = current();
511             libs.pop();
512             if (libs.isEmpty()) {
513                 // context can be safely removed once empty
514                 nativeLibraryThreadContext.remove(Thread.currentThread());
515             }
516         }
517 
518         private static boolean isEmpty() {
519             Deque<NativeLibraryImpl> context =
520                     nativeLibraryThreadContext.get(Thread.currentThread());
521             return (context == null || context.isEmpty());
522         }
523     }
524 
525     // Invoked in the VM to determine the context class in JNI_OnLoad
526     // and JNI_OnUnload
527     private static Class<?> getFromClass() {
528         if (NativeLibraryContext.isEmpty()) { // only default library
529             return Object.class;
530         }
531         return NativeLibraryContext.peek().fromClass;
532     }
533 
534     /*
535      * Return true if the given library is successfully loaded.
536      * If the given library cannot be loaded for any reason,
537      * if throwExceptionIfFail is false, then this method returns false;
538      * otherwise, UnsatisfiedLinkError will be thrown.
539      *
540      * JNI FindClass expects the caller class if invoked from JNI_OnLoad
541      * and JNI_OnUnload is NativeLibrary class.
542      */
543     private static native boolean load(NativeLibraryImpl impl, String name,
544                                        boolean isBuiltin,
545                                        boolean throwExceptionIfFail);
546     /*
547      * Unload the named library.  JNI_OnUnload, if present, will be invoked
548      * before the native library is unloaded.
549      */
550     private static native void unload(String name, boolean isBuiltin, long handle);
551     private static native String findBuiltinLib(String name);
552 }