aboutsummaryrefslogtreecommitdiff
path: root/src/jdk/nashorn/internal/runtime/ScriptFunctionData.java
blob: 683c7abb465aa4465eb37b6d55f91e01a1f63c96 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
/*
 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package jdk.nashorn.internal.runtime;

import static jdk.nashorn.internal.lookup.Lookup.MH;
import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import jdk.nashorn.internal.runtime.linker.LinkerCallSite;


/**
 * A container for data needed to instantiate a specific {@link ScriptFunction} at runtime.
 * Instances of this class are created during codegen and stored in script classes'
 * constants array to reduce function instantiation overhead during runtime.
 */
public abstract class ScriptFunctionData implements Serializable {
    static final int MAX_ARITY = LinkerCallSite.ARGLIMIT;
    static {
        // Assert it fits in a byte, as that's what we store it in. It's just a size optimization though, so if needed
        // "byte arity" field can be widened.
        assert MAX_ARITY < 256;
    }

    /** Name of the function or "" for anonymous functions */
    protected final String name;

    /**
     * A list of code versions of a function sorted in ascending order of generic descriptors.
     */
    protected transient LinkedList<CompiledFunction> code = new LinkedList<>();

    /** Function flags */
    protected int flags;

    // Parameter arity of the function, corresponding to "f.length". E.g. "function f(a, b, c) { ... }" arity is 3, and
    // some built-in ECMAScript functions have their arity declared by the specification. Note that regardless of this
    // value, the function might still be capable of receiving variable number of arguments, see isVariableArity.
    private int arity;

    /**
     * A pair of method handles used for generic invoker and constructor. Field is volatile as it can be initialized by
     * multiple threads concurrently, but we still tolerate a race condition in it as all values stored into it are
     * idempotent.
     */
    private volatile transient GenericInvokers genericInvokers;

    private static final MethodHandle BIND_VAR_ARGS = findOwnMH("bindVarArgs", Object[].class, Object[].class, Object[].class);

    /** Is this a strict mode function? */
    public static final int IS_STRICT            = 1 << 0;
    /** Is this a built-in function? */
    public static final int IS_BUILTIN           = 1 << 1;
    /** Is this a constructor function? */
    public static final int IS_CONSTRUCTOR       = 1 << 2;
    /** Does this function expect a callee argument? */
    public static final int NEEDS_CALLEE         = 1 << 3;
    /** Does this function make use of the this-object argument? */
    public static final int USES_THIS            = 1 << 4;
    /** Is this a variable arity function? */
    public static final int IS_VARIABLE_ARITY    = 1 << 5;
    /** Is this a object literal property getter or setter? */
    public static final int IS_PROPERTY_ACCESSOR = 1 << 6;

    /** Flag for strict or built-in functions */
    public static final int IS_STRICT_OR_BUILTIN = IS_STRICT | IS_BUILTIN;
    /** Flag for built-in constructors */
    public static final int IS_BUILTIN_CONSTRUCTOR = IS_BUILTIN | IS_CONSTRUCTOR;

    private static final long serialVersionUID = 4252901245508769114L;

    /**
     * Constructor
     *
     * @param name  script function name
     * @param arity arity
     * @param flags the function flags
     */
    ScriptFunctionData(final String name, final int arity, final int flags) {
        this.name  = name;
        this.flags = flags;
        setArity(arity);
    }

    final int getArity() {
        return arity;
    }

    final boolean isVariableArity() {
        return (flags & IS_VARIABLE_ARITY) != 0;
    }

    final boolean isPropertyAccessor() {
        return (flags & IS_PROPERTY_ACCESSOR) != 0;
    }

    /**
     * Used from e.g. Native*$Constructors as an explicit call. TODO - make arity immutable and final
     * @param arity new arity
     */
    void setArity(final int arity) {
        if(arity < 0 || arity > MAX_ARITY) {
            throw new IllegalArgumentException(String.valueOf(arity));
        }
        this.arity = arity;
    }

