aboutsummaryrefslogtreecommitdiff
path: root/src/jdk/nashorn/internal/runtime/linker/JavaAdapterFactory.java
blob: 7b0d6d761907f5c25d7858a8021b82c86abb026e (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
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
/*
 * 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.linker;

import static jdk.internal.org.objectweb.asm.Opcodes.ACC_FINAL;
import static jdk.internal.org.objectweb.asm.Opcodes.ACC_PRIVATE;
import static jdk.internal.org.objectweb.asm.Opcodes.ACC_PUBLIC;
import static jdk.internal.org.objectweb.asm.Opcodes.ACC_STATIC;
import static jdk.internal.org.objectweb.asm.Opcodes.ACC_SUPER;
import static jdk.internal.org.objectweb.asm.Opcodes.ACC_VARARGS;
import static jdk.internal.org.objectweb.asm.Opcodes.ACONST_NULL;
import static jdk.internal.org.objectweb.asm.Opcodes.ALOAD;
import static jdk.internal.org.objectweb.asm.Opcodes.ARETURN;
import static jdk.internal.org.objectweb.asm.Opcodes.ASTORE;
import static jdk.internal.org.objectweb.asm.Opcodes.DUP;
import static jdk.internal.org.objectweb.asm.Opcodes.IFNONNULL;
import static jdk.internal.org.objectweb.asm.Opcodes.ILOAD;
import static jdk.internal.org.objectweb.asm.Opcodes.ISTORE;
import static jdk.internal.org.objectweb.asm.Opcodes.RETURN;
import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
import static jdk.nashorn.internal.lookup.Lookup.MH;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.security.AccessController;
import java.security.AllPermission;
import java.security.CodeSigner;
import java.security.CodeSource;
import java.security.Permissions;
import java.security.PrivilegedAction;
import java.security.PrivilegedExceptionAction;
import java.security.ProtectionDomain;
import java.security.SecureClassLoader;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import jdk.internal.dynalink.beans.StaticClass;
import jdk.internal.dynalink.support.LinkRequestImpl;
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.Label;
import jdk.internal.org.objectweb.asm.Opcodes;
import jdk.internal.org.objectweb.asm.Type;
import jdk.internal.org.objectweb.asm.commons.InstructionAdapter;
import jdk.nashorn.internal.objects.NativeJava;
import jdk.nashorn.internal.runtime.Context;
import jdk.nashorn.internal.runtime.ECMAErrors;
import jdk.nashorn.internal.runtime.ECMAException;
import jdk.nashorn.internal.runtime.ScriptFunction;
import jdk.nashorn.internal.runtime.ScriptObject;
import jdk.nashorn.internal.runtime.ScriptRuntime;
import jdk.nashorn.internal.runtime.Undefined;

/**
 * <p>A factory class that generates adapter classes. Adapter classes allow implementation of Java interfaces and
 * extending of Java classes from JavaScript. For every combination of a superclass to extend and interfaces to
 * implement (collectively: "original types"), exactly one adapter class is generated that extends the specified
 * superclass and implements the specified interfaces.
 * </p><p>
 * The adapter class is generated in a new secure class loader that inherits Nashorn's protection domain, and has either
 * one of the original types' class loader or the Nashorn's class loader as its parent - the parent class loader
 * is chosen so that all the original types and the Nashorn core classes are visible from it (as the adapter will have
 * constant pool references to ScriptObject and ScriptFunction classes). In case none of the candidate class loaders has
 * visibility of all the required types, an error is thrown.
 * </p><p>
 * For every protected or public constructor in the extended class, the adapter class will have one or two public
 * constructors (visibility of protected constructors in the extended class is promoted to public). In every case, for
 * every original constructor, a new constructor taking a trailing ScriptObject argument preceded by original
 * constructor arguments is present on the adapter class. When such a constructor is invoked, the passed ScriptObject's
 * member functions are used to implement and/or override methods on the original class, dispatched by name. A single
 * JavaScript function will act as the implementation for all overloaded methods of the same name. When methods on an
 * adapter instance are invoked, the functions are invoked having the ScriptObject passed in the instance constructor as
 * their "this". Subsequent changes to the ScriptObject (reassignment or removal of its functions) are not reflected in
 * the adapter instance; the method implementations are bound to functions at constructor invocation time.
 * {@code java.lang.Object} methods {@code equals}, {@code hashCode}, and {@code toString} can also be overridden. The
 * only restriction is that since every JavaScript object already has a {@code toString} function through the
 * {@code Object.prototype}, the {@code toString} in the adapter is only overridden if the passed ScriptObject has a
 * {@code toString} function as its own property, and not inherited from a prototype. All other adapter methods can be
 * implemented or overridden through a prototype-inherited function of the ScriptObject passed to the constructor too.
 * </p><p>
 * If the original types collectively have only one abstract method, or have several of them, but all share the
 * same name, an additional constructor is provided for every original constructor; this one takes a ScriptFunction as
 * its last argument preceded by original constructor arguments. This constructor will use the passed function as the
 * implementation for all abstract methods. For consistency, any concrete methods sharing the single abstract method
 * name will also be overridden by the function. When methods on the adapter instance are invoked, the ScriptFunction is
 * invoked with {@code null} as its "this".
 * </p><p>
 * For adapter methods that return values, all the JavaScript-to-Java conversions supported by Nashorn will be in effect
 * to coerce the JavaScript function return value to the expected Java return type.
 * </p><p>
 * Since we are adding a trailing argument to the generated constructors in the adapter class, they will never be
 * declared as variable arity, even if the original constructor in the superclass was declared as variable arity. The
 * reason we are passing the additional argument at the end of the argument list instead at the front is that the
 * source-level script expression <code>new X(a, b) { ... }</code> (which is a proprietary syntax extension Nashorn uses
 * to resemble Java anonymous classes) is actually equivalent to <code>new X(a, b, { ... })</code>.
 * </p><p>
 * You normally don't use this class directly, but rather either create adapters from script using
 * {@link NativeJava#extend(Object, Object...)}, using the {@code new} operator on abstract classes and interfaces (see
 * {@link NativeJava#type(Object, Object)}), or implicitly when passing script functions to Java methods expecting SAM
 * types.
 * </p>
 */

public final class JavaAdapterFactory {
    private static final Type SCRIPT_FUNCTION_TYPE = Type.getType(ScriptFunction.class);
    private static final Type SCRIPT_OBJECT_TYPE = Type.getType(ScriptObject.class);
    private static final Type OBJECT_TYPE = Type.getType(Object.class);
    private static final Type STRING_TYPE = Type.getType(String.class);
    private static final Type CONTEXT_TYPE = Type.getType(Context.class);
    private static final Type METHOD_TYPE_TYPE = Type.getType(MethodType.class);
    private static final Type METHOD_HANDLE_TYPE = Type.getType(MethodHandle.class);
    private static final String GET_HANDLE_OBJECT_DESCRIPTOR = Type.getMethodDescriptor(METHOD_HANDLE_TYPE,
            OBJECT_TYPE, STRING_TYPE, METHOD_TYPE_TYPE, Type.BOOLEAN_TYPE);
    private static final String GET_HANDLE_FUNCTION_DESCRIPTOR = Type.getMethodDescriptor(METHOD_HANDLE_TYPE,
            SCRIPT_FUNCTION_TYPE, METHOD_TYPE_TYPE, Type.BOOLEAN_TYPE);
    private static final Type RUNTIME_EXCEPTION_TYPE = Type.getType(RuntimeException.class);
    private static final Type THROWABLE_TYPE = Type.getType(Throwable.class);
    private static final Type PRIVILEGED_ACTION_TYPE = Type.getType(PrivilegedAction.class);
    private static final Type UNSUPPORTED_OPERATION_TYPE = Type.getType(UnsupportedOperationException.class);

    private static final String THIS_CLASS_TYPE_NAME = Type.getInternalName(JavaAdapterFactory.class);
    private static final String RUNTIME_EXCEPTION_TYPE_NAME = RUNTIME_EXCEPTION_TYPE.getInternalName();
    private static final String ERROR_TYPE_NAME = Type.getInternalName(Error.class);
    private static final String THROWABLE_TYPE_NAME = THROWABLE_TYPE.getInternalName();
    private static final String CONTEXT_TYPE_NAME = CONTEXT_TYPE.getInternalName();
    private static final String OBJECT_TYPE_NAME = OBJECT_TYPE.getInternalName();
    private static final String PRIVILEGED_ACTION_TYPE_NAME = PRIVILEGED_ACTION_TYPE.getInternalName();
    private static final String UNSUPPORTED_OPERATION_TYPE_NAME = UNSUPPORTED_OPERATION_TYPE.getInternalName();

    private static final String METHOD_HANDLE_TYPE_DESCRIPTOR = METHOD_HANDLE_TYPE.getDescriptor();
    private static final String SCRIPT_OBJECT_TYPE_DESCRIPTOR = SCRIPT_OBJECT_TYPE.getDescriptor();
    private static final String GET_GLOBAL_METHOD_DESCRIPTOR = Type.getMethodDescriptor(SCRIPT_OBJECT_TYPE);
    private static final String SET_GLOBAL_METHOD_DESCRIPTOR = Type.getMethodDescriptor(Type.VOID_TYPE, SCRIPT_OBJECT_TYPE);
    private static final String GET_CLASS_METHOD_DESCRIPTOR = Type.getMethodDescriptor(Type.getType(Class.class));
    private static final String PRIVILEGED_RUN_METHOD_DESCRIPTOR = Type.getMethodDescriptor(OBJECT_TYPE);

    // Package used when the adapter can't be defined in the adaptee's package (either because it's sealed, or because
    // it's a java.* package.
    private static final String ADAPTER_PACKAGE_PREFIX = "jdk/nashorn/internal/javaadapters/";
    // Class name suffix used to append to the adaptee class name, when it can be defined in the adaptee's package.
    private static final String ADAPTER_CLASS_NAME_SUFFIX = "$$NashornJavaAdapter";
    private static final String JAVA_PACKAGE_PREFIX = "java/";
    private static final int MAX_GENERATED_TYPE_NAME_LENGTH = 238; //255 - 17; 17 is the maximum possible length for the global setter inner class suffix

    private static final String INIT = "<init>";
    private static final String VOID_NOARG = Type.getMethodDescriptor(Type.VOID_TYPE);
    private static final String GLOBAL_FIELD_NAME = "global";

    /**
     * Contains various outcomes for attempting to generate an adapter class. These are stored in AdapterInfo instances.
     * We have a successful outcome (adapter class was generated) and four possible error outcomes: superclass is final,
     * superclass is not public, superclass has no public or protected constructor, more than one superclass was
     * specified. We don't throw exceptions when we try to generate the adapter, but rather just record these error
     * conditions as they are still useful as partial outcomes, as Nashorn's linker can still successfully check whether
     * the class can be autoconverted from a script function even when it is not possible to generate an adapter for it.
     */
    private enum AdaptationOutcome {
        SUCCESS,
        ERROR_FINAL_CLASS,
        ERROR_NON_PUBLIC_CLASS,
        ERROR_NO_ACCESSIBLE_CONSTRUCTOR,
        ERROR_MULTIPLE_SUPERCLASSES,
        ERROR_NO_COMMON_LOADER
    }

    /**
     * Collection of methods we never override: Object.clone(), Object.finalize().
     */
    private static final Collection<MethodInfo> EXCLUDED = getExcludedMethods();

    /**
     * A mapping from an original Class object to AdapterInfo representing the adapter for the class it represents.
     */
    private static final ClassValue<Map<List<Class<?>>, AdapterInfo>> ADAPTER_INFO_MAPS = new ClassValue<Map<List<Class<?>>, AdapterInfo>>() {
        @Override
        protected Map<List<Class<?>>, AdapterInfo> computeValue(final Class<?> type) {
            return new HashMap<>();
        }
    };

    private static final Random random = new SecureRandom();
    private static final ProtectionDomain GENERATED_PROTECTION_DOMAIN = createGeneratedProtectionDomain();

    // This is the superclass for our generated adapter.
    private final Class<?> superClass;
    // Class loader used as the parent for the class loader we'll create to load the generated class. It will be a class
    // loader that has the visibility of all original types (class to extend and interfaces to implement) and of the
    // Nashorn classes.
    private final ClassLoader commonLoader;

    // Binary name of the superClass
    private final String superClassName;
    // Binary name of the generated class.
    private final String generatedClassName;
    // Binary name of the PrivilegedAction inner class that is used to
    private final String globalSetterClassName;
    private final Set<String> usedFieldNames = new HashSet<>();
    private final Set<String> abstractMethodNames = new HashSet<>();
    private final String samName;
    private final Set<MethodInfo> finalMethods = new HashSet<>(EXCLUDED);
    private final Set<MethodInfo> methodInfos = new HashSet<>();
    private boolean autoConvertibleFromFunction = false;

    private final ClassWriter cw;

    /**
     * Creates a factory that will produce the adapter type for the specified original type.
     * @param originalType the type for which this factory will generate the adapter type.
     * @param definingClassAndLoader the class in whose ClassValue we'll store the generated adapter, and its class loader.
     * @throws AdaptationException if the adapter can not be generated for some reason.
     */
    private JavaAdapterFactory(final Class<?> superType, final List<Class<?>> interfaces, final ClassAndLoader definingClassAndLoader) throws AdaptationException {
        assert superType != null && !superType.isInterface();
        assert interfaces != null;
        assert definingClassAndLoader != null;

        this.superClass = superType;
        this.commonLoader = findCommonLoader(definingClassAndLoader);
        cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS) {
            @Override
            protected String getCommonSuperClass(final String type1, final String type2) {
                // We need to override ClassWriter.getCommonSuperClass to use this factory's commonLoader as a class
                // loader to find the common superclass of two types when needed.
                return JavaAdapterFactory.this.getCommonSuperClass(type1, type2);
            }
        };
        superClassName = Type.getInternalName(superType);
        generatedClassName = getGeneratedClassName(superType, interfaces);

        // Randomize the name of the privileged global setter, to make it non-feasible to find.
        final long l;
        synchronized(random) {
            l = random.nextLong();
        }
        // NOTE: they way this class name is calculated affects the value of MAX_GENERATED_TYPE_NAME_LENGTH constant. If
        // you change the calculation of globalSetterClassName, adjust the constant too.
        globalSetterClassName = generatedClassName.concat("$" + Long.toHexString(l & Long.MAX_VALUE));
        cw.visit(Opcodes.V1_7, ACC_PUBLIC | ACC_SUPER | ACC_FINAL, generatedClassName, null, superClassName, getInternalTypeNames(interfaces));
        cw.visitField(ACC_PRIVATE | ACC_FINAL, GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR, null, null).visitEnd();
        usedFieldNames.add(GLOBAL_FIELD_NAME);

        gatherMethods(superType);
        gatherMethods(interfaces);
        samName = abstractMethodNames.size() == 1 ? abstractMethodNames.iterator().next() : null;
        generateFields();
        generateConstructors();
        generateMethods();
        // }
        cw.visitEnd();
    }

    private static String getGeneratedClassName(final Class<?> superType, final List<Class<?>> interfaces) {
        // The class we use to primarily name our adapter is either the superclass, or if it is Object (meaning we're
        // just implementing interfaces or extending Object), then the first implemented interface or Object.
        final Class<?> namingType = superType == Object.class ? (interfaces.isEmpty()? Object.class : interfaces.get(0)) : superType;
        final Package pkg = namingType.getPackage();
        final String namingTypeName = Type.getInternalName(namingType);
        final StringBuilder buf = new StringBuilder();
        if (namingTypeName.startsWith(JAVA_PACKAGE_PREFIX) || pkg == null || pkg.isSealed()) {
            // Can't define new classes in java.* packages
            buf.append(ADAPTER_PACKAGE_PREFIX).append(namingTypeName);
        } else {
            buf.append(namingTypeName).append(ADAPTER_CLASS_NAME_SUFFIX);
        }
        final Iterator<Class<?>> it = interfaces.iterator();
        if(superType == Object.class && it.hasNext()) {
            it.next(); // Skip first interface, it was used to primarily name the adapter
        }
        // Append interface names to the adapter name
        while(it.hasNext()) {
            buf.append("$$").append(it.next().getSimpleName());
        }
        return buf.toString().substring(0, Math.min(MAX_GENERATED_TYPE_NAME_LENGTH, buf.length()));
    }

    /**
     * Given a list of class objects, return an array with their binary names. Used to generate the array of interface
     * names to implement.
     * @param classes the classes
     * @return an array of names
     */
    private static String[] getInternalTypeNames(final List<Class<?>> classes) {
        final int interfaceCount = classes.size();
        final String[] interfaceNames = new String[interfaceCount];
        for(int i = 0; i < interfaceCount; ++i) {
            interfaceNames[i] = Type.getInternalName(classes.get(i));
        }
        return interfaceNames;
    }

    /**
     * Utility method used by few other places in the code. Tests if the class has the abstract modifier and is not an
     * array class. For some reason, array classes have the abstract modifier set in HotSpot JVM, and we don't want to
     * treat array classes as abstract.
     * @param clazz the inspected class
     * @return true if the class is abstract and is not an array type.
     */
    static boolean isAbstractClass(final Class<?> clazz) {
        return Modifier.isAbstract(clazz.getModifiers()) && !clazz.isArray();
    }

    /**
     * Returns an adapter class for the specified original types. The adapter class extends/implements the original
     * class/interfaces.
     * @param types the original types. The caller must pass at least one Java type representing either a public
     * interface or a non-final public class with at least one public or protected constructor. If more than one type is
     * specified, at most one can be a class and the rest have to be interfaces. The class can be in any position in the
     * array. Invoking the method twice with exactly the same types in the same order will return the same adapter
     * class, any reordering of types or even addition or removal of redundant types (i.e. interfaces that other types
     * in the list already implement/extend, or {@code java.lang.Object} in a list of types consisting purely of
     * interfaces) will result in a different adapter class, even though those adapter classes are functionally
     * identical; we deliberately don't want to incur the additional processing cost of canonicalizing type lists.
     * @return an adapter class. See this class' documentation for details on the generated adapter class.
     * @throws ECMAException with a TypeError if the adapter class can not be generated because the original class is
     * final, non-public, or has no public or protected constructors.
     */
    public static StaticClass getAdapterClassFor(final Class<?>[] types) {
        assert types != null && types.length > 0;
        final AdapterInfo adapterInfo = getAdapterInfo(types);

        final StaticClass clazz = adapterInfo.adapterClass;
        if (clazz != null) {
            return clazz;
        }
        adapterInfo.adaptationOutcome.typeError();

        throw new AssertionError();
    }

    private static AdapterInfo getAdapterInfo(final Class<?>[] types) {
        final ClassAndLoader definingClassAndLoader = getDefiningClassAndLoader(types);

        final Map<List<Class<?>>, AdapterInfo> adapterInfoMap = ADAPTER_INFO_MAPS.get(definingClassAndLoader.clazz);
        final List<Class<?>> typeList = types.length == 1 ? getSingletonClassList(types[0]) : Arrays.asList(types.clone());
        AdapterInfo adapterInfo;
        synchronized(adapterInfoMap) {
            adapterInfo = adapterInfoMap.get(typeList);
            if(adapterInfo == null) {
                adapterInfo = createAdapterInfo(types, definingClassAndLoader);
                adapterInfoMap.put(typeList, adapterInfo);
            }
        }
        return adapterInfo;
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    private static List<Class<?>> getSingletonClassList(final Class<?> clazz) {
        return (List)Collections.singletonList(clazz);
    }

    /**
     * Returns whether an instance of the specified class/interface can be generated from a ScriptFunction. Returns true
     * iff: the adapter for the class/interface can be created, it is abstract (this includes interfaces), it has at
     * least one abstract method, all the abstract methods share the same name, and it has a public or protected default
     * constructor. Note that invoking this class will most likely result in the adapter class being defined in the JVM
     * if it hasn't been already.
     * @param clazz the inspected class
     * @return true iff an instance of the specified class/interface can be generated from a ScriptFunction.
     */
    static boolean isAutoConvertibleFromFunction(final Class<?> clazz) {
        return getAdapterInfo(new Class<?>[] { clazz }).autoConvertibleFromFunction;
    }

    /**
     * Returns a method handle representing a constructor that takes a single argument of the source type (which,
     * really, should be one of {@link ScriptObject}, {@link ScriptFunction}, or {@link Object}, and returns an instance
     * of the adapter for the target type. Used to implement the function autoconverters as well as the Nashorn's
     * JSR-223 script engine's {@code getInterface()} method.
     * @param sourceType the source type; should be either {@link ScriptObject}, {@link ScriptFunction}, or
     * {@link Object}. In case of {@code Object}, it will return a method handle that dispatches to either the script
     * object or function constructor at invocation based on the actual argument.
     * @param targetType the target type, for which adapter instances will be created
     * @return the constructor method handle.
     * @throws Exception if anything goes wrong
     */
    public static MethodHandle getConstructor(final Class<?> sourceType, final Class<?> targetType) throws Exception {
        final StaticClass adapterClass = getAdapterClassFor(new Class<?>[] { targetType });
        return AccessController.doPrivileged(new PrivilegedExceptionAction<MethodHandle>() {
            @Override
            public MethodHandle run() throws Exception {
                return  MH.bindTo(Bootstrap.getLinkerServices().getGuardedInvocation(new LinkRequestImpl(NashornCallSiteDescriptor.get(
                    "dyn:new", MethodType.methodType(targetType, StaticClass.class, sourceType), 0), false,
                    adapterClass, null)).getInvocation(), adapterClass);
            }
        });
    }

    /**
     * Finishes the bytecode generation for the adapter class that was started in the constructor, and loads the
     * bytecode as a new class into the JVM.
     * @return the generated adapter class
     */
    private Class<?> generateClass() {
        final String binaryName = generatedClassName.replace('/', '.');
        try {
            return Class.forName(binaryName, true, createClassLoader(commonLoader, binaryName, cw.toByteArray(),
                    globalSetterClassName.replace('/', '.')));
        } catch (final ClassNotFoundException e) {
            throw new AssertionError(e); // cannot happen
        }
    }

    /**
     * Tells if the given Class is an adapter or support class
     * @param clazz Class object
     * @return true if the Class given is adapter or support class
     */
    public static boolean isAdapterClass(Class<?> clazz) {
        return clazz.getClassLoader() instanceof AdapterLoader;
    }

    private static class AdapterLoader extends SecureClassLoader {
        AdapterLoader(ClassLoader parent) {
            super(parent);
        }
    }

    // Creation of class loader is in a separate static method so that it doesn't retain a reference to the factory
    // instance. Note that the adapter class is created in the protection domain of the class/interface being
    // extended/implemented, and only the privileged global setter action class is generated in the protection domain
    // of Nashorn itself. Also note that the creation and loading of the global setter is deferred until it is
    // required by JVM linker, which will only happen on first invocation of any of the adapted method. We could defer
    // it even more by separating its invocation into a separate static method on the adapter class, but then someone
    // with ability to introspect on the class and use setAccessible(true) on it could invoke the method. It's a
    // security tradeoff...
    private static ClassLoader createClassLoader(final ClassLoader parentLoader, final String className,
            final byte[] classBytes, final String privilegedActionClassName) {
        return new AdapterLoader(parentLoader) {
            private final ClassLoader myLoader = getClass().getClassLoader();
            private final ProtectionDomain myProtectionDomain = getClass().getProtectionDomain();

            @Override
            public Class<?> loadClass(final String name, final boolean resolve) throws ClassNotFoundException {
                try {
                    return super.loadClass(name, resolve);
                } catch (final SecurityException se) {
                    // we may be implementing an interface or extending a class that was
                    // loaded by a loader that prevents package.access. If so, it'd throw
                    // SecurityException for nashorn's classes!. For adapter's to work, we
                    // should be able to refer to nashorn classes.
                    if (name.startsWith("jdk.nashorn.internal.")) {
                        return myLoader.loadClass(name);
                    }
                    throw se;
                }
            }

            @Override
            protected Class<?> findClass(final String name) throws ClassNotFoundException {
                if(name.equals(className)) {
                    final byte[] bytes = classBytes;
                    return defineClass(name, bytes, 0, bytes.length, GENERATED_PROTECTION_DOMAIN);
                } else if(name.equals(privilegedActionClassName)) {
                    final byte[] bytes = generatePrivilegedActionClassBytes(privilegedActionClassName.replace('.', '/'));
                    return defineClass(name, bytes, 0, bytes.length, myProtectionDomain);
                } else {
                    throw new ClassNotFoundException(name);
                }
            }
        };
    }

    /**
     * Generates a PrivilegedAction implementation class for invoking {@link Context#setGlobal(ScriptObject)} from the
     * adapter class.
     */
    private static byte[] generatePrivilegedActionClassBytes(final String className) {
        final ClassWriter w = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
        // class GlobalSetter implements PrivilegedAction {
        w.visit(Opcodes.V1_7, ACC_SUPER | ACC_FINAL, className, null, OBJECT_TYPE_NAME, new String[] {
                PRIVILEGED_ACTION_TYPE_NAME
        });

        // private final ScriptObject global;
        w.visitField(ACC_PRIVATE | ACC_FINAL, GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR, null, null).visitEnd();

        // private GlobalSetter(ScriptObject global) {
        InstructionAdapter mv = new InstructionAdapter(w.visitMethod(ACC_PRIVATE, INIT,
                SET_GLOBAL_METHOD_DESCRIPTOR, null, new String[0]));
        mv.visitCode();
        // super();
        mv.visitVarInsn(ALOAD, 0);
        mv.invokespecial(OBJECT_TYPE_NAME, INIT, VOID_NOARG);
        // this.global = global;
        mv.visitVarInsn(ALOAD, 0);
        mv.visitVarInsn(ALOAD, 1);
        mv.putfield(className, GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR);

        mv.visitInsn(RETURN);
        mv.visitEnd();
        mv.visitMaxs(0, 0);

        // public Object run() {
        mv = new InstructionAdapter(w.visitMethod(ACC_PUBLIC, "run", PRIVILEGED_RUN_METHOD_DESCRIPTOR, null,
                new String[0]));
        mv.visitCode();
        // Context.setGlobal(this.global);
        mv.visitVarInsn(ALOAD, 0);
        mv.getfield(className, GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR);
        mv.invokestatic(CONTEXT_TYPE_NAME, "setGlobal", SET_GLOBAL_METHOD_DESCRIPTOR);
        // return null;
        mv.visitInsn(ACONST_NULL);
        mv.visitInsn(ARETURN);

        mv.visitEnd();
        mv.visitMaxs(0, 0);

        // static void setGlobal(ScriptObject global) {
        mv = new InstructionAdapter(w.visitMethod(ACC_STATIC, "setGlobal", SET_GLOBAL_METHOD_DESCRIPTOR, null,
                new String[0]));
        mv.visitCode();
        // new GlobalSetter(ScriptObject global)
        mv.anew(Type.getType("L" + className + ";"));
        mv.dup();
        mv.visitVarInsn(ALOAD, 0);
        mv.invokespecial(className, INIT, SET_GLOBAL_METHOD_DESCRIPTOR);
        // AccessController.doPrivileged(...)
        mv.invokestatic(Type.getInternalName(AccessController.class), "doPrivileged", Type.getMethodDescriptor(
                OBJECT_TYPE, PRIVILEGED_ACTION_TYPE));
        mv.pop();
        mv.visitInsn(RETURN);

        mv.visitEnd();
        mv.visitMaxs(0, 0);

        return w.toByteArray();
    }

    private void generateFields() {
        for (final MethodInfo mi: methodInfos) {
            cw.visitField(ACC_PRIVATE | ACC_FINAL, mi.methodHandleFieldName, METHOD_HANDLE_TYPE_DESCRIPTOR, null, null).visitEnd();
        }
    }

    private void generateConstructors() throws AdaptationException {
        boolean gotCtor = false;
        for (final Constructor<?> ctor: superClass.getDeclaredConstructors()) {
            final int modifier = ctor.getModifiers();
            if((modifier & (Modifier.PUBLIC | Modifier.PROTECTED)) != 0) {
                generateConstructor(ctor);
                gotCtor = true;
            }
        }
        if(!gotCtor) {
            throw new AdaptationException(AdaptationOutcome.ERROR_NO_ACCESSIBLE_CONSTRUCTOR, superClass.getCanonicalName());
        }
    }

    boolean isAutoConvertibleFromFunction() {
        return autoConvertibleFromFunction;
    }

    private void generateConstructor(final Constructor<?> ctor) {
        // Generate a constructor that delegates to ctor, but takes an additional ScriptObject parameter at the
        // beginning of its parameter list.
        generateConstructor(ctor, false);

        if (samName != null) {
            if (!autoConvertibleFromFunction && ctor.getParameterTypes().length == 0) {
                // If the original type only has a single abstract method name, as well as a default ctor, then it can
                // be automatically converted from JS function.
                autoConvertibleFromFunction = true;
            }
            // If all our abstract methods have a single name, generate an additional constructor, one that takes a
            // ScriptFunction as its first parameter and assigns it as the implementation for all abstract methods.
            generateConstructor(ctor, true);
        }
    }

    /**
     * Generates a constructor for the adapter class. This constructor will take the same arguments as the supertype
     * constructor passed as the argument here, and delegate to it. However, it will take an additional argument of
     * either ScriptObject or ScriptFunction type (based on the value of the "fromFunction" parameter), and initialize
     * all the method handle fields of the adapter instance with functions from the script object (or the script
     * function itself, if that's what's passed). There is one method handle field in the adapter class for every method
     * that can be implemented or overridden; the name of every field is same as the name of the method, with a number
     * suffix that makes it unique in case of overloaded methods. The generated constructor will invoke
     * {@link #getHandle(ScriptFunction, MethodType, boolean)} or {@link #getHandle(Object, String, MethodType,
     * boolean)} to obtain the method handles; these methods make sure to add the necessary conversions and arity
     * adjustments so that the resulting method handles can be invoked from generated methods using {@code invokeExact}.
     * The constructor that takes a script function will only initialize the methods with the same name as the single
     * abstract method. The constructor will also store the Nashorn global that was current at the constructor
     * invocation time in a field named "global". The generated constructor will be public, regardless of whether the
     * supertype constructor was public or protected. The generated constructor will not be variable arity, even if the
     * supertype constructor was.
     * @param ctor the supertype constructor that is serving as the base for the generated constructor.
     * @param fromFunction true if we're generating a constructor that initializes SAM types from a single
     * ScriptFunction passed to it, false if we're generating a constructor that initializes an arbitrary type from a
     * ScriptObject passed to it.
     */
    private void generateConstructor(final Constructor<?> ctor, final boolean fromFunction) {
        final Type originalCtorType = Type.getType(ctor);
        final Type[] originalArgTypes = originalCtorType.getArgumentTypes();
        final int argLen = originalArgTypes.length;
        final Type[] newArgTypes = new Type[argLen + 1];

        // Insert ScriptFunction|Object as the last argument to the constructor
        final Type extraArgumentType = fromFunction ? SCRIPT_FUNCTION_TYPE : OBJECT_TYPE;
        newArgTypes[argLen] = extraArgumentType;
        System.arraycopy(originalArgTypes, 0, newArgTypes, 0, argLen);

        // All constructors must be public, even if in the superclass they were protected.
        // Existing super constructor <init>(this, args...) triggers generating <init>(this, scriptObj, args...).
        final InstructionAdapter mv = new InstructionAdapter(cw.visitMethod(ACC_PUBLIC, INIT,
                Type.getMethodDescriptor(originalCtorType.getReturnType(), newArgTypes), null, null));

        mv.visitCode();
        // First, invoke super constructor with original arguments. If the form of the constructor we're generating is
        // <init>(this, args..., scriptFn), then we're invoking super.<init>(this, args...).
        mv.visitVarInsn(ALOAD, 0);
        final Class<?>[] argTypes = ctor.getParameterTypes();
        int offset = 1; // First arg is at position 1, after this.
        for (int i = 0; i < argLen; ++i) {
            final Type argType = Type.getType(argTypes[i]);
            mv.load(offset, argType);
            offset += argType.getSize();
        }
        mv.invokespecial(superClassName, INIT, originalCtorType.getDescriptor());

        // Get a descriptor to the appropriate "JavaAdapterFactory.getHandle" method.
        final String getHandleDescriptor = fromFunction ? GET_HANDLE_FUNCTION_DESCRIPTOR : GET_HANDLE_OBJECT_DESCRIPTOR;

        // Assign MethodHandle fields through invoking getHandle()
        for (final MethodInfo mi : methodInfos) {
            mv.visitVarInsn(ALOAD, 0);
            if (fromFunction && !mi.getName().equals(samName)) {
                // Constructors initializing from a ScriptFunction only initialize methods with the SAM name.
                // NOTE: if there's a concrete overloaded method sharing the SAM name, it'll be overriden too. This
                // is a deliberate design choice. All other method handles are initialized to null.
                mv.visitInsn(ACONST_NULL);
            } else {
                mv.visitVarInsn(ALOAD, offset);
                if(!fromFunction) {
                    mv.aconst(mi.getName());
                }
                mv.aconst(Type.getMethodType(mi.type.toMethodDescriptorString()));
                mv.iconst(mi.method.isVarArgs() ? 1 : 0);
                mv.invokestatic(THIS_CLASS_TYPE_NAME, "getHandle", getHandleDescriptor);
            }
            mv.putfield(generatedClassName, mi.methodHandleFieldName, METHOD_HANDLE_TYPE_DESCRIPTOR);
        }

        // Assign "this.global = Context.getGlobal()"
        mv.visitVarInsn(ALOAD, 0);
        invokeGetGlobal(mv);
        mv.dup();
        mv.invokevirtual(OBJECT_TYPE_NAME, "getClass", GET_CLASS_METHOD_DESCRIPTOR); // check against null Context
        mv.pop();
        mv.putfield(generatedClassName, GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR);

        // Wrap up
        mv.visitInsn(RETURN);
        mv.visitMaxs(0, 0);
        mv.visitEnd();
    }

    private static void invokeGetGlobal(final InstructionAdapter mv) {
        mv.invokestatic(CONTEXT_TYPE_NAME, "getGlobal", GET_GLOBAL_METHOD_DESCRIPTOR);
    }

    private void invokeSetGlobal(final InstructionAdapter mv) {
        mv.invokestatic(globalSetterClassName, "setGlobal", SET_GLOBAL_METHOD_DESCRIPTOR);
    }

    /**
     * Given a JS script function, binds it to null JS "this", and adapts its parameter types, return types, and arity
     * to the specified type and arity. This method is public mainly for implementation reasons, so the adapter classes
     * can invoke it from their constructors that take a ScriptFunction in its first argument to obtain the method
     * handles for their abstract method implementations.
     * @param fn the script function
     * @param type the method type it has to conform to
     * @param varArg if the Java method for which the function is being adapted is a variable arity method
     * @return the appropriately adapted method handle for invoking the script function.
     */
    public static MethodHandle getHandle(final ScriptFunction fn, final MethodType type, final boolean varArg) {
        // JS "this" will be null for SAMs
        return adaptHandle(fn.getBoundInvokeHandle(null), type, varArg);
    }

    /**
     * Given a JS script object, retrieves a function from it by name, binds it to the script object as its "this", and
     * adapts its parameter types, return types, and arity to the specified type and arity. This method is public mainly
     * for implementation reasons, so the adapter classes can invoke it from their constructors that take a Object
     * in its first argument to obtain the method handles for their method implementations.
     * @param obj the script obj
     * @param name the name of the property that contains the function
     * @param type the method type it has to conform to
     * @param varArg if the Java method for which the function is being adapted is a variable arity method
     * @return the appropriately adapted method handle for invoking the script function, or null if the value of the
     * property is either null or undefined, or "toString" was requested as the name, but the object doesn't directly
     * define it but just inherits it through prototype.
     */
    public static MethodHandle getHandle(final Object obj, final String name, final MethodType type, final boolean varArg) {
        if (! (obj instanceof ScriptObject)) {
            throw typeError("not.an.object", ScriptRuntime.safeToString(obj));
        }

        final ScriptObject sobj = (ScriptObject)obj;
        // Since every JS Object has a toString, we only override "String toString()" it if it's explicitly specified
        if ("toString".equals(name) && !sobj.hasOwnProperty("toString")) {
            return null;
        }

        final Object fnObj = sobj.get(name);
        if (fnObj instanceof ScriptFunction) {
            return adaptHandle(((ScriptFunction)fnObj).getBoundInvokeHandle(sobj), type, varArg);
        } else if(fnObj == null || fnObj instanceof Undefined) {
            return null;
        } else {
            throw typeError("not.a.function", name);
        }
    }

    private static MethodHandle adaptHandle(final MethodHandle handle, final MethodType type, final boolean varArg) {
        return Bootstrap.getLinkerServices().asType(ScriptObject.pairArguments(handle, type, varArg), type);
    }

    /**
     * Encapsulation of the information used to generate methods in the adapter classes. Basically, a wrapper around the
     * reflective Method object, a cached MethodType, and the name of the field in the adapter class that will hold the
     * method handle serving as the implementation of this method in adapter instances.
     *
     */
    private static class MethodInfo {
        private final Method method;
        private final MethodType type;
        private String methodHandleFieldName;

        private MethodInfo(final Class<?> clazz, final String name, final Class<?>... argTypes) throws NoSuchMethodException {
            this(clazz.getDeclaredMethod(name, argTypes));
        }

        private MethodInfo(final Method method) {
            this.method = method;
            this.type   = MH.type(method.getReturnType(), method.getParameterTypes());
        }

        @Override
        public boolean equals(final Object obj) {
            return obj instanceof MethodInfo && equals((MethodInfo)obj);
        }

        private boolean equals(final MethodInfo other) {
            // Only method name and type are used for comparison; method handle field name is not.
            return getName().equals(other.getName()) && type.equals(other.type);
        }

        String getName() {
            return method.getName();
        }

        @Override
        public int hashCode() {
            return getName().hashCode() ^ type.hashCode();
        }

        void setIsCanonical(final Set<String> usedFieldNames) {
            int i = 0;
            String fieldName = getName();
            while(!usedFieldNames.add(fieldName)) {
                fieldName = getName() + (i++);
            }
            methodHandleFieldName = fieldName;
        }
    }

    private void generateMethods() {
        for(final MethodInfo mi: methodInfos) {
            generateMethod(mi);
        }
    }

    /**
     * Generates a method in the adapter class that adapts a method from the original class. The generated methods will
     * inspect the method handle field assigned to them. If it is null (the JS object doesn't provide an implementation
     * for the method) then it will either invoke its version in the supertype, or if it is abstract, throw an
     * {@link UnsupportedOperationException}. Otherwise, if the method handle field's value is not null, the handle is
     * invoked using invokeExact (signature polymorphic invocation as per JLS 15.12.3). Before the invocation, the
     * current Nashorn {@link Context} is checked, and if it is different than the global used to create the adapter
     * instance, the creating global is set to be the current global. In this case, the previously current global is
     * restored after the invocation. If invokeExact results in a Throwable that is not one of the method's declared
     * exceptions, and is not an unchecked throwable, then it is wrapped into a {@link RuntimeException} and the runtime
     * exception is thrown. The method handle retrieved from the field is guaranteed to exactly match the signature of
     * the method; this is guaranteed by the way constructors of the adapter class obtain them using
     * {@link #getHandle(Object, String, MethodType, boolean)}.
     * @param mi the method info describing the method to be generated.
     */
    private void generateMethod(final MethodInfo mi) {
        final Method method = mi.method;
        final int mod = method.getModifiers();
        final int access = ACC_PUBLIC | (method.isVarArgs() ? ACC_VARARGS : 0);
        final Class<?>[] exceptions = method.getExceptionTypes();
        final String[] exceptionNames = new String[exceptions.length];
        for (int i = 0; i < exceptions.length; ++i) {
            exceptionNames[i] = Type.getInternalName(exceptions[i]);
        }
        final MethodType type = mi.type;
        final String methodDesc = type.toMethodDescriptorString();
        final String name = mi.getName();

        final Type asmType = Type.getMethodType(methodDesc);
        final Type[] asmArgTypes = asmType.getArgumentTypes();

        // Determine the first index for a local variable
        int nextLocalVar = 1; // this
        for(final Type t: asmArgTypes) {
            nextLocalVar += t.getSize();
        }

        final InstructionAdapter mv = new InstructionAdapter(cw.visitMethod(access, name, methodDesc, null,
                exceptionNames));
        mv.visitCode();

        final Label methodHandleNotNull = new Label();
        final Label methodEnd = new Label();

        final Type returnType = Type.getType(type.returnType());

        // Get the method handle
        mv.visitVarInsn(ALOAD, 0);
        mv.getfield(generatedClassName, mi.methodHandleFieldName, METHOD_HANDLE_TYPE_DESCRIPTOR);
        mv.visitInsn(DUP); // It'll remain on the stack all the way until the invocation
        // Check if the method handle is null
        mv.visitJumpInsn(IFNONNULL, methodHandleNotNull);
        if(Modifier.isAbstract(mod)) {
            // If it's null, and the method is abstract, throw an exception
            mv.anew(UNSUPPORTED_OPERATION_TYPE);
            mv.dup();
            mv.invokespecial(UNSUPPORTED_OPERATION_TYPE_NAME, INIT, VOID_NOARG);
            mv.athrow();
        } else {
            // If it's null, and the method is not abstract, delegate to super method.
            mv.visitVarInsn(ALOAD, 0);
            int nextParam = 1;
            for(final Type t: asmArgTypes) {
                mv.load(nextParam, t);
                nextParam += t.getSize();
            }
            mv.invokespecial(superClassName, name, methodDesc);
            mv.areturn(returnType);
        }

        mv.visitLabel(methodHandleNotNull);
        final int currentGlobalVar = nextLocalVar++;
        final int globalsDifferVar = nextLocalVar++;

        // Emit code for switching to the creating global
        // ScriptObject currentGlobal = Context.getGlobal();
        invokeGetGlobal(mv);
        mv.dup();
        mv.visitVarInsn(ASTORE, currentGlobalVar);
        // if(this.global == currentGlobal) {
        loadGlobalOnStack(mv);
        final Label globalsDiffer = new Label();
        mv.ifacmpne(globalsDiffer);
        //     globalsDiffer = false
        mv.iconst(0); // false
        final Label proceed = new Label();
        mv.goTo(proceed);
        mv.visitLabel(globalsDiffer);
        // } else {
        //     Context.setGlobal(this.global);
        loadGlobalOnStack(mv);
        invokeSetGlobal(mv);
        //     globalsDiffer = true
        mv.iconst(1);

        mv.visitLabel(proceed);
        mv.visitVarInsn(ISTORE, globalsDifferVar);

        // Load all parameters back on stack for dynamic invocation.
        int varOffset = 1;
        for (final Type t : asmArgTypes) {
            mv.load(varOffset, t);
            varOffset += t.getSize();
        }

        // Invoke the target method handle
        final Label tryBlockStart = new Label();
        mv.visitLabel(tryBlockStart);
        mv.invokevirtual(METHOD_HANDLE_TYPE.getInternalName(), "invokeExact", type.toMethodDescriptorString());
        final Label tryBlockEnd = new Label();
        mv.visitLabel(tryBlockEnd);
        emitFinally(mv, currentGlobalVar, globalsDifferVar);
        mv.areturn(returnType);

        // If Throwable is not declared, we need an adapter from Throwable to RuntimeException
        final boolean throwableDeclared = isThrowableDeclared(exceptions);
        final Label throwableHandler;
        if (!throwableDeclared) {
            // Add "throw new RuntimeException(Throwable)" handler for Throwable
            throwableHandler = new Label();
            mv.visitLabel(throwableHandler);
            mv.anew(RUNTIME_EXCEPTION_TYPE);
            mv.dupX1();
            mv.swap();
            mv.invokespecial(RUNTIME_EXCEPTION_TYPE_NAME, INIT, Type.getMethodDescriptor(Type.VOID_TYPE, THROWABLE_TYPE));
            // Fall through to rethrow handler
        } else {
            throwableHandler = null;
        }
        final Label rethrowHandler = new Label();
        mv.visitLabel(rethrowHandler);
        // Rethrow handler for RuntimeException, Error, and all declared exception types
        emitFinally(mv, currentGlobalVar, globalsDifferVar);
        mv.athrow();
        mv.visitLabel(methodEnd);

        mv.visitLocalVariable("currentGlobal", SCRIPT_OBJECT_TYPE_DESCRIPTOR, null, methodHandleNotNull, methodEnd, currentGlobalVar);
        mv.visitLocalVariable("globalsDiffer", Type.INT_TYPE.getDescriptor(), null, methodHandleNotNull, methodEnd, globalsDifferVar);

        if(throwableDeclared) {
            mv.visitTryCatchBlock(tryBlockStart, tryBlockEnd, rethrowHandler, THROWABLE_TYPE_NAME);
            assert throwableHandler == null;
        } else {
            mv.visitTryCatchBlock(tryBlockStart, tryBlockEnd, rethrowHandler, RUNTIME_EXCEPTION_TYPE_NAME);
            mv.visitTryCatchBlock(tryBlockStart, tryBlockEnd, rethrowHandler, ERROR_TYPE_NAME);
            for(final String excName: exceptionNames) {
                mv.visitTryCatchBlock(tryBlockStart, tryBlockEnd, rethrowHandler, excName);
            }
            mv.visitTryCatchBlock(tryBlockStart, tryBlockEnd, throwableHandler, THROWABLE_TYPE_NAME);
        }
        mv.visitMaxs(0, 0);
        mv.visitEnd();
    }

    /**
     * Emit code to restore the previous Nashorn Context when needed.
     * @param mv the instruction adapter
     * @param currentGlobalVar index of the local variable holding the reference to the current global at method
     * entry.
     * @param globalsDifferVar index of the boolean local variable that is true if the global needs to be restored.
     */
    private void emitFinally(final InstructionAdapter mv, final int currentGlobalVar, final int globalsDifferVar) {
        // Emit code to restore the previous Nashorn global if needed
        mv.visitVarInsn(ILOAD, globalsDifferVar);
        final Label skip = new Label();
        mv.ifeq(skip);
        mv.visitVarInsn(ALOAD, currentGlobalVar);
        invokeSetGlobal(mv);
        mv.visitLabel(skip);
    }

    private void loadGlobalOnStack(final InstructionAdapter mv) {
        mv.visitVarInsn(ALOAD, 0);
        mv.getfield(generatedClassName, GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR);
    }

    private static boolean isThrowableDeclared(final Class<?>[] exceptions) {
        for (final Class<?> exception : exceptions) {
            if (exception == Throwable.class) {
                return true;
            }
        }
        return false;
    }

    /**
     * Gathers methods that can be implemented or overridden from the specified type into this factory's
     * {@link #methodInfos} set. It will add all non-final, non-static methods that are either public or protected from
     * the type if the type itself is public. If the type is a class, the method will recursively invoke itself for its
     * superclass and the interfaces it implements, and add further methods that were not directly declared on the
     * class.
     * @param type the type defining the methods.
     */
    private void gatherMethods(final Class<?> type) {
        if (Modifier.isPublic(type.getModifiers())) {
            final Method[] typeMethods = type.isInterface() ? type.getMethods() : type.getDeclaredMethods();

            for (final Method typeMethod: typeMethods) {
                final int m = typeMethod.getModifiers();
                if (Modifier.isStatic(m)) {
                    continue;
                }
                if (Modifier.isPublic(m) || Modifier.isProtected(m)) {
                    final MethodInfo mi = new MethodInfo(typeMethod);
                    if (Modifier.isFinal(m)) {
                        finalMethods.add(mi);
                    } else if (!finalMethods.contains(mi) && methodInfos.add(mi)) {
                        if (Modifier.isAbstract(m)) {
                            abstractMethodNames.add(mi.getName());
                        }
                        mi.setIsCanonical(usedFieldNames);
                    }
                }
            }
        }
        // If the type is a class, visit its superclasses and declared interfaces. If it's an interface, we're done.
        // Needing to invoke the method recursively for a non-interface Class object is the consequence of needing to
        // see all declared protected methods, and Class.getDeclaredMethods() doesn't provide those declared in a
        // superclass. For interfaces, we used Class.getMethods(), as we're only interested in public ones there, and
        // getMethods() does provide those declared in a superinterface.
        if (!type.isInterface()) {
            final Class<?> superType = type.getSuperclass();
            if (superType != null) {
                gatherMethods(superType);
            }
            for (final Class<?> itf: type.getInterfaces()) {
                gatherMethods(itf);
            }
        }
    }

    private void gatherMethods(final List<Class<?>> classes) {
        for(final Class<?> c: classes) {
            gatherMethods(c);
        }
    }

    /**
     * Creates a collection of methods that are not final, but we still never allow them to be overridden in adapters,
     * as explicitly declaring them automatically is a bad idea. Currently, this means {@code Object.finalize()} and
     * {@code Object.clone()}.
     * @return a collection of method infos representing those methods that we never override in adapter classes.
     */
    private static Collection<MethodInfo> getExcludedMethods() {
        return AccessController.doPrivileged(new PrivilegedAction<Collection<MethodInfo>>() {
            @Override
            public Collection<MethodInfo> run() {
                try {
                    return Arrays.asList(
                            new MethodInfo(Object.class, "finalize"),
                            new MethodInfo(Object.class, "clone"));
                } catch (final NoSuchMethodException e) {
                    throw new AssertionError(e);
                }
            }
        });
    }

    private static ProtectionDomain createGeneratedProtectionDomain() {
        // Generated classes need to have AllPermission. Since we require the "createClassLoader" RuntimePermission, we
        // can create a class loader that'll load new classes with any permissions. Our generated classes are just
        // delegating adapters, so having AllPermission can't cause anything wrong; the effective set of permissions for
        // the executing script functions will still be limited by the permissions of the caller and the permissions of
        // the script.
        final Permissions permissions = new Permissions();
        permissions.add(new AllPermission());
        return new ProtectionDomain(new CodeSource(null, (CodeSigner[])null), permissions);
    }

    private static class AdapterInfo {
        final StaticClass adapterClass;
        final boolean autoConvertibleFromFunction;
        final AnnotatedAdaptationOutcome adaptationOutcome;

        AdapterInfo(final StaticClass adapterClass, final boolean autoConvertibleFromFunction) {
            this.adapterClass = adapterClass;
            this.autoConvertibleFromFunction = autoConvertibleFromFunction;
            this.adaptationOutcome = AnnotatedAdaptationOutcome.SUCCESS;
        }

        AdapterInfo(final AdaptationOutcome outcome, final String classList) {
            this(new AnnotatedAdaptationOutcome(outcome, classList));
        }

        AdapterInfo(final AnnotatedAdaptationOutcome adaptationOutcome) {
            this.adapterClass = null;
            this.autoConvertibleFromFunction = false;
            this.adaptationOutcome = adaptationOutcome;
        }
    }

    /**
     * An adaptation outcome accompanied with a name of a class (or a list of multiple class names) that are the reason
     * an adapter could not be generated.
     */
    private static class AnnotatedAdaptationOutcome {
        static final AnnotatedAdaptationOutcome SUCCESS = new AnnotatedAdaptationOutcome(AdaptationOutcome.SUCCESS, "");

        private final AdaptationOutcome adaptationOutcome;
        private final String classList;

        AnnotatedAdaptationOutcome(final AdaptationOutcome adaptationOutcome, final String classList) {
            this.adaptationOutcome = adaptationOutcome;
            this.classList = classList;
        }

        void typeError() {
            assert adaptationOutcome != AdaptationOutcome.SUCCESS;
            throw ECMAErrors.typeError("extend." + adaptationOutcome, classList);
        }
    }

    /**
     * For a given class, create its adapter class and associated info.
     * @param type the class for which the adapter is created
     * @return the adapter info for the class.
     */
    private static AdapterInfo createAdapterInfo(final Class<?>[] types, final ClassAndLoader definingClassAndLoader) {
        Class<?> superClass = null;
        final List<Class<?>> interfaces = new ArrayList<>(types.length);
        for(final Class<?> t: types) {
            final int mod = t.getModifiers();
            if(!t.isInterface()) {
                if(superClass != null) {
                    return new AdapterInfo(AdaptationOutcome.ERROR_MULTIPLE_SUPERCLASSES, t.getCanonicalName() + " and " + superClass.getCanonicalName());
                }
                if (Modifier.isFinal(mod)) {
                    return new AdapterInfo(AdaptationOutcome.ERROR_FINAL_CLASS, t.getCanonicalName());
                }
                superClass = t;
            } else {
                interfaces.add(t);
            }
            if(!Modifier.isPublic(mod)) {
                return new AdapterInfo(AdaptationOutcome.ERROR_NON_PUBLIC_CLASS, t.getCanonicalName());
            }
        }
        final Class<?> effectiveSuperClass = superClass == null ? Object.class : superClass;
        return AccessController.doPrivileged(new PrivilegedAction<AdapterInfo>() {
            @Override
            public AdapterInfo run() {
                try {
                    final JavaAdapterFactory factory = new JavaAdapterFactory(effectiveSuperClass, interfaces, definingClassAndLoader);
                    return new AdapterInfo(StaticClass.forClass(factory.generateClass()),
                            factory.isAutoConvertibleFromFunction());
                } catch (final AdaptationException e) {
                    return new AdapterInfo(e.outcome);
                }
            }
        });
    }

    @SuppressWarnings("serial")
    private static class AdaptationException extends Exception {
        private final AnnotatedAdaptationOutcome outcome;
        AdaptationException(final AdaptationOutcome outcome, final String classList) {
            this.outcome = new AnnotatedAdaptationOutcome(outcome, classList);
        }
    }

    private String getCommonSuperClass(final String type1, final String type2) {
        try {
            final Class<?> c1 = Class.forName(type1.replace('/', '.'), false, commonLoader);
            final Class<?> c2 = Class.forName(type2.replace('/', '.'), false, commonLoader);
            if (c1.isAssignableFrom(c2)) {
                return type1;
            }
            if (c2.isAssignableFrom(c1)) {
                return type2;
            }
            if (c1.isInterface() || c2.isInterface()) {
                return "java/lang/Object";
            }
            return assignableSuperClass(c1, c2).getName().replace('.', '/');
        } catch(final ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    private static Class<?> assignableSuperClass(final Class<?> c1, final Class<?> c2) {
        final Class<?> superClass = c1.getSuperclass();
        return superClass.isAssignableFrom(c2) ? superClass : assignableSuperClass(superClass, c2);
    }

    /**
     * Choose between the passed class loader and the class loader that defines the ScriptObject class, based on which
     * of the two can see the classes in both.
     * @param classAndLoader the loader and a representative class from it that will be used to add the generated
     * adapter to its ADAPTER_INFO_MAPS.
     * @return the class loader that sees both the specified class and Nashorn classes.
     * @throws IllegalStateException if no such class loader is found.
     */
    private static ClassLoader findCommonLoader(final ClassAndLoader classAndLoader) throws AdaptationException {
        final ClassLoader loader = classAndLoader.getLoader();
        if (canSeeClass(loader, ScriptObject.class)) {
            return loader;
        }

        final ClassLoader nashornLoader = ScriptObject.class.getClassLoader();
        if(canSeeClass(nashornLoader, classAndLoader.clazz)) {
            return nashornLoader;
        }

        throw new AdaptationException(AdaptationOutcome.ERROR_NO_COMMON_LOADER, classAndLoader.clazz.getCanonicalName());
    }

    private static boolean canSeeClass(final ClassLoader cl, final Class<?> clazz) {
        try {
            return Class.forName(clazz.getName(), false, cl) == clazz;
        } catch (final ClassNotFoundException e) {
            return false;
        }
    }

    /**
     * Given a list of types that define the superclass/interfaces for an adapter class, returns a single type from the
     * list that will be used to attach the adapter to its ClassValue. The first type in the array that is defined in a
     * class loader that can also see all other types is returned. If there is no such loader, an exception is thrown.
     * @param types the input types
     * @return the first type from the array that is defined in a class loader that can also see all other types.
     */
    private static ClassAndLoader getDefiningClassAndLoader(final Class<?>[] types) {
        // Short circuit the cheap case
        if(types.length == 1) {
            return new ClassAndLoader(types[0], false);
        }

        return AccessController.doPrivileged(new PrivilegedAction<ClassAndLoader>() {
            @Override
            public ClassAndLoader run() {
                return getDefiningClassAndLoaderPrivileged(types);
            }
        });
    }

    private static ClassAndLoader getDefiningClassAndLoaderPrivileged(final Class<?>[] types) {
        final Collection<ClassAndLoader> maximumVisibilityLoaders = getMaximumVisibilityLoaders(types);

        final Iterator<ClassAndLoader> it = maximumVisibilityLoaders.iterator();
        if(maximumVisibilityLoaders.size() == 1) {
            // Fortunate case - single maximally specific class loader; return its representative class.
            return it.next();
        }

        // Ambiguity; throw an error.
        assert maximumVisibilityLoaders.size() > 1; // basically, can't be zero
        final StringBuilder b = new StringBuilder();
        b.append(it.next().clazz.getCanonicalName());
        while(it.hasNext()) {
            b.append(", ").append(it.next().clazz.getCanonicalName());
        }
        throw typeError("extend.ambiguous.defining.class", b.toString());
    }

    /**
     * Given an array of types, return a subset of their class loaders that are maximal according to the
     * "can see other loaders' classes" relation, which is presumed to be a partial ordering.
     * @param types types
     * @return a collection of maximum visibility class loaders. It is guaranteed to have at least one element.
     */
    private static Collection<ClassAndLoader> getMaximumVisibilityLoaders(final Class<?>[] types) {
        final List<ClassAndLoader> maximumVisibilityLoaders = new LinkedList<>();
        outer:  for(final ClassAndLoader maxCandidate: getClassLoadersForTypes(types)) {
            final Iterator<ClassAndLoader> it = maximumVisibilityLoaders.iterator();
            while(it.hasNext()) {
                final ClassAndLoader existingMax = it.next();
                final boolean candidateSeesExisting = canSeeClass(maxCandidate.getRetrievedLoader(), existingMax.clazz);
                final boolean exitingSeesCandidate = canSeeClass(existingMax.getRetrievedLoader(), maxCandidate.clazz);
                if(candidateSeesExisting) {
                    if(!exitingSeesCandidate) {
                        // The candidate sees the the existing maximum, so drop the existing one as it's no longer maximal.
                        it.remove();
                    }
                    // NOTE: there's also the anomalous case where both loaders see each other. Not sure what to do
                    // about that one, as two distinct class loaders both seeing each other's classes is weird and
                    // violates the assumption that the relation "sees others' classes" is a partial ordering. We'll
                    // just not do anything, and treat them as incomparable; hopefully some later class loader that
                    // comes along can eliminate both of them, if it can not, we'll end up with ambiguity anyway and
                    // throw an error at the end.
                } else if(exitingSeesCandidate) {
                    // Existing sees the candidate, so drop the candidate.
                    continue outer;
                }
            }
            // If we get here, no existing maximum visibility loader could see the candidate, so the candidate is a new
            // maximum.
            maximumVisibilityLoaders.add(maxCandidate);
        }
        return maximumVisibilityLoaders;
    }

    private static Collection<ClassAndLoader> getClassLoadersForTypes(final Class<?>[] types) {
        final Map<ClassAndLoader, ClassAndLoader> classesAndLoaders = new LinkedHashMap<>();
        for(final Class<?> c: types) {
            final ClassAndLoader cl = new ClassAndLoader(c, true);
            if(!classesAndLoaders.containsKey(cl)) {
                classesAndLoaders.put(cl, cl);
            }
        }
        return classesAndLoaders.keySet();
    }

    /**
     * A tuple of a class loader and a single class representative of the classes that can be loaded through it. Its
     * equals/hashCode is defined in terms of the identity of the class loader.
     */
    private static final class ClassAndLoader {
        private final Class<?> clazz;
        // Don't access this directly; most of the time, use getRetrievedLoader(), or if you know what you're doing,
        // getLoader().
        private ClassLoader loader;
        // We have mild affinity against eagerly retrieving the loader, as we need to do it in a privileged block. For
        // the most basic case of looking up an already-generated adapter info for a single type, we avoid it.
        private boolean loaderRetrieved;

        ClassAndLoader(final Class<?> clazz, final boolean retrieveLoader) {
            this.clazz = clazz;
            if(retrieveLoader) {
                retrieveLoader();
            }
        }

        ClassLoader getLoader() {
            if(!loaderRetrieved) {
                retrieveLoader();
            }
            return getRetrievedLoader();
        }

        ClassLoader getRetrievedLoader() {
            assert loaderRetrieved;
            return loader;
        }

        private void retrieveLoader() {
            loader = clazz.getClassLoader();
            loaderRetrieved = true;
        }

        @Override
        public boolean equals(final Object obj) {
            return obj instanceof ClassAndLoader && ((ClassAndLoader)obj).getRetrievedLoader() == getRetrievedLoader();
        }

        @Override
        public int hashCode() {
            return System.identityHashCode(getRetrievedLoader());
        }
    }
}