JavaAdapterServices.java revision 1819:4f90f5ae2b4a
118579Sfenner/*
2100787Sfenner * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
318579Sfenner * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
418579Sfenner *
518579Sfenner * This code is free software; you can redistribute it and/or modify it
618579Sfenner * under the terms of the GNU General Public License version 2 only, as
718579Sfenner * published by the Free Software Foundation.  Oracle designates this
818579Sfenner * particular file as subject to the "Classpath" exception as provided
918579Sfenner * by Oracle in the LICENSE file that accompanied this code.
1018579Sfenner *
1118579Sfenner * This code is distributed in the hope that it will be useful, but WITHOUT
1218579Sfenner * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
1318579Sfenner * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
1418579Sfenner * version 2 for more details (a copy is included in the LICENSE file that
1518579Sfenner * accompanied this code).
1618579Sfenner *
1718579Sfenner * You should have received a copy of the GNU General Public License version
1818579Sfenner * 2 along with this work; if not, write to the Free Software Foundation,
1918579Sfenner * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
2018579Sfenner *
2118579Sfenner * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
2218579Sfenner * or visit www.oracle.com if you need additional information or have any
2318579Sfenner * questions.
24100787Sfenner */
2518579Sfenner
2658835Sshinpackage jdk.nashorn.internal.runtime.linker;
2718579Sfenner
28100787Sfennerimport static jdk.internal.org.objectweb.asm.Opcodes.ACC_FINAL;
2918579Sfennerimport static jdk.internal.org.objectweb.asm.Opcodes.ACC_PUBLIC;
3058835Sshinimport static jdk.internal.org.objectweb.asm.Opcodes.ACC_STATIC;
3158835Sshinimport static jdk.internal.org.objectweb.asm.Opcodes.ACC_SUPER;
3258835Sshinimport static jdk.internal.org.objectweb.asm.Opcodes.ALOAD;
3318579Sfennerimport static jdk.internal.org.objectweb.asm.Opcodes.RETURN;
3418579Sfennerimport static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
3518579Sfenner
3618579Sfennerimport java.lang.invoke.CallSite;
3718579Sfennerimport java.lang.invoke.ConstantCallSite;
3818579Sfennerimport java.lang.invoke.MethodHandle;
3918579Sfennerimport java.lang.invoke.MethodHandles;
4018579Sfennerimport java.lang.invoke.MethodHandles.Lookup;
4118579Sfennerimport java.lang.invoke.MethodType;
4218579Sfennerimport java.lang.reflect.Field;
4377816Sruimport java.security.AccessController;
4477816Sruimport java.security.CodeSigner;
4577816Sruimport java.security.CodeSource;
4618579Sfennerimport java.security.Permissions;
4718579Sfennerimport java.security.PrivilegedAction;
4818579Sfennerimport java.security.ProtectionDomain;
4918579Sfennerimport java.security.SecureClassLoader;
5018579Sfennerimport java.util.Objects;
5118579Sfennerimport jdk.internal.org.objectweb.asm.ClassWriter;
5218579Sfennerimport jdk.internal.org.objectweb.asm.Opcodes;
5318579Sfennerimport jdk.internal.org.objectweb.asm.Type;
5418579Sfennerimport jdk.internal.org.objectweb.asm.commons.InstructionAdapter;
5518579Sfennerimport jdk.nashorn.api.scripting.ScriptObjectMirror;
5618579Sfennerimport jdk.nashorn.internal.objects.Global;
5718579Sfennerimport jdk.nashorn.internal.runtime.Context;
5818579Sfennerimport jdk.nashorn.internal.runtime.ECMAException;
5918579Sfennerimport jdk.nashorn.internal.runtime.JSType;
6077816Sruimport jdk.nashorn.internal.runtime.ScriptFunction;
6118579Sfennerimport jdk.nashorn.internal.runtime.ScriptObject;
6218579Sfennerimport jdk.nashorn.internal.runtime.ScriptRuntime;
6318579Sfenner
6418579Sfenner/**
6518579Sfenner * Provides static utility services to generated Java adapter classes.
6618579Sfenner */
6718579Sfennerpublic final class JavaAdapterServices {
6818579Sfenner    private static final ThreadLocal<ScriptObject> classOverrides = new ThreadLocal<>();
6918579Sfenner    private static final MethodHandle NO_PERMISSIONS_INVOKER = createNoPermissionsInvoker();
7018579Sfenner
7118579Sfenner    private JavaAdapterServices() {
7218579Sfenner    }
7318579Sfenner
7418579Sfenner    /**
7518579Sfenner     * Given a script function used as a delegate for a SAM adapter, figure out
7618579Sfenner     * the right object to use as its "this" when called.
7718579Sfenner     * @param delegate the delegate function
7818579Sfenner     * @param global the current global of the adapter
7918579Sfenner     * @return either the passed global, or UNDEFINED if the function is strict.
8077816Sru     */
8118579Sfenner    public static Object getCallThis(final ScriptFunction delegate, final Object global) {
8218579Sfenner        return delegate.isStrict() ? ScriptRuntime.UNDEFINED : global;
8318579Sfenner    }
8418579Sfenner
8518579Sfenner    /**
8618579Sfenner     * Throws a "not.an.object" type error. Used when the delegate passed to the
8718579Sfenner     * adapter constructor is not a script object.
8818579Sfenner     * @param obj the object that is not a script object.
8918579Sfenner     */
9018579Sfenner    public static void notAnObject(final Object obj) {
9118579Sfenner        throw typeError("not.an.object", ScriptRuntime.safeToString(obj));
9218579Sfenner    }
9318579Sfenner
9418579Sfenner    /**
9518579Sfenner     * Checks if the passed object, which is supposed to be a callee retrieved
9618579Sfenner     * through applying the GET_METHOD_PROPERTY operation on the delegate, is
9718579Sfenner     * a ScriptFunction, or null or undefined. These are the only allowed values
9818579Sfenner     * for adapter method implementations, so in case it is neither, it throws
9918579Sfenner     * a type error. Note that this restriction is somewhat artificial; as the
10018579Sfenner     * CALL dynamic operation could invoke any Nashorn callable. We are
10118579Sfenner     * restricting adapters to actual ScriptFunction objects for now though.
10218579Sfenner     * @param callee the callee to check
10318579Sfenner     * @param name the name of the function
10418579Sfenner     * @return the callee cast to a ScriptFunction, or null if it was null or undefined.
10518579Sfenner     * @throws ECMAException representing a JS TypeError with "not.a.function"
10618579Sfenner     * message if the passed callee is neither a script function, nor null, nor
10718579Sfenner     * undefined.
10818579Sfenner     */
10918579Sfenner    public static ScriptFunction checkFunction(final Object callee, final String name) {
11018579Sfenner        if (callee instanceof ScriptFunction) {
11118579Sfenner            return (ScriptFunction)callee;
11218579Sfenner        } else if (JSType.nullOrUndefined(callee)) {
11318579Sfenner            return null;
11418579Sfenner        }
11518579Sfenner        throw typeError("not.a.function.value", name, ScriptRuntime.safeToString(callee));
11618579Sfenner    }
11718579Sfenner
11818579Sfenner    /**
11918579Sfenner     * Returns a thread-local JS object used to define methods for the adapter class being initialized on the current
12018579Sfenner     * thread. This method is public solely for implementation reasons, so the adapter classes can invoke it from their
12118579Sfenner     * static initializers.
12218579Sfenner     * @return the thread-local JS object used to define methods for the class being initialized.
12318579Sfenner     */
12418579Sfenner    public static ScriptObject getClassOverrides() {
12518579Sfenner        final ScriptObject overrides = classOverrides.get();
12618579Sfenner        assert overrides != null;
12718579Sfenner        return overrides;
12818579Sfenner    }
12918579Sfenner
13018579Sfenner    /**
13118579Sfenner     * Takes a method handle and an argument to it, and invokes the method handle passing it the argument. Basically
13218579Sfenner     * equivalent to {@code method.invokeExact(arg)}, except that the method handle will be invoked in a protection
13318579Sfenner     * domain with absolutely no permissions.
13418579Sfenner     * @param method the method handle to invoke. The handle must have the exact type of {@code void(Object)}.
13518579Sfenner     * @param arg the argument to pass to the handle.
13618579Sfenner     * @throws Throwable if anything goes wrong.
13718579Sfenner     */
13818579Sfenner    public static void invokeNoPermissions(final MethodHandle method, final Object arg) throws Throwable {
13918579Sfenner        NO_PERMISSIONS_INVOKER.invokeExact(method, arg);
14018579Sfenner    }
14118579Sfenner
14218579Sfenner    /**
14318579Sfenner     * Set the current global scope to that of the adapter global
14418579Sfenner     * @param adapterGlobal the adapter's global scope
14518579Sfenner     * @return a Runnable that when invoked restores the previous global
14618579Sfenner     */
14718579Sfenner    public static Runnable setGlobal(final ScriptObject adapterGlobal) {
14818579Sfenner        final Global currentGlobal = Context.getGlobal();
14918579Sfenner        if (adapterGlobal != currentGlobal) {
15018579Sfenner            Context.setGlobal(adapterGlobal);
15118579Sfenner            return ()->Context.setGlobal(currentGlobal);
15218579Sfenner        }
15318579Sfenner        return ()->{};
15418579Sfenner    }
15518579Sfenner
15618579Sfenner    /**
15718579Sfenner     * Get the current non-null global scope
15818579Sfenner     * @return the current global scope
15918579Sfenner     * @throws NullPointerException if the current global scope is null.
16018579Sfenner     */
16118579Sfenner    public static ScriptObject getNonNullGlobal() {
16218579Sfenner        return Objects.requireNonNull(Context.getGlobal(), "Current global is null");
16318579Sfenner    }
16418579Sfenner
16518579Sfenner    /**
16618579Sfenner     * Returns true if the object has its own toString function. Used
16718579Sfenner     * when implementing toString for adapters. Since every JS Object has a
16818579Sfenner     * toString function, we only override "String toString()" in adapters if
16918579Sfenner     * it is explicitly specified and not inherited from a prototype.
17018579Sfenner     * @param sobj the object
17118579Sfenner     * @return true if the object has its own toString function.
17218579Sfenner     */
17318579Sfenner    public static boolean hasOwnToString(final ScriptObject sobj) {
17418579Sfenner        // NOTE: we could just use ScriptObject.hasOwnProperty("toString"), but
17518579Sfenner        // its logic is more complex and this is what it boils down to with a
17618579Sfenner        // fixed "toString" argument.
17718579Sfenner        return sobj.getMap().findProperty("toString") != null;
17818579Sfenner    }
17918579Sfenner
18018579Sfenner    /**
18118579Sfenner     * Returns the ScriptObject or Global field value from a ScriptObjectMirror using reflection.
18218579Sfenner     *
18318579Sfenner     * @param mirror the mirror object
18418579Sfenner     * @param getGlobal true if we want the global object, false to return the script object
18518579Sfenner     * @return the script object or global object
18618579Sfenner     */
18718579Sfenner    public static ScriptObject unwrapMirror(final Object mirror, final boolean getGlobal) {
18818579Sfenner        assert mirror instanceof ScriptObjectMirror;
18918579Sfenner        try {
19018579Sfenner            final Field field = getGlobal ? MirrorFieldHolder.GLOBAL_FIELD : MirrorFieldHolder.SOBJ_FIELD;
19118579Sfenner            return (ScriptObject) field.get(mirror);
19218579Sfenner        } catch (final IllegalAccessException x) {
19318579Sfenner            throw new RuntimeException(x);
19418579Sfenner        }
19518579Sfenner    }
19618579Sfenner
19718579Sfenner    /**
19818579Sfenner     * Delegate to {@link Bootstrap#bootstrap(Lookup, String, MethodType, int)}.
19918579Sfenner     * @param lookup MethodHandle lookup.
20018579Sfenner     * @param opDesc Dynalink dynamic operation descriptor.
20118579Sfenner     * @param type   Method type.
20218579Sfenner     * @param flags  flags for call type, trace/profile etc.
20318579Sfenner     * @return CallSite with MethodHandle to appropriate method or null if not found.
20418579Sfenner     */
20518579Sfenner    public static CallSite bootstrap(final Lookup lookup, final String opDesc, final MethodType type, final int flags) {
20618579Sfenner        return Bootstrap.bootstrap(lookup, opDesc, type, flags);
20718579Sfenner    }
20818579Sfenner
20918579Sfenner    static void setClassOverrides(final ScriptObject overrides) {
21018579Sfenner        classOverrides.set(overrides);
21118579Sfenner    }
21277816Sru
21377816Sru    private static MethodHandle createNoPermissionsInvoker() {
21477816Sru        final String className = "NoPermissionsInvoker";
21518579Sfenner
21618579Sfenner        final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
21718579Sfenner        cw.visit(Opcodes.V1_7, ACC_PUBLIC | ACC_SUPER | ACC_FINAL, className, null, "java/lang/Object", null);
21818579Sfenner        final Type objectType = Type.getType(Object.class);
21918579Sfenner        final Type methodHandleType = Type.getType(MethodHandle.class);
22018579Sfenner        final InstructionAdapter mv = new InstructionAdapter(cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "invoke",
22118579Sfenner                Type.getMethodDescriptor(Type.VOID_TYPE, methodHandleType, objectType), null, null));
222283808Stuexen        mv.visitCode();
22318579Sfenner        mv.visitVarInsn(ALOAD, 0);
22446542Sarchie        mv.visitVarInsn(ALOAD, 1);
225100789Sfenner        mv.invokevirtual(methodHandleType.getInternalName(), "invokeExact", Type.getMethodDescriptor(
22618579Sfenner                Type.VOID_TYPE, objectType), false);
22718579Sfenner        mv.visitInsn(RETURN);
22818579Sfenner        mv.visitMaxs(0, 0);
22958804Sshin        mv.visitEnd();
23058804Sshin        cw.visitEnd();
231171135Sgnn        final byte[] bytes = cw.toByteArray();
23258804Sshin
23358804Sshin        final ClassLoader loader = AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
23418579Sfenner            @Override
235100787Sfenner            public ClassLoader run() {
23618579Sfenner                return new SecureClassLoader(null) {
237100787Sfenner                    @Override
23818579Sfenner                    protected Class<?> findClass(final String name) throws ClassNotFoundException {
23918579Sfenner                        if(name.equals(className)) {
24018579Sfenner                            return defineClass(name, bytes, 0, bytes.length, new ProtectionDomain(
24118579Sfenner                                    new CodeSource(null, (CodeSigner[])null), new Permissions()));
24218579Sfenner                        }
24318579Sfenner                        throw new ClassNotFoundException(name);
24418579Sfenner                    }
24518579Sfenner                };
24618579Sfenner            }
24718579Sfenner        });
248100787Sfenner
249100787Sfenner        try {
250100787Sfenner            return MethodHandles.publicLookup().findStatic(Class.forName(className, true, loader), "invoke",
251100787Sfenner                    MethodType.methodType(void.class, MethodHandle.class, Object.class));
252100787Sfenner        } catch(final ReflectiveOperationException e) {
253100787Sfenner            throw new AssertionError(e.getMessage(), e);
254100787Sfenner        }
255100787Sfenner    }
256100787Sfenner
257100787Sfenner    /**
258100787Sfenner     * Invoked when returning Object from an adapted method to filter out internal Nashorn objects that must not be seen
259100787Sfenner     * by the callers. Currently only transforms {@code ConsString} into {@code String} and transforms {@code ScriptObject} into {@code ScriptObjectMirror}.
260100787Sfenner     * @param obj the return value
261176428Srpaulo     * @return the filtered return value.
262100787Sfenner     */
263100787Sfenner    public static Object exportReturnValue(final Object obj) {
26418579Sfenner        return NashornBeansLinker.exportArgument(obj, true);
26518579Sfenner    }
26618579Sfenner
26718579Sfenner    /**
26818579Sfenner     * Invoked to convert a return value of a delegate function to primitive char. There's no suitable conversion in
26918579Sfenner     * {@code JSType}, so we provide our own to adapters.
27018579Sfenner     * @param obj the return value.
27118579Sfenner     * @return the character value of the return value
27218579Sfenner     */
27318579Sfenner    public static char toCharPrimitive(final Object obj) {
27446542Sarchie        return JavaArgumentConverters.toCharPrimitive(obj);
27546542Sarchie    }
27646542Sarchie
27746542Sarchie    /**
27846542Sarchie     * Returns a new {@link RuntimeException} wrapping the passed throwable.
27946542Sarchie     * Makes generated bytecode smaller by doing an INVOKESTATIC to this method
28046542Sarchie     * rather than the NEW/DUP_X1/SWAP/INVOKESPECIAL &lt;init&gt; sequence.
28146542Sarchie     * @param t the original throwable to wrap
28246542Sarchie     * @return a newly created runtime exception wrapping the passed throwable.
28346542Sarchie     */
28446542Sarchie    public static RuntimeException wrapThrowable(final Throwable t) {
28546542Sarchie        return new RuntimeException(t);
28646542Sarchie    }
28746542Sarchie
288100787Sfenner    /**
289100787Sfenner     * Creates and returns a new {@link UnsupportedOperationException}. Makes
290100787Sfenner     * generated bytecode smaller by doing INVOKESTATIC to this method rather
291100787Sfenner     * than the NEW/DUP/INVOKESPECIAL &lt;init&gt; sequence.
292100787Sfenner     * @return a newly created {@link UnsupportedOperationException}.
293100787Sfenner     */
294100787Sfenner    public static UnsupportedOperationException unsupported() {
29518579Sfenner        return new UnsupportedOperationException();
29618579Sfenner    }
29718579Sfenner
29818579Sfenner    /**
29918579Sfenner     * A bootstrap method used to collect invocation arguments into an Object array.
30018579Sfenner     * for variable arity invocation.
30118579Sfenner     * @param lookup the adapter's lookup (not used).
302100787Sfenner     * @param name the call site name (not used).
303100787Sfenner     * @param type the method type
304100787Sfenner     * @return a method that takes the input parameters and packs them into a
305100787Sfenner     * newly allocated Object array.
306100787Sfenner     */
30746542Sarchie    public static CallSite createArrayBootstrap(final MethodHandles.Lookup lookup, final String name, final MethodType type) {
308100787Sfenner        return new ConstantCallSite(
30946542Sarchie                MethodHandles.identity(Object[].class)
31018579Sfenner                .asCollector(Object[].class, type.parameterCount())
31118579Sfenner                .asType(type));
31246542Sarchie    }
313100787Sfenner
31418579Sfenner    // Initialization on demand holder for accessible ScriptObjectMirror fields
315163387Sdwmalone    private static class MirrorFieldHolder {
316163387Sdwmalone
317163387Sdwmalone        private static final Field SOBJ_FIELD   = getMirrorField("sobj");
31818579Sfenner        private static final Field GLOBAL_FIELD = getMirrorField("global");
31918579Sfenner
32018579Sfenner        private static Field getMirrorField(final String fieldName) {
32118579Sfenner            try {
32218579Sfenner                final Field field = ScriptObjectMirror.class.getDeclaredField(fieldName);
32318579Sfenner                AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
32418579Sfenner                    field.setAccessible(true);
325100787Sfenner                    return null;
32618579Sfenner                });
32746542Sarchie                return field;
328100787Sfenner            } catch (final NoSuchFieldException e) {
32918579Sfenner                throw new RuntimeException(e);
330100787Sfenner            }
331100787Sfenner        }
33218579Sfenner    }
33318579Sfenner}
33418579Sfenner