    CompiledFunction bind(final CompiledFunction originalInv, final ScriptFunction fn, final Object self, final Object[] args) {
        final MethodHandle boundInvoker = bindInvokeHandle(originalInv.createComposableInvoker(), fn, self, args);

        if (isConstructor()) {
            return new CompiledFunction(boundInvoker, bindConstructHandle(originalInv.createComposableConstructor(), fn, args), null);
        }

        return new CompiledFunction(boundInvoker);
    }

    /**
     * Is this a ScriptFunction generated with strict semantics?
     * @return true if strict, false otherwise
     */
    public final boolean isStrict() {
        return (flags & IS_STRICT) != 0;
    }

    /**
     * Return the complete internal function name for this
     * data, not anonymous or similar. May be identical
     * @return internal function name
     */
    protected String getFunctionName() {
        return getName();
    }

    final boolean isBuiltin() {
        return (flags & IS_BUILTIN) != 0;
    }

    final boolean isConstructor() {
        return (flags & IS_CONSTRUCTOR) != 0;
    }

    abstract boolean needsCallee();

    /**
     * Returns true if this is a non-strict, non-built-in function that requires non-primitive this argument
     * according to ECMA 10.4.3.
     * @return true if this argument must be an object
     */
    final boolean needsWrappedThis() {
        return (flags & USES_THIS) != 0 && (flags & IS_STRICT_OR_BUILTIN) == 0;
    }

    String toSource() {
        return "function " + (name == null ? "" : name) + "() { [native code] }";
    }

    String getName() {
        return name;
    }

    /**
     * Get this function as a String containing its source code. If no source code
     * exists in this ScriptFunction, its contents will be displayed as {@code [native code]}
     *
     * @return string representation of this function
     */
    @Override
    public String toString() {
        return name.isEmpty() ? "<anonymous>" : name;
    }

    /**
     * Verbose description of data
     * @return verbose description
     */
    public String toStringVerbose() {
        final StringBuilder sb = new StringBuilder();

        sb.append("name='").
                append(name.isEmpty() ? "<anonymous>" : name).
                append("' ").
                append(code.size()).
                append(" invokers=").
                append(code);

        return sb.toString();
    }

    /**
     * Pick the best invoker, i.e. the one version of this method with as narrow and specific
     * types as possible. If the call site arguments are objects, but boxed primitives we can
     * also try to get a primitive version of the method and do an unboxing filter, but then
     * we need to insert a guard that checks the argument is really always a boxed primitive
     * and not suddenly a "real" object
     *
     * @param callSiteType callsite type
     * @return compiled function object representing the best invoker.
     */
    final CompiledFunction getBestInvoker(final MethodType callSiteType, final ScriptObject runtimeScope) {
        return getBestInvoker(callSiteType, runtimeScope, CompiledFunction.NO_FUNCTIONS);
    }

    final CompiledFunction getBestInvoker(final MethodType callSiteType, final ScriptObject runtimeScope, final Collection<CompiledFunction> forbidden) {
        final CompiledFunction cf = getBest(callSiteType, runtimeScope, forbidden);
        assert cf != null;
        return cf;
    }

    final CompiledFunction getBestConstructor(final MethodType callSiteType, final ScriptObject runtimeScope, final Collection<CompiledFunction> forbidden) {
        if (!isConstructor()) {
            throw typeError("not.a.constructor", toSource());
        }
        // Constructor call sites don't have a "this", but getBest is meant to operate on "callee, this, ..." style
        final CompiledFunction cf = getBest(callSiteType.insertParameterTypes(1, Object.class), runtimeScope, forbidden);
        return cf;
    }

    /**
     * If we can have lazy code generation, this is a hook to ensure that the code has been compiled.
     * This does not guarantee the code been installed in this {@code ScriptFunctionData} instance
     */
    protected void ensureCompiled() {
        //empty
    }

