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.nashorn.internal.lookup.Lookup.MH;
  29 import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
  30 
  31 import java.lang.invoke.MethodHandle;
  32 import java.lang.invoke.MethodHandles;
  33 import java.lang.invoke.MethodType;
  34 import java.lang.reflect.Method;
  35 import java.lang.reflect.Modifier;
  36 import jdk.internal.dynalink.CallSiteDescriptor;
  37 import jdk.internal.dynalink.beans.BeansLinker;
  38 import jdk.internal.dynalink.linker.ConversionComparator.Comparison;
  39 import jdk.internal.dynalink.linker.GuardedInvocation;
  40 import jdk.internal.dynalink.linker.GuardingDynamicLinker;
  41 import jdk.internal.dynalink.linker.LinkRequest;
  42 import jdk.internal.dynalink.linker.LinkerServices;
  43 import jdk.internal.dynalink.linker.MethodHandleTransformer;
  44 import jdk.internal.dynalink.support.DefaultInternalObjectFilter;
  45 import jdk.internal.dynalink.support.Guards;
  46 import jdk.internal.dynalink.support.Lookup;
  47 import jdk.nashorn.api.scripting.ScriptUtils;
  48 import jdk.nashorn.internal.runtime.ConsString;
  49 import jdk.nashorn.internal.runtime.Context;
  50 import jdk.nashorn.internal.runtime.ScriptObject;
  51 import jdk.nashorn.internal.runtime.ScriptRuntime;
  52 import jdk.nashorn.internal.runtime.options.Options;
  53 
  54 /**
  55  * This linker delegates to a {@code BeansLinker} but passes it a special linker services object that has a modified
  56  * {@code compareConversion} method that favors conversion of {@link ConsString} to either {@link String} or
  57  * {@link CharSequence}. It also provides a {@link #createHiddenObjectFilter()} method for use with bootstrap that will
  58  * ensure that we never pass internal engine objects that should not be externally observable (currently ConsString and
  59  * ScriptObject) to Java APIs, but rather that we flatten it into a String. We can't just add this functionality as
  60  * custom converters via {@code GuaardingTypeConverterFactory}, since they are not consulted when
  61  * the target method handle parameter signature is {@code Object}. This linker also makes sure that primitive
  62  * {@link String} operations can be invoked on a {@link ConsString}, and allows invocation of objects implementing
  63  * the {@link FunctionalInterface} attribute.
  64  */
  65 public class NashornBeansLinker implements GuardingDynamicLinker {
  66     // System property to control whether to wrap ScriptObject->ScriptObjectMirror for
  67     // Object type arguments of Java method calls, field set and array set.
  68     private static final boolean MIRROR_ALWAYS = Options.getBooleanProperty("nashorn.mirror.always", true);
  69 
  70     private static final MethodHandle EXPORT_ARGUMENT;
  71     private static final MethodHandle IMPORT_RESULT;
  72     private static final MethodHandle FILTER_CONSSTRING;
  73 
  74     static {
  75         final Lookup lookup  = new Lookup(MethodHandles.lookup());
  76         EXPORT_ARGUMENT      = lookup.findOwnStatic("exportArgument", Object.class, Object.class);
  77         IMPORT_RESULT        = lookup.findOwnStatic("importResult", Object.class, Object.class);
  78         FILTER_CONSSTRING    = lookup.findOwnStatic("consStringFilter", Object.class, Object.class);
  79     }
  80 
  81     // cache of @FunctionalInterface method of implementor classes
  82     private static final ClassValue<Method> FUNCTIONAL_IFACE_METHOD = new ClassValue<Method>() {
  83         @Override
  84         protected Method computeValue(final Class<?> type) {
  85             return findFunctionalInterfaceMethod(type);
  86         }
  87     };
  88 
  89     private final BeansLinker beansLinker = new BeansLinker();
  90 
  91     @Override
  92     public GuardedInvocation getGuardedInvocation(final LinkRequest linkRequest, final LinkerServices linkerServices) throws Exception {
  93         final Object self = linkRequest.getReceiver();
  94         final CallSiteDescriptor desc = linkRequest.getCallSiteDescriptor();
  95         if (self instanceof ConsString) {
  96             // In order to treat ConsString like a java.lang.String we need a link request with a string receiver.
  97             final Object[] arguments = linkRequest.getArguments();
  98             arguments[0] = "";
  99             final LinkRequest forgedLinkRequest = linkRequest.replaceArguments(desc, arguments);
 100             final GuardedInvocation invocation = getGuardedInvocation(beansLinker, forgedLinkRequest, linkerServices);
 101             // If an invocation is found we add a filter that makes it work for both Strings and ConsStrings.
 102             return invocation == null ? null : invocation.filterArguments(0, FILTER_CONSSTRING);
 103         }
 104 
 105         if (self != null && "call".equals(desc.getNameToken(CallSiteDescriptor.OPERATOR))) {
 106             // Support dyn:call on any object that supports some @FunctionalInterface
 107             // annotated interface. This way Java method, constructor references or
 108             // implementations of java.util.function.* interfaces can be called as though
 109             // those are script functions.
 110             final Method m = getFunctionalInterfaceMethod(self.getClass());
 111             if (m != null) {
 112                 final MethodType callType = desc.getMethodType();
 113                 // 'callee' and 'thiz' passed from script + actual arguments
 114                 if (callType.parameterCount() != m.getParameterCount() + 2) {
 115                     throw typeError("no.method.matches.args", ScriptRuntime.safeToString(self));
 116                 }
 117                 return new GuardedInvocation(
 118                         // drop 'thiz' passed from the script.
 119                         MH.dropArguments(linkerServices.filterInternalObjects(desc.getLookup().unreflect(m)), 1,
 120                                 callType.parameterType(1)), Guards.getInstanceOfGuard(
 121                                         m.getDeclaringClass())).asTypeSafeReturn(
 122                                                 new NashornBeansLinkerServices(linkerServices), callType);
 123             }
 124         }
 125         return getGuardedInvocation(beansLinker, linkRequest, linkerServices);
 126     }
 127 
 128     /**
 129      * Delegates to the specified linker but injects its linker services wrapper so that it will apply all special
 130      * conversions that this class does.
 131      * @param delegateLinker the linker to which the actual work is delegated to.
 132      * @param linkRequest the delegated link request
 133      * @param linkerServices the original link services that will be augmented with special conversions
 134      * @return the guarded invocation from the delegate, possibly augmented with special conversions
 135      * @throws Exception if the delegate throws an exception
 136      */
 137     public static GuardedInvocation getGuardedInvocation(final GuardingDynamicLinker delegateLinker, final LinkRequest linkRequest, final LinkerServices linkerServices) throws Exception {
 138         return delegateLinker.getGuardedInvocation(linkRequest, new NashornBeansLinkerServices(linkerServices));
 139     }
 140 
 141     @SuppressWarnings("unused")
 142     private static Object exportArgument(final Object arg) {
 143         return exportArgument(arg, MIRROR_ALWAYS);
 144     }
 145 
 146     static Object exportArgument(final Object arg, final boolean mirrorAlways) {
 147         if (arg instanceof ConsString) {
 148             return arg.toString();
 149         } else if (mirrorAlways && arg instanceof ScriptObject) {
 150             return ScriptUtils.wrap((ScriptObject)arg);
 151         } else {
 152             return arg;
 153         }
 154     }
 155 
 156     @SuppressWarnings("unused")
 157     private static Object importResult(final Object arg) {
 158         return ScriptUtils.unwrap(arg);
 159     }
 160 
 161     @SuppressWarnings("unused")
 162     private static Object consStringFilter(final Object arg) {
 163         return arg instanceof ConsString ? arg.toString() : arg;
 164     }
 165 
 166     private static Method findFunctionalInterfaceMethod(final Class<?> clazz) {
 167         if (clazz == null) {
 168             return null;
 169         }
 170 
 171         for (final Class<?> iface : clazz.getInterfaces()) {
 172             // check accessiblity up-front
 173             if (! Context.isAccessibleClass(iface)) {
 174                 continue;
 175             }
 176 
 177             // check for @FunctionalInterface
 178             if (iface.isAnnotationPresent(FunctionalInterface.class)) {
 179                 // return the first abstract method
 180                 for (final Method m : iface.getMethods()) {
 181                     if (Modifier.isAbstract(m.getModifiers())) {
 182                         return m;
 183                     }
 184                 }
 185             }
 186         }
 187 
 188         // did not find here, try super class
 189         return findFunctionalInterfaceMethod(clazz.getSuperclass());
 190     }
 191 
 192     // Returns @FunctionalInterface annotated interface's single abstract
 193     // method. If not found, returns null.
 194     static Method getFunctionalInterfaceMethod(final Class<?> clazz) {
 195         return FUNCTIONAL_IFACE_METHOD.get(clazz);
 196     }
 197 
 198     static MethodHandleTransformer createHiddenObjectFilter() {
 199         return new DefaultInternalObjectFilter(EXPORT_ARGUMENT, MIRROR_ALWAYS ? IMPORT_RESULT : null);
 200     }
 201 
 202     private static class NashornBeansLinkerServices implements LinkerServices {
 203         private final LinkerServices linkerServices;
 204 
 205         NashornBeansLinkerServices(final LinkerServices linkerServices) {
 206             this.linkerServices = linkerServices;
 207         }
 208 
 209         @Override
 210         public MethodHandle asType(final MethodHandle handle, final MethodType fromType) {
 211             return linkerServices.asType(handle, fromType);
 212         }
 213 
 214         @Override
 215         public MethodHandle asTypeLosslessReturn(final MethodHandle handle, final MethodType fromType) {
 216             return Implementation.asTypeLosslessReturn(this, handle, fromType);
 217         }
 218 
 219         @Override
 220         public MethodHandle getTypeConverter(final Class<?> sourceType, final Class<?> targetType) {
 221             return linkerServices.getTypeConverter(sourceType, targetType);
 222         }
 223 
 224         @Override
 225         public boolean canConvert(final Class<?> from, final Class<?> to) {
 226             return linkerServices.canConvert(from, to);
 227         }
 228 
 229         @Override
 230         public GuardedInvocation getGuardedInvocation(final LinkRequest linkRequest) throws Exception {
 231             return linkerServices.getGuardedInvocation(linkRequest);
 232         }
 233 
 234         @Override
 235         public Comparison compareConversion(final Class<?> sourceType, final Class<?> targetType1, final Class<?> targetType2) {
 236             if (sourceType == ConsString.class) {
 237                 if (String.class == targetType1 || CharSequence.class == targetType1) {
 238                     return Comparison.TYPE_1_BETTER;
 239                 }
 240 
 241                 if (String.class == targetType2 || CharSequence.class == targetType2) {
 242                     return Comparison.TYPE_2_BETTER;
 243                 }
 244             }
 245             return linkerServices.compareConversion(sourceType, targetType1, targetType2);
 246         }
 247 
 248         @Override
 249         public MethodHandle filterInternalObjects(final MethodHandle target) {
 250             return linkerServices.filterInternalObjects(target);
 251         }
 252     }
 253 }