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.dynalink.StandardNamespace.METHOD;
  29 import static jdk.dynalink.StandardOperation.GET;
  30 import static jdk.nashorn.internal.runtime.linker.JavaAdapterBytecodeGenerator.SUPER_PREFIX;
  31 
  32 import java.lang.invoke.MethodHandle;
  33 import java.lang.invoke.MethodHandles;
  34 import java.lang.invoke.MethodType;
  35 import jdk.dynalink.CallSiteDescriptor;
  36 import jdk.dynalink.Operation;
  37 import jdk.dynalink.beans.BeansLinker;
  38 import jdk.dynalink.linker.GuardedInvocation;
  39 import jdk.dynalink.linker.LinkRequest;
  40 import jdk.dynalink.linker.LinkerServices;
  41 import jdk.dynalink.linker.TypeBasedGuardingDynamicLinker;
  42 import jdk.dynalink.linker.support.Lookup;
  43 import jdk.nashorn.internal.runtime.ScriptRuntime;
  44 
  45 /**
  46  * A linker for instances of {@code JavaSuperAdapter}. Only links {@code getMethod} calls, by forwarding them to the
  47  * bean linker for the adapter class and prepending {@code super$} to method names.
  48  *
  49  */
  50 final class JavaSuperAdapterLinker implements TypeBasedGuardingDynamicLinker {
  51     private static final MethodHandle ADD_PREFIX_TO_METHOD_NAME;
  52     private static final MethodHandle BIND_DYNAMIC_METHOD;
  53     private static final MethodHandle GET_ADAPTER;
  54     private static final MethodHandle IS_ADAPTER_OF_CLASS;
  55 
  56     static {
  57         final Lookup lookup = new Lookup(MethodHandles.lookup());
  58         ADD_PREFIX_TO_METHOD_NAME = lookup.findOwnStatic("addPrefixToMethodName", Object.class, Object.class);
  59         BIND_DYNAMIC_METHOD = lookup.findOwnStatic("bindDynamicMethod", Object.class, Object.class, Object.class);
  60         GET_ADAPTER = lookup.findVirtual(JavaSuperAdapter.class, "getAdapter", MethodType.methodType(Object.class));
  61         IS_ADAPTER_OF_CLASS = lookup.findOwnStatic("isAdapterOfClass", boolean.class, Class.class, Object.class);
  62     }
  63 
  64     private static final Operation GET_METHOD = GET.withNamespace(METHOD);
  65 
  66     private final BeansLinker beansLinker;
  67 
  68     JavaSuperAdapterLinker(final BeansLinker beansLinker) {
  69         this.beansLinker = beansLinker;
  70     }
  71 
  72     @Override
  73     public boolean canLinkType(final Class<?> type) {
  74         return type == JavaSuperAdapter.class;
  75     }
  76 
  77     @Override
  78     public GuardedInvocation getGuardedInvocation(final LinkRequest linkRequest, final LinkerServices linkerServices)
  79             throws Exception {
  80         final Object objSuperAdapter = linkRequest.getReceiver();
  81         if(!(objSuperAdapter instanceof JavaSuperAdapter)) {
  82             return null;
  83         }
  84 
  85         final CallSiteDescriptor descriptor = linkRequest.getCallSiteDescriptor();
  86 
  87         if(!NashornCallSiteDescriptor.contains(descriptor, GET, METHOD)) {
  88             // We only handle GET:METHOD
  89             return null;
  90         }
  91 
  92         final Object adapter = ((JavaSuperAdapter)objSuperAdapter).getAdapter();
  93 
  94         // Replace argument (javaSuperAdapter, ...) => (adapter, ...) when delegating to BeansLinker
  95         final Object[] args = linkRequest.getArguments();
  96         args[0] = adapter;
  97 
  98         // Use R(T0, ...) => R(adapter.class, ...) call site type when delegating to BeansLinker.
  99         final MethodType type = descriptor.getMethodType();
 100         final Class<?> adapterClass = adapter.getClass();
 101         final String name = NashornCallSiteDescriptor.getOperand(descriptor);
 102         final Operation newOp = name == null ? GET_METHOD : GET_METHOD.named(SUPER_PREFIX + name);
 103 
 104         final CallSiteDescriptor newDescriptor = new CallSiteDescriptor(
 105                 NashornCallSiteDescriptor.getLookupInternal(descriptor), newOp,
 106                 type.changeParameterType(0, adapterClass));
 107 
 108         // Delegate to BeansLinker
 109         final GuardedInvocation guardedInv = NashornBeansLinker.getGuardedInvocation(
 110                 beansLinker, linkRequest.replaceArguments(newDescriptor, args),
 111                 linkerServices);
 112         // Even for non-existent methods, Bootstrap's BeansLinker will link a
 113         // noSuchMember handler.
 114         assert guardedInv != null;
 115 
 116         final MethodHandle guard = IS_ADAPTER_OF_CLASS.bindTo(adapterClass);
 117 
 118         final MethodHandle invocation = guardedInv.getInvocation();
 119         final MethodType invType = invocation.type();
 120         // For invocation typed R(T0, ...) create a dynamic method binder of type Object(R, T0)
 121         final MethodHandle typedBinder = BIND_DYNAMIC_METHOD.asType(MethodType.methodType(Object.class,
 122                 invType.returnType(), invType.parameterType(0)));
 123         // For invocation typed R(T0, T1, ...) create a dynamic method binder of type Object(R, T0, T1, ...)
 124         final MethodHandle droppingBinder = MethodHandles.dropArguments(typedBinder, 2,
 125                 invType.parameterList().subList(1, invType.parameterCount()));
 126         // Finally, fold the invocation into the binder to produce a method handle that will bind every returned
 127         // DynamicMethod object from StandardOperation.GET_METHOD calls to the actual receiver
 128         // Object(R(T0, T1, ...), T0, T1, ...)
 129         final MethodHandle bindingInvocation = MethodHandles.foldArguments(droppingBinder, invocation);
 130 
 131         final MethodHandle typedGetAdapter = asFilterType(GET_ADAPTER, 0, invType, type);
 132         final MethodHandle adaptedInvocation;
 133         if(name != null) {
 134             adaptedInvocation = MethodHandles.filterArguments(bindingInvocation, 0, typedGetAdapter);
 135         } else {
 136             // Add a filter that'll prepend "super$" to each name passed to the variable-name StandardOperation.GET_METHOD.
 137             final MethodHandle typedAddPrefix = asFilterType(ADD_PREFIX_TO_METHOD_NAME, 1, invType, type);
 138             adaptedInvocation = MethodHandles.filterArguments(bindingInvocation, 0, typedGetAdapter, typedAddPrefix);
 139         }
 140 
 141         return guardedInv.replaceMethods(adaptedInvocation, guard).asType(descriptor);
 142     }
 143 
 144     /**
 145      * Adapts the type of a method handle used as a filter in a position from a source method type to a target method type.
 146      * @param filter the filter method handle
 147      * @param pos the position in the argument list that it's filtering
 148      * @param targetType the target method type for filtering
 149      * @param sourceType the source method type for filtering
 150      * @return a type adapted filter
 151      */
 152     private static MethodHandle asFilterType(final MethodHandle filter, final int pos, final MethodType targetType, final MethodType sourceType) {
 153         return filter.asType(MethodType.methodType(targetType.parameterType(pos), sourceType.parameterType(pos)));
 154     }
 155 
 156     @SuppressWarnings("unused")
 157     private static Object addPrefixToMethodName(final Object name) {
 158         return SUPER_PREFIX.concat(String.valueOf(name));
 159     }
 160 
 161     /**
 162      * Used to transform the return value of getMethod; transform a {@code DynamicMethod} into a
 163      * {@code BoundDynamicMethod} while also accounting for the possibility of a non-existent method.
 164      * @param dynamicMethod the dynamic method to bind
 165      * @param boundThis the adapter underlying a super adapter, to which the dynamic method is bound.
 166      * @return a dynamic method bound to the adapter instance.
 167      */
 168     @SuppressWarnings("unused")
 169     private static Object bindDynamicMethod(final Object dynamicMethod, final Object boundThis) {
 170         return dynamicMethod == ScriptRuntime.UNDEFINED ? ScriptRuntime.UNDEFINED : Bootstrap.bindCallable(dynamicMethod, boundThis, null);
 171     }
 172 
 173     /**
 174      * Used as the guard of linkages, as the receiver is not guaranteed to be a JavaSuperAdapter.
 175      * @param clazz the class the receiver's adapter is tested against.
 176      * @param obj receiver
 177      * @return true if the receiver is a super adapter, and its underlying adapter is of the specified class
 178      */
 179     @SuppressWarnings("unused")
 180     private static boolean isAdapterOfClass(final Class<?> clazz, final Object obj) {
 181         return obj instanceof JavaSuperAdapter && clazz == (((JavaSuperAdapter)obj).getAdapter()).getClass();
 182     }
 183 }