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 }