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;
  27 
  28 import jdk.nashorn.internal.ir.FunctionNode;
  29 import jdk.nashorn.internal.parser.Token;
  30 import jdk.nashorn.internal.parser.TokenType;
  31 
  32 import java.lang.invoke.MethodHandle;
  33 import java.lang.invoke.MethodType;
  34 
  35 import static jdk.nashorn.internal.runtime.linker.Lookup.MH;
  36 
  37 /**
  38  * A container for data needed to instantiate a specific {@link ScriptFunction} at runtime.
  39  * Instances of this class are created during codegen and stored in script classes'
  40  * constants array to reduce function instantiation overhead during runtime.
  41  */
  42 public class ScriptFunctionData {
  43 
  44     // per-function object flags
  45     private static final int IS_STRICT  = 0b0000_0001;
  46     private static final int IS_BUILTIN = 0b0000_0010;
  47     private static final int HAS_CALLEE = 0b0000_0100;
  48     private static final int IS_VARARGS = 0b0000_1000;
  49 
  50     /** Name of the function or "" */
  51     private final String name;
  52     /** Source of this function, or null */
  53     private final Source source;
  54     /** Map for new instance constructor */
  55     private PropertyMap  allocatorMap;
  56     /** Start position and length in source */
  57     private final long   token;
  58     /** Number of expected arguments, either taken from FunctionNode or calculated from method handle signature*/
  59     private int          arity;
  60     /** Does this function need a callee argument? */
  61     private final int    flags;
  62 
  63     /** Reference to code for this method. */
  64     private MethodHandle invoker;
  65     /** Reference to code for this method when called to create "new" object */
  66     private MethodHandle constructor;
  67     /** Constructor to create a new instance. */
  68     private MethodHandle allocator;
  69     /** Generic invoker to used in {@link ScriptFunction#invoke(Object, Object...)}. */
  70     private MethodHandle genericInvoker;
  71     /** Generic constructor used in {@link ScriptFunction#construct(Object, Object...)}. */
  72     private MethodHandle genericConstructor;
  73     /** Specializations - see @SpecializedFunction */
  74     private MethodHandle[] invokeSpecializations;
  75     /** Specializations - see @SpecializedFunction */
  76     private MethodHandle[] constructSpecializations;
  77 
  78     /**
  79      * Constructor
  80      * @param fn the function node
  81      * @param allocatorMap the allocator property map
  82      */
  83     public ScriptFunctionData(final FunctionNode fn, final PropertyMap allocatorMap) {
  84 
  85         final long firstToken = fn.getFirstToken();
  86         final long lastToken  = fn.getLastToken();
  87         final int  position   = Token.descPosition(firstToken);
  88         final int  length     = Token.descPosition(lastToken) - position + Token.descLength(lastToken);
  89 
  90         this.name         = fn.isAnonymous() ? "" : fn.getIdent().getName();
  91         this.source       = fn.getSource();
  92         this.allocatorMap = allocatorMap;
  93         this.token        = Token.toDesc(TokenType.FUNCTION, position, length);
  94         this.arity        = fn.getParameters().size();
  95         this.flags        = makeFlags(fn.needsCallee(), fn.isVarArg(), fn.isStrictMode(), false);
  96     }
  97 
  98     /**
  99      * Constructor
 100      * @param name the function name
 101      * @param methodHandle the method handle
 102      * @param specs array of specialized method handles
 103      * @param strict strict flag
 104      * @param builtin builtin flag
 105      */
 106     public ScriptFunctionData(final String name, final MethodHandle methodHandle, final MethodHandle[] specs, final boolean strict, final boolean builtin) {
 107         this.name        = name;
 108         this.source      = null;
 109         this.token       = 0;
 110 
 111         final MethodType type     = methodHandle.type();
 112         final int paramCount      = type.parameterCount();
 113         final boolean isVarArg    = type.parameterType(paramCount - 1).isArray();
 114         final boolean needsCallee = needsCallee(methodHandle);
 115 
 116         this.flags = makeFlags(needsCallee, isVarArg, strict, builtin);
 117         this.arity = isVarArg ? -1 : paramCount - 1; //drop the self param for arity
 118 
 119         if (needsCallee && !isVarArg) {
 120             this.arity--;
 121         }
 122 
 123         if (isConstructor(methodHandle)) {
 124             if (!isVarArg) {
 125                 this.arity--;    // drop the boolean flag for arity
 126             }
 127             /*
 128              * We insert a boolean argument to tell if the method was invoked as
 129              * constructor or not if the method handle's first argument is boolean.
 130              */
 131             this.invoker     = MH.insertArguments(methodHandle, 0, false);
 132             this.constructor = MH.insertArguments(methodHandle, 0, true);
 133 
 134             if (specs != null) {
 135                 this.invokeSpecializations    = new MethodHandle[specs.length];
 136                 this.constructSpecializations = new MethodHandle[specs.length];
 137                 for (int i = 0; i < specs.length; i++) {
 138                     this.invokeSpecializations[i]    = MH.insertArguments(specs[i], 0, false);
 139                     this.constructSpecializations[i] = MH.insertArguments(specs[i], 0, true);
 140                 }
 141             }
 142         } else {
 143             this.invoker                  = methodHandle;
 144             this.constructor              = methodHandle;
 145             this.invokeSpecializations    = specs;
 146             this.constructSpecializations = specs;
 147         }
 148     }
 149 
 150     /**
 151      * Get the arity of the function.
 152      * @return the arity
 153      */
 154     public int getArity() {
 155         return arity;
 156     }
 157 
 158     /**
 159      * Set the arity of the function.
 160      * @param arity the arity
 161      */
 162     public void setArity(int arity) {
 163         this.arity = arity;
 164     }
 165 
 166     /**
 167      * Get the function name.
 168      * @return function name
 169      */
 170     public String getName() {
 171         return name;
 172     }
 173 
 174     /**
 175      * Get the source of the function.
 176      * @return the source
 177      */
 178     public Source getSource() {
 179         return source;
 180     }
 181 
 182     /**
 183      * Get this function as a String containing its source code. If no source code
 184      * exists in this ScriptFunction, its contents will be displayed as {@code [native code]}
 185      * @return string representation of this function's source
 186      */
 187     public String toSource() {
 188         if (source != null && token != 0) {
 189             return source.getString(Token.descPosition(token), Token.descLength(token));
 190         }
 191 
 192         return "function " + (name == null ? "" : name) + "() { [native code] }";
 193     }
 194 
 195     @Override
 196     public String toString() {
 197         final StringBuilder sb = new StringBuilder();
 198 
 199         sb.append(super.toString())
 200                 .append(" [ ")
 201                 .append(invoker)
 202                 .append(", ")
 203                 .append((name == null || name.isEmpty()) ? "<anonymous>" : name);
 204 
 205         if (source != null) {
 206             sb.append(" @ ")
 207                     .append(source.getName())
 208                     .append(':')
 209                     .append(source.getLine(Token.descPosition(token)));
 210         }
 211         sb.append(" ]");
 212 
 213         return sb.toString();
 214     }
 215 
 216     /**
 217      * Get the allocator property map.
 218      * @return the allocator map
 219      */
 220     public PropertyMap getAllocatorMap() {
 221         return allocatorMap;
 222     }
 223 
 224     /**
 225      * Get the function's parse token.
 226      * @return the token
 227      */
 228     public long getToken() {
 229         return token;
 230     }
 231 
 232     /**
 233      * Returns true if the function needs a callee argument.
 234      * @return the needsCallee flag
 235      */
 236     public boolean needsCallee() {
 237         return (flags & HAS_CALLEE) != 0;
 238     }
 239 
 240     /**
 241      * Returns true if this is a strict-mode function.
 242      * @return the strict flag
 243      */
 244     public boolean isStrict() {
 245         return (flags & IS_STRICT) != 0;
 246     }
 247 
 248     /**
 249      * Returns true if this is a built-in function.
 250      * @return the built-in flag
 251      */
 252     public boolean isBuiltin() {
 253         return (flags & IS_BUILTIN) != 0;
 254     }
 255 
 256     /**
 257      * Returns true if this is a var-arg function.
 258      * @return the var-arg flag
 259      */
 260     public boolean isVarArg() {
 261         return (flags & IS_VARARGS) != 0;
 262     }
 263 
 264     /**
 265      * Returns true if this is a non-strict, non-built-in function that requires non-primitive this argument
 266      * according to ECMA 10.4.3.
 267      * @return true if this argument must be an object
 268      */
 269     public boolean needsWrappedThis() {
 270         return (flags & (IS_STRICT | IS_BUILTIN)) == 0;
 271     }
 272 
 273     /**
 274      * Get the method handle used to invoke this function.
 275      * @return the invoke handle
 276      */
 277     public MethodHandle getInvoker() {
 278         return invoker;
 279     }
 280 
 281     /**
 282      * Get the method handle used to invoke this function as a constructor.
 283      * @return the constructor handle
 284      */
 285     public MethodHandle getConstructor() {
 286         return constructor;
 287     }
 288 
 289     /**
 290      * Set the constructor method handle.
 291      * @param constructor the constructor handle
 292      */
 293     public void setConstructor(MethodHandle constructor) {
 294         this.constructor = constructor;
 295         this.constructSpecializations = null;
 296     }
 297 
 298     /**
 299      * Get the method handle used to allocate a new object for this constructor.
 300      * @return the allocator handle
 301      */
 302     public MethodHandle getAllocator() {
 303         return allocator;
 304     }
 305 
 306     /**
 307      * Get an adapted version of the invoker handle that only uses {@code Object} as parameter and return types.
 308      * @return the generic invoke handle
 309      */
 310     public MethodHandle getGenericInvoker() {
 311         if (genericInvoker == null) {
 312             assert invoker != null : "invoker is null";
 313             genericInvoker = adaptMethodType(invoker);
 314         }
 315         return genericInvoker;
 316     }
 317 
 318     /**
 319      * Get an adapted version of the constructor handle that only uses {@code Object} as parameter and return types.
 320      * @return the generic constructor handle
 321      */
 322     public MethodHandle getGenericConstructor() {
 323         if (genericConstructor == null) {
 324             assert constructor != null : "constructor is null";
 325             genericConstructor = adaptMethodType(constructor);
 326         }
 327         return genericConstructor;
 328     }
 329 
 330     /**
 331      * Get the specialized invoke handles for this function.
 332      * @return array of specialized invoke handles
 333      */
 334     public MethodHandle[] getInvokeSpecializations() {
 335         return invokeSpecializations;
 336     }
 337 
 338     /**
 339      * Get the specialized construct handles for this function.
 340      * @return array of specialized construct handles
 341      */
 342     public MethodHandle[] getConstructSpecializations() {
 343         return constructSpecializations;
 344     }
 345 
 346     /**
 347      * Set the method handles for this function.
 348      * @param invoker the invoker handle
 349      * @param allocator the allocator handle
 350      */
 351     public void setMethodHandles(MethodHandle invoker, MethodHandle allocator) {
 352         // We can't make method handle fields final because they're not available during codegen
 353         // and they're set when first called, so we enforce set-once here.
 354         if (this.invoker == null) {
 355             this.invoker     = invoker;
 356             this.constructor = invoker;
 357             this.allocator   = allocator;
 358         }
 359     }
 360 
 361     /**
 362      * Convert boolean flags to int.
 363      * @param needsCallee needs-callee flag
 364      * @param isVarArg var-arg flag
 365      * @param isStrict strict flag
 366      * @param isBuiltin builtin flag
 367      * @return int flags
 368      */
 369     private static int makeFlags(final boolean needsCallee, final boolean isVarArg, final boolean isStrict, final boolean isBuiltin) {
 370         int flags = 0;
 371         if (needsCallee) {
 372             flags |= HAS_CALLEE;
 373         }
 374         if (isVarArg) {
 375             flags |= IS_VARARGS;
 376         }
 377         if (isStrict) {
 378             flags |= IS_STRICT;
 379         }
 380         if (isBuiltin) {
 381             flags |= IS_BUILTIN;
 382         }
 383         return flags;
 384     }
 385 
 386     /**
 387      * Test if a methodHandle refers to a constructor.
 388      * @param methodHandle MethodHandle to test.
 389      * @return True if method is a constructor.
 390      */
 391     private static boolean isConstructor(final MethodHandle methodHandle) {
 392         return methodHandle.type().parameterCount() >= 1 && methodHandle.type().parameterType(0) == boolean.class;
 393     }
 394 
 395     /**
 396      * Heuristic to figure out if the method handle has a callee argument. If it's type is either
 397      * {@code (boolean, Object, ScriptFunction, ...)} or {@code (Object, ScriptFunction, ...)}, then we'll assume it has
 398      * a callee argument. We need this as the constructor above is not passed this information, and can't just blindly
 399      * assume it's false (notably, it's being invoked for creation of new scripts, and scripts have scopes, therefore
 400      * they also always receive a callee.
 401      * @param methodHandle the examined method handle
 402      * @return true if the method handle expects a callee, false otherwise
 403      */
 404     private static boolean needsCallee(MethodHandle methodHandle) {
 405         final MethodType type = methodHandle.type();
 406         final int len = type.parameterCount();
 407         if(len == 0) {
 408             return false;
 409         }
 410         if(type.parameterType(0) == boolean.class) {
 411             return len > 2 && type.parameterType(2) == ScriptFunction.class;
 412         }
 413         return len > 1 && type.parameterType(1) == ScriptFunction.class;
 414     }
 415 
 416     /**
 417      * Takes a method handle, and returns a potentially different method handle that can be used in
 418      * {@link ScriptFunction#invoke(Object, Object...)} or {@link ScriptFunction#construct(Object, Object...)}.
 419      * The returned method handle will be sure to return {@code Object}, and will have all its parameters turned into
 420      * {@code Object} as well, except for the following ones:
 421      * <ul>
 422      *   <li>a last parameter of type {@code Object[]} which is used for vararg functions,</li>
 423      *   <li>the second argument, which is forced to be {@link ScriptFunction}, in case the function receives itself
 424      *   (callee) as an argument</li>
 425      * </ul>
 426      *
 427      * @param handle the original method handle
 428      * @return the new handle, conforming to the rules above.
 429      */
 430     private MethodHandle adaptMethodType(final MethodHandle handle) {
 431         final MethodType type = handle.type();
 432         MethodType newType = type.generic();
 433         if (isVarArg()) {
 434             newType = newType.changeParameterType(type.parameterCount() - 1, Object[].class);
 435         }
 436         if (needsCallee()) {
 437             newType = newType.changeParameterType(1, ScriptFunction.class);
 438         }
 439         return type.equals(newType) ? handle : handle.asType(newType);
 440     }
 441 
 442 }