JavaAdapterServices.java revision 1643:133ea8746b37
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.CallSite;
37import java.lang.invoke.ConstantCallSite;
38import java.lang.invoke.MethodHandle;
39import java.lang.invoke.MethodHandles;
40import java.lang.invoke.MethodHandles.Lookup;
41import java.lang.invoke.MethodType;
42import java.security.AccessController;
43import java.security.CodeSigner;
44import java.security.CodeSource;
45import java.security.Permissions;
46import java.security.PrivilegedAction;
47import java.security.ProtectionDomain;
48import java.security.SecureClassLoader;
49import java.util.Objects;
50import jdk.internal.org.objectweb.asm.ClassWriter;
51import jdk.internal.org.objectweb.asm.Opcodes;
52import jdk.internal.org.objectweb.asm.Type;
53import jdk.internal.org.objectweb.asm.commons.InstructionAdapter;
54import jdk.nashorn.internal.objects.Global;
55import jdk.nashorn.internal.runtime.Context;
56import jdk.nashorn.internal.runtime.ECMAException;
57import jdk.nashorn.internal.runtime.JSType;
58import jdk.nashorn.internal.runtime.ScriptFunction;
59import jdk.nashorn.internal.runtime.ScriptObject;
60import jdk.nashorn.internal.runtime.ScriptRuntime;
61
62/**
63 * Provides static utility services to generated Java adapter classes.
64 */
65public final class JavaAdapterServices {
66    private static final ThreadLocal<ScriptObject> classOverrides = new ThreadLocal<>();
67    private static final MethodHandle NO_PERMISSIONS_INVOKER = createNoPermissionsInvoker();
68
69    private JavaAdapterServices() {
70    }
71
72    /**
73     * Given a script function used as a delegate for a SAM adapter, figure out
74     * the right object to use as its "this" when called.
75     * @param delegate the delegate function
76     * @param global the current global of the adapter
77     * @return either the passed global, or UNDEFINED if the function is strict.
78     */
79    public static Object getCallThis(final ScriptFunction delegate, final Object global) {
80        return delegate.isStrict() ? ScriptRuntime.UNDEFINED : global;
81    }
82
83    /**
84     * Throws a "not.an.object" type error. Used when the delegate passed to the
85     * adapter constructor is not a script object.
86     * @param obj the object that is not a script object.
87     */
88    public static void notAnObject(final Object obj) {
89        throw typeError("not.an.object", ScriptRuntime.safeToString(obj));
90    }
91
92    /**
93     * Checks if the passed object, which is supposed to be a callee retrieved
94     * through applying the GET_METHOD_PROPERTY operation on the delegate, is
95     * a ScriptFunction, or null or undefined. These are the only allowed values
96     * for adapter method implementations, so in case it is neither, it throws
97     * a type error. Note that this restriction is somewhat artificial; as the
98     * CALL dynamic operation could invoke any Nashorn callable. We are
99     * restricting adapters to actual ScriptFunction objects for now though.
100     * @param callee the callee to check
101     * @param name the name of the function
102     * @return the callee cast to a ScriptFunction, or null if it was null or undefined.
103     * @throws ECMAException representing a JS TypeError with "not.a.function"
104     * message if the passed callee is neither a script function, nor null, nor
105     * undefined.
106     */
107    public static ScriptFunction checkFunction(final Object callee, final String name) {
108        if (callee instanceof ScriptFunction) {
109            return (ScriptFunction)callee;
110        } else if (JSType.nullOrUndefined(callee)) {
111            return null;
112        }
113        throw typeError("not.a.function.value", name, ScriptRuntime.safeToString(callee));
114    }
115
116    /**
117     * Returns a thread-local JS object used to define methods for the adapter class being initialized on the current
118     * thread. This method is public solely for implementation reasons, so the adapter classes can invoke it from their
119     * static initializers.
120     * @return the thread-local JS object used to define methods for the class being initialized.
121     */
122    public static ScriptObject getClassOverrides() {
123        final ScriptObject overrides = classOverrides.get();
124        assert overrides != null;
125        return overrides;
126    }
127
128    /**
129     * Takes a method handle and an argument to it, and invokes the method handle passing it the argument. Basically
130     * equivalent to {@code method.invokeExact(arg)}, except that the method handle will be invoked in a protection
131     * domain with absolutely no permissions.
132     * @param method the method handle to invoke. The handle must have the exact type of {@code void(Object)}.
133     * @param arg the argument to pass to the handle.
134     * @throws Throwable if anything goes wrong.
135     */
136    public static void invokeNoPermissions(final MethodHandle method, final Object arg) throws Throwable {
137        NO_PERMISSIONS_INVOKER.invokeExact(method, arg);
138    }
139
140    /**
141     * Set the current global scope to that of the adapter global
142     * @param adapterGlobal the adapter's global scope
143     * @return a Runnable that when invoked restores the previous global
144     */
145    public static Runnable setGlobal(final ScriptObject adapterGlobal) {
146        final Global currentGlobal = Context.getGlobal();
147        if (adapterGlobal != currentGlobal) {
148            Context.setGlobal(adapterGlobal);
149            return ()->Context.setGlobal(currentGlobal);
150        }
151        return ()->{};
152    }
153
154    /**
155     * Get the current non-null global scope
156     * @return the current global scope
157     * @throws NullPointerException if the current global scope is null.
158     */
159    public static ScriptObject getNonNullGlobal() {
160        return Objects.requireNonNull(Context.getGlobal(), "Current global is null");
161    }
162
163    /**
164     * Returns true if the object has its own toString function. Used
165     * when implementing toString for adapters. Since every JS Object has a
166     * toString function, we only override "String toString()" in adapters if
167     * it is explicitly specified and not inherited from a prototype.
168     * @param sobj the object
169     * @return true if the object has its own toString function.
170     */
171    public static boolean hasOwnToString(final ScriptObject sobj) {
172        // NOTE: we could just use ScriptObject.hasOwnProperty("toString"), but
173        // its logic is more complex and this is what it boils down to with a
174        // fixed "toString" argument.
175        return sobj.getMap().findProperty("toString") != null;
176    }
177
178    /**
179     * Delegate to {@link Bootstrap#bootstrap(Lookup, String, MethodType, int)}.
180     * @param lookup MethodHandle lookup.
181     * @param opDesc Dynalink dynamic operation descriptor.
182     * @param type   Method type.
183     * @param flags  flags for call type, trace/profile etc.
184     * @return CallSite with MethodHandle to appropriate method or null if not found.
185     */
186    public static CallSite bootstrap(final Lookup lookup, final String opDesc, final MethodType type, final int flags) {
187        return Bootstrap.bootstrap(lookup, opDesc, type, flags);
188    }
189
190    static void setClassOverrides(final ScriptObject overrides) {
191        classOverrides.set(overrides);
192    }
193
194    private static MethodHandle createNoPermissionsInvoker() {
195        final String className = "NoPermissionsInvoker";
196
197        final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
198        cw.visit(Opcodes.V1_7, ACC_PUBLIC | ACC_SUPER | ACC_FINAL, className, null, "java/lang/Object", null);
199        final Type objectType = Type.getType(Object.class);
200        final Type methodHandleType = Type.getType(MethodHandle.class);
201        final InstructionAdapter mv = new InstructionAdapter(cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "invoke",
202                Type.getMethodDescriptor(Type.VOID_TYPE, methodHandleType, objectType), null, null));
203        mv.visitCode();
204        mv.visitVarInsn(ALOAD, 0);
205        mv.visitVarInsn(ALOAD, 1);
206        mv.invokevirtual(methodHandleType.getInternalName(), "invokeExact", Type.getMethodDescriptor(
207                Type.VOID_TYPE, objectType), false);
208        mv.visitInsn(RETURN);
209        mv.visitMaxs(0, 0);
210        mv.visitEnd();
211        cw.visitEnd();
212        final byte[] bytes = cw.toByteArray();
213
214        final ClassLoader loader = AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
215            @Override
216            public ClassLoader run() {
217                return new SecureClassLoader(null) {
218                    @Override
219                    protected Class<?> findClass(final String name) throws ClassNotFoundException {
220                        if(name.equals(className)) {
221                            return defineClass(name, bytes, 0, bytes.length, new ProtectionDomain(
222                                    new CodeSource(null, (CodeSigner[])null), new Permissions()));
223                        }
224                        throw new ClassNotFoundException(name);
225                    }
226                };
227            }
228        });
229
230        try {
231            return MethodHandles.publicLookup().findStatic(Class.forName(className, true, loader), "invoke",
232                    MethodType.methodType(void.class, MethodHandle.class, Object.class));
233        } catch(final ReflectiveOperationException e) {
234            throw new AssertionError(e.getMessage(), e);
235        }
236    }
237
238    /**
239     * Invoked when returning Object from an adapted method to filter out internal Nashorn objects that must not be seen
240     * by the callers. Currently only transforms {@code ConsString} into {@code String} and transforms {@code ScriptObject} into {@code ScriptObjectMirror}.
241     * @param obj the return value
242     * @return the filtered return value.
243     */
244    public static Object exportReturnValue(final Object obj) {
245        return NashornBeansLinker.exportArgument(obj, true);
246    }
247
248    /**
249     * Invoked to convert a return value of a delegate function to primitive char. There's no suitable conversion in
250     * {@code JSType}, so we provide our own to adapters.
251     * @param obj the return value.
252     * @return the character value of the return value
253     */
254    public static char toCharPrimitive(final Object obj) {
255        return JavaArgumentConverters.toCharPrimitive(obj);
256    }
257
258    /**
259     * Returns a new {@link RuntimeException} wrapping the passed throwable.
260     * Makes generated bytecode smaller by doing an INVOKESTATIC to this method
261     * rather than the NEW/DUP_X1/SWAP/INVOKESPECIAL &lt;init&gt; sequence.
262     * @param t the original throwable to wrap
263     * @return a newly created runtime exception wrapping the passed throwable.
264     */
265    public static RuntimeException wrapThrowable(final Throwable t) {
266        return new RuntimeException(t);
267    }
268
269    /**
270     * Creates and returns a new {@link UnsupportedOperationException}. Makes
271     * generated bytecode smaller by doing INVOKESTATIC to this method rather
272     * than the NEW/DUP/INVOKESPECIAL &lt;init&gt; sequence.
273     * @return a newly created {@link UnsupportedOperationException}.
274     */
275    public static UnsupportedOperationException unsupported() {
276        return new UnsupportedOperationException();
277    }
278
279    /**
280     * A bootstrap method used to collect invocation arguments into an Object array.
281     * for variable arity invocation.
282     * @param lookup the adapter's lookup (not used).
283     * @param name the call site name (not used).
284     * @param type the method type
285     * @return a method that takes the input parameters and packs them into a
286     * newly allocated Object array.
287     */
288    public static CallSite createArrayBootstrap(final MethodHandles.Lookup lookup, final String name, final MethodType type) {
289        return new ConstantCallSite(
290                MethodHandles.identity(Object[].class)
291                .asCollector(Object[].class, type.parameterCount())
292                .asType(type));
293    }
294}
295