new variant of ConstantPoolChecker called ConstantPoolScanner
This commit is contained in:
@@ -0,0 +1,400 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
// TODO try to recall why I created ConstantPoolChecker2, what was up with ConstantPoolChecker?
|
||||
|
||||
// http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html
|
||||
/**
|
||||
* Enables us to check things quickly in the constant pool. This version accumulates the class references and the method
|
||||
* references, for classes that start with 'j' (we want to catch: java/lang). It skips everything it can and the end
|
||||
* result is a list of class references and a list of method references. The former look like this 'a/b/C' whilst the
|
||||
* latter look like this 'java/lang/Foo.bar' (the descriptor for the method is not included). Interface methods are
|
||||
* skipped.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.7.3
|
||||
*/
|
||||
public class ConstantPoolChecker2 {
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private final static byte CONSTANT_Utf8 = 1;
|
||||
|
||||
private final static byte CONSTANT_Integer = 3;
|
||||
|
||||
private final static byte CONSTANT_Float = 4;
|
||||
|
||||
private final static byte CONSTANT_Long = 5;
|
||||
|
||||
private final static byte CONSTANT_Double = 6;
|
||||
|
||||
private final static byte CONSTANT_Class = 7;
|
||||
|
||||
private final static byte CONSTANT_String = 8;
|
||||
|
||||
private final static byte CONSTANT_Fieldref = 9;
|
||||
|
||||
private final static byte CONSTANT_Methodref = 10;
|
||||
|
||||
private final static byte CONSTANT_InterfaceMethodref = 11;
|
||||
|
||||
private final static byte CONSTANT_NameAndType = 12;
|
||||
|
||||
private final static byte CONSTANT_MethodHandle = 15;
|
||||
|
||||
private final static byte CONSTANT_MethodType = 16;
|
||||
|
||||
private final static byte CONSTANT_InvokeDynamic = 18;
|
||||
|
||||
// Test entry point just goes through all the code in the bin folder
|
||||
public static void main(String[] args) throws Exception {
|
||||
File[] fs = new File("./bin").listFiles();
|
||||
// File[] fs = new File("../testdata-groovy/bin").listFiles();
|
||||
// File[] fs = new File("/Users/aclement/grailsreload/foo/target/classes").listFiles();
|
||||
|
||||
checkThemAll(fs);
|
||||
System.out.println("total=" + total / 1000000d);
|
||||
}
|
||||
|
||||
private static void checkThemAll(File[] fs) throws Exception {
|
||||
for (File f : fs) {
|
||||
if (f.isDirectory()) {
|
||||
checkThemAll(f.listFiles());
|
||||
}
|
||||
else if (f.getName().endsWith(".class")) {
|
||||
System.out.println(f);
|
||||
byte[] data = Utils.loadFromStream(new FileInputStream(f));
|
||||
long stime = System.nanoTime();
|
||||
References refs = getReferences(data);
|
||||
total += (System.nanoTime() - stime);
|
||||
System.out.println(refs.referencedClasses);
|
||||
System.out.println(refs.referencedMethods);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static long total = 0;
|
||||
|
||||
// ClassFile {
|
||||
// u4 magic;
|
||||
// u2 minor_version;
|
||||
// u2 major_version;
|
||||
// u2 constant_pool_count;
|
||||
// cp_info constant_pool[constant_pool_count-1];
|
||||
// u2 access_flags;
|
||||
// u2 this_class;
|
||||
// u2 super_class;
|
||||
// u2 interfaces_count;
|
||||
// u2 interfaces[interfaces_count];
|
||||
// u2 fields_count;
|
||||
// field_info fields[fields_count];
|
||||
// u2 methods_count;
|
||||
// method_info methods[methods_count];
|
||||
// u2 attributes_count;
|
||||
// attribute_info attributes[attributes_count];
|
||||
// }
|
||||
|
||||
static References getReferences(byte[] bytes) {
|
||||
ConstantPoolChecker2 cpc2 = new ConstantPoolChecker2(bytes);
|
||||
return new References(cpc2.slashedclassname, cpc2.referencedClasses, cpc2.referencedMethods);
|
||||
}
|
||||
|
||||
static class References {
|
||||
|
||||
String slashedClassName;
|
||||
|
||||
List<String> referencedClasses;
|
||||
|
||||
List<String> referencedMethods;
|
||||
|
||||
References(String slashedClassName, List<String> rc, List<String> rm) {
|
||||
this.slashedClassName = slashedClassName;
|
||||
this.referencedClasses = rc;
|
||||
this.referencedMethods = rm;
|
||||
}
|
||||
}
|
||||
|
||||
// Filled with strings and int[]
|
||||
private Object[] cpdata;
|
||||
|
||||
private int cpsize;
|
||||
|
||||
private int[] type;
|
||||
|
||||
// Does not need to be a set as there are no dups in the ConstantPool (for a class from a decent compiler...)
|
||||
private List<String> referencedClasses = new ArrayList<String>();
|
||||
|
||||
private List<String> referencedMethods = new ArrayList<String>();
|
||||
|
||||
private String slashedclassname;
|
||||
|
||||
private ConstantPoolChecker2(byte[] bytes) {
|
||||
readConstantPool(bytes);
|
||||
computeReferences();
|
||||
}
|
||||
|
||||
public void computeReferences() {
|
||||
for (int i = 0; i < cpsize; i++) {
|
||||
switch (type[i]) {
|
||||
case CONSTANT_Class:
|
||||
int classindex = ((Integer) cpdata[i]);
|
||||
String classname = (String) cpdata[classindex];
|
||||
if (classname == null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
referencedClasses.add(classname);
|
||||
break;
|
||||
case CONSTANT_Methodref:
|
||||
int[] indexes = (int[]) cpdata[i];
|
||||
int classindex2 = indexes[0];
|
||||
int nameAndTypeIndex = indexes[1];
|
||||
StringBuilder s = new StringBuilder();
|
||||
String theClassName = (String) cpdata[(Integer) cpdata[classindex2]];
|
||||
if (theClassName.charAt(0) == 'j') {
|
||||
s.append(theClassName);
|
||||
s.append(".");
|
||||
s.append((String) cpdata[(Integer) cpdata[nameAndTypeIndex]]);
|
||||
referencedMethods.add(s.toString());
|
||||
}
|
||||
break;
|
||||
// private final static byte CONSTANT_Utf8 = 1;
|
||||
// private final static byte CONSTANT_Integer = 3;
|
||||
// private final static byte CONSTANT_Float = 4;
|
||||
// private final static byte CONSTANT_Long = 5;
|
||||
// private final static byte CONSTANT_Double = 6;
|
||||
// private final static byte CONSTANT_String = 8;
|
||||
// private final static byte CONSTANT_Fieldref = 9;
|
||||
// private final static byte CONSTANT_InterfaceMethodref = 11;
|
||||
// private final static byte CONSTANT_NameAndType = 12;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void readConstantPool(byte[] bytes) {
|
||||
try {
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
|
||||
DataInputStream dis = new DataInputStream(bais);
|
||||
|
||||
int magic = dis.readInt(); // magic 0xCAFEBABE
|
||||
if (magic != 0xCAFEBABE) {
|
||||
throw new IllegalStateException("not bytecode, magic was 0x" + Integer.toString(magic, 16));
|
||||
}
|
||||
dis.skip(4); // skip minor and major versions
|
||||
cpsize = dis.readShort();
|
||||
if (DEBUG) {
|
||||
System.out.println("Constant Pool Size =" + cpsize);
|
||||
}
|
||||
cpdata = new Object[cpsize];
|
||||
type = new int[cpsize];
|
||||
for (int cpentry = 1; cpentry < cpsize; cpentry++) {
|
||||
boolean doubleSlot = processConstantPoolEntry(cpentry, dis);
|
||||
if (doubleSlot) {
|
||||
cpentry++;
|
||||
}
|
||||
}
|
||||
dis.skip(2); // access flags
|
||||
int thisclassname = dis.readShort();
|
||||
int classindex = ((Integer) cpdata[thisclassname]);
|
||||
slashedclassname = (String) cpdata[classindex];
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new IllegalStateException("Unexpected problem processing bytes for class", e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean processConstantPoolEntry(int index, DataInputStream dis) throws IOException {
|
||||
byte b = dis.readByte();
|
||||
switch (b) {
|
||||
case CONSTANT_Utf8:
|
||||
// CONSTANT_Utf8_info { u1 tag; u2 length; u1 bytes[length]; }
|
||||
cpdata[index] = dis.readUTF();
|
||||
// type[index] = b;
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":UTF8[" + cpdata[index] + "]");
|
||||
}
|
||||
break;
|
||||
case CONSTANT_Integer:
|
||||
// CONSTANT_Integer_info { u1 tag; u4 bytes; }
|
||||
if (DEBUG) {
|
||||
int i = dis.readInt();
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":INTEGER[" + i + "]");
|
||||
}
|
||||
}
|
||||
else {
|
||||
dis.skip(4);
|
||||
}
|
||||
break;
|
||||
case CONSTANT_Float:
|
||||
// CONSTANT_Float_info { u1 tag; u4 bytes; }
|
||||
if (DEBUG) {
|
||||
float f = dis.readFloat();
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":FLOAT[" + f + "]");
|
||||
}
|
||||
}
|
||||
else {
|
||||
dis.skip(4);
|
||||
}
|
||||
break;
|
||||
case CONSTANT_Long:
|
||||
// CONSTANT_Long_info {
|
||||
// u1 tag;
|
||||
// u4 high_bytes;
|
||||
// u4 low_bytes;
|
||||
// }
|
||||
if (DEBUG) {
|
||||
long l = dis.readLong();
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":LONG[" + l + "]");
|
||||
}
|
||||
}
|
||||
else {
|
||||
dis.skip(8);
|
||||
}
|
||||
return true;
|
||||
case CONSTANT_Double:
|
||||
// CONSTANT_Double_info {
|
||||
// u1 tag;
|
||||
// u4 high_bytes;
|
||||
// u4 low_bytes;
|
||||
// }
|
||||
if (DEBUG) {
|
||||
double d = dis.readDouble();
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":DOUBLE[" + d + "]");
|
||||
}
|
||||
}
|
||||
else {
|
||||
dis.skip(8);
|
||||
}
|
||||
return true;
|
||||
case CONSTANT_Class:
|
||||
// CONSTANT_Class_info { u1 tag; u2 name_index; }
|
||||
type[index] = b;
|
||||
cpdata[index] = (int) dis.readShort();
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":CLASS[name_index=" + cpdata[index] + "]");
|
||||
}
|
||||
break;
|
||||
case CONSTANT_String:
|
||||
// CONSTANT_String_info { u1 tag; u2 string_index; }
|
||||
if (DEBUG) {
|
||||
cpdata[index] = (int) dis.readShort();
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":STRING[string_index=" + cpdata[index] + "]");
|
||||
}
|
||||
}
|
||||
else {
|
||||
dis.skip(2);
|
||||
}
|
||||
break;
|
||||
case CONSTANT_Fieldref:
|
||||
// CONSTANT_Fieldref_info { u1 tag; u2 class_index; u2 name_and_type_index; }
|
||||
if (DEBUG) {
|
||||
cpdata[index] = new int[] { dis.readShort(), dis.readShort() };
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":FIELDREF[class_index=" + ((int[]) cpdata[index])[0]
|
||||
+ ",name_and_type_index="
|
||||
+ ((int[]) cpdata[index])[1] + "]");
|
||||
}
|
||||
}
|
||||
else {
|
||||
dis.skip(4);
|
||||
}
|
||||
break;
|
||||
case CONSTANT_Methodref:
|
||||
// CONSTANT_Methodref_info { u1 tag; u2 class_index; u2 name_and_type_index; }
|
||||
type[index] = b;
|
||||
//if (DEBUG) {
|
||||
cpdata[index] = new int[] { dis.readShort(), dis.readShort() };
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":METHODREF[class_index=" + ((int[]) cpdata[index])[0]
|
||||
+ ",name_and_type_index="
|
||||
+ ((int[]) cpdata[index])[1] + "]");
|
||||
}
|
||||
// } else {
|
||||
// dis.skip(4);
|
||||
// }
|
||||
break;
|
||||
case CONSTANT_InterfaceMethodref:
|
||||
// CONSTANT_InterfaceMethodref_info {
|
||||
// u1 tag;
|
||||
// u2 class_index;
|
||||
// u2 name_and_type_index;
|
||||
// }
|
||||
if (DEBUG) {
|
||||
cpdata[index] = new int[] { dis.readShort(), dis.readShort() };
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":INTERFACEMETHODREF[class_index=" + ((int[]) cpdata[index])[0]
|
||||
+ ",name_and_type_index=" + ((int[]) cpdata[index])[1] + "]");
|
||||
}
|
||||
}
|
||||
else {
|
||||
dis.skip(4);
|
||||
}
|
||||
break;
|
||||
case CONSTANT_NameAndType:
|
||||
// The CONSTANT_NameAndType_info structure is used to represent a field or method, without indicating which class or interface type it belongs to:
|
||||
// CONSTANT_NameAndType_info { u1 tag; u2 name_index; u2 descriptor_index; }
|
||||
// type[index] = b;
|
||||
cpdata[index] = (int) dis.readShort();// new int[] { dis.readShort(), dis.readShort() };
|
||||
dis.skip(2); // skip the descriptor for now
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":NAMEANDTYPE[name_index=" + ((int[]) cpdata[index])[0]
|
||||
+ ",descriptor_index="
|
||||
+ ((int[]) cpdata[index])[1] + "]");
|
||||
}
|
||||
break;
|
||||
case CONSTANT_InvokeDynamic:
|
||||
//CONSTANT_InvokeDynamic_info {
|
||||
// u1 tag;
|
||||
// u2 bootstrap_method_attr_index;
|
||||
// u2 name_and_type_index;
|
||||
//}
|
||||
dis.skipBytes(4);
|
||||
break;
|
||||
case CONSTANT_MethodHandle:
|
||||
//CONSTANT_MethodHandle_info {
|
||||
// u1 tag;
|
||||
// u1 reference_kind;
|
||||
// u2 reference_index;
|
||||
//}
|
||||
dis.skipBytes(3);
|
||||
break;
|
||||
case CONSTANT_MethodType:
|
||||
//CONSTANT_MethodType_info {
|
||||
// u1 tag;
|
||||
// u2 descriptor_index;
|
||||
//}
|
||||
dis.skipBytes(2);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Entry: " + index + " " + Byte.toString(b));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -16,28 +16,24 @@
|
||||
|
||||
package org.springsource.loaded;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
// TODO try to recall why I created ConstantPoolChecker2, what was up with ConstantPoolChecker?
|
||||
|
||||
// http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html
|
||||
/**
|
||||
* Enables us to check things quickly in the constant pool. This version accumulates the class references and the method
|
||||
* references, for classes that start with 'j' (we want to catch: java/lang). It skips everything it can and the end
|
||||
* result is a list of class references and a list of method references. The former look like this 'a/b/C' whilst the
|
||||
* latter look like this 'java/lang/Foo.bar' (the descriptor for the method is not included). Interface methods are
|
||||
* skipped.
|
||||
*
|
||||
*
|
||||
* Useful reference: http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.7.3
|
||||
*/
|
||||
public class ConstantPoolChecker2 {
|
||||
public class ConstantPoolScanner {
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
@@ -69,35 +65,37 @@ public class ConstantPoolChecker2 {
|
||||
|
||||
private final static byte CONSTANT_InvokeDynamic = 18;
|
||||
|
||||
// Test entry point just goes through all the code in the bin folder
|
||||
public static void main(String[] args) throws Exception {
|
||||
File[] fs = new File("./bin").listFiles();
|
||||
// File[] fs = new File("../testdata-groovy/bin").listFiles();
|
||||
// File[] fs = new File("/Users/aclement/grailsreload/foo/target/classes").listFiles();
|
||||
private byte[] classbytes;
|
||||
|
||||
checkThemAll(fs);
|
||||
System.out.println("total=" + total / 1000000d);
|
||||
// Used during the parse step
|
||||
private int ptr;
|
||||
|
||||
// Filled with strings and int[]
|
||||
private Object[] cpdata;
|
||||
|
||||
private int cpsize;
|
||||
|
||||
private int[] type;
|
||||
|
||||
// Does not need to be a set as there are no dups in the ConstantPool (for a class from a decent compiler...)
|
||||
private List<String> referencedClasses = new ArrayList<String>();
|
||||
|
||||
private List<String> referencedMethods = new ArrayList<String>();
|
||||
|
||||
private String slashedclassname;
|
||||
|
||||
|
||||
public static References getReferences(byte[] classbytes) {
|
||||
ConstantPoolScanner cpScanner = new ConstantPoolScanner(classbytes);
|
||||
return new References(cpScanner.slashedclassname, cpScanner.referencedClasses, cpScanner.referencedMethods);
|
||||
}
|
||||
|
||||
private static void checkThemAll(File[] fs) throws Exception {
|
||||
for (File f : fs) {
|
||||
if (f.isDirectory()) {
|
||||
checkThemAll(f.listFiles());
|
||||
}
|
||||
else if (f.getName().endsWith(".class")) {
|
||||
System.out.println(f);
|
||||
byte[] data = Utils.loadFromStream(new FileInputStream(f));
|
||||
long stime = System.nanoTime();
|
||||
References refs = getReferences(data);
|
||||
total += (System.nanoTime() - stime);
|
||||
System.out.println(refs.referencedClasses);
|
||||
System.out.println(refs.referencedMethods);
|
||||
}
|
||||
}
|
||||
private ConstantPoolScanner(byte[] bytes) {
|
||||
parseClass(bytes);
|
||||
computeReferences();
|
||||
}
|
||||
|
||||
static long total = 0;
|
||||
|
||||
// Format of a classfile:
|
||||
// ClassFile {
|
||||
// u4 magic;
|
||||
// u2 minor_version;
|
||||
@@ -116,52 +114,137 @@ public class ConstantPoolChecker2 {
|
||||
// u2 attributes_count;
|
||||
// attribute_info attributes[attributes_count];
|
||||
// }
|
||||
|
||||
static References getReferences(byte[] bytes) {
|
||||
ConstantPoolChecker2 cpc2 = new ConstantPoolChecker2(bytes);
|
||||
return new References(cpc2.slashedclassname, cpc2.referencedClasses, cpc2.referencedMethods);
|
||||
}
|
||||
|
||||
static class References {
|
||||
|
||||
String slashedClassName;
|
||||
|
||||
List<String> referencedClasses;
|
||||
|
||||
List<String> referencedMethods;
|
||||
|
||||
References(String slashedClassName, List<String> rc, List<String> rm) {
|
||||
this.slashedClassName = slashedClassName;
|
||||
this.referencedClasses = rc;
|
||||
this.referencedMethods = rm;
|
||||
private void parseClass(byte[] bytes) {
|
||||
try {
|
||||
this.classbytes = bytes;
|
||||
this.ptr = 0;
|
||||
int magic = readInt(); // magic 0xCAFEBABE
|
||||
if (magic != 0xCAFEBABE) {
|
||||
throw new IllegalStateException("not bytecode, magic was 0x" + Integer.toString(magic, 16));
|
||||
}
|
||||
ptr += 4; // skip minor and major versions
|
||||
cpsize = readUnsignedShort();
|
||||
if (DEBUG) {
|
||||
System.out.println("Constant Pool Size =" + cpsize);
|
||||
}
|
||||
cpdata = new Object[cpsize];
|
||||
type = new int[cpsize];
|
||||
for (int cpentry = 1; cpentry < cpsize; cpentry++) {
|
||||
boolean wasDoubleSlotItem = processConstantPoolEntry(cpentry);
|
||||
if (wasDoubleSlotItem) {
|
||||
cpentry++;
|
||||
}
|
||||
}
|
||||
ptr += 2; // access flags
|
||||
int thisclassname = readUnsignedShort();
|
||||
int classindex = ((Integer) cpdata[thisclassname]);
|
||||
slashedclassname = accessUtf8(classindex);
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new IllegalStateException("Unexpected problem processing bytes for class", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Filled with strings and int[]
|
||||
private Object[] cpdata;
|
||||
|
||||
private int cpsize;
|
||||
|
||||
private int[] type;
|
||||
|
||||
// Does not need to be a set as there are no dups in the ConstantPool (for a class from a decent compiler...)
|
||||
private List<String> referencedClasses = new ArrayList<String>();
|
||||
|
||||
private List<String> referencedMethods = new ArrayList<String>();
|
||||
|
||||
private String slashedclassname;
|
||||
|
||||
private ConstantPoolChecker2(byte[] bytes) {
|
||||
readConstantPool(bytes);
|
||||
computeReferences();
|
||||
/**
|
||||
* Return the UTF8 at the specified index in the constant pool. The data found at the constant pool for that index
|
||||
* may not have been unpacked yet if this is the first access of the string. If not unpacked the constant pool entry
|
||||
* is a pair of ints in an array representing the offset and length within the classbytes where the UTF8 string is
|
||||
* encoded. Once decoded the constant pool entry is flipped from an int array to a String for future fast access.
|
||||
*
|
||||
* @param cpIndex constant pool index
|
||||
* @return UTF8 string at that constant pool index
|
||||
*/
|
||||
private String accessUtf8(int cpIndex) {
|
||||
Object object = cpdata[cpIndex];
|
||||
if (object instanceof String) {
|
||||
return (String) object;
|
||||
}
|
||||
int[] ptrAndLen = (int[]) object;
|
||||
String value;
|
||||
try {
|
||||
value = new String(classbytes, ptrAndLen[0], ptrAndLen[1], "UTF8");
|
||||
}
|
||||
catch (UnsupportedEncodingException e) {
|
||||
throw new IllegalStateException("Bad data found at constant pool position " + cpIndex + " offset="
|
||||
+ ptrAndLen[0] + " length=" + ptrAndLen[1], e);
|
||||
}
|
||||
cpdata[cpIndex] = value;
|
||||
return value;
|
||||
}
|
||||
|
||||
public void computeReferences() {
|
||||
/**
|
||||
* @return an int constructed from the next four bytes to be processed
|
||||
*/
|
||||
private final int readInt() {
|
||||
return ((classbytes[ptr++] & 0xFF) << 24) + ((classbytes[ptr++] & 0xFF) << 16)
|
||||
+ ((classbytes[ptr++] & 0xFF) << 8)
|
||||
+ (classbytes[ptr++] & 0xFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return an unsigned short constructed from the next two bytes to be processed
|
||||
*/
|
||||
private final int readUnsignedShort() {
|
||||
return ((classbytes[ptr++] & 0xff) << 8) + (classbytes[ptr++] & 0xff);
|
||||
}
|
||||
|
||||
private boolean processConstantPoolEntry(int index) throws IOException {
|
||||
byte b = classbytes[ptr++];
|
||||
switch (b) {
|
||||
case CONSTANT_Integer: // CONSTANT_Integer_info { u1 tag; u4 bytes; }
|
||||
case CONSTANT_Float: // CONSTANT_Float_info { u1 tag; u4 bytes; }
|
||||
case CONSTANT_Fieldref: // CONSTANT_Fieldref_info { u1 tag; u2 class_index; u2 name_and_type_index; }
|
||||
case CONSTANT_InterfaceMethodref: // CONSTANT_InterfaceMethodref_info { u1 tag; u2 class_index; u2 name_and_type_index; }
|
||||
case CONSTANT_InvokeDynamic: // CONSTANT_InvokeDynamic_info { u1 tag; u2 bootstrap_method_attr_index; u2 name_and_type_index; }
|
||||
ptr += 4;
|
||||
break;
|
||||
case CONSTANT_Utf8:
|
||||
// CONSTANT_Utf8_info { u1 tag; u2 length; u1 bytes[length]; }
|
||||
// Cache just the index and length - do not unpack it now
|
||||
int len = readUnsignedShort();
|
||||
cpdata[index] = new int[] { ptr, len };
|
||||
ptr += len;
|
||||
break;
|
||||
case CONSTANT_Long: // CONSTANT_Long_info { u1 tag; u4 high_bytes; u4 low_bytes; }
|
||||
case CONSTANT_Double: // CONSTANT_Double_info { u1 tag; u4 high_bytes; u4 low_bytes; }
|
||||
ptr += 8;
|
||||
return true;
|
||||
case CONSTANT_Class: // CONSTANT_Class_info { u1 tag; u2 name_index; }
|
||||
type[index] = b;
|
||||
cpdata[index] = readUnsignedShort();
|
||||
break;
|
||||
case CONSTANT_Methodref:
|
||||
// CONSTANT_Methodref_info { u1 tag; u2 class_index; u2 name_and_type_index; }
|
||||
type[index] = b;
|
||||
cpdata[index] = new int[] { readUnsignedShort(), readUnsignedShort() };
|
||||
break;
|
||||
case CONSTANT_NameAndType:
|
||||
// The CONSTANT_NameAndType_info structure is used to represent a field or method, without indicating which class or interface type it belongs to:
|
||||
// CONSTANT_NameAndType_info { u1 tag; u2 name_index; u2 descriptor_index; }
|
||||
// type[index] = b;
|
||||
cpdata[index] = readUnsignedShort();
|
||||
ptr += 2; // skip the descriptor for now
|
||||
break;
|
||||
case CONSTANT_MethodHandle:
|
||||
// CONSTANT_MethodHandle_info { u1 tag; u1 reference_kind; u2 reference_index; }
|
||||
ptr += 3;
|
||||
break;
|
||||
case CONSTANT_String: // CONSTANT_String_info { u1 tag; u2 string_index; }
|
||||
case CONSTANT_MethodType: // CONSTANT_MethodType_info { u1 tag; u2 descriptor_index; }
|
||||
ptr += 2;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Entry: " + index + " " + Byte.toString(b));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void computeReferences() {
|
||||
for (int i = 0; i < cpsize; i++) {
|
||||
switch (type[i]) {
|
||||
case CONSTANT_Class:
|
||||
int classindex = ((Integer) cpdata[i]);
|
||||
String classname = (String) cpdata[classindex];
|
||||
String classname = accessUtf8(classindex);
|
||||
if (classname == null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
@@ -172,229 +255,50 @@ public class ConstantPoolChecker2 {
|
||||
int classindex2 = indexes[0];
|
||||
int nameAndTypeIndex = indexes[1];
|
||||
StringBuilder s = new StringBuilder();
|
||||
String theClassName = (String) cpdata[(Integer) cpdata[classindex2]];
|
||||
String theClassName = accessUtf8((Integer) cpdata[classindex2]);
|
||||
if (theClassName.charAt(0) == 'j') {
|
||||
s.append(theClassName);
|
||||
s.append(".");
|
||||
s.append((String) cpdata[(Integer) cpdata[nameAndTypeIndex]]);
|
||||
s.append(accessUtf8((Integer) cpdata[nameAndTypeIndex]));
|
||||
referencedMethods.add(s.toString());
|
||||
}
|
||||
break;
|
||||
// private final static byte CONSTANT_Utf8 = 1;
|
||||
// private final static byte CONSTANT_Integer = 3;
|
||||
// private final static byte CONSTANT_Float = 4;
|
||||
// private final static byte CONSTANT_Long = 5;
|
||||
// private final static byte CONSTANT_Double = 6;
|
||||
// private final static byte CONSTANT_String = 8;
|
||||
// private final static byte CONSTANT_Fieldref = 9;
|
||||
// private final static byte CONSTANT_InterfaceMethodref = 11;
|
||||
// private final static byte CONSTANT_NameAndType = 12;
|
||||
// private final static byte CONSTANT_Utf8 = 1;
|
||||
// private final static byte CONSTANT_Integer = 3;
|
||||
// private final static byte CONSTANT_Float = 4;
|
||||
// private final static byte CONSTANT_Long = 5;
|
||||
// private final static byte CONSTANT_Double = 6;
|
||||
// private final static byte CONSTANT_String = 8;
|
||||
// private final static byte CONSTANT_Fieldref = 9;
|
||||
// private final static byte CONSTANT_InterfaceMethodref = 11;
|
||||
// private final static byte CONSTANT_NameAndType = 12;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void readConstantPool(byte[] bytes) {
|
||||
try {
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
|
||||
DataInputStream dis = new DataInputStream(bais);
|
||||
|
||||
int magic = dis.readInt(); // magic 0xCAFEBABE
|
||||
if (magic != 0xCAFEBABE) {
|
||||
throw new IllegalStateException("not bytecode, magic was 0x" + Integer.toString(magic, 16));
|
||||
}
|
||||
dis.skip(4); // skip minor and major versions
|
||||
cpsize = dis.readShort();
|
||||
if (DEBUG) {
|
||||
System.out.println("Constant Pool Size =" + cpsize);
|
||||
}
|
||||
cpdata = new Object[cpsize];
|
||||
type = new int[cpsize];
|
||||
for (int cpentry = 1; cpentry < cpsize; cpentry++) {
|
||||
boolean doubleSlot = processConstantPoolEntry(cpentry, dis);
|
||||
if (doubleSlot) {
|
||||
cpentry++;
|
||||
}
|
||||
}
|
||||
dis.skip(2); // access flags
|
||||
int thisclassname = dis.readShort();
|
||||
int classindex = ((Integer) cpdata[thisclassname]);
|
||||
slashedclassname = (String) cpdata[classindex];
|
||||
public static class References {
|
||||
|
||||
public final String slashedClassName;
|
||||
|
||||
public final List<String> referencedClasses;
|
||||
|
||||
public final List<String> referencedMethods;
|
||||
|
||||
References(String slashedClassName, List<String> rc, List<String> rm) {
|
||||
this.slashedClassName = slashedClassName;
|
||||
this.referencedClasses = rc;
|
||||
this.referencedMethods = rm;
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new IllegalStateException("Unexpected problem processing bytes for class", e);
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder s = new StringBuilder();
|
||||
s.append("Class=").append(slashedClassName).append("\n");
|
||||
s.append("ReferencedClasses=#").append(referencedClasses.size()).append("\n");
|
||||
s.append("ReferencedMethods=#").append(referencedMethods.size()).append("\n");
|
||||
return s.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean processConstantPoolEntry(int index, DataInputStream dis) throws IOException {
|
||||
byte b = dis.readByte();
|
||||
switch (b) {
|
||||
case CONSTANT_Utf8:
|
||||
// CONSTANT_Utf8_info { u1 tag; u2 length; u1 bytes[length]; }
|
||||
cpdata[index] = dis.readUTF();
|
||||
// type[index] = b;
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":UTF8[" + cpdata[index] + "]");
|
||||
}
|
||||
break;
|
||||
case CONSTANT_Integer:
|
||||
// CONSTANT_Integer_info { u1 tag; u4 bytes; }
|
||||
if (DEBUG) {
|
||||
int i = dis.readInt();
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":INTEGER[" + i + "]");
|
||||
}
|
||||
}
|
||||
else {
|
||||
dis.skip(4);
|
||||
}
|
||||
break;
|
||||
case CONSTANT_Float:
|
||||
// CONSTANT_Float_info { u1 tag; u4 bytes; }
|
||||
if (DEBUG) {
|
||||
float f = dis.readFloat();
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":FLOAT[" + f + "]");
|
||||
}
|
||||
}
|
||||
else {
|
||||
dis.skip(4);
|
||||
}
|
||||
break;
|
||||
case CONSTANT_Long:
|
||||
// CONSTANT_Long_info {
|
||||
// u1 tag;
|
||||
// u4 high_bytes;
|
||||
// u4 low_bytes;
|
||||
// }
|
||||
if (DEBUG) {
|
||||
long l = dis.readLong();
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":LONG[" + l + "]");
|
||||
}
|
||||
}
|
||||
else {
|
||||
dis.skip(8);
|
||||
}
|
||||
return true;
|
||||
case CONSTANT_Double:
|
||||
// CONSTANT_Double_info {
|
||||
// u1 tag;
|
||||
// u4 high_bytes;
|
||||
// u4 low_bytes;
|
||||
// }
|
||||
if (DEBUG) {
|
||||
double d = dis.readDouble();
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":DOUBLE[" + d + "]");
|
||||
}
|
||||
}
|
||||
else {
|
||||
dis.skip(8);
|
||||
}
|
||||
return true;
|
||||
case CONSTANT_Class:
|
||||
// CONSTANT_Class_info { u1 tag; u2 name_index; }
|
||||
type[index] = b;
|
||||
cpdata[index] = (int) dis.readShort();
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":CLASS[name_index=" + cpdata[index] + "]");
|
||||
}
|
||||
break;
|
||||
case CONSTANT_String:
|
||||
// CONSTANT_String_info { u1 tag; u2 string_index; }
|
||||
if (DEBUG) {
|
||||
cpdata[index] = (int) dis.readShort();
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":STRING[string_index=" + cpdata[index] + "]");
|
||||
}
|
||||
}
|
||||
else {
|
||||
dis.skip(2);
|
||||
}
|
||||
break;
|
||||
case CONSTANT_Fieldref:
|
||||
// CONSTANT_Fieldref_info { u1 tag; u2 class_index; u2 name_and_type_index; }
|
||||
if (DEBUG) {
|
||||
cpdata[index] = new int[] { dis.readShort(), dis.readShort() };
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":FIELDREF[class_index=" + ((int[]) cpdata[index])[0]
|
||||
+ ",name_and_type_index="
|
||||
+ ((int[]) cpdata[index])[1] + "]");
|
||||
}
|
||||
}
|
||||
else {
|
||||
dis.skip(4);
|
||||
}
|
||||
break;
|
||||
case CONSTANT_Methodref:
|
||||
// CONSTANT_Methodref_info { u1 tag; u2 class_index; u2 name_and_type_index; }
|
||||
type[index] = b;
|
||||
//if (DEBUG) {
|
||||
cpdata[index] = new int[] { dis.readShort(), dis.readShort() };
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":METHODREF[class_index=" + ((int[]) cpdata[index])[0]
|
||||
+ ",name_and_type_index="
|
||||
+ ((int[]) cpdata[index])[1] + "]");
|
||||
}
|
||||
// } else {
|
||||
// dis.skip(4);
|
||||
// }
|
||||
break;
|
||||
case CONSTANT_InterfaceMethodref:
|
||||
// CONSTANT_InterfaceMethodref_info {
|
||||
// u1 tag;
|
||||
// u2 class_index;
|
||||
// u2 name_and_type_index;
|
||||
// }
|
||||
if (DEBUG) {
|
||||
cpdata[index] = new int[] { dis.readShort(), dis.readShort() };
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":INTERFACEMETHODREF[class_index=" + ((int[]) cpdata[index])[0]
|
||||
+ ",name_and_type_index=" + ((int[]) cpdata[index])[1] + "]");
|
||||
}
|
||||
}
|
||||
else {
|
||||
dis.skip(4);
|
||||
}
|
||||
break;
|
||||
case CONSTANT_NameAndType:
|
||||
// The CONSTANT_NameAndType_info structure is used to represent a field or method, without indicating which class or interface type it belongs to:
|
||||
// CONSTANT_NameAndType_info { u1 tag; u2 name_index; u2 descriptor_index; }
|
||||
// type[index] = b;
|
||||
cpdata[index] = (int) dis.readShort();// new int[] { dis.readShort(), dis.readShort() };
|
||||
dis.skip(2); // skip the descriptor for now
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":NAMEANDTYPE[name_index=" + ((int[]) cpdata[index])[0]
|
||||
+ ",descriptor_index="
|
||||
+ ((int[]) cpdata[index])[1] + "]");
|
||||
}
|
||||
break;
|
||||
case CONSTANT_InvokeDynamic:
|
||||
//CONSTANT_InvokeDynamic_info {
|
||||
// u1 tag;
|
||||
// u2 bootstrap_method_attr_index;
|
||||
// u2 name_and_type_index;
|
||||
//}
|
||||
dis.skipBytes(4);
|
||||
break;
|
||||
case CONSTANT_MethodHandle:
|
||||
//CONSTANT_MethodHandle_info {
|
||||
// u1 tag;
|
||||
// u1 reference_kind;
|
||||
// u2 reference_index;
|
||||
//}
|
||||
dis.skipBytes(3);
|
||||
break;
|
||||
case CONSTANT_MethodType:
|
||||
//CONSTANT_MethodType_info {
|
||||
// u1 tag;
|
||||
// u2 descriptor_index;
|
||||
//}
|
||||
dis.skipBytes(2);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Entry: " + index + " " + Byte.toString(b));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ import org.objectweb.asm.tree.MethodNode;
|
||||
|
||||
/**
|
||||
* Captures the information about the reloaded parts of a type that vary each time a new version is loaded.
|
||||
*
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
*/
|
||||
@@ -101,6 +101,7 @@ public class CurrentLiveVersion {
|
||||
&& GlobalConfiguration.classesToDump.contains(reloadableType.getSlashedName())) {
|
||||
Utils.dump(Utils.getExecutorName(reloadableType.getName(), versionstamp).replace('.', '/'), this.executor);
|
||||
}
|
||||
// DEFAULT METHODS - REMOVE THE IF
|
||||
if (!typeDescriptor.isInterface()) {
|
||||
this.dispatcherName = Utils.getDispatcherName(reloadableType.getName(), versionstamp);
|
||||
this.executorName = Utils.getExecutorName(reloadableType.getName(), versionstamp);
|
||||
@@ -117,6 +118,7 @@ public class CurrentLiveVersion {
|
||||
public void define() {
|
||||
staticInitializer = null;
|
||||
haveLookedForStaticInitializer = false;
|
||||
// DEFAULT METHODS - remove the if
|
||||
if (!typeDescriptor.isInterface()) {
|
||||
try {
|
||||
dispatcherClass = reloadableType.typeRegistry.defineClass(dispatcherName, dispatcher, false);
|
||||
@@ -143,6 +145,7 @@ public class CurrentLiveVersion {
|
||||
t.printStackTrace();
|
||||
}
|
||||
}
|
||||
// DEFAULT METHODS - remove the if
|
||||
if (!typeDescriptor.isInterface()) {
|
||||
try {
|
||||
dispatcherInstance = dispatcherClass.newInstance();
|
||||
|
||||
@@ -60,7 +60,7 @@ import org.springsource.loaded.ri.ReflectiveInterceptor;
|
||||
* </ul>
|
||||
* The cache is for types that are *only* getting reflection interception done, not for types touching anything
|
||||
* reloadable.
|
||||
*
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
*/
|
||||
@@ -83,7 +83,7 @@ public class MethodInvokerRewriter {
|
||||
* Rewrite regular operations on reloadable types and any reflective calls.
|
||||
* <p>
|
||||
* Note: no caching is done here (the cache is not read or written to)
|
||||
*
|
||||
*
|
||||
* @param typeRegistry the registry for which the rewriting is being done.
|
||||
* @param bytes the bytes for the type to modify.
|
||||
* @param skipReferencesCheck do we need to do a quick check to see if there is anything worth rewriting?
|
||||
@@ -126,9 +126,9 @@ public class MethodInvokerRewriter {
|
||||
if (b != null) {
|
||||
if (b.booleanValue()) { // the type was modified on an earlier run, there should be cached code around
|
||||
String cacheFileName = new StringBuilder(slashedClassName.replace('/', '_')).append("_").append(
|
||||
bytes.length)
|
||||
.append(".bytes").toString();
|
||||
File cacheFile = new File(GlobalConfiguration.cacheDir, ".slcache" + File.separator + cacheFileName);
|
||||
bytes.length).append(".bytes").toString();
|
||||
File cacheFile = new File(GlobalConfiguration.cacheDir,
|
||||
".slcache" + File.separator + cacheFileName);
|
||||
if (DEBUG_CACHING) {
|
||||
System.out.println("Checking for cache file " + cacheFile);
|
||||
}
|
||||
@@ -210,7 +210,7 @@ public class MethodInvokerRewriter {
|
||||
|
||||
/**
|
||||
* Load the cache index from the file '<cacheDir>/.index'.
|
||||
*
|
||||
*
|
||||
*/
|
||||
private static void ensureCacheIndexLoaded() {
|
||||
if (cacheIndex == null) {
|
||||
@@ -286,9 +286,10 @@ public class MethodInvokerRewriter {
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] rewrite(boolean canCache, TypeRegistry typeRegistry, byte[] bytes, boolean skipReferencesCheck) {
|
||||
private static byte[] rewrite(boolean canCache, TypeRegistry typeRegistry, byte[] bytes,
|
||||
boolean skipReferencesCheck) {
|
||||
|
||||
// v1 - just looks at classes, if it sees jlClass or a jlr type it has to be cautious and assume a
|
||||
// v1 - just looks at classes, if it sees jlClass or a jlr type it has to be cautious and assume a
|
||||
// rewrite is necessary:
|
||||
// List<String> classes = ConstantPoolChecker.getReferencedClasses(bytes);
|
||||
// // System.out.println(classes);
|
||||
@@ -664,10 +665,10 @@ public class MethodInvokerRewriter {
|
||||
* Call this method to declare that a certain method is 'interceptable'. An interceptable method should have a
|
||||
* corresponding interceptor method in {@link ReflectiveInterceptor}. The name and signature of the interceptor
|
||||
* will be derived from the interceptable method.
|
||||
*
|
||||
*
|
||||
* For example, java.lang.Class.getMethod(Class[] params) ==> ReflectiveInterceptor.jlClassGetMethod(Class thiz,
|
||||
* Class[] params)
|
||||
*
|
||||
*
|
||||
* @param owner Slashed class name of the declaring type.
|
||||
* @param methodName Name of the interceptable method.
|
||||
*/
|
||||
@@ -720,7 +721,8 @@ public class MethodInvokerRewriter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
|
||||
public void visit(int version, int access, String name, String signature, String superName,
|
||||
String[] interfaces) {
|
||||
super.visit(version, access, name, signature, superName, interfaces);
|
||||
this.slashedclassname = name;
|
||||
|
||||
@@ -737,6 +739,7 @@ public class MethodInvokerRewriter {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public FieldVisitor visitField(final int access, final String name, final String desc, final String signature,
|
||||
final Object value) {
|
||||
fieldcount++;
|
||||
@@ -837,7 +840,7 @@ public class MethodInvokerRewriter {
|
||||
// TODO write up how the code looks for these in a comment
|
||||
/**
|
||||
* code:
|
||||
*
|
||||
*
|
||||
* <code>
|
||||
* <pre>
|
||||
* boolean b = TypeRegistry.instanceFieldInterceptionRequired(regId|classId,name)
|
||||
@@ -971,14 +974,14 @@ public class MethodInvokerRewriter {
|
||||
* <li>whether to rewrite it
|
||||
* <li>what method should be called instead
|
||||
* </ul>
|
||||
*
|
||||
*
|
||||
* @return true if the call was modified/intercepted
|
||||
*/
|
||||
private boolean interceptReflection(String owner, String name, String desc) {
|
||||
if (isInterceptable(owner, name)) {
|
||||
//TODO: [...] this is probably a lot slower than unfolding this check into
|
||||
//TODO: [...] this is probably a lot slower than unfolding this check into
|
||||
// bunch of optimised if cases, but it is also much easier to manage.
|
||||
// It should be possible to write something to generate the optimised
|
||||
// It should be possible to write something to generate the optimised
|
||||
// if's from the contents of the 'interceptable' HashSet. Measure before optimizing.
|
||||
callReflectiveInterceptor(owner, name, desc, mv);
|
||||
return true;
|
||||
@@ -1021,7 +1024,7 @@ public class MethodInvokerRewriter {
|
||||
/**
|
||||
* Generate bytecode to convert parameters on the stack into an array (based on the descriptor). If the
|
||||
* descriptor shows there are no parameters then null is stacked.
|
||||
*
|
||||
*
|
||||
* @param descriptor MethodType descriptor showing parameters and return value
|
||||
*/
|
||||
private void stackParameters(String descriptor) {
|
||||
@@ -1153,7 +1156,7 @@ public class MethodInvokerRewriter {
|
||||
|
||||
/**
|
||||
* Determine if a method call is a reflective call and an attempt should be made to rewrite it.
|
||||
*
|
||||
*
|
||||
* @return true if the call was rewritten
|
||||
*/
|
||||
private boolean rewriteReflectiveCall(int opcode, String owner, String name, String desc) {
|
||||
@@ -1269,7 +1272,7 @@ public class MethodInvokerRewriter {
|
||||
* Invokeinterface rewriting is done by calling the type registry to see if what we are about to do is OK.
|
||||
* The method we call returns a boolean indicating whether it can be called directly or if we must direct it
|
||||
* through the dynamic dispatch method.
|
||||
*
|
||||
*
|
||||
*/
|
||||
private void rewriteINVOKEINTERFACE(final int opcode, final String owner, final String name,
|
||||
final String desc,
|
||||
|
||||
Reference in New Issue
Block a user