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.invoke.SwitchPoint;
  35 import jdk.dynalink.CallSiteDescriptor;
  36 import jdk.dynalink.linker.GuardedInvocation;
  37 import jdk.dynalink.linker.LinkRequest;
  38 import jdk.dynalink.linker.support.Guards;
  39 import jdk.nashorn.internal.runtime.Context;
  40 import jdk.nashorn.internal.runtime.FindProperty;
  41 import jdk.nashorn.internal.runtime.GlobalConstants;
  42 import jdk.nashorn.internal.runtime.JSType;
  43 import jdk.nashorn.internal.runtime.ScriptObject;
  44 import jdk.nashorn.internal.runtime.ScriptRuntime;
  45 
  46 /**
  47  * Implements lookup of methods to link for dynamic operations on JavaScript primitive values (booleans, strings, and
  48  * numbers). This class is only public so it can be accessed by classes in the {@code jdk.nashorn.internal.objects}
  49  * package.
  50  */
  51 public final class PrimitiveLookup {
  52 
  53     /** Method handle to link setters on primitive base. See ES5 8.7.2. */
  54     private static final MethodHandle PRIMITIVE_SETTER = findOwnMH("primitiveSetter",
  55             MH.type(void.class, ScriptObject.class, Object.class, Object.class, boolean.class, Object.class));
  56 
  57 
  58     private PrimitiveLookup() {
  59     }
  60 
  61     /**
  62      * Returns a guarded invocation representing the linkage for a dynamic operation on a primitive Java value.
  63      * @param request the link request for the dynamic call site.
  64      * @param receiverClass the class of the receiver value (e.g., {@link java.lang.Boolean}, {@link java.lang.String} etc.)
  65      * @param wrappedReceiver a transient JavaScript native wrapper object created as the object proxy for the primitive
  66      * value; see ECMAScript 5.1, section 8.7.1 for discussion of using {@code [[Get]]} on a property reference with a
  67      * primitive base value. This instance will be used to delegate actual lookup.
  68      * @param wrapFilter A method handle that takes a primitive value of type specified in the {@code receiverClass} and
  69      * creates a transient native wrapper of the same type as {@code wrappedReceiver} for subsequent invocations of the
  70      * method - it will be combined into the returned invocation as an argument filter on the receiver.
  71      * @return a guarded invocation representing the operation at the call site when performed on a JavaScript primitive
  72      * @param protoFilter A method handle that walks up the proto chain of this receiver object
  73      * type {@code receiverClass}.
  74      */
  75     public static GuardedInvocation lookupPrimitive(final LinkRequest request, final Class<?> receiverClass,
  76                                                     final ScriptObject wrappedReceiver, final MethodHandle wrapFilter,
  77                                                     final MethodHandle protoFilter) {
  78         return lookupPrimitive(request, Guards.getInstanceOfGuard(receiverClass), wrappedReceiver, wrapFilter, protoFilter);
  79     }
  80 
  81     /**
  82      * Returns a guarded invocation representing the linkage for a dynamic operation on a primitive Java value.
  83      * @param request the link request for the dynamic call site.
  84      * @param guard an explicit guard that will be used for the returned guarded invocation.
  85      * @param wrappedReceiver a transient JavaScript native wrapper object created as the object proxy for the primitive
  86      * value; see ECMAScript 5.1, section 8.7.1 for discussion of using {@code [[Get]]} on a property reference with a
  87      * primitive base value. This instance will be used to delegate actual lookup.
  88      * @param wrapFilter A method handle that takes a primitive value of type guarded by the {@code guard} and
  89      * creates a transient native wrapper of the same type as {@code wrappedReceiver} for subsequent invocations of the
  90      * method - it will be combined into the returned invocation as an argument filter on the receiver.
  91      * @param protoFilter A method handle that walks up the proto chain of this receiver object
  92      * @return a guarded invocation representing the operation at the call site when performed on a JavaScript primitive
  93      * type (that is implied by both {@code guard} and {@code wrappedReceiver}).
  94      */
  95     public static GuardedInvocation lookupPrimitive(final LinkRequest request, final MethodHandle guard,
  96                                                     final ScriptObject wrappedReceiver, final MethodHandle wrapFilter,
  97                                                     final MethodHandle protoFilter) {
  98         final CallSiteDescriptor desc = request.getCallSiteDescriptor();
  99         final String name = NashornCallSiteDescriptor.getOperand(desc);
 100         final FindProperty find = name != null ? wrappedReceiver.findProperty(name, true) : null;
 101 
 102         switch (NashornCallSiteDescriptor.getStandardOperation(desc)) {
 103         case GET:
 104             //checks whether the property name is hard-coded in the call-site (i.e. a getProp vs a getElem, or setProp vs setElem)
 105             //if it is we can make assumptions on the property: that if it is not defined on primitive wrapper itself it never will be.
 106             //so in that case we can skip creation of primitive wrapper and start our search with the prototype.
 107             if (name != null) {
 108                 if (find == null) {
 109                     // Give up early, give chance to BeanLinker and NashornBottomLinker to deal with it.
 110                     return null;
 111                 }
 112 
 113                 final SwitchPoint sp = find.getProperty().getBuiltinSwitchPoint(); //can use this instead of proto filter
 114                 if (sp instanceof Context.BuiltinSwitchPoint && !sp.hasBeenInvalidated()) {
 115                     return new GuardedInvocation(GlobalConstants.staticConstantGetter(find.getObjectValue()), guard, sp, null);
 116                 }
 117 
 118                 if (find.isInheritedOrdinaryProperty()) {
 119                     // If property is found in the prototype object bind the method handle directly to
 120                     // the proto filter instead of going through wrapper instantiation below.
 121                     final ScriptObject proto = wrappedReceiver.getProto();
 122                     final GuardedInvocation link = proto.lookup(desc, request);
 123 
 124                     if (link != null) {
 125                         final MethodHandle invocation = link.getInvocation(); //this contains the builtin switchpoint
 126                         final MethodHandle adaptedInvocation = MH.asType(invocation, invocation.type().changeParameterType(0, Object.class));
 127                         final MethodHandle method = MH.filterArguments(adaptedInvocation, 0, protoFilter);
 128                         final MethodHandle protoGuard = MH.filterArguments(link.getGuard(), 0, protoFilter);
 129                         return new GuardedInvocation(method, NashornGuards.combineGuards(guard, protoGuard));
 130                     }
 131                 }
 132             }
 133             break;
 134         case SET:
 135             return getPrimitiveSetter(name, guard, wrapFilter, NashornCallSiteDescriptor.isStrict(desc));
 136         default:
 137             break;
 138         }
 139 
 140         final GuardedInvocation link = wrappedReceiver.lookup(desc, request);
 141         if (link != null) {
 142             MethodHandle method = link.getInvocation();
 143             final Class<?> receiverType = method.type().parameterType(0);
 144             if (receiverType != Object.class) {
 145                 final MethodType wrapType = wrapFilter.type();
 146                 assert receiverType.isAssignableFrom(wrapType.returnType());
 147                 method = MH.filterArguments(method, 0, MH.asType(wrapFilter, wrapType.changeReturnType(receiverType)));
 148             }
 149 
 150             return new GuardedInvocation(method, guard, link.getSwitchPoints(), null);
 151         }
 152 
 153         return null;
 154     }
 155 
 156     private static GuardedInvocation getPrimitiveSetter(final String name, final MethodHandle guard,
 157                                                         final MethodHandle wrapFilter, final boolean isStrict) {
 158         MethodHandle filter = MH.asType(wrapFilter, wrapFilter.type().changeReturnType(ScriptObject.class));
 159         final MethodHandle target;
 160 
 161         if (name == null) {
 162             filter = MH.dropArguments(filter, 1, Object.class, Object.class);
 163             target = MH.insertArguments(PRIMITIVE_SETTER, 3, isStrict);
 164         } else {
 165             filter = MH.dropArguments(filter, 1, Object.class);
 166             target = MH.insertArguments(PRIMITIVE_SETTER, 2, name, isStrict);
 167         }
 168 
 169         return new GuardedInvocation(MH.foldArguments(target, filter), guard);
 170     }
 171 
 172 
 173     @SuppressWarnings("unused")
 174     private static void primitiveSetter(final ScriptObject wrappedSelf, final Object self, final Object key,
 175                                         final boolean strict, final Object value) {
 176         // See ES5.1 8.7.2 PutValue (V, W)
 177         final String name = JSType.toString(key);
 178         final FindProperty find = wrappedSelf.findProperty(name, true);
 179         if (find == null || !find.getProperty().isAccessorProperty() || !find.getProperty().hasNativeSetter()) {
 180             if (strict) {
 181                 if (find == null || !find.getProperty().isAccessorProperty()) {
 182                     throw typeError("property.not.writable", name, ScriptRuntime.safeToString(self));
 183                 } else {
 184                     throw typeError("property.has.no.setter", name, ScriptRuntime.safeToString(self));
 185                 }
 186             }
 187             return;
 188         }
 189         // property found and is a UserAccessorProperty
 190         find.setValue(value, strict);
 191     }
 192 
 193     private static MethodHandle findOwnMH(final String name, final MethodType type) {
 194         return MH.findStatic(MethodHandles.lookup(), PrimitiveLookup.class, name, type);
 195     }
 196 }