JavaAdapterFactory.java revision 1472:7dd80d7f47c3
1/*
2 * Copyright (c) 2010, 2015, 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
26package jdk.nashorn.internal.runtime.linker;
27
28import static jdk.nashorn.internal.lookup.Lookup.MH;
29
30import java.lang.invoke.MethodHandle;
31import java.lang.invoke.MethodHandles;
32import java.lang.invoke.MethodHandles.Lookup;
33import java.lang.invoke.MethodType;
34import java.lang.reflect.Modifier;
35import java.security.AccessControlContext;
36import java.security.AccessController;
37import java.security.CodeSigner;
38import java.security.CodeSource;
39import java.security.Permissions;
40import java.security.PrivilegedAction;
41import java.security.ProtectionDomain;
42import java.util.ArrayList;
43import java.util.Arrays;
44import java.util.Collections;
45import java.util.HashMap;
46import java.util.List;
47import java.util.Map;
48import java.util.concurrent.ConcurrentHashMap;
49import jdk.internal.dynalink.beans.StaticClass;
50import jdk.internal.dynalink.support.SimpleLinkRequest;
51import jdk.nashorn.internal.runtime.Context;
52import jdk.nashorn.internal.runtime.ECMAException;
53import jdk.nashorn.internal.runtime.ScriptFunction;
54import jdk.nashorn.internal.runtime.ScriptObject;
55
56/**
57 * A factory class that generates adapter classes. Adapter classes allow
58 * implementation of Java interfaces and extending of Java classes from
59 * JavaScript. For every combination of a superclass to extend and interfaces to
60 * implement (collectively: "original types"), exactly one adapter class is
61 * generated that extends the specified superclass and implements the specified
62 * interfaces. (But see the discussion of class-based overrides for exceptions.)
63 * <p>
64 * The adapter class is generated in a new secure class loader that inherits
65 * Nashorn's protection domain, and has either one of the original types' class
66 * loader or the Nashorn's class loader as its parent - the parent class loader
67 * is chosen so that all the original types and the Nashorn core classes are
68 * visible from it (as the adapter will have constant pool references to
69 * ScriptObject and ScriptFunction classes). In case none of the candidate class
70 * loaders has visibility of all the required types, an error is thrown. The
71 * class uses {@link JavaAdapterBytecodeGenerator} to generate the adapter class
72 * itself; see its documentation for details about the generated class.
73 * <p>
74 * You normally don't use this class directly, but rather either create adapters
75 * from script using {@link jdk.nashorn.internal.objects.NativeJava#extend(Object, Object...)},
76 * using the {@code new} operator on abstract classes and interfaces (see
77 * {@link jdk.nashorn.internal.objects.NativeJava#type(Object, Object)}), or
78 * implicitly when passing script functions to Java methods expecting SAM types.
79 */
80
81@SuppressWarnings("javadoc")
82public final class JavaAdapterFactory {
83    private static final ProtectionDomain MINIMAL_PERMISSION_DOMAIN = createMinimalPermissionDomain();
84
85    // context with permissions needs for AdapterInfo creation
86    private static final AccessControlContext CREATE_ADAPTER_INFO_ACC_CTXT =
87        ClassAndLoader.createPermAccCtxt("createClassLoader", "getClassLoader",
88            "accessDeclaredMembers", "accessClassInPackage.jdk.nashorn.internal.runtime");
89
90    /**
91     * A mapping from an original Class object to AdapterInfo representing the adapter for the class it represents.
92     */
93    private static final ClassValue<Map<List<Class<?>>, AdapterInfo>> ADAPTER_INFO_MAPS = new ClassValue<Map<List<Class<?>>, AdapterInfo>>() {
94        @Override
95        protected Map<List<Class<?>>, AdapterInfo> computeValue(final Class<?> type) {
96            return new HashMap<>();
97        }
98    };
99
100    /**
101     * Returns an adapter class for the specified original types. The adapter
102     * class extends/implements the original class/interfaces.
103     *
104     * @param types the original types. The caller must pass at least one Java
105     *        type representing either a public interface or a non-final public
106     *        class with at least one public or protected constructor. If more
107     *        than one type is specified, at most one can be a class and the
108     *        rest have to be interfaces. The class can be in any position in
109     *        the array. Invoking the method twice with exactly the same types
110     *        in the same order will return the same adapter class, any
111     *        reordering of types or even addition or removal of redundant types
112     *        (i.e., interfaces that other types in the list already
113     *        implement/extend, or {@code java.lang.Object} in a list of types
114     *        consisting purely of interfaces) will result in a different
115     *        adapter class, even though those adapter classes are functionally
116     *        identical; we deliberately don't want to incur the additional
117     *        processing cost of canonicalizing type lists.
118     * @param classOverrides a JavaScript object with functions serving as the
119     *        class-level overrides and implementations. These overrides are
120     *        defined for all instances of the class, and can be further
121     *        overridden on a per-instance basis by passing additional objects
122     *        in the constructor.
123     * @param lookup the lookup object identifying the caller class. The
124     *        generated adapter class will have the protection domain of the
125     *        caller class iff the lookup object is full-strength, otherwise it
126     *        will be completely unprivileged.
127     *
128     * @return an adapter class. See this class' documentation for details on
129     *         the generated adapter class.
130     *
131     * @throws ECMAException with a TypeError if the adapter class can not be
132     *         generated because the original class is final, non-public, or has
133     *         no public or protected constructors.
134     */
135    public static StaticClass getAdapterClassFor(final Class<?>[] types, final ScriptObject classOverrides, final MethodHandles.Lookup lookup) {
136        return getAdapterClassFor(types, classOverrides, getProtectionDomain(lookup));
137    }
138
139    private static StaticClass getAdapterClassFor(final Class<?>[] types, final ScriptObject classOverrides, final ProtectionDomain protectionDomain) {
140        assert types != null && types.length > 0;
141        final SecurityManager sm = System.getSecurityManager();
142        if (sm != null) {
143            for (final Class<?> type : types) {
144                // check for restricted package access
145                Context.checkPackageAccess(type);
146                // check for classes, interfaces in reflection
147                ReflectionCheckLinker.checkReflectionAccess(type, true);
148            }
149        }
150        return getAdapterInfo(types).getAdapterClass(classOverrides, protectionDomain);
151    }
152
153    private static ProtectionDomain getProtectionDomain(final MethodHandles.Lookup lookup) {
154        if((lookup.lookupModes() & Lookup.PRIVATE) == 0) {
155            return MINIMAL_PERMISSION_DOMAIN;
156        }
157        return getProtectionDomain(lookup.lookupClass());
158    }
159
160    private static ProtectionDomain getProtectionDomain(final Class<?> clazz) {
161        return AccessController.doPrivileged(new PrivilegedAction<ProtectionDomain>() {
162            @Override
163            public ProtectionDomain run() {
164                return clazz.getProtectionDomain();
165            }
166        });
167    }
168
169    /**
170     * Returns a method handle representing a constructor that takes a single
171     * argument of the source type (which, really, should be one of {@link ScriptObject},
172     * {@link ScriptFunction}, or {@link Object}, and returns an instance of the
173     * adapter for the target type. Used to implement the function autoconverters
174     * as well as the Nashorn JSR-223 script engine's {@code getInterface()}
175     * method.
176     *
177     * @param sourceType the source type; should be either {@link ScriptObject},
178     *        {@link ScriptFunction}, or {@link Object}. In case of {@code Object},
179     *        it will return a method handle that dispatches to either the script
180     *        object or function constructor at invocation based on the actual
181     *        argument.
182     * @param targetType the target type, for which adapter instances will be created
183     * @param lookup method handle lookup to use
184     *
185     * @return the constructor method handle.
186     *
187     * @throws Exception if anything goes wrong
188     */
189    public static MethodHandle getConstructor(final Class<?> sourceType, final Class<?> targetType, final MethodHandles.Lookup lookup) throws Exception {
190        final StaticClass adapterClass = getAdapterClassFor(new Class<?>[] { targetType }, null, lookup);
191        return MH.bindTo(Bootstrap.getLinkerServices().getGuardedInvocation(new SimpleLinkRequest(
192                NashornCallSiteDescriptor.get(lookup, "dyn:new",
193                        MethodType.methodType(targetType, StaticClass.class, sourceType), 0), false,
194                        adapterClass, null)).getInvocation(), adapterClass);
195    }
196
197    /**
198     * Returns whether an instance of the specified class/interface can be
199     * generated from a ScriptFunction. Returns {@code true} iff: the adapter
200     * for the class/interface can be created, it is abstract (this includes
201     * interfaces), it has at least one abstract method, all the abstract
202     * methods share the same name, and it has a public or protected default
203     * constructor. Note that invoking this class will most likely result in the
204     * adapter class being defined in the JVM if it hasn't been already.
205     *
206     * @param clazz the inspected class
207     *
208     * @return {@code true} iff an instance of the specified class/interface can
209     *         be generated from a ScriptFunction.
210     */
211    static boolean isAutoConvertibleFromFunction(final Class<?> clazz) {
212        return getAdapterInfo(new Class<?>[] { clazz }).autoConvertibleFromFunction;
213    }
214
215    private static AdapterInfo getAdapterInfo(final Class<?>[] types) {
216        final ClassAndLoader definingClassAndLoader = ClassAndLoader.getDefiningClassAndLoader(types);
217
218        final Map<List<Class<?>>, AdapterInfo> adapterInfoMap = ADAPTER_INFO_MAPS.get(definingClassAndLoader.getRepresentativeClass());
219        final List<Class<?>> typeList = types.length == 1 ? Collections.<Class<?>>singletonList(types[0]) : Arrays.asList(types.clone());
220        AdapterInfo adapterInfo;
221        synchronized(adapterInfoMap) {
222            adapterInfo = adapterInfoMap.get(typeList);
223            if(adapterInfo == null) {
224                adapterInfo = createAdapterInfo(types, definingClassAndLoader);
225                adapterInfoMap.put(typeList, adapterInfo);
226            }
227        }
228        return adapterInfo;
229    }
230
231   /**
232     * For a given class, create its adapter class and associated info.
233     *
234     * @param type the class for which the adapter is created
235     *
236     * @return the adapter info for the class.
237     */
238    private static AdapterInfo createAdapterInfo(final Class<?>[] types, final ClassAndLoader definingClassAndLoader) {
239        Class<?> superClass = null;
240        final List<Class<?>> interfaces = new ArrayList<>(types.length);
241        for(final Class<?> t: types) {
242            final int mod = t.getModifiers();
243            if(!t.isInterface()) {
244                if(superClass != null) {
245                    return new AdapterInfo(AdaptationResult.Outcome.ERROR_MULTIPLE_SUPERCLASSES, t.getCanonicalName() + " and " + superClass.getCanonicalName());
246                }
247                if (Modifier.isFinal(mod)) {
248                    return new AdapterInfo(AdaptationResult.Outcome.ERROR_FINAL_CLASS, t.getCanonicalName());
249                }
250                superClass = t;
251            } else {
252                if (interfaces.size() > 65535) {
253                    throw new IllegalArgumentException("interface limit exceeded");
254                }
255
256                interfaces.add(t);
257            }
258
259            if(!Modifier.isPublic(mod)) {
260                return new AdapterInfo(AdaptationResult.Outcome.ERROR_NON_PUBLIC_CLASS, t.getCanonicalName());
261            }
262        }
263
264
265        final Class<?> effectiveSuperClass = superClass == null ? Object.class : superClass;
266        return AccessController.doPrivileged(new PrivilegedAction<AdapterInfo>() {
267            @Override
268            public AdapterInfo run() {
269                try {
270                    return new AdapterInfo(effectiveSuperClass, interfaces, definingClassAndLoader);
271                } catch (final AdaptationException e) {
272                    return new AdapterInfo(e.getAdaptationResult());
273                } catch (final RuntimeException e) {
274                    return new AdapterInfo(new AdaptationResult(AdaptationResult.Outcome.ERROR_OTHER, Arrays.toString(types), e.toString()));
275                }
276            }
277        }, CREATE_ADAPTER_INFO_ACC_CTXT);
278    }
279
280    private static class AdapterInfo {
281        private static final ClassAndLoader SCRIPT_OBJECT_LOADER = new ClassAndLoader(ScriptFunction.class, true);
282
283        private final ClassLoader commonLoader;
284        // TODO: soft reference the JavaAdapterClassLoader objects. They can be recreated when needed.
285        private final JavaAdapterClassLoader classAdapterGenerator;
286        private final JavaAdapterClassLoader instanceAdapterGenerator;
287        private final Map<CodeSource, StaticClass> instanceAdapters = new ConcurrentHashMap<>();
288        final boolean autoConvertibleFromFunction;
289        final AdaptationResult adaptationResult;
290
291        AdapterInfo(final Class<?> superClass, final List<Class<?>> interfaces, final ClassAndLoader definingLoader) throws AdaptationException {
292            this.commonLoader = findCommonLoader(definingLoader);
293            final JavaAdapterBytecodeGenerator gen = new JavaAdapterBytecodeGenerator(superClass, interfaces, commonLoader, false);
294            this.autoConvertibleFromFunction = gen.isAutoConvertibleFromFunction();
295            instanceAdapterGenerator = gen.createAdapterClassLoader();
296            this.classAdapterGenerator = new JavaAdapterBytecodeGenerator(superClass, interfaces, commonLoader, true).createAdapterClassLoader();
297            this.adaptationResult = AdaptationResult.SUCCESSFUL_RESULT;
298        }
299
300        AdapterInfo(final AdaptationResult.Outcome outcome, final String classList) {
301            this(new AdaptationResult(outcome, classList));
302        }
303
304        AdapterInfo(final AdaptationResult adaptationResult) {
305            this.commonLoader = null;
306            this.classAdapterGenerator = null;
307            this.instanceAdapterGenerator = null;
308            this.autoConvertibleFromFunction = false;
309            this.adaptationResult = adaptationResult;
310        }
311
312        StaticClass getAdapterClass(final ScriptObject classOverrides, final ProtectionDomain protectionDomain) {
313            if(adaptationResult.getOutcome() != AdaptationResult.Outcome.SUCCESS) {
314                throw adaptationResult.typeError();
315            }
316            return classOverrides == null ? getInstanceAdapterClass(protectionDomain) :
317                getClassAdapterClass(classOverrides, protectionDomain);
318        }
319
320        private StaticClass getInstanceAdapterClass(final ProtectionDomain protectionDomain) {
321            CodeSource codeSource = protectionDomain.getCodeSource();
322            if(codeSource == null) {
323                codeSource = MINIMAL_PERMISSION_DOMAIN.getCodeSource();
324            }
325            StaticClass instanceAdapterClass = instanceAdapters.get(codeSource);
326            if(instanceAdapterClass != null) {
327                return instanceAdapterClass;
328            }
329            // Any "unknown source" code source will default to no permission domain.
330            final ProtectionDomain effectiveDomain = codeSource.equals(MINIMAL_PERMISSION_DOMAIN.getCodeSource()) ?
331                    MINIMAL_PERMISSION_DOMAIN : protectionDomain;
332
333            instanceAdapterClass = instanceAdapterGenerator.generateClass(commonLoader, effectiveDomain);
334            final StaticClass existing = instanceAdapters.putIfAbsent(codeSource, instanceAdapterClass);
335            return existing == null ? instanceAdapterClass : existing;
336        }
337
338        private StaticClass getClassAdapterClass(final ScriptObject classOverrides, final ProtectionDomain protectionDomain) {
339            JavaAdapterServices.setClassOverrides(classOverrides);
340            try {
341                return classAdapterGenerator.generateClass(commonLoader, protectionDomain);
342            } finally {
343                JavaAdapterServices.setClassOverrides(null);
344            }
345        }
346
347        /**
348         * Choose between the passed class loader and the class loader that defines the
349         * ScriptObject class, based on which of the two can see the classes in both.
350         *
351         * @param classAndLoader the loader and a representative class from it that will
352         *        be used to add the generated adapter to its ADAPTER_INFO_MAPS.
353         *
354         * @return the class loader that sees both the specified class and Nashorn classes.
355         *
356         * @throws IllegalStateException if no such class loader is found.
357         */
358        private static ClassLoader findCommonLoader(final ClassAndLoader classAndLoader) throws AdaptationException {
359            if(classAndLoader.canSee(SCRIPT_OBJECT_LOADER)) {
360                return classAndLoader.getLoader();
361            }
362            if (SCRIPT_OBJECT_LOADER.canSee(classAndLoader)) {
363                return SCRIPT_OBJECT_LOADER.getLoader();
364            }
365
366            throw new AdaptationException(AdaptationResult.Outcome.ERROR_NO_COMMON_LOADER, classAndLoader.getRepresentativeClass().getCanonicalName());
367        }
368    }
369
370    private static ProtectionDomain createMinimalPermissionDomain() {
371        // Generated classes need to have at least the permission to access Nashorn runtime and runtime.linker packages.
372        final Permissions permissions = new Permissions();
373        permissions.add(new RuntimePermission("accessClassInPackage.jdk.nashorn.internal.objects"));
374        permissions.add(new RuntimePermission("accessClassInPackage.jdk.nashorn.internal.runtime"));
375        permissions.add(new RuntimePermission("accessClassInPackage.jdk.nashorn.internal.runtime.linker"));
376        return new ProtectionDomain(new CodeSource(null, (CodeSigner[])null), permissions);
377    }
378}
379