    /**
     * Return a generic Object/Object invoker for this method. It will ensure code
     * is generated, get the most generic of all versions of this function and adapt it
     * to Objects.
     *
     * @param runtimeScope the runtime scope. It can be used to evaluate types of scoped variables to guide the
     * optimistic compilation, should the call to this method trigger code compilation. Can be null if current runtime
     * scope is not known, but that might cause compilation of code that will need more deoptimization passes.
     * @return generic invoker of this script function
     */
    final MethodHandle getGenericInvoker(final ScriptObject runtimeScope) {
        // This method has race conditions both on genericsInvoker and genericsInvoker.invoker, but even if invoked
        // concurrently, they'll create idempotent results, so it doesn't matter. We could alternatively implement this
        // using java.util.concurrent.AtomicReferenceFieldUpdater, but it's hardly worth it.
        final GenericInvokers lgenericInvokers = ensureGenericInvokers();
        MethodHandle invoker = lgenericInvokers.invoker;
        if(invoker == null) {
            lgenericInvokers.invoker = invoker = createGenericInvoker(runtimeScope);
        }
        return invoker;
    }

    private MethodHandle createGenericInvoker(final ScriptObject runtimeScope) {
        return makeGenericMethod(getGeneric(runtimeScope).createComposableInvoker());
    }

    final MethodHandle getGenericConstructor(final ScriptObject runtimeScope) {
        // This method has race conditions both on genericsInvoker and genericsInvoker.constructor, but even if invoked
        // concurrently, they'll create idempotent results, so it doesn't matter. We could alternatively implement this
        // using java.util.concurrent.AtomicReferenceFieldUpdater, but it's hardly worth it.
        final GenericInvokers lgenericInvokers = ensureGenericInvokers();
        MethodHandle constructor = lgenericInvokers.constructor;
        if(constructor == null) {
            lgenericInvokers.constructor = constructor = createGenericConstructor(runtimeScope);
        }
        return constructor;
    }

    private MethodHandle createGenericConstructor(final ScriptObject runtimeScope) {
        return makeGenericMethod(getGeneric(runtimeScope).createComposableConstructor());
    }

    private GenericInvokers ensureGenericInvokers() {
        GenericInvokers lgenericInvokers = genericInvokers;
        if(lgenericInvokers == null) {
            genericInvokers = lgenericInvokers = new GenericInvokers();
        }
        return lgenericInvokers;
    }

    private static MethodType widen(final MethodType cftype) {
        final Class<?>[] paramTypes = new Class<?>[cftype.parameterCount()];
        for (int i = 0; i < cftype.parameterCount(); i++) {
            paramTypes[i] = cftype.parameterType(i).isPrimitive() ? cftype.parameterType(i) : Object.class;
        }
        return MH.type(cftype.returnType(), paramTypes);
    }

    /**
     * Used to find an apply to call version that fits this callsite.
     * We cannot just, as in the normal matcher case, return e.g. (Object, Object, int)
     * for (Object, Object, int, int, int) or we will destroy the semantics and get
     * a function that, when padded with undefined values, behaves differently
     * @param type actual call site type
     * @return apply to call that perfectly fits this callsite or null if none found
     */
    CompiledFunction lookupExactApplyToCall(final MethodType type) {
        for (final CompiledFunction cf : code) {
            if (!cf.isApplyToCall()) {
                continue;
            }

            final MethodType cftype = cf.type();
            if (cftype.parameterCount() != type.parameterCount()) {
                continue;
            }

            if (widen(cftype).equals(widen(type))) {
                return cf;
            }
        }

        return null;
    }

    CompiledFunction pickFunction(final MethodType callSiteType, final boolean canPickVarArg) {
        for (final CompiledFunction candidate : code) {
            if (candidate.matchesCallSite(callSiteType, canPickVarArg)) {
                return candidate;
            }
        }
        return null;
    }

    /**
     * Returns the best function for the specified call site type.
     * @param callSiteType The call site type. Call site types are expected to have the form
     * {@code (callee, this[, args...])}.
     * @param runtimeScope the runtime scope. It can be used to evaluate types of scoped variables to guide the
     * optimistic compilation, should the call to this method trigger code compilation. Can be null if current runtime
     * scope is not known, but that might cause compilation of code that will need more deoptimization passes.
     * @param linkLogicOkay is a CompiledFunction with a LinkLogic acceptable?
     * @return the best function for the specified call site type.
     */
    abstract CompiledFunction getBest(final MethodType callSiteType, final ScriptObject runtimeScope, final Collection<CompiledFunction> forbidden, final boolean linkLogicOkay);

