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