1 /*
  2  * Copyright (c) 2020, 2024, 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 (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             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     private NativeLibrary findFromPaths(String[] paths, Class<?> fromClass, String name) {
249         for (String path : paths) {
250             File libfile = new File(path, System.mapLibraryName(name));
251             NativeLibrary nl = loadLibrary(fromClass, libfile);
252             if (nl != null) {
253                 return nl;
254             }
255             libfile = ClassLoaderHelper.mapAlternativeName(libfile);
256             if (libfile != null) {
257                 nl = loadLibrary(fromClass, libfile);
258                 if (nl != null) {
259                     return nl;
260                 }
261             }
262         }
263         return null;
264     }
265 
266     /**
267      * NativeLibraryImpl denotes a loaded native library instance.
268      * Each NativeLibraries contains a map of loaded native libraries in the
269      * private field {@code libraries}.
270      *
271      * Every native library requires a particular version of JNI. This is
272      * denoted by the private {@code jniVersion} field.  This field is set by
273      * the VM when it loads the library, and used by the VM to pass the correct
274      * version of JNI to the native methods.
275      */
276     static class NativeLibraryImpl extends NativeLibrary {
277         // the class from which the library is loaded, also indicates
278         // the loader this native library belongs.
279         final Class<?> fromClass;
280         // the canonicalized name of the native library.
281         // or static library name
282         final String name;
283         // Indicates if the native library is linked into the VM
284         final boolean isBuiltin;
285 
286         // opaque handle to native library, used in native code.
287         long handle;
288         // the version of JNI environment the native library requires.
289         int jniVersion;
290 
291         NativeLibraryImpl(Class<?> fromClass, String name, boolean isBuiltin) {
292             this.fromClass = fromClass;
293             this.name = name;
294             this.isBuiltin = isBuiltin;
295         }
296 
297         @Override
298         public String name() {
299             return name;
300         }
301 
302         @Override
303         public long find(String name) {
304             return findEntry0(handle, name);
305         }
306 
307         /*
308          * Unloader::run method is invoked to unload the native library
309          * when this class loader becomes phantom reachable.
310          */
311         private Runnable unloader() {
312             return new Unloader(name, handle, isBuiltin);
313         }
314 
315         /*
316          * Loads the named native library
317          */
318         boolean open() {
319             if (handle != 0) {
320                 throw new InternalError("Native library " + name + " has been loaded");
321             }
322 
323             return load(this, name, isBuiltin, throwExceptionIfFail());
324         }
325 
326         @SuppressWarnings("removal")
327         private boolean throwExceptionIfFail() {
328             if (loadLibraryOnlyIfPresent) return true;
329 
330             // If the file exists but fails to load, UnsatisfiedLinkException thrown by the VM
331             // will include the error message from dlopen to provide diagnostic information
332             return AccessController.doPrivileged(new PrivilegedAction<>() {
333                 public Boolean run() {
334                     File file = new File(name);
335                     return file.exists();
336                 }
337             });
338         }
339 
340         /*
341          * Close this native library.
342          */
343         void close() {
344             unload(name, isBuiltin, handle);
345         }
346     }
347 
348     /*
349      * The run() method will be invoked when this class loader becomes
350      * phantom reachable to unload the native library.
351      */
352     static class Unloader implements Runnable {
353         // This represents the context when a native library is unloaded
354         // and getFromClass() will return null,
355         static final NativeLibraryImpl UNLOADER =
356                 new NativeLibraryImpl(null, "dummy", false);
357 
358         final String name;
359         final long handle;
360         final boolean isBuiltin;
361 
362         Unloader(String name, long handle, boolean isBuiltin) {
363             if (handle == 0) {
364                 throw new IllegalArgumentException(
365                         "Invalid handle for native library " + name);
366             }
367 
368             this.name = name;
369             this.handle = handle;
370             this.isBuiltin = isBuiltin;
371         }
372 
373         @Override
374         public void run() {
375             acquireNativeLibraryLock(name);
376             try {
377                 /* remove the native library name */
378                 if (!loadedLibraryNames.remove(name)) {
379                     throw new IllegalStateException(name + " has already been unloaded");
380                 }
381                 NativeLibraryContext.push(UNLOADER);
382                 try {
383                     unload(name, isBuiltin, handle);
384                 } finally {
385                     NativeLibraryContext.pop();
386                 }
387             } finally {
388                 releaseNativeLibraryLock(name);
389             }
390         }
391     }
392 
393     /*
394      * Holds system and user library paths derived from the
395      * {@code java.library.path} and {@code sun.boot.library.path} system
396      * properties. The system properties are eagerly read at bootstrap, then
397      * lazily parsed on first use to avoid initialization ordering issues.
398      */
399     static class LibraryPaths {
400         // The paths searched for libraries
401         static final String[] SYS_PATHS = ClassLoaderHelper.parsePath(StaticProperty.sunBootLibraryPath());
402         static final String[] USER_PATHS = ClassLoaderHelper.parsePath(StaticProperty.javaLibraryPath());
403     }
404 
405     // All native libraries we've loaded.
406     private static final Set<String> loadedLibraryNames =
407             ConcurrentHashMap.newKeySet();
408 
409     // reentrant lock class that allows exact counting (with external synchronization)
410     @SuppressWarnings("serial")
411     private static final class CountedLock extends ReentrantLock {
412 
413         private int counter = 0;
414 
415         public void increment() {
416             if (counter == Integer.MAX_VALUE) {
417                 // prevent overflow
418                 throw new Error("Maximum lock count exceeded");
419             }
420             ++counter;
421         }
422 
423         public void decrement() {
424             --counter;
425         }
426 
427         public int getCounter() {
428             return counter;
429         }
430     }
431 
432     // Maps native library name to the corresponding lock object
433     private static final Map<String, CountedLock> nativeLibraryLockMap =
434             new ConcurrentHashMap<>();
435 
436     private static void acquireNativeLibraryLock(String libraryName) {
437         nativeLibraryLockMap.compute(libraryName,
438             new BiFunction<>() {
439                 public CountedLock apply(String name, CountedLock currentLock) {
440                     if (currentLock == null) {
441                         currentLock = new CountedLock();
442                     }
443                     // safe as compute BiFunction<> is executed atomically
444                     currentLock.increment();
445                     return currentLock;
446                 }
447             }
448         ).lock();
449     }
450 
451     private static void releaseNativeLibraryLock(String libraryName) {
452         CountedLock lock = nativeLibraryLockMap.computeIfPresent(libraryName,
453             new BiFunction<>() {
454                 public CountedLock apply(String name, CountedLock currentLock) {
455                     if (currentLock.getCounter() == 1) {
456                         // unlock and release the object if no other threads are queued
457                         currentLock.unlock();
458                         // remove the element
459                         return null;
460                     } else {
461                         currentLock.decrement();
462                         return currentLock;
463                     }
464                 }
465             }
466         );
467         if (lock != null) {
468             lock.unlock();
469         }
470     }
471 
472     // native libraries being loaded
473     private static final class NativeLibraryContext {
474 
475         // Maps thread object to the native library context stack, maintained by each thread
476         private static Map<Thread, Deque<NativeLibraryImpl>> nativeLibraryThreadContext =
477                 new ConcurrentHashMap<>();
478 
479         // returns a context associated with the current thread
480         private static Deque<NativeLibraryImpl> current() {
481             return nativeLibraryThreadContext.computeIfAbsent(
482                     Thread.currentThread(),
483                     new Function<>() {
484                         public Deque<NativeLibraryImpl> apply(Thread t) {
485                             return new ArrayDeque<>(8);
486                         }
487                     });
488         }
489 
490         private static NativeLibraryImpl peek() {
491             return current().peek();
492         }
493 
494         private static void push(NativeLibraryImpl lib) {
495             current().push(lib);
496         }
497 
498         private static void pop() {
499             // this does not require synchronization since each
500             // thread has its own context
501             Deque<NativeLibraryImpl> libs = current();
502             libs.pop();
503             if (libs.isEmpty()) {
504                 // context can be safely removed once empty
505                 nativeLibraryThreadContext.remove(Thread.currentThread());
506             }
507         }
508 
509         private static boolean isEmpty() {
510             Deque<NativeLibraryImpl> context =
511                     nativeLibraryThreadContext.get(Thread.currentThread());
512             return (context == null || context.isEmpty());
513         }
514     }
515 
516     // Invoked in the VM to determine the context class in JNI_OnLoad
517     // and JNI_OnUnload
518     private static Class<?> getFromClass() {
519         if (NativeLibraryContext.isEmpty()) { // only default library
520             return Object.class;
521         }
522         return NativeLibraryContext.peek().fromClass;
523     }
524 
525     /*
526      * Return true if the given library is successfully loaded.
527      * If the given library cannot be loaded for any reason,
528      * if throwExceptionIfFail is false, then this method returns false;
529      * otherwise, UnsatisfiedLinkError will be thrown.
530      *
531      * JNI FindClass expects the caller class if invoked from JNI_OnLoad
532      * and JNI_OnUnload is NativeLibrary class.
533      */
534     private static native boolean load(NativeLibraryImpl impl, String name,
535                                        boolean isBuiltin,
536                                        boolean throwExceptionIfFail);
537     /*
538      * Unload the named library.  JNI_OnUnload, if present, will be invoked
539      * before the native library is unloaded.
540      */
541     private static native void unload(String name, boolean isBuiltin, long handle);
542     private static native String findBuiltinLib(String name);
543 }