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 java.lang.invoke.MethodHandle;
  29 import java.lang.invoke.MethodHandles;
  30 import java.lang.invoke.MethodType;
  31 import java.util.HashMap;
  32 import java.util.Map;
  33 import javax.script.Bindings;
  34 import jdk.internal.dynalink.CallSiteDescriptor;
  35 import jdk.internal.dynalink.linker.GuardedInvocation;
  36 import jdk.internal.dynalink.linker.GuardedTypeConversion;
  37 import jdk.internal.dynalink.linker.GuardingTypeConverterFactory;
  38 import jdk.internal.dynalink.linker.LinkRequest;
  39 import jdk.internal.dynalink.linker.LinkerServices;
  40 import jdk.internal.dynalink.linker.TypeBasedGuardingDynamicLinker;
  41 import jdk.internal.dynalink.support.CallSiteDescriptorFactory;
  42 import jdk.nashorn.api.scripting.JSObject;
  43 import jdk.nashorn.internal.lookup.MethodHandleFactory;
  44 import jdk.nashorn.internal.lookup.MethodHandleFunctionality;
  45 import jdk.nashorn.internal.runtime.JSType;
  46 
  47 /**
  48  * A Dynalink linker to handle web browser built-in JS (DOM etc.) objects as well
  49  * as ScriptObjects from other Nashorn contexts.
  50  */
  51 final class JSObjectLinker implements TypeBasedGuardingDynamicLinker, GuardingTypeConverterFactory {
  52     private final NashornBeansLinker nashornBeansLinker;
  53 
  54     JSObjectLinker(final NashornBeansLinker nashornBeansLinker) {
  55         this.nashornBeansLinker = nashornBeansLinker;
  56     }
  57 
  58     @Override
  59     public boolean canLinkType(final Class<?> type) {
  60         return canLinkTypeStatic(type);
  61     }
  62 
  63     static boolean canLinkTypeStatic(final Class<?> type) {
  64         // can link JSObject also handles Map, Bindings to make
  65         // sure those are not JSObjects.
  66         return Map.class.isAssignableFrom(type) ||
  67                Bindings.class.isAssignableFrom(type) ||
  68                JSObject.class.isAssignableFrom(type);
  69     }
  70 
  71     @Override
  72     public GuardedInvocation getGuardedInvocation(final LinkRequest request, final LinkerServices linkerServices) throws Exception {
  73         final LinkRequest requestWithoutContext = request.withoutRuntimeContext(); // Nashorn has no runtime context
  74         final Object self = requestWithoutContext.getReceiver();
  75         final CallSiteDescriptor desc = requestWithoutContext.getCallSiteDescriptor();
  76 
  77         if (desc.getNameTokenCount() < 2 || !"dyn".equals(desc.getNameToken(CallSiteDescriptor.SCHEME))) {
  78             // We only support standard "dyn:*[:*]" operations
  79             return null;
  80         }
  81 
  82         final GuardedInvocation inv;
  83         if (self instanceof JSObject) {
  84             inv = lookup(desc);
  85         } else if (self instanceof Map || self instanceof Bindings) {
  86             // guard to make sure the Map or Bindings does not turn into JSObject later!
  87             final GuardedInvocation beanInv = nashornBeansLinker.getGuardedInvocation(request, linkerServices);
  88             inv = new GuardedInvocation(beanInv.getInvocation(),
  89                 NashornGuards.combineGuards(beanInv.getGuard(), NashornGuards.getNotJSObjectGuard()));
  90         } else {
  91             throw new AssertionError(); // Should never reach here.
  92         }
  93 
  94         return Bootstrap.asType(inv, linkerServices, desc);
  95     }
  96 
  97     @Override
  98     public GuardedTypeConversion convertToType(final Class<?> sourceType, final Class<?> targetType) throws Exception {
  99         final boolean sourceIsAlwaysJSObject = JSObject.class.isAssignableFrom(sourceType);
 100         if(!sourceIsAlwaysJSObject && !sourceType.isAssignableFrom(JSObject.class)) {
 101             return null;
 102         }
 103 
 104         final MethodHandle converter = CONVERTERS.get(targetType);
 105         if(converter == null) {
 106             return null;
 107         }
 108 
 109         return new GuardedTypeConversion(new GuardedInvocation(converter, sourceIsAlwaysJSObject ? null : IS_JSOBJECT_GUARD).asType(MethodType.methodType(targetType, sourceType)), true);
 110     }
 111 
 112 
 113     private static GuardedInvocation lookup(final CallSiteDescriptor desc) {
 114         final String operator = CallSiteDescriptorFactory.tokenizeOperators(desc).get(0);
 115         final int c = desc.getNameTokenCount();
 116         switch (operator) {
 117             case "getProp":
 118             case "getElem":
 119             case "getMethod":
 120                 return c > 2 ? findGetMethod(desc) : findGetIndexMethod();
 121             case "setProp":
 122             case "setElem":
 123                 return c > 2 ? findSetMethod(desc) : findSetIndexMethod();
 124             case "call":
 125                 return findCallMethod(desc);
 126             case "new":
 127                 return findNewMethod(desc);
 128             default:
 129                 return null;
 130         }
 131     }
 132 
 133     private static GuardedInvocation findGetMethod(final CallSiteDescriptor desc) {
 134         final MethodHandle getter = MH.insertArguments(JSOBJECT_GETMEMBER, 1, desc.getNameToken(2));
 135         return new GuardedInvocation(getter, null, IS_JSOBJECT_GUARD);
 136     }
 137 
 138     private static GuardedInvocation findGetIndexMethod() {
 139         return new GuardedInvocation(JSOBJECTLINKER_GET, null, IS_JSOBJECT_GUARD);
 140     }
 141 
 142     private static GuardedInvocation findSetMethod(final CallSiteDescriptor desc) {
 143         final MethodHandle getter = MH.insertArguments(JSOBJECT_SETMEMBER, 1, desc.getNameToken(2));
 144         return new GuardedInvocation(getter, null, IS_JSOBJECT_GUARD);
 145     }
 146 
 147     private static GuardedInvocation findSetIndexMethod() {
 148         return new GuardedInvocation(JSOBJECTLINKER_PUT, null, IS_JSOBJECT_GUARD);
 149     }
 150 
 151     private static GuardedInvocation findCallMethod(final CallSiteDescriptor desc) {
 152         final MethodHandle func = MH.asCollector(JSOBJECT_CALL, Object[].class, desc.getMethodType().parameterCount() - 2);
 153         return new GuardedInvocation(func, null, IS_JSOBJECT_GUARD);
 154     }
 155 
 156     private static GuardedInvocation findNewMethod(final CallSiteDescriptor desc) {
 157         final MethodHandle func = MH.asCollector(JSOBJECT_NEW, Object[].class, desc.getMethodType().parameterCount() - 1);
 158         return new GuardedInvocation(func, null, IS_JSOBJECT_GUARD);
 159     }
 160 
 161     @SuppressWarnings("unused")
 162     private static boolean isJSObject(final Object self) {
 163         return self instanceof JSObject;
 164     }
 165 
 166     @SuppressWarnings("unused")
 167     private static Object get(final Object jsobj, final Object key) {
 168         if (key instanceof Integer) {
 169             return ((JSObject)jsobj).getSlot((Integer)key);
 170         } else if (key instanceof Number) {
 171             final int index = getIndex((Number)key);
 172             if (index > -1) {
 173                 return ((JSObject)jsobj).getSlot(index);
 174             }
 175         } else if (key instanceof String) {
 176             return ((JSObject)jsobj).getMember((String)key);
 177         }
 178         return null;
 179     }
 180 
 181     @SuppressWarnings("unused")
 182     private static void put(final Object jsobj, final Object key, final Object value) {
 183         if (key instanceof Integer) {
 184             ((JSObject)jsobj).setSlot((Integer)key, value);
 185         } else if (key instanceof Number) {
 186             ((JSObject)jsobj).setSlot(getIndex((Number)key), value);
 187         } else if (key instanceof String) {
 188             ((JSObject)jsobj).setMember((String)key, value);
 189         }
 190     }
 191 
 192     @SuppressWarnings("unused")
 193     private static int toInt32(final JSObject obj) {
 194         return JSType.toInt32(toNumber(obj));
 195     }
 196 
 197     @SuppressWarnings("unused")
 198     private static long toInt64(final JSObject obj) {
 199         return JSType.toInt64(toNumber(obj));
 200     }
 201 
 202     private static double toNumber(final JSObject obj) {
 203         return obj == null ? 0 : obj.toNumber();
 204     }
 205 
 206     @SuppressWarnings("unused")
 207     private static boolean toBoolean(final JSObject obj) {
 208         return obj != null;
 209     }
 210 
 211     private static int getIndex(final Number n) {
 212         final double value = n.doubleValue();
 213         return JSType.isRepresentableAsInt(value) ? (int)value : -1;
 214     }
 215 
 216     private static final MethodHandleFunctionality MH = MethodHandleFactory.getFunctionality();
 217 
 218     // method handles of the current class
 219     private static final MethodHandle IS_JSOBJECT_GUARD  = findOwnMH("isJSObject", boolean.class, Object.class);
 220     private static final MethodHandle JSOBJECTLINKER_GET = findOwnMH("get", Object.class, Object.class, Object.class);
 221     private static final MethodHandle JSOBJECTLINKER_PUT = findOwnMH("put", Void.TYPE, Object.class, Object.class, Object.class);
 222 
 223     // method handles of JSObject class
 224     private static final MethodHandle JSOBJECT_GETMEMBER  = findJSObjectMH("getMember", Object.class, String.class);
 225     private static final MethodHandle JSOBJECT_SETMEMBER  = findJSObjectMH("setMember", Void.TYPE, String.class, Object.class);
 226     private static final MethodHandle JSOBJECT_CALL       = findJSObjectMH("call", Object.class, Object.class, Object[].class);
 227     private static final MethodHandle JSOBJECT_NEW        = findJSObjectMH("newObject", Object.class, Object[].class);
 228 
 229     private static final Map<Class<?>, MethodHandle> CONVERTERS = new HashMap<>();
 230     static {
 231         CONVERTERS.put(boolean.class, findOwnMH("toBoolean", boolean.class, JSObject.class));
 232         CONVERTERS.put(int.class, findOwnMH("toInt32", int.class, JSObject.class));
 233         CONVERTERS.put(long.class, findOwnMH("toInt64", long.class, JSObject.class));
 234         CONVERTERS.put(double.class, findOwnMH("toNumber", double.class, JSObject.class));
 235     }
 236 
 237     private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) {
 238         return findMH(name, JSObjectLinker.class, rtype, types);
 239     }
 240 
 241     private static MethodHandle findJSObjectMH(final String name, final Class<?> rtype, final Class<?>... types) {
 242         return findMH(name, JSObject.class, rtype, types);
 243     }
 244 
 245     private static MethodHandle findMH(final String name, final Class<?> target, final Class<?> rtype, final Class<?>... types) {
 246         final MethodType mt  = MH.type(rtype, types);
 247         try {
 248             return MH.findStatic(MethodHandles.lookup(), target, name, mt);
 249         } catch (final MethodHandleFactory.LookupException e) {
 250             return MH.findVirtual(MethodHandles.lookup(), target, name, mt);
 251         }
 252     }
 253 }