    /**
     * Returns the best function for the specified call site type.
     * @param callSiteType The call site type. Call site types are expected to have the form
     * {@code (callee, this[, args...])}.
     * @param runtimeScope the runtime scope. It can be used to evaluate types of scoped variables to guide the
     * optimistic compilation, should the call to this method trigger code compilation. Can be null if current runtime
     * scope is not known, but that might cause compilation of code that will need more deoptimization passes.
     * @return the best function for the specified call site type.
     */
    final CompiledFunction getBest(final MethodType callSiteType, final ScriptObject runtimeScope, final Collection<CompiledFunction> forbidden) {
        return getBest(callSiteType, runtimeScope, forbidden, true);
    }

    boolean isValidCallSite(final MethodType callSiteType) {
        return callSiteType.parameterCount() >= 2  && // Must have at least (callee, this)
               callSiteType.parameterType(0).isAssignableFrom(ScriptFunction.class); // Callee must be assignable from script function
    }

    CompiledFunction getGeneric(final ScriptObject runtimeScope) {
        return getBest(getGenericType(), runtimeScope, CompiledFunction.NO_FUNCTIONS, false);
    }

    /**
     * Get a method type for a generic invoker.
     * @return the method type for the generic invoker
     */
    abstract MethodType getGenericType();

    /**
     * Allocates an object using this function's allocator.
     *
     * @param map the property map for the allocated object.
     * @return the object allocated using this function's allocator, or null if the function doesn't have an allocator.
     */
    ScriptObject allocate(final PropertyMap map) {
        return null;
    }

    /**
     * Get the property map to use for objects allocated by this function.
     *
     * @param prototype the prototype of the allocated object
     * @return the property map for allocated objects.
     */
    PropertyMap getAllocatorMap(final ScriptObject prototype) {
        return null;
    }

    /**
     * This method is used to create the immutable portion of a bound function.
     * See {@link ScriptFunction#createBound(Object, Object[])}
     *
     * @param fn the original function being bound
     * @param self this reference to bind. Can be null.
     * @param args additional arguments to bind. Can be null.
     */
    ScriptFunctionData makeBoundFunctionData(final ScriptFunction fn, final Object self, final Object[] args) {
        final Object[] allArgs = args == null ? ScriptRuntime.EMPTY_ARRAY : args;
        final int length = args == null ? 0 : args.length;
        // Clear the callee and this flags
        final int boundFlags = flags & ~NEEDS_CALLEE & ~USES_THIS;

        final List<CompiledFunction> boundList = new LinkedList<>();
        final ScriptObject runtimeScope = fn.getScope();
        final CompiledFunction bindTarget = new CompiledFunction(getGenericInvoker(runtimeScope), getGenericConstructor(runtimeScope), null);
        boundList.add(bind(bindTarget, fn, self, allArgs));

        return new FinalScriptFunctionData(name, Math.max(0, getArity() - length), boundList, boundFlags);
    }

    /**
     * Convert this argument for non-strict functions according to ES 10.4.3
     *
     * @param thiz the this argument
     *
     * @return the converted this object
     */
    private Object convertThisObject(final Object thiz) {
        return needsWrappedThis() ? wrapThis(thiz) : thiz;
    }

    static Object wrapThis(final Object thiz) {
        if (!(thiz instanceof ScriptObject)) {
            if (JSType.nullOrUndefined(thiz)) {
                return Context.getGlobal();
            }

            if (isPrimitiveThis(thiz)) {
                return Context.getGlobal().wrapAsObject(thiz);
            }
        }

        return thiz;
    }

    static boolean isPrimitiveThis(final Object obj) {
        return JSType.isString(obj) || obj instanceof Number || obj instanceof Boolean;
    }

