1 /*
   2  * Copyright (c) 2010, 2013, 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 
  26 package jdk.nashorn.internal.runtime.linker;
  27 
  28 import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
  29 
  30 import java.security.AccessControlContext;
  31 import java.security.AccessController;
  32 import java.security.Permissions;
  33 import java.security.PrivilegedAction;
  34 import java.security.ProtectionDomain;
  35 import java.util.Collection;
  36 import java.util.Iterator;
  37 import java.util.LinkedHashMap;
  38 import java.util.LinkedList;
  39 import java.util.List;
  40 import java.util.Map;
  41 
  42 /**
  43  * A tuple of a class loader and a single class representative of the classes that can be loaded through it. Its
  44  * equals/hashCode is defined in terms of the identity of the class loader. The rationale for this class is that it
  45  * couples a class loader with a random representative class coming from that loader - this representative class is then
  46  * used to determine if one loader can see the other loader's classes.
  47  */
  48 final class ClassAndLoader {
  49     static AccessControlContext createPermAccCtxt(final String... permNames) {
  50         final Permissions perms = new Permissions();
  51         for (final String permName : permNames) {
  52             perms.add(new RuntimePermission(permName));
  53         }
  54         return new AccessControlContext(new ProtectionDomain[] { new ProtectionDomain(null, perms) });
  55     }
  56 
  57     private static final AccessControlContext GET_LOADER_ACC_CTXT = createPermAccCtxt("getClassLoader");
  58 
  59     private final Class<?> representativeClass;
  60     // Don't access this directly; most of the time, use getRetrievedLoader(), or if you know what you're doing,
  61     // getLoader().
  62     private ClassLoader loader;
  63     // We have mild affinity against eagerly retrieving the loader, as we need to do it in a privileged block. For
  64     // the most basic case of looking up an already-generated adapter info for a single type, we avoid it.
  65     private boolean loaderRetrieved;
  66 
  67     ClassAndLoader(final Class<?> representativeClass, final boolean retrieveLoader) {
  68         this.representativeClass = representativeClass;
  69         if(retrieveLoader) {
  70             retrieveLoader();
  71         }
  72     }
  73 
  74     Class<?> getRepresentativeClass() {
  75         return representativeClass;
  76     }
  77 
  78     boolean canSee(final ClassAndLoader other) {
  79         try {
  80             final Class<?> otherClass = other.getRepresentativeClass();
  81             return Class.forName(otherClass.getName(), false, getLoader()) == otherClass;
  82         } catch (final ClassNotFoundException e) {
  83             return false;
  84         }
  85     }
  86 
  87     ClassLoader getLoader() {
  88         if(!loaderRetrieved) {
  89             retrieveLoader();
  90         }
  91         return getRetrievedLoader();
  92     }
  93 
  94     ClassLoader getRetrievedLoader() {
  95         assert loaderRetrieved;
  96         return loader;
  97     }
  98 
  99     private void retrieveLoader() {
 100         loader = representativeClass.getClassLoader();
 101         loaderRetrieved = true;
 102     }
 103 
 104     @Override
 105     public boolean equals(final Object obj) {
 106         return obj instanceof ClassAndLoader && ((ClassAndLoader)obj).getRetrievedLoader() == getRetrievedLoader();
 107     }
 108 
 109     @Override
 110     public int hashCode() {
 111         return System.identityHashCode(getRetrievedLoader());
 112     }
 113 
 114     /**
 115      * Given a list of types that define the superclass/interfaces for an adapter class, returns a single type from the
 116      * list that will be used to attach the adapter to its ClassValue. The first type in the array that is defined in a
 117      * class loader that can also see all other types is returned. If there is no such loader, an exception is thrown.
 118      * @param types the input types
 119      * @return the first type from the array that is defined in a class loader that can also see all other types.
 120      */
 121     static ClassAndLoader getDefiningClassAndLoader(final Class<?>[] types) {
 122         // Short circuit the cheap case
 123         if(types.length == 1) {
 124             return new ClassAndLoader(types[0], false);
 125         }
 126 
 127         return AccessController.doPrivileged(new PrivilegedAction<ClassAndLoader>() {
 128             @Override
 129             public ClassAndLoader run() {
 130                 return getDefiningClassAndLoaderPrivileged(types);
 131             }
 132         }, GET_LOADER_ACC_CTXT);
 133     }
 134 
 135     static ClassAndLoader getDefiningClassAndLoaderPrivileged(final Class<?>[] types) {
 136         final Collection<ClassAndLoader> maximumVisibilityLoaders = getMaximumVisibilityLoaders(types);
 137 
 138         final Iterator<ClassAndLoader> it = maximumVisibilityLoaders.iterator();
 139         if(maximumVisibilityLoaders.size() == 1) {
 140             // Fortunate case - single maximally specific class loader; return its representative class.
 141             return it.next();
 142         }
 143 
 144         // Ambiguity; throw an error.
 145         assert maximumVisibilityLoaders.size() > 1; // basically, can't be zero
 146         final StringBuilder b = new StringBuilder();
 147         b.append(it.next().getRepresentativeClass().getCanonicalName());
 148         while(it.hasNext()) {
 149             b.append(", ").append(it.next().getRepresentativeClass().getCanonicalName());
 150         }
 151         throw typeError("extend.ambiguous.defining.class", b.toString());
 152     }
 153 
 154     /**
 155      * Given an array of types, return a subset of their class loaders that are maximal according to the
 156      * "can see other loaders' classes" relation, which is presumed to be a partial ordering.
 157      * @param types types
 158      * @return a collection of maximum visibility class loaders. It is guaranteed to have at least one element.
 159      */
 160     private static Collection<ClassAndLoader> getMaximumVisibilityLoaders(final Class<?>[] types) {
 161         final List<ClassAndLoader> maximumVisibilityLoaders = new LinkedList<>();
 162         outer:  for(final ClassAndLoader maxCandidate: getClassLoadersForTypes(types)) {
 163             final Iterator<ClassAndLoader> it = maximumVisibilityLoaders.iterator();
 164             while(it.hasNext()) {
 165                 final ClassAndLoader existingMax = it.next();
 166                 final boolean candidateSeesExisting = maxCandidate.canSee(existingMax);
 167                 final boolean exitingSeesCandidate = existingMax.canSee(maxCandidate);
 168                 if(candidateSeesExisting) {
 169                     if(!exitingSeesCandidate) {
 170                         // The candidate sees the the existing maximum, so drop the existing one as it's no longer maximal.
 171                         it.remove();
 172                     }
 173                     // NOTE: there's also the anomalous case where both loaders see each other. Not sure what to do
 174                     // about that one, as two distinct class loaders both seeing each other's classes is weird and
 175                     // violates the assumption that the relation "sees others' classes" is a partial ordering. We'll
 176                     // just not do anything, and treat them as incomparable; hopefully some later class loader that
 177                     // comes along can eliminate both of them, if it can not, we'll end up with ambiguity anyway and
 178                     // throw an error at the end.
 179                 } else if(exitingSeesCandidate) {
 180                     // Existing sees the candidate, so drop the candidate.
 181                     continue outer;
 182                 }
 183             }
 184             // If we get here, no existing maximum visibility loader could see the candidate, so the candidate is a new
 185             // maximum.
 186             maximumVisibilityLoaders.add(maxCandidate);
 187         }
 188         return maximumVisibilityLoaders;
 189     }
 190 
 191     private static Collection<ClassAndLoader> getClassLoadersForTypes(final Class<?>[] types) {
 192         final Map<ClassAndLoader, ClassAndLoader> classesAndLoaders = new LinkedHashMap<>();
 193         for(final Class<?> c: types) {
 194             final ClassAndLoader cl = new ClassAndLoader(c, true);
 195             if(!classesAndLoaders.containsKey(cl)) {
 196                 classesAndLoaders.put(cl, cl);
 197             }
 198         }
 199         return classesAndLoaders.keySet();
 200     }
 201 }