8139895: Introduce GuardingDynamicLinkerExporter
authorattila
Tue, 20 Oct 2015 23:34:46 +0200
changeset 33340 6c945c826f36
parent 33339 334cd3ebfa5e
child 33341 cc9fa3638714
8139895: Introduce GuardingDynamicLinkerExporter Reviewed-by: hannesw, sundar
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/internal/dynalink/DynamicLinkerFactory.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/internal/dynalink/linker/GuardingDynamicLinker.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/internal/dynalink/linker/GuardingDynamicLinkerExporter.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/internal/dynalink/package-info.java
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/internal/dynalink/DynamicLinkerFactory.java	Tue Oct 20 23:34:16 2015 +0200
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/internal/dynalink/DynamicLinkerFactory.java	Tue Oct 20 23:34:46 2015 +0200
@@ -92,15 +92,19 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Objects;
+import java.util.ServiceConfigurationError;
 import java.util.ServiceLoader;
 import java.util.Set;
+import java.util.function.Supplier;
 import jdk.internal.dynalink.beans.BeansLinker;
 import jdk.internal.dynalink.linker.GuardedInvocation;
 import jdk.internal.dynalink.linker.GuardedInvocationTransformer;
 import jdk.internal.dynalink.linker.GuardingDynamicLinker;
+import jdk.internal.dynalink.linker.GuardingDynamicLinkerExporter;
 import jdk.internal.dynalink.linker.GuardingTypeConverterFactory;
 import jdk.internal.dynalink.linker.LinkRequest;
 import jdk.internal.dynalink.linker.LinkerServices;
@@ -141,6 +145,8 @@
     private MethodTypeConversionStrategy autoConversionStrategy;
     private MethodHandleTransformer internalObjectsFilter;
 
