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