/* * Copyright (c) 2010, 2020, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions.
*/
/** * Transform one or more class files to incorporate JSR 292 features, * such as {@code invokedynamic}. * <p> * This is a standalone program in a single source file. * In this form, it may be useful for test harnesses, small experiments, and javadoc examples. * Copies of this file may show up in multiple locations for standalone usage. * The primary maintained location of this file is as follows: * <a href="http://kenai.com/projects/ninja/sources/indify-repo/content/src/indify/Indify.java"> * http://kenai.com/projects/ninja/sources/indify-repo/content/src/indify/Indify.java</a> * <p> * Static private methods named MH_x and MT_x (where x is arbitrary) * must be stereotyped generators of MethodHandle and MethodType * constants. All calls to them are transformed to {@code CONSTANT_MethodHandle} * and {@code CONSTANT_MethodType} "ldc" instructions. * The stereotyped code must create method types by calls to {@code methodType} or * {@code fromMethodDescriptorString}. The "lookup" argument must be created * by calls to {@code java.lang.invoke.MethodHandles#lookup MethodHandles.lookup}. * The class and string arguments must be constant. * The following methods of {@code java.lang.invoke.MethodHandle.Lookup Lookup} are * allowed for method handle creation: {@code findStatic}, {@code findVirtual}, * {@code findConstructor}, {@code findSpecial}, * {@code findGetter}, {@code findSetter}, * {@code findStaticGetter}, or {@code findStaticSetter}. * The call to one of these methods must be followed immediately * by an {@code areturn} instruction. * The net result of the call to the MH_x or MT_x method must be * the creation of a constant method handle. Thus, replacing calls * to MH_x or MT_x methods by {@code ldc} instructions should leave * the meaning of the program unchanged. * <p> * Static private methods named INDY_x must be stereotyped generators * of {@code invokedynamic} call sites. * All calls to them must be immediately followed by * {@code invokeExact} calls. * All such pairs of calls are transformed to {@code invokedynamic} * instructions. Each INDY_x method must begin with a call to a * MH_x method, which is taken to be its bootstrap method. * The method must be immediately invoked (via {@code invokeGeneric} * on constant lookup, name, and type arguments. An object array of * constants may also be appended to the {@code invokeGeneric call}. * This call must be cast to {@code CallSite}, and the result must be * immediately followed by a call to {@code dynamicInvoker}, with the * resulting method handle returned. * <p> * The net result of all of these actions is equivalent to the JVM's * execution of an {@code invokedynamic} instruction in the unlinked state. * Running this code once should produce the same results as running * the corresponding {@code invokedynamic} instruction. * In order to model the caching behavior, the code of an INDY_x * method is allowed to begin with getstatic, aaload, and if_acmpne * instructions which load a static method handle value and return it * if the value is non-null. * <p> * Example usage: * <blockquote><pre> $ JAVA_HOME=(some recent OpenJDK 7 build) $ ant $ $JAVA_HOME/bin/java -cp build/classes indify.Indify --overwrite --dest build/testout build/classes/indify/Example.class $ $JAVA_HOME/bin/java -cp build/classes indify.Example MT = (java.lang.Object)java.lang.Object MH = adder(int,int)java.lang.Integer adder(1,2) = 3 calling indy: 42 $ $JAVA_HOME/bin/java -cp build/testout indify.Example (same output as above) * </pre></blockquote> * <p> * A version of this transformation built on top of <a href="http://asm.ow2.org/">http://asm.ow2.org/</a> would be welcome. * @author John Rose
*/ publicclass Indify { publicstaticvoid main(String... av) throws IOException { new Indify().run(av);
}
publicvoid indifyTree(File f, File dest) throws IOException { if (verbose) System.err.println("reading directory: "+f); for (File f2 : f.listFiles(new FilenameFilter() { publicboolean accept(File dir, String name) { if (name.endsWith(".class")) returntrue; if (name.contains(".")) returnfalse; // return true if it might be a package name: return Character.isJavaIdentifierStart(name.charAt(0));
}})) { if (f2.getName().endsWith(".class"))
indifyFile(f2, dest); elseif (f2.isDirectory())
indifyTree(f2, dest);
}
}
public ClassLoader makeClassLoader() { returnnew Loader();
} privateclass Loader extends ClassLoader {
Loader() { this(Indify.class.getClassLoader());
}
Loader(ClassLoader parent) { super(parent);
} publicClass<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
File f = findClassInPath(name); if (f != null) { try { Class<?> c = transformAndLoadClass(f); if (c != null) { if (resolve) resolveClass(c); return c;
}
} catch (ClassNotFoundException ex) { // fall through
} catch (IOException ex) { // fall through
} catch (Exception ex) { // pass error from reportPatternMethods, etc. if (ex instanceof RuntimeException) throw (RuntimeException) ex; thrownew RuntimeException(ex);
}
} returnsuper.loadClass(name, resolve);
} private File findClassInPath(String name) { for (String s : classpath) {
File f = classPathFile(new File(s), name); //System.out.println("Checking for "+f); if (f.exists() && f.canRead()) { return f;
}
} returnnull;
} protectedClass<?> findClass(String name) throws ClassNotFoundException { try {
File f = findClassInPath(name); if (f != null) { Class<?> c = transformAndLoadClass(f); if (c != null) return c;
}
} catch (IOException ex) { thrownew ClassNotFoundException("IO error", ex);
} thrownew ClassNotFoundException();
} privateClass<?> transformAndLoadClass(File f) throws ClassNotFoundException, IOException { if (verbose) System.err.println("Loading class from "+f);
ClassFile cf = new ClassFile(f);
Logic logic = new Logic(cf); boolean changed = logic.transform(); if (verbose && !changed) System.err.println("(no change)");
logic.reportPatternMethods(!verbose, keepgoing); byte[] bytes = cf.toByteArray(); return defineClass(null, bytes, 0, bytes.length);
}
}
privateclass Logic { // Indify logic, per se.
ClassFile cf; finalchar[] poolMarks; final Map<Method,Constant> constants = new HashMap<>(); final Map<Method,String> indySignatures = new HashMap<>();
Logic(ClassFile cf) { this.cf = cf;
poolMarks = newchar[cf.pool.size()];
} boolean transform() { if (!initializeMarks()) returnfalse; if (!findPatternMethods()) returnfalse;
Pool pool = cf.pool; //for (Constant c : cp) System.out.println(" # "+c); for (Method m : cf.methods) { if (constants.containsKey(m)) continue; // don't bother // Transform references. int blab = 0; for (Instruction i = m.instructions(); i != null; i = i.next()) { if (i.bc != opc_invokestatic) continue; int methi = i.u2At(1); if (poolMarks[methi] == 0) continue; Short[] ref = pool.getMemberRef((short)methi);
Method conm = findMember(cf.methods, ref[1], ref[2]); if (conm == null) continue;
Constant con = constants.get(conm); if (con == null) continue; if (blab++ == 0 && !quiet)
System.err.println("patching "+cf.nameString()+"."+m); //if (blab == 1) { for (Instruction j = m.instructions(); j != null; j = j.next()) System.out.println(" |"+j); } if (con.tag == CONSTANT_InvokeDynamic) { // need to patch the following instruction too, // but there are usually intervening argument pushes too
Instruction i2 = findPop(i); Short[] ref2 = null; short ref2i = 0; if (i2 != null && i2.bc == opc_invokevirtual &&
poolMarks[(char)(ref2i = (short) i2.u2At(1))] == 'D')
ref2 = pool.getMemberRef(ref2i); if (ref2 == null || !"invokeExact".equals(pool.getString(ref2[1]))) {
System.err.println(m+": failed to create invokedynamic at "+i.pc); continue;
}
String invType = pool.getString(ref2[2]);
String bsmType = indySignatures.get(conm); if (!invType.equals(bsmType)) {
System.err.println(m+": warning: "+conm+" call type and local invoke type differ: "
+bsmType+", "+invType);
} assert(i.len == 3 || i2.len == 3); if (!quiet) System.err.println(i+" "+conm+";...; "+i2+" => invokedynamic "+con); int start = i.pc + 3, end = i2.pc;
System.arraycopy(i.codeBase, start, i.codeBase, i.pc, end-start);
i.forceNext(0); // force revisit of new instruction
i2.u1AtPut(-3, opc_invokedynamic);
i2.u2AtPut(-2, con.index);
i2.u2AtPut(0, (short)0);
i2.u1AtPut(2, opc_nop); //System.out.println(new Instruction(i.codeBase, i2.pc-3));
} else { if (!quiet) System.err.println(i+" "+conm+" => ldc "+con); assert(i.len == 3);
i.u1AtPut(0, opc_ldc_w);
i.u2AtPut(1, con.index);
}
} //if (blab >= 1) { for (Instruction j = m.instructions(); j != null; j = j.next()) System.out.println(" |"+j); }
}
cf.methods.removeAll(constants.keySet()); returntrue;
}
// Scan forward from the instruction to find where the stack p // below the current sp at the instruction.
Instruction findPop(Instruction i) { //System.out.println("findPop from "+i);
Pool pool = cf.pool;
JVMState jvm = new JVMState();
decode: for (i = i.clone().next(); i != null; i = i.next()) {
String pops = INSTRUCTION_POPS[i.bc]; //System.out.println(" "+i+" "+jvm.stack+" : "+pops.replace("$", " => ")); if (pops == null) break; if (jvm.stackMotion(i.bc)) continue decode; if (pops.indexOf('Q') >= 0) { Short[] ref = pool.getMemberRef((short) i.u2At(1));
String type = simplifyType(pool.getString(CONSTANT_Utf8, ref[2])); switch (i.bc) { case opc_getstatic: case opc_getfield: case opc_putstatic: case opc_putfield:
pops = pops.replace("Q", type); break; default: if (!type.startsWith("(")) thrownew InternalError(i.toString());
pops = pops.replace("Q$Q", type.substring(1).replace(")","$")); break;
} //System.out.println("special type: "+type+" => "+pops);
} int npops = pops.indexOf('$'); if (npops < 0) thrownew InternalError(); if (npops > jvm.sp()) return i;
List<Object> args = jvm.args(npops); int k = 0; for (Object x : args) { char have = (Character) x; char want = pops.charAt(k++); if (have == 'X' || want == 'X') continue; if (have != want) break decode;
} if (pops.charAt(k++) != '$') break decode;
args.clear(); while (k < pops.length())
args.add(pops.charAt(k++));
}
System.err.println("*** bailout on jvm: "+jvm.stack+" "+i); returnnull;
}
boolean findPatternMethods() { boolean found = false; for (char mark : "THI".toCharArray()) { for (Method m : cf.methods) { if (!Modifier.isPrivate(m.access)) continue; if (!Modifier.isStatic(m.access)) continue; if (nameAndTypeMark(m.name, m.type) == mark) {
Constant con = scanPattern(m, mark); if (con == null) continue;
constants.put(m, con);
found = true;
}
}
} return found;
}
void reportPatternMethods(boolean quietly, boolean allowMatchFailure) { if (!quietly && !constants.keySet().isEmpty())
System.err.println("pattern methods removed: "+constants.keySet()); for (Method m : cf.methods) { if (nameMark(cf.pool.getString(m.name)) != 0 &&
constants.get(m) == null) {
String failure = "method has special name but fails to match pattern: "+m; if (!allowMatchFailure) thrownew IllegalArgumentException(failure); elseif (!quietly)
System.err.println("warning: "+failure);
}
} if (verifySpecifierCount >= 0) {
List<Object[]> specs = bootstrapMethodSpecifiers(false); int specsLen = (specs == null ? 0 : specs.size()); // Pass by specsLen == 0, to help with associated (inner) classes. if (specsLen == 0) specsLen = verifySpecifierCount; if (specsLen != verifySpecifierCount) { thrownew IllegalArgumentException("BootstrapMethods length is "+specsLen+" but should be "+verifySpecifierCount);
}
} if (!quiet) System.err.flush();
}
// mark constant pool entries according to participation in patterns boolean initializeMarks() { boolean changed = false; for (;;) { boolean changed1 = false; int cpindex = -1; for (Constant e : cf.pool) {
++cpindex; if (e == null) continue; char mark = poolMarks[cpindex]; if (mark != 0) continue; switch (e.tag) { case CONSTANT_Utf8:
mark = nameMark(e.itemString()); break; case CONSTANT_NameAndType:
mark = nameAndTypeMark(e.itemIndexes()); break; case CONSTANT_Class: { int n1 = e.itemIndex(); char nmark = poolMarks[(char)n1]; if ("DJ".indexOf(nmark) >= 0)
mark = nmark; break;
} case CONSTANT_Field: case CONSTANT_Method: { Short[] n12 = e.itemIndexes(); short cl = n12[0]; short nt = n12[1]; char cmark = poolMarks[(char)cl]; if (cmark != 0) {
mark = cmark; // it is a java.lang.invoke.* or java.lang.* method break;
}
String cls = cf.pool.getString(CONSTANT_Class, cl); if (cls.equals(cf.nameString())) { switch (poolMarks[(char)nt]) { // it is a private MH/MT/INDY method case'T': case'H': case'I':
mark = poolMarks[(char)nt]; break;
}
} break;
} default: break;
} if (mark != 0) {
poolMarks[cpindex] = mark;
changed1 = true;
}
} if (!changed1) break;
changed = true;
} return changed;
} char nameMark(String s) { if (s.startsWith("MT_")) return'T'; elseif (s.startsWith("MH_")) return'H'; elseif (s.startsWith("INDY_")) return'I'; elseif (s.startsWith("java/lang/invoke/")) return'D'; elseif (s.startsWith("java/lang/")) return'J'; return 0;
} char nameAndTypeMark(Short[] n12) { return nameAndTypeMark(n12[0], n12[1]);
} char nameAndTypeMark(short n1, short n2) { char mark = poolMarks[(char)n1]; if (mark == 0) return 0;
String descr = cf.pool.getString(CONSTANT_Utf8, n2);
String requiredType; switch (poolMarks[(char)n1]) { case'H': requiredType = "()Ljava/lang/invoke/MethodHandle;"; break; case'T': requiredType = "()Ljava/lang/invoke/MethodType;"; break; case'I': requiredType = "()Ljava/lang/invoke/MethodHandle;"; break; default: return 0;
} if (matchType(descr, requiredType)) return mark; return 0;
}
private Constant scanPattern(Method m, char patternMark) { if (verbose) System.err.println("scan "+m+" for pattern="+patternMark); int wantTag; switch (patternMark) { case'T': wantTag = CONSTANT_MethodType; break; case'H': wantTag = CONSTANT_MethodHandle; break; case'I': wantTag = CONSTANT_InvokeDynamic; break; default: thrownew InternalError();
}
Instruction i = m.instructions();
JVMState jvm = new JVMState();
Pool pool = cf.pool; int branchCount = 0;
Object arg;
List<Object> args;
List<Object> bsmArgs = null; // args to invokeGeneric
decode: for (; i != null; i = i.next()) { //System.out.println(jvm.stack+" "+i); int bc = i.bc; switch (bc) { case opc_ldc: jvm.push(pool.get(i.u1At(1))); break; case opc_ldc_w: jvm.push(pool.get(i.u2At(1))); break; case opc_ldc2_w: jvm.push2(pool.get(i.u2At(1))); break; case opc_aconst_null: jvm.push(null); break; case opc_bipush: jvm.push((int)(byte) i.u1At(1)); break; case opc_sipush: jvm.push((int)(short)i.u2At(1)); break;
// these support creation of a restarg array case opc_anewarray:
arg = jvm.pop(); if (!(arg instanceof Integer)) break decode;
arg = Arrays.asList(new Object[(Integer)arg]);
jvm.push(arg); break; case opc_dup:
jvm.push(jvm.top()); break; case opc_aastore:
args = jvm.args(3); // array, index, value if (args.get(0) instanceof List &&
args.get(1) instanceof Integer) {
@SuppressWarnings("unchecked")
List<Object> arg0 = (List<Object>)args.get(0);
arg0.set( (Integer)args.get(1), args.get(2) );
}
args.clear(); break;
case opc_new:
{
String type = pool.getString(CONSTANT_Class, (short)i.u2At(1)); //System.out.println("new "+type); switch (type) { case"java/lang/StringBuilder":
jvm.push("StringBuilder"); continue decode; // go to next instruction
} break decode; // bail out
}
case opc_getstatic:
{ // int.class compiles to getstatic Integer.TYPE int fieldi = i.u2At(1); char mark = poolMarks[fieldi]; //System.err.println("getstatic "+fieldi+Arrays.asList(pool.getStrings(pool.getMemberRef((short)fieldi)))+mark); if (mark == 'J') { Short[] ref = pool.getMemberRef((short) fieldi);
String name = pool.getString(CONSTANT_Utf8, ref[1]); if ("TYPE".equals(name)) {
String wrapperName = pool.getString(CONSTANT_Class, ref[0]).replace('/', '.'); // a primitive type descriptor Class<?> primClass; try {
primClass = (Class<?>) Class.forName(wrapperName).getField(name).get(null);
} catch (Exception ex) { thrownew InternalError("cannot load "+wrapperName+"."+name);
}
jvm.push(primClass); break;
}
} // unknown field; keep going...
jvm.push(UNKNOWN_CON); break;
} case opc_putstatic:
{ if (patternMark != 'I') break decode;
jvm.pop(); // unknown field; keep going... break;
}
case opc_invokestatic: case opc_invokevirtual: case opc_invokespecial:
{ boolean hasRecv = (bc != opc_invokestatic); int methi = i.u2At(1); char mark = poolMarks[methi]; Short[] ref = pool.getMemberRef((short)methi);
String type = pool.getString(CONSTANT_Utf8, ref[2]); //System.out.println("invoke "+pool.getString(CONSTANT_Utf8, ref[1])+" "+Arrays.asList(ref)+" : "+type);
args = jvm.args(hasRecv, type);
String intrinsic = null;
Constant con; if (mark == 'D' || mark == 'J') {
intrinsic = pool.getString(CONSTANT_Utf8, ref[1]); if (mark == 'J') {
String cls = pool.getString(CONSTANT_Class, ref[0]);
cls = cls.substring(1+cls.lastIndexOf('/'));
intrinsic = cls+"."+intrinsic;
} //System.out.println("recognized intrinsic "+intrinsic); byte refKind = -1; switch (intrinsic) { case"findGetter": refKind = REF_getField; break; case"findStaticGetter": refKind = REF_getStatic; break; case"findSetter": refKind = REF_putField; break; case"findStaticSetter": refKind = REF_putStatic; break; case"findVirtual": refKind = REF_invokeVirtual; break; case"findStatic": refKind = REF_invokeStatic; break; case"findSpecial": refKind = REF_invokeSpecial; break; case"findConstructor": refKind = REF_newInvokeSpecial; break;
} if (refKind >= 0 && (con = parseMemberLookup(refKind, args)) != null) {
args.clear(); args.add(con); continue;
}
}
Method ownMethod = null; if (mark == 'T' || mark == 'H' || mark == 'I') {
ownMethod = findMember(cf.methods, ref[1], ref[2]);
} //if (intrinsic != null) System.out.println("intrinsic = "+intrinsic); switch (intrinsic == null ? "" : intrinsic) { case"fromMethodDescriptorString":
con = makeMethodTypeCon(args.get(0));
args.clear(); args.add(con); continue; case"methodType": {
flattenVarargs(args); // there are several overloadings, some with varargs
StringBuilder buf = new StringBuilder();
String rtype = null; for (Object typeArg : args) { if (typeArg instanceofClass) { Class<?> argClass = (Class<?>) typeArg; if (argClass.isPrimitive()) { char tchar; switch (argClass.getName()) { case"void": tchar = 'V'; break; case"boolean": tchar = 'Z'; break; case"byte": tchar = 'B'; break; case"char": tchar = 'C'; break; case"short": tchar = 'S'; break; case"int": tchar = 'I'; break; case"long": tchar = 'J'; break; case"float": tchar = 'F'; break; case"double": tchar = 'D'; break; default: thrownew InternalError(argClass.toString());
}
buf.append(tchar);
} else { // should not happen, but...
buf.append('L').append(argClass.getName().replace('.','/')).append(';');
}
} elseif (typeArg instanceof Constant) {
Constant argCon = (Constant) typeArg; if (argCon.tag == CONSTANT_Class) {
String cn = pool.get(argCon.itemIndex()).itemString(); if (cn.endsWith(";"))
buf.append(cn); else
buf.append('L').append(cn).append(';');
} else { break decode;
}
} else { break decode;
} if (rtype == null) { // first arg is treated differently
rtype = buf.toString();
buf.setLength(0);
buf.append('(');
}
}
buf.append(')').append(rtype);
con = con = makeMethodTypeCon(buf.toString());
args.clear(); args.add(con); continue;
} case"lookup": case"dynamicInvoker":
args.clear(); args.add(intrinsic); continue; case"lookupClass": if (args.equals(Arrays.asList("lookup"))) { // fold lookup().lookupClass() to the enclosing class
args.clear(); args.add(pool.get(cf.thisc)); continue;
} break; case"invoke": case"invokeGeneric": case"invokeWithArguments": if (patternMark != 'I') break decode; if ("invokeWithArguments".equals(intrinsic))
flattenVarargs(args);
bsmArgs = new ArrayList<>(args);
args.clear(); args.add("invokeGeneric"); continue; case"Integer.valueOf": case"Float.valueOf": case"Long.valueOf": case"Double.valueOf":
removeEmptyJVMSlots(args); if (args.size() == 1) {
arg = args.remove(0); assert(3456 == (CONSTANT_Integer*1000 + CONSTANT_Float*100 + CONSTANT_Long*10 + CONSTANT_Double)); if (isConstant(arg, CONSTANT_Integer + "IFLD".indexOf(intrinsic.charAt(0)))
|| arg instanceof Number) {
args.add(arg); continue;
}
} break decode; case"StringBuilder.append": // allow calls like ("value = "+x)
removeEmptyJVMSlots(args);
args.subList(1, args.size()).clear(); continue; case"StringBuilder.toString":
args.clear();
args.add(intrinsic); continue;
} if (!hasRecv && ownMethod != null && patternMark != 0) {
con = constants.get(ownMethod); if (con == null) break decode;
args.clear(); args.add(con); continue;
} elseif (type.endsWith(")V")) { // allow calls like println("reached the pattern method")
args.clear(); continue;
} break decode; // bail out for most calls
} case opc_areturn:
{
++branchCount; if (bsmArgs != null) { // parse bsmArgs as (MH, lookup, String, MT, [extra])
Constant indyCon = makeInvokeDynamicCon(bsmArgs); if (indyCon != null) {
Constant typeCon = (Constant) bsmArgs.get(3);
indySignatures.put(m, pool.getString(typeCon.itemIndex())); return indyCon;
}
System.err.println(m+": inscrutable bsm arguments: "+bsmArgs); break decode; // bail out
}
arg = jvm.pop(); if (branchCount == 2 && UNKNOWN_CON.equals(arg)) break; // merge to next path if (isConstant(arg, wantTag)) return (Constant) arg; break decode; // bail out
} default: if (jvm.stackMotion(i.bc)) break; if (bc >= opc_nconst_MIN && bc <= opc_nconst_MAX)
{ jvm.push(INSTRUCTION_CONSTANTS[bc - opc_nconst_MIN]); break; } if (patternMark == 'I') { // these support caching paths in INDY_x methods if (bc == opc_aload || bc >= opc_aload_0 && bc <= opc_aload_MAX)
{ jvm.push(UNKNOWN_CON); break; } if (bc == opc_astore || bc >= opc_astore_0 && bc <= opc_astore_MAX)
{ jvm.pop(); break; } switch (bc) { case opc_getfield: case opc_aaload:
jvm.push(UNKNOWN_CON); break; case opc_ifnull: case opc_ifnonnull: // ignore branch target if (++branchCount != 1) break decode;
jvm.pop(); break; case opc_checkcast:
arg = jvm.top(); if ("invokeWithArguments".equals(arg) || "invokeGeneric".equals(arg)) break; // assume it is a helpful cast break decode; default: break decode; // bail out
} continue decode; // go to next instruction
} break decode; // bail out
} //end switch
}
System.err.println(m+": bailout on "+i+" jvm stack: "+jvm.stack); returnnull;
} privatefinal String UNKNOWN_CON = "";
publicabstractstaticclass Outer { publicabstract List<? extends Inner> inners(); protectedvoid linkInners() { for (Inner i : inners()) {
i.linkOuter(this); if (i instanceof Outer)
((Outer)i).linkInners();
}
} public <T extends Outer> T outer(Class<T> c) { for (Outer walk = this;; walk = ((Inner)walk).outer()) { if (c.isInstance(walk)) return c.cast(walk); //if (!(walk instanceof Inner)) return null;
}
}
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.35Bemerkung:
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.