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