View Javadoc
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 static jdk.nashorn.internal.codegen.CompilerConstants.virtualCallNoLookup;
29  import static jdk.nashorn.internal.lookup.Lookup.MH;
30  import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
31  import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED;
32  
33  import java.lang.invoke.MethodHandle;
34  import java.lang.invoke.MethodHandles;
35  import java.lang.invoke.MethodType;
36  import jdk.internal.dynalink.CallSiteDescriptor;
37  import jdk.internal.dynalink.linker.GuardedInvocation;
38  import jdk.internal.dynalink.linker.LinkRequest;
39  import jdk.nashorn.internal.codegen.CompilerConstants.Call;
40  import jdk.nashorn.internal.lookup.MethodHandleFactory;
41  import jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor;
42  import jdk.nashorn.internal.runtime.linker.NashornGuards;
43  
44  /**
45   * Runtime representation of a JavaScript function.
46   */
47  public abstract class ScriptFunction extends ScriptObject {
48  
49      /** Method handle for prototype getter for this ScriptFunction */
50      public static final MethodHandle G$PROTOTYPE = findOwnMH("G$prototype", Object.class, Object.class);
51  
52      /** Method handle for prototype setter for this ScriptFunction */
53      public static final MethodHandle S$PROTOTYPE = findOwnMH("S$prototype", void.class, Object.class, Object.class);
54  
55      /** Method handle for length getter for this ScriptFunction */
56      public static final MethodHandle G$LENGTH = findOwnMH("G$length", int.class, Object.class);
57  
58      /** Method handle for name getter for this ScriptFunction */
59      public static final MethodHandle G$NAME = findOwnMH("G$name", Object.class, Object.class);
60  
61      /** Method handle used for implementing sync() in mozilla_compat */
62      public static final MethodHandle INVOKE_SYNC = findOwnMH("invokeSync", Object.class, ScriptFunction.class, Object.class, Object.class, Object[].class);
63  
64      /** Method handle for allocate function for this ScriptFunction */
65      static final MethodHandle ALLOCATE = findOwnMH("allocate", Object.class);
66  
67      private static final MethodHandle WRAPFILTER = findOwnMH("wrapFilter", Object.class, Object.class);
68  
69      /** method handle to scope getter for this ScriptFunction */
70      public static final Call GET_SCOPE = virtualCallNoLookup(ScriptFunction.class, "getScope", ScriptObject.class);
71  
72      private static final MethodHandle IS_FUNCTION_MH  = findOwnMH("isFunctionMH", boolean.class, Object.class, ScriptFunctionData.class);
73  
74      private static final MethodHandle IS_NONSTRICT_FUNCTION = findOwnMH("isNonStrictFunction", boolean.class, Object.class, Object.class, ScriptFunctionData.class);
75  
76      private static final MethodHandle ADD_ZEROTH_ELEMENT = findOwnMH("addZerothElement", Object[].class, Object[].class, Object.class);
77  
78      /** The parent scope. */
79      private final ScriptObject scope;
80  
81      private final ScriptFunctionData data;
82  
83      /**
84       * Constructor
85       *
86       * @param name          function name
87       * @param methodHandle  method handle to function (if specializations are present, assumed to be most generic)
88       * @param map           property map
89       * @param scope         scope
90       * @param specs         specialized version of this function - other method handles
91       * @param strict        is this a strict mode function?
92       * @param builtin       is this a built in function?
93       * @param isConstructor is this a constructor?
94       */
95      protected ScriptFunction(
96              final String name,
97              final MethodHandle methodHandle,
98              final PropertyMap map,
99              final ScriptObject scope,
100             final MethodHandle[] specs,
101             final boolean strict,
102             final boolean builtin,
103             final boolean isConstructor) {
104 
105         this(new FinalScriptFunctionData(name, methodHandle, specs, strict, builtin, isConstructor), map, scope);
106     }
107 
108     /**
109      * Constructor
110      *
111      * @param data          static function data
112      * @param map           property map
113      * @param scope         scope
114      */
115     protected ScriptFunction(
116             final ScriptFunctionData data,
117             final PropertyMap map,
118             final ScriptObject scope) {
119 
120         super(map);
121 
122         if (Context.DEBUG) {
123             constructorCount++;
124         }
125 
126         this.data  = data;
127         this.scope = scope;
128     }
129 
130     @Override
131     public String getClassName() {
132         return "Function";
133     }
134 
135     /**
136      * ECMA 15.3.5.3 [[HasInstance]] (V)
137      * Step 3 if "prototype" value is not an Object, throw TypeError
138      */
139     @Override
140     public boolean isInstance(final ScriptObject instance) {
141         final Object basePrototype = getTargetFunction().getPrototype();
142         if (!(basePrototype instanceof ScriptObject)) {
143             throw typeError("prototype.not.an.object", ScriptRuntime.safeToString(getTargetFunction()), ScriptRuntime.safeToString(basePrototype));
144         }
145 
146         for (ScriptObject proto = instance.getProto(); proto != null; proto = proto.getProto()) {
147             if (proto == basePrototype) {
148                 return true;
149             }
150         }
151 
152         return false;
153     }
154 
155     /**
156      * Returns the target function for this function. If the function was not created using
157      * {@link #makeBoundFunction(Object, Object[])}, its target function is itself. If it is bound, its target function
158      * is the target function of the function it was made from (therefore, the target function is always the final,
159      * unbound recipient of the calls).
160      * @return the target function for this function.
161      */
162     protected ScriptFunction getTargetFunction() {
163         return this;
164     }
165 
166     boolean isBoundFunction() {
167         return getTargetFunction() != this;
168     }
169 
170     /**
171      * Set the arity of this ScriptFunction
172      * @param arity arity
173      */
174     public final void setArity(final int arity) {
175         data.setArity(arity);
176     }
177 
178     /**
179      * Is this a ECMAScript 'use strict' function?
180      * @return true if function is in strict mode
181      */
182     public boolean isStrict() {
183         return data.isStrict();
184     }
185 
186     /**
187      * Returns true if this is a non-strict, non-built-in function that requires non-primitive this argument
188      * according to ECMA 10.4.3.
189      * @return true if this argument must be an object
190      */
191     public boolean needsWrappedThis() {
192         return data.needsWrappedThis();
193     }
194 
195     /**
196      * Execute this script function.
197      * @param self  Target object.
198      * @param arguments  Call arguments.
199      * @return ScriptFunction result.
200      * @throws Throwable if there is an exception/error with the invocation or thrown from it
201      */
202     Object invoke(final Object self, final Object... arguments) throws Throwable {
203         if (Context.DEBUG) {
204             invokes++;
205         }
206         return data.invoke(this, self, arguments);
207     }
208 
209     /**
210      * Execute this script function as a constructor.
211      * @param arguments  Call arguments.
212      * @return Newly constructed result.
213      * @throws Throwable if there is an exception/error with the invocation or thrown from it
214      */
215     Object construct(final Object... arguments) throws Throwable {
216         return data.construct(this, arguments);
217     }
218 
219     /**
220      * Allocate function. Called from generated {@link ScriptObject} code
221      * for allocation as a factory method
222      *
223      * @return a new instance of the {@link ScriptObject} whose allocator this is
224      */
225     @SuppressWarnings("unused")
226     private Object allocate() {
227         if (Context.DEBUG) {
228             allocations++;
229         }
230         assert !isBoundFunction(); // allocate never invoked on bound functions
231 
232         final ScriptObject object = data.allocate();
233 
234         if (object != null) {
235             Object prototype = getPrototype();
236             if (prototype instanceof ScriptObject) {
237                 object.setProto((ScriptObject)prototype);
238             }
239 
240             if (object.getProto() == null) {
241                 object.setProto(getObjectPrototype());
242             }
243         }
244 
245         return object;
246     }
247 
248     /**
249      * Return Object.prototype - used by "allocate"
250      * @return Object.prototype
251      */
252     protected abstract ScriptObject getObjectPrototype();
253 
254     /**
255      * Creates a version of this function bound to a specific "self" and other arguments, as per
256      * {@code Function.prototype.bind} functionality in ECMAScript 5.1 section 15.3.4.5.
257      * @param self the self to bind to this function. Can be null (in which case, null is bound as this).
258      * @param args additional arguments to bind to this function. Can be null or empty to not bind additional arguments.
259      * @return a function with the specified self and parameters bound.
260      */
261     protected ScriptFunction makeBoundFunction(final Object self, final Object[] args) {
262         return makeBoundFunction(data.makeBoundFunctionData(this, self, args));
263     }
264 
265     /**
266      * Create a version of this function as in {@link ScriptFunction#makeBoundFunction(Object, Object[])},
267      * but using a {@link ScriptFunctionData} for the bound data.
268      *
269      * @param boundData ScriptFuntionData for the bound function
270      * @return a function with the bindings performed according to the given data
271      */
272     protected abstract ScriptFunction makeBoundFunction(ScriptFunctionData boundData);
273 
274     @Override
275     public final String safeToString() {
276         return toSource();
277     }
278 
279     @Override
280     public String toString() {
281         return data.toString();
282     }
283 
284     /**
285      * Get this function as a String containing its source code. If no source code
286      * exists in this ScriptFunction, its contents will be displayed as {@code [native code]}
287      * @return string representation of this function's source
288      */
289     public final String toSource() {
290         return data.toSource();
291     }
292 
293     /**
294      * Get the prototype object for this function
295      * @return prototype
296      */
297     public abstract Object getPrototype();
298 
299     /**
300      * Set the prototype object for this function
301      * @param prototype new prototype object
302      */
303     public abstract void setPrototype(Object prototype);
304 
305     /**
306      * Create a function that invokes this function synchronized on {@code sync} or the self object
307      * of the invocation.
308      * @param sync the Object to synchronize on, or undefined
309      * @return synchronized function
310      */
311     public abstract ScriptFunction makeSynchronizedFunction(Object sync);
312 
313     /**
314      * Return the most appropriate invoke handle if there are specializations
315      * @param type most specific method type to look for invocation with
316      * @param args args for trampoline invocation
317      * @return invoke method handle
318      */
319     private MethodHandle getBestInvoker(final MethodType type, final Object[] args) {
320         return data.getBestInvoker(type, args);
321     }
322 
323     /**
324      * Return the most appropriate invoke handle if there are specializations
325      * @param type most specific method type to look for invocation with
326      * @return invoke method handle
327      */
328     public MethodHandle getBestInvoker(final MethodType type) {
329         return getBestInvoker(type, null);
330     }
331 
332     /**
333      * Return the invoke handle bound to a given ScriptObject self reference.
334      * If callee parameter is required result is rebound to this.
335      *
336      * @param self self reference
337      * @return bound invoke handle
338      */
339     public final MethodHandle getBoundInvokeHandle(final Object self) {
340         return MH.bindTo(bindToCalleeIfNeeded(data.getGenericInvoker()), self);
341     }
342 
343     /**
344      * Bind the method handle to this {@code ScriptFunction} instance if it needs a callee parameter. If this function's
345      * method handles don't have a callee parameter, the handle is returned unchanged.
346      * @param methodHandle the method handle to potentially bind to this function instance.
347      * @return the potentially bound method handle
348      */
349     private MethodHandle bindToCalleeIfNeeded(final MethodHandle methodHandle) {
350         return ScriptFunctionData.needsCallee(methodHandle) ? MH.bindTo(methodHandle, this) : methodHandle;
351 
352     }
353 
354     /**
355      * Get the name for this function
356      * @return the name
357      */
358     public final String getName() {
359         return data.getName();
360     }
361 
362 
363     /**
364      * Get the scope for this function
365      * @return the scope
366      */
367     public final ScriptObject getScope() {
368         return scope;
369     }
370 
371     /**
372      * Prototype getter for this ScriptFunction - follows the naming convention
373      * used by Nasgen and the code generator
374      *
375      * @param self  self reference
376      * @return self's prototype
377      */
378     public static Object G$prototype(final Object self) {
379         return (self instanceof ScriptFunction) ?
380             ((ScriptFunction)self).getPrototype() :
381             UNDEFINED;
382     }
383 
384     /**
385      * Prototype setter for this ScriptFunction - follows the naming convention
386      * used by Nasgen and the code generator
387      *
388      * @param self  self reference
389      * @param prototype prototype to set
390      */
391     public static void S$prototype(final Object self, final Object prototype) {
392         if (self instanceof ScriptFunction) {
393             ((ScriptFunction)self).setPrototype(prototype);
394         }
395     }
396 
397     /**
398      * Length getter - ECMA 15.3.3.2: Function.length
399      * @param self self reference
400      * @return length
401      */
402     public static int G$length(final Object self) {
403         if (self instanceof ScriptFunction) {
404             return ((ScriptFunction)self).data.getArity();
405         }
406 
407         return 0;
408     }
409 
410     /**
411      * Name getter - ECMA Function.name
412      * @param self self refence
413      * @return the name, or undefined if none
414      */
415     public static Object G$name(final Object self) {
416         if (self instanceof ScriptFunction) {
417             return ((ScriptFunction)self).getName();
418         }
419 
420         return UNDEFINED;
421     }
422 
423     /**
424      * Get the prototype for this ScriptFunction
425      * @param constructor constructor
426      * @return prototype, or null if given constructor is not a ScriptFunction
427      */
428     public static ScriptObject getPrototype(final Object constructor) {
429         if (constructor instanceof ScriptFunction) {
430             final Object proto = ((ScriptFunction)constructor).getPrototype();
431             if (proto instanceof ScriptObject) {
432                 return (ScriptObject)proto;
433             }
434         }
435 
436         return null;
437     }
438 
439     // These counters are updated only in debug mode.
440     private static int constructorCount;
441     private static int invokes;
442     private static int allocations;
443 
444     /**
445      * @return the constructorCount
446      */
447     public static int getConstructorCount() {
448         return constructorCount;
449     }
450 
451     /**
452      * @return the invokes
453      */
454     public static int getInvokes() {
455         return invokes;
456     }
457 
458     /**
459      * @return the allocations
460      */
461     public static int getAllocations() {
462         return allocations;
463     }
464 
465     @Override
466     protected GuardedInvocation findNewMethod(final CallSiteDescriptor desc) {
467         final MethodType type = desc.getMethodType();
468         return new GuardedInvocation(pairArguments(data.getBestConstructor(type.changeParameterType(0, ScriptFunction.class), null), type), null, getFunctionGuard(this));
469     }
470 
471     @SuppressWarnings("unused")
472     private static Object wrapFilter(final Object obj) {
473         if (obj instanceof ScriptObject || !ScriptFunctionData.isPrimitiveThis(obj)) {
474             return obj;
475         }
476         return ((GlobalObject)Context.getGlobalTrusted()).wrapAsObject(obj);
477     }
478 
479     /**
480      * dyn:call call site signature: (callee, thiz, [args...])
481      * generated method signature:   (callee, thiz, [args...])
482      *
483      * cases:
484      * (a) method has callee parameter
485      *   (1) for local/scope calls, we just bind thiz and drop the second argument.
486      *   (2) for normal this-calls, we have to swap thiz and callee to get matching signatures.
487      * (b) method doesn't have callee parameter (builtin functions)
488      *   (3) for local/scope calls, bind thiz and drop both callee and thiz.
489      *   (4) for normal this-calls, drop callee.
490      */
491     @Override
492     protected GuardedInvocation findCallMethod(final CallSiteDescriptor desc, final LinkRequest request) {
493         final MethodType type = desc.getMethodType();
494 
495         if (request.isCallSiteUnstable()) {
496             // (this, callee, args...) => (this, callee, args[])
497             final MethodHandle collector = MH.asCollector(ScriptRuntime.APPLY.methodHandle(), Object[].class,
498                     type.parameterCount() - 2);
499 
500             // If call site is statically typed to take a ScriptFunction, we don't need a guard, otherwise we need a
501             // generic "is this a ScriptFunction?" guard.
502             return new GuardedInvocation(collector, ScriptFunction.class.isAssignableFrom(desc.getMethodType().parameterType(0))
503                     ? null : NashornGuards.getScriptFunctionGuard());
504         }
505 
506         MethodHandle boundHandle;
507         MethodHandle guard = null;
508 
509         final boolean scopeCall = NashornCallSiteDescriptor.isScope(desc);
510 
511         if (data.needsCallee()) {
512             final MethodHandle callHandle = getBestInvoker(type, request.getArguments());
513             if (scopeCall) {
514                 // Make a handle that drops the passed "this" argument and substitutes either Global or Undefined
515                 // (callee, this, args...) => (callee, args...)
516                 boundHandle = MH.insertArguments(callHandle, 1, needsWrappedThis() ? Context.getGlobalTrusted() : ScriptRuntime.UNDEFINED);
517                 // (callee, args...) => (callee, [this], args...)
518                 boundHandle = MH.dropArguments(boundHandle, 1, Object.class);
519 
520             } else {
521                 // It's already (callee, this, args...), just what we need
522                 boundHandle = callHandle;
523             }
524         } else {
525             final MethodHandle callHandle = getBestInvoker(type.dropParameterTypes(0, 1), request.getArguments());
526             if (data.isBuiltin() && "extend".equals(data.getName())) {
527                 // NOTE: the only built-in named "extend" is NativeJava.extend. As a special-case we're binding the
528                 // current lookup as its "this" so it can do security-sensitive creation of adapter classes.
529                 boundHandle = MH.dropArguments(MH.bindTo(callHandle, desc.getLookup()), 0, Object.class, Object.class);
530             } else if (scopeCall) {
531                 // Make a handle that drops the passed "this" argument and substitutes either Global or Undefined
532                 // (this, args...) => (args...)
533                 boundHandle = MH.bindTo(callHandle, needsWrappedThis() ? Context.getGlobalTrusted() : ScriptRuntime.UNDEFINED);
534                 // (args...) => ([callee], [this], args...)
535                 boundHandle = MH.dropArguments(boundHandle, 0, Object.class, Object.class);
536             } else {
537                 // (this, args...) => ([callee], this, args...)
538                 boundHandle = MH.dropArguments(callHandle, 0, Object.class);
539             }
540         }
541 
542         // For non-strict functions, check whether this-object is primitive type.
543         // If so add a to-object-wrapper argument filter.
544         // Else install a guard that will trigger a relink when the argument becomes primitive.
545         if (!scopeCall && needsWrappedThis()) {
546             if (ScriptFunctionData.isPrimitiveThis(request.getArguments()[1])) {
547                 boundHandle = MH.filterArguments(boundHandle, 1, WRAPFILTER);
548             } else {
549                 guard = getNonStrictFunctionGuard(this);
550             }
551         }
552 
553         boundHandle = pairArguments(boundHandle, type);
554 
555         return new GuardedInvocation(boundHandle, guard == null ? getFunctionGuard(this) : guard);
556    }
557 
558     /**
559      * Used for noSuchMethod/noSuchProperty and JSAdapter hooks.
560      *
561      * These don't want a callee parameter, so bind that. Name binding is optional.
562      */
563     MethodHandle getCallMethodHandle(final MethodType type, final String bindName) {
564         return pairArguments(bindToNameIfNeeded(bindToCalleeIfNeeded(getBestInvoker(type, null)), bindName), type);
565     }
566 
567     private static MethodHandle bindToNameIfNeeded(final MethodHandle methodHandle, final String bindName) {
568         if (bindName == null) {
569             return methodHandle;
570         }
571 
572         // if it is vararg method, we need to extend argument array with
573         // a new zeroth element that is set to bindName value.
574         final MethodType methodType = methodHandle.type();
575         final int parameterCount = methodType.parameterCount();
576         final boolean isVarArg = parameterCount > 0 && methodType.parameterType(parameterCount - 1).isArray();
577 
578         if (isVarArg) {
579             return MH.filterArguments(methodHandle, 1, MH.insertArguments(ADD_ZEROTH_ELEMENT, 1, bindName));
580         }
581         return MH.insertArguments(methodHandle, 1, bindName);
582     }
583 
584     /**
585      * Get the guard that checks if a {@link ScriptFunction} is equal to
586      * a known ScriptFunction, using reference comparison
587      *
588      * @param function The ScriptFunction to check against. This will be bound to the guard method handle
589      *
590      * @return method handle for guard
591      */
592     private static MethodHandle getFunctionGuard(final ScriptFunction function) {
593         assert function.data != null;
594         return MH.insertArguments(IS_FUNCTION_MH, 1, function.data);
595     }
596 
597     /**
598      * Get a guard that checks if a {@link ScriptFunction} is equal to
599      * a known ScriptFunction using reference comparison, and whether the type of
600      * the second argument (this-object) is not a JavaScript primitive type.
601      *
602      * @param function The ScriptFunction to check against. This will be bound to the guard method handle
603      *
604      * @return method handle for guard
605      */
606     private static MethodHandle getNonStrictFunctionGuard(final ScriptFunction function) {
607         assert function.data != null;
608         return MH.insertArguments(IS_NONSTRICT_FUNCTION, 2, function.data);
609     }
610 
611     @SuppressWarnings("unused")
612     private static boolean isFunctionMH(final Object self, final ScriptFunctionData data) {
613         return self instanceof ScriptFunction && ((ScriptFunction)self).data == data;
614     }
615 
616     @SuppressWarnings("unused")
617     private static boolean isNonStrictFunction(final Object self, final Object arg, final ScriptFunctionData data) {
618         return self instanceof ScriptFunction && ((ScriptFunction)self).data == data && arg instanceof ScriptObject;
619     }
620 
621     @SuppressWarnings("unused")
622     private static Object[] addZerothElement(final Object[] args, final Object value) {
623         // extends input array with by adding new zeroth element
624         final Object[] src = (args == null)? ScriptRuntime.EMPTY_ARRAY : args;
625         final Object[] result = new Object[src.length + 1];
626         System.arraycopy(src, 0, result, 1, src.length);
627         result[0] = value;
628         return result;
629     }
630 
631     @SuppressWarnings("unused")
632     private static Object invokeSync(final ScriptFunction func, final Object sync, final Object self, final Object... args)
633             throws Throwable {
634         final Object syncObj = sync == UNDEFINED ? self : sync;
635         synchronized (syncObj) {
636             return func.invoke(self, args);
637         }
638     }
639 
640     private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) {
641         final Class<?>   own = ScriptFunction.class;
642         final MethodType mt  = MH.type(rtype, types);
643         try {
644             return MH.findStatic(MethodHandles.lookup(), own, name, mt);
645         } catch (final MethodHandleFactory.LookupException e) {
646             return MH.findVirtual(MethodHandles.lookup(), own, name, mt);
647         }
648     }
649 }
650