blob: 08e4682181d1229d32450cba896ff3d7ec44a8e9 [file] [log] [blame]
/*
* 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.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
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 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.internal.runtime.Context;
import jdk.nashorn.internal.runtime.ScriptFunction;
import jdk.nashorn.internal.runtime.ScriptObject;
import jdk.nashorn.internal.runtime.ScriptRuntime;
import jdk.nashorn.internal.runtime.Undefined;
/**
* 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 JS script function, binds it to null JS "this", and adapts its parameter types, return types, and arity
* to the specified type and arity. This method is public mainly for implementation reasons, so the adapter classes
* can invoke it from their constructors that take a ScriptFunction in its first argument to obtain the method
* handles for their abstract method implementations.
* @param fn the script function
* @param type the method type it has to conform to
* @return the appropriately adapted method handle for invoking the script function.
*/
public static MethodHandle getHandle(final ScriptFunction fn, final MethodType type) {
// JS "this" will be global object or undefined depending on if 'fn' is strict or not
return bindAndAdaptHandle(fn, fn.isStrict()? ScriptRuntime.UNDEFINED : Context.getGlobal(), type);
}
/**
* Given a JS script object, retrieves a function from it by name, binds it to the script object as its "this", and
* adapts its parameter types, return types, and arity to the specified type and arity. This method is public mainly
* for implementation reasons, so the adapter classes can invoke it from their constructors that take a Object
* in its first argument to obtain the method handles for their method implementations.
* @param obj the script obj
* @param name the name of the property that contains the function
* @param type the method type it has to conform to
* @return the appropriately adapted method handle for invoking the script function, or null if the value of the
* property is either null or undefined, or "toString" was requested as the name, but the object doesn't directly
* define it but just inherits it through prototype.
*/
public static MethodHandle getHandle(final Object obj, final String name, final MethodType type) {
if (! (obj instanceof ScriptObject)) {
throw typeError("not.an.object", ScriptRuntime.safeToString(obj));
}
final ScriptObject sobj = (ScriptObject)obj;
// Since every JS Object has a toString, we only override "String toString()" it if it's explicitly specified
if ("toString".equals(name) && !sobj.hasOwnProperty("toString")) {
return null;
}
final Object fnObj = sobj.get(name);
if (fnObj instanceof ScriptFunction) {
return bindAndAdaptHandle((ScriptFunction)fnObj, sobj, type);
} else if(fnObj == null || fnObj instanceof Undefined) {
return null;
} else {
throw typeError("not.a.function", name);
}
}
/**
* 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 Object getClassOverrides() {
final Object 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
* @param global the global scope
*/
public static void setGlobal(final Object global) {
Context.setGlobal((ScriptObject)global);
}
/**
* Get the current global scope
* @return the current global scope
*/
public static Object getGlobal() {
return Context.getGlobal();
}
static void setClassOverrides(final ScriptObject overrides) {
classOverrides.set(overrides);
}
private static MethodHandle bindAndAdaptHandle(final ScriptFunction fn, final Object self, final MethodType type) {
return Bootstrap.getLinkerServices().asType(ScriptObject.pairArguments(fn.getBoundInvokeHandle(self), type, false), type);
}
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.lookup().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);
}
}
/**
* Returns a method handle used to convert a return value from a delegate method (always Object) to the expected
* Java return type.
* @param returnType the return type
* @return the converter for the expected return type
*/
public static MethodHandle getObjectConverter(final Class<?> returnType) {
return Bootstrap.getLinkerServices().getTypeConverter(Object.class, returnType);
}
/**
* 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);
}
/**
* Invoked to convert a return value of a delegate function to String. It is similar to
* {@code JSType.toString(Object)}, except it doesn't handle StaticClass specially, and it returns null for null
* input instead of the string "null".
* @param obj the return value.
* @return the String value of the return value
*/
public static String toString(final Object obj) {
return JavaArgumentConverters.toString(obj);
}
}