/* * Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package indify; import java.util.*; import java.io.*; import java.lang.reflect.Modifier; import java.util.regex.*; /** * Transform one or more class files to incorporate JSR 292 features, * such as {@code invokedynamic}. * <p> * This is a standalone program in a single source file. * In this form, it may be useful for test harnesses, small experiments, and javadoc examples. * Copies of this file may show up in multiple locations for standalone usage. * The primary maintained location of this file is as follows: * <a href="http://kenai.com/projects/ninja/sources/indify-repo/content/src/indify/Indify.java"> * http://kenai.com/projects/ninja/sources/indify-repo/content/src/indify/Indify.java</a> * <p> * Static private methods named MH_x and MT_x (where x is arbitrary) * must be stereotyped generators of MethodHandle and MethodType * constants. All calls to them are transformed to {@code CONSTANT_MethodHandle} * and {@code CONSTANT_MethodType} "ldc" instructions. * The stereotyped code must create method types by calls to {@code methodType} or * {@code fromMethodDescriptorString}. The "lookup" argument must be created * by calls to {@code java.lang.invoke.MethodHandles#lookup MethodHandles.lookup}. * The class and string arguments must be constant. * The following methods of {@code java.lang.invoke.MethodHandle.Lookup Lookup} are * allowed for method handle creation: {@code findStatic}, {@code findVirtual}, * {@code findConstructor}, {@code findSpecial}, * {@code findGetter}, {@code findSetter}, * {@code findStaticGetter}, or {@code findStaticSetter}. * The call to one of these methods must be followed immediately * by an {@code areturn} instruction. * The net result of the call to the MH_x or MT_x method must be * the creation of a constant method handle. Thus, replacing calls * to MH_x or MT_x methods by {@code ldc} instructions should leave * the meaning of the program unchanged. * <p> * Static private methods named INDY_x must be stereotyped generators * of {@code invokedynamic} call sites. * All calls to them must be immediately followed by * {@code invokeExact} calls. * All such pairs of calls are transformed to {@code invokedynamic} * instructions. Each INDY_x method must begin with a call to a * MH_x method, which is taken to be its bootstrap method. * The method must be immediately invoked (via {@code invokeGeneric} * on constant lookup, name, and type arguments. An object array of * constants may also be appended to the {@code invokeGeneric call}. * This call must be cast to {@code CallSite}, and the result must be * immediately followed by a call to {@code dynamicInvoker}, with the * resulting method handle returned. * <p> * The net result of all of these actions is equivalent to the JVM's * execution of an {@code invokedynamic} instruction in the unlinked state. * Running this code once should produce the same results as running * the corresponding {@code invokedynamic} instruction. * In order to model the caching behavior, the code of an INDY_x * method is allowed to begin with getstatic, aaload, and if_acmpne * instructions which load a static method handle value and return it * if the value is non-null. * <p> * Example usage: * <blockquote><pre> $ JAVA_HOME=(some recent OpenJDK 7 build) $ ant $ $JAVA_HOME/bin/java -cp build/classes indify.Indify --overwrite --dest build/testout build/classes/indify/Example.class $ $JAVA_HOME/bin/java -cp build/classes indify.Example MT = (java.lang.Object)java.lang.Object MH = adder(int,int)java.lang.Integer adder(1,2) = 3 calling indy: 42 $ $JAVA_HOME/bin/java -cp build/testout indify.Example (same output as above) * </pre></blockquote> * <p> * A version of this transformation built on top of <a href="http://asm.ow2.org/">http://asm.ow2.org/</a> would be welcome. * @author John Rose */ public class Indify { public static void main(String... av) throws IOException { new Indify().run(av); } public File dest; public String[] classpath = {"."}; public boolean keepgoing = false; public boolean expandProperties = false; public boolean overwrite = false; public boolean quiet = false; public boolean verbose = false; public boolean all = false; public int verifySpecifierCount = -1; public void run(String... av) throws IOException { List<String> avl = new ArrayList<>(Arrays.asList(av)); parseOptions(avl); if (avl.isEmpty()) throw new IllegalArgumentException("Usage: indify [--dest dir] [option...] file..."); if ("--java".equals(avl.get(0))) { avl.remove(0); try { runApplication(avl.toArray(new String[0])); } catch (Exception ex) { if (ex instanceof RuntimeException) throw (RuntimeException) ex; throw new RuntimeException(ex); } return; } Exception err = null; for (String a : avl) { try { indify(a); } catch (Exception ex) { if (err == null) err = ex; System.err.println("failure on "+a); if (!keepgoing) break; } } if (err != null) { if (err instanceof IOException) throw (IOException) err; throw (RuntimeException) err; } } /** Execute the given application under a class loader which indifies all application classes. */ public void runApplication(String... av) throws Exception { List<String> avl = new ArrayList<>(Arrays.asList(av)); String mainClassName = avl.remove(0); av = avl.toArray(new String[0]); Class<?> mainClass = Class.forName(mainClassName, true, makeClassLoader()); java.lang.reflect.Method main = mainClass.getMethod("main", String[].class); try { main.setAccessible(true); } catch (SecurityException ex) { } main.invoke(null, (Object) av); } public void parseOptions(List<String> av) throws IOException { for (; !av.isEmpty(); av.remove(0)) { String a = av.get(0); if (a.startsWith("-")) { String a2 = null; int eq = a.indexOf('='); if (eq > 0) { a2 = maybeExpandProperties(a.substring(eq+1)); a = a.substring(0, eq+1); } switch (a) { case "--java": return; // keep this argument case "-d": case "--dest": case "-d=": case "--dest=": dest = new File(a2 != null ? a2 : maybeExpandProperties(av.remove(1))); break; case "-cp": case "--classpath": classpath = maybeExpandProperties(av.remove(1)).split("["+File.pathSeparatorChar+"]"); break; case "-k": case "--keepgoing": case "--keepgoing=": keepgoing = booleanOption(a2); // print errors but keep going break; case "--expand-properties": case "--expand-properties=": expandProperties = booleanOption(a2); // expand property references in subsequent arguments break; case "--verify-specifier-count": case "--verify-specifier-count=": verifySpecifierCount = Integer.valueOf(a2); break; case "--overwrite": case "--overwrite=": overwrite = booleanOption(a2); // overwrite output files break; case "--all": case "--all=": all = booleanOption(a2); // copy all classes, even if no patterns break; case "-q": case "--quiet": case "--quiet=": quiet = booleanOption(a2); // less output break; case "-v": case "--verbose": case "--verbose=": verbose = booleanOption(a2); // more output break; default: throw new IllegalArgumentException("unrecognized flag: "+a); } continue; } else { break; } } if (dest == null && !overwrite) throw new RuntimeException("no output specified; need --dest d or --overwrite"); if (expandProperties) { for (int i = 0; i < av.size(); i++) av.set(i, maybeExpandProperties(av.get(i))); } } private boolean booleanOption(String s) { if (s == null) return true; switch (s) { case "true": case "yes": case "on": case "1": return true; case "false": case "no": case "off": case "0": return false; } throw new IllegalArgumentException("unrecognized boolean flag="+s); } private String maybeExpandProperties(String s) { if (!expandProperties) return s; Set<String> propsDone = new HashSet<>(); while (s.contains("${")) { int lbrk = s.indexOf("${"); int rbrk = s.indexOf('}', lbrk); if (rbrk < 0) break; String prop = s.substring(lbrk+2, rbrk); if (!propsDone.add(prop)) break; String value = System.getProperty(prop); if (verbose) System.err.println("expanding ${"+prop+"} => "+value); if (value == null) break; s = s.substring(0, lbrk) + value + s.substring(rbrk+1); } return s; } public void indify(String a) throws IOException { File f = new File(a); String fn = f.getName(); if (fn.endsWith(".class") && f.isFile()) indifyFile(f, dest); else if (fn.endsWith(".jar") && f.isFile()) indifyJar(f, dest); else if (f.isDirectory()) indifyTree(f, dest); else if (!keepgoing) throw new RuntimeException("unrecognized file: "+a); } private void ensureDirectory(File dir) { if (dir.mkdirs() && !quiet) System.err.println("created "+dir); } public void indifyFile(File f, File dest) throws IOException { if (verbose) System.err.println("reading "+f); ClassFile cf = new ClassFile(f); Logic logic = new Logic(cf); boolean changed = logic.transform(); logic.reportPatternMethods(quiet, keepgoing); if (changed || all) { File outfile; if (dest != null) { ensureDirectory(dest); outfile = classPathFile(dest, cf.nameString()); } else { outfile = f; // overwrite input file, no matter where it is } cf.writeTo(outfile); if (!quiet) System.err.println("wrote "+outfile); } } File classPathFile(File pathDir, String className) { String qualname = className.replace('.','/')+".class"; qualname = qualname.replace('/', File.separatorChar); return new File(pathDir, qualname); } public void indifyJar(File f, Object dest) throws IOException { throw new UnsupportedOperationException("Not yet implemented"); } public void indifyTree(File f, File dest) throws IOException { if (verbose) System.err.println("reading directory: "+f); for (File f2 : f.listFiles(new FilenameFilter() { public boolean accept(File dir, String name) { if (name.endsWith(".class")) return true; if (name.contains(".")) return false; // return true if it might be a package name: return Character.isJavaIdentifierStart(name.charAt(0)); }})) { if (f2.getName().endsWith(".class")) indifyFile(f2, dest); else if (f2.isDirectory()) indifyTree(f2, dest); } } public ClassLoader makeClassLoader() { return new Loader(); } private class Loader extends ClassLoader { Loader() { this(Indify.class.getClassLoader()); } Loader(ClassLoader parent) { super(parent); } public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { File f = findClassInPath(name); if (f != null) { try { Class<?> c = transformAndLoadClass(f); if (c != null) { if (resolve) resolveClass(c); return c; } } catch (ClassNotFoundException ex) { // fall through } catch (IOException ex) { // fall through } catch (Exception ex) { // pass error from reportPatternMethods, etc. if (ex instanceof RuntimeException) throw (RuntimeException) ex; throw new RuntimeException(ex); } } return super.loadClass(name, resolve); } private File findClassInPath(String name) { for (String s : classpath) { File f = classPathFile(new File(s), name); //System.out.println("Checking for "+f); if (f.exists() && f.canRead()) { return f; } } return null; } protected Class<?> findClass(String name) throws ClassNotFoundException { try { File f = findClassInPath(name); if (f != null) { Class<?> c = transformAndLoadClass(f); if (c != null) return c; } } catch (IOException ex) { throw new ClassNotFoundException("IO error", ex); } throw new ClassNotFoundException(); } private Class<?> transformAndLoadClass(File f) throws ClassNotFoundException, IOException { if (verbose) System.err.println("Loading class from "+f); ClassFile cf = new ClassFile(f); Logic logic = new Logic(cf); boolean changed = logic.transform(); if (verbose && !changed) System.err.println("(no change)"); logic.reportPatternMethods(!verbose, keepgoing); byte[] bytes = cf.toByteArray(); return defineClass(null, bytes, 0, bytes.length); } } private class Logic { // Indify logic, per se. ClassFile cf; final char[] poolMarks; final Map<Method,Constant> constants = new HashMap<>(); final Map<Method,String> indySignatures = new HashMap<>(); Logic(ClassFile cf) { this.cf = cf; poolMarks = new char[cf.pool.size()]; } boolean transform() { if (!initializeMarks()) return false; if (!findPatternMethods()) return false; Pool pool = cf.pool; //for (Constant c : cp) System.out.println(" # "+c); for (Method m : cf.methods) { if (constants.containsKey(m)) continue; // don't bother // Transform references. int blab = 0; for (Instruction i = m.instructions(); i != null; i = i.next()) { if (i.bc != opc_invokestatic) continue; int methi = i.u2At(1); if (poolMarks[methi] == 0) continue; Short[] ref = pool.getMemberRef((short)methi); Method conm = findMember(cf.methods, ref[1], ref[2]); if (conm == null) continue; Constant con = constants.get(conm); if (con == null) continue; if (blab++ == 0 && !quiet) System.err.println("patching "+cf.nameString()+"."+m); //if (blab == 1) { for (Instruction j = m.instructions(); j != null; j = j.next()) System.out.println(" |"+j); } if (con.tag == CONSTANT_InvokeDynamic) { // need to patch the following instruction too, // but there are usually intervening argument pushes too Instruction i2 = findPop(i); Short[] ref2 = null; short ref2i = 0; if (i2 != null && i2.bc == opc_invokevirtual && poolMarks[(char)(ref2i = (short) i2.u2At(1))] == 'D') ref2 = pool.getMemberRef(ref2i); if (ref2 == null || !"invokeExact".equals(pool.getString(ref2[1]))) { System.err.println(m+": failed to create invokedynamic at "+i.pc); continue; } String invType = pool.getString(ref2[2]); String bsmType = indySignatures.get(conm); if (!invType.equals(bsmType)) { System.err.println(m+": warning: "+conm+" call type and local invoke type differ: " +bsmType+", "+invType); } assert(i.len == 3 || i2.len == 3); if (!quiet) System.err.println(i+" "+conm+";...; "+i2+" => invokedynamic "+con); int start = i.pc + 3, end = i2.pc; System.arraycopy(i.codeBase, start, i.codeBase, i.pc, end-start); i.forceNext(0); // force revisit of new instruction i2.u1AtPut(-3, opc_invokedynamic); i2.u2AtPut(-2, con.index); i2.u2AtPut(0, (short)0); i2.u1AtPut(2, opc_nop); //System.out.println(new Instruction(i.codeBase, i2.pc-3)); } else { if (!quiet) System.err.println(i+" "+conm+" => ldc "+con); assert(i.len == 3); i.u1AtPut(0, opc_ldc_w); i.u2AtPut(1, con.index); } } //if (blab >= 1) { for (Instruction j = m.instructions(); j != null; j = j.next()) System.out.println(" |"+j); } } cf.methods.removeAll(constants.keySet()); return true; } // Scan forward from the instruction to find where the stack p // below the current sp at the instruction. Instruction findPop(Instruction i) { //System.out.println("findPop from "+i); Pool pool = cf.pool; JVMState jvm = new JVMState(); decode: for (i = i.clone().next(); i != null; i = i.next()) { String pops = INSTRUCTION_POPS[i.bc]; //System.out.println(" "+i+" "+jvm.stack+" : "+pops.replace("$", " => ")); if (pops == null) break; if (jvm.stackMotion(i.bc)) continue decode; if (pops.indexOf('Q') >= 0) { Short[] ref = pool.getMemberRef((short) i.u2At(1)); String type = simplifyType(pool.getString(CONSTANT_Utf8, ref[2])); switch (i.bc) { case opc_getstatic: case opc_getfield: case opc_putstatic: case opc_putfield: pops = pops.replace("Q", type); break; default: if (!type.startsWith("(")) throw new InternalError(i.toString()); pops = pops.replace("Q$Q", type.substring(1).replace(")","$")); break; } //System.out.println("special type: "+type+" => "+pops); } int npops = pops.indexOf('$'); if (npops < 0) throw new InternalError(); if (npops > jvm.sp()) return i; List<Object> args = jvm.args(npops); int k = 0; for (Object x : args) { char have = (Character) x; char want = pops.charAt(k++); if (have == 'X' || want == 'X') continue; if (have != want) break decode; } if (pops.charAt(k++) != '$') break decode; args.clear(); while (k < pops.length()) args.add(pops.charAt(k++)); } System.err.println("*** bailout on jvm: "+jvm.stack+" "+i); return null; } boolean findPatternMethods() { boolean found = false; for (char mark : "THI".toCharArray()) { for (Method m : cf.methods) { if (!Modifier.isPrivate(m.access)) continue; if (!Modifier.isStatic(m.access)) continue; if (nameAndTypeMark(m.name, m.type) == mark) { Constant con = scanPattern(m, mark); if (con == null) continue; constants.put(m, con); found = true; } } } return found; } void reportPatternMethods(boolean quietly, boolean allowMatchFailure) { if (!quietly && !constants.keySet().isEmpty()) System.err.println("pattern methods removed: "+constants.keySet()); for (Method m : cf.methods) { if (nameMark(cf.pool.getString(m.name)) != 0 && constants.get(m) == null) { String failure = "method has special name but fails to match pattern: "+m; if (!allowMatchFailure) throw new IllegalArgumentException(failure); else if (!quietly) System.err.println("warning: "+failure); } } if (verifySpecifierCount >= 0) { List<Object[]> specs = bootstrapMethodSpecifiers(false); int specsLen = (specs == null ? 0 : specs.size()); // Pass by specsLen == 0, to help with associated (inner) classes. if (specsLen == 0) specsLen = verifySpecifierCount; if (specsLen != verifySpecifierCount) { throw new IllegalArgumentException("BootstrapMethods length is "+specsLen+" but should be "+verifySpecifierCount); } } if (!quiet) System.err.flush(); } // mark constant pool entries according to participation in patterns boolean initializeMarks() { boolean changed = false; for (;;) { boolean changed1 = false; int cpindex = -1; for (Constant e : cf.pool) { ++cpindex; if (e == null) continue; char mark = poolMarks[cpindex]; if (mark != 0) continue; switch (e.tag) { case CONSTANT_Utf8: mark = nameMark(e.itemString()); break; case CONSTANT_NameAndType: mark = nameAndTypeMark(e.itemIndexes()); break; case CONSTANT_Class: { int n1 = e.itemIndex(); char nmark = poolMarks[(char)n1]; if ("DJ".indexOf(nmark) >= 0) mark = nmark; break; } case CONSTANT_Field: case CONSTANT_Method: { Short[] n12 = e.itemIndexes(); short cl = n12[0]; short nt = n12[1]; char cmark = poolMarks[(char)cl]; if (cmark != 0) { mark = cmark; // it is a java.lang.invoke.* or java.lang.* method break; } String cls = cf.pool.getString(CONSTANT_Class, cl); if (cls.equals(cf.nameString())) { switch (poolMarks[(char)nt]) { // it is a private MH/MT/INDY method case 'T': case 'H': case 'I': mark = poolMarks[(char)nt]; break; } } break; } default: break; } if (mark != 0) { poolMarks[cpindex] = mark; changed1 = true; } } if (!changed1) break; changed = true; } return changed; } char nameMark(String s) { if (s.startsWith("MT_")) return 'T'; else if (s.startsWith("MH_")) return 'H'; else if (s.startsWith("INDY_")) return 'I'; else if (s.startsWith("java/lang/invoke/")) return 'D'; else if (s.startsWith("java/lang/")) return 'J'; return 0; } char nameAndTypeMark(Short[] n12) { return nameAndTypeMark(n12[0], n12[1]); } char nameAndTypeMark(short n1, short n2) { char mark = poolMarks[(char)n1]; if (mark == 0) return 0; String descr = cf.pool.getString(CONSTANT_Utf8, n2); String requiredType; switch (poolMarks[(char)n1]) { case 'H': requiredType = "()Ljava/lang/invoke/MethodHandle;"; break; case 'T': requiredType = "()Ljava/lang/invoke/MethodType;"; break; case 'I': requiredType = "()Ljava/lang/invoke/MethodHandle;"; break; default: return 0; } if (matchType(descr, requiredType)) return mark; return 0; } boolean matchType(String descr, String requiredType) { if (descr.equals(requiredType)) return true; return false; } private class JVMState { final List<Object> stack = new ArrayList<>(); int sp() { return stack.size(); } void push(Object x) { stack.add(x); } void push2(Object x) { stack.add(EMPTY_SLOT); stack.add(x); } void pushAt(int pos, Object x) { stack.add(stack.size()+pos, x); } Object pop() { return stack.remove(sp()-1); } Object top() { return stack.get(sp()-1); } List<Object> args(boolean hasRecv, String type) { return args(argsize(type) + (hasRecv ? 1 : 0)); } List<Object> args(int argsize) { return stack.subList(sp()-argsize, sp()); } boolean stackMotion(int bc) { switch (bc) { case opc_pop: pop(); break; case opc_pop2: pop(); pop(); break; case opc_swap: pushAt(-1, pop()); break; case opc_dup: push(top()); break; case opc_dup_x1: pushAt(-2, top()); break; case opc_dup_x2: pushAt(-3, top()); break; // ? also: dup2{,_x1,_x2} default: return false; } return true; } } private final String EMPTY_SLOT = "_"; private void removeEmptyJVMSlots(List<Object> args) { for (;;) { int i = args.indexOf(EMPTY_SLOT); if (i >= 0 && i+1 < args.size() && (isConstant(args.get(i+1), CONSTANT_Long) || isConstant(args.get(i+1), CONSTANT_Double))) args.remove(i); else break; } } private Constant scanPattern(Method m, char patternMark) { if (verbose) System.err.println("scan "+m+" for pattern="+patternMark); int wantTag; switch (patternMark) { case 'T': wantTag = CONSTANT_MethodType; break; case 'H': wantTag = CONSTANT_MethodHandle; break; case 'I': wantTag = CONSTANT_InvokeDynamic; break; default: throw new InternalError(); } Instruction i = m.instructions(); JVMState jvm = new JVMState(); Pool pool = cf.pool; int branchCount = 0; Object arg; List<Object> args; List<Object> bsmArgs = null; // args to invokeGeneric decode: for (; i != null; i = i.next()) { //System.out.println(jvm.stack+" "+i); int bc = i.bc; switch (bc) { case opc_ldc: jvm.push(pool.get(i.u1At(1))); break; case opc_ldc_w: jvm.push(pool.get(i.u2At(1))); break; case opc_ldc2_w: jvm.push2(pool.get(i.u2At(1))); break; case opc_aconst_null: jvm.push(null); break; case opc_bipush: jvm.push((int)(byte) i.u1At(1)); break; case opc_sipush: jvm.push((int)(short)i.u2At(1)); break; // these support creation of a restarg array case opc_anewarray: arg = jvm.pop(); if (!(arg instanceof Integer)) break decode; arg = Arrays.asList(new Object[(Integer)arg]); jvm.push(arg); break; case opc_dup: jvm.push(jvm.top()); break; case opc_aastore: args = jvm.args(3); // array, index, value if (args.get(0) instanceof List && args.get(1) instanceof Integer) { ((List<Object>)args.get(0)).set( (Integer)args.get(1), args.get(2) ); } args.clear(); break; case opc_new: { String type = pool.getString(CONSTANT_Class, (short)i.u2At(1)); //System.out.println("new "+type); switch (type) { case "java/lang/StringBuilder": jvm.push("StringBuilder"); continue decode; // go to next instruction } break decode; // bail out } case opc_getstatic: { // int.class compiles to getstatic Integer.TYPE int fieldi = i.u2At(1); char mark = poolMarks[fieldi]; //System.err.println("getstatic "+fieldi+Arrays.asList(pool.getStrings(pool.getMemberRef((short)fieldi)))+mark); if (mark == 'J') { Short[] ref = pool.getMemberRef((short) fieldi); String name = pool.getString(CONSTANT_Utf8, ref[1]); if ("TYPE".equals(name)) { String wrapperName = pool.getString(CONSTANT_Class, ref[0]).replace('/', '.'); // a primitive type descriptor Class<?> primClass; try { primClass = (Class<?>) Class.forName(wrapperName).getField(name).get(null); } catch (Exception ex) { throw new InternalError("cannot load "+wrapperName+"."+name); } jvm.push(primClass); break; } } // unknown field; keep going... jvm.push(UNKNOWN_CON); break; } case opc_putstatic: { if (patternMark != 'I') break decode; jvm.pop(); // unknown field; keep going... break; } case opc_invokestatic: case opc_invokevirtual: case opc_invokespecial: { boolean hasRecv = (bc != opc_invokestatic); int methi = i.u2At(1); char mark = poolMarks[methi]; Short[] ref = pool.getMemberRef((short)methi); String type = pool.getString(CONSTANT_Utf8, ref[2]); //System.out.println("invoke "+pool.getString(CONSTANT_Utf8, ref[1])+" "+Arrays.asList(ref)+" : "+type); args = jvm.args(hasRecv, type); String intrinsic = null; Constant con; if (mark == 'D' || mark == 'J') { intrinsic = pool.getString(CONSTANT_Utf8, ref[1]); if (mark == 'J') { String cls = pool.getString(CONSTANT_Class, ref[0]); cls = cls.substring(1+cls.lastIndexOf('/')); intrinsic = cls+"."+intrinsic; } //System.out.println("recognized intrinsic "+intrinsic); byte refKind = -1; switch (intrinsic) { case "findGetter": refKind = REF_getField; break; case "findStaticGetter": refKind = REF_getStatic; break; case "findSetter": refKind = REF_putField; break; case "findStaticSetter": refKind = REF_putStatic; break; case "findVirtual": refKind = REF_invokeVirtual; break; case "findStatic": refKind = REF_invokeStatic; break; case "findSpecial": refKind = REF_invokeSpecial; break; case "findConstructor": refKind = REF_newInvokeSpecial; break; } if (refKind >= 0 && (con = parseMemberLookup(refKind, args)) != null) { args.clear(); args.add(con); continue; } } Method ownMethod = null; if (mark == 'T' || mark == 'H' || mark == 'I') { ownMethod = findMember(cf.methods, ref[1], ref[2]); } //if (intrinsic != null) System.out.println("intrinsic = "+intrinsic); switch (intrinsic == null ? "" : intrinsic) { case "fromMethodDescriptorString": con = makeMethodTypeCon(args.get(0)); args.clear(); args.add(con); continue; case "methodType": { flattenVarargs(args); // there are several overloadings, some with varargs StringBuilder buf = new StringBuilder(); String rtype = null; for (Object typeArg : args) { if (typeArg instanceof Class) { Class<?> argClass = (Class<?>) typeArg; if (argClass.isPrimitive()) { char tchar; switch (argClass.getName()) { case "void": tchar = 'V'; break; case "boolean": tchar = 'Z'; break; case "byte": tchar = 'B'; break; case "char": tchar = 'C'; break; case "short": tchar = 'S'; break; case "int": tchar = 'I'; break; case "long": tchar = 'J'; break; case "float": tchar = 'F'; break; case "double": tchar = 'D'; break; default: throw new InternalError(argClass.toString()); } buf.append(tchar); } else { // should not happen, but... buf.append('L').append(argClass.getName().replace('.','/')).append(';'); } } else if (typeArg instanceof Constant) { Constant argCon = (Constant) typeArg; if (argCon.tag == CONSTANT_Class) { String cn = pool.get(argCon.itemIndex()).itemString(); if (cn.endsWith(";")) buf.append(cn); else buf.append('L').append(cn).append(';'); } else { break decode; } } else { break decode; } if (rtype == null) { // first arg is treated differently rtype = buf.toString(); buf.setLength(0); buf.append('('); } } buf.append(')').append(rtype); con = con = makeMethodTypeCon(buf.toString()); args.clear(); args.add(con); continue; } case "lookup": case "dynamicInvoker": args.clear(); args.add(intrinsic); continue; case "lookupClass": if (args.equals(Arrays.asList("lookup"))) { // fold lookup().lookupClass() to the enclosing class args.clear(); args.add(pool.get(cf.thisc)); continue; } break; case "invoke": case "invokeGeneric": case "invokeWithArguments": if (patternMark != 'I') break decode; if ("invokeWithArguments".equals(intrinsic)) flattenVarargs(args); bsmArgs = new ArrayList(args); args.clear(); args.add("invokeGeneric"); continue; case "Integer.valueOf": case "Float.valueOf": case "Long.valueOf": case "Double.valueOf": removeEmptyJVMSlots(args); if (args.size() == 1) { arg = args.remove(0); assert(3456 == (CONSTANT_Integer*1000 + CONSTANT_Float*100 + CONSTANT_Long*10 + CONSTANT_Double)); if (isConstant(arg, CONSTANT_Integer + "IFLD".indexOf(intrinsic.charAt(0))) || arg instanceof Number) { args.add(arg); continue; } } break decode; case "StringBuilder.append": // allow calls like ("value = "+x) removeEmptyJVMSlots(args); args.subList(1, args.size()).clear(); continue; case "StringBuilder.toString": args.clear(); args.add(intrinsic); continue; } if (!hasRecv && ownMethod != null && patternMark != 0) { con = constants.get(ownMethod); if (con == null) break decode; args.clear(); args.add(con); continue; } else if (type.endsWith(")V")) { // allow calls like println("reached the pattern method") args.clear(); continue; } break decode; // bail out for most calls } case opc_areturn: { ++branchCount; if (bsmArgs != null) { // parse bsmArgs as (MH, lookup, String, MT, [extra]) Constant indyCon = makeInvokeDynamicCon(bsmArgs); if (indyCon != null) { Constant typeCon = (Constant) bsmArgs.get(3); indySignatures.put(m, pool.getString(typeCon.itemIndex())); return indyCon; } System.err.println(m+": inscrutable bsm arguments: "+bsmArgs); break decode; // bail out } arg = jvm.pop(); if (branchCount == 2 && UNKNOWN_CON.equals(arg)) break; // merge to next path if (isConstant(arg, wantTag)) return (Constant) arg; break decode; // bail out } default: if (jvm.stackMotion(i.bc)) break; if (bc >= opc_nconst_MIN && bc <= opc_nconst_MAX) { jvm.push(INSTRUCTION_CONSTANTS[bc - opc_nconst_MIN]); break; } if (patternMark == 'I') { // these support caching paths in INDY_x methods if (bc == opc_aload || bc >= opc_aload_0 && bc <= opc_aload_MAX) { jvm.push(UNKNOWN_CON); break; } if (bc == opc_astore || bc >= opc_astore_0 && bc <= opc_astore_MAX) { jvm.pop(); break; } switch (bc) { case opc_getfield: case opc_aaload: jvm.push(UNKNOWN_CON); break; case opc_ifnull: case opc_ifnonnull: // ignore branch target if (++branchCount != 1) break decode; jvm.pop(); break; case opc_checkcast: arg = jvm.top(); if ("invokeWithArguments".equals(arg) || "invokeGeneric".equals(arg)) break; // assume it is a helpful cast break decode; default: break decode; // bail out } continue decode; // go to next instruction } break decode; // bail out } //end switch } System.err.println(m+": bailout on "+i+" jvm stack: "+jvm.stack); return null; } private final String UNKNOWN_CON = "<unknown>"; private void flattenVarargs(List<Object> args) { int size = args.size(); if (size > 0 && args.get(size-1) instanceof List) args.addAll((List<Object>) args.remove(size-1)); } private boolean isConstant(Object x, int tag) { return x instanceof Constant && ((Constant)x).tag == tag; } private Constant makeMethodTypeCon(Object x) { short utfIndex; if (x instanceof String) utfIndex = (short) cf.pool.addConstant(CONSTANT_Utf8, x).index; else if (isConstant(x, CONSTANT_String)) utfIndex = ((Constant)x).itemIndex(); else return null; return cf.pool.addConstant(CONSTANT_MethodType, utfIndex); } private Constant parseMemberLookup(byte refKind, List<Object> args) { // E.g.: lookup().findStatic(Foo.class, "name", MethodType) if (args.size() != 4) return null; int argi = 0; if (!"lookup".equals(args.get(argi++))) return null; short refindex, cindex, ntindex, nindex, tindex; Object con; if (!isConstant(con = args.get(argi++), CONSTANT_Class)) return null; cindex = (short)((Constant)con).index; if (!isConstant(con = args.get(argi++), CONSTANT_String)) return null; nindex = ((Constant)con).itemIndex(); if (isConstant(con = args.get(argi++), CONSTANT_MethodType) || isConstant(con, CONSTANT_Class)) { tindex = ((Constant)con).itemIndex(); } else return null; ntindex = (short) cf.pool.addConstant(CONSTANT_NameAndType, new Short[]{ nindex, tindex }).index; byte reftag = CONSTANT_Method; if (refKind <= REF_putStatic) reftag = CONSTANT_Field; else if (refKind == REF_invokeInterface) reftag = CONSTANT_InterfaceMethod; Constant ref = cf.pool.addConstant(reftag, new Short[]{ cindex, ntindex }); return cf.pool.addConstant(CONSTANT_MethodHandle, new Object[]{ refKind, (short)ref.index }); } private Constant makeInvokeDynamicCon(List<Object> args) { // E.g.: MH_bsm.invokeGeneric(lookup(), "name", MethodType, "extraArg") removeEmptyJVMSlots(args); if (args.size() < 4) return null; int argi = 0; short nindex, tindex, ntindex, bsmindex; Object con; if (!isConstant(con = args.get(argi++), CONSTANT_MethodHandle)) return null; bsmindex = (short) ((Constant)con).index; if (!"lookup".equals(args.get(argi++))) return null; if (!isConstant(con = args.get(argi++), CONSTANT_String)) return null; nindex = ((Constant)con).itemIndex(); if (!isConstant(con = args.get(argi++), CONSTANT_MethodType)) return null; tindex = ((Constant)con).itemIndex(); ntindex = (short) cf.pool.addConstant(CONSTANT_NameAndType, new Short[]{ nindex, tindex }).index; List<Object> extraArgs = new ArrayList<Object>(); if (argi < args.size()) { extraArgs.addAll(args.subList(argi, args.size() - 1)); Object lastArg = args.get(args.size() - 1); if (lastArg instanceof List) { List<Object> lastArgs = (List<Object>) lastArg; removeEmptyJVMSlots(lastArgs); extraArgs.addAll(lastArgs); } else { extraArgs.add(lastArg); } } List<Short> extraArgIndexes = new CountedList<>(Short.class); for (Object x : extraArgs) { if (x instanceof Number) { Object num = null; byte numTag = 0; if (x instanceof Integer) { num = x; numTag = CONSTANT_Integer; } if (x instanceof Float) { num = Float.floatToRawIntBits((Float)x); numTag = CONSTANT_Float; } if (x instanceof Long) { num = x; numTag = CONSTANT_Long; } if (x instanceof Double) { num = Double.doubleToRawLongBits((Double)x); numTag = CONSTANT_Double; } if (num != null) x = cf.pool.addConstant(numTag, x); } if (!(x instanceof Constant)) { System.err.println("warning: unrecognized BSM argument "+x); return null; } extraArgIndexes.add((short) ((Constant)x).index); } List<Object[]> specs = bootstrapMethodSpecifiers(true); int specindex = -1; Object[] spec = new Object[]{ bsmindex, extraArgIndexes }; for (Object[] spec1 : specs) { if (Arrays.equals(spec1, spec)) { specindex = specs.indexOf(spec1); if (verbose) System.err.println("reusing BSM specifier: "+spec1[0]+spec1[1]); break; } } if (specindex == -1) { specindex = (short) specs.size(); specs.add(spec); if (verbose) System.err.println("adding BSM specifier: "+spec[0]+spec[1]); } return cf.pool.addConstant(CONSTANT_InvokeDynamic, new Short[]{ (short)specindex, ntindex }); } List<Object[]> bootstrapMethodSpecifiers(boolean createIfNotFound) { Attr bsms = cf.findAttr("BootstrapMethods"); if (bsms == null) { if (!createIfNotFound) return null; bsms = new Attr(cf, "BootstrapMethods", new byte[]{0,0}); assert(bsms == cf.findAttr("BootstrapMethods")); } if (bsms.item instanceof byte[]) { // unflatten List<Object[]> specs = new CountedList<>(Object[].class); DataInputStream in = new DataInputStream(new ByteArrayInputStream((byte[]) bsms.item)); try { int len = (char) in.readShort(); for (int i = 0; i < len; i++) { short bsm = in.readShort(); int argc = (char) in.readShort(); List<Short> argv = new CountedList<>(Short.class); for (int j = 0; j < argc; j++) argv.add(in.readShort()); specs.add(new Object[]{ bsm, argv }); } } catch (IOException ex) { throw new InternalError(); } bsms.item = specs; } return (List<Object[]>) bsms.item; } } private DataInputStream openInput(File f) throws IOException { return new DataInputStream(new BufferedInputStream(new FileInputStream(f))); } private DataOutputStream openOutput(File f) throws IOException { if (!overwrite && f.exists()) throw new IOException("file already exists: "+f); ensureDirectory(f.getParentFile()); return new DataOutputStream(new BufferedOutputStream(new FileOutputStream(f))); } static byte[] readRawBytes(DataInputStream in, int size) throws IOException { byte[] bytes = new byte[size]; int nr = in.read(bytes); if (nr != size) throw new InternalError("wrong size: "+nr); return bytes; } private interface Chunk { void readFrom(DataInputStream in) throws IOException; void writeTo(DataOutputStream out) throws IOException; } private static class CountedList<T> extends ArrayList<T> implements Chunk { final Class<? extends T> itemClass; final int rowlen; CountedList(Class<? extends T> itemClass, int rowlen) { this.itemClass = itemClass; this.rowlen = rowlen; } CountedList(Class<? extends T> itemClass) { this(itemClass, -1); } public void readFrom(DataInputStream in) throws IOException { int count = in.readUnsignedShort(); while (size() < count) { if (rowlen < 0) { add(readInput(in, itemClass)); } else { Class<?> elemClass = itemClass.getComponentType(); Object[] row = (Object[]) java.lang.reflect.Array.newInstance(elemClass, rowlen); for (int i = 0; i < rowlen; i++) row[i] = readInput(in, elemClass); add(itemClass.cast(row)); } } } public void writeTo(DataOutputStream out) throws IOException { out.writeShort((short)size()); for (T item : this) { writeOutput(out, item); } } } private static <T> T readInput(DataInputStream in, Class<T> dataClass) throws IOException { Object data; if (dataClass == Integer.class) { data = in.readInt(); } else if (dataClass == Short.class) { data = in.readShort(); } else if (dataClass == Byte.class) { data = in.readByte(); } else if (dataClass == String.class) { data = in.readUTF(); } else if (Chunk.class.isAssignableFrom(dataClass)) { T obj; try { obj = dataClass.newInstance(); } catch (Exception ex) { throw new RuntimeException(ex); } ((Chunk)obj).readFrom(in); data = obj; } else { throw new InternalError("bad input datum: "+dataClass); } return dataClass.cast(data); } private static <T> T readInput(byte[] bytes, Class<T> dataClass) { try { return readInput(new DataInputStream(new ByteArrayInputStream(bytes)), dataClass); } catch (IOException ex) { throw new InternalError(); } } private static void readInputs(DataInputStream in, Object... data) throws IOException { for (Object x : data) ((Chunk)x).readFrom(in); } private static void writeOutput(DataOutputStream out, Object data) throws IOException { if (data == null) { return; } if (data instanceof Integer) { out.writeInt((Integer)data); } else if (data instanceof Long) { out.writeLong((Long)data); } else if (data instanceof Short) { out.writeShort((Short)data); } else if (data instanceof Byte) { out.writeByte((Byte)data); } else if (data instanceof String) { out.writeUTF((String)data); } else if (data instanceof byte[]) { out.write((byte[])data); } else if (data instanceof Object[]) { for (Object x : (Object[]) data) writeOutput(out, x); } else if (data instanceof Chunk) { Chunk x = (Chunk) data; x.writeTo(out); } else if (data instanceof List) { for (Object x : (List<?>) data) writeOutput(out, x); } else { throw new InternalError("bad output datum: "+data+" : "+data.getClass().getName()); } } private static void writeOutputs(DataOutputStream out, Object... data) throws IOException { for (Object x : data) writeOutput(out, x); } public abstract static class Outer { public abstract List<? extends Inner> inners(); protected void linkInners() { for (Inner i : inners()) { i.linkOuter(this); if (i instanceof Outer) ((Outer)i).linkInners(); } } public <T extends Outer> T outer(Class<T> c) { for (Outer walk = this;; walk = ((Inner)walk).outer()) { if (c.isInstance(walk)) return c.cast(walk); //if (!(walk instanceof Inner)) return null; } } public abstract List<Attr> attrs(); public Attr findAttr(String name) { return findAttr(outer(ClassFile.class).pool.stringIndex(name, false)); } public Attr findAttr(int name) { if (name == 0) return null; for (Attr a : attrs()) { if (a.name == name) return a; } return null; } } public interface Inner { Outer outer(); void linkOuter(Outer o); } public abstract static class InnerOuter extends Outer implements Inner { public Outer outer; public Outer outer() { return outer; } public void linkOuter(Outer o) { assert(outer == null); outer = o; } } public static class Constant<T> implements Chunk { public final byte tag; public final T item; public final int index; public Constant(int index, byte tag, T item) { this.index = index; this.tag = tag; this.item = item; } public Constant checkTag(byte tag) { if (this.tag != tag) throw new InternalError(this.toString()); return this; } public String itemString() { return (String)item; } public Short itemIndex() { return (Short)item; } public Short[] itemIndexes() { return (Short[])item; } public void readFrom(DataInputStream in) throws IOException { throw new InternalError("do not call"); } public void writeTo(DataOutputStream out) throws IOException { writeOutputs(out, tag, item); } public boolean equals(Object x) { return (x instanceof Constant && equals((Constant)x)); } public boolean equals(Constant that) { return (this.tag == that.tag && this.itemAsComparable().equals(that.itemAsComparable())); } public int hashCode() { return (tag * 31) + this.itemAsComparable().hashCode(); } public Object itemAsComparable() { switch (tag) { case CONSTANT_Double: return Double.longBitsToDouble((Long)item); case CONSTANT_Float: return Float.intBitsToFloat((Integer)item); } return (item instanceof Object[] ? Arrays.asList((Object[])item) : item); } public String toString() { String itstr = String.valueOf(itemAsComparable()); return (index + ":" + tagName(tag) + (itstr.startsWith("[")?"":"=") + itstr); } private static String[] TAG_NAMES; public static String tagName(byte tag) { // used for error messages if (TAG_NAMES == null) TAG_NAMES = ("None Utf8 Unicode Integer Float Long Double Class String" +" Fieldref Methodref InterfaceMethodref NameAndType #13 #14" +" MethodHandle MethodType InvokeDynamic#17 InvokeDynamic").split(" "); if ((tag & 0xFF) >= TAG_NAMES.length) return "#"+(tag & 0xFF); return TAG_NAMES[tag & 0xFF]; } } public static class Pool extends CountedList<Constant> implements Chunk { private Map<String,Short> strings = new TreeMap<>(); public Pool() { super(Constant.class); } public void readFrom(DataInputStream in) throws IOException { int count = in.readUnsignedShort(); add(null); // always ignore first item while (size() < count) { readConstant(in); } } public <T> Constant<T> addConstant(byte tag, T item) { Constant<T> con = new Constant<>(size(), tag, item); int idx = indexOf(con); if (idx >= 0) return get(idx); add(con); if (tag == CONSTANT_Utf8) strings.put((String)item, (short) con.index); return con; } private void readConstant(DataInputStream in) throws IOException { byte tag = in.readByte(); int index = size(); Object arg; switch (tag) { case CONSTANT_Utf8: arg = in.readUTF(); strings.put((String) arg, (short) size()); break; case CONSTANT_Integer: case CONSTANT_Float: arg = in.readInt(); break; case CONSTANT_Long: case CONSTANT_Double: add(new Constant(index, tag, in.readLong())); add(null); return; case CONSTANT_Class: case CONSTANT_String: arg = in.readShort(); break; case CONSTANT_Field: case CONSTANT_Method: case CONSTANT_InterfaceMethod: case CONSTANT_NameAndType: case CONSTANT_InvokeDynamic: // read an ordered pair arg = new Short[] { in.readShort(), in.readShort() }; break; case CONSTANT_MethodHandle: // read an ordered pair; first part is a u1 (not u2) arg = new Object[] { in.readByte(), in.readShort() }; break; case CONSTANT_MethodType: arg = in.readShort(); break; default: throw new InternalError("bad CP tag "+tag); } add(new Constant(index, tag, arg)); } // Access: public Constant get(int index) { // extra 1-bits get into the shorts return super.get((char) index); } String getString(byte tag, short index) { get(index).checkTag(tag); return getString(index); } String getString(short index) { Object v = get(index).item; if (v instanceof Short) v = get((Short)v).checkTag(CONSTANT_Utf8).item; return (String) v; } String[] getStrings(Short[] indexes) { String[] res = new String[indexes.length]; for (int i = 0; i < indexes.length; i++) res[i] = getString(indexes[i]); return res; } int stringIndex(String name, boolean createIfNotFound) { Short x = strings.get(name); if (x != null) return (char)(int) x; if (!createIfNotFound) return 0; return addConstant(CONSTANT_Utf8, name).index; } Short[] getMemberRef(short index) { Short[] cls_nnt = get(index).itemIndexes(); Short[] name_type = get(cls_nnt[1]).itemIndexes(); return new Short[]{ cls_nnt[0], name_type[0], name_type[1] }; } } public class ClassFile extends Outer implements Chunk { ClassFile(File f) throws IOException { DataInputStream in = openInput(f); try { readFrom(in); } finally { if (in != null) in.close(); } } public int magic, version; // <min:maj> public final Pool pool = new Pool(); public short access, thisc, superc; public final List<Short> interfaces = new CountedList<>(Short.class); public final List<Field> fields = new CountedList<>(Field.class); public final List<Method> methods = new CountedList<>(Method.class); public final List<Attr> attrs = new CountedList<>(Attr.class); public final void readFrom(DataInputStream in) throws IOException { magic = in.readInt(); version = in.readInt(); if (magic != 0xCAFEBABE) throw new IOException("bad magic number"); pool.readFrom(in); Code_index = pool.stringIndex("Code", false); access = in.readShort(); thisc = in.readShort(); superc = in.readShort(); readInputs(in, interfaces, fields, methods, attrs); if (in.read() >= 0) throw new IOException("junk after end of file"); linkInners(); } void writeTo(File f) throws IOException { DataOutputStream out = openOutput(f); try { writeTo(out); } finally { out.close(); } } public void writeTo(DataOutputStream out) throws IOException { writeOutputs(out, magic, version, pool, access, thisc, superc, interfaces, fields, methods, attrs); } public byte[] toByteArray() { try { ByteArrayOutputStream buf = new ByteArrayOutputStream(); writeTo(new DataOutputStream(buf)); return buf.toByteArray(); } catch (IOException ex) { throw new InternalError(); } } public List<Inner> inners() { List<Inner> inns = new ArrayList<>(); inns.addAll(fields); inns.addAll(methods); inns.addAll(attrs); return inns; } public List<Attr> attrs() { return attrs; } // derived stuff: public String nameString() { return pool.getString(CONSTANT_Class, thisc); } int Code_index; } private static <T extends Member> T findMember(List<T> mems, int name, int type) { if (name == 0 || type == 0) return null; for (T m : mems) { if (m.name == name && m.type == type) return m; } return null; } public static class Member extends InnerOuter implements Chunk { public short access, name, type; public final List<Attr> attrs = new CountedList<>(Attr.class); public void readFrom(DataInputStream in) throws IOException { access = in.readShort(); name = in.readShort(); type = in.readShort(); readInputs(in, attrs); } public void writeTo(DataOutputStream out) throws IOException { writeOutputs(out, access, name, type, attrs); } public List<Attr> inners() { return attrs; } public List<Attr> attrs() { return attrs; } public ClassFile outer() { return (ClassFile) outer; } public String nameString() { return outer().pool.getString(CONSTANT_Utf8, name); } public String typeString() { return outer().pool.getString(CONSTANT_Utf8, type); } public String toString() { if (outer == null) return super.toString(); return nameString() + (this instanceof Method ? "" : ":") + simplifyType(typeString()); } } public static class Field extends Member { } public static class Method extends Member { public Code code() { Attr a = findAttr("Code"); if (a == null) return null; return (Code) a.item; } public Instruction instructions() { Code code = code(); if (code == null) return null; return code.instructions(); } } public static class Attr extends InnerOuter implements Chunk { public short name; public int size = -1; // no pre-declared size public Object item; public Attr() {} public Attr(Outer outer, String name, Object item) { ClassFile cf = outer.outer(ClassFile.class); linkOuter(outer); this.name = (short) cf.pool.stringIndex(name, true); this.item = item; outer.attrs().add(this); } public void readFrom(DataInputStream in) throws IOException { name = in.readShort(); size = in.readInt(); item = readRawBytes(in, size); } public void writeTo(DataOutputStream out) throws IOException { out.writeShort(name); // write the 4-byte size header and then the contents: byte[] bytes; int trueSize; if (item instanceof byte[]) { bytes = (byte[]) item; out.writeInt(trueSize = bytes.length); out.write(bytes); } else { trueSize = flatten(out); //if (!(item instanceof Code)) System.err.println("wrote complex attr name="+(int)(char)name+" size="+trueSize+" data="+Arrays.toString(flatten())); } if (trueSize != size && size >= 0) System.err.println("warning: attribute size changed "+size+" to "+trueSize); } public void linkOuter(Outer o) { super.linkOuter(o); if (item instanceof byte[] && outer instanceof Method && ((Method)outer).outer().Code_index == name) { item = readInput((byte[])item, Code.class); } } public List<Inner> inners() { if (item instanceof Inner) return Collections.nCopies(1, (Inner)item); return Collections.emptyList(); } public List<Attr> attrs() { return null; } // Code overrides this public byte[] flatten() { ByteArrayOutputStream buf = new ByteArrayOutputStream(Math.max(20, size)); flatten(buf); return buf.toByteArray(); } public int flatten(DataOutputStream out) throws IOException { ByteArrayOutputStream buf = new ByteArrayOutputStream(Math.max(20, size)); int trueSize = flatten(buf); out.writeInt(trueSize); buf.writeTo(out); return trueSize; } private int flatten(ByteArrayOutputStream buf) { try { writeOutput(new DataOutputStream(buf), item); return buf.size(); } catch (IOException ex) { throw new InternalError(); } } public String nameString() { ClassFile cf = outer(ClassFile.class); if (cf == null) return "#"+name; return cf.pool.getString(name); } public String toString() { return nameString()+(size < 0 ? "=" : "["+size+"]=")+item; } } public static class Code extends InnerOuter implements Chunk { public short stacks, locals; public byte[] bytes; public final List<Short[]> etable = new CountedList<>(Short[].class, 4); public final List<Attr> attrs = new CountedList<>(Attr.class); // etable[N] = (N)*{ startpc, endpc, handlerpc, catchtype } public void readFrom(DataInputStream in) throws IOException { stacks = in.readShort(); locals = in.readShort(); bytes = readRawBytes(in, in.readInt()); readInputs(in, etable, attrs); } public void writeTo(DataOutputStream out) throws IOException { writeOutputs(out, stacks, locals, bytes.length, bytes, etable, attrs); } public List<Attr> inners() { return attrs; } public List<Attr> attrs() { return attrs; } public Instruction instructions() { return new Instruction(bytes, 0); } } // lots of constants private static final byte CONSTANT_Utf8 = 1, CONSTANT_Integer = 3, CONSTANT_Float = 4, CONSTANT_Long = 5, CONSTANT_Double = 6, CONSTANT_Class = 7, CONSTANT_String = 8, CONSTANT_Field = 9, CONSTANT_Method = 10, CONSTANT_InterfaceMethod = 11, CONSTANT_NameAndType = 12, CONSTANT_MethodHandle = 15, // JSR 292 CONSTANT_MethodType = 16, // JSR 292 CONSTANT_InvokeDynamic = 18; // JSR 292 private static final byte REF_getField = 1, REF_getStatic = 2, REF_putField = 3, REF_putStatic = 4, REF_invokeVirtual = 5, REF_invokeStatic = 6, REF_invokeSpecial = 7, REF_newInvokeSpecial = 8, REF_invokeInterface = 9; private static final int opc_nop = 0, opc_aconst_null = 1, opc_nconst_MIN = 2, // iconst_m1 opc_nconst_MAX = 15, // dconst_1 opc_bipush = 16, opc_sipush = 17, opc_ldc = 18, opc_ldc_w = 19, opc_ldc2_w = 20, opc_aload = 25, opc_aload_0 = 42, opc_aload_MAX = 45, opc_aaload = 50, opc_astore = 58, opc_astore_0 = 75, opc_astore_MAX = 78, opc_aastore = 83, opc_pop = 87, opc_pop2 = 88, opc_dup = 89, opc_dup_x1 = 90, opc_dup_x2 = 91, opc_dup2 = 92, opc_dup2_x1 = 93, opc_dup2_x2 = 94, opc_swap = 95, opc_tableswitch = 170, opc_lookupswitch = 171, opc_areturn = 176, opc_getstatic = 178, opc_putstatic = 179, opc_getfield = 180, opc_putfield = 181, opc_invokevirtual = 182, opc_invokespecial = 183, opc_invokestatic = 184, opc_invokeinterface = 185, opc_invokedynamic = 186, opc_new = 187, opc_anewarray = 189, opc_checkcast = 192, opc_ifnull = 198, opc_ifnonnull = 199, opc_wide = 196; private static final Object[] INSTRUCTION_CONSTANTS = { -1, 0, 1, 2, 3, 4, 5, 0L, 1L, 0.0F, 1.0F, 2.0F, 0.0D, 1.0D }; private static final String INSTRUCTION_FORMATS = "nop$ aconst_null$L iconst_m1$I iconst_0$I iconst_1$I "+ "iconst_2$I iconst_3$I iconst_4$I iconst_5$I lconst_0$J_ "+ "lconst_1$J_ fconst_0$F fconst_1$F fconst_2$F dconst_0$D_ "+ "dconst_1$D_ bipush=bx$I sipush=bxx$I ldc=bk$X ldc_w=bkk$X "+ "ldc2_w=bkk$X_ iload=bl/wbll$I lload=bl/wbll$J_ fload=bl/wbll$F "+ "dload=bl/wbll$D_ aload=bl/wbll$L iload_0$I iload_1$I "+ "iload_2$I iload_3$I lload_0$J_ lload_1$J_ lload_2$J_ "+ "lload_3$J_ fload_0$F fload_1$F fload_2$F fload_3$F dload_0$D_ "+ "dload_1$D_ dload_2$D_ dload_3$D_ aload_0$L aload_1$L "+ "aload_2$L aload_3$L iaload$LI$I laload$LI$J_ faload$LI$F "+ "daload$LI$D_ aaload$LI$L baload$LI$I caload$LI$I saload$LI$I "+ "istore=bl/wbll$I$ lstore=bl/wbll$J_$ fstore=bl/wbll$F$ "+ "dstore=bl/wbll$D_$ astore=bl/wbll$L$ istore_0$I$ istore_1$I$ "+ "istore_2$I$ istore_3$I$ lstore_0$J_$ lstore_1$J_$ "+ "lstore_2$J_$ lstore_3$J_$ fstore_0$F$ fstore_1$F$ fstore_2$F$ "+ "fstore_3$F$ dstore_0$D_$ dstore_1$D_$ dstore_2$D_$ "+ "dstore_3$D_$ astore_0$L$ astore_1$L$ astore_2$L$ astore_3$L$ "+ "iastore$LII$ lastore$LIJ_$ fastore$LIF$ dastore$LID_$ "+ "aastore$LIL$ bastore$LII$ castore$LII$ sastore$LII$ pop$X$ "+ "pop2$XX$ dup$X$XX dup_x1$XX$XXX dup_x2$XXX$XXXX dup2$XX$XXXX "+ "dup2_x1$XXX$XXXXX dup2_x2$XXXX$XXXXXX swap$XX$XX "+ "iadd$II$I ladd$J_J_$J_ fadd$FF$F dadd$D_D_$D_ isub$II$I "+ "lsub$J_J_$J_ fsub$FF$F dsub$D_D_$D_ imul$II$I lmul$J_J_$J_ "+ "fmul$FF$F dmul$D_D_$D_ idiv$II$I ldiv$J_J_$J_ fdiv$FF$F "+ "ddiv$D_D_$D_ irem$II$I lrem$J_J_$J_ frem$FF$F drem$D_D_$D_ "+ "ineg$I$I lneg$J_$J_ fneg$F$F dneg$D_$D_ ishl$II$I lshl$J_I$J_ "+ "ishr$II$I lshr$J_I$J_ iushr$II$I lushr$J_I$J_ iand$II$I "+ "land$J_J_$J_ ior$II$I lor$J_J_$J_ ixor$II$I lxor$J_J_$J_ "+ "iinc=blx/wbllxx$ i2l$I$J_ i2f$I$F i2d$I$D_ l2i$J_$I l2f$J_$F "+ "l2d$J_$D_ f2i$F$I f2l$F$J_ f2d$F$D_ d2i$D_$I d2l$D_$J_ "+ "d2f$D_$F i2b$I$I i2c$I$I i2s$I$I lcmp fcmpl fcmpg dcmpl dcmpg "+ "ifeq=boo ifne=boo iflt=boo ifge=boo ifgt=boo ifle=boo "+ "if_icmpeq=boo if_icmpne=boo if_icmplt=boo if_icmpge=boo "+ "if_icmpgt=boo if_icmple=boo if_acmpeq=boo if_acmpne=boo "+ "goto=boo jsr=boo ret=bl/wbll tableswitch=* lookupswitch=* "+ "ireturn lreturn freturn dreturn areturn return "+ "getstatic=bkf$Q putstatic=bkf$Q$ getfield=bkf$L$Q "+ "putfield=bkf$LQ$ invokevirtual=bkm$LQ$Q "+ "invokespecial=bkm$LQ$Q invokestatic=bkm$Q$Q "+ "invokeinterface=bkixx$LQ$Q invokedynamic=bkd__$Q$Q new=bkc$L "+ "newarray=bx$I$L anewarray=bkc$I$L arraylength$L$I athrow "+ "checkcast=bkc$L$L instanceof=bkc$L$I monitorenter$L "+ "monitorexit$L wide=* multianewarray=bkcx ifnull=boo "+ "ifnonnull=boo goto_w=boooo jsr_w=boooo "; private static final String[] INSTRUCTION_NAMES; private static final String[] INSTRUCTION_POPS; private static final int[] INSTRUCTION_INFO; static { String[] insns = INSTRUCTION_FORMATS.split(" "); assert(insns[opc_lookupswitch].startsWith("lookupswitch")); assert(insns[opc_tableswitch].startsWith("tableswitch")); assert(insns[opc_wide].startsWith("wide")); assert(insns[opc_invokedynamic].startsWith("invokedynamic")); int[] info = new int[256]; String[] names = new String[256]; String[] pops = new String[256]; for (int i = 0; i < insns.length; i++) { String insn = insns[i]; int dl = insn.indexOf('$'); if (dl > 0) { String p = insn.substring(dl+1); if (p.indexOf('$') < 0) p = "$" + p; pops[i] = p; insn = insn.substring(0, dl); } int eq = insn.indexOf('='); if (eq < 0) { info[i] = 1; names[i] = insn; continue; } names[i] = insn.substring(0, eq); String fmt = insn.substring(eq+1); if (fmt.equals("*")) { info[i] = 0; continue; } int sl = fmt.indexOf('/'); if (sl < 0) { info[i] = (char) fmt.length(); } else { String wfmt = fmt.substring(sl+1); fmt = fmt.substring(0, sl); info[i] = (char)( fmt.length() + (wfmt.length() * 16) ); } } INSTRUCTION_INFO = info; INSTRUCTION_NAMES = names; INSTRUCTION_POPS = pops; } public static class Instruction implements Cloneable { byte[] codeBase; int pc; int bc; int info; int wide; int len; Instruction(byte[] codeBase, int pc) { this.codeBase = codeBase; init(pc); } public Instruction clone() { try { return (Instruction) super.clone(); } catch (CloneNotSupportedException ex) { throw new InternalError(); } } private Instruction init(int pc) { this.pc = pc; this.bc = codeBase[pc] & 0xFF; this.info = INSTRUCTION_INFO[bc]; this.wide = 0; this.len = (info & 0x0F); if (len == 0) computeLength(); return this; } Instruction next() { if (len == 0 && bc != 0) throw new InternalError(); int npc = pc + len; if (npc == codeBase.length) return null; return init(npc); } void forceNext(int newLen) { bc = opc_nop; len = newLen; } public String toString() { StringBuilder buf = new StringBuilder(); buf.append(pc).append(":").append(INSTRUCTION_NAMES[bc]); switch (len) { case 3: buf.append(" ").append(u2At(1)); break; case 5: buf.append(" ").append(u2At(1)).append(" ").append(u2At(3)); break; default: for (int i = 1; i < len; i++) buf.append(" ").append(u1At(1)); } return buf.toString(); } // these are the hard parts private void computeLength() { int cases; switch (bc) { case opc_wide: bc = codeBase[pc + 1]; info = INSTRUCTION_INFO[bc]; len = ((info >> 4) & 0x0F); if (len == 0) throw new RuntimeException("misplaced wide bytecode: "+bc); return; case opc_tableswitch: cases = (u4At(alignedIntOffset(2)) - u4At(alignedIntOffset(1)) + 1); len = alignedIntOffset(3 + cases*1); return; case opc_lookupswitch: cases = u4At(alignedIntOffset(1)); len = alignedIntOffset(2 + cases*2); return; default: throw new RuntimeException("unknown bytecode: "+bc); } } // switch code // clget the Nth int (where 0 is the first after the opcode itself) public int alignedIntOffset(int n) { int pos = pc + 1; pos += ((-pos) & 0x03); // align it pos += (n * 4); return pos - pc; } public int u1At(int pos) { return (codeBase[pc+pos] & 0xFF); } public int u2At(int pos) { return (u1At(pos+0)<<8) + u1At(pos+1); } public int u4At(int pos) { return (u2At(pos+0)<<16) + u2At(pos+2); } public void u1AtPut(int pos, int x) { codeBase[pc+pos] = (byte)x; } public void u2AtPut(int pos, int x) { codeBase[pc+pos+0] = (byte)(x >> 8); codeBase[pc+pos+1] = (byte)(x >> 0); } } static String simplifyType(String type) { String simpleType = OBJ_SIGNATURE.matcher(type).replaceAll("L"); assert(simpleType.matches("^\\([A-Z]*\\)[A-Z]$")); // change (DD)D to (D_D_)D_ simpleType = WIDE_SIGNATURE.matcher(simpleType).replaceAll("\\0_"); return simpleType; } static int argsize(String type) { return simplifyType(type).length()-3; } private static final Pattern OBJ_SIGNATURE = Pattern.compile("\\[*L[^;]*;|\\[+[A-Z]"); private static final Pattern WIDE_SIGNATURE = Pattern.compile("[JD]"); }