    /**
     * Creates an invoker method handle for a bound function.
     *
     * @param targetFn the function being bound
     * @param originalInvoker an original invoker method handle for the function. This can be its generic invoker or
     * any of its specializations.
     * @param self the "this" value being bound
     * @param args additional arguments being bound
     *
     * @return a bound invoker method handle that will bind the self value and the specified arguments. The resulting
     * invoker never needs a callee; if the original invoker needed it, it will be bound to {@code fn}. The resulting
     * invoker still takes an initial {@code this} parameter, but it is always dropped and the bound {@code self} passed
     * to the original invoker on invocation.
     */
    private MethodHandle bindInvokeHandle(final MethodHandle originalInvoker, final ScriptFunction targetFn, final Object self, final Object[] args) {
        // Is the target already bound? If it is, we won't bother binding either callee or self as they're already bound
        // in the target and will be ignored anyway.
        final boolean isTargetBound = targetFn.isBoundFunction();

        final boolean needsCallee = needsCallee(originalInvoker);
        assert needsCallee == needsCallee() : "callee contract violation 2";
        assert !(isTargetBound && needsCallee); // already bound functions don't need a callee

        final Object boundSelf = isTargetBound ? null : convertThisObject(self);
        final MethodHandle boundInvoker;

        if (isVarArg(originalInvoker)) {
            // First, bind callee and this without arguments
            final MethodHandle noArgBoundInvoker;

            if (isTargetBound) {
                // Don't bind either callee or this
                noArgBoundInvoker = originalInvoker;
            } else if (needsCallee) {
                // Bind callee and this
                noArgBoundInvoker = MH.insertArguments(originalInvoker, 0, targetFn, boundSelf);
            } else {
                // Only bind this
                noArgBoundInvoker = MH.bindTo(originalInvoker, boundSelf);
            }
            // Now bind arguments
            if (args.length > 0) {
                boundInvoker = varArgBinder(noArgBoundInvoker, args);
            } else {
                boundInvoker = noArgBoundInvoker;
            }
        } else {
            // If target is already bound, insert additional bound arguments after "this" argument, at position 1.
            final int argInsertPos = isTargetBound ? 1 : 0;
            final Object[] boundArgs = new Object[Math.min(originalInvoker.type().parameterCount() - argInsertPos, args.length + (isTargetBound ? 0 : needsCallee  ? 2 : 1))];
            int next = 0;
            if (!isTargetBound) {
                if (needsCallee) {
                    boundArgs[next++] = targetFn;
                }
                boundArgs[next++] = boundSelf;
            }
            // If more bound args were specified than the function can take, we'll just drop those.
            System.arraycopy(args, 0, boundArgs, next, boundArgs.length - next);
            // If target is already bound, insert additional bound arguments after "this" argument, at position 1;
            // "this" will get dropped anyway by the target invoker. We previously asserted that already bound functions
            // don't take a callee parameter, so we can know that the signature is (this[, args...]) therefore args
            // start at position 1. If the function is not bound, we start inserting arguments at position 0.
            boundInvoker = MH.insertArguments(originalInvoker, argInsertPos, boundArgs);
        }

        if (isTargetBound) {
            return boundInvoker;
        }

        // If the target is not already bound, add a dropArguments that'll throw away the passed this
        return MH.dropArguments(boundInvoker, 0, Object.class);
    }

    /**
     * Creates a constructor method handle for a bound function using the passed constructor handle.
     *
     * @param originalConstructor the constructor handle to bind. It must be a composed constructor.
     * @param fn the function being bound
     * @param args arguments being bound
     *
     * @return a bound constructor method handle that will bind the specified arguments. The resulting constructor never
     * needs a callee; if the original constructor needed it, it will be bound to {@code fn}. The resulting constructor
     * still takes an initial {@code this} parameter and passes it to the underlying original constructor. Finally, if
     * this script function data object has no constructor handle, null is returned.
     */
    private static MethodHandle bindConstructHandle(final MethodHandle originalConstructor, final ScriptFunction fn, final Object[] args) {
        assert originalConstructor != null;

        // If target function is already bound, don't bother binding the callee.
        final MethodHandle calleeBoundConstructor = fn.isBoundFunction() ? originalConstructor :
            MH.dropArguments(MH.bindTo(originalConstructor, fn), 0, ScriptFunction.class);

        if (args.length == 0) {
            return calleeBoundConstructor;
        }

        if (isVarArg(calleeBoundConstructor)) {
            return varArgBinder(calleeBoundConstructor, args);
        }

        final Object[] boundArgs;

        final int maxArgCount = calleeBoundConstructor.type().parameterCount() - 1;
        if (args.length <= maxArgCount) {
            boundArgs = args;
        } else {
            boundArgs = new Object[maxArgCount];
            System.arraycopy(args, 0, boundArgs, 0, maxArgCount);
        }

        return MH.insertArguments(calleeBoundConstructor, 1, boundArgs);
    }

