/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package indify;
/**
* Transform one or more class files to incorporate JSR 292 features,
* such as {@code invokedynamic}.
* <p>
* This is a standalone program in a single source file.
* In this form, it may be useful for test harnesses, small experiments, and javadoc examples.
* Copies of this file may show up in multiple locations for standalone usage.
* The primary maintained location of this file is as follows:
* <p>
* Static private methods named MH_x and MT_x (where x is arbitrary)
* must be stereotyped generators of MethodHandle and MethodType
* constants. All calls to them are transformed to {@code CONSTANT_MethodHandle}
* and {@code CONSTANT_MethodType} "ldc" instructions.
* The stereotyped code must create method types by calls to {@code methodType} or
* {@code fromMethodDescriptorString}. The "lookup" argument must be created
* by calls to {@code java.lang.invoke.MethodHandles#lookup MethodHandles.lookup}.
* The class and string arguments must be constant.
* The following methods of {@code java.lang.invoke.MethodHandle.Lookup Lookup} are
* allowed for method handle creation: {@code findStatic}, {@code findVirtual},
* {@code findConstructor}, {@code findSpecial},
* {@code findGetter}, {@code findSetter},
* {@code findStaticGetter}, or {@code findStaticSetter}.
* The call to one of these methods must be followed immediately
* by an {@code areturn} instruction.
* The net result of the call to the MH_x or MT_x method must be
* the creation of a constant method handle. Thus, replacing calls
* to MH_x or MT_x methods by {@code ldc} instructions should leave
* the meaning of the program unchanged.
* <p>
* Static private methods named INDY_x must be stereotyped generators
* of {@code invokedynamic} call sites.
* All calls to them must be immediately followed by
* {@code invokeExact} calls.
* All such pairs of calls are transformed to {@code invokedynamic}
* instructions. Each INDY_x method must begin with a call to a
* MH_x method, which is taken to be its bootstrap method.
* The method must be immediately invoked (via {@code invokeGeneric}
* on constant lookup, name, and type arguments. An object array of
* constants may also be appended to the {@code invokeGeneric call}.
* This call must be cast to {@code CallSite}, and the result must be
* immediately followed by a call to {@code dynamicInvoker}, with the
* resulting method handle returned.
* <p>
* The net result of all of these actions is equivalent to the JVM's
* execution of an {@code invokedynamic} instruction in the unlinked state.
* Running this code once should produce the same results as running
* the corresponding {@code invokedynamic} instruction.
* In order to model the caching behavior, the code of an INDY_x
* method is allowed to begin with getstatic, aaload, and if_acmpne
* instructions which load a static method handle value and return it
* if the value is non-null.
* <p>
* Example usage:
* <blockquote><pre>
$ JAVA_HOME=(some recent OpenJDK 7 build)
$ ant
$ $JAVA_HOME/bin/java -cp build/classes indify.Indify --overwrite --dest build/testout build/classes/indify/Example.class
MT = (java.lang.Object)java.lang.Object
MH = adder(int,int)java.lang.Integer
adder(1,2) = 3
calling indy: 42
(same output as above)
* </pre></blockquote>
* <p>
* A version of this transformation built on top of <a href="http://asm.ow2.org/">http://asm.ow2.org/</a> would be welcome.
* @author John Rose
*/
public class Indify {
}
public boolean keepgoing = false;
public boolean expandProperties = false;
public boolean overwrite = false;
public boolean quiet = false;
public boolean verbose = false;
public boolean all = false;
throw new IllegalArgumentException("Usage: indify [--dest dir] [option...] file...");
try {
throw new RuntimeException(ex);
}
return;
}
try {
indify(a);
if (!keepgoing) break;
}
}
throw (RuntimeException) err;
}
}
/** Execute the given application under a class loader which indifies all application classes. */
}
if (a.startsWith("-")) {
if (eq > 0) {
}
switch (a) {
case "--java":
return; // keep this argument
case "-d": case "--dest": case "-d=": case "--dest=":
break;
case "-cp": case "--classpath":
break;
case "-k": case "--keepgoing": case "--keepgoing=":
break;
case "--expand-properties": case "--expand-properties=":
break;
case "--verify-specifier-count": case "--verify-specifier-count=":
break;
case "--overwrite": case "--overwrite=":
break;
case "--all": case "--all=":
break;
case "-q": case "--quiet": case "--quiet=":
break;
case "-v": case "--verbose": case "--verbose=":
break;
default:
throw new IllegalArgumentException("unrecognized flag: "+a);
}
continue;
} else {
break;
}
}
throw new RuntimeException("no output specified; need --dest d or --overwrite");
if (expandProperties) {
}
}
if (s == null) return true;
switch (s) {
case "true": case "yes": case "on": case "1": return true;
case "false": case "no": case "off": case "0": return false;
}
throw new IllegalArgumentException("unrecognized boolean flag="+s);
}
if (!expandProperties) return s;
while (s.contains("${")) {
if (rbrk < 0) break;
}
return s;
}
indifyFile(f, dest);
else if (f.isDirectory())
indifyTree(f, dest);
else if (!keepgoing)
throw new RuntimeException("unrecognized file: "+a);
}
}
} else {
outfile = f; // overwrite input file, no matter where it is
}
}
}
}
throw new UnsupportedOperationException("Not yet implemented");
}
// return true if it might be a package name:
}})) {
else if (f2.isDirectory())
}
}
return new Loader();
}
Loader() {
this(Indify.class.getClassLoader());
}
super(parent);
}
if (f != null) {
try {
Class<?> c = transformAndLoadClass(f);
if (c != null) {
if (resolve) resolveClass(c);
return c;
}
} catch (ClassNotFoundException ex) {
// fall through
} catch (IOException ex) {
// fall through
// pass error from reportPatternMethods, etc.
throw new RuntimeException(ex);
}
}
}
//System.out.println("Checking for "+f);
return f;
}
}
return null;
}
try {
if (f != null) {
Class<?> c = transformAndLoadClass(f);
if (c != null) return c;
}
} catch (IOException ex) {
}
throw new ClassNotFoundException();
}
}
}
private class Logic {
// Indify logic, per se.
final char[] poolMarks;
}
boolean transform() {
if (!initializeMarks()) return false;
if (!findPatternMethods()) return false;
//for (Constant c : cp) System.out.println(" # "+c);
// Transform references.
int blab = 0;
if (i.bc != opc_invokestatic) continue;
//if (blab == 1) { for (Instruction j = m.instructions(); j != null; j = j.next()) System.out.println(" |"+j); }
// need to patch the following instruction too,
// but there are usually intervening argument pushes too
short ref2i = 0;
continue;
}
}
//System.out.println(new Instruction(i.codeBase, i2.pc-3));
} else {
assert(i.len == 3);
}
}
//if (blab >= 1) { for (Instruction j = m.instructions(); j != null; j = j.next()) System.out.println(" |"+j); }
}
return true;
}
// Scan forward from the instruction to find where the stack p
// below the current sp at the instruction.
//System.out.println("findPop from "+i);
//System.out.println(" "+i+" "+jvm.stack+" : "+pops.replace("$", " => "));
switch (i.bc) {
case opc_getstatic:
case opc_getfield:
case opc_putstatic:
case opc_putfield:
break;
default:
throw new InternalError(i.toString());
break;
}
//System.out.println("special type: "+type+" => "+pops);
}
int k = 0;
}
}
return null;
}
boolean findPatternMethods() {
boolean found = false;
found = true;
}
}
}
return found;
}
if (!allowMatchFailure)
throw new IllegalArgumentException(failure);
else if (!quietly)
}
}
if (verifySpecifierCount >= 0) {
// Pass by specsLen == 0, to help with associated (inner) classes.
if (specsLen != verifySpecifierCount) {
throw new IllegalArgumentException("BootstrapMethods length is "+specsLen+" but should be "+verifySpecifierCount);
}
}
}
// mark constant pool entries according to participation in patterns
boolean initializeMarks() {
boolean changed = false;
for (;;) {
boolean changed1 = false;
int cpindex = -1;
++cpindex;
if (e == null) continue;
if (mark != 0) continue;
switch (e.tag) {
case CONSTANT_Utf8:
case CONSTANT_NameAndType:
case CONSTANT_Class: {
break;
}
case CONSTANT_Field:
case CONSTANT_Method: {
if (cmark != 0) {
break;
}
case 'T': case 'H': case 'I':
break;
}
}
break;
}
default: break;
}
if (mark != 0) {
changed1 = true;
}
}
if (!changed1)
break;
changed = true;
}
return changed;
}
return 0;
}
}
default: return 0;
}
return 0;
}
return false;
}
private class JVMState {
}
}
switch (bc) {
// ? also: dup2{,_x1,_x2}
default: return false;
}
return true;
}
}
for (;;) {
else break;
}
}
int wantTag;
switch (patternMark) {
default: throw new InternalError();
}
Instruction i = m.instructions();
int branchCount = 0;
//System.out.println(jvm.stack+" "+i);
switch (bc) {
// these support creation of a restarg array
case opc_anewarray:
break;
case opc_dup:
case opc_aastore:
}
break;
case opc_new:
{
//System.out.println("new "+type);
switch (type) {
case "java/lang/StringBuilder":
continue decode; // go to next instruction
}
break decode; // bail out
}
case opc_getstatic:
{
// int.class compiles to getstatic Integer.TYPE
//System.err.println("getstatic "+fieldi+Arrays.asList(pool.getStrings(pool.getMemberRef((short)fieldi)))+mark);
if (mark == 'J') {
// a primitive type descriptor
try {
}
break;
}
}
// unknown field; keep going...
break;
}
case opc_putstatic:
{
// unknown field; keep going...
break;
}
case opc_invokestatic:
case opc_invokevirtual:
case opc_invokespecial:
{
//System.out.println("invoke "+pool.getString(CONSTANT_Utf8, ref[1])+" "+Arrays.asList(ref)+" : "+type);
if (mark == 'J') {
}
//System.out.println("recognized intrinsic "+intrinsic);
byte refKind = -1;
switch (intrinsic) {
}
continue;
}
}
}
//if (intrinsic != null) System.out.println("intrinsic = "+intrinsic);
case "fromMethodDescriptorString":
continue;
case "methodType": {
if (argClass.isPrimitive()) {
char tchar;
}
} else {
// should not happen, but...
}
else
} else {
break decode;
}
} else {
break decode;
}
// first arg is treated differently
}
}
continue;
}
case "lookup":
case "dynamicInvoker":
continue;
case "lookupClass":
// fold lookup().lookupClass() to the enclosing class
continue;
}
break;
case "invoke":
case "invokeGeneric":
case "invokeWithArguments":
continue;
case "Integer.valueOf":
case "Float.valueOf":
case "Long.valueOf":
case "Double.valueOf":
}
}
break decode;
case "StringBuilder.append":
// allow calls like ("value = "+x)
continue;
case "StringBuilder.toString":
continue;
}
continue;
// allow calls like println("reached the pattern method")
continue;
}
break decode; // bail out for most calls
}
case opc_areturn:
{
++branchCount;
// parse bsmArgs as (MH, lookup, String, MT, [extra])
return indyCon;
}
break decode; // bail out
}
break; // merge to next path
break decode; // bail out
}
default:
if (patternMark == 'I') {
// these support caching paths in INDY_x methods
switch (bc) {
case opc_getfield:
case opc_aaload:
case opc_ifnull:
case opc_ifnonnull:
// ignore branch target
break;
case opc_checkcast:
break; // assume it is a helpful cast
break decode;
default:
break decode; // bail out
}
continue decode; // go to next instruction
}
break decode; // bail out
} //end switch
}
return null;
}
}
}
short utfIndex;
if (x instanceof String)
else if (isConstant(x, CONSTANT_String))
else return null;
}
// E.g.: lookup().findStatic(Foo.class, "name", MethodType)
int argi = 0;
} else return null;
byte reftag = CONSTANT_Method;
if (refKind <= REF_putStatic)
else if (refKind == REF_invokeInterface)
}
// E.g.: MH_bsm.invokeGeneric(lookup(), "name", MethodType, "extraArg")
int argi = 0;
} else {
}
}
if (x instanceof Number) {
}
if (!(x instanceof Constant)) {
return null;
}
}
int specindex = -1;
break;
}
}
if (specindex == -1) {
}
}
if (!createIfNotFound) return null;
}
// unflatten
try {
for (int i = 0; i < len; i++) {
for (int j = 0; j < argc; j++)
}
}
}
}
}
throw new IOException("file already exists: "+f);
ensureDirectory(f.getParentFile());
}
return bytes;
}
private interface Chunk {
}
final int rowlen;
}
if (rowlen < 0) {
} else {
for (int i = 0; i < rowlen; i++)
}
}
}
for (T item : this) {
}
}
}
T obj;
} else {
}
}
try {
} catch (IOException ex) {
throw new InternalError();
}
}
}
return;
} else if (data instanceof byte[]) {
writeOutput(out, x);
writeOutput(out, x);
} else {
}
}
}
public static abstract class Outer {
protected void linkInners() {
i.linkOuter(this);
if (i instanceof Outer)
((Outer)i).linkInners();
}
}
if (c.isInstance(walk))
//if (!(walk instanceof Inner)) return null;
}
}
}
}
return null;
}
}
}
public final byte tag;
public final T item;
public final int index;
}
return this;
}
throw new InternalError("do not call");
}
}
}
switch (tag) {
}
}
}
TAG_NAMES = ("None Utf8 Unicode Integer Float Long Double Class String"
+" Fieldref Methodref InterfaceMethodref NameAndType #13 #14"
}
}
public Pool() {
super(Constant.class);
}
}
}
return con;
}
switch (tag) {
case CONSTANT_Utf8:
break;
case CONSTANT_Integer:
case CONSTANT_Float:
case CONSTANT_Long:
case CONSTANT_Double:
return;
case CONSTANT_Class:
case CONSTANT_String:
case CONSTANT_Field:
case CONSTANT_Method:
case CONSTANT_InterfaceMethod:
case CONSTANT_NameAndType:
case CONSTANT_InvokeDynamic:
// read an ordered pair
break;
case CONSTANT_MethodHandle:
// read an ordered pair; first part is a u1 (not u2)
break;
case CONSTANT_MethodType:
default:
}
}
// Access:
// extra 1-bits get into the shorts
}
}
if (v instanceof Short)
return (String) v;
}
return res;
}
if (x != null) return (char)(int) x;
if (!createIfNotFound) return 0;
}
}
}
try {
} finally {
}
}
linkInners();
}
try {
} finally {
}
}
}
public byte[] toByteArray() {
try {
return buf.toByteArray();
} catch (IOException ex) {
throw new InternalError();
}
}
return inns;
}
// derived stuff:
int Code_index;
}
for (T m : mems) {
}
return null;
}
}
}
+ simplifyType(typeString());
}
}
}
}
return code.instructions();
}
}
public short name;
public Attr() {}
}
}
// write the 4-byte size header and then the contents:
byte[] bytes;
int trueSize;
if (item instanceof byte[]) {
} else {
//if (!(item instanceof Code)) System.err.println("wrote complex attr name="+(int)(char)name+" size="+trueSize+" data="+Arrays.toString(flatten()));
}
}
super.linkOuter(o);
if (item instanceof byte[] &&
}
}
return Collections.emptyList();
}
public byte[] flatten() {
return buf.toByteArray();
}
return trueSize;
}
try {
} catch (IOException ex) {
throw new InternalError();
}
}
}
}
}
public byte[] bytes;
// etable[N] = (N)*{ startpc, endpc, handlerpc, catchtype }
}
}
}
}
// lots of constants
private static final byte
private static final byte
private static final int
-1, 0, 1, 2, 3, 4, 5, 0L, 1L, 0.0F, 1.0F, 2.0F, 0.0D, 1.0D
};
"nop$ aconst_null$L iconst_m1$I iconst_0$I iconst_1$I "+
"iconst_2$I iconst_3$I iconst_4$I iconst_5$I lconst_0$J_ "+
"lconst_1$J_ fconst_0$F fconst_1$F fconst_2$F dconst_0$D_ "+
"dconst_1$D_ bipush=bx$I sipush=bxx$I ldc=bk$X ldc_w=bkk$X "+
"iload_2$I iload_3$I lload_0$J_ lload_1$J_ lload_2$J_ "+
"lload_3$J_ fload_0$F fload_1$F fload_2$F fload_3$F dload_0$D_ "+
"dload_1$D_ dload_2$D_ dload_3$D_ aload_0$L aload_1$L "+
"aload_2$L aload_3$L iaload$LI$I laload$LI$J_ faload$LI$F "+
"daload$LI$D_ aaload$LI$L baload$LI$I caload$LI$I saload$LI$I "+
"istore_2$I$ istore_3$I$ lstore_0$J_$ lstore_1$J_$ "+
"lstore_2$J_$ lstore_3$J_$ fstore_0$F$ fstore_1$F$ fstore_2$F$ "+
"fstore_3$F$ dstore_0$D_$ dstore_1$D_$ dstore_2$D_$ "+
"dstore_3$D_$ astore_0$L$ astore_1$L$ astore_2$L$ astore_3$L$ "+
"iastore$LII$ lastore$LIJ_$ fastore$LIF$ dastore$LID_$ "+
"aastore$LIL$ bastore$LII$ castore$LII$ sastore$LII$ pop$X$ "+
"pop2$XX$ dup$X$XX dup_x1$XX$XXX dup_x2$XXX$XXXX dup2$XX$XXXX "+
"dup2_x1$XXX$XXXXX dup2_x2$XXXX$XXXXXX swap$XX$XX "+
"iadd$II$I ladd$J_J_$J_ fadd$FF$F dadd$D_D_$D_ isub$II$I "+
"lsub$J_J_$J_ fsub$FF$F dsub$D_D_$D_ imul$II$I lmul$J_J_$J_ "+
"fmul$FF$F dmul$D_D_$D_ idiv$II$I ldiv$J_J_$J_ fdiv$FF$F "+
"ddiv$D_D_$D_ irem$II$I lrem$J_J_$J_ frem$FF$F drem$D_D_$D_ "+
"ineg$I$I lneg$J_$J_ fneg$F$F dneg$D_$D_ ishl$II$I lshl$J_I$J_ "+
"ishr$II$I lshr$J_I$J_ iushr$II$I lushr$J_I$J_ iand$II$I "+
"land$J_J_$J_ ior$II$I lor$J_J_$J_ ixor$II$I lxor$J_J_$J_ "+
"l2d$J_$D_ f2i$F$I f2l$F$J_ f2d$F$D_ d2i$D_$I d2l$D_$J_ "+
"d2f$D_$F i2b$I$I i2c$I$I i2s$I$I lcmp fcmpl fcmpg dcmpl dcmpg "+
"ifeq=boo ifne=boo iflt=boo ifge=boo ifgt=boo ifle=boo "+
"if_icmpeq=boo if_icmpne=boo if_icmplt=boo if_icmpge=boo "+
"if_icmpgt=boo if_icmple=boo if_acmpeq=boo if_acmpne=boo "+
"ireturn lreturn freturn dreturn areturn return "+
"getstatic=bkf$Q putstatic=bkf$Q$ getfield=bkf$L$Q "+
"putfield=bkf$LQ$ invokevirtual=bkm$LQ$Q "+
"invokespecial=bkm$LQ$Q invokestatic=bkm$Q$Q "+
"invokeinterface=bkixx$LQ$Q invokedynamic=bkd__$Q$Q new=bkc$L "+
"newarray=bx$I$L anewarray=bkc$I$L arraylength$L$I athrow "+
"checkcast=bkc$L$L instanceof=bkc$L$I monitorenter$L "+
"monitorexit$L wide=* multianewarray=bkcx ifnull=boo "+
"ifnonnull=boo goto_w=boooo jsr_w=boooo ";
private static final int[] INSTRUCTION_INFO;
static {
int[] info = new int[256];
if (dl > 0) {
pops[i] = p;
}
if (eq < 0) {
info[i] = 1;
continue;
}
info[i] = 0;
continue;
}
if (sl < 0) {
} else {
}
}
}
byte[] codeBase;
int pc;
int bc;
int info;
int wide;
int len;
}
try {
return (Instruction) super.clone();
} catch (CloneNotSupportedException ex) {
throw new InternalError();
}
}
this.wide = 0;
if (len == 0)
return this;
}
return null;
}
}
switch (len) {
}
}
// these are the hard parts
private void computeLength() {
int cases;
switch (bc) {
case opc_wide:
return;
case opc_tableswitch:
return;
case opc_lookupswitch:
return;
default:
}
}
// switch code
// clget the Nth int (where 0 is the first after the opcode itself)
public int alignedIntOffset(int n) {
pos += (n * 4);
}
}
}
}
}
}
}
// change (DD)D to (D_D_)D_
return simpleType;
}
}
}