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