    /**
     * Takes a method handle, and returns a potentially different method handle that can be used in
     * {@code ScriptFunction#invoke(Object, Object...)} or {code ScriptFunction#construct(Object, Object...)}.
     * The returned method handle will be sure to return {@code Object}, and will have all its parameters turned into
     * {@code Object} as well, except for the following ones:
     * <ul>
     *   <li>a last parameter of type {@code Object[]} which is used for vararg functions,</li>
     *   <li>the first argument, which is forced to be {@link ScriptFunction}, in case the function receives itself
     *   (callee) as an argument.</li>
     * </ul>
     *
     * @param mh the original method handle
     *
     * @return the new handle, conforming to the rules above.
     */
    private static MethodHandle makeGenericMethod(final MethodHandle mh) {
        final MethodType type = mh.type();
        final MethodType newType = makeGenericType(type);
        return type.equals(newType) ? mh : mh.asType(newType);
    }

    private static MethodType makeGenericType(final MethodType type) {
        MethodType newType = type.generic();
        if (isVarArg(type)) {
            newType = newType.changeParameterType(type.parameterCount() - 1, Object[].class);
        }
        if (needsCallee(type)) {
            newType = newType.changeParameterType(0, ScriptFunction.class);
        }
        return newType;
    }

    /**
     * Execute this script function.
     *
     * @param self  Target object.
     * @param arguments  Call arguments.
     * @return ScriptFunction result.
     *
     * @throws Throwable if there is an exception/error with the invocation or thrown from it
     */
    Object invoke(final ScriptFunction fn, final Object self, final Object... arguments) throws Throwable {
        final MethodHandle mh      = getGenericInvoker(fn.getScope());
        final Object       selfObj = convertThisObject(self);
        final Object[]     args    = arguments == null ? ScriptRuntime.EMPTY_ARRAY : arguments;

        DebuggerSupport.notifyInvoke(mh);

        if (isVarArg(mh)) {
            if (needsCallee(mh)) {
                return mh.invokeExact(fn, selfObj, args);
            }
            return mh.invokeExact(selfObj, args);
        }

        final int paramCount = mh.type().parameterCount();
        if (needsCallee(mh)) {
            switch (paramCount) {
            case 2:
                return mh.invokeExact(fn, selfObj);
            case 3:
                return mh.invokeExact(fn, selfObj, getArg(args, 0));
            case 4:
                return mh.invokeExact(fn, selfObj, getArg(args, 0), getArg(args, 1));
            case 5:
                return mh.invokeExact(fn, selfObj, getArg(args, 0), getArg(args, 1), getArg(args, 2));
            case 6:
                return mh.invokeExact(fn, selfObj, getArg(args, 0), getArg(args, 1), getArg(args, 2), getArg(args, 3));
            case 7:
                return mh.invokeExact(fn, selfObj, getArg(args, 0), getArg(args, 1), getArg(args, 2), getArg(args, 3), getArg(args, 4));
            case 8:
                return mh.invokeExact(fn, selfObj, getArg(args, 0), getArg(args, 1), getArg(args, 2), getArg(args, 3), getArg(args, 4), getArg(args, 5));
            default:
                return mh.invokeWithArguments(withArguments(fn, selfObj, paramCount, args));
            }
        }

        switch (paramCount) {
        case 1:
            return mh.invokeExact(selfObj);
        case 2:
            return mh.invokeExact(selfObj, getArg(args, 0));
        case 3:
            return mh.invokeExact(selfObj, getArg(args, 0), getArg(args, 1));
        case 4:
            return mh.invokeExact(selfObj, getArg(args, 0), getArg(args, 1), getArg(args, 2));
        case 5:
            return mh.invokeExact(selfObj, getArg(args, 0), getArg(args, 1), getArg(args, 2), getArg(args, 3));
        case 6:
            return mh.invokeExact(selfObj, getArg(args, 0), getArg(args, 1), getArg(args, 2), getArg(args, 3), getArg(args, 4));
        case 7:
            return mh.invokeExact(selfObj, getArg(args, 0), getArg(args, 1), getArg(args, 2), getArg(args, 3), getArg(args, 4), getArg(args, 5));
        default:
            return mh.invokeWithArguments(withArguments(null, selfObj, paramCount, args));
        }
    }

