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