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