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 
  26 package jdk.nashorn.internal.runtime.linker;
  27 
  28 import static jdk.internal.org.objectweb.asm.Opcodes.ACC_FINAL;
  29 import static jdk.internal.org.objectweb.asm.Opcodes.ACC_PUBLIC;
  30 import static jdk.internal.org.objectweb.asm.Opcodes.ACC_STATIC;
  31 import static jdk.internal.org.objectweb.asm.Opcodes.ACC_SUPER;
  32 import static jdk.internal.org.objectweb.asm.Opcodes.ALOAD;
  33 import static jdk.internal.org.objectweb.asm.Opcodes.RETURN;
  34 import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
  35 
  36 import java.lang.invoke.CallSite;
  37 import java.lang.invoke.ConstantCallSite;
  38 import java.lang.invoke.MethodHandle;
  39 import java.lang.invoke.MethodHandles;
  40 import java.lang.invoke.MethodHandles.Lookup;
  41 import java.lang.invoke.MethodType;
  42 import java.lang.reflect.Field;
  43 import java.security.AccessController;
  44 import java.security.CodeSigner;
  45 import java.security.CodeSource;
  46 import java.security.Permissions;
  47 import java.security.PrivilegedAction;
  48 import java.security.ProtectionDomain;
  49 import java.security.SecureClassLoader;
  50 import java.util.Objects;
  51 import jdk.internal.org.objectweb.asm.ClassWriter;
  52 import jdk.internal.org.objectweb.asm.Opcodes;
  53 import jdk.internal.org.objectweb.asm.Type;
  54 import jdk.internal.org.objectweb.asm.commons.InstructionAdapter;
  55 import jdk.nashorn.api.scripting.ScriptObjectMirror;
  56 import jdk.nashorn.internal.objects.Global;
  57 import jdk.nashorn.internal.runtime.Context;
  58 import jdk.nashorn.internal.runtime.ECMAException;
  59 import jdk.nashorn.internal.runtime.JSType;
  60 import jdk.nashorn.internal.runtime.ScriptFunction;
  61 import jdk.nashorn.internal.runtime.ScriptObject;
  62 import jdk.nashorn.internal.runtime.ScriptRuntime;
  63 
  64 /**
  65  * Provides static utility services to generated Java adapter classes.
  66  */
  67 public 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 }