+    private List<ServiceConfigurationError> autoLoadingErrors = Collections.emptyList();
+
     /**
      * Creates a new dynamic linker factory with default configuration. Upon
      * creation, the factory can be configured using various {@code setXxx()}
@@ -152,17 +158,18 @@
 
     /**
      * Sets the class loader for automatic discovery of available guarding
-     * dynamic linkers. Guarding dynamic linker classes declared in
-     * {@code /META-INF/services/jdk.internal.dynalink.linker.GuardingDynamicLinker}
-     * resources available through this class loader will be automatically
-     * instantiated using the {@link ServiceLoader} mechanism and incorporated
-     * into {@code DynamicLinker}s that this factory creates. This allows for
-     * cross-language interoperability where call sites belonging to this language
-     * runtime can be linked by linkers from these autodiscovered runtimes if
-     * their native objects are passed to this runtime. If class loader is not
-     * set explicitly, then the thread context class loader of the thread
-     * invoking {@link #createLinker()} will be used. Can be invoked with null
-     * to signify system class loader.
+     * dynamic linkers. {@link GuardingDynamicLinkerExporter} implementations
+     * available through this class loader will be automatically instantiated
+     * using the {@link ServiceLoader} mechanism and the linkers they provide
+     * will be incorporated into {@code DynamicLinker}s that this factory
+     * creates. This allows for cross-language interoperability where call sites
+     * belonging to this language runtime can be linked by linkers from these
+     * automatically discovered runtimes if their native objects are passed to
+     * this runtime. If class loader is not set explicitly by invoking this
+     * method, then the thread context class loader of the thread invoking
+     * {@link #createLinker()} will be used. If this method is invoked
+     * explicitly with null then {@link ServiceLoader#loadInstalled(Class)} will
+     * be used to load the linkers.
      *
      * @param classLoader the class loader used for the automatic discovery of
      * available linkers.
@@ -185,7 +192,7 @@
      * @throws NullPointerException if any of the list elements are null.
      */
     public void setPrioritizedLinkers(final List<? extends GuardingDynamicLinker> prioritizedLinkers) {
-        this.prioritizedLinkers = reqireNonNullElements(prioritizedLinkers);
+        this.prioritizedLinkers = copyListRequireNonNullElements(prioritizedLinkers);
     }
 
     /**
@@ -227,7 +234,7 @@
      * @throws NullPointerException if any of the list elements are null.
      */
     public void setFallbackLinkers(final List<? extends GuardingDynamicLinker> fallbackLinkers) {
-        this.fallbackLinkers = reqireNonNullElements(fallbackLinkers);
+        this.fallbackLinkers = copyListRequireNonNullElements(fallbackLinkers);
     }
 
     /**
@@ -345,9 +352,14 @@
     /**
      * Creates a new dynamic linker based on the current configuration. This
      * method can be invoked more than once to create multiple dynamic linkers.
-     * Autodiscovered linkers are newly instantiated on every invocation of this
-     * method. It is allowed to change the factory's configuration between
-     * invocations. The method is not thread safe.
+     * Automatically discovered linkers are newly instantiated on every
+     * invocation of this method. It is allowed to change the factory's
+     * configuration between invocations. The method is not thread safe. After
+     * invocation, callers can invoke {@link #getAutoLoadingErrors()} to
+     * retrieve a list of {@link ServiceConfigurationError}s that occurred while
+     * trying to load automatically discovered linkers. These are never thrown
+     * from the call to this method as it makes every effort to recover from
+     * them and ignore the failing linkers.
      * @return the new dynamic Linker
      */
     public DynamicLinker createLinker() {
@@ -366,12 +378,7 @@
         addClasses(knownLinkerClasses, prioritizedLinkers);
         addClasses(knownLinkerClasses, fallbackLinkers);
 
-        final ClassLoader effectiveClassLoader = classLoaderExplicitlySet ? classLoader : getThreadContextClassLoader();
-        final List<GuardingDynamicLinker> discovered = new LinkedList<>();
-        final ServiceLoader<GuardingDynamicLinker> linkerLoader = ServiceLoader.load(GuardingDynamicLinker.class, effectiveClassLoader);
-        for(final GuardingDynamicLinker linker: linkerLoader) {
-            discovered.add(linker);
-        }
+        final List<GuardingDynamicLinker> discovered = discoverAutoLoadLinkers();
 
         // Now, concatenate ...
         final List<GuardingDynamicLinker> linkers =
@@ -420,6 +427,68 @@
                 syncOnRelink, unstableRelinkThreshold);
     }
 
+    /**
+     * Returns a list of {@link ServiceConfigurationError}s that were
+     * encountered while loading automatically discovered linkers during the
+     * last invocation of {@link #createLinker()}. They can be any non-Dynalink
+     * specific service configuration issues, as well as some Dynalink-specific
+     * errors when an exporter that the factory tried to automatically load:
+     * <ul>
+     * <li>did not have the runtime permission named
+     * {@link GuardingDynamicLinkerExporter#AUTOLOAD_PERMISSION_NAME} in a
+     * system with a security manager, or</li>
+     * <li>returned null from {@link GuardingDynamicLinkerExporter#get()}, or</li>
+     * <li>the list returned from {@link GuardingDynamicLinkerExporter#get()}
+     * had a null element.</li>
+     * </ul>
+     * @return an immutable list of encountered
+     * {@link ServiceConfigurationError}s. Can be empty.
+     */
+    public List<ServiceConfigurationError> getAutoLoadingErrors() {
+        return Collections.unmodifiableList(autoLoadingErrors);
+    }
+
+    private List<GuardingDynamicLinker> discoverAutoLoadLinkers() {
+        autoLoadingErrors = new LinkedList<>();
+        final ClassLoader effectiveClassLoader = classLoaderExplicitlySet ? classLoader : getThreadContextClassLoader();
+        final List<GuardingDynamicLinker> discovered = new LinkedList<>();
+        try {
+            final ServiceLoader<GuardingDynamicLinkerExporter> linkerLoader =
+                    AccessController.doPrivileged((PrivilegedAction<ServiceLoader<GuardingDynamicLinkerExporter>>)()-> {
+                        if (effectiveClassLoader == null) {
+                            return ServiceLoader.loadInstalled(GuardingDynamicLinkerExporter.class);
+                        }
+                        return ServiceLoader.load(GuardingDynamicLinkerExporter.class, effectiveClassLoader);
+                    });
+
+            for(final Iterator<GuardingDynamicLinkerExporter> it = linkerLoader.iterator(); it.hasNext();) {
+                try {
+                    final GuardingDynamicLinkerExporter autoLoader = it.next();
+                    try {
+                        discovered.addAll(requireNonNullElements(
+                                Objects.requireNonNull(autoLoader.get(),
+                                        ()->(autoLoader.getClass().getName() + " returned null from get()")),
+                                ()->(autoLoader.getClass().getName() + " returned a list with at least one null element")));
+                    } catch (final ServiceConfigurationError|VirtualMachineError e) {
+                        // Don't wrap a SCE in another SCE. Also, don't ignore
+                        // any VME (e.g. StackOverflowError or OutOfMemoryError).
+                        throw e;
+                    } catch (final Throwable t) {
+                        throw new ServiceConfigurationError(t.getMessage(), t);
+                    }
+                } catch (final ServiceConfigurationError e) {
+                    // Catch SCE with an individual exporter, carry on with it.hasNext().
+                    autoLoadingErrors.add(e);
+                }
+            }
+        } catch (final ServiceConfigurationError e) {
+            // Catch a top-level SCE; one either in ServiceLoader.load(),
+            // ServiceLoader.iterator(), or Iterator.hasNext().
+            autoLoadingErrors.add(e);
+        }
+        return discovered;
+    }
+
     private static ClassLoader getThreadContextClassLoader() {
         return AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
             @Override
@@ -436,14 +505,18 @@
         }
     }
 