    Object construct(final ScriptFunction fn, final Object... arguments) throws Throwable {
        final MethodHandle mh   = getGenericConstructor(fn.getScope());
        final Object[]     args = arguments == null ? ScriptRuntime.EMPTY_ARRAY : arguments;

        DebuggerSupport.notifyInvoke(mh);

        if (isVarArg(mh)) {
            if (needsCallee(mh)) {
                return mh.invokeExact(fn, args);
            }
            return mh.invokeExact(args);
        }

        final int paramCount = mh.type().parameterCount();
        if (needsCallee(mh)) {
            switch (paramCount) {
            case 1:
                return mh.invokeExact(fn);
            case 2:
                return mh.invokeExact(fn, getArg(args, 0));
            case 3:
                return mh.invokeExact(fn, getArg(args, 0), getArg(args, 1));
            case 4:
                return mh.invokeExact(fn, getArg(args, 0), getArg(args, 1), getArg(args, 2));
            case 5:
                return mh.invokeExact(fn, getArg(args, 0), getArg(args, 1), getArg(args, 2), getArg(args, 3));
            case 6:
                return mh.invokeExact(fn, getArg(args, 0), getArg(args, 1), getArg(args, 2), getArg(args, 3), getArg(args, 4));
            case 7:
                return mh.invokeExact(fn, getArg(args, 0), getArg(args, 1), getArg(args, 2), getArg(args, 3), getArg(args, 4), getArg(args, 5));
            default:
                return mh.invokeWithArguments(withArguments(fn, paramCount, args));
            }
        }

        switch (paramCount) {
        case 0:
            return mh.invokeExact();
        case 1:
            return mh.invokeExact(getArg(args, 0));
        case 2:
            return mh.invokeExact(getArg(args, 0), getArg(args, 1));
        case 3:
            return mh.invokeExact(getArg(args, 0), getArg(args, 1), getArg(args, 2));
        case 4:
            return mh.invokeExact(getArg(args, 0), getArg(args, 1), getArg(args, 2), getArg(args, 3));
        case 5:
            return mh.invokeExact(getArg(args, 0), getArg(args, 1), getArg(args, 2), getArg(args, 3), getArg(args, 4));
        case 6:
            return mh.invokeExact(getArg(args, 0), getArg(args, 1), getArg(args, 2), getArg(args, 3), getArg(args, 4), getArg(args, 5));
        default:
            return mh.invokeWithArguments(withArguments(null, paramCount, args));
        }
    }

    private static Object getArg(final Object[] args, final int i) {
        return i < args.length ? args[i] : UNDEFINED;
    }

    private static Object[] withArguments(final ScriptFunction fn, final int argCount, final Object[] args) {
        final Object[] finalArgs = new Object[argCount];

        int nextArg = 0;
        if (fn != null) {
            //needs callee
            finalArgs[nextArg++] = fn;
        }

        // Don't add more args that there is argCount in the handle (including self and callee).
        for (int i = 0; i < args.length && nextArg < argCount;) {
            finalArgs[nextArg++] = args[i++];
        }

        // If we have fewer args than argCount, pad with undefined.
        while (nextArg < argCount) {
            finalArgs[nextArg++] = UNDEFINED;
        }

        return finalArgs;
    }

