1 /*
   2  * Copyright (c) 2014, 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.runtime.linker.BrowserJSObjectLinker.JSObjectHandles.JSOBJECT_GETMEMBER;
  29 import static jdk.nashorn.internal.runtime.linker.BrowserJSObjectLinker.JSObjectHandles.JSOBJECT_GETSLOT;
  30 import static jdk.nashorn.internal.runtime.linker.BrowserJSObjectLinker.JSObjectHandles.JSOBJECT_SETMEMBER;
  31 import static jdk.nashorn.internal.runtime.linker.BrowserJSObjectLinker.JSObjectHandles.JSOBJECT_SETSLOT;
  32 import java.lang.invoke.MethodHandle;
  33 import java.lang.invoke.MethodHandles;
  34 import jdk.internal.dynalink.CallSiteDescriptor;
  35 import jdk.internal.dynalink.linker.GuardedInvocation;
  36 import jdk.internal.dynalink.linker.LinkRequest;
  37 import jdk.internal.dynalink.linker.LinkerServices;
  38 import jdk.internal.dynalink.linker.TypeBasedGuardingDynamicLinker;
  39 import jdk.internal.dynalink.support.CallSiteDescriptorFactory;
  40 import jdk.nashorn.internal.lookup.MethodHandleFactory;
  41 import jdk.nashorn.internal.lookup.MethodHandleFunctionality;
  42 import jdk.nashorn.internal.runtime.JSType;
  43 
  44 /**
  45  * A Dynalink linker to handle web browser built-in JS (DOM etc.) objects.
  46  */
  47 final class BrowserJSObjectLinker implements TypeBasedGuardingDynamicLinker {
  48     private static final ClassLoader myLoader = BrowserJSObjectLinker.class.getClassLoader();








  49     private static final String JSOBJECT_CLASS = "netscape.javascript.JSObject";
  50     // not final because this is lazily initialized
  51     // when we hit a subclass for the first time.
  52     private static volatile Class<?> jsObjectClass;
  53     private final NashornBeansLinker nashornBeansLinker;
  54 
  55     BrowserJSObjectLinker(final NashornBeansLinker nashornBeansLinker) {
  56         this.nashornBeansLinker = nashornBeansLinker;
  57     }
  58 
  59     @Override
  60     public boolean canLinkType(final Class<?> type) {
  61         return canLinkTypeStatic(type);
  62     }
  63 
  64     static boolean canLinkTypeStatic(final Class<?> type) {
  65         if (jsObjectClass != null && jsObjectClass.isAssignableFrom(type)) {
  66             return true;
  67         }
  68 
  69         // check if this class is a subclass of JSObject
  70         Class<?> clazz = type;
  71         while (clazz != null) {
  72             if (clazz.getClassLoader() == myLoader &&
  73                 clazz.getName().equals(JSOBJECT_CLASS)) {
  74                 jsObjectClass = clazz;
  75                 return true;
  76             }
  77             clazz = clazz.getSuperclass();
  78         }
  79 
  80         return false;
  81     }
  82 
  83     private static void checkJSObjectClass() {
  84         assert jsObjectClass != null : JSOBJECT_CLASS + " not found!";
  85     }
  86 
  87     @Override
  88     public GuardedInvocation getGuardedInvocation(final LinkRequest request, final LinkerServices linkerServices) throws Exception {
  89         final LinkRequest requestWithoutContext = request.withoutRuntimeContext(); // Nashorn has no runtime context
  90         final Object self = requestWithoutContext.getReceiver();
  91         final CallSiteDescriptor desc = requestWithoutContext.getCallSiteDescriptor();
  92         checkJSObjectClass();
  93 
  94         if (desc.getNameTokenCount() < 2 || !"dyn".equals(desc.getNameToken(CallSiteDescriptor.SCHEME))) {
  95             // We only support standard "dyn:*[:*]" operations
  96             return null;
  97         }
  98 
  99         final GuardedInvocation inv;
 100         if (jsObjectClass.isInstance(self)) {
 101             inv = lookup(desc, request, linkerServices);
 102         } else {
 103             throw new AssertionError(); // Should never reach here.
 104         }
 105 
 106         return Bootstrap.asTypeSafeReturn(inv, linkerServices, desc);
 107     }
 108 
 109     private GuardedInvocation lookup(final CallSiteDescriptor desc, final LinkRequest request, final LinkerServices linkerServices) throws Exception {
 110         final String operator = CallSiteDescriptorFactory.tokenizeOperators(desc).get(0);
 111         final int c = desc.getNameTokenCount();
 112 
 113         switch (operator) {
 114             case "getProp":
 115             case "getElem":
 116             case "getMethod":
 117                 if (c > 2) {
 118                     return findGetMethod(desc);
 119                 }
 120             // For indexed get, we want GuardedInvocation from beans linker and pass it.
 121             // BrowserJSObjectLinker.get uses this fallback getter for explicit signature method access.
 122             return findGetIndexMethod(nashornBeansLinker.getGuardedInvocation(request, linkerServices));
 123             case "setProp":
 124             case "setElem":
 125                 return c > 2 ? findSetMethod(desc) : findSetIndexMethod();
 126             default:
 127                 return null;
 128         }
 129     }
 130 
 131     private static GuardedInvocation findGetMethod(final CallSiteDescriptor desc) {
 132         final String name = desc.getNameToken(CallSiteDescriptor.NAME_OPERAND);
 133         final MethodHandle getter = MH.insertArguments(JSOBJECT_GETMEMBER, 1, name);
 134         return new GuardedInvocation(getter, IS_JSOBJECT_GUARD);
 135     }
 136 
 137     private static GuardedInvocation findGetIndexMethod(final GuardedInvocation inv) {
 138         final MethodHandle getter = MH.insertArguments(JSOBJECTLINKER_GET, 0, inv.getInvocation());
 139         return inv.replaceMethods(getter, inv.getGuard());
 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, IS_JSOBJECT_GUARD);
 145     }
 146 
 147     private static GuardedInvocation findSetIndexMethod() {
 148         return new GuardedInvocation(JSOBJECTLINKER_PUT, IS_JSOBJECT_GUARD);
 149     }
 150 
 151     @SuppressWarnings("unused")
 152     private static boolean isJSObject(final Object self) {
 153         return jsObjectClass.isInstance(self);
 154     }
 155 
 156     @SuppressWarnings("unused")
 157     private static Object get(final MethodHandle fallback, final Object jsobj, final Object key) throws Throwable {
 158         if (key instanceof Integer) {
 159             return JSOBJECT_GETSLOT.invokeExact(jsobj, (int)key);
 160         } else if (key instanceof Number) {
 161             final int index = getIndex((Number)key);
 162             if (index > -1) {
 163                 return JSOBJECT_GETSLOT.invokeExact(jsobj, index);
 164             }
 165         } else if (key instanceof String) {
 166             final String name = (String)key;
 167             if (name.indexOf('(') != -1) {
 168                 return fallback.invokeExact(jsobj, key);
 169             }
 170             return JSOBJECT_GETMEMBER.invokeExact(jsobj, (String)key);
 171         }
 172         return null;
 173     }
 174 
 175     @SuppressWarnings("unused")
 176     private static void put(final Object jsobj, final Object key, final Object value) throws Throwable {
 177         if (key instanceof Integer) {
 178             JSOBJECT_SETSLOT.invokeExact(jsobj, (int)key, value);
 179         } else if (key instanceof Number) {
 180             JSOBJECT_SETSLOT.invokeExact(jsobj, getIndex((Number)key), value);
 181         } else if (key instanceof String) {
 182             JSOBJECT_SETMEMBER.invokeExact(jsobj, (String)key, value);
 183         }
 184     }
 185 
 186     private static int getIndex(final Number n) {
 187         final double value = n.doubleValue();
 188         return JSType.isRepresentableAsInt(value) ? (int)value : -1;
 189     }
 190 
 191     private static final MethodHandleFunctionality MH = MethodHandleFactory.getFunctionality();
 192     // method handles of the current class
 193     private static final MethodHandle IS_JSOBJECT_GUARD  = findOwnMH_S("isJSObject", boolean.class, Object.class);
 194     private static final MethodHandle JSOBJECTLINKER_GET = findOwnMH_S("get", Object.class, MethodHandle.class, Object.class, Object.class);
 195     private static final MethodHandle JSOBJECTLINKER_PUT = findOwnMH_S("put", Void.TYPE, Object.class, Object.class, Object.class);
 196 
 197     private static MethodHandle findOwnMH_S(final String name, final Class<?> rtype, final Class<?>... types) {
 198             return MH.findStatic(MethodHandles.lookup(), BrowserJSObjectLinker.class, name, MH.type(rtype, types));
 199     }
 200 
 201     // method handles of netscape.javascript.JSObject class
 202     // These are in separate class as we lazily initialize these
 203     // method handles when we hit a subclass of JSObject first time.
 204     static class JSObjectHandles {
 205         // method handles of JSObject class
 206         static final MethodHandle JSOBJECT_GETMEMBER     = findJSObjectMH_V("getMember", Object.class, String.class).asType(MH.type(Object.class, Object.class, String.class));
 207         static final MethodHandle JSOBJECT_GETSLOT       = findJSObjectMH_V("getSlot", Object.class, int.class).asType(MH.type(Object.class, Object.class, int.class));
 208         static final MethodHandle JSOBJECT_SETMEMBER     = findJSObjectMH_V("setMember", Void.TYPE, String.class, Object.class).asType(MH.type(Void.TYPE, Object.class, String.class, Object.class));
 209         static final MethodHandle JSOBJECT_SETSLOT       = findJSObjectMH_V("setSlot", Void.TYPE, int.class, Object.class).asType(MH.type(Void.TYPE, Object.class, int.class, Object.class));
 210 
 211         private static MethodHandle findJSObjectMH_V(final String name, final Class<?> rtype, final Class<?>... types) {
 212             checkJSObjectClass();
 213             return MH.findVirtual(MethodHandles.publicLookup(), jsObjectClass, name, MH.type(rtype, types));
 214         }
 215     }
 216 }
--- EOF ---