1/*
2 * Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package indify;
27
28import java.util.*;
29import java.io.*;
30import java.lang.reflect.Modifier;
31import java.util.regex.*;
32
33/**
34 * Transform one or more class files to incorporate JSR 292 features,
35 * such as {@code invokedynamic}.
36 * <p>
37 * This is a standalone program in a single source file.
38 * In this form, it may be useful for test harnesses, small experiments, and javadoc examples.
39 * Copies of this file may show up in multiple locations for standalone usage.
40 * The primary maintained location of this file is as follows:
41 * <a href="http://kenai.com/projects/ninja/sources/indify-repo/content/src/indify/Indify.java">
42 * http://kenai.com/projects/ninja/sources/indify-repo/content/src/indify/Indify.java</a>
43 * <p>
44 * Static private methods named MH_x and MT_x (where x is arbitrary)
45 * must be stereotyped generators of MethodHandle and MethodType
46 * constants.  All calls to them are transformed to {@code CONSTANT_MethodHandle}
47 * and {@code CONSTANT_MethodType} "ldc" instructions.
48 * The stereotyped code must create method types by calls to {@code methodType} or
49 * {@code fromMethodDescriptorString}.  The "lookup" argument must be created
50 * by calls to {@code java.lang.invoke.MethodHandles#lookup MethodHandles.lookup}.
51 * The class and string arguments must be constant.
52 * The following methods of {@code java.lang.invoke.MethodHandle.Lookup Lookup} are
53 * allowed for method handle creation: {@code findStatic}, {@code findVirtual},
54 * {@code findConstructor}, {@code findSpecial},
55 * {@code findGetter}, {@code findSetter},
56 * {@code findStaticGetter}, or {@code findStaticSetter}.
57 * The call to one of these methods must be followed immediately
58 * by an {@code areturn} instruction.
59 * The net result of the call to the MH_x or MT_x method must be
60 * the creation of a constant method handle.  Thus, replacing calls
61 * to MH_x or MT_x methods by {@code ldc} instructions should leave
62 * the meaning of the program unchanged.
63 * <p>
64 * Static private methods named INDY_x must be stereotyped generators
65 * of {@code invokedynamic} call sites.
66 * All calls to them must be immediately followed by
67 * {@code invokeExact} calls.
68 * All such pairs of calls are transformed to {@code invokedynamic}
69 * instructions.  Each INDY_x method must begin with a call to a
70 * MH_x method, which is taken to be its bootstrap method.
71 * The method must be immediately invoked (via {@code invokeGeneric}
72 * on constant lookup, name, and type arguments.  An object array of
73 * constants may also be appended to the {@code invokeGeneric call}.
74 * This call must be cast to {@code CallSite}, and the result must be
75 * immediately followed by a call to {@code dynamicInvoker}, with the
76 * resulting method handle returned.
77 * <p>
78 * The net result of all of these actions is equivalent to the JVM's
79 * execution of an {@code invokedynamic} instruction in the unlinked state.
80 * Running this code once should produce the same results as running
81 * the corresponding {@code invokedynamic} instruction.
82 * In order to model the caching behavior, the code of an INDY_x
83 * method is allowed to begin with getstatic, aaload, and if_acmpne
84 * instructions which load a static method handle value and return it
85 * if the value is non-null.
86 * <p>
87 * Example usage:
88 * <blockquote><pre>
89$ JAVA_HOME=(some recent OpenJDK 7 build)
90$ ant
91$ $JAVA_HOME/bin/java -cp build/classes indify.Indify --overwrite --dest build/testout build/classes/indify/Example.class
92$ $JAVA_HOME/bin/java -cp build/classes indify.Example
93MT = (java.lang.Object)java.lang.Object
94MH = adder(int,int)java.lang.Integer
95adder(1,2) = 3
96calling indy:  42
97$ $JAVA_HOME/bin/java -cp build/testout indify.Example
98(same output as above)
99 * </pre></blockquote>
100 * <p>
101 * A version of this transformation built on top of <a href="http://asm.ow2.org/">http://asm.ow2.org/</a> would be welcome.
102 * @author John Rose
103 */
104public class Indify {
105    public static void main(String... av) throws IOException {
106        new Indify().run(av);
107    }
108
109    public File dest;
110    public String[] classpath = {"."};
111    public boolean keepgoing = false;
112    public boolean expandProperties = false;
113    public boolean overwrite = false;
114    public boolean quiet = false;
115    public boolean verbose = false;
116    public boolean all = false;
117    public int verifySpecifierCount = -1;
118
119    public void run(String... av) throws IOException {
120        List<String> avl = new ArrayList<>(Arrays.asList(av));
121        parseOptions(avl);
122        if (avl.isEmpty())
123            throw new IllegalArgumentException("Usage: indify [--dest dir] [option...] file...");
124        if ("--java".equals(avl.get(0))) {
125            avl.remove(0);
126            try {
127                runApplication(avl.toArray(new String[0]));
128            } catch (Exception ex) {
129                if (ex instanceof RuntimeException)  throw (RuntimeException) ex;
130                throw new RuntimeException(ex);
131            }
132            return;
133        }
134        Exception err = null;
135        for (String a : avl) {
136            try {
137                indify(a);
138            } catch (Exception ex) {
139                if (err == null)  err = ex;
140                System.err.println("failure on "+a);
141                if (!keepgoing)  break;
142            }
143        }
144        if (err != null) {
145            if (err instanceof IOException)  throw (IOException) err;
146            throw (RuntimeException) err;
147        }
148    }
149
150    /** Execute the given application under a class loader which indifies all application classes. */
151    public void runApplication(String... av) throws Exception {
152        List<String> avl = new ArrayList<>(Arrays.asList(av));
153        String mainClassName = avl.remove(0);
154        av = avl.toArray(new String[0]);
155        Class<?> mainClass = Class.forName(mainClassName, true, makeClassLoader());
156        java.lang.reflect.Method main = mainClass.getMethod("main", String[].class);
157        try { main.setAccessible(true); } catch (SecurityException ex) { }
158        main.invoke(null, (Object) av);
159    }
160
161    public void parseOptions(List<String> av) throws IOException {
162        for (; !av.isEmpty(); av.remove(0)) {
163            String a = av.get(0);
164            if (a.startsWith("-")) {
165                String a2 = null;
166                int eq = a.indexOf('=');
167                if (eq > 0) {
168                    a2 = maybeExpandProperties(a.substring(eq+1));
169                    a = a.substring(0, eq+1);
170                }
171                switch (a) {
172                case "--java":
173                    return;  // keep this argument
174                case "-d": case "--dest": case "-d=": case "--dest=":
175                    dest = new File(a2 != null ? a2 : maybeExpandProperties(av.remove(1)));
176                    break;
177                case "-cp": case "--classpath":
178                    classpath = maybeExpandProperties(av.remove(1)).split("["+File.pathSeparatorChar+"]");
179                    break;
180                case "-k": case "--keepgoing": case "--keepgoing=":
181                    keepgoing = booleanOption(a2);  // print errors but keep going
182                    break;
183                case "--expand-properties": case "--expand-properties=":
184                    expandProperties = booleanOption(a2);  // expand property references in subsequent arguments
185                    break;
186                case "--verify-specifier-count": case "--verify-specifier-count=":
187                    verifySpecifierCount = Integer.valueOf(a2);
188                    break;
189                case "--overwrite": case "--overwrite=":
190                    overwrite = booleanOption(a2);  // overwrite output files
191                    break;
192                case "--all": case "--all=":
193                    all = booleanOption(a2);  // copy all classes, even if no patterns
194                    break;
195                case "-q": case "--quiet": case "--quiet=":
196                    quiet = booleanOption(a2);  // less output
197                    break;
198                case "-v": case "--verbose": case "--verbose=":
199                    verbose = booleanOption(a2);  // more output
200                    break;
201                default:
202                    throw new IllegalArgumentException("unrecognized flag: "+a);
203                }
204                continue;
205            } else {
206                break;
207            }
208        }
209        if (dest == null && !overwrite)
210            throw new RuntimeException("no output specified; need --dest d or --overwrite");
211        if (expandProperties) {
212            for (int i = 0; i < av.size(); i++)
213                av.set(i, maybeExpandProperties(av.get(i)));
214        }
215    }
216
217    private boolean booleanOption(String s) {
218        if (s == null)  return true;
219        switch (s) {
220        case "true":  case "yes": case "on":  case "1": return true;
221        case "false": case "no":  case "off": case "0": return false;
222        }
223        throw new IllegalArgumentException("unrecognized boolean flag="+s);
224    }
225
226    private String maybeExpandProperties(String s) {
227        if (!expandProperties)  return s;
228        Set<String> propsDone = new HashSet<>();
229        while (s.contains("${")) {
230            int lbrk = s.indexOf("${");
231            int rbrk = s.indexOf('}', lbrk);
232            if (rbrk < 0)  break;
233            String prop = s.substring(lbrk+2, rbrk);
234            if (!propsDone.add(prop))  break;
235            String value = System.getProperty(prop);
236            if (verbose)  System.err.println("expanding ${"+prop+"} => "+value);
237            if (value == null)  break;
238            s = s.substring(0, lbrk) + value + s.substring(rbrk+1);
239        }
240        return s;
241    }
242
243    public void indify(String a) throws IOException {
244        File f = new File(a);
245        String fn = f.getName();
246        if (fn.endsWith(".class") && f.isFile())
247            indifyFile(f, dest);
248        else if (fn.endsWith(".jar") && f.isFile())
249            indifyJar(f, dest);
250        else if (f.isDirectory())
251            indifyTree(f, dest);
252        else if (!keepgoing)
253            throw new RuntimeException("unrecognized file: "+a);
254    }
255
256    private void ensureDirectory(File dir) {
257        if (dir.mkdirs() && !quiet)
258            System.err.println("created "+dir);
259    }
260
261    public void indifyFile(File f, File dest) throws IOException {
262        if (verbose)  System.err.println("reading "+f);
263        ClassFile cf = new ClassFile(f);
264        Logic logic = new Logic(cf);
265        boolean changed = logic.transform();
266        logic.reportPatternMethods(quiet, keepgoing);
267        if (changed || all) {
268            File outfile;
269            if (dest != null) {
270                ensureDirectory(dest);
271                outfile = classPathFile(dest, cf.nameString());
272            } else {
273                outfile = f;  // overwrite input file, no matter where it is
274            }
275            cf.writeTo(outfile);
276            if (!quiet)  System.err.println("wrote "+outfile);
277        }
278    }
279
280    File classPathFile(File pathDir, String className) {
281        String qualname = className.replace('.','/')+".class";
282        qualname = qualname.replace('/', File.separatorChar);
283        return new File(pathDir, qualname);
284    }
285
286    public void indifyJar(File f, Object dest) throws IOException {
287        throw new UnsupportedOperationException("Not yet implemented");
288    }
289
290    public void indifyTree(File f, File dest) throws IOException {
291        if (verbose)  System.err.println("reading directory: "+f);
292        for (File f2 : f.listFiles(new FilenameFilter() {
293                public boolean accept(File dir, String name) {
294                    if (name.endsWith(".class"))  return true;
295                    if (name.contains("."))  return false;
296                    // return true if it might be a package name:
297                    return Character.isJavaIdentifierStart(name.charAt(0));
298                }})) {
299            if (f2.getName().endsWith(".class"))
300                indifyFile(f2, dest);
301            else if (f2.isDirectory())
302                indifyTree(f2, dest);
303        }
304    }
305
306    public ClassLoader makeClassLoader() {
307        return new Loader();
308    }
309    private class Loader extends ClassLoader {
310        Loader() {
311            this(Indify.class.getClassLoader());
312        }
313        Loader(ClassLoader parent) {
314            super(parent);
315        }
316        public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
317            File f = findClassInPath(name);
318            if (f != null) {
319                try {
320                    Class<?> c = transformAndLoadClass(f);
321                    if (c != null) {
322                        if (resolve)  resolveClass(c);
323                        return c;
324                    }
325                } catch (ClassNotFoundException ex) {
326                    // fall through
327                } catch (IOException ex) {
328                    // fall through
329                } catch (Exception ex) {
330                    // pass error from reportPatternMethods, etc.
331                    if (ex instanceof RuntimeException)  throw (RuntimeException) ex;
332                    throw new RuntimeException(ex);
333                }
334            }
335            return super.loadClass(name, resolve);
336        }
337        private File findClassInPath(String name) {
338            for (String s : classpath) {
339                File f = classPathFile(new File(s), name);
340                //System.out.println("Checking for "+f);
341                if (f.exists() && f.canRead()) {
342                    return f;
343                }
344            }
345            return null;
346        }
347        protected Class<?> findClass(String name) throws ClassNotFoundException {
348            try {
349                File f = findClassInPath(name);
350                if (f != null) {
351                    Class<?> c = transformAndLoadClass(f);
352                    if (c != null)  return c;
353                }
354            } catch (IOException ex) {
355                throw new ClassNotFoundException("IO error", ex);
356            }
357            throw new ClassNotFoundException();
358        }
359        private Class<?> transformAndLoadClass(File f) throws ClassNotFoundException, IOException {
360            if (verbose)  System.err.println("Loading class from "+f);
361            ClassFile cf = new ClassFile(f);
362            Logic logic = new Logic(cf);
363            boolean changed = logic.transform();
364            if (verbose && !changed)  System.err.println("(no change)");
365            logic.reportPatternMethods(!verbose, keepgoing);
366            byte[] bytes = cf.toByteArray();
367            return defineClass(null, bytes, 0, bytes.length);
368        }
369    }
370
371    private class Logic {
372        // Indify logic, per se.
373        ClassFile cf;
374        final char[] poolMarks;
375        final Map<Method,Constant> constants = new HashMap<>();
376        final Map<Method,String> indySignatures = new HashMap<>();
377        Logic(ClassFile cf) {
378            this.cf = cf;
379            poolMarks = new char[cf.pool.size()];
380        }
381        boolean transform() {
382            if (!initializeMarks())  return false;
383            if (!findPatternMethods())  return false;
384            Pool pool = cf.pool;
385            //for (Constant c : cp)  System.out.println("  # "+c);
386            for (Method m : cf.methods) {
387                if (constants.containsKey(m))  continue;  // don't bother
388                // Transform references.
389                int blab = 0;
390                for (Instruction i = m.instructions(); i != null; i = i.next()) {
391                    if (i.bc != opc_invokestatic)  continue;
392                    int methi = i.u2At(1);
393                    if (poolMarks[methi] == 0)  continue;
394                    Short[] ref = pool.getMemberRef((short)methi);
395                    Method conm = findMember(cf.methods, ref[1], ref[2]);
396                    if (conm == null)  continue;
397                    Constant con = constants.get(conm);
398                    if (con == null)  continue;
399                    if (blab++ == 0 && !quiet)
400                        System.err.println("patching "+cf.nameString()+"."+m);
401                    //if (blab == 1) { for (Instruction j = m.instructions(); j != null; j = j.next()) System.out.println("  |"+j); }
402                    if (con.tag == CONSTANT_InvokeDynamic) {
403                        // need to patch the following instruction too,
404                        // but there are usually intervening argument pushes too
405                        Instruction i2 = findPop(i);
406                        Short[] ref2 = null;
407                        short ref2i = 0;
408                        if (i2 != null && i2.bc == opc_invokevirtual &&
409                                poolMarks[(char)(ref2i = (short) i2.u2At(1))] == 'D')
410                            ref2 = pool.getMemberRef(ref2i);
411                        if (ref2 == null || !"invokeExact".equals(pool.getString(ref2[1]))) {
412                            System.err.println(m+": failed to create invokedynamic at "+i.pc);
413                            continue;
414                        }
415                        String invType = pool.getString(ref2[2]);
416                        String bsmType = indySignatures.get(conm);
417                        if (!invType.equals(bsmType)) {
418                            System.err.println(m+": warning: "+conm+" call type and local invoke type differ: "
419                                    +bsmType+", "+invType);
420                        }
421                        assert(i.len == 3 || i2.len == 3);
422                        if (!quiet)  System.err.println(i+" "+conm+";...; "+i2+" => invokedynamic "+con);
423                        int start = i.pc + 3, end = i2.pc;
424                        System.arraycopy(i.codeBase, start, i.codeBase, i.pc, end-start);
425                        i.forceNext(0);  // force revisit of new instruction
426                        i2.u1AtPut(-3, opc_invokedynamic);
427                        i2.u2AtPut(-2, con.index);
428                        i2.u2AtPut(0, (short)0);
429                        i2.u1AtPut(2, opc_nop);
430                        //System.out.println(new Instruction(i.codeBase, i2.pc-3));
431                    } else {
432                        if (!quiet)  System.err.println(i+" "+conm+" => ldc "+con);
433                        assert(i.len == 3);
434                        i.u1AtPut(0, opc_ldc_w);
435                        i.u2AtPut(1, con.index);
436                    }
437                }
438                //if (blab >= 1) { for (Instruction j = m.instructions(); j != null; j = j.next()) System.out.println("    |"+j); }
439            }
440            cf.methods.removeAll(constants.keySet());
441            return true;
442        }
443
444        // Scan forward from the instruction to find where the stack p
445        // below the current sp at the instruction.
446        Instruction findPop(Instruction i) {
447            //System.out.println("findPop from "+i);
448            Pool pool = cf.pool;
449            JVMState jvm = new JVMState();
450        decode:
451            for (i = i.clone().next(); i != null; i = i.next()) {
452                String pops = INSTRUCTION_POPS[i.bc];
453                //System.out.println("  "+i+" "+jvm.stack+" : "+pops.replace("$", " => "));
454                if (pops == null)  break;
455                if (jvm.stackMotion(i.bc))  continue decode;
456                if (pops.indexOf('Q') >= 0) {
457                    Short[] ref = pool.getMemberRef((short) i.u2At(1));
458                    String type = simplifyType(pool.getString(CONSTANT_Utf8, ref[2]));
459                    switch (i.bc) {
460                    case opc_getstatic:
461                    case opc_getfield:
462                    case opc_putstatic:
463                    case opc_putfield:
464                        pops = pops.replace("Q", type);
465                        break;
466                    default:
467                        if (!type.startsWith("("))
468                            throw new InternalError(i.toString());
469                        pops = pops.replace("Q$Q", type.substring(1).replace(")","$"));
470                        break;
471                    }
472                    //System.out.println("special type: "+type+" => "+pops);
473                }
474                int npops = pops.indexOf('$');
475                if (npops < 0)  throw new InternalError();
476                if (npops > jvm.sp())  return i;
477                List<Object> args = jvm.args(npops);
478                int k = 0;
479                for (Object x : args) {
480                    char have = (Character) x;
481                    char want = pops.charAt(k++);
482                    if (have == 'X' || want == 'X')  continue;
483                    if (have != want)  break decode;
484                }
485                if (pops.charAt(k++) != '$')  break decode;
486                args.clear();
487                while (k < pops.length())
488                    args.add(pops.charAt(k++));
489            }
490            System.err.println("*** bailout on jvm: "+jvm.stack+" "+i);
491            return null;
492        }
493
494        boolean findPatternMethods() {
495            boolean found = false;
496            for (char mark : "THI".toCharArray()) {
497                for (Method m : cf.methods) {
498                    if (!Modifier.isPrivate(m.access))  continue;
499                    if (!Modifier.isStatic(m.access))  continue;
500                    if (nameAndTypeMark(m.name, m.type) == mark) {
501                        Constant con = scanPattern(m, mark);
502                        if (con == null)  continue;
503                        constants.put(m, con);
504                        found = true;
505                    }
506                }
507            }
508            return found;
509        }
510
511        void reportPatternMethods(boolean quietly, boolean allowMatchFailure) {
512            if (!quietly && !constants.keySet().isEmpty())
513                System.err.println("pattern methods removed: "+constants.keySet());
514            for (Method m : cf.methods) {
515                if (nameMark(cf.pool.getString(m.name)) != 0 &&
516                    constants.get(m) == null) {
517                    String failure = "method has special name but fails to match pattern: "+m;
518                    if (!allowMatchFailure)
519                        throw new IllegalArgumentException(failure);
520                    else if (!quietly)
521                        System.err.println("warning: "+failure);
522                }
523            }
524            if (verifySpecifierCount >= 0) {
525                List<Object[]> specs = bootstrapMethodSpecifiers(false);
526                int specsLen = (specs == null ? 0 : specs.size());
527                // Pass by specsLen == 0, to help with associated (inner) classes.
528                if (specsLen == 0)  specsLen = verifySpecifierCount;
529                if (specsLen != verifySpecifierCount) {
530                    throw new IllegalArgumentException("BootstrapMethods length is "+specsLen+" but should be "+verifySpecifierCount);
531                }
532            }
533            if (!quiet)  System.err.flush();
534        }
535
536        // mark constant pool entries according to participation in patterns
537        boolean initializeMarks() {
538            boolean changed = false;
539            for (;;) {
540                boolean changed1 = false;
541                int cpindex = -1;
542                for (Constant e : cf.pool) {
543                    ++cpindex;
544                    if (e == null)  continue;
545                    char mark = poolMarks[cpindex];
546                    if (mark != 0)  continue;
547                    switch (e.tag) {
548                    case CONSTANT_Utf8:
549                        mark = nameMark(e.itemString()); break;
550                    case CONSTANT_NameAndType:
551                        mark = nameAndTypeMark(e.itemIndexes()); break;
552                    case CONSTANT_Class: {
553                        int n1 = e.itemIndex();
554                        char nmark = poolMarks[(char)n1];
555                        if ("DJ".indexOf(nmark) >= 0)
556                            mark = nmark;
557                        break;
558                    }
559                    case CONSTANT_Field:
560                    case CONSTANT_Method: {
561                        Short[] n12 = e.itemIndexes();
562                        short cl = n12[0];
563                        short nt = n12[1];
564                        char cmark = poolMarks[(char)cl];
565                        if (cmark != 0) {
566                            mark = cmark;  // it is a java.lang.invoke.* or java.lang.* method
567                            break;
568                        }
569                        String cls = cf.pool.getString(CONSTANT_Class, cl);
570                        if (cls.equals(cf.nameString())) {
571                            switch (poolMarks[(char)nt]) {
572                            // it is a private MH/MT/INDY method
573                            case 'T': case 'H': case 'I':
574                                mark = poolMarks[(char)nt];
575                                break;
576                            }
577                        }
578                        break;
579                    }
580                    default:  break;
581                    }
582                    if (mark != 0) {
583                        poolMarks[cpindex] = mark;
584                        changed1 = true;
585                    }
586                }
587                if (!changed1)
588                    break;
589                changed = true;
590            }
591            return changed;
592        }
593        char nameMark(String s) {
594            if (s.startsWith("MT_"))                return 'T';
595            else if (s.startsWith("MH_"))           return 'H';
596            else if (s.startsWith("INDY_"))         return 'I';
597            else if (s.startsWith("java/lang/invoke/"))  return 'D';
598            else if (s.startsWith("java/lang/"))    return 'J';
599            return 0;
600        }
601        char nameAndTypeMark(Short[] n12) {
602            return nameAndTypeMark(n12[0], n12[1]);
603        }
604        char nameAndTypeMark(short n1, short n2) {
605            char mark = poolMarks[(char)n1];
606            if (mark == 0)  return 0;
607            String descr = cf.pool.getString(CONSTANT_Utf8, n2);
608            String requiredType;
609            switch (poolMarks[(char)n1]) {
610            case 'H': requiredType = "()Ljava/lang/invoke/MethodHandle;";  break;
611            case 'T': requiredType = "()Ljava/lang/invoke/MethodType;";    break;
612            case 'I': requiredType = "()Ljava/lang/invoke/MethodHandle;";  break;
613            default:  return 0;
614            }
615            if (matchType(descr, requiredType))  return mark;
616            return 0;
617        }
618
619        boolean matchType(String descr, String requiredType) {
620            if (descr.equals(requiredType))  return true;
621            return false;
622        }
623
624        private class JVMState {
625            final List<Object> stack = new ArrayList<>();
626            int sp() { return stack.size(); }
627            void push(Object x) { stack.add(x); }
628            void push2(Object x) { stack.add(EMPTY_SLOT); stack.add(x); }
629            void pushAt(int pos, Object x) { stack.add(stack.size()+pos, x); }
630            Object pop() { return stack.remove(sp()-1); }
631            Object top() { return stack.get(sp()-1); }
632            List<Object> args(boolean hasRecv, String type) {
633                return args(argsize(type) + (hasRecv ? 1 : 0));
634            }
635            List<Object> args(int argsize) {
636                return stack.subList(sp()-argsize, sp());
637            }
638            boolean stackMotion(int bc) {
639                switch (bc) {
640                case opc_pop:    pop();             break;
641                case opc_pop2:   pop(); pop();      break;
642                case opc_swap:   pushAt(-1, pop()); break;
643                case opc_dup:    push(top());       break;
644                case opc_dup_x1: pushAt(-2, top()); break;
645                case opc_dup_x2: pushAt(-3, top()); break;
646                // ? also: dup2{,_x1,_x2}
647                default:  return false;
648                }
649                return true;
650            }
651        }
652        private final String EMPTY_SLOT = "_";
653        private void removeEmptyJVMSlots(List<Object> args) {
654            for (;;) {
655                int i = args.indexOf(EMPTY_SLOT);
656                if (i >= 0 && i+1 < args.size()
657                    && (isConstant(args.get(i+1), CONSTANT_Long) ||
658                        isConstant(args.get(i+1), CONSTANT_Double)))
659                    args.remove(i);
660                else  break;
661            }
662        }
663
664        private Constant scanPattern(Method m, char patternMark) {
665            if (verbose)  System.err.println("scan "+m+" for pattern="+patternMark);
666            int wantTag;
667            switch (patternMark) {
668            case 'T': wantTag = CONSTANT_MethodType; break;
669            case 'H': wantTag = CONSTANT_MethodHandle; break;
670            case 'I': wantTag = CONSTANT_InvokeDynamic; break;
671            default: throw new InternalError();
672            }
673            Instruction i = m.instructions();
674            JVMState jvm = new JVMState();
675            Pool pool = cf.pool;
676            int branchCount = 0;
677            Object arg;
678            List<Object> args;
679            List<Object> bsmArgs = null;  // args to invokeGeneric
680        decode:
681            for (; i != null; i = i.next()) {
682                //System.out.println(jvm.stack+" "+i);
683                int bc = i.bc;
684                switch (bc) {
685                case opc_ldc:           jvm.push(pool.get(i.u1At(1)));   break;
686                case opc_ldc_w:         jvm.push(pool.get(i.u2At(1)));   break;
687                case opc_ldc2_w:        jvm.push2(pool.get(i.u2At(1)));  break;
688                case opc_aconst_null:   jvm.push(null);                  break;
689                case opc_bipush:        jvm.push((int)(byte) i.u1At(1)); break;
690                case opc_sipush:        jvm.push((int)(short)i.u2At(1)); break;
691
692                // these support creation of a restarg array
693                case opc_anewarray:
694                    arg = jvm.pop();
695                    if (!(arg instanceof Integer))  break decode;
696                    arg = Arrays.asList(new Object[(Integer)arg]);
697                    jvm.push(arg);
698                    break;
699                case opc_dup:
700                    jvm.push(jvm.top()); break;
701                case opc_aastore:
702                    args = jvm.args(3);  // array, index, value
703                    if (args.get(0) instanceof List &&
704                        args.get(1) instanceof Integer) {
705                        ((List<Object>)args.get(0)).set( (Integer)args.get(1), args.get(2) );
706                    }
707                    args.clear();
708                    break;
709
710                case opc_new:
711                {
712                    String type = pool.getString(CONSTANT_Class, (short)i.u2At(1));
713                    //System.out.println("new "+type);
714                    switch (type) {
715                    case "java/lang/StringBuilder":
716                        jvm.push("StringBuilder");
717                        continue decode;  // go to next instruction
718                    }
719                    break decode;  // bail out
720                }
721
722                case opc_getstatic:
723                {
724                    // int.class compiles to getstatic Integer.TYPE
725                    int fieldi = i.u2At(1);
726                    char mark = poolMarks[fieldi];
727                    //System.err.println("getstatic "+fieldi+Arrays.asList(pool.getStrings(pool.getMemberRef((short)fieldi)))+mark);
728                    if (mark == 'J') {
729                        Short[] ref = pool.getMemberRef((short) fieldi);
730                        String name = pool.getString(CONSTANT_Utf8, ref[1]);
731                        if ("TYPE".equals(name)) {
732                            String wrapperName = pool.getString(CONSTANT_Class, ref[0]).replace('/', '.');
733                            // a primitive type descriptor
734                            Class<?> primClass;
735                            try {
736                                primClass = (Class<?>) Class.forName(wrapperName).getField(name).get(null);
737                            } catch (Exception ex) {
738                                throw new InternalError("cannot load "+wrapperName+"."+name);
739                            }
740                            jvm.push(primClass);
741                            break;
742                        }
743                    }
744                    // unknown field; keep going...
745                    jvm.push(UNKNOWN_CON);
746                    break;
747                }
748                case opc_putstatic:
749                {
750                    if (patternMark != 'I')  break decode;
751                    jvm.pop();
752                    // unknown field; keep going...
753                    break;
754                }
755
756                case opc_invokestatic:
757                case opc_invokevirtual:
758                case opc_invokespecial:
759                {
760                    boolean hasRecv = (bc != opc_invokestatic);
761                    int methi = i.u2At(1);
762                    char mark = poolMarks[methi];
763                    Short[] ref = pool.getMemberRef((short)methi);
764                    String type = pool.getString(CONSTANT_Utf8, ref[2]);
765                    //System.out.println("invoke "+pool.getString(CONSTANT_Utf8, ref[1])+" "+Arrays.asList(ref)+" : "+type);
766                    args = jvm.args(hasRecv, type);
767                    String intrinsic = null;
768                    Constant con;
769                    if (mark == 'D' || mark == 'J') {
770                        intrinsic = pool.getString(CONSTANT_Utf8, ref[1]);
771                        if (mark == 'J') {
772                            String cls = pool.getString(CONSTANT_Class, ref[0]);
773                            cls = cls.substring(1+cls.lastIndexOf('/'));
774                            intrinsic = cls+"."+intrinsic;
775                        }
776                        //System.out.println("recognized intrinsic "+intrinsic);
777                        byte refKind = -1;
778                        switch (intrinsic) {
779                        case "findGetter":          refKind = REF_getField;         break;
780                        case "findStaticGetter":    refKind = REF_getStatic;        break;
781                        case "findSetter":          refKind = REF_putField;         break;
782                        case "findStaticSetter":    refKind = REF_putStatic;        break;
783                        case "findVirtual":         refKind = REF_invokeVirtual;    break;
784                        case "findStatic":          refKind = REF_invokeStatic;     break;
785                        case "findSpecial":         refKind = REF_invokeSpecial;    break;
786                        case "findConstructor":     refKind = REF_newInvokeSpecial; break;
787                        }
788                        if (refKind >= 0 && (con = parseMemberLookup(refKind, args)) != null) {
789                            args.clear(); args.add(con);
790                            continue;
791                        }
792                    }
793                    Method ownMethod = null;
794                    if (mark == 'T' || mark == 'H' || mark == 'I') {
795                        ownMethod = findMember(cf.methods, ref[1], ref[2]);
796                    }
797                    //if (intrinsic != null)  System.out.println("intrinsic = "+intrinsic);
798                    switch (intrinsic == null ? "" : intrinsic) {
799                    case "fromMethodDescriptorString":
800                        con = makeMethodTypeCon(args.get(0));
801                        args.clear(); args.add(con);
802                        continue;
803                    case "methodType": {
804                        flattenVarargs(args);  // there are several overloadings, some with varargs
805                        StringBuilder buf = new StringBuilder();
806                        String rtype = null;
807                        for (Object typeArg : args) {
808                            if (typeArg instanceof Class) {
809                                Class<?> argClass = (Class<?>) typeArg;
810                                if (argClass.isPrimitive()) {
811                                    char tchar;
812                                    switch (argClass.getName()) {
813                                    case "void":    tchar = 'V'; break;
814                                    case "boolean": tchar = 'Z'; break;
815                                    case "byte":    tchar = 'B'; break;
816                                    case "char":    tchar = 'C'; break;
817                                    case "short":   tchar = 'S'; break;
818                                    case "int":     tchar = 'I'; break;
819                                    case "long":    tchar = 'J'; break;
820                                    case "float":   tchar = 'F'; break;
821                                    case "double":  tchar = 'D'; break;
822                                    default:  throw new InternalError(argClass.toString());
823                                    }
824                                    buf.append(tchar);
825                                } else {
826                                    // should not happen, but...
827                                    buf.append('L').append(argClass.getName().replace('.','/')).append(';');
828                                }
829                            } else if (typeArg instanceof Constant) {
830                                Constant argCon = (Constant) typeArg;
831                                if (argCon.tag == CONSTANT_Class) {
832                                    String cn = pool.get(argCon.itemIndex()).itemString();
833                                    if (cn.endsWith(";"))
834                                        buf.append(cn);
835                                    else
836                                        buf.append('L').append(cn).append(';');
837                                } else {
838                                    break decode;
839                                }
840                            } else {
841                                break decode;
842                            }
843                            if (rtype == null) {
844                                // first arg is treated differently
845                                rtype = buf.toString();
846                                buf.setLength(0);
847                                buf.append('(');
848                            }
849                        }
850                        buf.append(')').append(rtype);
851                        con = con = makeMethodTypeCon(buf.toString());
852                        args.clear(); args.add(con);
853                        continue;
854                    }
855                    case "lookup":
856                    case "dynamicInvoker":
857                        args.clear(); args.add(intrinsic);
858                        continue;
859                    case "lookupClass":
860                        if (args.equals(Arrays.asList("lookup"))) {
861                            // fold lookup().lookupClass() to the enclosing class
862                            args.clear(); args.add(pool.get(cf.thisc));
863                            continue;
864                        }
865                        break;
866                    case "invoke":
867                    case "invokeGeneric":
868                    case "invokeWithArguments":
869                        if (patternMark != 'I')  break decode;
870                        if ("invokeWithArguments".equals(intrinsic))
871                            flattenVarargs(args);
872                        bsmArgs = new ArrayList(args);
873                        args.clear(); args.add("invokeGeneric");
874                        continue;
875                    case "Integer.valueOf":
876                    case "Float.valueOf":
877                    case "Long.valueOf":
878                    case "Double.valueOf":
879                        removeEmptyJVMSlots(args);
880                        if (args.size() == 1) {
881                            arg = args.remove(0);
882                            assert(3456 == (CONSTANT_Integer*1000 + CONSTANT_Float*100 + CONSTANT_Long*10 + CONSTANT_Double));
883                            if (isConstant(arg, CONSTANT_Integer + "IFLD".indexOf(intrinsic.charAt(0)))
884                                || arg instanceof Number) {
885                                args.add(arg); continue;
886                            }
887                        }
888                        break decode;
889                    case "StringBuilder.append":
890                        // allow calls like ("value = "+x)
891                        removeEmptyJVMSlots(args);
892                        args.subList(1, args.size()).clear();
893                        continue;
894                    case "StringBuilder.toString":
895                        args.clear();
896                        args.add(intrinsic);
897                        continue;
898                    }
899                    if (!hasRecv && ownMethod != null && patternMark != 0) {
900                        con = constants.get(ownMethod);
901                        if (con == null)  break decode;
902                        args.clear(); args.add(con);
903                        continue;
904                    } else if (type.endsWith(")V")) {
905                        // allow calls like println("reached the pattern method")
906                        args.clear();
907                        continue;
908                    }
909                    break decode;  // bail out for most calls
910                }
911                case opc_areturn:
912                {
913                    ++branchCount;
914                    if (bsmArgs != null) {
915                        // parse bsmArgs as (MH, lookup, String, MT, [extra])
916                        Constant indyCon = makeInvokeDynamicCon(bsmArgs);
917                        if (indyCon != null) {
918                            Constant typeCon = (Constant) bsmArgs.get(3);
919                            indySignatures.put(m, pool.getString(typeCon.itemIndex()));
920                            return indyCon;
921                        }
922                        System.err.println(m+": inscrutable bsm arguments: "+bsmArgs);
923                        break decode;  // bail out
924                    }
925                    arg = jvm.pop();
926                    if (branchCount == 2 && UNKNOWN_CON.equals(arg))
927                        break;  // merge to next path
928                    if (isConstant(arg, wantTag))
929                        return (Constant) arg;
930                    break decode;  // bail out
931                }
932                default:
933                    if (jvm.stackMotion(i.bc))  break;
934                    if (bc >= opc_nconst_MIN && bc <= opc_nconst_MAX)
935                        { jvm.push(INSTRUCTION_CONSTANTS[bc - opc_nconst_MIN]); break; }
936                    if (patternMark == 'I') {
937                        // these support caching paths in INDY_x methods
938                        if (bc == opc_aload || bc >= opc_aload_0 && bc <= opc_aload_MAX)
939                            { jvm.push(UNKNOWN_CON); break; }
940                        if (bc == opc_astore || bc >= opc_astore_0 && bc <= opc_astore_MAX)
941                            { jvm.pop(); break; }
942                        switch (bc) {
943                        case opc_getfield:
944                        case opc_aaload:
945                            jvm.push(UNKNOWN_CON); break;
946                        case opc_ifnull:
947                        case opc_ifnonnull:
948                            // ignore branch target
949                            if (++branchCount != 1)  break decode;
950                            jvm.pop();
951                            break;
952                        case opc_checkcast:
953                            arg = jvm.top();
954                            if ("invokeWithArguments".equals(arg) ||
955                                "invokeGeneric".equals(arg))
956                                break;  // assume it is a helpful cast
957                            break decode;
958                        default:
959                            break decode;  // bail out
960                        }
961                        continue decode; // go to next instruction
962                    }
963                    break decode;  // bail out
964                } //end switch
965            }
966            System.err.println(m+": bailout on "+i+" jvm stack: "+jvm.stack);
967            return null;
968        }
969        private final String UNKNOWN_CON = "<unknown>";
970
971        private void flattenVarargs(List<Object> args) {
972            int size = args.size();
973            if (size > 0 && args.get(size-1) instanceof List)
974                args.addAll((List<Object>) args.remove(size-1));
975        }
976
977        private boolean isConstant(Object x, int tag) {
978            return x instanceof Constant && ((Constant)x).tag == tag;
979        }
980        private Constant makeMethodTypeCon(Object x) {
981            short utfIndex;
982            if (x instanceof String)
983                utfIndex = (short) cf.pool.addConstant(CONSTANT_Utf8, x).index;
984            else if (isConstant(x, CONSTANT_String))
985                utfIndex = ((Constant)x).itemIndex();
986            else  return null;
987            return cf.pool.addConstant(CONSTANT_MethodType, utfIndex);
988        }
989        private Constant parseMemberLookup(byte refKind, List<Object> args) {
990            // E.g.: lookup().findStatic(Foo.class, "name", MethodType)
991            if (args.size() != 4)  return null;
992            int argi = 0;
993            if (!"lookup".equals(args.get(argi++)))  return null;
994            short refindex, cindex, ntindex, nindex, tindex;
995            Object con;
996            if (!isConstant(con = args.get(argi++), CONSTANT_Class))  return null;
997            cindex = (short)((Constant)con).index;
998            if (!isConstant(con = args.get(argi++), CONSTANT_String))  return null;
999            nindex = ((Constant)con).itemIndex();
1000            if (isConstant(con = args.get(argi++), CONSTANT_MethodType) ||
1001                isConstant(con, CONSTANT_Class)) {
1002                tindex = ((Constant)con).itemIndex();
1003            } else return null;
1004            ntindex = (short) cf.pool.addConstant(CONSTANT_NameAndType,
1005                    new Short[]{ nindex, tindex }).index;
1006            byte reftag = CONSTANT_Method;
1007            if (refKind <= REF_putStatic)
1008                reftag = CONSTANT_Field;
1009            else if (refKind == REF_invokeInterface)
1010                reftag = CONSTANT_InterfaceMethod;
1011            Constant ref = cf.pool.addConstant(reftag, new Short[]{ cindex, ntindex });
1012            return cf.pool.addConstant(CONSTANT_MethodHandle, new Object[]{ refKind, (short)ref.index });
1013        }
1014        private Constant makeInvokeDynamicCon(List<Object> args) {
1015            // E.g.: MH_bsm.invokeGeneric(lookup(), "name", MethodType, "extraArg")
1016            removeEmptyJVMSlots(args);
1017            if (args.size() < 4)  return null;
1018            int argi = 0;
1019            short nindex, tindex, ntindex, bsmindex;
1020            Object con;
1021            if (!isConstant(con = args.get(argi++), CONSTANT_MethodHandle))  return null;
1022            bsmindex = (short) ((Constant)con).index;
1023            if (!"lookup".equals(args.get(argi++)))  return null;
1024            if (!isConstant(con = args.get(argi++), CONSTANT_String))  return null;
1025            nindex = ((Constant)con).itemIndex();
1026            if (!isConstant(con = args.get(argi++), CONSTANT_MethodType))  return null;
1027            tindex = ((Constant)con).itemIndex();
1028            ntindex = (short) cf.pool.addConstant(CONSTANT_NameAndType,
1029                                                  new Short[]{ nindex, tindex }).index;
1030            List<Object> extraArgs = new ArrayList<Object>();
1031            if (argi < args.size()) {
1032                extraArgs.addAll(args.subList(argi, args.size() - 1));
1033                Object lastArg = args.get(args.size() - 1);
1034                if (lastArg instanceof List) {
1035                    List<Object> lastArgs = (List<Object>) lastArg;
1036                    removeEmptyJVMSlots(lastArgs);
1037                    extraArgs.addAll(lastArgs);
1038                } else {
1039                    extraArgs.add(lastArg);
1040                }
1041            }
1042            List<Short> extraArgIndexes = new CountedList<>(Short.class);
1043            for (Object x : extraArgs) {
1044                if (x instanceof Number) {
1045                    Object num = null; byte numTag = 0;
1046                    if (x instanceof Integer) { num = x; numTag = CONSTANT_Integer; }
1047                    if (x instanceof Float)   { num = Float.floatToRawIntBits((Float)x); numTag = CONSTANT_Float; }
1048                    if (x instanceof Long)    { num = x; numTag = CONSTANT_Long; }
1049                    if (x instanceof Double)  { num = Double.doubleToRawLongBits((Double)x); numTag = CONSTANT_Double; }
1050                    if (num != null)  x = cf.pool.addConstant(numTag, x);
1051                }
1052                if (!(x instanceof Constant)) {
1053                    System.err.println("warning: unrecognized BSM argument "+x);
1054                    return null;
1055                }
1056                extraArgIndexes.add((short) ((Constant)x).index);
1057            }
1058            List<Object[]> specs = bootstrapMethodSpecifiers(true);
1059            int specindex = -1;
1060            Object[] spec = new Object[]{ bsmindex, extraArgIndexes };
1061            for (Object[] spec1 : specs) {
1062                if (Arrays.equals(spec1, spec)) {
1063                    specindex = specs.indexOf(spec1);
1064                    if (verbose)  System.err.println("reusing BSM specifier: "+spec1[0]+spec1[1]);
1065                    break;
1066                }
1067            }
1068            if (specindex == -1) {
1069                specindex = (short) specs.size();
1070                specs.add(spec);
1071                if (verbose)  System.err.println("adding BSM specifier: "+spec[0]+spec[1]);
1072            }
1073            return cf.pool.addConstant(CONSTANT_InvokeDynamic,
1074                        new Short[]{ (short)specindex, ntindex });
1075        }
1076
1077        List<Object[]> bootstrapMethodSpecifiers(boolean createIfNotFound) {
1078            Attr bsms = cf.findAttr("BootstrapMethods");
1079            if (bsms == null) {
1080                if (!createIfNotFound)  return null;
1081                bsms = new Attr(cf, "BootstrapMethods", new byte[]{0,0});
1082                assert(bsms == cf.findAttr("BootstrapMethods"));
1083            }
1084            if (bsms.item instanceof byte[]) {
1085                // unflatten
1086                List<Object[]> specs = new CountedList<>(Object[].class);
1087                DataInputStream in = new DataInputStream(new ByteArrayInputStream((byte[]) bsms.item));
1088                try {
1089                    int len = (char) in.readShort();
1090                    for (int i = 0; i < len; i++) {
1091                        short bsm = in.readShort();
1092                        int argc = (char) in.readShort();
1093                        List<Short> argv = new CountedList<>(Short.class);
1094                        for (int j = 0; j < argc; j++)
1095                            argv.add(in.readShort());
1096                        specs.add(new Object[]{ bsm, argv });
1097                    }
1098                } catch (IOException ex) { throw new InternalError(); }
1099                bsms.item = specs;
1100            }
1101            return (List<Object[]>) bsms.item;
1102        }
1103    }
1104
1105    private DataInputStream openInput(File f) throws IOException {
1106        return new DataInputStream(new BufferedInputStream(new FileInputStream(f)));
1107    }
1108
1109    private DataOutputStream openOutput(File f) throws IOException {
1110        if (!overwrite && f.exists())
1111            throw new IOException("file already exists: "+f);
1112        ensureDirectory(f.getParentFile());
1113        return new DataOutputStream(new BufferedOutputStream(new FileOutputStream(f)));
1114    }
1115
1116    static byte[] readRawBytes(DataInputStream in, int size) throws IOException {
1117        byte[] bytes = new byte[size];
1118        int nr = in.read(bytes);
1119        if (nr != size)
1120            throw new InternalError("wrong size: "+nr);
1121        return bytes;
1122    }
1123
1124    private interface Chunk {
1125        void readFrom(DataInputStream in) throws IOException;
1126        void writeTo(DataOutputStream out) throws IOException;
1127    }
1128
1129    private static class CountedList<T> extends ArrayList<T> implements Chunk {
1130        final Class<? extends T> itemClass;
1131        final int rowlen;
1132        CountedList(Class<? extends T> itemClass, int rowlen) {
1133            this.itemClass = itemClass;
1134            this.rowlen = rowlen;
1135        }
1136        CountedList(Class<? extends T> itemClass) { this(itemClass, -1); }
1137        public void readFrom(DataInputStream in) throws IOException {
1138            int count = in.readUnsignedShort();
1139            while (size() < count) {
1140                if (rowlen < 0) {
1141                    add(readInput(in, itemClass));
1142                } else {
1143                    Class<?> elemClass = itemClass.getComponentType();
1144                    Object[] row = (Object[]) java.lang.reflect.Array.newInstance(elemClass, rowlen);
1145                    for (int i = 0; i < rowlen; i++)
1146                        row[i] = readInput(in, elemClass);
1147                    add(itemClass.cast(row));
1148                }
1149            }
1150        }
1151        public void writeTo(DataOutputStream out) throws IOException {
1152            out.writeShort((short)size());
1153            for (T item : this) {
1154                writeOutput(out, item);
1155            }
1156        }
1157    }
1158
1159    private static <T> T readInput(DataInputStream in, Class<T> dataClass) throws IOException {
1160        Object data;
1161        if (dataClass == Integer.class) {
1162            data = in.readInt();
1163        } else if (dataClass == Short.class) {
1164            data = in.readShort();
1165        } else if (dataClass == Byte.class) {
1166            data = in.readByte();
1167        } else if (dataClass == String.class) {
1168            data = in.readUTF();
1169        } else if (Chunk.class.isAssignableFrom(dataClass)) {
1170            T obj;
1171            try { obj = dataClass.newInstance(); }
1172                catch (Exception ex) { throw new RuntimeException(ex); }
1173            ((Chunk)obj).readFrom(in);
1174            data = obj;
1175        } else {
1176            throw new InternalError("bad input datum: "+dataClass);
1177        }
1178        return dataClass.cast(data);
1179    }
1180    private static <T> T readInput(byte[] bytes, Class<T> dataClass) {
1181        try {
1182            return readInput(new DataInputStream(new ByteArrayInputStream(bytes)), dataClass);
1183        } catch (IOException ex) {
1184            throw new InternalError();
1185        }
1186    }
1187    private static void readInputs(DataInputStream in, Object... data) throws IOException {
1188        for (Object x : data)  ((Chunk)x).readFrom(in);
1189    }
1190
1191    private static void writeOutput(DataOutputStream out, Object data) throws IOException {
1192        if (data == null) {
1193            return;
1194        } if (data instanceof Integer) {
1195            out.writeInt((Integer)data);
1196        } else if (data instanceof Long) {
1197            out.writeLong((Long)data);
1198        } else if (data instanceof Short) {
1199            out.writeShort((Short)data);
1200        } else if (data instanceof Byte) {
1201            out.writeByte((Byte)data);
1202        } else if (data instanceof String) {
1203            out.writeUTF((String)data);
1204        } else if (data instanceof byte[]) {
1205            out.write((byte[])data);
1206        } else if (data instanceof Object[]) {
1207            for (Object x : (Object[]) data)
1208                writeOutput(out, x);
1209        } else if (data instanceof Chunk) {
1210            Chunk x = (Chunk) data;
1211            x.writeTo(out);
1212        } else if (data instanceof List) {
1213            for (Object x : (List<?>) data)
1214                writeOutput(out, x);
1215        } else {
1216            throw new InternalError("bad output datum: "+data+" : "+data.getClass().getName());
1217        }
1218    }
1219    private static void writeOutputs(DataOutputStream out, Object... data) throws IOException {
1220        for (Object x : data)  writeOutput(out, x);
1221    }
1222
1223    public abstract static class Outer {
1224        public abstract List<? extends Inner> inners();
1225        protected void linkInners() {
1226            for (Inner i : inners()) {
1227                i.linkOuter(this);
1228                if (i instanceof Outer)
1229                    ((Outer)i).linkInners();
1230            }
1231        }
1232        public <T extends Outer> T outer(Class<T> c) {
1233            for (Outer walk = this;; walk = ((Inner)walk).outer()) {
1234                if (c.isInstance(walk))
1235                    return c.cast(walk);
1236                //if (!(walk instanceof Inner))  return null;
1237            }
1238        }
1239
1240        public abstract List<Attr> attrs();
1241        public Attr findAttr(String name) {
1242            return findAttr(outer(ClassFile.class).pool.stringIndex(name, false));
1243        }
1244        public Attr findAttr(int name) {
1245            if (name == 0)  return null;
1246            for (Attr a : attrs()) {
1247                if (a.name == name)  return a;
1248            }
1249            return null;
1250        }
1251    }
1252    public interface Inner { Outer outer(); void linkOuter(Outer o); }
1253    public abstract static class InnerOuter extends Outer implements Inner {
1254        public Outer outer;
1255        public Outer outer() { return outer; }
1256        public void linkOuter(Outer o) { assert(outer == null); outer = o; }
1257    }
1258    public static class Constant<T> implements Chunk {
1259        public final byte tag;
1260        public final T item;
1261        public final int index;
1262        public Constant(int index, byte tag, T item) {
1263            this.index = index;
1264            this.tag = tag;
1265            this.item = item;
1266        }
1267        public Constant checkTag(byte tag) {
1268            if (this.tag != tag)  throw new InternalError(this.toString());
1269            return this;
1270        }
1271        public String itemString() { return (String)item; }
1272        public Short itemIndex() { return (Short)item; }
1273        public Short[] itemIndexes() { return (Short[])item; }
1274        public void readFrom(DataInputStream in) throws IOException {
1275            throw new InternalError("do not call");
1276        }
1277        public void writeTo(DataOutputStream out) throws IOException {
1278            writeOutputs(out, tag, item);
1279        }
1280        public boolean equals(Object x) { return (x instanceof Constant && equals((Constant)x)); }
1281        public boolean equals(Constant that) {
1282            return (this.tag == that.tag && this.itemAsComparable().equals(that.itemAsComparable()));
1283        }
1284        public int hashCode() { return (tag * 31) + this.itemAsComparable().hashCode(); }
1285        public Object itemAsComparable() {
1286            switch (tag) {
1287            case CONSTANT_Double:   return Double.longBitsToDouble((Long)item);
1288            case CONSTANT_Float:    return Float.intBitsToFloat((Integer)item);
1289            }
1290            return (item instanceof Object[] ? Arrays.asList((Object[])item) : item);
1291        }
1292        public String toString() {
1293            String itstr = String.valueOf(itemAsComparable());
1294            return (index + ":" + tagName(tag) + (itstr.startsWith("[")?"":"=") + itstr);
1295        }
1296        private static String[] TAG_NAMES;
1297        public static String tagName(byte tag) {  // used for error messages
1298            if (TAG_NAMES == null)
1299                TAG_NAMES = ("None Utf8 Unicode Integer Float Long Double Class String"
1300                             +" Fieldref Methodref InterfaceMethodref NameAndType #13 #14"
1301                             +" MethodHandle MethodType InvokeDynamic#17 InvokeDynamic").split(" ");
1302            if ((tag & 0xFF) >= TAG_NAMES.length)  return "#"+(tag & 0xFF);
1303            return TAG_NAMES[tag & 0xFF];
1304        }
1305    }
1306
1307    public static class Pool extends CountedList<Constant> implements Chunk {
1308        private Map<String,Short> strings = new TreeMap<>();
1309
1310        public Pool() {
1311            super(Constant.class);
1312        }
1313        public void readFrom(DataInputStream in) throws IOException {
1314            int count = in.readUnsignedShort();
1315            add(null);  // always ignore first item
1316            while (size() < count) {
1317                readConstant(in);
1318            }
1319        }
1320        public <T> Constant<T> addConstant(byte tag, T item) {
1321            Constant<T> con = new Constant<>(size(), tag, item);
1322            int idx = indexOf(con);
1323            if (idx >= 0)  return get(idx);
1324            add(con);
1325            if (tag == CONSTANT_Utf8)  strings.put((String)item, (short) con.index);
1326            return con;
1327        }
1328        private void readConstant(DataInputStream in) throws IOException {
1329            byte tag = in.readByte();
1330            int index = size();
1331            Object arg;
1332            switch (tag) {
1333            case CONSTANT_Utf8:
1334                arg = in.readUTF();
1335                strings.put((String) arg, (short) size());
1336                break;
1337            case CONSTANT_Integer:
1338            case CONSTANT_Float:
1339                arg = in.readInt(); break;
1340            case CONSTANT_Long:
1341            case CONSTANT_Double:
1342                add(new Constant(index, tag, in.readLong()));
1343                add(null);
1344                return;
1345            case CONSTANT_Class:
1346            case CONSTANT_String:
1347                arg = in.readShort(); break;
1348            case CONSTANT_Field:
1349            case CONSTANT_Method:
1350            case CONSTANT_InterfaceMethod:
1351            case CONSTANT_NameAndType:
1352            case CONSTANT_InvokeDynamic:
1353                // read an ordered pair
1354                arg = new Short[] { in.readShort(), in.readShort() };
1355                break;
1356            case CONSTANT_MethodHandle:
1357                // read an ordered pair; first part is a u1 (not u2)
1358                arg = new Object[] { in.readByte(), in.readShort() };
1359                break;
1360            case CONSTANT_MethodType:
1361                arg = in.readShort(); break;
1362            default:
1363                throw new InternalError("bad CP tag "+tag);
1364            }
1365            add(new Constant(index, tag, arg));
1366        }
1367
1368        // Access:
1369        public Constant get(int index) {
1370            // extra 1-bits get into the shorts
1371            return super.get((char) index);
1372        }
1373        String getString(byte tag, short index) {
1374            get(index).checkTag(tag);
1375            return getString(index);
1376        }
1377        String getString(short index) {
1378            Object v = get(index).item;
1379            if (v instanceof Short)
1380                v = get((Short)v).checkTag(CONSTANT_Utf8).item;
1381            return (String) v;
1382        }
1383        String[] getStrings(Short[] indexes) {
1384            String[] res = new String[indexes.length];
1385            for (int i = 0; i < indexes.length; i++)
1386                res[i] = getString(indexes[i]);
1387            return res;
1388        }
1389        int stringIndex(String name, boolean createIfNotFound) {
1390            Short x = strings.get(name);
1391            if (x != null)  return (char)(int) x;
1392            if (!createIfNotFound)  return 0;
1393            return addConstant(CONSTANT_Utf8, name).index;
1394        }
1395        Short[] getMemberRef(short index) {
1396            Short[] cls_nnt = get(index).itemIndexes();
1397            Short[] name_type = get(cls_nnt[1]).itemIndexes();
1398            return new Short[]{ cls_nnt[0], name_type[0], name_type[1] };
1399        }
1400    }
1401
1402    public class ClassFile extends Outer implements Chunk {
1403        ClassFile(File f) throws IOException {
1404            DataInputStream in = openInput(f);
1405            try {
1406                readFrom(in);
1407            } finally {
1408                if (in != null)  in.close();
1409            }
1410        }
1411
1412        public int                magic, version;  // <min:maj>
1413        public final Pool         pool       = new Pool();
1414        public short              access, thisc, superc;
1415        public final List<Short>  interfaces = new CountedList<>(Short.class);
1416        public final List<Field>  fields     = new CountedList<>(Field.class);
1417        public final List<Method> methods    = new CountedList<>(Method.class);
1418        public final List<Attr>   attrs      = new CountedList<>(Attr.class);
1419
1420        public final void readFrom(DataInputStream in) throws IOException {
1421            magic = in.readInt(); version = in.readInt();
1422            if (magic != 0xCAFEBABE)  throw new IOException("bad magic number");
1423            pool.readFrom(in);
1424            Code_index = pool.stringIndex("Code", false);
1425            access = in.readShort(); thisc = in.readShort(); superc = in.readShort();
1426            readInputs(in, interfaces, fields, methods, attrs);
1427            if (in.read() >= 0)  throw new IOException("junk after end of file");
1428            linkInners();
1429        }
1430
1431        void writeTo(File f) throws IOException {
1432            DataOutputStream out = openOutput(f);
1433            try {
1434                writeTo(out);
1435            } finally {
1436                out.close();
1437            }
1438        }
1439
1440        public void writeTo(DataOutputStream out) throws IOException {
1441            writeOutputs(out, magic, version, pool,
1442                         access, thisc, superc, interfaces,
1443                         fields, methods, attrs);
1444        }
1445
1446        public byte[] toByteArray() {
1447            try {
1448                ByteArrayOutputStream buf = new ByteArrayOutputStream();
1449                writeTo(new DataOutputStream(buf));
1450                return buf.toByteArray();
1451            } catch (IOException ex) {
1452                throw new InternalError();
1453            }
1454        }
1455
1456        public List<Inner> inners() {
1457            List<Inner> inns = new ArrayList<>();
1458            inns.addAll(fields); inns.addAll(methods); inns.addAll(attrs);
1459            return inns;
1460        }
1461        public List<Attr> attrs() { return attrs; }
1462
1463        // derived stuff:
1464        public String nameString() { return pool.getString(CONSTANT_Class, thisc); }
1465        int Code_index;
1466    }
1467
1468    private static <T extends Member> T findMember(List<T> mems, int name, int type) {
1469        if (name == 0 || type == 0)  return null;
1470        for (T m : mems) {
1471            if (m.name == name && m.type == type)  return m;
1472        }
1473        return null;
1474    }
1475
1476    public static class Member extends InnerOuter implements Chunk {
1477        public short access, name, type;
1478        public final List<Attr> attrs = new CountedList<>(Attr.class);
1479        public void readFrom(DataInputStream in) throws IOException {
1480            access = in.readShort(); name = in.readShort(); type = in.readShort();
1481            readInputs(in, attrs);
1482        }
1483        public void writeTo(DataOutputStream out) throws IOException {
1484            writeOutputs(out, access, name, type, attrs);
1485        }
1486        public List<Attr> inners() { return attrs; }
1487        public List<Attr> attrs() { return attrs; }
1488        public ClassFile outer() { return (ClassFile) outer; }
1489        public String nameString() { return outer().pool.getString(CONSTANT_Utf8, name); }
1490        public String typeString() { return outer().pool.getString(CONSTANT_Utf8, type); }
1491        public String toString() {
1492            if (outer == null)  return super.toString();
1493            return nameString() + (this instanceof Method ? "" : ":")
1494                    + simplifyType(typeString());
1495        }
1496    }
1497    public static class Field extends Member {
1498    }
1499    public static class Method extends Member {
1500        public Code code() {
1501            Attr a = findAttr("Code");
1502            if (a == null)  return null;
1503            return (Code) a.item;
1504        }
1505        public Instruction instructions() {
1506            Code code = code();
1507            if (code == null)  return null;
1508            return code.instructions();
1509        }
1510    }
1511
1512    public static class Attr extends InnerOuter implements Chunk {
1513        public short name;
1514        public int size = -1;  // no pre-declared size
1515        public Object item;
1516
1517        public Attr() {}
1518        public Attr(Outer outer, String name, Object item) {
1519            ClassFile cf = outer.outer(ClassFile.class);
1520            linkOuter(outer);
1521            this.name = (short) cf.pool.stringIndex(name, true);
1522            this.item = item;
1523            outer.attrs().add(this);
1524        }
1525        public void readFrom(DataInputStream in) throws IOException {
1526            name = in.readShort();
1527            size = in.readInt();
1528            item = readRawBytes(in, size);
1529        }
1530        public void writeTo(DataOutputStream out) throws IOException {
1531            out.writeShort(name);
1532            // write the 4-byte size header and then the contents:
1533            byte[] bytes;
1534            int trueSize;
1535            if (item instanceof byte[]) {
1536                bytes = (byte[]) item;
1537                out.writeInt(trueSize = bytes.length);
1538                out.write(bytes);
1539            } else {
1540                trueSize = flatten(out);
1541                //if (!(item instanceof Code))  System.err.println("wrote complex attr name="+(int)(char)name+" size="+trueSize+" data="+Arrays.toString(flatten()));
1542            }
1543            if (trueSize != size && size >= 0)
1544                System.err.println("warning: attribute size changed "+size+" to "+trueSize);
1545        }
1546        public void linkOuter(Outer o) {
1547            super.linkOuter(o);
1548            if (item instanceof byte[] &&
1549                outer instanceof Method &&
1550                ((Method)outer).outer().Code_index == name) {
1551                    item = readInput((byte[])item, Code.class);
1552            }
1553        }
1554        public List<Inner> inners() {
1555            if (item instanceof Inner)
1556                return Collections.nCopies(1, (Inner)item);
1557            return Collections.emptyList();
1558        }
1559        public List<Attr> attrs() { return null; }  // Code overrides this
1560        public byte[] flatten() {
1561            ByteArrayOutputStream buf = new ByteArrayOutputStream(Math.max(20, size));
1562            flatten(buf);
1563            return buf.toByteArray();
1564        }
1565        public int flatten(DataOutputStream out) throws IOException {
1566            ByteArrayOutputStream buf = new ByteArrayOutputStream(Math.max(20, size));
1567            int trueSize = flatten(buf);
1568            out.writeInt(trueSize);
1569            buf.writeTo(out);
1570            return trueSize;
1571        }
1572        private int flatten(ByteArrayOutputStream buf) {
1573            try {
1574                writeOutput(new DataOutputStream(buf), item);
1575                return buf.size();
1576            } catch (IOException ex) {
1577                throw new InternalError();
1578            }
1579        }
1580        public String nameString() {
1581            ClassFile cf = outer(ClassFile.class);
1582            if (cf == null)  return "#"+name;
1583            return cf.pool.getString(name);
1584        }
1585        public String toString() {
1586            return nameString()+(size < 0 ? "=" : "["+size+"]=")+item;
1587        }
1588    }
1589
1590    public static class Code extends InnerOuter implements Chunk {
1591        public short stacks, locals;
1592        public byte[] bytes;
1593        public final List<Short[]> etable = new CountedList<>(Short[].class, 4);
1594        public final List<Attr> attrs = new CountedList<>(Attr.class);
1595        // etable[N] = (N)*{ startpc, endpc, handlerpc, catchtype }
1596        public void readFrom(DataInputStream in) throws IOException {
1597            stacks = in.readShort(); locals = in.readShort();
1598            bytes = readRawBytes(in, in.readInt());
1599            readInputs(in, etable, attrs);
1600        }
1601        public void writeTo(DataOutputStream out) throws IOException {
1602            writeOutputs(out, stacks, locals, bytes.length, bytes, etable, attrs);
1603        }
1604        public List<Attr> inners() { return attrs; }
1605        public List<Attr> attrs() { return attrs; }
1606        public Instruction instructions() {
1607            return new Instruction(bytes, 0);
1608        }
1609    }
1610
1611    // lots of constants
1612    private static final byte
1613        CONSTANT_Utf8              = 1,
1614        CONSTANT_Integer           = 3,
1615        CONSTANT_Float             = 4,
1616        CONSTANT_Long              = 5,
1617        CONSTANT_Double            = 6,
1618        CONSTANT_Class             = 7,
1619        CONSTANT_String            = 8,
1620        CONSTANT_Field             = 9,
1621        CONSTANT_Method            = 10,
1622        CONSTANT_InterfaceMethod   = 11,
1623        CONSTANT_NameAndType       = 12,
1624        CONSTANT_MethodHandle      = 15,  // JSR 292
1625        CONSTANT_MethodType        = 16,  // JSR 292
1626        CONSTANT_InvokeDynamic     = 18;  // JSR 292
1627    private static final byte
1628        REF_getField               = 1,
1629        REF_getStatic              = 2,
1630        REF_putField               = 3,
1631        REF_putStatic              = 4,
1632        REF_invokeVirtual          = 5,
1633        REF_invokeStatic           = 6,
1634        REF_invokeSpecial          = 7,
1635        REF_newInvokeSpecial       = 8,
1636        REF_invokeInterface        = 9;
1637
1638    private static final int
1639        opc_nop                    = 0,
1640        opc_aconst_null            = 1,
1641        opc_nconst_MIN             = 2,  // iconst_m1
1642        opc_nconst_MAX             = 15, // dconst_1
1643        opc_bipush                 = 16,
1644        opc_sipush                 = 17,
1645        opc_ldc                    = 18,
1646        opc_ldc_w                  = 19,
1647        opc_ldc2_w                 = 20,
1648        opc_aload                  = 25,
1649        opc_aload_0                = 42,
1650        opc_aload_MAX              = 45,
1651        opc_aaload                 = 50,
1652        opc_astore                 = 58,
1653        opc_astore_0               = 75,
1654        opc_astore_MAX             = 78,
1655        opc_aastore                = 83,
1656        opc_pop                    = 87,
1657        opc_pop2                   = 88,
1658        opc_dup                    = 89,
1659        opc_dup_x1                 = 90,
1660        opc_dup_x2                 = 91,
1661        opc_dup2                   = 92,
1662        opc_dup2_x1                = 93,
1663        opc_dup2_x2                = 94,
1664        opc_swap                   = 95,
1665        opc_tableswitch            = 170,
1666        opc_lookupswitch           = 171,
1667        opc_areturn                = 176,
1668        opc_getstatic              = 178,
1669        opc_putstatic              = 179,
1670        opc_getfield               = 180,
1671        opc_putfield               = 181,
1672        opc_invokevirtual          = 182,
1673        opc_invokespecial          = 183,
1674        opc_invokestatic           = 184,
1675        opc_invokeinterface        = 185,
1676        opc_invokedynamic          = 186,
1677        opc_new                    = 187,
1678        opc_anewarray              = 189,
1679        opc_checkcast              = 192,
1680        opc_ifnull                 = 198,
1681        opc_ifnonnull              = 199,
1682        opc_wide                   = 196;
1683
1684    private static final Object[] INSTRUCTION_CONSTANTS = {
1685        -1, 0, 1, 2, 3, 4, 5, 0L, 1L, 0.0F, 1.0F, 2.0F, 0.0D, 1.0D
1686    };
1687
1688    private static final String INSTRUCTION_FORMATS =
1689        "nop$ aconst_null$L iconst_m1$I iconst_0$I iconst_1$I "+
1690        "iconst_2$I iconst_3$I iconst_4$I iconst_5$I lconst_0$J_ "+
1691        "lconst_1$J_ fconst_0$F fconst_1$F fconst_2$F dconst_0$D_ "+
1692        "dconst_1$D_ bipush=bx$I sipush=bxx$I ldc=bk$X ldc_w=bkk$X "+
1693        "ldc2_w=bkk$X_ iload=bl/wbll$I lload=bl/wbll$J_ fload=bl/wbll$F "+
1694        "dload=bl/wbll$D_ aload=bl/wbll$L iload_0$I iload_1$I "+
1695        "iload_2$I iload_3$I lload_0$J_ lload_1$J_ lload_2$J_ "+
1696        "lload_3$J_ fload_0$F fload_1$F fload_2$F fload_3$F dload_0$D_ "+
1697        "dload_1$D_ dload_2$D_ dload_3$D_ aload_0$L aload_1$L "+
1698        "aload_2$L aload_3$L iaload$LI$I laload$LI$J_ faload$LI$F "+
1699        "daload$LI$D_ aaload$LI$L baload$LI$I caload$LI$I saload$LI$I "+
1700        "istore=bl/wbll$I$ lstore=bl/wbll$J_$ fstore=bl/wbll$F$ "+
1701        "dstore=bl/wbll$D_$ astore=bl/wbll$L$ istore_0$I$ istore_1$I$ "+
1702        "istore_2$I$ istore_3$I$ lstore_0$J_$ lstore_1$J_$ "+
1703        "lstore_2$J_$ lstore_3$J_$ fstore_0$F$ fstore_1$F$ fstore_2$F$ "+
1704        "fstore_3$F$ dstore_0$D_$ dstore_1$D_$ dstore_2$D_$ "+
1705        "dstore_3$D_$ astore_0$L$ astore_1$L$ astore_2$L$ astore_3$L$ "+
1706        "iastore$LII$ lastore$LIJ_$ fastore$LIF$ dastore$LID_$ "+
1707        "aastore$LIL$ bastore$LII$ castore$LII$ sastore$LII$ pop$X$ "+
1708        "pop2$XX$ dup$X$XX dup_x1$XX$XXX dup_x2$XXX$XXXX dup2$XX$XXXX "+
1709        "dup2_x1$XXX$XXXXX dup2_x2$XXXX$XXXXXX swap$XX$XX "+
1710        "iadd$II$I ladd$J_J_$J_ fadd$FF$F dadd$D_D_$D_ isub$II$I "+
1711        "lsub$J_J_$J_ fsub$FF$F dsub$D_D_$D_ imul$II$I lmul$J_J_$J_ "+
1712        "fmul$FF$F dmul$D_D_$D_ idiv$II$I ldiv$J_J_$J_ fdiv$FF$F "+
1713        "ddiv$D_D_$D_ irem$II$I lrem$J_J_$J_ frem$FF$F drem$D_D_$D_ "+
1714        "ineg$I$I lneg$J_$J_ fneg$F$F dneg$D_$D_ ishl$II$I lshl$J_I$J_ "+
1715        "ishr$II$I lshr$J_I$J_ iushr$II$I lushr$J_I$J_ iand$II$I "+
1716        "land$J_J_$J_ ior$II$I lor$J_J_$J_ ixor$II$I lxor$J_J_$J_ "+
1717        "iinc=blx/wbllxx$ i2l$I$J_ i2f$I$F i2d$I$D_ l2i$J_$I l2f$J_$F "+
1718        "l2d$J_$D_ f2i$F$I f2l$F$J_ f2d$F$D_ d2i$D_$I d2l$D_$J_ "+
1719        "d2f$D_$F i2b$I$I i2c$I$I i2s$I$I lcmp fcmpl fcmpg dcmpl dcmpg "+
1720        "ifeq=boo ifne=boo iflt=boo ifge=boo ifgt=boo ifle=boo "+
1721        "if_icmpeq=boo if_icmpne=boo if_icmplt=boo if_icmpge=boo "+
1722        "if_icmpgt=boo if_icmple=boo if_acmpeq=boo if_acmpne=boo "+
1723        "goto=boo jsr=boo ret=bl/wbll tableswitch=* lookupswitch=* "+
1724        "ireturn lreturn freturn dreturn areturn return "+
1725        "getstatic=bkf$Q putstatic=bkf$Q$ getfield=bkf$L$Q "+
1726        "putfield=bkf$LQ$ invokevirtual=bkm$LQ$Q "+
1727        "invokespecial=bkm$LQ$Q invokestatic=bkm$Q$Q "+
1728        "invokeinterface=bkixx$LQ$Q invokedynamic=bkd__$Q$Q new=bkc$L "+
1729        "newarray=bx$I$L anewarray=bkc$I$L arraylength$L$I athrow "+
1730        "checkcast=bkc$L$L instanceof=bkc$L$I monitorenter$L "+
1731        "monitorexit$L wide=* multianewarray=bkcx ifnull=boo "+
1732        "ifnonnull=boo goto_w=boooo jsr_w=boooo ";
1733    private static final String[] INSTRUCTION_NAMES;
1734    private static final String[] INSTRUCTION_POPS;
1735    private static final int[] INSTRUCTION_INFO;
1736    static {
1737        String[] insns = INSTRUCTION_FORMATS.split(" ");
1738        assert(insns[opc_lookupswitch].startsWith("lookupswitch"));
1739        assert(insns[opc_tableswitch].startsWith("tableswitch"));
1740        assert(insns[opc_wide].startsWith("wide"));
1741        assert(insns[opc_invokedynamic].startsWith("invokedynamic"));
1742        int[] info = new int[256];
1743        String[] names = new String[256];
1744        String[] pops = new String[256];
1745        for (int i = 0; i < insns.length; i++) {
1746            String insn = insns[i];
1747            int dl = insn.indexOf('$');
1748            if (dl > 0) {
1749                String p = insn.substring(dl+1);
1750                if (p.indexOf('$') < 0)  p = "$" + p;
1751                pops[i] = p;
1752                insn = insn.substring(0, dl);
1753            }
1754            int eq = insn.indexOf('=');
1755            if (eq < 0) {
1756                info[i] = 1;
1757                names[i] = insn;
1758                continue;
1759            }
1760            names[i] = insn.substring(0, eq);
1761            String fmt = insn.substring(eq+1);
1762            if (fmt.equals("*")) {
1763                info[i] = 0;
1764                continue;
1765            }
1766            int sl = fmt.indexOf('/');
1767            if (sl < 0) {
1768                info[i] = (char) fmt.length();
1769            } else {
1770                String wfmt = fmt.substring(sl+1);
1771                fmt = fmt.substring(0, sl);
1772                info[i] = (char)( fmt.length() + (wfmt.length() * 16) );
1773            }
1774        }
1775        INSTRUCTION_INFO = info;
1776        INSTRUCTION_NAMES = names;
1777        INSTRUCTION_POPS = pops;
1778    }
1779
1780    public static class Instruction implements Cloneable {
1781        byte[] codeBase;
1782        int pc;
1783        int bc;
1784        int info;
1785        int wide;
1786        int len;
1787        Instruction(byte[] codeBase, int pc) {
1788            this.codeBase = codeBase;
1789            init(pc);
1790        }
1791        public Instruction clone() {
1792            try {
1793                return (Instruction) super.clone();
1794            } catch (CloneNotSupportedException ex) {
1795                throw new InternalError();
1796            }
1797        }
1798        private Instruction init(int pc) {
1799            this.pc = pc;
1800            this.bc = codeBase[pc] & 0xFF;
1801            this.info = INSTRUCTION_INFO[bc];
1802            this.wide = 0;
1803            this.len = (info & 0x0F);
1804            if (len == 0)
1805                computeLength();
1806            return this;
1807        }
1808        Instruction next() {
1809            if (len == 0 && bc != 0)  throw new InternalError();
1810            int npc = pc + len;
1811            if (npc == codeBase.length)
1812                return null;
1813            return init(npc);
1814        }
1815        void forceNext(int newLen) {
1816            bc = opc_nop;
1817            len = newLen;
1818        }
1819
1820        public String toString() {
1821            StringBuilder buf = new StringBuilder();
1822            buf.append(pc).append(":").append(INSTRUCTION_NAMES[bc]);
1823            switch (len) {
1824            case 3: buf.append(" ").append(u2At(1)); break;
1825            case 5: buf.append(" ").append(u2At(1)).append(" ").append(u2At(3)); break;
1826            default:  for (int i = 1; i < len; i++)  buf.append(" ").append(u1At(1));
1827            }
1828            return buf.toString();
1829        }
1830
1831        // these are the hard parts
1832        private void computeLength() {
1833            int cases;
1834            switch (bc) {
1835            case opc_wide:
1836                bc = codeBase[pc + 1];
1837                info = INSTRUCTION_INFO[bc];
1838                len = ((info >> 4) & 0x0F);
1839                if (len == 0)  throw new RuntimeException("misplaced wide bytecode: "+bc);
1840                return;
1841
1842            case opc_tableswitch:
1843                cases = (u4At(alignedIntOffset(2)) - u4At(alignedIntOffset(1)) + 1);
1844                len = alignedIntOffset(3 + cases*1);
1845                return;
1846
1847            case opc_lookupswitch:
1848                cases = u4At(alignedIntOffset(1));
1849                len = alignedIntOffset(2 + cases*2);
1850                return;
1851
1852            default:
1853                throw new RuntimeException("unknown bytecode: "+bc);
1854            }
1855        }
1856        // switch code
1857        // clget the Nth int (where 0 is the first after the opcode itself)
1858        public int alignedIntOffset(int n) {
1859            int pos = pc + 1;
1860            pos += ((-pos) & 0x03);  // align it
1861            pos += (n * 4);
1862            return pos - pc;
1863        }
1864        public int u1At(int pos) {
1865            return (codeBase[pc+pos] & 0xFF);
1866        }
1867        public int u2At(int pos) {
1868            return (u1At(pos+0)<<8) + u1At(pos+1);
1869        }
1870        public int u4At(int pos) {
1871            return (u2At(pos+0)<<16) + u2At(pos+2);
1872        }
1873        public void u1AtPut(int pos, int x) {
1874            codeBase[pc+pos] = (byte)x;
1875        }
1876        public void u2AtPut(int pos, int x) {
1877            codeBase[pc+pos+0] = (byte)(x >> 8);
1878            codeBase[pc+pos+1] = (byte)(x >> 0);
1879        }
1880    }
1881
1882    static String simplifyType(String type) {
1883        String simpleType = OBJ_SIGNATURE.matcher(type).replaceAll("L");
1884        assert(simpleType.matches("^\\([A-Z]*\\)[A-Z]$"));
1885        // change (DD)D to (D_D_)D_
1886        simpleType = WIDE_SIGNATURE.matcher(simpleType).replaceAll("\\0_");
1887        return simpleType;
1888    }
1889    static int argsize(String type) {
1890        return simplifyType(type).length()-3;
1891    }
1892    private static final Pattern OBJ_SIGNATURE = Pattern.compile("\\[*L[^;]*;|\\[+[A-Z]");
1893    private static final Pattern WIDE_SIGNATURE = Pattern.compile("[JD]");
1894}
1895