-    private static <T> List<T> reqireNonNullElements(final List<T> list) {
+    private static <T> List<T> copyListRequireNonNullElements(final List<T> list) {
         if (list == null) {
             return null;
         }
+        return new ArrayList<>(requireNonNullElements(list, ()->"List has at least one null element"));
+    }
+
+    private static <T> List<T> requireNonNullElements(final List<T> list, final Supplier<String> msgSupplier) {
         for(final T t: list) {
-            Objects.requireNonNull(t);
+            Objects.requireNonNull(t, msgSupplier);
         }
-        return new ArrayList<>(list);
+        return list;
     }
 
 }
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/internal/dynalink/linker/GuardingDynamicLinker.java	Tue Oct 20 23:34:16 2015 +0200
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/internal/dynalink/linker/GuardingDynamicLinker.java	Tue Oct 20 23:34:46 2015 +0200
@@ -101,16 +101,15 @@
  * set some as {@link DynamicLinkerFactory#setFallbackLinkers(List) fallback
  * linkers} to handle language-specific "property not found" etc. conditions.
  * <p>
- * Languages can declare the linkers they want to expose to other runtimes for
- * {@link DynamicLinkerFactory#setClassLoader(ClassLoader) automatic discovery}
- * in <tt>META-INF/services/jdk.internal.dynalink.linker.GuardingDynamicLinker</tt>
- * resources of their JAR files.
- * <p>
  * Consider implementing {@link TypeBasedGuardingDynamicLinker} interface
  * instead of this interface for those linkers that are based on the Java class
  * of the objects. If you need to implement language-specific type conversions,
  * have your {@code GuardingDynamicLinker} also implement the
  * {@link GuardingTypeConverterFactory} interface.
+ * <p>
+ * Languages can export linkers to other language runtimes for
+ * {@link DynamicLinkerFactory#setClassLoader(ClassLoader) automatic discovery}
+ * using a {@link GuardingDynamicLinkerExporter}.
  */
 public interface GuardingDynamicLinker {
     /**
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/internal/dynalink/linker/GuardingDynamicLinkerExporter.java	Tue Oct 20 23:34:46 2015 +0200
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2015, 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.dynalink.linker;
+
+import java.security.Permission;
+import java.util.List;
+import java.util.ServiceLoader;
+import java.util.function.Supplier;
+import jdk.internal.dynalink.DynamicLinkerFactory;
+
+/**
+ * A class acting as a supplier of guarding dynamic linkers that can be
+ * automatically loaded by other language runtimes. Language runtimes wishing
+ * to export their own linkers should subclass this class and implement the
+ * {@link #get()} method to return a list of exported linkers and declare the
+ * subclass in
+ * {@code /META-INF/services/jdk.internal.dynalink.linker.GuardingDynamicLinkerExporter}
+ * resource of their distribution (typically, JAR file) so that dynamic linker
+ * factories can discover them using the {@link ServiceLoader} mechanism. Note
+ * that instantiating this class is tied to a security check for the
+ * {@code RuntimePermission("dynalink.exportLinkersAutomatically")} when a
+ * security manager is present, to ensure that only trusted runtimes can
+ * automatically export their linkers into other runtimes.
+ * @see DynamicLinkerFactory#setClassLoader(ClassLoader)
+ */
+public abstract class GuardingDynamicLinkerExporter implements Supplier<List<GuardingDynamicLinker>> {
+    /**
+     * The name of the runtime permission for creating instances of this class.
+     * Granting this permission to a language runtime allows it to export its
+     * linkers for automatic loading into other language runtimes.
+     */
+    public static final String AUTOLOAD_PERMISSION_NAME = "dynalink.exportLinkersAutomatically";
+
+    private static final Permission AUTOLOAD_PERMISSION = new RuntimePermission(AUTOLOAD_PERMISSION_NAME);
+
+    /**
+     * Creates a new linker exporter. If there is a security manager installed
+     * checks for the
+     * {@code RuntimePermission("dynalink.exportLinkersAutomatically")} runtime
+     * permission. This ensures only language runtimes granted this permission
+     * will be allowed to export their linkers for automatic loading.
+     * @throws SecurityException if the necessary runtime permission is not
+     * granted.
+     */
+    protected GuardingDynamicLinkerExporter() {
+        final SecurityManager sm = System.getSecurityManager();
+        if (sm != null) {
+            sm.checkPermission(AUTOLOAD_PERMISSION);
+        }
+    }
+}
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/internal/dynalink/package-info.java	Tue Oct 20 23:34:16 2015 +0200
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/internal/dynalink/package-info.java	Tue Oct 20 23:34:46 2015 +0200
@@ -389,17 +389,17 @@
  * <h2>Cross-language interoperability</h2>
  * A {@code DynamicLinkerFactory} can be configured with a
  * {@link jdk.internal.dynalink.DynamicLinkerFactory#setClassLoader(ClassLoader) class
- * loader}. It will try to instantiate all linker classes declared in
- * {@code META-INF/services/jdk.internal.dynalink.linker.GuardingDynamicLinker} resources
- * visible to that class loader and compose them into the {@code DynamicLinker}
- * it creates. This allows for interoperability between languages because if you
- * have two language runtimes A and B deployed in your JVM and they expose their
- * linkers through the above mechanism, language runtime A will have a
- * language-specific linker instance from B and vice versa inside their
- * {@code DynamicLinker} objects. This means that if an object from language
- * runtime B gets passed to code from language runtime A, the linker from B will
- * get a chance to link the call site in A when it encounters the object from B.
- * @since 1.9
+ * loader}. It will try to instantiate all
+ * {@link jdk.internal.dynalink.linker.GuardingDynamicLinkerExporter} classes visible to
+ * that class loader and compose the linkers they provide into the
+ * {@code DynamicLinker} it creates. This allows for interoperability between
+ * languages: if you have two language runtimes A and B deployed in your JVM and
+ * they export their linkers through the above mechanism, language runtime A
+ * will have a language-specific linker instance from B and vice versa inside
+ * their {@code DynamicLinker} objects. This means that if an object from
+ * language runtime B gets passed to code from language runtime A, the linker
+ * from B will get a chance to link the call site in A when it encounters the
+ * object from B.
  */
 @jdk.Exported
 package jdk.internal.dynalink;