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