    private static Object[] withArguments(final ScriptFunction fn, final Object self, final int argCount, final Object[] args) {
        final Object[] finalArgs = new Object[argCount];

        int nextArg = 0;
        if (fn != null) {
            //needs callee
            finalArgs[nextArg++] = fn;
        }
        finalArgs[nextArg++] = self;

        // Don't add more args that there is argCount in the handle (including self and callee).
        for (int i = 0; i < args.length && nextArg < argCount;) {
            finalArgs[nextArg++] = args[i++];
        }

        // If we have fewer args than argCount, pad with undefined.
        while (nextArg < argCount) {
            finalArgs[nextArg++] = UNDEFINED;
        }

        return finalArgs;
    }
    /**
     * Takes a variable-arity method and binds a variable number of arguments in it. The returned method will filter the
     * vararg array and pass a different array that prepends the bound arguments in front of the arguments passed on
     * invocation
     *
     * @param mh the handle
     * @param args the bound arguments
     *
     * @return the bound method handle
     */
    private static MethodHandle varArgBinder(final MethodHandle mh, final Object[] args) {
        assert args != null;
        assert args.length > 0;
        return MH.filterArguments(mh, mh.type().parameterCount() - 1, MH.bindTo(BIND_VAR_ARGS, args));
    }

    /**
     * Heuristic to figure out if the method handle has a callee argument. If it's type is
     * {@code (ScriptFunction, ...)}, then we'll assume it has a callee argument. We need this as
     * the constructor above is not passed this information, and can't just blindly assume it's false
     * (notably, it's being invoked for creation of new scripts, and scripts have scopes, therefore
     * they also always receive a callee).
     *
     * @param mh the examined method handle
     *
     * @return true if the method handle expects a callee, false otherwise
     */
    protected static boolean needsCallee(final MethodHandle mh) {
        return needsCallee(mh.type());
    }

    static boolean needsCallee(final MethodType type) {
        final int length = type.parameterCount();

        if (length == 0) {
            return false;
        }

        final Class<?> param0 = type.parameterType(0);
        return param0 == ScriptFunction.class || param0 == boolean.class && length > 1 && type.parameterType(1) == ScriptFunction.class;
    }

    /**
     * Check if a javascript function methodhandle is a vararg handle
     *
     * @param mh method handle to check
     *
     * @return true if vararg
     */
    protected static boolean isVarArg(final MethodHandle mh) {
        return isVarArg(mh.type());
    }

    static boolean isVarArg(final MethodType type) {
        return type.parameterType(type.parameterCount() - 1).isArray();
    }

    /**
     * Is this ScriptFunction declared in a dynamic context
     * @return true if in dynamic context, false if not or irrelevant
     */
    public boolean inDynamicContext() {
        return false;
    }

    @SuppressWarnings("unused")
    private static Object[] bindVarArgs(final Object[] array1, final Object[] array2) {
        if (array2 == null) {
            // Must clone it, as we can't allow the receiving method to alter the array
            return array1.clone();
        }

        final int l2 = array2.length;
        if (l2 == 0) {
            return array1.clone();
        }

        final int l1 = array1.length;
        final Object[] concat = new Object[l1 + l2];
        System.arraycopy(array1, 0, concat, 0, l1);
        System.arraycopy(array2, 0, concat, l1, l2);

        return concat;
    }

    private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) {
        return MH.findStatic(MethodHandles.lookup(), ScriptFunctionData.class, name, MH.type(rtype, types));
    }

    /**
     * This class is used to hold the generic invoker and generic constructor pair. It is structured in this way since
     * most functions will never use them, so this way ScriptFunctionData only pays storage cost for one null reference
     * to the GenericInvokers object, instead of two null references for the two method handles.
     */
    private static final class GenericInvokers {
        volatile MethodHandle invoker;
        volatile MethodHandle constructor;
    }

    private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        code = new LinkedList<>();
    }
}