8275703: System.loadLibrary fails on Big Sur for libraries hidden from filesystem

Reviewed-by: phh
Backport-of: 309acbf0e86a0d248294503fccc7a936fa0a846e
diff --git a/make/test/JtregNativeJdk.gmk b/make/test/JtregNativeJdk.gmk
index 8ed5cbd..270ff93 100644
--- a/make/test/JtregNativeJdk.gmk
+++ b/make/test/JtregNativeJdk.gmk
@@ -87,8 +87,9 @@
       -framework Cocoa -framework SystemConfiguration
 else
   BUILD_JDK_JTREG_EXCLUDE += libTestMainKeyWindow.m
-  BUILD_JDK_JTREG_EXCLUDE += exeJniInvocationTest.c
   BUILD_JDK_JTREG_EXCLUDE += libTestDynamicStore.m
+  BUILD_JDK_JTREG_EXCLUDE += exeJniInvocationTest.c
+  BUILD_JDK_JTREG_EXCLUDE += exeLibraryCache.c
 endif
 
 ifeq ($(call isTargetOs, linux), true)
diff --git a/src/hotspot/share/include/jvm.h b/src/hotspot/share/include/jvm.h
index 7b80d72..f12a4be 100644
--- a/src/hotspot/share/include/jvm.h
+++ b/src/hotspot/share/include/jvm.h
@@ -151,7 +151,7 @@
 JVM_IsUseContainerSupport(void);
 
 JNIEXPORT void * JNICALL
-JVM_LoadLibrary(const char *name);
+JVM_LoadLibrary(const char *name, jboolean throwException);
 
 JNIEXPORT void JNICALL
 JVM_UnloadLibrary(void * handle);
diff --git a/src/hotspot/share/prims/jvm.cpp b/src/hotspot/share/prims/jvm.cpp
index 91da785..28f6361 100644
--- a/src/hotspot/share/prims/jvm.cpp
+++ b/src/hotspot/share/prims/jvm.cpp
@@ -3357,7 +3357,7 @@
 
 // Library support ///////////////////////////////////////////////////////////////////////////
 
-JVM_ENTRY_NO_ENV(void*, JVM_LoadLibrary(const char* name))
+JVM_ENTRY_NO_ENV(void*, JVM_LoadLibrary(const char* name, jboolean throwException))
   //%note jvm_ct
   char ebuf[1024];
   void *load_result;
@@ -3366,18 +3366,23 @@
     load_result = os::dll_load(name, ebuf, sizeof ebuf);
   }
   if (load_result == NULL) {
-    char msg[1024];
-    jio_snprintf(msg, sizeof msg, "%s: %s", name, ebuf);
-    // Since 'ebuf' may contain a string encoded using
-    // platform encoding scheme, we need to pass
-    // Exceptions::unsafe_to_utf8 to the new_exception method
-    // as the last argument. See bug 6367357.
-    Handle h_exception =
-      Exceptions::new_exception(thread,
-                                vmSymbols::java_lang_UnsatisfiedLinkError(),
-                                msg, Exceptions::unsafe_to_utf8);
+    if (throwException) {
+      char msg[1024];
+      jio_snprintf(msg, sizeof msg, "%s: %s", name, ebuf);
+      // Since 'ebuf' may contain a string encoded using
+      // platform encoding scheme, we need to pass
+      // Exceptions::unsafe_to_utf8 to the new_exception method
+      // as the last argument. See bug 6367357.
+      Handle h_exception =
+        Exceptions::new_exception(thread,
+                                  vmSymbols::java_lang_UnsatisfiedLinkError(),
+                                  msg, Exceptions::unsafe_to_utf8);
 
-    THROW_HANDLE_0(h_exception);
+      THROW_HANDLE_0(h_exception);
+    } else {
+      log_info(library)("Failed to load library %s", name);
+      return load_result;
+    }
   }
   log_info(library)("Loaded library %s, handle " INTPTR_FORMAT, name, p2i(load_result));
   return load_result;
diff --git a/src/java.base/macosx/classes/jdk/internal/loader/ClassLoaderHelper.java b/src/java.base/macosx/classes/jdk/internal/loader/ClassLoaderHelper.java
index ac8deb2..8f6cfc4 100644
--- a/src/java.base/macosx/classes/jdk/internal/loader/ClassLoaderHelper.java
+++ b/src/java.base/macosx/classes/jdk/internal/loader/ClassLoaderHelper.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2021, 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
@@ -27,12 +27,38 @@
 
 import java.io.File;
 import java.util.ArrayList;
