| /* |
| * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| package jdk.nashorn.internal.runtime.linker; |
| |
| import static jdk.internal.org.objectweb.asm.Opcodes.ACC_FINAL; |
| import static jdk.internal.org.objectweb.asm.Opcodes.ACC_PUBLIC; |
| import static jdk.internal.org.objectweb.asm.Opcodes.ACC_STATIC; |
| import static jdk.internal.org.objectweb.asm.Opcodes.ACC_SUPER; |
| import static jdk.internal.org.objectweb.asm.Opcodes.ALOAD; |
| import static jdk.internal.org.objectweb.asm.Opcodes.RETURN; |
| import static jdk.nashorn.internal.runtime.ECMAErrors.typeError; |
| |
| import java.lang.invoke.CallSite; |
| import java.lang.invoke.ConstantCallSite; |
| import java.lang.invoke.MethodHandle; |
| import java.lang.invoke.MethodHandles; |
| import java.lang.invoke.MethodHandles.Lookup; |
| import java.lang.invoke.MethodType; |
| import java.lang.reflect.Field; |
| import java.security.AccessController; |
| import java.security.CodeSigner; |
| import java.security.CodeSource; |
| import java.security.Permissions; |
| import java.security.PrivilegedAction; |
| import java.security.ProtectionDomain; |
| import java.security.SecureClassLoader; |
| import java.util.Objects; |
| import jdk.internal.org.objectweb.asm.ClassWriter; |
| import jdk.internal.org.objectweb.asm.Opcodes; |
| import jdk.internal.org.objectweb.asm.Type; |
| import jdk.internal.org.objectweb.asm.commons.InstructionAdapter; |
| import jdk.nashorn.api.scripting.ScriptObjectMirror; |
| import jdk.nashorn.internal.objects.Global; |
| import jdk.nashorn.internal.runtime.Context; |
| import jdk.nashorn.internal.runtime.ECMAException; |
| import jdk.nashorn.internal.runtime.JSType; |
| import jdk.nashorn.internal.runtime.ScriptFunction; |
| import jdk.nashorn.internal.runtime.ScriptObject; |
| import jdk.nashorn.internal.runtime.ScriptRuntime; |
| |
| /** |
| * Provides static utility services to generated Java adapter classes. |
| */ |
| public final class JavaAdapterServices { |
| private static final ThreadLocal<ScriptObject> classOverrides = new ThreadLocal<>(); |
| private static final MethodHandle NO_PERMISSIONS_INVOKER = createNoPermissionsInvoker(); |
| |
| private JavaAdapterServices() { |
| } |
| |
| /** |
| * Given a script function used as a delegate for a SAM adapter, figure out |
| * the right object to use as its "this" when called. |
| * @param delegate the delegate function |
| * @param global the current global of the adapter |
| * @return either the passed global, or UNDEFINED if the function is strict. |
| */ |
| public static Object getCallThis(final ScriptFunction delegate, final Object global) { |
| return delegate.isStrict() ? ScriptRuntime.UNDEFINED : global; |
| } |
| |
| /** |
| * Throws a "not.an.object" type error. Used when the delegate passed to the |
| * adapter constructor is not a script object. |
| * @param obj the object that is not a script object. |
| */ |
| public static void notAnObject(final Object obj) { |
| throw typeError("not.an.object", ScriptRuntime.safeToString(obj)); |
| } |
| |
| /** |
| * Checks if the passed object, which is supposed to be a callee retrieved |
| * through applying the GET_METHOD_PROPERTY operation on the delegate, is |
| * a ScriptFunction, or null or undefined. These are the only allowed values |
| * for adapter method implementations, so in case it is neither, it throws |
| * a type error. Note that this restriction is somewhat artificial; as the |
| * CALL dynamic operation could invoke any Nashorn callable. We are |
| * restricting adapters to actual ScriptFunction objects for now though. |
| * @param callee the callee to check |
| * @param name the name of the function |
| * @return the callee cast to a ScriptFunction, or null if it was null or undefined. |
| * @throws ECMAException representing a JS TypeError with "not.a.function" |
| * message if the passed callee is neither a script function, nor null, nor |
| * undefined. |
| */ |
| public static ScriptFunction checkFunction(final Object callee, final String name) { |
| if (callee instanceof ScriptFunction) { |
| return (ScriptFunction)callee; |
| } else if (JSType.nullOrUndefined(callee)) { |
| return null; |
| } |
| throw typeError("not.a.function.value", name, ScriptRuntime.safeToString(callee)); |
| } |
| |
| /** |
| * Returns a thread-local JS object used to define methods for the adapter class being initialized on the current |
| * thread. This method is public solely for implementation reasons, so the adapter classes can invoke it from their |
| * static initializers. |
| * @return the thread-local JS object used to define methods for the class being initialized. |
| */ |
| public static ScriptObject getClassOverrides() { |
| final ScriptObject overrides = classOverrides.get(); |
| assert overrides != null; |
| return overrides; |
| } |
| |
| /** |
| * Takes a method handle and an argument to it, and invokes the method handle passing it the argument. Basically |
| * equivalent to {@code method.invokeExact(arg)}, except that the method handle will be invoked in a protection |
| * domain with absolutely no permissions. |
| * @param method the method handle to invoke. The handle must have the exact type of {@code void(Object)}. |
| * @param arg the argument to pass to the handle. |
| * @throws Throwable if anything goes wrong. |
| */ |
| public static void invokeNoPermissions(final MethodHandle method, final Object arg) throws Throwable { |
| NO_PERMISSIONS_INVOKER.invokeExact(method, arg); |
| } |
| |
| /** |
| * Set the current global scope to that of the adapter global |
| * @param adapterGlobal the adapter's global scope |
| * @return a Runnable that when invoked restores the previous global |
| */ |
| public static Runnable setGlobal(final ScriptObject adapterGlobal) { |
| final Global currentGlobal = Context.getGlobal(); |
| if (adapterGlobal != currentGlobal) { |
| Context.setGlobal(adapterGlobal); |
| return ()->Context.setGlobal(currentGlobal); |
| } |
| return ()->{}; |
| } |
| |
| /** |
| * Get the current non-null global scope |
| * @return the current global scope |
| * @throws NullPointerException if the current global scope is null. |
| */ |
| public static ScriptObject getNonNullGlobal() { |
| return Objects.requireNonNull(Context.getGlobal(), "Current global is null"); |
| } |
| |
| /** |
| * Returns true if the object has its own toString function. Used |
| * when implementing toString for adapters. Since every JS Object has a |
| * toString function, we only override "String toString()" in adapters if |
| * it is explicitly specified and not inherited from a prototype. |
| * @param sobj the object |
| * @return true if the object has its own toString function. |
| */ |
| public static boolean hasOwnToString(final ScriptObject sobj) { |
| // NOTE: we could just use ScriptObject.hasOwnProperty("toString"), but |
| // its logic is more complex and this is what it boils down to with a |
| // fixed "toString" argument. |
| return sobj.getMap().findProperty("toString") != null; |
| } |
| |
| /** |
| * Returns the ScriptObject or Global field value from a ScriptObjectMirror using reflection. |
| * |
| * @param mirror the mirror object |
| * @param getGlobal true if we want the global object, false to return the script object |
| * @return the script object or global object |
| */ |
| public static ScriptObject unwrapMirror(final Object mirror, final boolean getGlobal) { |
| assert mirror instanceof ScriptObjectMirror; |
| try { |
| final Field field = getGlobal ? MirrorFieldHolder.GLOBAL_FIELD : MirrorFieldHolder.SOBJ_FIELD; |
| return (ScriptObject) field.get(mirror); |
| } catch (final IllegalAccessException x) { |
| throw new RuntimeException(x); |
| } |
| } |
| |
| /** |
| * Delegate to {@link Bootstrap#bootstrap(Lookup, String, MethodType, int)}. |
| * @param lookup MethodHandle lookup. |
| * @param opDesc Dynalink dynamic operation descriptor. |
| * @param type Method type. |
| * @param flags flags for call type, trace/profile etc. |
| * @return CallSite with MethodHandle to appropriate method or null if not found. |
| */ |
| public static CallSite bootstrap(final Lookup lookup, final String opDesc, final MethodType type, final int flags) { |
| return Bootstrap.bootstrap(lookup, opDesc, type, flags); |
| } |
| |
| static void setClassOverrides(final ScriptObject overrides) { |
| classOverrides.set(overrides); |
| } |
| |
| private static MethodHandle createNoPermissionsInvoker() { |
| final String className = "NoPermissionsInvoker"; |
| |
| final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); |
| cw.visit(Opcodes.V1_7, ACC_PUBLIC | ACC_SUPER | ACC_FINAL, className, null, "java/lang/Object", null); |
| final Type objectType = Type.getType(Object.class); |
| final Type methodHandleType = Type.getType(MethodHandle.class); |
| final InstructionAdapter mv = new InstructionAdapter(cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "invoke", |
| Type.getMethodDescriptor(Type.VOID_TYPE, methodHandleType, objectType), null, null)); |
| mv.visitCode(); |
| mv.visitVarInsn(ALOAD, 0); |
| mv.visitVarInsn(ALOAD, 1); |
| mv.invokevirtual(methodHandleType.getInternalName(), "invokeExact", Type.getMethodDescriptor( |
| Type.VOID_TYPE, objectType), false); |
| mv.visitInsn(RETURN); |
| mv.visitMaxs(0, 0); |
| mv.visitEnd(); |
| cw.visitEnd(); |
| final byte[] bytes = cw.toByteArray(); |
| |
| final ClassLoader loader = AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() { |
| @Override |
| public ClassLoader run() { |
| return new SecureClassLoader(null) { |
| @Override |
| protected Class<?> findClass(final String name) throws ClassNotFoundException { |
| if(name.equals(className)) { |
| return defineClass(name, bytes, 0, bytes.length, new ProtectionDomain( |
| new CodeSource(null, (CodeSigner[])null), new Permissions())); |
| } |
| throw new ClassNotFoundException(name); |
| } |
| }; |
| } |
| }); |
| |
| try { |
| return MethodHandles.publicLookup().findStatic(Class.forName(className, true, loader), "invoke", |
| MethodType.methodType(void.class, MethodHandle.class, Object.class)); |
| } catch(final ReflectiveOperationException e) { |
| throw new AssertionError(e.getMessage(), e); |
| } |
| } |
| |
| /** |
| * Invoked when returning Object from an adapted method to filter out internal Nashorn objects that must not be seen |
| * by the callers. Currently only transforms {@code ConsString} into {@code String} and transforms {@code ScriptObject} into {@code ScriptObjectMirror}. |
| * @param obj the return value |
| * @return the filtered return value. |
| */ |
| public static Object exportReturnValue(final Object obj) { |
| return NashornBeansLinker.exportArgument(obj, true); |
| } |
| |
| /** |
| * Invoked to convert a return value of a delegate function to primitive char. There's no suitable conversion in |
| * {@code JSType}, so we provide our own to adapters. |
| * @param obj the return value. |
| * @return the character value of the return value |
| */ |
| public static char toCharPrimitive(final Object obj) { |
| return JavaArgumentConverters.toCharPrimitive(obj); |
| } |
| |
| /** |
| * Returns a new {@link RuntimeException} wrapping the passed throwable. |
| * Makes generated bytecode smaller by doing an INVOKESTATIC to this method |
| * rather than the NEW/DUP_X1/SWAP/INVOKESPECIAL <init> sequence. |
| * @param t the original throwable to wrap |
| * @return a newly created runtime exception wrapping the passed throwable. |
| */ |
| public static RuntimeException wrapThrowable(final Throwable t) { |
| return new RuntimeException(t); |
| } |
| |
| /** |
| * Creates and returns a new {@link UnsupportedOperationException}. Makes |
| * generated bytecode smaller by doing INVOKESTATIC to this method rather |
| * than the NEW/DUP/INVOKESPECIAL <init> sequence. |
| * @return a newly created {@link UnsupportedOperationException}. |
| */ |
| public static UnsupportedOperationException unsupported() { |
| return new UnsupportedOperationException(); |
| } |
| |
| /** |
| * A bootstrap method used to collect invocation arguments into an Object array. |
| * for variable arity invocation. |
| * @param lookup the adapter's lookup (not used). |
| * @param name the call site name (not used). |
| * @param type the method type |
| * @return a method that takes the input parameters and packs them into a |
| * newly allocated Object array. |
| */ |
| public static CallSite createArrayBootstrap(final MethodHandles.Lookup lookup, final String name, final MethodType type) { |
| return new ConstantCallSite( |
| MethodHandles.identity(Object[].class) |
| .asCollector(Object[].class, type.parameterCount()) |
| .asType(type)); |
| } |
| |
| // Initialization on demand holder for accessible ScriptObjectMirror fields |
| private static class MirrorFieldHolder { |
| |
| private static final Field SOBJ_FIELD = getMirrorField("sobj"); |
| private static final Field GLOBAL_FIELD = getMirrorField("global"); |
| |
| private static Field getMirrorField(final String fieldName) { |
| try { |
| final Field field = ScriptObjectMirror.class.getDeclaredField(fieldName); |
| AccessController.doPrivileged((PrivilegedAction<Void>) () -> { |
| field.setAccessible(true); |
| return null; |
| }); |
| return field; |
| } catch (final NoSuchFieldException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| } |
| } |