JavaAdapterServices.java revision 953:221a84ef44c0
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
26package jdk.nashorn.internal.runtime.linker;
27
28import static jdk.internal.org.objectweb.asm.Opcodes.ACC_FINAL;
29import static jdk.internal.org.objectweb.asm.Opcodes.ACC_PUBLIC;
30import static jdk.internal.org.objectweb.asm.Opcodes.ACC_STATIC;
31import static jdk.internal.org.objectweb.asm.Opcodes.ACC_SUPER;
32import static jdk.internal.org.objectweb.asm.Opcodes.ALOAD;
33import static jdk.internal.org.objectweb.asm.Opcodes.RETURN;
34import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
35
36import java.lang.invoke.MethodHandle;
37import java.lang.invoke.MethodHandles;
38import java.lang.invoke.MethodType;
39import java.security.AccessController;
40import java.security.CodeSigner;
41import java.security.CodeSource;
42import java.security.Permissions;
43import java.security.PrivilegedAction;
44import java.security.ProtectionDomain;
45import java.security.SecureClassLoader;
46import jdk.internal.org.objectweb.asm.ClassWriter;
47import jdk.internal.org.objectweb.asm.Opcodes;
48import jdk.internal.org.objectweb.asm.Type;
49import jdk.internal.org.objectweb.asm.commons.InstructionAdapter;
50import jdk.nashorn.internal.runtime.Context;
51import jdk.nashorn.internal.runtime.ScriptFunction;
52import jdk.nashorn.internal.runtime.ScriptObject;
53import jdk.nashorn.internal.runtime.ScriptRuntime;
54import jdk.nashorn.internal.runtime.Undefined;
55
56/**
57 * Provides static utility services to generated Java adapter classes.
58 */
59public final class JavaAdapterServices {
60    private static final ThreadLocal<ScriptObject> classOverrides = new ThreadLocal<>();
61    private static final MethodHandle NO_PERMISSIONS_INVOKER = createNoPermissionsInvoker();
62
63    private JavaAdapterServices() {
64    }
65
66    /**
67     * Given a JS script function, binds it to null JS "this", and adapts its parameter types, return types, and arity
68     * to the specified type and arity. This method is public mainly for implementation reasons, so the adapter classes
69     * can invoke it from their constructors that take a ScriptFunction in its first argument to obtain the method
70     * handles for their abstract method implementations.
71     * @param fn the script function
72     * @param type the method type it has to conform to
73     * @return the appropriately adapted method handle for invoking the script function.
74     */
75    public static MethodHandle getHandle(final ScriptFunction fn, final MethodType type) {
76        // JS "this" will be global object or undefined depending on if 'fn' is strict or not
77        return bindAndAdaptHandle(fn, fn.isStrict()? ScriptRuntime.UNDEFINED : Context.getGlobal(), type);
78    }
79
80    /**
81     * Given a JS script object, retrieves a function from it by name, binds it to the script object as its "this", and
82     * adapts its parameter types, return types, and arity to the specified type and arity. This method is public mainly
83     * for implementation reasons, so the adapter classes can invoke it from their constructors that take a Object
84     * in its first argument to obtain the method handles for their method implementations.
85     * @param obj the script obj
86     * @param name the name of the property that contains the function
87     * @param type the method type it has to conform to
88     * @return the appropriately adapted method handle for invoking the script function, or null if the value of the
89     * property is either null or undefined, or "toString" was requested as the name, but the object doesn't directly
90     * define it but just inherits it through prototype.
91     */
92    public static MethodHandle getHandle(final Object obj, final String name, final MethodType type) {
93        if (! (obj instanceof ScriptObject)) {
94            throw typeError("not.an.object", ScriptRuntime.safeToString(obj));
95        }
96
97        final ScriptObject sobj = (ScriptObject)obj;
98        // Since every JS Object has a toString, we only override "String toString()" it if it's explicitly specified
99        if ("toString".equals(name) && !sobj.hasOwnProperty("toString")) {
100            return null;
101        }
102
103        final Object fnObj = sobj.get(name);
104        if (fnObj instanceof ScriptFunction) {
105            return bindAndAdaptHandle((ScriptFunction)fnObj, sobj, type);
106        } else if(fnObj == null || fnObj instanceof Undefined) {
107            return null;
108        } else {
109            throw typeError("not.a.function", name);
110        }
111    }
112
113    /**
114     * Returns a thread-local JS object used to define methods for the adapter class being initialized on the current
115     * thread. This method is public solely for implementation reasons, so the adapter classes can invoke it from their
116     * static initializers.
117     * @return the thread-local JS object used to define methods for the class being initialized.
118     */
119    public static Object getClassOverrides() {
120        final Object overrides = classOverrides.get();
121        assert overrides != null;
122        return overrides;
123    }
124
125    /**
126     * Takes a method handle and an argument to it, and invokes the method handle passing it the argument. Basically
127     * equivalent to {@code method.invokeExact(arg)}, except that the method handle will be invoked in a protection
128     * domain with absolutely no permissions.
129     * @param method the method handle to invoke. The handle must have the exact type of {@code void(Object)}.
130     * @param arg the argument to pass to the handle.
131     * @throws Throwable if anything goes wrong.
132     */
133    public static void invokeNoPermissions(final MethodHandle method, final Object arg) throws Throwable {
134        NO_PERMISSIONS_INVOKER.invokeExact(method, arg);
135    }
136
137    /**
138     * Set the current global scope
139     * @param global the global scope
140     */
141    public static void setGlobal(final Object global) {
142        Context.setGlobal((ScriptObject)global);
143    }
144
145    /**
146     * Get the current global scope
147     * @return the current global scope
148     */
149    public static Object getGlobal() {
150        return Context.getGlobal();
151    }
152
153    static void setClassOverrides(final ScriptObject overrides) {
154        classOverrides.set(overrides);
155    }
156
157    private static MethodHandle bindAndAdaptHandle(final ScriptFunction fn, final Object self, final MethodType type) {
158        return Bootstrap.getLinkerServices().asType(ScriptObject.pairArguments(fn.getBoundInvokeHandle(self), type, false), type);
159    }
160
161    private static MethodHandle createNoPermissionsInvoker() {
162        final String className = "NoPermissionsInvoker";
163
164        final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
165        cw.visit(Opcodes.V1_7, ACC_PUBLIC | ACC_SUPER | ACC_FINAL, className, null, "java/lang/Object", null);
166        final Type objectType = Type.getType(Object.class);
167        final Type methodHandleType = Type.getType(MethodHandle.class);
168        final InstructionAdapter mv = new InstructionAdapter(cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "invoke",
169                Type.getMethodDescriptor(Type.VOID_TYPE, methodHandleType, objectType), null, null));
170        mv.visitCode();
171        mv.visitVarInsn(ALOAD, 0);
172        mv.visitVarInsn(ALOAD, 1);
173        mv.invokevirtual(methodHandleType.getInternalName(), "invokeExact", Type.getMethodDescriptor(
174                Type.VOID_TYPE, objectType), false);
175        mv.visitInsn(RETURN);
176        mv.visitMaxs(0, 0);
177        mv.visitEnd();
178        cw.visitEnd();
179        final byte[] bytes = cw.toByteArray();
180
181        final ClassLoader loader = AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
182            @Override
183            public ClassLoader run() {
184                return new SecureClassLoader(null) {
185                    @Override
186                    protected Class<?> findClass(final String name) throws ClassNotFoundException {
187                        if(name.equals(className)) {
188                            return defineClass(name, bytes, 0, bytes.length, new ProtectionDomain(
189                                    new CodeSource(null, (CodeSigner[])null), new Permissions()));
190                        }
191                        throw new ClassNotFoundException(name);
192                    }
193                };
194            }
195        });
196
197        try {
198            return MethodHandles.lookup().findStatic(Class.forName(className, true, loader), "invoke",
199                    MethodType.methodType(void.class, MethodHandle.class, Object.class));
200        } catch(final ReflectiveOperationException e) {
201            throw new AssertionError(e.getMessage(), e);
202        }
203    }
204
205    /**
206     * Returns a method handle used to convert a return value from a delegate method (always Object) to the expected
207     * Java return type.
208     * @param returnType the return type
209     * @return the converter for the expected return type
210     */
211    public static MethodHandle getObjectConverter(final Class<?> returnType) {
212        return Bootstrap.getLinkerServices().getTypeConverter(Object.class, returnType);
213    }
214
215    /**
216     * Invoked when returning Object from an adapted method to filter out internal Nashorn objects that must not be seen
217     * by the callers. Currently only transforms {@code ConsString} into {@code String}.
218     * @param obj the return value
219     * @return the filtered return value.
220     */
221    public static Object exportReturnValue(final Object obj) {
222        return NashornBeansLinker.exportArgument(obj);
223    }
224
225    /**
226     * Invoked to convert a return value of a delegate function to primitive char. There's no suitable conversion in
227     * {@code JSType}, so we provide our own to adapters.
228     * @param obj the return value.
229     * @return the character value of the return value
230     */
231    public static char toCharPrimitive(final Object obj) {
232        return JavaArgumentConverters.toCharPrimitive(obj);
233    }
234
235    /**
236     * Invoked to convert a return value of a delegate function to String. It is similar to
237     * {@code JSType.toString(Object)}, except it doesn't handle StaticClass specially, and it returns null for null
238     * input instead of the string "null".
239     * @param obj the return value.
240     * @return the String value of the return value
241     */
242    public static String toString(final Object obj) {
243        return JavaArgumentConverters.toString(obj);
244    }
245}
246