+import sun.security.action.GetPropertyAction;
 
 class ClassLoaderHelper {
+    private static final boolean hasDynamicLoaderCache;
+    static {
+        String osVersion = GetPropertyAction.privilegedGetProperty("os.version");
+        // dynamic linker cache support on os.version >= 11.x
+        int major = 11;
+        int i = osVersion.indexOf('.');
+        try {
+            major = Integer.parseInt(i < 0 ? osVersion : osVersion.substring(0, i));
+        } catch (NumberFormatException e) {}
+        hasDynamicLoaderCache = major >= 11;
+    }
 
     private ClassLoaderHelper() {}
 
     /**
+     * Returns true if loading a native library only if
+     * it's present on the file system.
+     *
+     * @implNote
+     * On macOS 11.x or later which supports dynamic linker cache,
+     * the dynamic library is not present on the filesystem.  The
+     * library cannot determine if a dynamic library exists on a
+     * given path or not and so this method returns false.
+     */
+    static boolean loadLibraryOnlyIfPresent() {
+        return !hasDynamicLoaderCache;
+    }
+
+    /**
      * Returns an alternate path name for the given file
      * such that if the original pathname did not exist, then the
      * file may be located at the alternate location.
diff --git a/src/java.base/share/classes/jdk/internal/loader/NativeLibraries.java b/src/java.base/share/classes/jdk/internal/loader/NativeLibraries.java
index 2dbf585..981c378 100644
--- a/src/java.base/share/classes/jdk/internal/loader/NativeLibraries.java
+++ b/src/java.base/share/classes/jdk/internal/loader/NativeLibraries.java
@@ -53,7 +53,7 @@
  * will fail.
  */
 public final class NativeLibraries {
-
+    private static final boolean loadLibraryOnlyIfPresent = ClassLoaderHelper.loadLibraryOnlyIfPresent();
     private final Map<String, NativeLibraryImpl> libraries = new ConcurrentHashMap<>();
     private final ClassLoader loader;
     // caller, if non-null, is the fromClass parameter for NativeLibraries::loadLibrary
@@ -142,7 +142,8 @@
     }
 
     /*
-     * Load a native library from the given file.  Returns null if file does not exist.
+     * Load a native library from the given file.  Returns null if the given
+     * library is determined to be non-loadable, which is system-dependent.
      *
      * @param fromClass the caller class calling System::loadLibrary
      * @param file the path of the native library
@@ -155,14 +156,17 @@
         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;
+                    public String run() {
+                        try {
+                            if (loadLibraryOnlyIfPresent && !file.exists()) {
+                                return null;
+                            }
+                            return file.getCanonicalPath();
+                        } catch (IOException e) {
+                            return null;
+                        }
                     }
-                }
-            });
+                });
             if (name == null) {
                 return null;
             }
@@ -381,7 +385,7 @@
                 throw new InternalError("Native library " + name + " has been loaded");
             }
 
-            return load(this, name, isBuiltin, isJNI);
+            return load(this, name, isBuiltin, isJNI, loadLibraryOnlyIfPresent);
         }
     }
 
@@ -461,7 +465,9 @@
 
     // 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, boolean isJNI);
+    private static native boolean load(NativeLibraryImpl impl, String name,
+                                       boolean isBuiltin, boolean isJNI,
+                                       boolean throwExceptionIfFail);
     private static native void unload(String name, boolean isBuiltin, boolean isJNI, long handle);
     private static native String findBuiltinLib(String name);
     private static native long findEntry0(NativeLibraryImpl lib, String name);
diff --git a/src/java.base/share/native/libjava/NativeLibraries.c b/src/java.base/share/native/libjava/NativeLibraries.c
index 1d973c9..71b8b8c 100644
--- a/src/java.base/share/native/libjava/NativeLibraries.c
+++ b/src/java.base/share/native/libjava/NativeLibraries.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2021, 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
@@ -113,7 +113,8 @@
  */
 JNIEXPORT jboolean JNICALL
 Java_jdk_internal_loader_NativeLibraries_load
-  (JNIEnv *env, jobject this, jobject lib, jstring name, jboolean isBuiltin, jboolean isJNI)
+  (JNIEnv *env, jobject this, jobject lib, jstring name,
+   jboolean isBuiltin, jboolean isJNI, jboolean throwExceptionIfFail)
 {
     const char *cname;
     jint jniVersion;
@@ -127,7 +128,7 @@
     cname = JNU_GetStringPlatformChars(env, name, 0);
     if (cname == 0)
         return JNI_FALSE;
-    handle = isBuiltin ? procHandle : JVM_LoadLibrary(cname);
+    handle = isBuiltin ? procHandle : JVM_LoadLibrary(cname, throwExceptionIfFail);
     if (isJNI) {
         if (handle) {
             JNI_OnLoad_t JNI_OnLoad;
diff --git a/src/java.base/unix/classes/jdk/internal/loader/ClassLoaderHelper.java b/src/java.base/unix/classes/jdk/internal/loader/ClassLoaderHelper.java
index fb4a504..2151ad0 100644
--- a/src/java.base/unix/classes/jdk/internal/loader/ClassLoaderHelper.java
+++ b/src/java.base/unix/classes/jdk/internal/loader/ClassLoaderHelper.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2021, 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
@@ -33,6 +33,14 @@
     private ClassLoaderHelper() {}
 
     /**
+     * Returns true if loading a native library only if
+     * it's present on the file system.
+     */
+    static boolean loadLibraryOnlyIfPresent() {
+        return true;
+    }
+
+    /**
      * Returns an alternate path name for the given file
      * such that if the original pathname did not exist, then the
      * file may be located at the alternate location.
diff --git a/src/java.base/windows/classes/jdk/internal/loader/ClassLoaderHelper.java b/src/java.base/windows/classes/jdk/internal/loader/ClassLoaderHelper.java
index e386585..423805f 100644
--- a/src/java.base/windows/classes/jdk/internal/loader/ClassLoaderHelper.java
+++ b/src/java.base/windows/classes/jdk/internal/loader/ClassLoaderHelper.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2021, 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
@@ -32,6 +32,14 @@
     private ClassLoaderHelper() {}
 
     /**
+     * Returns true if loading a native library only if
+     * it's present on the file system.
+     */
+    static boolean loadLibraryOnlyIfPresent() {
+        return true;
+    }
+
+    /**
      * Returns an alternate path name for the given file
      * such that if the original pathname did not exist, then the
      * file may be located at the alternate location.
diff --git a/test/jdk/java/lang/RuntimeTests/loadLibrary/exeLibraryCache/LibraryFromCache.java b/test/jdk/java/lang/RuntimeTests/loadLibrary/exeLibraryCache/LibraryFromCache.java
new file mode 100644
index 0000000..e11746a
--- /dev/null
+++ b/test/jdk/java/lang/RuntimeTests/loadLibrary/exeLibraryCache/LibraryFromCache.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2021, 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.
+ *
+ */
+
+/**
+ * @test
+ * @bug 8275703
+ * @library /test/lib
+ * @requires os.family == "mac"
+ * @run main/native/othervm -Djava.library.path=/usr/lib LibraryFromCache blas
+ * @run main/native/othervm -Djava.library.path=/usr/lib LibraryFromCache BLAS
+ * @summary Test System::loadLibrary to be able to load a library even
+ *          if it's not present on the filesystem on macOS which supports
+ *          dynamic library cache
+ */
+
+import jdk.test.lib.process.OutputAnalyzer;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+public class LibraryFromCache {
+    public static void main(String[] args) throws IOException {
+        String libname = args[0];
+        if (!systemHasLibrary(libname)) {
+            System.out.println("Test skipped. Library " + libname + " not found");
+            return;
+        }
+
+        System.loadLibrary(libname);
+    }
+
+    /*
+     * Returns true if dlopen successfully loads the specified library
+     */
+    private static boolean systemHasLibrary(String libname) throws IOException {
+        Path launcher = Paths.get(System.getProperty("test.nativepath"), "LibraryCache");
+        ProcessBuilder pb = new ProcessBuilder(launcher.toString(), "lib" + libname + ".dylib");
+        OutputAnalyzer outputAnalyzer = new OutputAnalyzer(pb.start());
+        System.out.println(outputAnalyzer.getOutput());
+        return outputAnalyzer.getExitValue() == 0;
+    }
+}
diff --git a/test/jdk/java/lang/RuntimeTests/loadLibrary/exeLibraryCache/exeLibraryCache.c b/test/jdk/java/lang/RuntimeTests/loadLibrary/exeLibraryCache/exeLibraryCache.c
new file mode 100644
index 0000000..846e04e
--- /dev/null
+++ b/test/jdk/java/lang/RuntimeTests/loadLibrary/exeLibraryCache/exeLibraryCache.c
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2021, 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.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <dlfcn.h>
+
+int main(int argc, char** argv)
+{
+    void *handle;
+
+    if (argc != 2) {
+        fprintf(stderr, "Usage: %s <lib_filename_or_full_path>\n", argv[0]);
+        return EXIT_FAILURE;
+    }
+
+    printf("Attempting to load library '%s'...\n", argv[1]);
+
+    handle = dlopen(argv[1], RTLD_LAZY);
+
+    if (handle == NULL) {
+       fprintf(stderr, "Unable to load library!\n");
+       return EXIT_FAILURE;
+    }
+
+    printf("Library successfully loaded!\n");
+
+    return dlclose(handle);
+}