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 
26 package indify;
27 
28 import java.util.*;
29 import java.io.*;
30 import java.lang.reflect.Modifier;
31 import 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
93 MT = (java.lang.Object)java.lang.Object
94 MH = adder(int,int)java.lang.Integer
95 adder(1,2) = 3
96 calling 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  */
104 public class Indify {
main(String... av)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 
run(String... av)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. */
runApplication(String... av)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 
parseOptions(List<String> av)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 
booleanOption(String s)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 
maybeExpandProperties(String s)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 
indify(String a)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 
ensureDirectory(File dir)256     private void ensureDirectory(File dir) {
257         if (dir.mkdirs() && !quiet)
258             System.err.println("created "+dir);
259     }
260 
indifyFile(File f, File dest)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 
classPathFile(File pathDir, String className)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 
indifyJar(File f, Object dest)286     public void indifyJar(File f, Object dest) throws IOException {
287         throw new UnsupportedOperationException("Not yet implemented");
288     }
289 
indifyTree(File f, File dest)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 
makeClassLoader()306     public ClassLoader makeClassLoader() {
307         return new Loader();
308     }
309     private class Loader extends ClassLoader {
Loader()310         Loader() {
311             this(Indify.class.getClassLoader());
312         }
Loader(ClassLoader parent)313         Loader(ClassLoader parent) {
314             super(parent);
315         }
loadClass(String name, boolean resolve)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         }
findClassInPath(String name)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         }
findClass(String name)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         }
transformAndLoadClass(File f)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<>();
Logic(ClassFile cf)377         Logic(ClassFile cf) {
378             this.cf = cf;
379             poolMarks = new char[cf.pool.size()];
380         }
transform()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.
findPop(Instruction i)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 
findPatternMethods()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 
reportPatternMethods(boolean quietly, boolean allowMatchFailure)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
initializeMarks()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         }
nameMark(String s)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         }
nameAndTypeMark(Short[] n12)601         char nameAndTypeMark(Short[] n12) {
602             return nameAndTypeMark(n12[0], n12[1]);
603         }
nameAndTypeMark(short n1, short n2)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 
matchType(String descr, String requiredType)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<>();
sp()626             int sp() { return stack.size(); }
push(Object x)627             void push(Object x) { stack.add(x); }
push2(Object x)628             void push2(Object x) { stack.add(EMPTY_SLOT); stack.add(x); }
pushAt(int pos, Object x)629             void pushAt(int pos, Object x) { stack.add(stack.size()+pos, x); }
pop()630             Object pop() { return stack.remove(sp()-1); }
top()631             Object top() { return stack.get(sp()-1); }
args(boolean hasRecv, String type)632             List<Object> args(boolean hasRecv, String type) {
633                 return args(argsize(type) + (hasRecv ? 1 : 0));
634             }
args(int argsize)635             List<Object> args(int argsize) {
636                 return stack.subList(sp()-argsize, sp());
637             }
stackMotion(int bc)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 = "_";
removeEmptyJVMSlots(List<Object> args)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 
scanPattern(Method m, char patternMark)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 
flattenVarargs(List<Object> args)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 
isConstant(Object x, int tag)977         private boolean isConstant(Object x, int tag) {
978             return x instanceof Constant && ((Constant)x).tag == tag;
979         }
makeMethodTypeCon(Object x)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         }
parseMemberLookup(byte refKind, List<Object> args)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         }
makeInvokeDynamicCon(List<Object> args)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 
bootstrapMethodSpecifiers(boolean createIfNotFound)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 
openInput(File f)1105     private DataInputStream openInput(File f) throws IOException {
1106         return new DataInputStream(new BufferedInputStream(new FileInputStream(f)));
1107     }
1108 
openOutput(File f)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 
readRawBytes(DataInputStream in, int size)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 {
readFrom(DataInputStream in)1125         void readFrom(DataInputStream in) throws IOException;
writeTo(DataOutputStream out)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;
CountedList(Class<? extends T> itemClass, int rowlen)1132         CountedList(Class<? extends T> itemClass, int rowlen) {
1133             this.itemClass = itemClass;
1134             this.rowlen = rowlen;
1135         }
CountedList(Class<? extends T> itemClass)1136         CountedList(Class<? extends T> itemClass) { this(itemClass, -1); }
readFrom(DataInputStream in)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         }
writeTo(DataOutputStream out)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 
readInput(DataInputStream in, Class<T> dataClass)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     }
readInput(byte[] bytes, Class<T> dataClass)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     }
readInputs(DataInputStream in, Object... data)1187     private static void readInputs(DataInputStream in, Object... data) throws IOException {
1188         for (Object x : data)  ((Chunk)x).readFrom(in);
1189     }
1190 
writeOutput(DataOutputStream out, Object data)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     }
writeOutputs(DataOutputStream out, Object... data)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 {
inners()1224         public abstract List<? extends Inner> inners();
linkInners()1225         protected void linkInners() {
1226             for (Inner i : inners()) {
1227                 i.linkOuter(this);
1228                 if (i instanceof Outer)
1229                     ((Outer)i).linkInners();
1230             }
1231         }
outer(Class<T> c)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 
attrs()1240         public abstract List<Attr> attrs();
findAttr(String name)1241         public Attr findAttr(String name) {
1242             return findAttr(outer(ClassFile.class).pool.stringIndex(name, false));
1243         }
findAttr(int name)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     }
outer()1252     public interface Inner { Outer outer(); void linkOuter(Outer o); }
1253     public abstract static class InnerOuter extends Outer implements Inner {
1254         public Outer outer;
outer()1255         public Outer outer() { return outer; }
linkOuter(Outer o)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;
Constant(int index, byte tag, T item)1262         public Constant(int index, byte tag, T item) {
1263             this.index = index;
1264             this.tag = tag;
1265             this.item = item;
1266         }
checkTag(byte tag)1267         public Constant checkTag(byte tag) {
1268             if (this.tag != tag)  throw new InternalError(this.toString());
1269             return this;
1270         }
itemString()1271         public String itemString() { return (String)item; }
itemIndex()1272         public Short itemIndex() { return (Short)item; }
itemIndexes()1273         public Short[] itemIndexes() { return (Short[])item; }
readFrom(DataInputStream in)1274         public void readFrom(DataInputStream in) throws IOException {
1275             throw new InternalError("do not call");
1276         }
writeTo(DataOutputStream out)1277         public void writeTo(DataOutputStream out) throws IOException {
1278             writeOutputs(out, tag, item);
1279         }
equals(Object x)1280         public boolean equals(Object x) { return (x instanceof Constant && equals((Constant)x)); }
equals(Constant that)1281         public boolean equals(Constant that) {
1282             return (this.tag == that.tag && this.itemAsComparable().equals(that.itemAsComparable()));
1283         }
hashCode()1284         public int hashCode() { return (tag * 31) + this.itemAsComparable().hashCode(); }
itemAsComparable()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         }
toString()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;
tagName(byte tag)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 
Pool()1310         public Pool() {
1311             super(Constant.class);
1312         }
readFrom(DataInputStream in)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         }
addConstant(byte tag, T item)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         }
readConstant(DataInputStream in)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:
get(int index)1369         public Constant get(int index) {
1370             // extra 1-bits get into the shorts
1371             return super.get((char) index);
1372         }
getString(byte tag, short index)1373         String getString(byte tag, short index) {
1374             get(index).checkTag(tag);
1375             return getString(index);
1376         }
getString(short index)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         }
getStrings(Short[] indexes)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         }
stringIndex(String name, boolean createIfNotFound)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         }
getMemberRef(short index)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 {
ClassFile(File f)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 
readFrom(DataInputStream in)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 
writeTo(File f)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 
writeTo(DataOutputStream out)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 
toByteArray()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 
inners()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         }
attrs()1461         public List<Attr> attrs() { return attrs; }
1462 
1463         // derived stuff:
nameString()1464         public String nameString() { return pool.getString(CONSTANT_Class, thisc); }
1465         int Code_index;
1466     }
1467 
findMember(List<T> mems, int name, int type)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);
readFrom(DataInputStream in)1479         public void readFrom(DataInputStream in) throws IOException {
1480             access = in.readShort(); name = in.readShort(); type = in.readShort();
1481             readInputs(in, attrs);
1482         }
writeTo(DataOutputStream out)1483         public void writeTo(DataOutputStream out) throws IOException {
1484             writeOutputs(out, access, name, type, attrs);
1485         }
inners()1486         public List<Attr> inners() { return attrs; }
attrs()1487         public List<Attr> attrs() { return attrs; }
outer()1488         public ClassFile outer() { return (ClassFile) outer; }
nameString()1489         public String nameString() { return outer().pool.getString(CONSTANT_Utf8, name); }
typeString()1490         public String typeString() { return outer().pool.getString(CONSTANT_Utf8, type); }
toString()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 {
code()1500         public Code code() {
1501             Attr a = findAttr("Code");
1502             if (a == null)  return null;
1503             return (Code) a.item;
1504         }
instructions()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 
Attr()1517         public Attr() {}
Attr(Outer outer, String name, Object item)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         }
readFrom(DataInputStream in)1525         public void readFrom(DataInputStream in) throws IOException {
1526             name = in.readShort();
1527             size = in.readInt();
1528             item = readRawBytes(in, size);
1529         }
writeTo(DataOutputStream out)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         }
linkOuter(Outer o)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         }
inners()1554         public List<Inner> inners() {
1555             if (item instanceof Inner)
1556                 return Collections.nCopies(1, (Inner)item);
1557             return Collections.emptyList();
1558         }
attrs()1559         public List<Attr> attrs() { return null; }  // Code overrides this
flatten()1560         public byte[] flatten() {
1561             ByteArrayOutputStream buf = new ByteArrayOutputStream(Math.max(20, size));
1562             flatten(buf);
1563             return buf.toByteArray();
1564         }
flatten(DataOutputStream out)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         }
flatten(ByteArrayOutputStream buf)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         }
nameString()1580         public String nameString() {
1581             ClassFile cf = outer(ClassFile.class);
1582             if (cf == null)  return "#"+name;
1583             return cf.pool.getString(name);
1584         }
toString()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 }
readFrom(DataInputStream in)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         }
writeTo(DataOutputStream out)1601         public void writeTo(DataOutputStream out) throws IOException {
1602             writeOutputs(out, stacks, locals, bytes.length, bytes, etable, attrs);
1603         }
inners()1604         public List<Attr> inners() { return attrs; }
attrs()1605         public List<Attr> attrs() { return attrs; }
instructions()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(" ");
assert(insns[opc_lookupswitch].startsWith(R))1738         assert(insns[opc_lookupswitch].startsWith("lookupswitch"));
assert(insns[opc_tableswitch].startsWith(R))1739         assert(insns[opc_tableswitch].startsWith("tableswitch"));
assert(insns[opc_wide].startsWith(R))1740         assert(insns[opc_wide].startsWith("wide"));
assert(insns[opc_invokedynamic].startsWith(R))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;
Instruction(byte[] codeBase, int pc)1787         Instruction(byte[] codeBase, int pc) {
1788             this.codeBase = codeBase;
1789             init(pc);
1790         }
clone()1791         public Instruction clone() {
1792             try {
1793                 return (Instruction) super.clone();
1794             } catch (CloneNotSupportedException ex) {
1795                 throw new InternalError();
1796             }
1797         }
init(int pc)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         }
next()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         }
forceNext(int newLen)1815         void forceNext(int newLen) {
1816             bc = opc_nop;
1817             len = newLen;
1818         }
1819 
toString()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
computeLength()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)
alignedIntOffset(int n)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         }
u1At(int pos)1864         public int u1At(int pos) {
1865             return (codeBase[pc+pos] & 0xFF);
1866         }
u2At(int pos)1867         public int u2At(int pos) {
1868             return (u1At(pos+0)<<8) + u1At(pos+1);
1869         }
u4At(int pos)1870         public int u4At(int pos) {
1871             return (u2At(pos+0)<<16) + u2At(pos+2);
1872         }
u1AtPut(int pos, int x)1873         public void u1AtPut(int pos, int x) {
1874             codeBase[pc+pos] = (byte)x;
1875         }
u2AtPut(int pos, int x)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 
simplifyType(String type)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     }
argsize(String type)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