Spring formatting rules committed and applied
This commit is contained in:
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded;
|
||||
|
||||
import java.lang.reflect.Modifier;
|
||||
@@ -26,12 +27,17 @@ import java.lang.reflect.Modifier;
|
||||
public abstract class AbstractMember implements Constants {
|
||||
|
||||
protected final int modifiers;
|
||||
|
||||
protected final String name;
|
||||
|
||||
protected final String descriptor; // this is the erased descriptor. There is no generic descriptor.
|
||||
|
||||
// Members have a well known id within their type - ids are unique per kind of member (methods/fields/constructors)
|
||||
protected int id = -1;
|
||||
|
||||
// For generic methods, contains generic signature
|
||||
protected final String signature;
|
||||
|
||||
private final boolean isPrivate; // gets asked a lot so made into a flag
|
||||
|
||||
protected AbstractMember(int modifiers, String name, String descriptor, String signature) {
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded;
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded;
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded;
|
||||
|
||||
// TODO [moredoc]
|
||||
|
||||
@@ -13,19 +13,21 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded;
|
||||
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
|
||||
/**
|
||||
* The ChildClassLoader will load the generated dispatchers and executors which change for each reload. Instances of this can be
|
||||
* discarded which will cause 'old' dispatchers/executors to be candidates for GC too (avoiding memory leaks when lots of reloads
|
||||
* occur).
|
||||
* The ChildClassLoader will load the generated dispatchers and executors which change for each reload. Instances of
|
||||
* this can be discarded which will cause 'old' dispatchers/executors to be candidates for GC too (avoiding memory leaks
|
||||
* when lots of reloads occur).
|
||||
*/
|
||||
public class ChildClassLoader extends URLClassLoader {
|
||||
|
||||
private static URL[] NO_URLS = new URL[0];
|
||||
|
||||
private int definedCount = 0;
|
||||
|
||||
public ChildClassLoader(ClassLoader classloader) {
|
||||
@@ -41,4 +43,4 @@ public class ChildClassLoader extends URLClassLoader {
|
||||
return definedCount;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded;
|
||||
|
||||
import java.util.HashMap;
|
||||
@@ -28,8 +29,8 @@ import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.Type;
|
||||
|
||||
/**
|
||||
* Modify a class by changing it from one name to another. References to other types can also be changed. Basically used in the test
|
||||
* suite.
|
||||
* Modify a class by changing it from one name to another. References to other types can also be changed. Basically used
|
||||
* in the test suite.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
@@ -37,9 +38,9 @@ import org.objectweb.asm.Type;
|
||||
public class ClassRenamer {
|
||||
|
||||
/**
|
||||
* Rename a type - changing it to specified new name (which should be the dotted form of the name). Retargets are an optional
|
||||
* sequence of retargets to also perform during the rename. Retargets take the form of "a.b:a.c" which will change all
|
||||
* references to a.b to a.c.
|
||||
* Rename a type - changing it to specified new name (which should be the dotted form of the name). Retargets are an
|
||||
* optional sequence of retargets to also perform during the rename. Retargets take the form of "a.b:a.c" which will
|
||||
* change all references to a.b to a.c.
|
||||
*
|
||||
* @param dottedNewName dotted name, e.g. com.foo.Bar
|
||||
* @param classbytes the bytecode for the class to be renamed
|
||||
@@ -57,18 +58,22 @@ public class ClassRenamer {
|
||||
static class RenameAdapter extends ClassVisitor implements Opcodes {
|
||||
|
||||
private ClassWriter cw;
|
||||
|
||||
private String oldname;
|
||||
|
||||
private String newname;
|
||||
|
||||
private Map<String, String> retargets = new HashMap<String, String>();
|
||||
|
||||
public RenameAdapter(String newname, String[] retargets) {
|
||||
super(ASM5,new ClassWriter(0));
|
||||
super(ASM5, new ClassWriter(0));
|
||||
cw = (ClassWriter) cv;
|
||||
this.newname = newname.replace('.', '/');
|
||||
if (retargets != null) {
|
||||
for (String retarget : retargets) {
|
||||
int i = retarget.indexOf(":");
|
||||
this.retargets.put(retarget.substring(0, i).replace('.', '/'), retarget.substring(i + 1).replace('.', '/'));
|
||||
this.retargets.put(retarget.substring(0, i).replace('.', '/'),
|
||||
retarget.substring(i + 1).replace('.', '/'));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -98,9 +103,10 @@ public class ClassRenamer {
|
||||
|
||||
@Override
|
||||
public void visitInnerClass(String name, String outername, String innerName, int access) {
|
||||
super.visitInnerClass(renameRetargetIfNecessary(name), renameRetargetIfNecessary(outername), renameRetargetIfNecessary(innerName), access);
|
||||
super.visitInnerClass(renameRetargetIfNecessary(name), renameRetargetIfNecessary(outername),
|
||||
renameRetargetIfNecessary(innerName), access);
|
||||
}
|
||||
|
||||
|
||||
private String renameRetargetIfNecessary(String string) {
|
||||
String value = retargets.get(string);
|
||||
if (value != null) {
|
||||
@@ -111,12 +117,14 @@ public class ClassRenamer {
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public MethodVisitor visitMethod(int flags, String name, String descriptor, String signature, String[] exceptions) {
|
||||
public MethodVisitor visitMethod(int flags, String name, String descriptor, String signature,
|
||||
String[] exceptions) {
|
||||
if (descriptor.indexOf(oldname) != -1) {
|
||||
descriptor = descriptor.replace(oldname, newname);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
if (descriptor.indexOf(oldname) != -1) {
|
||||
descriptor = descriptor.replace(oldname, newname);
|
||||
}
|
||||
@@ -134,7 +142,8 @@ public class ClassRenamer {
|
||||
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
|
||||
if (desc.indexOf(oldname) != -1) {
|
||||
desc = desc.replace(oldname, newname);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
for (String s : retargets.keySet()) {
|
||||
if (desc.indexOf(s) != -1) {
|
||||
desc = desc.replace(s, retargets.get(s));
|
||||
@@ -147,10 +156,11 @@ public class ClassRenamer {
|
||||
class RenameMethodAdapter extends MethodVisitor implements Opcodes {
|
||||
|
||||
String oldname;
|
||||
|
||||
String newname;
|
||||
|
||||
public RenameMethodAdapter(MethodVisitor mv, String oldname, String newname) {
|
||||
super(ASM5,mv);
|
||||
super(ASM5, mv);
|
||||
this.oldname = oldname;
|
||||
this.newname = newname;
|
||||
}
|
||||
@@ -158,7 +168,8 @@ public class ClassRenamer {
|
||||
public void visitFieldInsn(int opcode, String owner, String name, String desc) {
|
||||
if (owner.equals(oldname)) {
|
||||
owner = newname;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
String retarget = retargets.get(owner);
|
||||
if (retarget != null) {
|
||||
owner = retarget;
|
||||
@@ -166,7 +177,8 @@ public class ClassRenamer {
|
||||
}
|
||||
if (desc.indexOf(oldname) != -1) {
|
||||
desc = desc.replace(oldname, newname);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
desc = checkIfShouldBeRewritten(desc);
|
||||
}
|
||||
mv.visitFieldInsn(opcode, owner, name, desc);
|
||||
@@ -175,11 +187,13 @@ public class ClassRenamer {
|
||||
public void visitTypeInsn(int opcode, String type) {
|
||||
if (type.equals(oldname)) {
|
||||
type = newname;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
String retarget = retargets.get(type);
|
||||
if (retarget != null) {
|
||||
type = retarget;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
if (type.startsWith("[")) {
|
||||
if (type.indexOf(oldname) != -1) {
|
||||
type = type.replaceFirst(oldname, newname);
|
||||
@@ -192,28 +206,32 @@ public class ClassRenamer {
|
||||
|
||||
@Override
|
||||
public void visitLdcInsn(Object obj) {
|
||||
// System.out.println("Possibly remapping "+obj);
|
||||
// System.out.println("Possibly remapping "+obj);
|
||||
if (obj instanceof Type) {
|
||||
Type t = (Type) obj;
|
||||
String s = t.getInternalName();
|
||||
String retarget = retargets.get(s);
|
||||
if (retarget != null) {
|
||||
mv.visitLdcInsn(Type.getObjectType(retarget));
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
mv.visitLdcInsn(obj);
|
||||
}
|
||||
} else if (obj instanceof String) {
|
||||
}
|
||||
else if (obj instanceof String) {
|
||||
String s = (String) obj;
|
||||
String retarget = retargets.get(s.replace('.', '/'));
|
||||
if (retarget != null) {
|
||||
mv.visitLdcInsn(retarget.replace('/', '.'));
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
String oldnameDotted = oldname.replace('/', '.');
|
||||
if (s.equals(oldnameDotted)) {
|
||||
String nname = newname.replace('/', '.');
|
||||
mv.visitLdcInsn(nname);
|
||||
return;
|
||||
} else if (s.startsWith("[")) {
|
||||
}
|
||||
else if (s.startsWith("[")) {
|
||||
// might be array of oldname
|
||||
if (s.indexOf(oldnameDotted) != -1) {
|
||||
mv.visitLdcInsn(s.replaceFirst(oldnameDotted, newname.replace('/', '.')));
|
||||
@@ -222,21 +240,23 @@ public class ClassRenamer {
|
||||
}
|
||||
mv.visitLdcInsn(obj);
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
mv.visitLdcInsn(obj);
|
||||
}
|
||||
}
|
||||
|
||||
private String toString(Handle bsm) {
|
||||
return "["+bsm.getTag()+"]"+bsm.getOwner()+"."+bsm.getName()+bsm.getDesc();
|
||||
return "[" + bsm.getTag() + "]" + bsm.getOwner() + "." + bsm.getName() + bsm.getDesc();
|
||||
}
|
||||
|
||||
|
||||
private String toString(Object[] os) {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
if (os!=null) {
|
||||
if (os != null) {
|
||||
buf.append("[");
|
||||
for (int i=0;i<os.length;i++) {
|
||||
if (i>0) buf.append(",");
|
||||
for (int i = 0; i < os.length; i++) {
|
||||
if (i > 0)
|
||||
buf.append(",");
|
||||
buf.append(os[i]);
|
||||
}
|
||||
buf.append("]");
|
||||
@@ -246,21 +266,21 @@ public class ClassRenamer {
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
|
||||
private Handle retargetHandle(Handle oldHandle) {
|
||||
int tag = oldHandle.getTag();
|
||||
String owner = oldHandle.getOwner();
|
||||
String name = oldHandle.getName();
|
||||
String desc = oldHandle.getDesc();
|
||||
// System.out.println("handle: owner: "+owner);
|
||||
// System.out.println("handle: name: "+name);
|
||||
// System.out.println("handle: desc: "+desc);
|
||||
// System.out.println("handle: owner: "+owner);
|
||||
// System.out.println("handle: name: "+name);
|
||||
// System.out.println("handle: desc: "+desc);
|
||||
owner = renameRetargetIfNecessary(owner);
|
||||
desc = renameRetargetIfNecessary(desc);
|
||||
Handle newHandle = new Handle(tag,owner,name,desc);
|
||||
Handle newHandle = new Handle(tag, owner, name, desc);
|
||||
return newHandle;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) {
|
||||
// Example:
|
||||
@@ -269,20 +289,22 @@ public class ClassRenamer {
|
||||
// bsmArgs=[()I,basic/LambdaA2.lambda$run$1()I (6),()I])
|
||||
desc = renameRetargetIfNecessary(desc);
|
||||
if (bsmArgs[1] instanceof Handle) {
|
||||
bsmArgs[1] = retargetHandle((Handle)bsmArgs[1]);
|
||||
bsmArgs[1] = retargetHandle((Handle) bsmArgs[1]);
|
||||
}
|
||||
mv.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs);
|
||||
mv.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs);
|
||||
}
|
||||
|
||||
|
||||
public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
|
||||
if (owner.equals(oldname)) {
|
||||
owner = newname;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
owner = retargetIfNecessary(owner);
|
||||
}
|
||||
if (desc.indexOf(oldname) != -1) {
|
||||
desc = desc.replace(oldname, newname);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
desc = checkIfShouldBeRewritten(desc);
|
||||
}
|
||||
mv.visitMethodInsn(opcode, owner, name, desc, itf);
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
@@ -25,8 +26,8 @@ import java.util.List;
|
||||
|
||||
// http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html
|
||||
/**
|
||||
* Quickly checks the constant pool for class references, it skips everything else as fast as it can. The class references are then
|
||||
* available for checking.
|
||||
* Quickly checks the constant pool for class references, it skips everything else as fast as it can. The class
|
||||
* references are then available for checking.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.7.3
|
||||
@@ -36,15 +37,25 @@ public class ConstantPoolChecker {
|
||||
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;
|
||||
|
||||
// Test entry point just goes through all the code in the bin folder
|
||||
@@ -97,8 +108,11 @@ public class ConstantPoolChecker {
|
||||
|
||||
// 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>();
|
||||
|
||||
@@ -110,14 +124,14 @@ public class ConstantPoolChecker {
|
||||
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_Class:
|
||||
int classindex = ((Integer) cpdata[i]);
|
||||
String classname = (String) cpdata[classindex];
|
||||
if (classname == null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
referencedClasses.add(classname);
|
||||
break;
|
||||
// private final static byte CONSTANT_Utf8 = 1;
|
||||
// private final static byte CONSTANT_Integer = 3;
|
||||
// private final static byte CONSTANT_Float = 4;
|
||||
@@ -157,7 +171,8 @@ public class ConstantPoolChecker {
|
||||
cpentry++;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new IllegalStateException("Unexpected problem processing bytes for class", e);
|
||||
}
|
||||
}
|
||||
@@ -166,166 +181,178 @@ public class ConstantPoolChecker {
|
||||
byte b = dis.readByte();
|
||||
type[index] = b;
|
||||
switch (b) {
|
||||
case CONSTANT_Utf8:
|
||||
// CONSTANT_Utf8_info {
|
||||
// u1 tag;
|
||||
// u2 length;
|
||||
// u1 bytes[length];
|
||||
// }
|
||||
cpdata[index] = dis.readUTF();
|
||||
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();
|
||||
case CONSTANT_Utf8:
|
||||
// CONSTANT_Utf8_info {
|
||||
// u1 tag;
|
||||
// u2 length;
|
||||
// u1 bytes[length];
|
||||
// }
|
||||
cpdata[index] = dis.readUTF();
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":INTEGER[" + i + "]");
|
||||
System.out.println(index + ":UTF8[" + cpdata[index] + "]");
|
||||
}
|
||||
} else {
|
||||
dis.skip(4);
|
||||
}
|
||||
break;
|
||||
case CONSTANT_Float:
|
||||
// CONSTANT_Float_info {
|
||||
// u1 tag;
|
||||
// u4 bytes;
|
||||
// }
|
||||
if (DEBUG) {
|
||||
float f = dis.readFloat();
|
||||
break;
|
||||
case CONSTANT_Integer:
|
||||
// CONSTANT_Integer_info {
|
||||
// u1 tag;
|
||||
// u4 bytes;
|
||||
// }
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":FLOAT[" + f + "]");
|
||||
int i = dis.readInt();
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":INTEGER[" + i + "]");
|
||||
}
|
||||
}
|
||||
} 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();
|
||||
else {
|
||||
dis.skip(4);
|
||||
}
|
||||
break;
|
||||
case CONSTANT_Float:
|
||||
// CONSTANT_Float_info {
|
||||
// u1 tag;
|
||||
// u4 bytes;
|
||||
// }
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":LONG[" + l + "]");
|
||||
float f = dis.readFloat();
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":FLOAT[" + f + "]");
|
||||
}
|
||||
}
|
||||
} 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();
|
||||
else {
|
||||
dis.skip(4);
|
||||
}
|
||||
break;
|
||||
case CONSTANT_Long:
|
||||
// CONSTANT_Long_info {
|
||||
// u1 tag;
|
||||
// u4 high_bytes;
|
||||
// u4 low_bytes;
|
||||
// }
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":DOUBLE[" + d + "]");
|
||||
long l = dis.readLong();
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":LONG[" + l + "]");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
dis.skip(8);
|
||||
}
|
||||
return true;
|
||||
case CONSTANT_Class:
|
||||
// CONSTANT_Class_info {
|
||||
// u1 tag;
|
||||
// u2 name_index;
|
||||
// }
|
||||
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) {
|
||||
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;
|
||||
// }
|
||||
cpdata[index] = (int) dis.readShort();
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":STRING[string_index=" + cpdata[index] + "]");
|
||||
System.out.println(index + ":CLASS[name_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() };
|
||||
break;
|
||||
case CONSTANT_String:
|
||||
// CONSTANT_String_info {
|
||||
// u1 tag;
|
||||
// u2 string_index;
|
||||
// }
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":FIELDREF[class_index=" + ((int[]) cpdata[index])[0] + ",name_and_type_index="
|
||||
+ ((int[]) cpdata[index])[1] + "]");
|
||||
cpdata[index] = (int) dis.readShort();
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":STRING[string_index=" + cpdata[index] + "]");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
dis.skip(4);
|
||||
}
|
||||
break;
|
||||
case CONSTANT_Methodref:
|
||||
// CONSTANT_Methodref_info {
|
||||
// u1 tag;
|
||||
// u2 class_index;
|
||||
// u2 name_and_type_index;
|
||||
// }
|
||||
if (DEBUG) {
|
||||
cpdata[index] = new int[] { dis.readShort(), dis.readShort() };
|
||||
else {
|
||||
dis.skip(2);
|
||||
}
|
||||
break;
|
||||
case CONSTANT_Fieldref:
|
||||
// CONSTANT_Fieldref_info {
|
||||
// u1 tag;
|
||||
// u2 class_index;
|
||||
// u2 name_and_type_index;
|
||||
// }
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":METHODREF[class_index=" + ((int[]) cpdata[index])[0] + ",name_and_type_index="
|
||||
+ ((int[]) cpdata[index])[1] + "]");
|
||||
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_InterfaceMethodref:
|
||||
// CONSTANT_InterfaceMethodref_info {
|
||||
// u1 tag;
|
||||
// u2 class_index;
|
||||
// u2 name_and_type_index;
|
||||
// }
|
||||
if (DEBUG) {
|
||||
cpdata[index] = new int[] { dis.readShort(), dis.readShort() };
|
||||
else {
|
||||
dis.skip(4);
|
||||
}
|
||||
break;
|
||||
case CONSTANT_Methodref:
|
||||
// CONSTANT_Methodref_info {
|
||||
// u1 tag;
|
||||
// u2 class_index;
|
||||
// u2 name_and_type_index;
|
||||
// }
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":INTERFACEMETHODREF[class_index=" + ((int[]) cpdata[index])[0]
|
||||
+ ",name_and_type_index=" + ((int[]) cpdata[index])[1] + "]");
|
||||
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_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;
|
||||
// }
|
||||
if (DEBUG) {
|
||||
cpdata[index] = new int[] { dis.readShort(), dis.readShort() };
|
||||
else {
|
||||
dis.skip(4);
|
||||
}
|
||||
break;
|
||||
case CONSTANT_InterfaceMethodref:
|
||||
// CONSTANT_InterfaceMethodref_info {
|
||||
// u1 tag;
|
||||
// u2 class_index;
|
||||
// u2 name_and_type_index;
|
||||
// }
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":NAMEANDTYPE[name_index=" + ((int[]) cpdata[index])[0] + ",descriptor_index="
|
||||
+ ((int[]) cpdata[index])[1] + "]");
|
||||
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;
|
||||
default:
|
||||
throw new IllegalStateException("Entry: " + index + " " + Byte.toString(b));
|
||||
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;
|
||||
// }
|
||||
if (DEBUG) {
|
||||
cpdata[index] = new int[] { dis.readShort(), dis.readShort() };
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":NAMEANDTYPE[name_index=" + ((int[]) cpdata[index])[0]
|
||||
+ ",descriptor_index="
|
||||
+ ((int[]) cpdata[index])[1] + "]");
|
||||
}
|
||||
}
|
||||
else {
|
||||
dis.skip(4);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Entry: " + index + " " + Byte.toString(b));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
@@ -27,10 +28,11 @@ import java.util.List;
|
||||
|
||||
// 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.
|
||||
* 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
|
||||
@@ -40,18 +42,31 @@ 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
|
||||
@@ -68,7 +83,8 @@ public class ConstantPoolChecker2 {
|
||||
for (File f : fs) {
|
||||
if (f.isDirectory()) {
|
||||
checkThemAll(f.listFiles());
|
||||
} else if (f.getName().endsWith(".class")) {
|
||||
}
|
||||
else if (f.getName().endsWith(".class")) {
|
||||
System.out.println(f);
|
||||
byte[] data = Utils.loadFromStream(new FileInputStream(f));
|
||||
long stime = System.nanoTime();
|
||||
@@ -107,8 +123,11 @@ public class ConstantPoolChecker2 {
|
||||
}
|
||||
|
||||
static class References {
|
||||
|
||||
String slashedClassName;
|
||||
|
||||
List<String> referencedClasses;
|
||||
|
||||
List<String> referencedMethods;
|
||||
|
||||
References(String slashedClassName, List<String> rc, List<String> rm) {
|
||||
@@ -120,11 +139,16 @@ public class ConstantPoolChecker2 {
|
||||
|
||||
// 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) {
|
||||
@@ -135,27 +159,27 @@ public class ConstantPoolChecker2 {
|
||||
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;
|
||||
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;
|
||||
@@ -195,7 +219,8 @@ public class ConstantPoolChecker2 {
|
||||
int thisclassname = dis.readShort();
|
||||
int classindex = ((Integer) cpdata[thisclassname]);
|
||||
slashedclassname = (String) cpdata[classindex];
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new IllegalStateException("Unexpected problem processing bytes for class", e);
|
||||
}
|
||||
}
|
||||
@@ -203,162 +228,172 @@ public class ConstantPoolChecker2 {
|
||||
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();
|
||||
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 + ":INTEGER[" + i + "]");
|
||||
System.out.println(index + ":UTF8[" + cpdata[index] + "]");
|
||||
}
|
||||
} else {
|
||||
dis.skip(4);
|
||||
}
|
||||
break;
|
||||
case CONSTANT_Float:
|
||||
// CONSTANT_Float_info { u1 tag; u4 bytes; }
|
||||
if (DEBUG) {
|
||||
float f = dis.readFloat();
|
||||
break;
|
||||
case CONSTANT_Integer:
|
||||
// CONSTANT_Integer_info { u1 tag; u4 bytes; }
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":FLOAT[" + f + "]");
|
||||
int i = dis.readInt();
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":INTEGER[" + i + "]");
|
||||
}
|
||||
}
|
||||
} 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();
|
||||
else {
|
||||
dis.skip(4);
|
||||
}
|
||||
break;
|
||||
case CONSTANT_Float:
|
||||
// CONSTANT_Float_info { u1 tag; u4 bytes; }
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":LONG[" + l + "]");
|
||||
float f = dis.readFloat();
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":FLOAT[" + f + "]");
|
||||
}
|
||||
}
|
||||
} 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();
|
||||
else {
|
||||
dis.skip(4);
|
||||
}
|
||||
break;
|
||||
case CONSTANT_Long:
|
||||
// CONSTANT_Long_info {
|
||||
// u1 tag;
|
||||
// u4 high_bytes;
|
||||
// u4 low_bytes;
|
||||
// }
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":DOUBLE[" + d + "]");
|
||||
long l = dis.readLong();
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":LONG[" + l + "]");
|
||||
}
|
||||
}
|
||||
} 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) {
|
||||
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 + ":STRING[string_index=" + cpdata[index] + "]");
|
||||
System.out.println(index + ":CLASS[name_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) {
|
||||
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 + ":FIELDREF[class_index=" + ((int[]) cpdata[index])[0] + ",name_and_type_index="
|
||||
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_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() };
|
||||
// } else {
|
||||
// dis.skip(4);
|
||||
// }
|
||||
break;
|
||||
case CONSTANT_InterfaceMethodref:
|
||||
// CONSTANT_InterfaceMethodref_info {
|
||||
// u1 tag;
|
||||
// u2 class_index;
|
||||
// u2 name_and_type_index;
|
||||
// }
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":INTERFACEMETHODREF[class_index=" + ((int[]) cpdata[index])[0]
|
||||
+ ",name_and_type_index=" + ((int[]) cpdata[index])[1] + "]");
|
||||
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));
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
@@ -30,12 +31,19 @@ import org.objectweb.asm.Opcodes;
|
||||
public interface Constants extends Opcodes {
|
||||
|
||||
public static final Integer DEFAULT_INT = Integer.valueOf(0);
|
||||
|
||||
public static final Byte DEFAULT_BYTE = Byte.valueOf((byte) 0);
|
||||
|
||||
public static final Character DEFAULT_CHAR = Character.valueOf((char) 0);
|
||||
|
||||
public static final Short DEFAULT_SHORT = Short.valueOf((short) 0);
|
||||
|
||||
public static final Long DEFAULT_LONG = Long.valueOf(0);
|
||||
|
||||
public static final Float DEFAULT_FLOAT = Float.valueOf(0);
|
||||
|
||||
public static final Double DEFAULT_DOUBLE = Double.valueOf(0);
|
||||
|
||||
public static final Boolean DEFAULT_BOOLEAN = Boolean.FALSE;
|
||||
|
||||
static String magicDescriptorForGeneratedCtors = "org.springsource.loaded.C";
|
||||
@@ -44,74 +52,111 @@ public interface Constants extends Opcodes {
|
||||
static String PREFIX = "r$";
|
||||
|
||||
static String tRegistryType = "org/springsource/loaded/TypeRegistry";
|
||||
|
||||
static String lRegistryType = "L" + tRegistryType + ";";
|
||||
|
||||
static String tDynamicallyDispatchable = "org/springsource/loaded/__DynamicallyDispatchable";
|
||||
|
||||
static String lDynamicallyDispatchable = "L" + tDynamicallyDispatchable + ";";
|
||||
|
||||
static String tReloadableType = "org/springsource/loaded/ReloadableType";
|
||||
|
||||
static String lReloadableType = "L" + tReloadableType + ";";
|
||||
|
||||
static String tInstanceStateManager = "org/springsource/loaded/ISMgr";
|
||||
|
||||
static String lInstanceStateManager = "L" + tInstanceStateManager + ";";
|
||||
|
||||
static String tStaticStateManager = "org/springsource/loaded/SSMgr";
|
||||
|
||||
static String lStaticStateManager = "L" + tStaticStateManager + ";";
|
||||
|
||||
static String fReloadableTypeFieldName = PREFIX + "type";
|
||||
|
||||
// Static field holding map and accessors
|
||||
static String fStaticFieldsName = PREFIX + "sfields";
|
||||
|
||||
static String mStaticFieldSetterName = PREFIX + "sets";
|
||||
|
||||
static String mStaticFieldSetterDescriptor = "(Ljava/lang/Object;Ljava/lang/String;)V";
|
||||
|
||||
static String mStaticFieldGetterName = PREFIX + "gets";
|
||||
|
||||
// Instance field holding map and accessors
|
||||
static String fInstanceFieldsName = PREFIX + "fields";
|
||||
|
||||
static String mInstanceFieldSetterName = PREFIX + "set";
|
||||
|
||||
static String mInstanceFieldSetterDescriptor = "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/String;)V";
|
||||
|
||||
static String mInstanceFieldGetterName = PREFIX + "get";
|
||||
|
||||
static String mInstanceFieldGetterDescriptor = "(Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;";
|
||||
|
||||
static String mStaticFieldInterceptionRequired = "staticFieldInterceptionRequired";
|
||||
|
||||
static String mInstanceFieldInterceptionRequired = "instanceFieldInterceptionRequired";
|
||||
|
||||
// method called to see if the target of what is about to be called has changed
|
||||
static String mChangedForInvocationName = "anyChanges";
|
||||
|
||||
static String mChangedForInvokeStaticName = "istcheck";
|
||||
|
||||
static String mChangedForInvokeInterfaceName = "iincheck";
|
||||
|
||||
static String mChangedForInvokeDynamicName = "idycheck";
|
||||
|
||||
static String mChangedForInvokeVirtualName = "ivicheck";
|
||||
|
||||
static String mChangedForInvokeSpecialName = "ispcheck";
|
||||
|
||||
static String mPerformInvokeDynamicName = "idyrun";
|
||||
|
||||
static String descriptorChangedForInvokeSpecialName = "(ILjava/lang/String;)Lorg/springsource/loaded/__DynamicallyDispatchable;";
|
||||
|
||||
static String mChangedForConstructorName = "ccheck";
|
||||
|
||||
static int WAS_INVOKESTATIC = 0x0001;
|
||||
|
||||
static int WAS_INVOKEVIRTUAL = 0x0002;
|
||||
|
||||
// Dynamic dispatch method
|
||||
static String mDynamicDispatchName = "__execute";
|
||||
|
||||
static String mDynamicDispatchDescriptor = "([Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;";
|
||||
|
||||
static String mInitializerName = "___init___";
|
||||
|
||||
static String mStaticInitializerName = "___clinit___";
|
||||
|
||||
static int ACC_PUBLIC_ABSTRACT = Opcodes.ACC_PUBLIC | Opcodes.ACC_ABSTRACT;
|
||||
|
||||
static int ACC_PRIVATE_STATIC = Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC;
|
||||
|
||||
static int ACC_PUBLIC_STATIC = Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC;
|
||||
|
||||
static int ACC_PUBLIC_STATIC_FINAL = Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL;
|
||||
|
||||
static int ACC_PUBLIC_INTERFACE = Opcodes.ACC_PUBLIC | Opcodes.ACC_INTERFACE | Opcodes.ACC_ABSTRACT;
|
||||
|
||||
static int ACC_PUBLIC_STATIC_SYNTHETIC = Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC;
|
||||
|
||||
static int ACC_PUBLIC_SYNTHETIC = Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC;
|
||||
|
||||
static int ACC_PUBLIC_PROTECTED = Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED;
|
||||
|
||||
static int ACC_PUBLIC_PRIVATE_PROTECTED = Opcodes.ACC_PUBLIC | Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED;
|
||||
|
||||
static int ACC_PRIVATE_STATIC_SYNTHETIC = Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC;
|
||||
|
||||
static int ACC_PRIVATE_PROTECTED = Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED;
|
||||
|
||||
static int ACC_PRIVATE_STATIC_FINAL = ACC_FINAL | ACC_STATIC | ACC_PRIVATE;
|
||||
|
||||
static String[] NO_STRINGS = new String[0];
|
||||
|
||||
static Method[] NO_METHODS = new Method[0];
|
||||
|
||||
static Field[] NO_FIELDS = new Field[0];
|
||||
|
||||
//Name pattern used to recognise names of Executor classes.
|
||||
@@ -121,54 +166,95 @@ public interface Constants extends Opcodes {
|
||||
|
||||
//
|
||||
public static int JLC_GETDECLAREDFIELDS = 0x0001;
|
||||
|
||||
public static int JLC_GETDECLAREDFIELD = 0x0002;
|
||||
|
||||
public static int JLC_GETFIELD = 0x0004;
|
||||
|
||||
public static int JLC_GETDECLAREDMETHODS = 0x0008;
|
||||
|
||||
public static int JLC_GETDECLAREDMETHOD = 0x0010;
|
||||
|
||||
public static int JLC_GETMETHOD = 0x0020;
|
||||
|
||||
public static int JLC_GETDECLAREDCONSTRUCTOR = 0x0040;
|
||||
|
||||
public static int JLC_GETMODIFIERS = 0x0080;
|
||||
|
||||
public static int JLC_GETMETHODS = 0x0100;
|
||||
|
||||
public static int JLC_GETCONSTRUCTOR = 0x0200;
|
||||
|
||||
public static int JLC_GETDECLAREDCONSTRUCTORS = 0x0400;
|
||||
|
||||
public static int JLRM_INVOKE = 0x0800;
|
||||
|
||||
public static int JLRF_GET = 0x1000;
|
||||
|
||||
public static int JLRF_GETLONG = 0x2000;
|
||||
|
||||
public static int JLOS_HASSTATICINITIALIZER = 0x4000;
|
||||
|
||||
// For rewritten reflection in system classes, these are used:
|
||||
// The member names are used for fields *and* methods
|
||||
static final String jlcgdfs = "__sljlcgdfs";
|
||||
|
||||
static final String jlcgdfsDescriptor = "(Ljava/lang/Class;)[Ljava/lang/reflect/Field;";
|
||||
|
||||
static final String jlcgdf = "__sljlcgdf";
|
||||
|
||||
static final String jlcgdfDescriptor = "(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/reflect/Field;";
|
||||
|
||||
static final String jlcgf = "__sljlcgf";
|
||||
|
||||
static final String jlcgfDescriptor = "(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/reflect/Field;";
|
||||
|
||||
static final String jlcgdms = "__sljlcgdms";
|
||||
|
||||
static final String jlcgdmsDescriptor = "(Ljava/lang/Class;)[Ljava/lang/reflect/Method;";
|
||||
|
||||
static final String jlcgdm = "__sljlcgdm";
|
||||
|
||||
static final String jlcgdmDescriptor = "(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;";
|
||||
|
||||
static final String jlcgm = "__sljlcgm";
|
||||
|
||||
static final String jlcgmDescriptor = "(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;";
|
||||
|
||||
static final String jlcgdc = "__sljlcgdc";
|
||||
|
||||
static final String jlcgdcDescriptor = "(Ljava/lang/Class;[Ljava/lang/Class;)Ljava/lang/reflect/Constructor;";
|
||||
|
||||
static final String jlcgc = "__sljlcgc";
|
||||
|
||||
static final String jlcgcDescriptor = "(Ljava/lang/Class;[Ljava/lang/Class;)Ljava/lang/reflect/Constructor;";
|
||||
|
||||
static final String jlcgmods = "__sljlcgmods";
|
||||
|
||||
static final String jlcgmodsDescriptor = "(Ljava/lang/Class;)I";
|
||||
|
||||
static final String jlcgms = "__sljlcgms";
|
||||
|
||||
static final String jlcgmsDescriptor = "(Ljava/lang/Class;)[Ljava/lang/reflect/Method;";
|
||||
|
||||
|
||||
// TODO migrate those above to this slightly more comprehensible format
|
||||
static final String jlcGetDeclaredConstructorsMember = "__sljlcgdcs";
|
||||
|
||||
static final String jlcGetDeclaredConstructorsDescriptor = "(Ljava/lang/Class;)[Ljava/lang/reflect/Constructor;";
|
||||
|
||||
static final String jlrmInvokeMember = "__sljlrmi";
|
||||
|
||||
static final String jlrmInvokeDescriptor = "(Ljava/lang/reflect/Method;Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;";
|
||||
|
||||
static final String jlrfGetMember = "__sljlrfg";
|
||||
|
||||
static final String jlrfGetDescriptor = "(Ljava/lang/reflect/Field;Ljava/lang/Object;)Ljava/lang/Object;";
|
||||
|
||||
static final String jlrfGetLongMember = "__sljlrfgl";
|
||||
|
||||
static final String jlrfGetLongDescriptor = "(Ljava/lang/reflect/Field;Ljava/lang/Object;)J";
|
||||
|
||||
static final String jloObjectStream_hasInitializerMethod = "__sljlos_him";
|
||||
|
||||
|
||||
static final String methodSuffixSuperDispatcher = "_$superdispatcher$";
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded;
|
||||
|
||||
import org.objectweb.asm.Label;
|
||||
@@ -25,6 +26,7 @@ import org.objectweb.asm.MethodVisitor;
|
||||
class ConstructorCopier extends MethodVisitor implements Constants {
|
||||
|
||||
private final static int preInvokeSpecial = 0;
|
||||
|
||||
private final static int postInvokeSpecial = 1;
|
||||
|
||||
// It is important to know when an INVOKESPECIAL is hit, whether it is our actual one that delegates to the super or just
|
||||
@@ -32,13 +34,17 @@ class ConstructorCopier extends MethodVisitor implements Constants {
|
||||
// how many unitialized objects there are (count the NEWs) and how many INVOKESPECIALs have occurred, it is possible
|
||||
// to identify the right one.
|
||||
private int state = preInvokeSpecial;
|
||||
|
||||
private int unitializedObjectsCount = 0;
|
||||
|
||||
private TypeDescriptor typeDescriptor;
|
||||
|
||||
private String suffix;
|
||||
|
||||
private String classname;
|
||||
|
||||
public ConstructorCopier(MethodVisitor mv, TypeDescriptor typeDescriptor, String suffix, String classname) {
|
||||
super(ASM5,mv);
|
||||
super(ASM5, mv);
|
||||
this.typeDescriptor = typeDescriptor;
|
||||
this.suffix = suffix;
|
||||
this.classname = classname;
|
||||
@@ -49,7 +55,8 @@ class ConstructorCopier extends MethodVisitor implements Constants {
|
||||
// Rename 'this' to 'thiz' in executor otherwise Eclipse debugger will fail (static method with 'this')
|
||||
if (index == 0 && name.equals("this")) {
|
||||
super.visitLocalVariable("thiz", desc, signature, start, end, index);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
super.visitLocalVariable(name, desc, signature, start, end, index);
|
||||
}
|
||||
}
|
||||
@@ -74,13 +81,15 @@ class ConstructorCopier extends MethodVisitor implements Constants {
|
||||
if (opcode == INVOKESPECIAL && name.charAt(0) == '<') {
|
||||
if (unitializedObjectsCount != 0) {
|
||||
unitializedObjectsCount--;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
// This looks like our INVOKESPECIAL
|
||||
if (state == preInvokeSpecial) {
|
||||
// special case for calling jlObject, do nothing!
|
||||
if (owner.equals("java/lang/Object")) {
|
||||
mv.visitInsn(POP);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
// Need to replace this INVOKESPECIAL call.
|
||||
String supertypename = typeDescriptor.getSupertypeName();
|
||||
ReloadableType superRtype = typeDescriptor.getReloadableType().getTypeRegistry()
|
||||
@@ -101,10 +110,12 @@ class ConstructorCopier extends MethodVisitor implements Constants {
|
||||
}
|
||||
Utils.insertPopsForAllParameters(mv, desc);
|
||||
mv.visitInsn(POP); // pop 'this'
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
// Check the original form of the supertype for a constructor to call
|
||||
MethodMember existingCtor = (superRtype == null ? null : superRtype.getTypeDescriptor().getConstructor(
|
||||
desc));
|
||||
MethodMember existingCtor = (superRtype == null ? null
|
||||
: superRtype.getTypeDescriptor().getConstructor(
|
||||
desc));
|
||||
if (existingCtor == null) {
|
||||
// It did not exist in the original supertype version, need to use dynamic dispatch method
|
||||
// collapse the arguments on the stack
|
||||
@@ -114,12 +125,15 @@ class ConstructorCopier extends MethodVisitor implements Constants {
|
||||
mv.visitInsn(DUP_X1);
|
||||
// no stack is instance then params then instance
|
||||
mv.visitLdcInsn("<init>" + desc);
|
||||
mv.visitMethodInsn(INVOKESPECIAL, typeDescriptor.getSupertypeName(), mDynamicDispatchName,
|
||||
mv.visitMethodInsn(INVOKESPECIAL, typeDescriptor.getSupertypeName(),
|
||||
mDynamicDispatchName,
|
||||
mDynamicDispatchDescriptor, false);
|
||||
mv.visitInsn(POP);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
// it did exist in the original, so there will be parallel constructor
|
||||
mv.visitMethodInsn(INVOKESPECIAL, typeDescriptor.getSupertypeName(), mInitializerName, desc, false);
|
||||
mv.visitMethodInsn(INVOKESPECIAL, typeDescriptor.getSupertypeName(), mInitializerName,
|
||||
desc, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -136,7 +150,8 @@ class ConstructorCopier extends MethodVisitor implements Constants {
|
||||
// leaving the invokespecial alone will cause a verify error
|
||||
String descriptor = Utils.insertExtraParameter(owner, desc);
|
||||
super.visitMethodInsn(INVOKESTATIC, Utils.getExecutorName(classname, suffix), name, descriptor, false);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
boolean done = false;
|
||||
// TODO dup of code in method copier - can we refactor?
|
||||
if (opcode == INVOKESTATIC) {
|
||||
@@ -151,4 +166,4 @@ class ConstructorCopier extends MethodVisitor implements Constants {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
@@ -45,24 +46,31 @@ public class CurrentLiveVersion {
|
||||
public final IncrementalTypeDescriptor incrementalTypeDescriptor;
|
||||
|
||||
String dispatcherName;
|
||||
|
||||
byte[] dispatcher;
|
||||
|
||||
Class<?> dispatcherClass;
|
||||
|
||||
Object dispatcherInstance;
|
||||
|
||||
String executorName;
|
||||
|
||||
byte[] executor;
|
||||
|
||||
Class<?> executorClass;
|
||||
|
||||
TypeDelta typeDelta;
|
||||
|
||||
private Method staticInitializer;
|
||||
|
||||
private boolean haveLookedForStaticInitializer;
|
||||
|
||||
public boolean staticInitializedNeedsRerunningOnDefine = false;
|
||||
|
||||
public CurrentLiveVersion(ReloadableType reloadableType, String versionstamp, byte[] newbytedata) {
|
||||
if (GlobalConfiguration.logging && log.isLoggable(Level.FINER)) {
|
||||
log.entering("CurrentLiveVersion", "<init>", " new version of " + reloadableType.getName() + " loaded, version stamp '"
|
||||
log.entering("CurrentLiveVersion", "<init>", " new version of " + reloadableType.getName()
|
||||
+ " loaded, version stamp '"
|
||||
+ versionstamp + "'");
|
||||
}
|
||||
this.reloadableType = reloadableType;
|
||||
@@ -71,19 +79,22 @@ public class CurrentLiveVersion {
|
||||
|
||||
if (GlobalConfiguration.assertsMode) {
|
||||
if (!this.typeDescriptor.getName().equals(reloadableType.typedescriptor.getName())) {
|
||||
throw new IllegalStateException("New version has wrong name. Expected " + reloadableType.typedescriptor.getName()
|
||||
throw new IllegalStateException("New version has wrong name. Expected "
|
||||
+ reloadableType.typedescriptor.getName()
|
||||
+ " but was " + typeDescriptor.getName());
|
||||
}
|
||||
}
|
||||
|
||||
newbytedata = GlobalConfiguration.callsideRewritingOn ? MethodInvokerRewriter.rewrite(reloadableType.typeRegistry,
|
||||
newbytedata = GlobalConfiguration.callsideRewritingOn ? MethodInvokerRewriter.rewrite(
|
||||
reloadableType.typeRegistry,
|
||||
newbytedata) : newbytedata;
|
||||
|
||||
this.incrementalTypeDescriptor = new IncrementalTypeDescriptor(reloadableType.typedescriptor);
|
||||
this.incrementalTypeDescriptor.setLatestTypeDescriptor(this.typeDescriptor);
|
||||
|
||||
// Executors for interfaces simply hold annotations
|
||||
this.executor = reloadableType.getTypeRegistry().executorBuilder.createFor(reloadableType, versionstamp, typeDescriptor,
|
||||
this.executor = reloadableType.getTypeRegistry().executorBuilder.createFor(reloadableType, versionstamp,
|
||||
typeDescriptor,
|
||||
newbytedata);
|
||||
|
||||
if (GlobalConfiguration.classesToDump != null
|
||||
@@ -100,8 +111,8 @@ public class CurrentLiveVersion {
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines this version. Called up front but can also be called later if the ChildClassLoader in a type registry is discarded
|
||||
* and recreated.
|
||||
* Defines this version. Called up front but can also be called later if the ChildClassLoader in a type registry is
|
||||
* discarded and recreated.
|
||||
*/
|
||||
public void define() {
|
||||
staticInitializer = null;
|
||||
@@ -109,31 +120,37 @@ public class CurrentLiveVersion {
|
||||
if (!typeDescriptor.isInterface()) {
|
||||
try {
|
||||
dispatcherClass = reloadableType.typeRegistry.defineClass(dispatcherName, dispatcher, false);
|
||||
} catch (RuntimeException t) {
|
||||
}
|
||||
catch (RuntimeException t) {
|
||||
// TODO check for something strange. something to do with the file detection misbehaving, see the same file attempted to be reloaded twice...
|
||||
if (t.getMessage().indexOf("duplicate class definition") == -1) {
|
||||
throw t;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
t.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
executorClass = reloadableType.typeRegistry.defineClass(executorName, executor, false);
|
||||
} catch (RuntimeException t) {
|
||||
}
|
||||
catch (RuntimeException t) {
|
||||
// TODO check for something strange. something to do with the file detection misbehaving, see the same file attempted to be reloaded twice...
|
||||
if (t.getMessage().indexOf("duplicate class definition") == -1) {
|
||||
throw t;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
t.printStackTrace();
|
||||
}
|
||||
}
|
||||
if (!typeDescriptor.isInterface()) {
|
||||
try {
|
||||
dispatcherInstance = dispatcherClass.newInstance();
|
||||
} catch (InstantiationException e) {
|
||||
}
|
||||
catch (InstantiationException e) {
|
||||
throw new RuntimeException("Unable to build dispatcher class instance", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
}
|
||||
catch (IllegalAccessException e) {
|
||||
throw new RuntimeException("Unable to build dispatcher class instance", e);
|
||||
}
|
||||
}
|
||||
@@ -160,7 +177,8 @@ public class CurrentLiveVersion {
|
||||
//What to search for:
|
||||
if (methodMember.isConstructor()) {
|
||||
name = Constants.mInitializerName;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
name = methodMember.getName();
|
||||
}
|
||||
executorDescriptor = getExecutorDescriptor(methodMember);
|
||||
@@ -185,13 +203,15 @@ public class CurrentLiveVersion {
|
||||
System.arraycopy(params, 0, newParametersArray, 1, params.length);
|
||||
newParametersArray[0] = Type.getType(reloadableType.getClazz());
|
||||
}
|
||||
String executorDescriptor = Type.getMethodDescriptor(Type.getReturnType(methodMember.getDescriptor()), newParametersArray);
|
||||
String executorDescriptor = Type.getMethodDescriptor(Type.getReturnType(methodMember.getDescriptor()),
|
||||
newParametersArray);
|
||||
return executorDescriptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CurrentLiveVersion [reloadableType=" + reloadableType + ", typeDescriptor=" + typeDescriptor + ", versionstamp="
|
||||
return "CurrentLiveVersion [reloadableType=" + reloadableType + ", typeDescriptor=" + typeDescriptor
|
||||
+ ", versionstamp="
|
||||
+ versionstamp + ", dispatcherName=" + dispatcherName + ", executorName=" + executorName + "]";
|
||||
}
|
||||
|
||||
@@ -277,7 +297,8 @@ public class CurrentLiveVersion {
|
||||
if (!haveLookedForStaticInitializer) {
|
||||
try {
|
||||
staticInitializer = this.getExecutorClass().getDeclaredMethod(Constants.mStaticInitializerName);
|
||||
} catch (NoSuchMethodException e) {
|
||||
}
|
||||
catch (NoSuchMethodException e) {
|
||||
// some types don't have a static initializer, that is OK
|
||||
}
|
||||
haveLookedForStaticInitializer = true;
|
||||
@@ -285,8 +306,10 @@ public class CurrentLiveVersion {
|
||||
if (staticInitializer != null) {
|
||||
try {
|
||||
staticInitializer.invoke(null);
|
||||
} catch (Exception e) {
|
||||
log.severe("Unexpected exception whilst trying to call the static initializer on " + this.reloadableType.getName());
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.severe("Unexpected exception whilst trying to call the static initializer on "
|
||||
+ this.reloadableType.getName());
|
||||
e.printStackTrace(); // TODO remove when happy
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -31,8 +32,8 @@ import org.springsource.loaded.Utils.ReturnType;
|
||||
|
||||
|
||||
/**
|
||||
* Builder that creates the dispatcher. The dispatcher is the implementation of the interface extracted for a type which then
|
||||
* delegates to the executor. A new dispatcher (and executor) is built for each class reload.
|
||||
* Builder that creates the dispatcher. The dispatcher is the implementation of the interface extracted for a type which
|
||||
* then delegates to the executor. A new dispatcher (and executor) is built for each class reload.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
@@ -43,13 +44,16 @@ public class DispatcherBuilder {
|
||||
* Factory method that builds the dispatcher for a specified reloadabletype.
|
||||
*
|
||||
* @param rtype the reloadable type
|
||||
* @param newVersionTypeDescriptor the descriptor of the new version (the executor will be generated according to this)
|
||||
* @param newVersionTypeDescriptor the descriptor of the new version (the executor will be generated according to
|
||||
* this)
|
||||
* @param versionstamp the suffix that should be appended to the generated dispatcher
|
||||
* @return the bytecode for the new dispatcher
|
||||
*/
|
||||
public static byte[] createFor(ReloadableType rtype, IncrementalTypeDescriptor newVersionTypeDescriptor, String versionstamp) {
|
||||
public static byte[] createFor(ReloadableType rtype, IncrementalTypeDescriptor newVersionTypeDescriptor,
|
||||
String versionstamp) {
|
||||
ClassReader fileReader = new ClassReader(rtype.interfaceBytes);
|
||||
DispatcherBuilderVisitor dispatcherVisitor = new DispatcherBuilderVisitor(rtype, newVersionTypeDescriptor, versionstamp);
|
||||
DispatcherBuilderVisitor dispatcherVisitor = new DispatcherBuilderVisitor(rtype, newVersionTypeDescriptor,
|
||||
versionstamp);
|
||||
fileReader.accept(dispatcherVisitor, 0);
|
||||
return dispatcherVisitor.getBytes();
|
||||
}
|
||||
@@ -62,9 +66,13 @@ public class DispatcherBuilder {
|
||||
private ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
|
||||
|
||||
private String classname;
|
||||
|
||||
private String executorClassName;
|
||||
|
||||
private String suffix;
|
||||
|
||||
private ReloadableType rtype;
|
||||
|
||||
private IncrementalTypeDescriptor typeDescriptor;
|
||||
|
||||
public DispatcherBuilderVisitor(ReloadableType rtype, IncrementalTypeDescriptor typeDescriptor, String suffix) {
|
||||
@@ -80,10 +88,12 @@ public class DispatcherBuilder {
|
||||
return cw.toByteArray();
|
||||
}
|
||||
|
||||
public void visit(int version, int flags, String name, String signature, String superclassName, String[] interfaceNames) {
|
||||
public void visit(int version, int flags, String name, String signature, String superclassName,
|
||||
String[] interfaceNames) {
|
||||
String dispatcherName = Utils.getDispatcherName(classname, suffix);
|
||||
cw.visit(version, Opcodes.ACC_PUBLIC, dispatcherName, null, "java/lang/Object",
|
||||
new String[] { Utils.getInterfaceName(classname), "org/springsource/loaded/__DynamicallyDispatchable" });
|
||||
new String[] { Utils.getInterfaceName(classname),
|
||||
"org/springsource/loaded/__DynamicallyDispatchable" });
|
||||
generateDefaultConstructor();
|
||||
}
|
||||
|
||||
@@ -123,18 +133,20 @@ public class DispatcherBuilder {
|
||||
public void visitInnerClass(String arg0, String arg1, String arg2, int arg3) {
|
||||
}
|
||||
|
||||
public MethodVisitor visitMethod(int flags, String name, String descriptor, String signature, String[] exceptions) {
|
||||
public MethodVisitor visitMethod(int flags, String name, String descriptor, String signature,
|
||||
String[] exceptions) {
|
||||
if (name.equals(mDynamicDispatchName)) {
|
||||
generateDynamicDispatchMethod(name, descriptor, signature, exceptions);
|
||||
} else if (!name.equals("<init>")) {
|
||||
}
|
||||
else if (!name.equals("<init>")) {
|
||||
generateRegularMethod(name, descriptor, signature, exceptions);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the body of the dynamic dispatcher method. This method is responsible for calling all the methods that are added
|
||||
* to a type after the first time it is defined.
|
||||
* Generate the body of the dynamic dispatcher method. This method is responsible for calling all the methods
|
||||
* that are added to a type after the first time it is defined.
|
||||
*/
|
||||
private void generateDynamicDispatchMethod(String name, String descriptor, String signature, String[] exceptions) {
|
||||
final int indexDispatcherInstance = 0;
|
||||
@@ -144,7 +156,7 @@ public class DispatcherBuilder {
|
||||
|
||||
// Should be generating the code for each additional method in
|
||||
// the executor (new version) that wasn't in the original.
|
||||
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC , name, descriptor, signature, exceptions);
|
||||
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, name, descriptor, signature, exceptions);
|
||||
mv.visitCode();
|
||||
|
||||
// Entries required here for all methods that exist in the new version but didn't exist in the original version
|
||||
@@ -199,7 +211,8 @@ public class DispatcherBuilder {
|
||||
mv.visitMethodInsn(Opcodes.INVOKESTATIC, executorClassName, method.name, callDescriptor, false);
|
||||
if (returnType.isVoid()) {
|
||||
mv.visitInsn(ACONST_NULL);
|
||||
} else if (returnType.isPrimitive()) {
|
||||
}
|
||||
else if (returnType.isPrimitive()) {
|
||||
Utils.insertBoxInsns(mv, returnType.descriptor);
|
||||
}
|
||||
mv.visitInsn(Opcodes.ARETURN);
|
||||
@@ -262,7 +275,8 @@ public class DispatcherBuilder {
|
||||
mv.visitVarInsn(ALOAD, indexArgs);
|
||||
mv.visitVarInsn(ALOAD, indexTarget);
|
||||
mv.visitVarInsn(ALOAD, indexNameAndDescriptor);
|
||||
mv.visitMethodInsn(INVOKEINTERFACE, tDynamicallyDispatchable, mDynamicDispatchName, mDynamicDispatchDescriptor, false);
|
||||
mv.visitMethodInsn(INVOKEINTERFACE, tDynamicallyDispatchable, mDynamicDispatchName,
|
||||
mDynamicDispatchDescriptor, false);
|
||||
mv.visitInsn(ARETURN);
|
||||
|
||||
// mv.visitTypeInsn(NEW, "java/lang/IllegalStateException");
|
||||
@@ -275,8 +289,8 @@ public class DispatcherBuilder {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to generate the implementation of a normal method on the interface - a normal method is one that did exist when
|
||||
* the type was first defined. Might be a catcher.
|
||||
* Called to generate the implementation of a normal method on the interface - a normal method is one that did
|
||||
* exist when the type was first defined. Might be a catcher.
|
||||
*/
|
||||
private void generateRegularMethod(String name, String descriptor, String signature, String[] exceptions) {
|
||||
// The original descriptor is how it was defined on the original type and how it is defined in the executor class.
|
||||
@@ -289,23 +303,27 @@ public class DispatcherBuilder {
|
||||
if (name.equals("___init___")) {
|
||||
// it is a ctor
|
||||
method = rtype.getConstructor(originalDescriptor);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
if (isClinit) {
|
||||
generateClinitDispatcher();
|
||||
return;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
// TODO need a better solution that these __
|
||||
if (name.startsWith("__") && !name.equals("__$swapInit")) { // __$swapInit is the groovy reset method
|
||||
// clash avoidance name
|
||||
method = rtype.getMethod(name.substring(2), originalDescriptor);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
method = rtype.getMethod(name, originalDescriptor);
|
||||
}
|
||||
}
|
||||
}
|
||||
boolean isStatic = method.isStatic();
|
||||
|
||||
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC, name, descriptor, signature, exceptions);
|
||||
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC, name, descriptor, signature,
|
||||
exceptions);
|
||||
mv.visitCode();
|
||||
// The input descriptor will include the extra initial parameter (the instance, or null for static methods)
|
||||
ReturnType returnTypeDescriptor = Utils.getReturnTypeDescriptor(descriptor);
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded;
|
||||
|
||||
import org.objectweb.asm.AnnotationVisitor;
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded;
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded;
|
||||
|
||||
import java.lang.reflect.Modifier;
|
||||
@@ -31,16 +32,17 @@ import org.objectweb.asm.Opcodes;
|
||||
* <p>
|
||||
* The executor is the class full of static methods that looks very like the original class.
|
||||
* <p>
|
||||
* <b>Methods</b>. For each method in the original type we have a method in the executor, it has the same SourceFile attribute and
|
||||
* the same local variable and line number details for debugging to work. Note the first variable will have been renamed from 'this'
|
||||
* to 'thiz' to prevent the eclipse debugger crashing. All annotations from the new version will be copied to the methods on an
|
||||
* executor.
|
||||
* <b>Methods</b>. For each method in the original type we have a method in the executor, it has the same SourceFile
|
||||
* attribute and the same local variable and line number details for debugging to work. Note the first variable will
|
||||
* have been renamed from 'this' to 'thiz' to prevent the eclipse debugger crashing. All annotations from the new
|
||||
* version will be copied to the methods on an executor.
|
||||
* <p>
|
||||
* <b>Fields</b>. Fields are copied into the executor but only so that there is a place to hang the annotations off (so that they
|
||||
* can be accessed through reflection).
|
||||
* <b>Fields</b>. Fields are copied into the executor but only so that there is a place to hang the annotations off (so
|
||||
* that they can be accessed through reflection).
|
||||
* <p>
|
||||
* <b>Constructors</b>. Constructors are added to the executor as ___init___ methods, with the invokespecials within them
|
||||
* transformed, either removed if they are calls to Object.<init> or mutated into ___init___ calls on the supertype instance.
|
||||
* <b>Constructors</b>. Constructors are added to the executor as ___init___ methods, with the invokespecials within
|
||||
* them transformed, either removed if they are calls to Object.<init> or mutated into ___init___ calls on the
|
||||
* supertype instance.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
@@ -53,28 +55,32 @@ public class ExecutorBuilder {
|
||||
this.typeRegistry = typeRegistry;
|
||||
}
|
||||
|
||||
public byte[] createFor(ReloadableType reloadableType, String versionstamp, TypeDescriptor typeDescriptor, byte[] newVersionData) {
|
||||
public byte[] createFor(ReloadableType reloadableType, String versionstamp, TypeDescriptor typeDescriptor,
|
||||
byte[] newVersionData) {
|
||||
if (typeDescriptor == null) {
|
||||
// must be reloadable or we would not be here - so can pass 'true'
|
||||
typeDescriptor = typeRegistry.getExtractor().extract(newVersionData, true);
|
||||
}
|
||||
ClassReader fileReader = new ClassReader(newVersionData);
|
||||
ExecutorBuilderVisitor executorVisitor = new ExecutorBuilderVisitor(reloadableType.getSlashedName(), versionstamp,
|
||||
ExecutorBuilderVisitor executorVisitor = new ExecutorBuilderVisitor(reloadableType.getSlashedName(),
|
||||
versionstamp,
|
||||
typeDescriptor);
|
||||
fileReader.accept(executorVisitor, 0);
|
||||
return executorVisitor.getBytes();
|
||||
}
|
||||
|
||||
/**
|
||||
* ClassVisitor that constructs the executor by visiting the original class. The basic goal is to visit the original class and
|
||||
* 'copy' the methods into the executor, making adjustments as we go.
|
||||
* ClassVisitor that constructs the executor by visiting the original class. The basic goal is to visit the original
|
||||
* class and 'copy' the methods into the executor, making adjustments as we go.
|
||||
*/
|
||||
static class ExecutorBuilderVisitor extends ClassVisitor implements Constants {
|
||||
|
||||
private ClassWriter cw = new ClassWriter(0);
|
||||
|
||||
private String classname;
|
||||
|
||||
private String suffix;
|
||||
|
||||
protected TypeDescriptor typeDescriptor;
|
||||
|
||||
public ExecutorBuilderVisitor(String classname, String suffix, TypeDescriptor typeDescriptor) {
|
||||
@@ -88,8 +94,10 @@ public class ExecutorBuilder {
|
||||
return cw.toByteArray();
|
||||
}
|
||||
|
||||
public void visit(int version, int flags, String name, String signature, String superclassName, String[] interfaceNames) {
|
||||
cw.visit(version, Opcodes.ACC_PUBLIC, Utils.getExecutorName(classname, suffix), null, "java/lang/Object", null);
|
||||
public void visit(int version, int flags, String name, String signature, String superclassName,
|
||||
String[] interfaceNames) {
|
||||
cw.visit(version, Opcodes.ACC_PUBLIC, Utils.getExecutorName(classname, suffix), null, "java/lang/Object",
|
||||
null);
|
||||
}
|
||||
|
||||
// For type level annotation copying
|
||||
@@ -104,15 +112,18 @@ public class ExecutorBuilder {
|
||||
}
|
||||
|
||||
// For each method, copy it into the new class making appropriate adjustments
|
||||
public MethodVisitor visitMethod(int flags, String name, String descriptor, String signature, String[] exceptions) {
|
||||
public MethodVisitor visitMethod(int flags, String name, String descriptor, String signature,
|
||||
String[] exceptions) {
|
||||
if (!Utils.isInitializer(name)) {
|
||||
// method
|
||||
if (!Modifier.isStatic(flags)) {
|
||||
// For non static methods add the extra initial parameter which is 'this'
|
||||
descriptor = Utils.insertExtraParameter(classname, descriptor);
|
||||
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC_STATIC, name, descriptor, signature, exceptions);
|
||||
return new MethodCopier(mv, typeDescriptor.isInterface(), descriptor, typeDescriptor, classname, suffix);
|
||||
} else {
|
||||
return new MethodCopier(mv, typeDescriptor.isInterface(), descriptor, typeDescriptor, classname,
|
||||
suffix);
|
||||
}
|
||||
else {
|
||||
// If this static method would 'clash' with an instance method that has the extra parameter added then
|
||||
// we have a couple of options to make them different:
|
||||
// 1. tweak the name
|
||||
@@ -122,31 +133,37 @@ public class ExecutorBuilder {
|
||||
name = "__" + name;
|
||||
}
|
||||
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC_STATIC, name, descriptor, signature, exceptions);
|
||||
return new MethodCopier(mv, typeDescriptor.isInterface(), descriptor, typeDescriptor, classname, suffix);
|
||||
return new MethodCopier(mv, typeDescriptor.isInterface(), descriptor, typeDescriptor, classname,
|
||||
suffix);
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
// constructor
|
||||
if (name.charAt(1) != 'c') {
|
||||
// regular constructor
|
||||
// want to create the ___init___ handler for this constructor
|
||||
|
||||
|
||||
// With the JDT compiler the inner class constructor gets an extra first parameter that is the type of
|
||||
// containing class. But with javac the inner class constructor gets an extra first parameter that is of
|
||||
// a special anonymous type (inner class of the containing class)
|
||||
// For example: class Foo { class Bar {}}
|
||||
// JDT: ctor in Bar is <init>(Foo) {}
|
||||
// JAVAC: ctor in Bar is <init>(Foo$1) {}
|
||||
|
||||
|
||||
descriptor = Utils.insertExtraParameter(classname, descriptor);
|
||||
|
||||
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC_STATIC, mInitializerName, descriptor, signature, exceptions);
|
||||
|
||||
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC_STATIC, mInitializerName, descriptor, signature,
|
||||
exceptions);
|
||||
|
||||
ConstructorCopier cc = new ConstructorCopier(mv, typeDescriptor, suffix, classname);
|
||||
return cc;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
// static initializer
|
||||
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC_STATIC, mStaticInitializerName, descriptor, signature, exceptions);
|
||||
return new MethodCopier(mv, typeDescriptor.isInterface(), descriptor, typeDescriptor, classname, suffix);
|
||||
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC_STATIC, mStaticInitializerName, descriptor, signature,
|
||||
exceptions);
|
||||
return new MethodCopier(mv, typeDescriptor.isInterface(), descriptor, typeDescriptor, classname,
|
||||
suffix);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -154,7 +171,7 @@ public class ExecutorBuilder {
|
||||
public void visitSource(String sourcefile, String debug) {
|
||||
cw.visitSource(sourcefile, debug);//getSMAP(sourcefile));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create the SMAP according to the JSR45 spec, *Note* this method is a work in progress not currently used.
|
||||
*
|
||||
@@ -162,30 +179,30 @@ public class ExecutorBuilder {
|
||||
* @return debug extension attribute encoded into a string
|
||||
*/
|
||||
public String getSMAP(String sourcefile) {
|
||||
System.out.println("Building smap for "+sourcefile);
|
||||
System.out.println("Building smap for " + sourcefile);
|
||||
StringBuilder s = new StringBuilder();
|
||||
|
||||
|
||||
// Header
|
||||
s.append("SMAP\n");
|
||||
s.append(sourcefile+"\n"); // name of the generated file
|
||||
s.append(sourcefile + "\n"); // name of the generated file
|
||||
s.append("SpringLoaded\n"); // Default stratum (Java)
|
||||
|
||||
// StratumSection
|
||||
s.append("*S SpringLoaded\n");
|
||||
|
||||
|
||||
// FileSection
|
||||
s.append("*F\n");
|
||||
s.append("+ 1 "+sourcefile+"\n");
|
||||
s.append("jaapplication1/"+sourcefile+"\n");
|
||||
// s.append("1 javaapplication1/"+sourcefile+"\n");
|
||||
|
||||
s.append("+ 1 " + sourcefile + "\n");
|
||||
s.append("jaapplication1/" + sourcefile + "\n");
|
||||
// s.append("1 javaapplication1/"+sourcefile+"\n");
|
||||
|
||||
// LineSection
|
||||
s.append("*L\n");
|
||||
s.append("1#1,1000:1,1\n");
|
||||
|
||||
|
||||
// EndSection
|
||||
s.append("*E\n");
|
||||
|
||||
|
||||
System.out.println(s.toString());
|
||||
return s.toString();
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded;
|
||||
|
||||
/**
|
||||
@@ -22,18 +23,24 @@ package org.springsource.loaded;
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public class FieldDelta {
|
||||
|
||||
public int changed;
|
||||
|
||||
private final static int CHANGED_TYPE = 0x0001;
|
||||
|
||||
private final static int CHANGED_ACCESS = 0x0002;
|
||||
|
||||
private final static int CHANGED_ANNOTATIONS = 0x0004;
|
||||
|
||||
private final static int CHANGED_MASK = CHANGED_TYPE | CHANGED_ACCESS | CHANGED_ANNOTATIONS;
|
||||
|
||||
public final String name;
|
||||
|
||||
// o = original, n = new
|
||||
String oDesc, nDesc;
|
||||
|
||||
String annotationChanges;
|
||||
|
||||
int oAccess, nAccess;
|
||||
|
||||
public FieldDelta(String name) {
|
||||
@@ -77,4 +84,4 @@ public class FieldDelta {
|
||||
return s.toString();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded;
|
||||
|
||||
/**
|
||||
@@ -24,6 +25,7 @@ package org.springsource.loaded;
|
||||
public class FieldMember extends AbstractMember {
|
||||
|
||||
final static FieldMember[] NONE = new FieldMember[0];
|
||||
|
||||
String typename;
|
||||
|
||||
protected FieldMember(String typename, int modifiers, String name, String descriptor, String signature) {
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
@@ -23,8 +24,8 @@ import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Able to read or write a particular field in a type. Knows nothing about the instance upon which the read/write may be getting
|
||||
* done.
|
||||
* Able to read or write a particular field in a type. Knows nothing about the instance upon which the read/write may be
|
||||
* getting done.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
@@ -50,12 +51,13 @@ public class FieldReaderWriter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of an instance field on the specified instance to the specified value. If a state manager is passed in things
|
||||
* can be done in a more optimal way, otherwise the state manager has to be discovered from the instance.
|
||||
* Set the value of an instance field on the specified instance to the specified value. If a state manager is passed
|
||||
* in things can be done in a more optimal way, otherwise the state manager has to be discovered from the instance.
|
||||
*
|
||||
* @param instance the object instance upon which to set the field
|
||||
* @param newValue the new value for that field
|
||||
* @param stateManager the optional state manager for this instance, which will be looked up (expensive) if not passed in
|
||||
* @param stateManager the optional state manager for this instance, which will be looked up (expensive) if not
|
||||
* passed in
|
||||
* @throws IllegalAccessException if the field value cannot be set
|
||||
*/
|
||||
public void setValue(Object instance, Object newValue, ISMgr stateManager) throws IllegalAccessException {
|
||||
@@ -72,13 +74,16 @@ public class FieldReaderWriter {
|
||||
stateManager.getMap().put(declaringTypeName, typeLevelValues);
|
||||
}
|
||||
typeLevelValues.put(theField.getName(), newValue);
|
||||
} else { // the type is not reloadable, must use reflection to access the value
|
||||
}
|
||||
else { // the type is not reloadable, must use reflection to access the value
|
||||
// TODO generate get/set in the topmost reloader for these kinds of field and use them?
|
||||
if (typeDescriptor.isInterface()) {
|
||||
// field resolution has left us with an interface field, those can't be set like this
|
||||
throw new IncompatibleClassChangeError("Expected non-static field " + instance.getClass().getName() + "."
|
||||
throw new IncompatibleClassChangeError("Expected non-static field " + instance.getClass().getName()
|
||||
+ "."
|
||||
+ theField.getName());
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
findAndSetFieldValueInHierarchy(instance, newValue);
|
||||
}
|
||||
}
|
||||
@@ -102,23 +107,26 @@ public class FieldReaderWriter {
|
||||
stateManager.getMap().put(declaringTypeName, typeLevelValues);
|
||||
}
|
||||
typeLevelValues.put(theField.getName(), newValue);
|
||||
} else { // the type is not reloadable, must use reflection to access the value
|
||||
}
|
||||
else { // the type is not reloadable, must use reflection to access the value
|
||||
try {
|
||||
Field f = locateFieldByReflection(clazz, typeDescriptor.getDottedName(), typeDescriptor.isInterface(),
|
||||
theField.getName());
|
||||
f.setAccessible(true);
|
||||
f.set(null, newValue);
|
||||
// cant cache result - we dont control the sets so won't know it is happening anyway
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Unexpectedly unable to reflectively set the field " + theField.getName()
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new IllegalStateException("Unexpectedly unable to reflectively set the field "
|
||||
+ theField.getName()
|
||||
+ " on the type " + clazz.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the value of the field for which is reader-writer exists. To improve performance a fieldAccessor can be supplied but
|
||||
* if it is missing the code will go and discover it.
|
||||
* Return the value of the field for which is reader-writer exists. To improve performance a fieldAccessor can be
|
||||
* supplied but if it is missing the code will go and discover it.
|
||||
*
|
||||
* @param instance the instance for which the field should be fetched
|
||||
* @param stateManager an optional state manager containing the map of values (will be discovered if not supplied)
|
||||
@@ -164,11 +172,14 @@ public class FieldReaderWriter {
|
||||
stateManager.getMap().put(declaringTypeName, typeLevelValues);
|
||||
}
|
||||
typeLevelValues.put(fieldname, result);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Unexpectedly unable to access field " + fieldname + " on class "
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new IllegalStateException("Unexpectedly unable to access field " + fieldname
|
||||
+ " on class "
|
||||
+ rt.getClazz(), e);
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
// The field was not on the original type. As not seen before, can default it
|
||||
result = Utils.toResultCheckIfNull(null, theField.getDescriptor());
|
||||
if (typeLevelValues == null) {
|
||||
@@ -187,14 +198,17 @@ public class FieldReaderWriter {
|
||||
}
|
||||
}
|
||||
result = Utils.toResultCheckIfNull(result, theField.getDescriptor());
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
// the type is not reloadable, must use reflection to access the value.
|
||||
// TODO measure how often we hit the reflection path, should never happen unless reflection is already on the frame
|
||||
|
||||
if (typeDescriptor.isInterface()) { // cant be an instance field if it is found to be on an interface
|
||||
throw new IncompatibleClassChangeError("Expected non-static field " + instance.getClass().getName() + "."
|
||||
throw new IncompatibleClassChangeError("Expected non-static field " + instance.getClass().getName()
|
||||
+ "."
|
||||
+ fieldname);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
result = findAndGetFieldValueInHierarchy(instance);
|
||||
}
|
||||
}
|
||||
@@ -205,7 +219,8 @@ public class FieldReaderWriter {
|
||||
return result;
|
||||
}
|
||||
|
||||
public Object getStaticFieldValue(Class<?> clazz, SSMgr stateManager) throws IllegalAccessException, IllegalArgumentException {
|
||||
public Object getStaticFieldValue(Class<?> clazz, SSMgr stateManager) throws IllegalAccessException,
|
||||
IllegalArgumentException {
|
||||
Object result = null;
|
||||
if (clazz == null) {
|
||||
throw new IllegalStateException();
|
||||
@@ -265,11 +280,14 @@ public class FieldReaderWriter {
|
||||
stateManager.getMap().put(declaringTypeName, typeLevelValues);
|
||||
}
|
||||
typeLevelValues.put(theField.getName(), result);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Unexpectedly unable to read field " + theField.getName() + " on type "
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new IllegalStateException("Unexpectedly unable to read field " + theField.getName()
|
||||
+ " on type "
|
||||
+ rt.getClazz(), e);
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
// The field was not on the original type. As not seen before, can default it
|
||||
result = Utils.toResultCheckIfNull(null, theField.getDescriptor());
|
||||
if (typeLevelValues == null) {
|
||||
@@ -290,7 +308,8 @@ public class FieldReaderWriter {
|
||||
}
|
||||
}
|
||||
result = Utils.toResultCheckIfNull(result, theField.getDescriptor());
|
||||
} else { // the type is not reloadable, must use reflection to access the value
|
||||
}
|
||||
else { // the type is not reloadable, must use reflection to access the value
|
||||
// TODO measure how often this code gets hit - ensure it does not in the common (non reflective) case
|
||||
try {
|
||||
Field f = locateFieldByReflection(clazz, typeDescriptor.getDottedName(), typeDescriptor.isInterface(),
|
||||
@@ -298,8 +317,10 @@ public class FieldReaderWriter {
|
||||
f.setAccessible(true);
|
||||
result = f.get(null);
|
||||
// cant cache result - we dont control the sets so won't know it is happening anyway
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Unexpectedly unable to set static field " + theField.getName() + " on type "
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new IllegalStateException("Unexpectedly unable to set static field " + theField.getName()
|
||||
+ " on type "
|
||||
+ typeDescriptor.getDottedName());
|
||||
}
|
||||
|
||||
@@ -346,13 +367,15 @@ public class FieldReaderWriter {
|
||||
Class<?> superclass = clazz.getSuperclass();
|
||||
if (superclass == null) {
|
||||
return null;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
return locateFieldByReflection(superclass, typeWanted, isInterface, name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Discover the instance state manager for the specific object instance. Will fail by exception rather than returning null.
|
||||
* Discover the instance state manager for the specific object instance. Will fail by exception rather than
|
||||
* returning null.
|
||||
*
|
||||
* @param instance the object instance on which to look
|
||||
* @return the discovered state manager
|
||||
@@ -369,7 +392,7 @@ public class FieldReaderWriter {
|
||||
// Looks to not have been initialized yet, this can happen if a non standard ctor was used.
|
||||
// We could push this step into the generated ctors...
|
||||
ISMgr instanceStateManager = new ISMgr(instance, typeDescriptor.getReloadableType());
|
||||
fieldAccessorField.set(instance,instanceStateManager);
|
||||
fieldAccessorField.set(instance, instanceStateManager);
|
||||
stateManager = (ISMgr) fieldAccessorField.get(instance);
|
||||
// For some reason it didn't stick!
|
||||
if (stateManager == null) {
|
||||
@@ -378,13 +401,16 @@ public class FieldReaderWriter {
|
||||
}
|
||||
}
|
||||
return stateManager;
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Unexpectedly unable to find instance state manager on class " + clazz.getName(), e);
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new IllegalStateException("Unexpectedly unable to find instance state manager on class "
|
||||
+ clazz.getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Discover the static state manager on the specified class and return it. Will fail by exception rather than returning null.
|
||||
* Discover the static state manager on the specified class and return it. Will fail by exception rather than
|
||||
* returning null.
|
||||
*
|
||||
* @param clazz the class on which to look
|
||||
* @return the discovered state manager
|
||||
@@ -393,7 +419,8 @@ public class FieldReaderWriter {
|
||||
try {
|
||||
Field stateManagerField = clazz.getField(Constants.fStaticFieldsName);
|
||||
if (stateManagerField == null) {
|
||||
throw new IllegalStateException("Cant find field accessor for type " + typeDescriptor.getReloadableType().getName());
|
||||
throw new IllegalStateException("Cant find field accessor for type "
|
||||
+ typeDescriptor.getReloadableType().getName());
|
||||
}
|
||||
SSMgr stateManager = (SSMgr) stateManagerField.get(null);
|
||||
// Field should always have been initialized - it is done at the start of the top most reloadable type <clinit>
|
||||
@@ -401,8 +428,10 @@ public class FieldReaderWriter {
|
||||
throw new IllegalStateException("Instance of this class has no state manager: " + clazz.getName());
|
||||
}
|
||||
return stateManager;
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Unexpectedly unable to find static state manager on class " + clazz.getName(), e);
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new IllegalStateException("Unexpectedly unable to find static state manager on class "
|
||||
+ clazz.getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -411,8 +440,8 @@ public class FieldReaderWriter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Walk up the instance hierarchy looking for the field, and when it is found access it and return the result. Will exit via
|
||||
* exception if it cannot find the field or something goes wrong when accessing it.
|
||||
* Walk up the instance hierarchy looking for the field, and when it is found access it and return the result. Will
|
||||
* exit via exception if it cannot find the field or something goes wrong when accessing it.
|
||||
*
|
||||
* @param instance the object instance upon which the field is being accessed
|
||||
* @return the value of the field
|
||||
@@ -431,15 +460,16 @@ public class FieldReaderWriter {
|
||||
Field f = clazz.getDeclaredField(fieldname);
|
||||
f.setAccessible(true);
|
||||
return f.get(instance);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new IllegalStateException("Unexpectedly could not access field named " + fieldname + " on class "
|
||||
+ clazz.getName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Walk up the instance hierarchy looking for the field, and when it is found set it. Will exit via exception if it cannot find
|
||||
* the field or something goes wrong when accessing it.
|
||||
* Walk up the instance hierarchy looking for the field, and when it is found set it. Will exit via exception if it
|
||||
* cannot find the field or something goes wrong when accessing it.
|
||||
*
|
||||
* @param instance the object instance upon which the field is being set
|
||||
* @param newValue the new value for the field
|
||||
@@ -458,10 +488,11 @@ public class FieldReaderWriter {
|
||||
Field f = clazz.getDeclaredField(fieldname);
|
||||
f.setAccessible(true);
|
||||
f.set(instance, newValue);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new IllegalStateException("Unexpectedly could not access field named " + fieldname + " on class "
|
||||
+ clazz.getName());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded;
|
||||
|
||||
import java.io.File;
|
||||
@@ -29,4 +30,4 @@ public interface FileChangeListener {
|
||||
|
||||
void register(ReloadableType rtype, File file);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded;
|
||||
|
||||
import java.io.File;
|
||||
@@ -26,9 +27,9 @@ import java.util.logging.Logger;
|
||||
import org.springsource.loaded.agent.SpringPlugin;
|
||||
|
||||
/**
|
||||
* Encapsulates configurable elements - these are set (to values other than the defaults) in TypeRegistry when the system property
|
||||
* springloaded configuration is processed. Some of the options are only used by testcases to make the testcases easier to write and
|
||||
* more straightforward.
|
||||
* Encapsulates configurable elements - these are set (to values other than the defaults) in TypeRegistry when the
|
||||
* system property springloaded configuration is processed. Some of the options are only used by testcases to make the
|
||||
* testcases easier to write and more straightforward.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
@@ -45,7 +46,8 @@ public class GlobalConfiguration {
|
||||
public static boolean catchersOn = true;
|
||||
|
||||
/**
|
||||
* If active, SpringLoaded will be trying to watch for types changing on the file system once they have been made reloadable.
|
||||
* If active, SpringLoaded will be trying to watch for types changing on the file system once they have been made
|
||||
* reloadable.
|
||||
*/
|
||||
public static boolean fileSystemMonitoring = false;
|
||||
|
||||
@@ -58,15 +60,14 @@ public class GlobalConfiguration {
|
||||
* verbose mode can trigger extra messages. Enable with 'verbose=true'
|
||||
*/
|
||||
public static boolean verboseMode = false;
|
||||
|
||||
|
||||
/**
|
||||
* asserts mode will trigger extra checking (performance impact but confirms correctness)
|
||||
*/
|
||||
public static boolean assertsMode = false;
|
||||
|
||||
|
||||
/**
|
||||
* Can be turned on to enable users to determine the decision process around why
|
||||
* something is not reloadable.
|
||||
* Can be turned on to enable users to determine the decision process around why something is not reloadable.
|
||||
*/
|
||||
public static boolean explainMode = false;
|
||||
|
||||
@@ -86,8 +87,9 @@ public class GlobalConfiguration {
|
||||
* Determine whether on disk caching will be used.
|
||||
*/
|
||||
public static boolean isCaching = false;
|
||||
|
||||
|
||||
public static boolean investigateSystemClassReflection = false;
|
||||
|
||||
public static boolean rewriteAllSystemClasses = false;
|
||||
|
||||
/**
|
||||
@@ -110,29 +112,30 @@ public class GlobalConfiguration {
|
||||
public static boolean directlyDefineTypes = true;
|
||||
|
||||
public final static boolean interceptReflection = true;
|
||||
|
||||
|
||||
// max number of values before we prevent them being reloaded (the clinit rewrite blows the codesize limit)
|
||||
public static int enumLimit = 1000;
|
||||
|
||||
public static boolean reloadMessages = false;// can be forced on for testing
|
||||
|
||||
/**
|
||||
* When a reload is attempted, if this is true it will be checked to confirm it is allowed and does not violate the supported
|
||||
* reloadable changes that can be made to a type.
|
||||
* When a reload is attempted, if this is true it will be checked to confirm it is allowed and does not violate the
|
||||
* supported reloadable changes that can be made to a type.
|
||||
*/
|
||||
public static boolean verifyReloads = true;
|
||||
|
||||
/**
|
||||
* When classes are dumped by Utils.dump() this specifies where. A null value will cause us to dump into the default temp
|
||||
* folder.
|
||||
* When classes are dumped by Utils.dump() this specifies where. A null value will cause us to dump into the default
|
||||
* temp folder.
|
||||
*/
|
||||
public static String dumpFolder = null;
|
||||
|
||||
/**
|
||||
* Global configuration properties set based on the value of system property 'springloaded'. If null then not yet initialized
|
||||
* (and a call to initializeFromSystemProperty()) is needed. If settings are truely once per VM, they are set directly in
|
||||
* GlobalConfiguration whereas if they may be overridden on a per classloader level, they are set in this properties object and
|
||||
* may be overridden by the springloaded.properties files accessible through each classloader.
|
||||
* Global configuration properties set based on the value of system property 'springloaded'. If null then not yet
|
||||
* initialized (and a call to initializeFromSystemProperty()) is needed. If settings are truely once per VM, they
|
||||
* are set directly in GlobalConfiguration whereas if they may be overridden on a per classloader level, they are
|
||||
* set in this properties object and may be overridden by the springloaded.properties files accessible through each
|
||||
* classloader.
|
||||
*/
|
||||
public static Properties globalConfigurationProperties;
|
||||
|
||||
@@ -150,7 +153,7 @@ public class GlobalConfiguration {
|
||||
|
||||
public final static boolean debugplugins;
|
||||
|
||||
|
||||
|
||||
private static void printUsage() {
|
||||
System.out.println("SpringLoaded");
|
||||
System.out.println("============");
|
||||
@@ -166,7 +169,7 @@ public class GlobalConfiguration {
|
||||
System.out.println("Options:");
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Look for a springloaded system property and initialize the 'default system wide' configuration based upon it.
|
||||
* Support configuration options:
|
||||
@@ -209,21 +212,27 @@ public class GlobalConfiguration {
|
||||
// if (isRuntimeLogging && log.isLoggable(Level.INFO)) {
|
||||
// log.info("configuration: interceptReflection = " + interceptReflection);
|
||||
// }
|
||||
} else if (key.equals("cleanCache")) {
|
||||
}
|
||||
else if (key.equals("cleanCache")) {
|
||||
cleanCache = kv.substring(equals + 1).equalsIgnoreCase("true");
|
||||
} else if (key.equals("caching")) {
|
||||
}
|
||||
else if (key.equals("caching")) {
|
||||
specifiedCaching = true;
|
||||
isCaching = kv.substring(equals + 1).equalsIgnoreCase("true");
|
||||
} else if (key.equals("debugplugins")) {
|
||||
}
|
||||
else if (key.equals("debugplugins")) {
|
||||
debugPlugins = true;
|
||||
}
|
||||
else if(key.equals("enumlimit")) {
|
||||
enumLimit = toInt(kv.substring(equals+1),enumLimit);
|
||||
} else if (key.equals("profile")) {
|
||||
else if (key.equals("enumlimit")) {
|
||||
enumLimit = toInt(kv.substring(equals + 1), enumLimit);
|
||||
}
|
||||
else if (key.equals("profile")) {
|
||||
profile = kv.substring(equals + 1);
|
||||
} else if (key.equals("cacheDir")) {
|
||||
}
|
||||
else if (key.equals("cacheDir")) {
|
||||
cacheDir = kv.substring(equals + 1);
|
||||
} else if (key.equals("callsideRewritingOn")) { // global setting
|
||||
}
|
||||
else if (key.equals("callsideRewritingOn")) { // global setting
|
||||
callsideRewritingOn = kv.substring(equals + 1).equalsIgnoreCase("true");
|
||||
if (isRuntimeLogging && log.isLoggable(Level.INFO)) {
|
||||
log.info("configuration: callsideRewritingOn = " + callsideRewritingOn);
|
||||
@@ -233,31 +242,38 @@ public class GlobalConfiguration {
|
||||
// if (isRuntimeLogging && log.isLoggable(Level.INFO)) {
|
||||
// log.info("configuration: logNonInterceptedReflectiveCalls = " + logNonInterceptedReflectiveCalls);
|
||||
// }
|
||||
} else if (key.equals("verifyReloads")) { // global setting
|
||||
}
|
||||
else if (key.equals("verifyReloads")) { // global setting
|
||||
verifyReloads = kv.substring(equals + 1).equalsIgnoreCase("true");
|
||||
if (isRuntimeLogging && log.isLoggable(Level.INFO)) {
|
||||
log.info("configuration: verifyReloads = " + verifyReloads);
|
||||
}
|
||||
} else if (key.equals("dumpFolder")) { // global setting
|
||||
}
|
||||
else if (key.equals("dumpFolder")) { // global setting
|
||||
dumpFolder = kv.substring(equals + 1);
|
||||
if (isRuntimeLogging && log.isLoggable(Level.INFO)) {
|
||||
log.info("configuration: dumpFolder = " + dumpFolder);
|
||||
}
|
||||
} else if (key.equals("maxClassDefinitions")) {
|
||||
}
|
||||
else if (key.equals("maxClassDefinitions")) {
|
||||
try {
|
||||
maxClassDefinitions = Integer.parseInt(kv.substring(equals + 1));
|
||||
if (isRuntimeLogging && log.isLoggable(Level.INFO)) {
|
||||
log.info("configuration: maxClassDefinitions = " + maxClassDefinitions);
|
||||
}
|
||||
} catch (NumberFormatException nfe) {
|
||||
System.err.println("ERROR: unable to parse " + kv.substring(equals + 1) + " as a integer");
|
||||
}
|
||||
} else if (key.equals("logging")) {
|
||||
catch (NumberFormatException nfe) {
|
||||
System.err.println("ERROR: unable to parse " + kv.substring(equals + 1)
|
||||
+ " as a integer");
|
||||
}
|
||||
}
|
||||
else if (key.equals("logging")) {
|
||||
GlobalConfiguration.isRuntimeLogging = kv.substring(equals + 1).equalsIgnoreCase("true");
|
||||
GlobalConfiguration.logging = kv.substring(equals + 1).equalsIgnoreCase("true");
|
||||
System.out.println("Spring-Loaded logging = (" + GlobalConfiguration.isRuntimeLogging + ","
|
||||
+ GlobalConfiguration.logging + ")");
|
||||
} else if (key.equals("verbose")) {
|
||||
}
|
||||
else if (key.equals("verbose")) {
|
||||
verboseMode = kv.substring(equals + 1).equalsIgnoreCase("true");
|
||||
reloadMessages = verboseMode;
|
||||
}
|
||||
@@ -267,11 +283,14 @@ public class GlobalConfiguration {
|
||||
else if (key.equals("rebasePaths")) {
|
||||
// value is a series of "a=b,c=d,e=f" indicating from and to
|
||||
globalConfigurationProperties.put("rebasePaths", kv.substring(equals + 1));
|
||||
} else if (key.equals("inclusions")) {
|
||||
}
|
||||
else if (key.equals("inclusions")) {
|
||||
globalConfigurationProperties.put("inclusions", kv.substring(equals + 1));
|
||||
} else if (key.equals("exclusions")) {
|
||||
}
|
||||
else if (key.equals("exclusions")) {
|
||||
globalConfigurationProperties.put("exclusions", kv.substring(equals + 1));
|
||||
} else if (key.equals("plugins")) {
|
||||
}
|
||||
else if (key.equals("plugins")) {
|
||||
// plugins=com.myplugin.Plugin,com.somethingelse.SomeOtherPlugin
|
||||
String pluginList = kv.substring(equals + 1);
|
||||
StringTokenizer pluginListTokenizer = new StringTokenizer(pluginList, ",");
|
||||
@@ -280,12 +299,13 @@ public class GlobalConfiguration {
|
||||
pluginClassnameList.add(pluginListTokenizer.nextToken());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
if (kv.equals("?")) {
|
||||
printUsage();
|
||||
}
|
||||
else if (kv.equals("verbose")) {
|
||||
Log.log("[verbose mode on] Full configuration is:"+value);
|
||||
Log.log("[verbose mode on] Full configuration is:" + value);
|
||||
verboseMode = true;
|
||||
reloadMessages = true;
|
||||
}
|
||||
@@ -319,7 +339,8 @@ public class GlobalConfiguration {
|
||||
cacheDir = new StringBuilder(userhome).append(File.separator).append(".grails").toString();
|
||||
new File(cacheDir).mkdir();
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
}
|
||||
catch (Throwable t) {
|
||||
System.err.println("looks like user.home is not set, or cannot write to it: cannot create cache.");
|
||||
t.printStackTrace(System.err);
|
||||
}
|
||||
@@ -336,7 +357,8 @@ public class GlobalConfiguration {
|
||||
// turn off the 3.0 reloading, for now (just because it hasn't been tested)
|
||||
SpringPlugin.support305 = false;
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
if (isCaching) {
|
||||
if (cacheDir == null) {
|
||||
try {
|
||||
@@ -344,7 +366,8 @@ public class GlobalConfiguration {
|
||||
if (userhome != null) {
|
||||
cacheDir = userhome;
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
}
|
||||
catch (Throwable t) {
|
||||
System.err.println("looks like user.home is not set: cannot create cache.");
|
||||
t.printStackTrace(System.err);
|
||||
}
|
||||
@@ -361,41 +384,45 @@ public class GlobalConfiguration {
|
||||
System.err.println("Caching deactivated: failed to create cache directory: " + cacheDir);
|
||||
isCaching = false;
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
if (!cacheDirFile.isDirectory()) {
|
||||
System.err.println("Caching deactivated: unable to use specified cache area, it is not a directory: "
|
||||
+ cacheDirFile);
|
||||
isCaching = false;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
System.err.println("Unexpected problem creating specified cachedir: " + cacheDir);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Alternative route for specifying values
|
||||
value = System.getProperty("springloaded.enumlimit");
|
||||
if (value != null) {
|
||||
enumLimit = toInt(value,enumLimit);
|
||||
enumLimit = toInt(value, enumLimit);
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
}
|
||||
catch (Throwable t) {
|
||||
System.err.println("Unexpected problem reading global configuration setting:" + t.toString());
|
||||
t.printStackTrace();
|
||||
}
|
||||
debugplugins = debugPlugins;
|
||||
}
|
||||
|
||||
|
||||
private static int toInt(String value, int defaultValue) {
|
||||
try {
|
||||
return Integer.parseInt(value);
|
||||
} catch (NumberFormatException nfe) {
|
||||
}
|
||||
catch (NumberFormatException nfe) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public final static boolean isJava18orHigher;
|
||||
|
||||
|
||||
static {
|
||||
String version = System.getProperty("java.version");
|
||||
if (version.startsWith("1.8")) {
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
@@ -22,10 +23,11 @@ import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Every reloadable hierarchy gets an Instance State Manager (ISMgr). The instance state manager is used to find the value of a
|
||||
* field for a particular object instance. The manager is added to the top most type in a reloadable hierarchy and is accessible to
|
||||
* all the subtypes. It maintains a map from types to secondary maps. The secondary maps record name,value pairs for each field. The
|
||||
* maps are only used if something has happened to mean we cannot continue to store the values in the original fields.
|
||||
* Every reloadable hierarchy gets an Instance State Manager (ISMgr). The instance state manager is used to find the
|
||||
* value of a field for a particular object instance. The manager is added to the top most type in a reloadable
|
||||
* hierarchy and is accessible to all the subtypes. It maintains a map from types to secondary maps. The secondary maps
|
||||
* record name,value pairs for each field. The maps are only used if something has happened to mean we cannot continue
|
||||
* to store the values in the original fields.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
@@ -48,8 +50,8 @@ public class ISMgr {
|
||||
* Get the value of a instance field - this will use 'any means necessary' to get to it.
|
||||
*
|
||||
* @param rtype the reloadabletype
|
||||
* @param instance the object instance on which the field is being accessed (whose type may not be that which declares the
|
||||
* field)
|
||||
* @param instance the object instance on which the field is being accessed (whose type may not be that which
|
||||
* declares the field)
|
||||
* @param name the name of the field
|
||||
*
|
||||
* @return the value of the field
|
||||
@@ -73,14 +75,17 @@ public class ISMgr {
|
||||
// Used to be caused because we were not reloading constructors - so when a new version of the type was
|
||||
// loaded, maybe a field was removed, but the constructor may still be referring to it. Should no longer
|
||||
// happen but what about static initializers that aren't run straightaway?
|
||||
log.info("Unexpectedly unable to locate instance field " + name + " starting from type " + rtype.dottedtypename
|
||||
log.info("Unexpectedly unable to locate instance field " + name + " starting from type "
|
||||
+ rtype.dottedtypename
|
||||
+ ": clinit running late?");
|
||||
return null;
|
||||
}
|
||||
result = frw.getValue(instance, this);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
if (field.isStatic()) {
|
||||
throw new IncompatibleClassChangeError("Expected non-static field " + rtype.dottedtypename + "." + field.getName());
|
||||
throw new IncompatibleClassChangeError("Expected non-static field " + rtype.dottedtypename + "."
|
||||
+ field.getName());
|
||||
}
|
||||
String declaringTypeName = field.getDeclaringTypeName();
|
||||
Map<String, Object> typeLevelValues = values.get(declaringTypeName);
|
||||
@@ -103,7 +108,8 @@ public class ISMgr {
|
||||
// 'field' tells us if we know about it now, it doesn't tell us if we've always known about it
|
||||
|
||||
// TODO lookup performance
|
||||
FieldMember fieldOnOriginalType = rtype.getTypeRegistry().getReloadableType(field.getDeclaringTypeName())
|
||||
FieldMember fieldOnOriginalType = rtype.getTypeRegistry().getReloadableType(
|
||||
field.getDeclaringTypeName())
|
||||
.getTypeDescriptor().getField(name);
|
||||
|
||||
if (fieldOnOriginalType != null) {
|
||||
@@ -118,11 +124,13 @@ public class ISMgr {
|
||||
values.put(declaringTypeName, typeLevelValues);
|
||||
}
|
||||
typeLevelValues.put(name, result);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new IllegalStateException("Unexpectedly unable to access field " + name + " on type "
|
||||
+ rt.getClazz().getName(), e);
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
// The field was not on the original type. As not seen before, can default it
|
||||
result = Utils.toResultCheckIfNull(null, field.getDescriptor());
|
||||
if (typeLevelValues == null) {
|
||||
@@ -157,7 +165,8 @@ public class ISMgr {
|
||||
* @param name the name of the field
|
||||
* @throws IllegalAccessException if there is a problem setting the field value
|
||||
*/
|
||||
public void setValue(ReloadableType rtype, Object instance, Object value, String name) throws IllegalAccessException {
|
||||
public void setValue(ReloadableType rtype, Object instance, Object value, String name)
|
||||
throws IllegalAccessException {
|
||||
// System.err.println(">setValue(rtype=" + rtype + ",instance=" + instance + ",value=" + value + ",name=" + name + ")");
|
||||
|
||||
// Look up through our reloadable hierarchy to find it
|
||||
@@ -170,12 +179,14 @@ public class ISMgr {
|
||||
FieldReaderWriter frw = rtype.locateField(name);
|
||||
if (frw == null) {
|
||||
// bad code redeployed?
|
||||
log.info("Unexpectedly unable to locate instance field " + name + " starting from type " + rtype.dottedtypename
|
||||
log.info("Unexpectedly unable to locate instance field " + name + " starting from type "
|
||||
+ rtype.dottedtypename
|
||||
+ ": clinit running late?");
|
||||
return;
|
||||
}
|
||||
frw.setValue(instance, value, this);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
if (fieldmember.isStatic()) {
|
||||
throw new IncompatibleClassChangeError("Expected non-static field " + rtype.dottedtypename + "."
|
||||
+ fieldmember.getName());
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -22,7 +23,8 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* This class computes and then encapsulates what has changed between the original form of a type and a newly loaded version.
|
||||
* This class computes and then encapsulates what has changed between the original form of a type and a newly loaded
|
||||
* version.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
@@ -30,6 +32,7 @@ import java.util.Map;
|
||||
public class IncrementalTypeDescriptor implements Constants {
|
||||
|
||||
private TypeDescriptor initialTypeDescriptor;
|
||||
|
||||
private TypeDescriptor latestTypeDescriptor;
|
||||
|
||||
private int bits;
|
||||
@@ -37,8 +40,11 @@ public class IncrementalTypeDescriptor implements Constants {
|
||||
private final static int BIT_COMPUTED_DIFF = 0x0001;
|
||||
|
||||
private Map<String, MethodMember> latestMethods; // Map from nameAndDescriptor to the MethodMember
|
||||
|
||||
private List<MethodMember> newOrChangedMethods;
|
||||
|
||||
private List<MethodMember> newOrChangedConstructors; // TODO required?
|
||||
|
||||
private List<MethodMember> deletedMethods;
|
||||
|
||||
public IncrementalTypeDescriptor(TypeDescriptor initialTypeDescriptor) {
|
||||
@@ -71,6 +77,7 @@ public class IncrementalTypeDescriptor implements Constants {
|
||||
* <li>method was represented as a catcher in the original, but is now 'real'
|
||||
* </ul>
|
||||
* It does not include catchers.
|
||||
*
|
||||
* @return list of new or changed methods in this type
|
||||
*/
|
||||
public List<MethodMember> getNewOrChangedMethods() {
|
||||
@@ -84,6 +91,7 @@ public class IncrementalTypeDescriptor implements Constants {
|
||||
* <li>constructors that did not exist in the original class as loaded, but do now
|
||||
* <li>constructors that did exist in the original class but have changed in some way (visibility)
|
||||
* </ul>
|
||||
*
|
||||
* @return list of new or changed constructors in this type
|
||||
*/
|
||||
public List<MethodMember> getNewOrChangedConstructors() {
|
||||
@@ -113,7 +121,8 @@ public class IncrementalTypeDescriptor implements Constants {
|
||||
if (original == null) {
|
||||
latest.bits |= MethodMember.IS_NEW;
|
||||
newOrChangedMethods.add(latest);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
if (!original.equals(latest)) { // check more than just name/descriptor
|
||||
newOrChangedMethods.add(latest);
|
||||
}
|
||||
@@ -127,7 +136,7 @@ public class IncrementalTypeDescriptor implements Constants {
|
||||
latest.bits |= MethodMember.IS_NEW;
|
||||
newOrChangedMethods.add(latest);
|
||||
}
|
||||
|
||||
|
||||
// If it now is a catcher where it didn't used to be, it has been deleted
|
||||
if (MethodMember.isCatcher(latest) && !MethodMember.isCatcher(original)) {
|
||||
latest.bits |= MethodMember.WAS_DELETED;
|
||||
@@ -142,7 +151,8 @@ public class IncrementalTypeDescriptor implements Constants {
|
||||
if (wasStatic) {
|
||||
// has been made non-static
|
||||
latest.bits |= MethodMember.MADE_NON_STATIC;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
// has been made static
|
||||
latest.bits |= MethodMember.MADE_STATIC;
|
||||
}
|
||||
@@ -283,7 +293,8 @@ public class IncrementalTypeDescriptor implements Constants {
|
||||
|
||||
public String toString(boolean compute) {
|
||||
StringBuilder s = new StringBuilder();
|
||||
s.append("Original:\n").append(this.initialTypeDescriptor).append("\nCurrent:\n").append(this.latestTypeDescriptor);
|
||||
s.append("Original:\n").append(this.initialTypeDescriptor).append("\nCurrent:\n").append(
|
||||
this.latestTypeDescriptor);
|
||||
s.append('\n');
|
||||
if (compute) {
|
||||
compute();
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded;
|
||||
|
||||
import org.objectweb.asm.AnnotationVisitor;
|
||||
@@ -24,21 +25,26 @@ import org.objectweb.asm.FieldVisitor;
|
||||
import org.objectweb.asm.MethodVisitor;
|
||||
|
||||
/**
|
||||
* Extract an interface for a type. The interface embodies the shape of the type as originally loaded. The key difference with
|
||||
* methods in the interface is that they contain an extra (leading) parameter that is the type of the original loaded class.<br>
|
||||
* Extract an interface for a type. The interface embodies the shape of the type as originally loaded. The key
|
||||
* difference with methods in the interface is that they contain an extra (leading) parameter that is the type of the
|
||||
* original loaded class.<br>
|
||||
* For example:<br>
|
||||
*
|
||||
* <pre><tt>
|
||||
* <pre>
|
||||
* <tt>
|
||||
* class Foo {
|
||||
* public String foo(int i) {}
|
||||
* }
|
||||
* </tt></pre>
|
||||
* </tt>
|
||||
* </pre>
|
||||
*
|
||||
* will cause creation of an interface method:
|
||||
*
|
||||
* <pre> <tt>
|
||||
* <pre>
|
||||
* <tt>
|
||||
* String foo(Foo instance, int i) {}
|
||||
* </tt></pre>
|
||||
* </tt>
|
||||
* </pre>
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
@@ -74,7 +80,9 @@ public class InterfaceExtractor {
|
||||
class ExtractorVisitor extends ClassVisitor implements Constants {
|
||||
|
||||
private TypeDescriptor typeDescriptor;
|
||||
|
||||
private ClassWriter interfaceWriter = new ClassWriter(0);
|
||||
|
||||
private String slashedtypename;
|
||||
|
||||
public ExtractorVisitor(TypeDescriptor typeDescriptor) {
|
||||
@@ -86,13 +94,16 @@ public class InterfaceExtractor {
|
||||
return interfaceWriter.toByteArray();
|
||||
}
|
||||
|
||||
public void visit(int version, int flags, String name, String signature, String superclassName, String[] interfaceNames) {
|
||||
public void visit(int version, int flags, String name, String signature, String superclassName,
|
||||
String[] interfaceNames) {
|
||||
// Create interface "public interface [typename]__I {"
|
||||
interfaceWriter.visit(version, ACC_PUBLIC_INTERFACE, Utils.getInterfaceName(name), null, "java/lang/Object", null);
|
||||
interfaceWriter.visit(version, ACC_PUBLIC_INTERFACE, Utils.getInterfaceName(name), null,
|
||||
"java/lang/Object", null);
|
||||
this.slashedtypename = name;
|
||||
}
|
||||
|
||||
public MethodVisitor visitMethod(int flags, String name, String descriptor, String signature, String[] exceptions) {
|
||||
public MethodVisitor visitMethod(int flags, String name, String descriptor, String signature,
|
||||
String[] exceptions) {
|
||||
// TODO should we special case statics (and not have them require an extra leading param)?
|
||||
if (isClinitOrInit(name)) {
|
||||
if (name.charAt(1) != 'c') { // avoid <clinit>
|
||||
@@ -102,7 +113,8 @@ public class InterfaceExtractor {
|
||||
name = "___init___";
|
||||
interfaceWriter.visitMethod(ACC_PUBLIC_ABSTRACT, name, newDescriptor, signature, exceptions);
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
String newDescriptor = createDescriptorWithPrefixedParameter(descriptor);
|
||||
// generic signature is erased
|
||||
MethodMember method = typeDescriptor.getByDescriptor(name, descriptor);
|
||||
@@ -149,13 +161,15 @@ public class InterfaceExtractor {
|
||||
continue;
|
||||
}
|
||||
descriptor = createDescriptorWithPrefixedParameter(method.getDescriptor());
|
||||
interfaceWriter.visitMethod(ACC_PUBLIC_ABSTRACT, method.getName(), descriptor, null, method.getExceptions());
|
||||
interfaceWriter.visitMethod(ACC_PUBLIC_ABSTRACT, method.getName(), descriptor, null,
|
||||
method.getExceptions());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify the descriptor to include a leading parameter of the type of the class being visited. For example: if visiting
|
||||
* type "com.Bar" and hit method "(Ljava/lang/String;)V" then this method will return "(Lcom/Bar;Ljava/lang/String;)V"
|
||||
* Modify the descriptor to include a leading parameter of the type of the class being visited. For example: if
|
||||
* visiting type "com.Bar" and hit method "(Ljava/lang/String;)V" then this method will return
|
||||
* "(Lcom/Bar;Ljava/lang/String;)V"
|
||||
*
|
||||
* @return new descriptor with extra leading parameter
|
||||
*/
|
||||
|
||||
@@ -13,15 +13,17 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded;
|
||||
|
||||
import java.security.ProtectionDomain;
|
||||
|
||||
import org.springsource.loaded.agent.ReloadDecision;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Plugins implementing this interface are allowed to participate in determining whether a type should be made reloadable.
|
||||
* Plugins implementing this interface are allowed to participate in determining whether a type should be made
|
||||
* reloadable.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.7.1
|
||||
@@ -35,6 +37,7 @@ public interface IsReloadableTypePlugin extends Plugin {
|
||||
* @param bytes the classfile data
|
||||
* @return a decision indicating whether the specified type should be made reloadable
|
||||
*/
|
||||
ReloadDecision shouldBeMadeReloadable(TypeRegistry typeRegistry, String typename, ProtectionDomain protectionDomain, byte[] bytes);
|
||||
ReloadDecision shouldBeMadeReloadable(TypeRegistry typeRegistry, String typename,
|
||||
ProtectionDomain protectionDomain, byte[] bytes);
|
||||
|
||||
}
|
||||
|
||||
@@ -13,14 +13,16 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded;
|
||||
|
||||
import java.security.ProtectionDomain;
|
||||
|
||||
/**
|
||||
* Plugins that implement this interface are allowed to modify types as they are loaded - this can be necessary sometimes to ensure,
|
||||
* for example, that a particular field is accessible later when a reload event occurs or that some factory method returns a wrapper
|
||||
* rather than the original object it intended to. For information on how to register plugins with the agent, see {@link Plugin}
|
||||
* Plugins that implement this interface are allowed to modify types as they are loaded - this can be necessary
|
||||
* sometimes to ensure, for example, that a particular field is accessible later when a reload event occurs or that some
|
||||
* factory method returns a wrapper rather than the original object it intended to. For information on how to register
|
||||
* plugins with the agent, see {@link Plugin}
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
@@ -29,9 +31,9 @@ public interface LoadtimeInstrumentationPlugin extends Plugin {
|
||||
|
||||
// TODO should probably be dotted names rather than slashed
|
||||
/**
|
||||
* Called by the agent to determine if this plugin is interested in changing the specified type at load time. This is used when
|
||||
* the plugin wishes to do some kind of transformation itself before the type is loaded - for example modify it to record
|
||||
* something that will later be used on a reload event.
|
||||
* Called by the agent to determine if this plugin is interested in changing the specified type at load time. This
|
||||
* is used when the plugin wishes to do some kind of transformation itself before the type is loaded - for example
|
||||
* modify it to record something that will later be used on a reload event.
|
||||
*
|
||||
* @param slashedTypeName the type name, slashed form (e.g. java/lang/String)
|
||||
* @param classLoader the classloader loading the type
|
||||
@@ -42,7 +44,8 @@ public interface LoadtimeInstrumentationPlugin extends Plugin {
|
||||
boolean accept(String slashedTypeName, ClassLoader classLoader, ProtectionDomain protectionDomain, byte[] bytes);
|
||||
|
||||
/**
|
||||
* Once accept has returned true for a type, the modify method will be called to make the actual change to the classfile bytes.
|
||||
* Once accept has returned true for a type, the modify method will be called to make the actual change to the
|
||||
* classfile bytes.
|
||||
*
|
||||
* @param slashedClassName the class name, slashed form (e.g. java/lang/String)
|
||||
* @param classLoader the classloader loading the type
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
|
||||
package org.springsource.loaded;
|
||||
|
||||
/**
|
||||
* Minimal support for logging messages, avoids all dependencies it can because it will be loaded very early by the VM and
|
||||
* we don't want to introduce unnecessarily complex classloading.
|
||||
* Minimal support for logging messages, avoids all dependencies it can because it will be loaded very early by the VM
|
||||
* and we don't want to introduce unnecessarily complex classloading.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 1.1.5
|
||||
@@ -10,7 +11,7 @@ package org.springsource.loaded;
|
||||
public class Log {
|
||||
|
||||
public static void log(String message) {
|
||||
System.out.println("SL: "+message);
|
||||
System.out.println("SL: " + message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded;
|
||||
|
||||
import org.objectweb.asm.Label;
|
||||
@@ -28,15 +29,21 @@ import org.springsource.loaded.Utils.ReturnType;
|
||||
class MethodCopier extends MethodVisitor implements Constants {
|
||||
|
||||
private boolean isInterface;
|
||||
|
||||
private String descriptor;
|
||||
|
||||
private TypeDescriptor typeDescriptor;
|
||||
|
||||
private String classname;
|
||||
|
||||
private String suffix;
|
||||
|
||||
private boolean hasFieldsRequiringAccessors;
|
||||
|
||||
public MethodCopier(MethodVisitor mv, boolean isInterface, String descriptor, TypeDescriptor typeDescriptor, String classname,
|
||||
public MethodCopier(MethodVisitor mv, boolean isInterface, String descriptor, TypeDescriptor typeDescriptor,
|
||||
String classname,
|
||||
String suffix) {
|
||||
super(ASM5,mv);
|
||||
super(ASM5, mv);
|
||||
this.isInterface = isInterface;
|
||||
this.descriptor = descriptor;
|
||||
this.typeDescriptor = typeDescriptor;
|
||||
@@ -50,7 +57,8 @@ class MethodCopier extends MethodVisitor implements Constants {
|
||||
// Rename 'this' to 'thiz' in executor otherwise Eclipse debugger will fail (static method with 'this')
|
||||
if (index == 0 && name.equals("this")) {
|
||||
super.visitLocalVariable("thiz", desc, signature, start, end, index);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
super.visitLocalVariable(name, desc, signature, start, end, index);
|
||||
}
|
||||
}
|
||||
@@ -76,9 +84,9 @@ class MethodCopier extends MethodVisitor implements Constants {
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the supplied type is a supertype of the current type we are modifying. This is used to determine if the owner we
|
||||
* have discovered for a field is one of our supertypes (and so, if it is protected, whether it is something that needs
|
||||
* redirecting through an accessor).
|
||||
* Determine if the supplied type is a supertype of the current type we are modifying. This is used to determine if
|
||||
* the owner we have discovered for a field is one of our supertypes (and so, if it is protected, whether it is
|
||||
* something that needs redirecting through an accessor).
|
||||
*
|
||||
* @param type the type which may be one of this types supertypes
|
||||
* @return true if it is a supertype
|
||||
@@ -94,7 +102,7 @@ class MethodCopier extends MethodVisitor implements Constants {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private TypeDescriptor getType(String type) {
|
||||
TypeDescriptor typeDescriptor = this.typeDescriptor.getTypeRegistry().getDescriptorFor(type);
|
||||
return typeDescriptor;
|
||||
@@ -107,18 +115,22 @@ class MethodCopier extends MethodVisitor implements Constants {
|
||||
FieldMember fm = findFieldIfRequiresAccessorUsage(owner, name);
|
||||
if (fm != null) {
|
||||
switch (opcode) {
|
||||
case GETFIELD:
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, classname, Utils.getProtectedFieldGetterName(name), "()" + desc, false);
|
||||
return;
|
||||
case PUTFIELD:
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, classname, Utils.getProtectedFieldSetterName(name), "(" + desc + ")V", false);
|
||||
return;
|
||||
case GETSTATIC:
|
||||
mv.visitMethodInsn(INVOKESTATIC, classname, Utils.getProtectedFieldGetterName(name), "()" + desc, false);
|
||||
return;
|
||||
case PUTSTATIC:
|
||||
mv.visitMethodInsn(INVOKESTATIC, classname, Utils.getProtectedFieldSetterName(name), "(" + desc + ")V", false);
|
||||
return;
|
||||
case GETFIELD:
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, classname, Utils.getProtectedFieldGetterName(name), "()"
|
||||
+ desc, false);
|
||||
return;
|
||||
case PUTFIELD:
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, classname, Utils.getProtectedFieldSetterName(name), "("
|
||||
+ desc + ")V", false);
|
||||
return;
|
||||
case GETSTATIC:
|
||||
mv.visitMethodInsn(INVOKESTATIC, classname, Utils.getProtectedFieldGetterName(name), "()"
|
||||
+ desc, false);
|
||||
return;
|
||||
case PUTSTATIC:
|
||||
mv.visitMethodInsn(INVOKESTATIC, classname, Utils.getProtectedFieldSetterName(name), "(" + desc
|
||||
+ ")V", false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -137,18 +149,20 @@ class MethodCopier extends MethodVisitor implements Constants {
|
||||
String descriptor = Utils.insertExtraParameter(owner, desc);
|
||||
super.visitMethodInsn(INVOKESTATIC, Utils.getExecutorName(classname, suffix), name, descriptor, false);
|
||||
return;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
// super call
|
||||
// TODO Check if this is true: we can just call the catcher directly if there was one, there is no need
|
||||
// for a superdispatcher
|
||||
|
||||
// Only need to redirect to the superdispatcher if it was a protected method
|
||||
TypeDescriptor supertypeDescriptor = getType(owner);
|
||||
MethodMember target = supertypeDescriptor.getByNameAndDescriptor(name+desc);
|
||||
if (target!=null && target.isProtected()) {
|
||||
MethodMember target = supertypeDescriptor.getByNameAndDescriptor(name + desc);
|
||||
if (target != null && target.isProtected()) {
|
||||
// A null target means that method is not in the supertype, so didn't get a superdispatcher
|
||||
super.visitMethodInsn(INVOKESPECIAL,classname,name+methodSuffixSuperDispatcher,desc, false);
|
||||
} else {
|
||||
super.visitMethodInsn(INVOKESPECIAL, classname, name + methodSuffixSuperDispatcher, desc, false);
|
||||
}
|
||||
else {
|
||||
super.visitMethodInsn(opcode, owner, name, desc, itf);
|
||||
}
|
||||
return;
|
||||
@@ -183,37 +197,39 @@ class MethodCopier extends MethodVisitor implements Constants {
|
||||
if (returnType.isVoid()) {
|
||||
super.visitInsn(RETURN);
|
||||
super.visitMaxs(1, descriptorSize);
|
||||
} else if (returnType.isPrimitive()) {
|
||||
}
|
||||
else if (returnType.isPrimitive()) {
|
||||
super.visitLdcInsn(0);
|
||||
switch (returnType.descriptor.charAt(0)) {
|
||||
case 'B':
|
||||
case 'C':
|
||||
case 'I':
|
||||
case 'S':
|
||||
case 'Z':
|
||||
super.visitInsn(IRETURN);
|
||||
super.visitMaxs(2, descriptorSize);
|
||||
break;
|
||||
case 'D':
|
||||
super.visitInsn(DRETURN);
|
||||
super.visitMaxs(3, descriptorSize);
|
||||
break;
|
||||
case 'F':
|
||||
super.visitInsn(FRETURN);
|
||||
super.visitMaxs(2, descriptorSize);
|
||||
break;
|
||||
case 'J':
|
||||
super.visitInsn(LRETURN);
|
||||
super.visitMaxs(3, descriptorSize);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException(returnType.descriptor);
|
||||
case 'B':
|
||||
case 'C':
|
||||
case 'I':
|
||||
case 'S':
|
||||
case 'Z':
|
||||
super.visitInsn(IRETURN);
|
||||
super.visitMaxs(2, descriptorSize);
|
||||
break;
|
||||
case 'D':
|
||||
super.visitInsn(DRETURN);
|
||||
super.visitMaxs(3, descriptorSize);
|
||||
break;
|
||||
case 'F':
|
||||
super.visitInsn(FRETURN);
|
||||
super.visitMaxs(2, descriptorSize);
|
||||
break;
|
||||
case 'J':
|
||||
super.visitInsn(LRETURN);
|
||||
super.visitMaxs(3, descriptorSize);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException(returnType.descriptor);
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
// reference type
|
||||
super.visitInsn(ACONST_NULL);
|
||||
super.visitInsn(ARETURN);
|
||||
super.visitMaxs(1, descriptorSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded;
|
||||
|
||||
import org.objectweb.asm.tree.AbstractInsnNode;
|
||||
@@ -24,23 +25,34 @@ import org.objectweb.asm.tree.AbstractInsnNode;
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public class MethodDelta {
|
||||
|
||||
public int changed;
|
||||
|
||||
private final static int CHANGED_INSTRUCTIONS = 0x0001;
|
||||
|
||||
private final static int CHANGED_ACCESS = 0x0002;
|
||||
|
||||
private final static int CHANGED_ANNOTATIONS = 0x0004;
|
||||
|
||||
private final static int CHANGED_INVOKESPECIAL = 0x0008;
|
||||
|
||||
private final static int CHANGED_CODE = 0x0010;
|
||||
|
||||
private final static int CHANGED_MASK = CHANGED_INSTRUCTIONS | CHANGED_ACCESS | CHANGED_ANNOTATIONS | CHANGED_INVOKESPECIAL
|
||||
private final static int CHANGED_MASK = CHANGED_INSTRUCTIONS | CHANGED_ACCESS | CHANGED_ANNOTATIONS
|
||||
| CHANGED_INVOKESPECIAL
|
||||
| CHANGED_CODE;
|
||||
|
||||
// o = original, n = new
|
||||
public final String name;
|
||||
|
||||
public final String desc;
|
||||
|
||||
String annotationChanges;
|
||||
|
||||
int oAccess, nAccess;
|
||||
|
||||
String oInvokespecialDescriptor, nInvokespecialDescriptor;
|
||||
|
||||
AbstractInsnNode[] oInstructions, nInstructions;
|
||||
|
||||
public MethodDelta(String name, String desc) {
|
||||
@@ -99,4 +111,4 @@ public class MethodDelta {
|
||||
this.oInstructions = oInstructions;
|
||||
this.nInstructions = nInstructions;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -13,14 +13,15 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
/**
|
||||
* Representation of a Method. Some of the bitflags and state are only set for 'incremental' methods - those found in a secondary
|
||||
* type descriptor representing a type reload.
|
||||
* Representation of a Method. Some of the bitflags and state are only set for 'incremental' methods - those found in a
|
||||
* secondary type descriptor representing a type reload.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
@@ -35,17 +36,25 @@ public class MethodMember extends AbstractMember {
|
||||
|
||||
// computed up front:
|
||||
public final static int BIT_CATCHER = 0x001;
|
||||
|
||||
public final static int BIT_CLASH = 0x0002;
|
||||
|
||||
// identifies a catcher method placed into an abstract class (where a method from a super interface hasn't been implemented)
|
||||
public final static int BIT_CATCHER_INTERFACE = 0x004;
|
||||
|
||||
public final static int BIT_SUPERDISPATCHER = 0x0008;
|
||||
|
||||
// computed on incremental members to indicate what changed:
|
||||
public final static int MADE_STATIC = 0x0010;
|
||||
|
||||
public final static int MADE_NON_STATIC = 0x0020;
|
||||
|
||||
public final static int VISIBILITY_CHANGE = 0x0040;
|
||||
|
||||
public final static int IS_NEW = 0x0080;
|
||||
|
||||
public final static int WAS_DELETED = 0x0100;
|
||||
|
||||
public final static int EXCEPTIONS_CHANGE = 0x0200;
|
||||
|
||||
// For MethodMembers in an incremental type descriptor, this tracks the method in the original type descriptor (if there was one)
|
||||
@@ -155,12 +164,14 @@ public class MethodMember extends AbstractMember {
|
||||
int newModifiers = modifiers & ~Modifier.NATIVE;
|
||||
if (name.equals("clone") && (modifiers & Modifier.NATIVE) != 0) {
|
||||
newModifiers = Modifier.PUBLIC;
|
||||
} else if ((modifiers & Modifier.PROTECTED) != 0) {
|
||||
}
|
||||
else if ((modifiers & Modifier.PROTECTED) != 0) {
|
||||
// promote to public
|
||||
// The reason for this is that the executor may try and call these things and as it is not in the hierarchy
|
||||
// it cannot. The necessary knock on effect is that subtypes get their methods promoted to public too...
|
||||
newModifiers = Modifier.PUBLIC;
|
||||
} else if ((modifiers & Constants.ACC_PUBLIC_PRIVATE_PROTECTED) == 0) {
|
||||
}
|
||||
else if ((modifiers & Constants.ACC_PUBLIC_PRIVATE_PROTECTED) == 0) {
|
||||
// promote to public from default
|
||||
// The reason for this is that the executor may try and call these things and as it is not in the hierarchy
|
||||
// it cannot. The necessary knock on effect is that subtypes get their methods promoted to public too...
|
||||
@@ -170,23 +181,26 @@ public class MethodMember extends AbstractMember {
|
||||
copy.bits |= MethodMember.BIT_CATCHER;
|
||||
return copy;
|
||||
}
|
||||
|
||||
|
||||
public MethodMember superDispatcherFor() {
|
||||
int newModifiers = modifiers & ~Modifier.NATIVE;
|
||||
if (name.equals("clone") && (modifiers & Modifier.NATIVE) != 0) {
|
||||
newModifiers = Modifier.PUBLIC;
|
||||
} else if ((modifiers & Modifier.PROTECTED) != 0) {
|
||||
}
|
||||
else if ((modifiers & Modifier.PROTECTED) != 0) {
|
||||
// promote to public
|
||||
// The reason for this is that the executor may try and call these things and as it is not in the hierarchy
|
||||
// it cannot. The necessary knock on effect is that subtypes get their methods promoted to public too...
|
||||
newModifiers = Modifier.PUBLIC;
|
||||
} else if ((modifiers & Constants.ACC_PUBLIC_PRIVATE_PROTECTED) == 0) {
|
||||
}
|
||||
else if ((modifiers & Constants.ACC_PUBLIC_PRIVATE_PROTECTED) == 0) {
|
||||
// promote to public from default
|
||||
// The reason for this is that the executor may try and call these things and as it is not in the hierarchy
|
||||
// it cannot. The necessary knock on effect is that subtypes get their methods promoted to public too...
|
||||
newModifiers = Modifier.PUBLIC;
|
||||
}
|
||||
MethodMember copy = new MethodMember(newModifiers, name+"_$superdispatcher$", descriptor, signature, exceptions);
|
||||
MethodMember copy = new MethodMember(newModifiers, name + "_$superdispatcher$", descriptor, signature,
|
||||
exceptions);
|
||||
copy.bits |= MethodMember.BIT_SUPERDISPATCHER;
|
||||
return copy;
|
||||
}
|
||||
@@ -195,12 +209,14 @@ public class MethodMember extends AbstractMember {
|
||||
int newModifiers = modifiers & ~(Modifier.NATIVE | Modifier.ABSTRACT);
|
||||
if (name.equals("clone") && (modifiers & Modifier.NATIVE) != 0) {
|
||||
newModifiers = Modifier.PUBLIC;
|
||||
} else if ((modifiers & Modifier.PROTECTED) != 0) {
|
||||
}
|
||||
else if ((modifiers & Modifier.PROTECTED) != 0) {
|
||||
// promote to public
|
||||
// The reason for this is that the executor may try and call these things and as it is not in the hierarchy
|
||||
// it cannot. The necessary knock on effect is that subtypes get their methods promoted to public too...
|
||||
newModifiers = Modifier.PUBLIC;
|
||||
} else if ((modifiers & Constants.ACC_PUBLIC_PRIVATE_PROTECTED) == 0) {
|
||||
}
|
||||
else if ((modifiers & Constants.ACC_PUBLIC_PRIVATE_PROTECTED) == 0) {
|
||||
// promote to public from default
|
||||
// The reason for this is that the executor may try and call these things and as it is not in the hierarchy
|
||||
// it cannot. The necessary knock on effect is that subtypes get their methods promoted to public too...
|
||||
@@ -241,7 +257,7 @@ public class MethodMember extends AbstractMember {
|
||||
public static boolean isClash(MethodMember method) {
|
||||
return (method.bits & MethodMember.BIT_CLASH) != 0;
|
||||
}
|
||||
|
||||
|
||||
public static boolean isSuperDispatcher(MethodMember method) {
|
||||
return (method.bits & BIT_SUPERDISPATCHER) != 0;
|
||||
}
|
||||
|
||||
@@ -13,14 +13,15 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded;
|
||||
|
||||
/**
|
||||
* Manages a mapping of names to numbers. The same number anywhere means the same name. This means that if some type a/b/C has been
|
||||
* loaded in two places (by different classloaders), it will have the same number in both. Only one of those a/b/C types will be
|
||||
* visible at the location in question, the tricky part could be working out which one (if classloaders are being naughty) but in
|
||||
* theory the first time we get confused (due to finding the name twice), we can work out which one is right and use that mapping
|
||||
* from then on.
|
||||
* Manages a mapping of names to numbers. The same number anywhere means the same name. This means that if some type
|
||||
* a/b/C has been loaded in two places (by different classloaders), it will have the same number in both. Only one of
|
||||
* those a/b/C types will be visible at the location in question, the tricky part could be working out which one (if
|
||||
* classloaders are being naughty) but in theory the first time we get confused (due to finding the name twice), we can
|
||||
* work out which one is right and use that mapping from then on.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.8.1
|
||||
@@ -28,7 +29,9 @@ package org.springsource.loaded;
|
||||
public class NameRegistry {
|
||||
|
||||
private static int nextTypeId = 0;
|
||||
|
||||
private static int size = 10;
|
||||
|
||||
private static String[] allocatedIds = new String[size];
|
||||
|
||||
private NameRegistry() {
|
||||
@@ -44,8 +47,8 @@ public class NameRegistry {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the id for a particular type. This method will not allocate a new id if the type is unknown, it will return -1
|
||||
* instead.
|
||||
* Return the id for a particular type. This method will not allocate a new id if the type is unknown, it will
|
||||
* return -1 instead.
|
||||
*
|
||||
* @param slashedClassName a type name like java/lang/String
|
||||
* @return the allocated ID for that type or -1 if unknown
|
||||
@@ -61,8 +64,8 @@ public class NameRegistry {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the id for a particular type. This method will not allocate a new id if the type is unknown, it will return -1
|
||||
* instead.
|
||||
* Return the id for a particular type. This method will not allocate a new id if the type is unknown, it will
|
||||
* return -1 instead.
|
||||
*
|
||||
* @param slashedClassName a type name like java/lang/String
|
||||
* @return the allocated ID for that type or -1 if unknown
|
||||
|
||||
@@ -13,14 +13,15 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded;
|
||||
|
||||
/**
|
||||
* Top level interface for Spring-Loaded plugins. Plugins are allowed to participate in class loading and modify code as it is
|
||||
* loaded and can be notified as types are reloaded.
|
||||
* Top level interface for Spring-Loaded plugins. Plugins are allowed to participate in class loading and modify code as
|
||||
* it is loaded and can be notified as types are reloaded.
|
||||
* <p>
|
||||
* Implementations should be registered by creating a META-INF/services/org.springsource.reloading.agent.Plugins file that lists
|
||||
* (one per line) the plugin classes, for example: org.springsource.loaded.ReloadEventProcessorPluginImpl
|
||||
* Implementations should be registered by creating a META-INF/services/org.springsource.reloading.agent.Plugins file
|
||||
* that lists (one per line) the plugin classes, for example: org.springsource.loaded.ReloadEventProcessorPluginImpl
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded;
|
||||
|
||||
import org.springsource.loaded.agent.SpringLoadedPreProcessor;
|
||||
@@ -27,6 +28,7 @@ import org.springsource.loaded.agent.SpringLoadedPreProcessor;
|
||||
* @since 0.7.2
|
||||
*/
|
||||
public class Plugins {
|
||||
|
||||
public static void registerGlobalPlugin(Plugin instance) {
|
||||
SpringLoadedPreProcessor.registerGlobalPlugin(instance);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded;
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,13 +13,14 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded;
|
||||
|
||||
import org.objectweb.asm.ClassReader;
|
||||
|
||||
/**
|
||||
* Can be used to take a quick look in the bytecode for something. The various static get* methods are the things that the quick
|
||||
* visitor can discover.
|
||||
* Can be used to take a quick look in the bytecode for something. The various static get* methods are the things that
|
||||
* the quick visitor can discover.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.7.3
|
||||
@@ -31,12 +32,14 @@ public class QuickVisitor {
|
||||
QuickVisitor1 qv = new QuickVisitor1();
|
||||
try {
|
||||
fileReader.accept(qv, ClassReader.SKIP_FRAMES);// TODO more flags to skip other things?
|
||||
} catch (EarlyExitException eee) {
|
||||
}
|
||||
catch (EarlyExitException eee) {
|
||||
}
|
||||
return qv.interfaces;
|
||||
}
|
||||
|
||||
static class QuickVisitor1 extends EmptyClassVisitor {
|
||||
|
||||
String[] interfaces;
|
||||
|
||||
@Override
|
||||
@@ -150,4 +153,4 @@ public class QuickVisitor {
|
||||
//
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,14 +13,16 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded;
|
||||
|
||||
// TODO many more to add here - would drastically reduce amount of generated bytecode but the cost would be debugging confusion
|
||||
// TODO can these methods be made synthetic (at load time) so they don't interfere with the debugger?
|
||||
// TODO can we check on whether debugging is going to happen and then choose whether to use these helper methods at startup?
|
||||
/**
|
||||
* Runtime Helper Class. Provides utility methods called by generated code to perform common functions. Using these does reduce the
|
||||
* amount of generated bytecode but it introduces extra paths into the code (calls) that a debugger might step into.
|
||||
* Runtime Helper Class. Provides utility methods called by generated code to perform common functions. Using these does
|
||||
* reduce the amount of generated bytecode but it introduces extra paths into the code (calls) that a debugger might
|
||||
* step into.
|
||||
*
|
||||
*
|
||||
* @author Andy Clement
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
@@ -34,7 +35,8 @@ public class ReflectionFieldReaderWriter extends FieldReaderWriter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getStaticFieldValue(Class<?> type, SSMgr fieldAccessor) throws IllegalAccessException, IllegalArgumentException {
|
||||
public Object getStaticFieldValue(Class<?> type, SSMgr fieldAccessor) throws IllegalAccessException,
|
||||
IllegalArgumentException {
|
||||
field.setAccessible(true);
|
||||
return field.get(null);
|
||||
}
|
||||
@@ -52,7 +54,8 @@ public class ReflectionFieldReaderWriter extends FieldReaderWriter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValue(Object instance, ISMgr fieldAccessor) throws IllegalAccessException, IllegalArgumentException {
|
||||
public Object getValue(Object instance, ISMgr fieldAccessor) throws IllegalAccessException,
|
||||
IllegalArgumentException {
|
||||
field.setAccessible(true);
|
||||
return field.get(instance);
|
||||
}
|
||||
|
||||
@@ -13,10 +13,12 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded;
|
||||
|
||||
/**
|
||||
* ReloadEventProcessor Plugins are called when a type is reloading. For information on registering them, see {@link Plugin}
|
||||
* ReloadEventProcessor Plugins are called when a type is reloading. For information on registering them, see
|
||||
* {@link Plugin}
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
@@ -24,8 +26,8 @@ package org.springsource.loaded;
|
||||
public interface ReloadEventProcessorPlugin extends Plugin {
|
||||
|
||||
/**
|
||||
* Called when a type has been reloaded, allows the plugin to decide if the static initializer should be re-run for the reloaded
|
||||
* type. If the reloaded type has a different static initializer, the new one is the one that will run.
|
||||
* Called when a type has been reloaded, allows the plugin to decide if the static initializer should be re-run for
|
||||
* the reloaded type. If the reloaded type has a different static initializer, the new one is the one that will run.
|
||||
*
|
||||
* @param typename the (dotted) type name, for example java.lang.String
|
||||
* @param clazz the Class object that has been reloaded
|
||||
@@ -37,10 +39,10 @@ public interface ReloadEventProcessorPlugin extends Plugin {
|
||||
// TODO expose detailed delta for changes in the type? (i.e. what new fields/methods/etc)
|
||||
// TODO expose instances when they are being tracked?
|
||||
/**
|
||||
* Called when a type has been reloaded. Note, the class is only truly defined to the VM once, and so the Class object (clazz
|
||||
* parameter) is always the same for the same type (ignoring multiple classloader situations). It is passed here so that plugins
|
||||
* processing events can clear any cached state related to it. The encodedTimestamp is an encoding of the ID that the agent has
|
||||
* assigned to this reloaded version of this type.
|
||||
* Called when a type has been reloaded. Note, the class is only truly defined to the VM once, and so the Class
|
||||
* object (clazz parameter) is always the same for the same type (ignoring multiple classloader situations). It is
|
||||
* passed here so that plugins processing events can clear any cached state related to it. The encodedTimestamp is
|
||||
* an encoding of the ID that the agent has assigned to this reloaded version of this type.
|
||||
*
|
||||
* @param typename the (dotted) type name, for example java.lang.String
|
||||
* @param clazz the Class object that has been reloaded
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded;
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded;
|
||||
|
||||
import java.lang.ref.Reference;
|
||||
@@ -95,15 +96,19 @@ public class ReloadableType {
|
||||
private Class<?> superclazz;
|
||||
|
||||
private ReloadableType superRtype;
|
||||
|
||||
private ReloadableType[] interfaceRtypes;
|
||||
|
||||
List<Reference<ReloadableType>> associatedSubtypes = null;
|
||||
|
||||
/** Caches Method objects for this reloadable type. This cache should be invalidated (set to null) when a type is reloaded! */
|
||||
/**
|
||||
* Caches Method objects for this reloadable type. This cache should be invalidated (set to null) when a type is
|
||||
* reloaded!
|
||||
*/
|
||||
private JavaMethodCache javaMethodCache;
|
||||
|
||||
private final static int IS_RESOLVED = 0x0001;
|
||||
|
||||
|
||||
// Indicates that this type or one in its hierarchy (super/sub) has been reloaded
|
||||
private final static int IMPACTED_BY_RELOAD = 0x0002;
|
||||
|
||||
@@ -111,9 +116,12 @@ public class ReloadableType {
|
||||
|
||||
/** Cache of the invokers used to answer getDeclaredMethods() call made on this type */
|
||||
public List<Invoker> invokersCache_getDeclaredMethods = null;
|
||||
|
||||
// TODO clear these out on reload
|
||||
public Collection<Invoker> invokersCache_getMethods = null;
|
||||
|
||||
public Map<String, Map<String, Invoker>> invokerCache_getMethod = new HashMap<String, Map<String, Invoker>>();
|
||||
|
||||
public Map<String, Map<String, Invoker>> invokerCache_getDeclaredMethod = new HashMap<String, Map<String, Invoker>>();
|
||||
|
||||
public Class<?> getClazz() {
|
||||
@@ -122,8 +130,10 @@ public class ReloadableType {
|
||||
// classloader has defined it.
|
||||
try {
|
||||
clazz = Class.forName(dottedtypename, false, typeRegistry.getClassLoader());
|
||||
} catch (ClassNotFoundException cnfe) {
|
||||
throw new ReloadException("Unexpectedly unable to find class " + this.dottedtypename + ". Asked classloader "
|
||||
}
|
||||
catch (ClassNotFoundException cnfe) {
|
||||
throw new ReloadException("Unexpectedly unable to find class " + this.dottedtypename
|
||||
+ ". Asked classloader "
|
||||
+ typeRegistry.getClassLoader(), cnfe);
|
||||
}
|
||||
}
|
||||
@@ -150,12 +160,13 @@ public class ReloadableType {
|
||||
}
|
||||
this.id = id;
|
||||
if (GlobalConfiguration.verboseMode && log.isLoggable(Level.INFO)) {
|
||||
log.info("New reloadable type: "+dottedtypename+ " (allocatedId="+id+") "+typeRegistry.toString());
|
||||
log.info("New reloadable type: " + dottedtypename + " (allocatedId=" + id + ") " + typeRegistry.toString());
|
||||
}
|
||||
this.typeRegistry = typeRegistry;
|
||||
this.dottedtypename = dottedtypename;
|
||||
this.slashedtypename = dottedtypename.replace('.', '/');
|
||||
this.typedescriptor = (typeDescriptor != null ? typeDescriptor : typeRegistry.getExtractor().extract(initialBytes, true));
|
||||
this.typedescriptor = (typeDescriptor != null ? typeDescriptor : typeRegistry.getExtractor().extract(
|
||||
initialBytes, true));
|
||||
this.interfaceBytes = InterfaceExtractor.extract(initialBytes, typeRegistry, this.typedescriptor);
|
||||
this.bytesInitial = initialBytes;
|
||||
rewriteCallSitesAndDefine();
|
||||
@@ -168,6 +179,7 @@ public class ReloadableType {
|
||||
}
|
||||
|
||||
public final static ReloadableType NOT_RELOADABLE_TYPE = new ReloadableType();
|
||||
|
||||
public final static WeakReference<ReloadableType> NOT_RELOADABLE_TYPE_REF = new WeakReference<ReloadableType>(
|
||||
NOT_RELOADABLE_TYPE);
|
||||
|
||||
@@ -176,8 +188,8 @@ public class ReloadableType {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the 'orignal' method corresponding to given name and method descriptor. This only considers methods that exist in the
|
||||
* first (non-reloaded) version of the type.
|
||||
* Gets the 'orignal' method corresponding to given name and method descriptor. This only considers methods that
|
||||
* exist in the first (non-reloaded) version of the type.
|
||||
*
|
||||
* @param name method name
|
||||
* @param descriptor method descriptor (e.g (Ljava/lang/String;)I)
|
||||
@@ -190,7 +202,8 @@ public class ReloadableType {
|
||||
return method;
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("Unable to find member '" + name + descriptor + "' on type " + this.dottedtypename);
|
||||
throw new IllegalStateException("Unable to find member '" + name + descriptor + "' on type "
|
||||
+ this.dottedtypename);
|
||||
}
|
||||
|
||||
public MethodMember getConstructor(String descriptor) {
|
||||
@@ -199,13 +212,15 @@ public class ReloadableType {
|
||||
return ctor;
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("Unable to find constructor '<init>" + descriptor + "' on type " + this.dottedtypename);
|
||||
throw new IllegalStateException("Unable to find constructor '<init>" + descriptor + "' on type "
|
||||
+ this.dottedtypename);
|
||||
}
|
||||
|
||||
// TODO what about 'regular' spring load time weaving?
|
||||
/**
|
||||
* This method will attempt to apply any pre-existing transforms to the provided bytecode, if it is thought to be necessary.
|
||||
* Currently 'necessary' is determined by finding ourselves running under tcServer and Spring Insight being turned on.
|
||||
* This method will attempt to apply any pre-existing transforms to the provided bytecode, if it is thought to be
|
||||
* necessary. Currently 'necessary' is determined by finding ourselves running under tcServer and Spring Insight
|
||||
* being turned on.
|
||||
*
|
||||
* @param bytes the new bytes to be possibly transformed.
|
||||
* @return either the original bytes or a transformed set of bytes
|
||||
@@ -231,7 +246,8 @@ public class ReloadableType {
|
||||
log.info("Determining if retransform necessary, result = " + retransformNecessary);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.log(Level.SEVERE, "Unexpected exception when determining if Spring Insight enabled", e);
|
||||
retransformNecessary = false;
|
||||
}
|
||||
@@ -247,7 +263,8 @@ public class ReloadableType {
|
||||
log.info("retransform was attempted, oldsize=" + bytes.length + " newsize=" + newdata.length);
|
||||
}
|
||||
return newdata;
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
if (GlobalConfiguration.isRuntimeLogging) {
|
||||
log.log(Level.SEVERE, "Unexpected exception when trying to run other weaving transformers", e);
|
||||
}
|
||||
@@ -257,8 +274,11 @@ public class ReloadableType {
|
||||
}
|
||||
|
||||
private boolean determinedNeedToRetransform = false;
|
||||
|
||||
private boolean retransformNecessary = false;
|
||||
|
||||
private Object retransformWeavingTransformer;
|
||||
|
||||
private java.lang.reflect.Method retransformWeavingTransformMethod;
|
||||
|
||||
// for lazy tests that are only loading one new version, fill in the versionsuffix for them
|
||||
@@ -288,7 +308,8 @@ public class ReloadableType {
|
||||
public boolean loadNewVersion(String versionsuffix, byte[] newbytedata) {
|
||||
javaMethodCache = null;
|
||||
if (GlobalConfiguration.verboseMode && log.isLoggable(Level.INFO)) {
|
||||
log.info("Loading new version of "+slashedtypename+", identifying suffix "+versionsuffix+", new data length is "+newbytedata.length+"bytes");
|
||||
log.info("Loading new version of " + slashedtypename + ", identifying suffix " + versionsuffix
|
||||
+ ", new data length is " + newbytedata.length + "bytes");
|
||||
}
|
||||
|
||||
// If we find our parent classloader has a weavingTransformer
|
||||
@@ -307,13 +328,15 @@ public class ReloadableType {
|
||||
// Not allowed to change the type
|
||||
reload = false;
|
||||
|
||||
s = new StringBuilder("Spring Loaded: Cannot reload new version of ").append(this.dottedtypename).append("\n");
|
||||
s = new StringBuilder("Spring Loaded: Cannot reload new version of ").append(this.dottedtypename).append(
|
||||
"\n");
|
||||
if (td.hasTypeAccessChanged()) {
|
||||
s.append(" Reason: Type modifiers changed\n");
|
||||
cantReload = true;
|
||||
}
|
||||
if (td.hasTypeSupertypeChanged()) {
|
||||
s.append(" Reason: Supertype changed from ").append(td.oSuperName).append(" to ").append(td.nSuperName)
|
||||
s.append(" Reason: Supertype changed from ").append(td.oSuperName).append(" to ").append(
|
||||
td.nSuperName)
|
||||
.append("\n");
|
||||
cantReload = true;
|
||||
}
|
||||
@@ -388,7 +411,8 @@ public class ReloadableType {
|
||||
invokersCache_getDeclaredMethods = null; // will no longer use this cache
|
||||
if (GlobalConfiguration.reloadMessages) {
|
||||
// Only put out the message when running in limit mode (under tc Server)
|
||||
System.out.println("Reloading: Loading new version of " + this.dottedtypename + " [" + versionsuffix + "]");
|
||||
System.out.println("Reloading: Loading new version of " + this.dottedtypename + " [" + versionsuffix
|
||||
+ "]");
|
||||
}
|
||||
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) {
|
||||
log.info("Reloading: Loading new version of " + this.dottedtypename + " [" + versionsuffix + "]");
|
||||
@@ -405,7 +429,8 @@ public class ReloadableType {
|
||||
if (typeRegistry.shouldRerunStaticInitializer(this, versionsuffix) || typedescriptor.isEnum()) {
|
||||
liveVersion.staticInitializedNeedsRerunningOnDefine = true;
|
||||
liveVersion.runStaticInitializer();
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
liveVersion.staticInitializedNeedsRerunningOnDefine = false;
|
||||
}
|
||||
// For performance:
|
||||
@@ -425,7 +450,7 @@ public class ReloadableType {
|
||||
|
||||
private void tagSupertypesAsAffectedByReload() {
|
||||
ReloadableType superRtype = getSuperRtype();
|
||||
if (superRtype!=null) {
|
||||
if (superRtype != null) {
|
||||
superRtype.tagAsAffectedByReload();
|
||||
// need to recurse up with the tagging
|
||||
superRtype.tagSupertypesAsAffectedByReload();
|
||||
@@ -433,8 +458,8 @@ public class ReloadableType {
|
||||
|
||||
// need to recurse through super interfaces too
|
||||
ReloadableType[] superinterfaceRtypes = getInterfacesRtypes();
|
||||
if (superinterfaceRtypes!=null) {
|
||||
for (ReloadableType superinterfaceRtype: superinterfaceRtypes) {
|
||||
if (superinterfaceRtypes != null) {
|
||||
for (ReloadableType superinterfaceRtype : superinterfaceRtypes) {
|
||||
superinterfaceRtype.tagAsAffectedByReload();
|
||||
superinterfaceRtype.tagSupertypesAsAffectedByReload();
|
||||
}
|
||||
@@ -443,8 +468,8 @@ public class ReloadableType {
|
||||
|
||||
// TODO who is clearing up dead entries?
|
||||
private void tagSubtypesAsAffectedByReload() {
|
||||
if (associatedSubtypes !=null) {
|
||||
for (Reference<ReloadableType> ref: associatedSubtypes) {
|
||||
if (associatedSubtypes != null) {
|
||||
for (Reference<ReloadableType> ref : associatedSubtypes) {
|
||||
ReloadableType rsubtype = ref.get();
|
||||
if (rsubtype != null) {
|
||||
rsubtype.tagAsAffectedByReload();
|
||||
@@ -453,18 +478,17 @@ public class ReloadableType {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void tagAsAffectedByReload() {
|
||||
bits |= IMPACTED_BY_RELOAD;
|
||||
invokersCache_getMethods = null;
|
||||
invokersCache_getDeclaredMethods = null;
|
||||
}
|
||||
|
||||
|
||||
public boolean isAffectedByReload() {
|
||||
return (bits&IMPACTED_BY_RELOAD)!=0;
|
||||
return (bits & IMPACTED_BY_RELOAD) != 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// TODO cache these field objects to avoid digging for them every time?
|
||||
/**
|
||||
@@ -479,14 +503,16 @@ public class ReloadableType {
|
||||
Field f = clazz.getClass().getDeclaredField("enumConstants");
|
||||
f.setAccessible(true);
|
||||
f.set(clazz, null);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
try {
|
||||
Field f = clazz.getClass().getDeclaredField("enumConstantDirectory");
|
||||
f.setAccessible(true);
|
||||
f.set(clazz, null);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
@@ -507,10 +533,11 @@ public class ReloadableType {
|
||||
// TODO Subclassloader lookups (via subregistries) when the cglib proxies are being loaded below this registry
|
||||
// TODO caching discovered Method objects
|
||||
/**
|
||||
* Go through proxies we know about in this registry and see if any of them are for the type we have just reloaded. If they are,
|
||||
* regenerate them and reload them.
|
||||
* Go through proxies we know about in this registry and see if any of them are for the type we have just reloaded.
|
||||
* If they are, regenerate them and reload them.
|
||||
*
|
||||
* @param versionsuffix the suffix to use when reloading the proxies (it matches what is being used to reload the type)
|
||||
* @param versionsuffix the suffix to use when reloading the proxies (it matches what is being used to reload the
|
||||
* type)
|
||||
*/
|
||||
private void reloadProxiesIfNecessary(String versionsuffix) {
|
||||
ReloadableType proxy = typeRegistry.cglibProxies.get(this.slashedtypename);
|
||||
@@ -537,7 +564,8 @@ public class ReloadableType {
|
||||
byte[] bs = (byte[]) found.invoke(a, b);
|
||||
proxy.loadNewVersion(versionsuffix, bs);
|
||||
proxy.runStaticInitializer();
|
||||
} catch (Throwable t) {
|
||||
}
|
||||
catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
}
|
||||
}
|
||||
@@ -564,7 +592,8 @@ public class ReloadableType {
|
||||
byte[] bs = (byte[]) found.invoke(a, b);
|
||||
proxy.loadNewVersion(versionsuffix, bs);
|
||||
proxy.runStaticInitializer();
|
||||
} catch (Throwable t) {
|
||||
}
|
||||
catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
}
|
||||
}
|
||||
@@ -578,14 +607,16 @@ public class ReloadableType {
|
||||
for (ReloadableType relevantProxy : relevantProxies) {
|
||||
Class<?>[] interfacesImplementedByProxy = relevantProxy.getClazz().getInterfaces();
|
||||
// check slashedname correct
|
||||
// @SuppressWarnings("restriction")
|
||||
byte[] newProxyBytes = sun.misc.ProxyGenerator.generateProxyClass(relevantProxy.getSlashedName(),
|
||||
// @SuppressWarnings("restriction")
|
||||
byte[] newProxyBytes = sun.misc.ProxyGenerator.generateProxyClass(
|
||||
relevantProxy.getSlashedName(),
|
||||
interfacesImplementedByProxy);
|
||||
relevantProxy.loadNewVersion(versionsuffix, newProxyBytes, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
}
|
||||
catch (Throwable t) {
|
||||
new RuntimeException("Unexpected problem trying to reload proxy for interface " + this.dottedtypename, t)
|
||||
.printStackTrace();
|
||||
}
|
||||
@@ -594,14 +625,16 @@ public class ReloadableType {
|
||||
Object[] reflectiveTargets;
|
||||
|
||||
private final static int INDEX_SWAPINIT_METHOD = 0;
|
||||
|
||||
private final static int INDEX_CALLSITEARRAY_FIELD = 1;
|
||||
|
||||
private final static int INDEX_METACLASS_FIELD = 2;
|
||||
|
||||
/**
|
||||
* Groovy types need some extra fixup:
|
||||
* <ul>
|
||||
* <li>they contain a callsite array that caches destinations for calls. It needs clearing (it will be reinitialized when
|
||||
* required)
|
||||
* <li>they contain a callsite array that caches destinations for calls. It needs clearing (it will be reinitialized
|
||||
* when required)
|
||||
* <li>not quite sure about the two: $staticClassInfo and GroovySystem removeMetaClass
|
||||
* <li>ClassScope.getClassInfo(Foo.class).cachedClassRef.clear()
|
||||
* </ul>
|
||||
@@ -613,22 +646,26 @@ public class ReloadableType {
|
||||
reflectiveTargets = new Object[5];
|
||||
try {
|
||||
reflectiveTargets[INDEX_SWAPINIT_METHOD] = clazz.getDeclaredMethod("__$swapInit");
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
s.append("cannot discover __$swapInit " + e.toString() + " -- ");
|
||||
}
|
||||
try {
|
||||
reflectiveTargets[INDEX_CALLSITEARRAY_FIELD] = clazz.getDeclaredField("$callSiteArray");
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
s.append("cannot discover $callSiteArray " + e.toString() + " -- ");
|
||||
}
|
||||
try {
|
||||
reflectiveTargets[INDEX_METACLASS_FIELD] = clazz.getDeclaredField("$class$groovy$lang$MetaClass");
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
s.append("cannot discover $class$groovy$lang$MetaClass " + e.toString() + " -- ");
|
||||
}
|
||||
try {
|
||||
reflectiveTargets[3] = clazz.getDeclaredField("$staticClassInfo");
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
s.append("cannot discover $staticClassInfo " + e.toString() + " -- ");
|
||||
}
|
||||
}
|
||||
@@ -658,7 +695,8 @@ public class ReloadableType {
|
||||
f.setAccessible(true);
|
||||
f.set(null, null);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
s.append("cannot reset state" + e.toString() + " -- ");
|
||||
// new RuntimeException("Unable to fix up groovy state for " + this.dottedtypename, e);
|
||||
}
|
||||
@@ -668,11 +706,14 @@ public class ReloadableType {
|
||||
Field metaClassRegistryField = clazz.getDeclaredField("META_CLASS_REGISTRY");
|
||||
metaClassRegistryField.setAccessible(true);
|
||||
Object metaClassRegistry = metaClassRegistryField.get(null);
|
||||
Method metaClassRegistryMethod = metaClassRegistry.getClass().getDeclaredMethod("removeMetaClass", Class.class);
|
||||
Method metaClassRegistryMethod = metaClassRegistry.getClass().getDeclaredMethod("removeMetaClass",
|
||||
Class.class);
|
||||
metaClassRegistryMethod.setAccessible(true);
|
||||
metaClassRegistryMethod.invoke(metaClassRegistry, getClazz());
|
||||
} catch (Exception e) {
|
||||
s.append("Unable to remove meta class for groovy type " + this.dottedtypename + ": " + e.toString() + " -- ");
|
||||
}
|
||||
catch (Exception e) {
|
||||
s.append("Unable to remove meta class for groovy type " + this.dottedtypename + ": " + e.toString()
|
||||
+ " -- ");
|
||||
// new RuntimeException("Unable to remove meta class for groovy type " + this.dottedtypename, e)
|
||||
// .printStackTrace(System.err);
|
||||
}
|
||||
@@ -688,8 +729,10 @@ public class ReloadableType {
|
||||
// java.lang.NoSuchMethodException: org.codehaus.groovy.reflection.ClassInfo$LazyCachedClassRef.clear()
|
||||
Method clearMethod = lazyReferenceClass.getMethod("clear");//DeclaredMethod("clear");
|
||||
clearMethod.invoke(cachedClassRefObject);
|
||||
} catch (Exception e) {
|
||||
s.append("1 Unable to clear ClassInfo CachedClass data for groovy type " + this.dottedtypename + ": " + e.toString()
|
||||
}
|
||||
catch (Exception e) {
|
||||
s.append("1 Unable to clear ClassInfo CachedClass data for groovy type " + this.dottedtypename + ": "
|
||||
+ e.toString()
|
||||
+ " -- ");
|
||||
// new RuntimeException("Unable to clear ClassInfo CachedClass data for groovy type " + this.dottedtypename, e)
|
||||
// .printStackTrace(System.err);
|
||||
@@ -714,8 +757,10 @@ public class ReloadableType {
|
||||
// // java.lang.NoSuchMethodException: org.codehaus.groovy.reflection.ClassInfo$LazyCachedClassRef.clear()
|
||||
// Method clearMethod = lazyReferenceClass.getMethod("clear");//DeclaredMethod("clear");
|
||||
// clearMethod.invoke(cachedClassRefObject);
|
||||
} catch (Exception e) {
|
||||
s.append("2 Unable to clear ClassInfo CachedClass data for groovy type " + this.dottedtypename + ": " + e.toString()
|
||||
}
|
||||
catch (Exception e) {
|
||||
s.append("2 Unable to clear ClassInfo CachedClass data for groovy type " + this.dottedtypename + ": "
|
||||
+ e.toString()
|
||||
+ " -- ");
|
||||
// new RuntimeException("Unable to clear ClassInfo CachedClass data for groovy type " + this.dottedtypename, e)
|
||||
// .printStackTrace(System.err);
|
||||
@@ -731,7 +776,8 @@ public class ReloadableType {
|
||||
deadInstances = new HashSet<WeakReference<Object>>();
|
||||
}
|
||||
deadInstances.add(instance);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
f.setAccessible(true);
|
||||
f.set(o, null);
|
||||
}
|
||||
@@ -739,8 +785,10 @@ public class ReloadableType {
|
||||
if (deadInstances != null) {
|
||||
liveInstances.removeAll(deadInstances);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
s.append("2 Unable to clear metaClass for groovy object instance (class=" + this.dottedtypename + ") " + e.toString()
|
||||
}
|
||||
catch (Exception e) {
|
||||
s.append("2 Unable to clear metaClass for groovy object instance (class=" + this.dottedtypename + ") "
|
||||
+ e.toString()
|
||||
+ " -- ");
|
||||
// new RuntimeException("Unable to clear metaClass for groovy object instance (class=" + this.dottedtypename + ")", e)
|
||||
// .printStackTrace(System.err);
|
||||
@@ -771,7 +819,8 @@ public class ReloadableType {
|
||||
loadNewVersion("0", bytesInitial);
|
||||
}
|
||||
return liveVersion.dispatcherInstance;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
// Same as getLatestDispatcherInstance()
|
||||
return (liveVersion == null ? null : liveVersion.dispatcherInstance);
|
||||
}
|
||||
@@ -802,18 +851,19 @@ public class ReloadableType {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the method corresponding to given name and descriptor, taking into consideration changes that have happened by
|
||||
* reloading.
|
||||
* Gets the method corresponding to given name and descriptor, taking into consideration changes that have happened
|
||||
* by reloading.
|
||||
*
|
||||
* @param name the member name
|
||||
* @param descriptor the member descriptor (e.g. (Ljava/lang/String;)I)
|
||||
* @return the MethodMember for that name and descriptor. Null if not found on a live version, or an exception if there is no live version and
|
||||
* it cannot be found.
|
||||
* @return the MethodMember for that name and descriptor. Null if not found on a live version, or an exception if
|
||||
* there is no live version and it cannot be found.
|
||||
*/
|
||||
public MethodMember getCurrentMethod(String name, String descriptor) {
|
||||
if (liveVersion == null) {
|
||||
return getMethod(name, descriptor);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
return liveVersion.getReloadableMethod(name, descriptor);
|
||||
}
|
||||
}
|
||||
@@ -885,8 +935,8 @@ public class ReloadableType {
|
||||
}
|
||||
if (typeRegistry.shouldDefineClasses()) {
|
||||
/**
|
||||
* Define the actual class. This is a separate call because it doesn't need doing when the ReloadableType is built
|
||||
* during agent processing, because that agent will define the class.
|
||||
* Define the actual class. This is a separate call because it doesn't need doing when the ReloadableType is
|
||||
* built during agent processing, because that agent will define the class.
|
||||
*/
|
||||
// ClassPrinter.print(bytesLoaded);
|
||||
clazz = typeRegistry.defineClass(dottedtypename, bytesLoaded, true);
|
||||
@@ -898,13 +948,15 @@ public class ReloadableType {
|
||||
* This merges the two steps: method invocation rewriting and type rewriting
|
||||
*/
|
||||
static class MergedRewrite {
|
||||
|
||||
public static byte[] rewrite(ReloadableType rtype, byte[] bytes) {
|
||||
try {
|
||||
ClassReader fileReader = new ClassReader(bytes);
|
||||
ChainedAdapters classAdaptor = new ChainedAdapters(rtype);
|
||||
fileReader.accept(classAdaptor, 0);
|
||||
return classAdaptor.getBytes();
|
||||
} catch (DontRewriteException drex) {
|
||||
}
|
||||
catch (DontRewriteException drex) {
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
@@ -912,8 +964,9 @@ public class ReloadableType {
|
||||
static class ChainedAdapters extends ClassVisitor implements Constants {
|
||||
|
||||
public ChainedAdapters(ReloadableType rtype) {
|
||||
super(ASM5,new RewriteClassAdaptor(rtype.typeRegistry, new TypeRewriter.RewriteClassAdaptor(rtype, new ClassWriter(
|
||||
ClassWriter.COMPUTE_MAXS))));
|
||||
super(ASM5, new RewriteClassAdaptor(rtype.typeRegistry, new TypeRewriter.RewriteClassAdaptor(rtype,
|
||||
new ClassWriter(
|
||||
ClassWriter.COMPUTE_MAXS))));
|
||||
}
|
||||
|
||||
public byte[] getBytes() {
|
||||
@@ -977,13 +1030,15 @@ public class ReloadableType {
|
||||
* Check if the specified method is different to the original form from the type as loaded.
|
||||
*
|
||||
* @param methodId the ID of the method currently executing
|
||||
* @return 0 if the method cannot have changed. 1 if the method has changed. 2 if the method has been deleted in a new version.
|
||||
* @return 0 if the method cannot have changed. 1 if the method has changed. 2 if the method has been deleted in a
|
||||
* new version.
|
||||
*/
|
||||
@UsedByGeneratedCode
|
||||
public int changed(int methodId) {
|
||||
if (liveVersion == null) {
|
||||
return 0;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
int retval = 0;
|
||||
// First check if a new version of the type was loaded:
|
||||
if (liveVersion != null) {
|
||||
@@ -998,7 +1053,8 @@ public class ReloadableType {
|
||||
boolean b = liveVersion.incrementalTypeDescriptor.hasBeenDeleted(methodId);
|
||||
if (b) {
|
||||
retval = 2;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
retval = liveVersion.incrementalTypeDescriptor.mustUseExecutorForThisMethod(methodId) ? 1 : 0;
|
||||
}
|
||||
}
|
||||
@@ -1038,7 +1094,7 @@ public class ReloadableType {
|
||||
public String getSlashedSupertypeName() {
|
||||
return getTypeDescriptor().getSupertypeName();
|
||||
}
|
||||
|
||||
|
||||
public String[] getSlashedSuperinterfacesName() {
|
||||
return getTypeDescriptor().getSuperinterfacesName();
|
||||
}
|
||||
@@ -1058,8 +1114,8 @@ public class ReloadableType {
|
||||
}
|
||||
|
||||
/**
|
||||
* Intended to handle dynamic dispatch. This will determine the right type to handle the specified method and return a
|
||||
* dispatcher that can handle it.
|
||||
* Intended to handle dynamic dispatch. This will determine the right type to handle the specified method and return
|
||||
* a dispatcher that can handle it.
|
||||
*
|
||||
* @param instance the target instance for the invocation
|
||||
* @param nameAndDescriptor an encoded method name and descriptor, e.g. foo(Ljava/langString;)V
|
||||
@@ -1080,7 +1136,8 @@ public class ReloadableType {
|
||||
// iterate up the hierarchy finding the first person that can satisfy that method from a virtual dispatch perspective
|
||||
ReloadableType rtype = typeRegistry.getReloadableType(dynamicTypeName.replace('.', '/'));
|
||||
if (rtype == null) {
|
||||
throw new ReloadException("ReloadableType.determineDispatcher(): expected " + dynamicTypeName + " to be reloadable");
|
||||
throw new ReloadException("ReloadableType.determineDispatcher(): expected " + dynamicTypeName
|
||||
+ " to be reloadable");
|
||||
}
|
||||
boolean found = false;
|
||||
while (rtype != null && !found) {
|
||||
@@ -1101,12 +1158,14 @@ public class ReloadableType {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
// Did the type originally define it:
|
||||
MethodMember[] mms = rtype.getTypeDescriptor().getMethods();
|
||||
for (MethodMember mm : mms) {
|
||||
// TODO don't need superdispatcher check, name won't match will it...
|
||||
if (mm.getNameAndDescriptor().equals(nameAndDescriptor) && !MethodMember.isCatcher(mm) && !MethodMember.isSuperDispatcher(mm)) {
|
||||
if (mm.getNameAndDescriptor().equals(nameAndDescriptor) && !MethodMember.isCatcher(mm)
|
||||
&& !MethodMember.isSuperDispatcher(mm)) {
|
||||
// the original version does implement it
|
||||
found = true;
|
||||
break;
|
||||
@@ -1141,7 +1200,8 @@ public class ReloadableType {
|
||||
int dotIndex = dottedtypename.lastIndexOf(".");
|
||||
if (dotIndex == -1) {
|
||||
return dottedtypename;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
return dottedtypename.substring(dotIndex + 1);
|
||||
}
|
||||
}
|
||||
@@ -1149,7 +1209,8 @@ public class ReloadableType {
|
||||
public TypeDescriptor getLatestTypeDescriptor() {
|
||||
if (liveVersion == null) {
|
||||
return typedescriptor;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
return liveVersion.incrementalTypeDescriptor.getLatestTypeDescriptor();
|
||||
}
|
||||
}
|
||||
@@ -1190,9 +1251,10 @@ public class ReloadableType {
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the named instance field either on this reloadable type or on a reloadable supertype - it will not go into the
|
||||
* non-reloadable types. This method also avoids interfaces because it is looking for instance fields. This is slightly naughty
|
||||
* but if we assume the code we are reloading is valid code, it should never be referring to interface fields.
|
||||
* Find the named instance field either on this reloadable type or on a reloadable supertype - it will not go into
|
||||
* the non-reloadable types. This method also avoids interfaces because it is looking for instance fields. This is
|
||||
* slightly naughty but if we assume the code we are reloading is valid code, it should never be referring to
|
||||
* interface fields.
|
||||
*
|
||||
* @param name the name of the field to locate
|
||||
* @return the FieldMember or null if the field is not found
|
||||
@@ -1218,9 +1280,9 @@ public class ReloadableType {
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for a static field from this type upwards, as far as the topmost reloadable types. This is searching for a field, it
|
||||
* is not checking the result. It is up to the caller to check they have not ended up with an instance field and throw the
|
||||
* appropriate exception.
|
||||
* Search for a static field from this type upwards, as far as the topmost reloadable types. This is searching for a
|
||||
* field, it is not checking the result. It is up to the caller to check they have not ended up with an instance
|
||||
* field and throw the appropriate exception.
|
||||
*
|
||||
* @param name the name of the field to look for
|
||||
* @return a FieldMember for the named field or null if not found
|
||||
@@ -1265,33 +1327,40 @@ public class ReloadableType {
|
||||
* @param newValue the new value to put into the field
|
||||
* @throws IllegalAccessException if there is a problem setting the field value
|
||||
*/
|
||||
public void setField(Object instance, String fieldname, boolean isStatic, Object newValue) throws IllegalAccessException {
|
||||
public void setField(Object instance, String fieldname, boolean isStatic, Object newValue)
|
||||
throws IllegalAccessException {
|
||||
FieldReaderWriter fieldReaderWriter = locateField(fieldname);
|
||||
if (isStatic && !fieldReaderWriter.isStatic()) {
|
||||
throw new IncompatibleClassChangeError("Expected static field " + fieldReaderWriter.theField.getDeclaringTypeName()
|
||||
throw new IncompatibleClassChangeError("Expected static field "
|
||||
+ fieldReaderWriter.theField.getDeclaringTypeName()
|
||||
+ "." + fieldReaderWriter.theField.getName());
|
||||
} else if (!isStatic && fieldReaderWriter.isStatic()) {
|
||||
throw new IncompatibleClassChangeError("Expected non-static field " + fieldReaderWriter.theField.getDeclaringTypeName()
|
||||
}
|
||||
else if (!isStatic && fieldReaderWriter.isStatic()) {
|
||||
throw new IncompatibleClassChangeError("Expected non-static field "
|
||||
+ fieldReaderWriter.theField.getDeclaringTypeName()
|
||||
+ "." + fieldReaderWriter.theField.getName());
|
||||
}
|
||||
|
||||
if (fieldReaderWriter.isStatic()) {
|
||||
fieldReaderWriter.setStaticFieldValue(getClazz(), newValue, null);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
fieldReaderWriter.setValue(instance, newValue, null);
|
||||
}
|
||||
}
|
||||
|
||||
private Set<WeakReference<Object>> liveInstances = Collections.synchronizedSet(new HashSet<WeakReference<Object>>());
|
||||
|
||||
private ReferenceQueue<Object> liveInstancesRQ = new ReferenceQueue<Object>();
|
||||
|
||||
// reflective state caching
|
||||
public Reference<Method[]> jlClassGetDeclaredMethods_cache = new WeakReference<Method[]>(null);
|
||||
|
||||
public Reference<Method[]> jlClassGetMethods_cache = new WeakReference<Method[]>(null);
|
||||
|
||||
/**
|
||||
* Attempt to set the value of a field on an instance to the specified value. Simply locate the field, which returns an object
|
||||
* capable of reading/writing it, then use that to retrieve the value.
|
||||
* Attempt to set the value of a field on an instance to the specified value. Simply locate the field, which returns
|
||||
* an object capable of reading/writing it, then use that to retrieve the value.
|
||||
*
|
||||
* @param instance the object upon which to set the field (maybe null for static fields)
|
||||
* @param fieldname the name of the field
|
||||
@@ -1302,16 +1371,20 @@ public class ReloadableType {
|
||||
public Object getField(Object instance, String fieldname, boolean isStatic) throws IllegalAccessException {
|
||||
FieldReaderWriter fieldReaderWriter = locateField(fieldname);
|
||||
if (isStatic && !fieldReaderWriter.isStatic()) {
|
||||
throw new IncompatibleClassChangeError("Expected static field " + fieldReaderWriter.theField.getDeclaringTypeName()
|
||||
throw new IncompatibleClassChangeError("Expected static field "
|
||||
+ fieldReaderWriter.theField.getDeclaringTypeName()
|
||||
+ "." + fieldReaderWriter.theField.getName());
|
||||
} else if (!isStatic && fieldReaderWriter.isStatic()) {
|
||||
throw new IncompatibleClassChangeError("Expected non-static field " + fieldReaderWriter.theField.getDeclaringTypeName()
|
||||
}
|
||||
else if (!isStatic && fieldReaderWriter.isStatic()) {
|
||||
throw new IncompatibleClassChangeError("Expected non-static field "
|
||||
+ fieldReaderWriter.theField.getDeclaringTypeName()
|
||||
+ "." + fieldReaderWriter.theField.getName());
|
||||
}
|
||||
Object o = null;
|
||||
if (fieldReaderWriter.isStatic()) {
|
||||
o = fieldReaderWriter.getStaticFieldValue(getClazz(), null);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
o = fieldReaderWriter.getValue(instance, null);
|
||||
}
|
||||
|
||||
@@ -1325,7 +1398,8 @@ public class ReloadableType {
|
||||
public FieldReaderWriter locateField(String name) {
|
||||
if (hasFieldChangedInHierarchy(name)) {
|
||||
return walk(name, getLatestTypeDescriptor());
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
return getFieldInHierarchy(name);
|
||||
}
|
||||
}
|
||||
@@ -1335,7 +1409,8 @@ public class ReloadableType {
|
||||
if (theField != null) {
|
||||
// Found it
|
||||
return new FieldReaderWriter(theField, typeDescriptor);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
String[] superinterfaceNames = typeDescriptor.getSuperinterfacesName();
|
||||
for (String superinterfaceName : superinterfaceNames) {
|
||||
TypeDescriptor interfaceTypeDescriptor = getTypeRegistry().getLatestDescriptorFor(superinterfaceName);
|
||||
@@ -1379,7 +1454,8 @@ public class ReloadableType {
|
||||
if (originalField != null && field != null) {
|
||||
if (originalField.equals(field)) {
|
||||
return FieldWalkDiscoveryResult.UNCHANGED_STOPWALKINGNOW;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
return FieldWalkDiscoveryResult.CHANGED_STOPNOW;
|
||||
}
|
||||
}
|
||||
@@ -1435,12 +1511,12 @@ public class ReloadableType {
|
||||
|
||||
FieldWalkDiscoveryResult b = hasFieldChangedInHierarchy(name, rtype.getTypeDescriptor().getSupertypeName());
|
||||
switch (b) {
|
||||
case CHANGED_STOPNOW:
|
||||
return true;
|
||||
case UNCHANGED_STOPWALKINGNOW:
|
||||
return false;
|
||||
case DONTKNOW:
|
||||
throw new IllegalStateException();
|
||||
case CHANGED_STOPNOW:
|
||||
return true;
|
||||
case UNCHANGED_STOPWALKINGNOW:
|
||||
return false;
|
||||
case DONTKNOW:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
@@ -1456,7 +1532,8 @@ public class ReloadableType {
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
if (field != null) {
|
||||
@@ -1500,7 +1577,8 @@ public class ReloadableType {
|
||||
Reference<Object> r = (Reference<Object>) liveInstancesRQ.poll();
|
||||
if (r != null) {
|
||||
liveInstances.remove(r);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1526,10 +1604,9 @@ public class ReloadableType {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the ReloadableType representing the superclass of this type. If the supertype
|
||||
* is not reloadable, this method will return null. The ReloadableType that is returned
|
||||
* may not be within the same type registry, if the supertype was loaded by a different
|
||||
* classloader.
|
||||
* Return the ReloadableType representing the superclass of this type. If the supertype is not reloadable, this
|
||||
* method will return null. The ReloadableType that is returned may not be within the same type registry, if the
|
||||
* supertype was loaded by a different classloader.
|
||||
*
|
||||
* @return the ReloadableType for the supertype or null if it is not reloadable
|
||||
*/
|
||||
@@ -1556,17 +1633,18 @@ public class ReloadableType {
|
||||
return superRtype;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public ReloadableType[] getInterfacesRtypes() {
|
||||
if (interfaceRtypes != null) {
|
||||
return interfaceRtypes;
|
||||
}
|
||||
if (this.getSlashedSuperinterfacesName() == null) {
|
||||
return null;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
List<ReloadableType> reloadableInterfaces = new ArrayList<ReloadableType>();
|
||||
String[] names = this.getSlashedSuperinterfacesName();
|
||||
for (String name: names) {
|
||||
for (String name : names) {
|
||||
ReloadableType interfaceRtype = typeRegistry.getReloadableSuperType(name);
|
||||
if (interfaceRtype != null) { // If null then that interface is not reloadable
|
||||
reloadableInterfaces.add(interfaceRtype);
|
||||
@@ -1576,7 +1654,7 @@ public class ReloadableType {
|
||||
return interfaceRtypes;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public boolean hasStaticInitializer() {
|
||||
return this.typedescriptor.hasClinit();
|
||||
@@ -1599,11 +1677,10 @@ public class ReloadableType {
|
||||
public List<Reference<ReloadableType>> getAssociatedSubtypes() {
|
||||
return associatedSubtypes;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* For this specified reloadable type, records the type with its parent types
|
||||
* (super class and super interfaces). With this information the system can run faster
|
||||
* when reloading has occurred.
|
||||
* For this specified reloadable type, records the type with its parent types (super class and super interfaces).
|
||||
* With this information the system can run faster when reloading has occurred.
|
||||
*/
|
||||
public void createTypeAssociations() {
|
||||
// Connect the child to the parent rtype and interface rtypes
|
||||
@@ -1612,12 +1689,12 @@ public class ReloadableType {
|
||||
return;
|
||||
}
|
||||
ReloadableType srtype = getSuperRtype();
|
||||
if (srtype!=null) {
|
||||
if (srtype != null) {
|
||||
srtype.recordSubtype(this);
|
||||
}
|
||||
ReloadableType[] irtypes = getInterfacesRtypes();
|
||||
if (irtypes!=null) {
|
||||
for (ReloadableType irtype: irtypes) {
|
||||
if (irtypes != null) {
|
||||
for (ReloadableType irtype : irtypes) {
|
||||
irtype.recordSubtype(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
@@ -22,10 +23,10 @@ import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Static State Manager. The top most class in every hierarchy of reloadable types gets a static state manager instance. The static
|
||||
* state manager is used to find the value of a field for a particular object instance. The FieldAccessor is added to the top most
|
||||
* type in a reloadable hierarchy and is accessible to all the subtypes. It maintains a map from type names to fields (name/value
|
||||
* pairs).
|
||||
* Static State Manager. The top most class in every hierarchy of reloadable types gets a static state manager instance.
|
||||
* The static state manager is used to find the value of a field for a particular object instance. The FieldAccessor is
|
||||
* added to the top most type in a reloadable hierarchy and is accessible to all the subtypes. It maintains a map from
|
||||
* type names to fields (name/value pairs).
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
@@ -48,12 +49,14 @@ public class SSMgr {
|
||||
if (fieldmember == null) {
|
||||
FieldReaderWriter flr = rtype.locateField(name);
|
||||
if (flr == null) {
|
||||
log.info("Unexpectedly unable to locate static field " + name + " starting from type " + rtype.dottedtypename
|
||||
log.info("Unexpectedly unable to locate static field " + name + " starting from type "
|
||||
+ rtype.dottedtypename
|
||||
+ ": clinit running late?");
|
||||
return null;
|
||||
}
|
||||
result = flr.getStaticFieldValue(rtype.getClazz(), this);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
if (!fieldmember.isStatic()) {
|
||||
throw new IncompatibleClassChangeError("Expected static field " + rtype.dottedtypename + "."
|
||||
+ fieldmember.getName());
|
||||
@@ -90,11 +93,13 @@ public class SSMgr {
|
||||
values.put(declaringTypeName, typeLevelValues);
|
||||
}
|
||||
typeLevelValues.put(name, result);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new IllegalStateException("Unexpectedly unable to access field " + name + " on type "
|
||||
+ rt.getClazz().getName(), e);
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
// The field was not on the original type. As not seen before, can default it
|
||||
result = Utils.toResultCheckIfNull(null, fieldmember.getDescriptor());
|
||||
if (typeLevelValues == null) {
|
||||
@@ -135,12 +140,14 @@ public class SSMgr {
|
||||
FieldReaderWriter frw = rtype.locateField(name);
|
||||
if (frw == null) {
|
||||
// bad code redeployed?
|
||||
log.info("Unexpectedly unable to locate static field " + name + " starting from type " + rtype.dottedtypename
|
||||
log.info("Unexpectedly unable to locate static field " + name + " starting from type "
|
||||
+ rtype.dottedtypename
|
||||
+ ": clinit running late?");
|
||||
return;
|
||||
}
|
||||
frw.setStaticFieldValue(rtype.getClazz(), newValue, this);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
if (!fieldmember.isStatic()) {
|
||||
throw new IncompatibleClassChangeError("Expected static field " + rtype.dottedtypename + "."
|
||||
+ fieldmember.getName());
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded;
|
||||
|
||||
/**
|
||||
@@ -28,8 +29,8 @@ public class SpringLoaded {
|
||||
*
|
||||
* @param clazz the class to be reloaded
|
||||
* @param newbytes the data bytecode data to reload as the new version
|
||||
* @return int return code: 0 is success. 1 is unknown classloader, 2 is unknown type (possibly not yet loaded). 3 is reload
|
||||
* event failed. 4 is exception occurred.
|
||||
* @return int return code: 0 is success. 1 is unknown classloader, 2 is unknown type (possibly not yet loaded). 3
|
||||
* is reload event failed. 4 is exception occurred.
|
||||
*/
|
||||
public static int loadNewVersionOfType(Class<?> clazz, byte[] newbytes) {
|
||||
return loadNewVersionOfType(clazz.getClassLoader(), clazz.getName(), newbytes);
|
||||
@@ -41,8 +42,8 @@ public class SpringLoaded {
|
||||
* @param classLoader the classloader that was used to load the original form of the type
|
||||
* @param dottedClassname the dotted name of the type being reloaded, e.g. com.foo.Bar
|
||||
* @param newbytes the data bytecode data to reload as the new version
|
||||
* @return int return code: 0 is success. 1 is unknown classloader, 2 is unknown type (possibly not yet loaded). 3 is reload
|
||||
* event failed. 4 is exception occurred.
|
||||
* @return int return code: 0 is success. 1 is unknown classloader, 2 is unknown type (possibly not yet loaded). 3
|
||||
* is reload event failed. 4 is exception occurred.
|
||||
*/
|
||||
public static int loadNewVersionOfType(ClassLoader classLoader, String dottedClassname, byte[] newbytes) {
|
||||
try {
|
||||
@@ -60,7 +61,8 @@ public class SpringLoaded {
|
||||
String tag = Utils.encode(System.currentTimeMillis());
|
||||
boolean reloaded = reloadableType.loadNewVersion(tag, newbytes);
|
||||
return reloaded ? 0 : 3;
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return 4;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded;
|
||||
|
||||
import org.objectweb.asm.ClassReader;
|
||||
@@ -22,16 +23,17 @@ import org.objectweb.asm.MethodVisitor;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
|
||||
/**
|
||||
* This is similar to SystemClassReflectionRewriter but this version just summarizes what it finds, rather than making any changes.
|
||||
* Using the results of this we can determine whether it needs proper rewriting by the SystemClassReflectionRewriter (which would be
|
||||
* done by adding this class to the list of those in the SLPP that should be processed like that).
|
||||
* This is similar to SystemClassReflectionRewriter but this version just summarizes what it finds, rather than making
|
||||
* any changes. Using the results of this we can determine whether it needs proper rewriting by the
|
||||
* SystemClassReflectionRewriter (which would be done by adding this class to the list of those in the SLPP that should
|
||||
* be processed like that).
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.7.3
|
||||
*/
|
||||
public class SystemClassReflectionInvestigator {
|
||||
|
||||
|
||||
|
||||
public static int investigate(String slashedClassName, byte[] bytes, boolean print) {
|
||||
ClassReader fileReader = new ClassReader(bytes);
|
||||
RewriteClassAdaptor classAdaptor = new RewriteClassAdaptor(print);
|
||||
@@ -42,9 +44,13 @@ public class SystemClassReflectionInvestigator {
|
||||
static class RewriteClassAdaptor extends ClassVisitor implements Constants {
|
||||
|
||||
int hitCount = 0;
|
||||
|
||||
private ClassWriter cw;
|
||||
|
||||
int bits = 0x0000;
|
||||
|
||||
private boolean print;
|
||||
|
||||
private String classname;
|
||||
|
||||
private static boolean isInterceptable(String owner, String methodName) {
|
||||
@@ -53,7 +59,7 @@ public class SystemClassReflectionInvestigator {
|
||||
|
||||
public RewriteClassAdaptor(boolean print) {
|
||||
// TODO should it also compute frames?
|
||||
super(ASM5,new ClassWriter(ClassWriter.COMPUTE_MAXS));
|
||||
super(ASM5, new ClassWriter(ClassWriter.COMPUTE_MAXS));
|
||||
this.print = print;
|
||||
cw = (ClassWriter) cv;
|
||||
}
|
||||
@@ -74,7 +80,8 @@ public class SystemClassReflectionInvestigator {
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodVisitor visitMethod(int flags, String name, String descriptor, String signature, String[] exceptions) {
|
||||
public MethodVisitor visitMethod(int flags, String name, String descriptor, String signature,
|
||||
String[] exceptions) {
|
||||
MethodVisitor mv = super.visitMethod(flags, name, descriptor, signature, exceptions);
|
||||
return new RewritingMethodAdapter(mv);
|
||||
}
|
||||
@@ -82,14 +89,15 @@ public class SystemClassReflectionInvestigator {
|
||||
class RewritingMethodAdapter extends MethodVisitor implements Opcodes, Constants {
|
||||
|
||||
public RewritingMethodAdapter(MethodVisitor mv) {
|
||||
super(ASM5,mv);
|
||||
super(ASM5, mv);
|
||||
}
|
||||
|
||||
private boolean interceptReflection(String owner, String name, String desc) {
|
||||
if (isInterceptable(owner, name)) {
|
||||
hitCount++;
|
||||
if (print) {
|
||||
System.out.println("SystemClassReflectionInvestigator: " + classname + " uses " + owner + "." + name + desc);
|
||||
System.out.println("SystemClassReflectionInvestigator: " + classname + " uses " + owner + "."
|
||||
+ name + desc);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@@ -106,7 +114,8 @@ public class SystemClassReflectionInvestigator {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc, final boolean itf) {
|
||||
public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc,
|
||||
final boolean itf) {
|
||||
if (!GlobalConfiguration.interceptReflection || rewriteReflectiveCall(opcode, owner, name, desc)) {
|
||||
return;
|
||||
}
|
||||
@@ -134,4 +143,4 @@ public class SystemClassReflectionInvestigator {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
@@ -28,13 +29,14 @@ import org.objectweb.asm.MethodVisitor;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
|
||||
/**
|
||||
* This is a special rewriter that should be used on system classes that are using reflection. These classes are loader above the
|
||||
* agent code and so cannot use the agent code directly (they can't see the classes). In these situations we will do some rewriting
|
||||
* that will only use other system types. How can that work? Well the affected types are modified to expose a static field (per
|
||||
* reflective API used), these static fields are set by springloaded during later startup and then are available for access from the
|
||||
* rewritten system class code.
|
||||
* This is a special rewriter that should be used on system classes that are using reflection. These classes are loader
|
||||
* above the agent code and so cannot use the agent code directly (they can't see the classes). In these situations we
|
||||
* will do some rewriting that will only use other system types. How can that work? Well the affected types are modified
|
||||
* to expose a static field (per reflective API used), these static fields are set by springloaded during later startup
|
||||
* and then are available for access from the rewritten system class code.
|
||||
* <p>
|
||||
* There is a null check in the injected method for cases where everything runs even sooner than can be plugged by SpringLoaded.
|
||||
* There is a null check in the injected method for cases where everything runs even sooner than can be plugged by
|
||||
* SpringLoaded.
|
||||
* <p>
|
||||
* The following are implemented so far:
|
||||
*
|
||||
@@ -64,8 +66,9 @@ import org.objectweb.asm.Opcodes;
|
||||
* The method hasStaticInitializer(Class) in ObjectStream needs special handling.
|
||||
*
|
||||
* <p>
|
||||
* This class modifies the calls to the reflective APIs, adds the fields and helper methods. The wiring of the SpringLoaded
|
||||
* reflectiveinterceptor into types affected by this rewriter is currently done in SpringLoadedPreProcessor.
|
||||
* This class modifies the calls to the reflective APIs, adds the fields and helper methods. The wiring of the
|
||||
* SpringLoaded reflectiveinterceptor into types affected by this rewriter is currently done in
|
||||
* SpringLoadedPreProcessor.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.7.3
|
||||
@@ -89,6 +92,7 @@ public class SystemClassReflectionRewriter {
|
||||
public static class RewriteResult implements Constants {
|
||||
|
||||
public final byte[] bytes;
|
||||
|
||||
// These bits describe which kinds of reflective things were done in the
|
||||
// type - and so which fields (of the __sl variety) need filling in. For example,
|
||||
// if the JLC_GETDECLAREDFIELDS bit is set, the field __sljlcgdfs must be set
|
||||
@@ -101,7 +105,7 @@ public class SystemClassReflectionRewriter {
|
||||
|
||||
public String summarize() {
|
||||
StringBuilder s = new StringBuilder();
|
||||
s.append((bits & JLC_GETDECLAREDCONSTRUCTORS) != 0 ? "Class.getDeclaredConstructors()":"");
|
||||
s.append((bits & JLC_GETDECLAREDCONSTRUCTORS) != 0 ? "Class.getDeclaredConstructors()" : "");
|
||||
s.append((bits & JLC_GETDECLAREDCONSTRUCTOR) != 0 ? "Class.getDeclaredConstructor()" : "");
|
||||
s.append((bits & JLC_GETCONSTRUCTOR) != 0 ? "Class.getConstructor()" : "");
|
||||
s.append((bits & JLC_GETMODIFIERS) != 0 ? "Class.getModifiers()" : "");
|
||||
@@ -123,8 +127,11 @@ public class SystemClassReflectionRewriter {
|
||||
static class RewriteClassAdaptor extends ClassVisitor implements Constants {
|
||||
|
||||
private ClassWriter cw;
|
||||
|
||||
int bits = 0x0000;
|
||||
|
||||
private String classname;
|
||||
|
||||
private boolean is_jlObjectStream;
|
||||
|
||||
// enum SpecialRewrite { NotSpecial, java_io_ObjectStreamClass_2 };
|
||||
@@ -135,9 +142,9 @@ public class SystemClassReflectionRewriter {
|
||||
String s = new StringBuilder(owner).append(".").append(methodName).toString();
|
||||
return MethodInvokerRewriter.RewriteClassAdaptor.intercepted.contains(s);
|
||||
}
|
||||
|
||||
|
||||
public RewriteClassAdaptor(boolean is_jlObjectStream) {
|
||||
super(ASM5,new ClassWriter(ClassWriter.COMPUTE_MAXS));
|
||||
super(ASM5, new ClassWriter(ClassWriter.COMPUTE_MAXS));
|
||||
cw = (ClassWriter) cv;
|
||||
this.is_jlObjectStream = is_jlObjectStream;
|
||||
if (this.is_jlObjectStream) {
|
||||
@@ -162,21 +169,22 @@ public class SystemClassReflectionRewriter {
|
||||
// special = SpecialRewrite.java_io_ObjectStreamClass_2;
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
static Method m = null;
|
||||
|
||||
@Override
|
||||
public MethodVisitor visitMethod(int flags, String name, String descriptor, String signature, String[] exceptions) {
|
||||
// if (is_jlObjectStream) {
|
||||
// // TODO [serialization] clear those caches in the JVMPlugin
|
||||
// // TODO [serialization] deal with FieldReflectors and changing formats? Maybe leave that for now and assume all the real fields are 'first'?
|
||||
// // TODO [serialization] because not all classes to be serialized are reloadable ones, we'll need to change what we do here, generate the existing native method but an additional one that can delegate to it or call our SL layer
|
||||
// if (name.equals("hasStaticInitializer")) {
|
||||
// bits |= JLOS_HASSTATICINITIALIZER;
|
||||
// SystemClassReflectionGenerator.generateJLObjectStream_hasStaticInitializer(cw, classname);
|
||||
// return null;
|
||||
// }
|
||||
// }
|
||||
public MethodVisitor visitMethod(int flags, String name, String descriptor, String signature,
|
||||
String[] exceptions) {
|
||||
// if (is_jlObjectStream) {
|
||||
// // TODO [serialization] clear those caches in the JVMPlugin
|
||||
// // TODO [serialization] deal with FieldReflectors and changing formats? Maybe leave that for now and assume all the real fields are 'first'?
|
||||
// // TODO [serialization] because not all classes to be serialized are reloadable ones, we'll need to change what we do here, generate the existing native method but an additional one that can delegate to it or call our SL layer
|
||||
// if (name.equals("hasStaticInitializer")) {
|
||||
// bits |= JLOS_HASSTATICINITIALIZER;
|
||||
// SystemClassReflectionGenerator.generateJLObjectStream_hasStaticInitializer(cw, classname);
|
||||
// return null;
|
||||
// }
|
||||
// }
|
||||
MethodVisitor mv = super.visitMethod(flags, name, descriptor, signature, exceptions);
|
||||
return new RewritingMethodAdapter(mv);
|
||||
}
|
||||
@@ -238,12 +246,12 @@ public class SystemClassReflectionRewriter {
|
||||
class RewritingMethodAdapter extends MethodVisitor implements Opcodes, Constants {
|
||||
|
||||
public RewritingMethodAdapter(MethodVisitor mv) {
|
||||
super(ASM5,mv);
|
||||
super(ASM5, mv);
|
||||
}
|
||||
|
||||
/**
|
||||
* The big method for intercepting reflection. It is passed what the original code is trying to do (which method it is
|
||||
* calling) and decides:
|
||||
* The big method for intercepting reflection. It is passed what the original code is trying to do (which
|
||||
* method it is calling) and decides:
|
||||
* <ul>
|
||||
* <li>whether to rewrite it
|
||||
* <li>what method should be called instead
|
||||
@@ -269,7 +277,8 @@ public class SystemClassReflectionRewriter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc, final boolean itf) {
|
||||
public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc,
|
||||
final boolean itf) {
|
||||
if (!GlobalConfiguration.interceptReflection || rewriteReflectiveCall(opcode, owner, name, desc)) {
|
||||
return;
|
||||
}
|
||||
@@ -327,7 +336,7 @@ public class SystemClassReflectionRewriter {
|
||||
}
|
||||
else if (is_jlObjectStream && owner.equals(classname) && name.equals("hasStaticInitializer")) {
|
||||
// Call our interception method generated into this type
|
||||
mv.visitMethodInsn(INVOKESTATIC,classname,jloObjectStream_hasInitializerMethod,desc);
|
||||
mv.visitMethodInsn(INVOKESTATIC, classname, jloObjectStream_hasInitializerMethod, desc);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -340,80 +349,96 @@ public class SystemClassReflectionRewriter {
|
||||
bits |= JLC_GETDECLAREDFIELDS;
|
||||
mv.visitMethodInsn(INVOKESTATIC, classname, jlcgdfs, jlcgdfsDescriptor, false);
|
||||
return true;
|
||||
} else if (name.equals("getDeclaredField")) {
|
||||
}
|
||||
else if (name.equals("getDeclaredField")) {
|
||||
// stack on arrival: <Class instance> <String fieldname>
|
||||
bits |= JLC_GETDECLAREDFIELD;
|
||||
mv.visitMethodInsn(INVOKESTATIC, classname, jlcgdf, jlcgdfDescriptor, false);
|
||||
return true;
|
||||
} else if (name.equals("getField")) {
|
||||
}
|
||||
else if (name.equals("getField")) {
|
||||
// stack on arrival: <Class instance> <String fieldname>
|
||||
bits |= JLC_GETFIELD;
|
||||
mv.visitMethodInsn(INVOKESTATIC, classname, jlcgf, jlcgfDescriptor, false);
|
||||
return true;
|
||||
} else if (name.equals("getDeclaredMethods")) {
|
||||
}
|
||||
else if (name.equals("getDeclaredMethods")) {
|
||||
// stack on arrival: <Class instance>
|
||||
bits |= JLC_GETDECLAREDMETHODS;
|
||||
mv.visitMethodInsn(INVOKESTATIC, classname, jlcgdms, jlcgdmsDescriptor, false);
|
||||
return true;
|
||||
} else if (name.equals("getDeclaredMethod")) {
|
||||
}
|
||||
else if (name.equals("getDeclaredMethod")) {
|
||||
// stack on arrival: <Class instance> <String methodname> <Class[] paramTypes>
|
||||
bits |= JLC_GETDECLAREDMETHOD;
|
||||
mv.visitMethodInsn(INVOKESTATIC, classname, jlcgdm, jlcgdmDescriptor, false);
|
||||
return true;
|
||||
} else if (name.equals("getMethod")) {
|
||||
}
|
||||
else if (name.equals("getMethod")) {
|
||||
// stack on arrival: <Class instance> <String methodname> <Class[] paramTypes>
|
||||
bits |= JLC_GETMETHOD;
|
||||
mv.visitMethodInsn(INVOKESTATIC, classname, jlcgm, jlcgmDescriptor, false);
|
||||
return true;
|
||||
} else if (name.equals("getDeclaredConstructor")) {
|
||||
}
|
||||
else if (name.equals("getDeclaredConstructor")) {
|
||||
// stack on arrival: <Class instance> <Class[] paramTypes>
|
||||
bits |= JLC_GETDECLAREDCONSTRUCTOR;
|
||||
mv.visitMethodInsn(INVOKESTATIC, classname, jlcgdc, jlcgdcDescriptor, false);
|
||||
return true;
|
||||
} else if (name.equals("getDeclaredConstructors")) {
|
||||
}
|
||||
else if (name.equals("getDeclaredConstructors")) {
|
||||
// stack on arrival: <Class instance>
|
||||
bits |= JLC_GETDECLAREDCONSTRUCTORS;
|
||||
mv.visitMethodInsn(INVOKESTATIC, classname, jlcGetDeclaredConstructorsMember,jlcGetDeclaredConstructorsDescriptor, false);
|
||||
mv.visitMethodInsn(INVOKESTATIC, classname, jlcGetDeclaredConstructorsMember,
|
||||
jlcGetDeclaredConstructorsDescriptor, false);
|
||||
return true;
|
||||
} else if (name.equals("getConstructor")) {
|
||||
}
|
||||
else if (name.equals("getConstructor")) {
|
||||
// stack on arrival: <Class instance> <Class[] paramTypes>
|
||||
bits |= JLC_GETCONSTRUCTOR;
|
||||
mv.visitMethodInsn(INVOKESTATIC, classname, jlcgc, jlcgcDescriptor, false);
|
||||
return true;
|
||||
} else if (name.equals("getModifiers")) {
|
||||
}
|
||||
else if (name.equals("getModifiers")) {
|
||||
// stack on arrival: <Class instance>
|
||||
bits |= JLC_GETMODIFIERS;
|
||||
mv.visitMethodInsn(INVOKESTATIC, classname, jlcgmods, jlcgmodsDescriptor, false);
|
||||
return true;
|
||||
} else if (name.equals("getMethods")) {
|
||||
}
|
||||
else if (name.equals("getMethods")) {
|
||||
// stack on arrival: <class instance>
|
||||
bits |= JLC_GETMETHODS;
|
||||
mv.visitMethodInsn(INVOKESTATIC, classname, jlcgms, jlcgmsDescriptor, false);
|
||||
return true;
|
||||
} else if (name.equals("newInstance")) {
|
||||
}
|
||||
else if (name.equals("newInstance")) {
|
||||
// TODO determine if this actually needs rewriting? Just catching in this if clause to avoid the message
|
||||
return false;
|
||||
}
|
||||
} else if (owner.equals("java/lang/reflect/Constructor")) {
|
||||
}
|
||||
else if (owner.equals("java/lang/reflect/Constructor")) {
|
||||
if (name.equals("newInstance")) {
|
||||
// catching to avoid message
|
||||
// seen: in Proxy Constructor.newInstance() is used on the newly created proxy class - we don't need to intercept that
|
||||
return false;
|
||||
}
|
||||
} else if (owner.equals("java/lang/reflect/Method")) {
|
||||
}
|
||||
else if (owner.equals("java/lang/reflect/Method")) {
|
||||
if (name.equals("invoke")) {
|
||||
bits |= JLRM_INVOKE;
|
||||
// stack on arrival: <Method> <target instance> <parameters array>
|
||||
mv.visitMethodInsn(INVOKESTATIC, classname, jlrmInvokeMember, jlrmInvokeDescriptor);
|
||||
return true;
|
||||
}
|
||||
} else if (owner.equals("java/lang/reflect/Field")) {
|
||||
}
|
||||
else if (owner.equals("java/lang/reflect/Field")) {
|
||||
if (name.equals("get")) {
|
||||
bits |= JLRF_GET;
|
||||
// stack on arrival: <Field> <target instance>
|
||||
mv.visitMethodInsn(INVOKESTATIC, classname, jlrfGetMember, jlrfGetDescriptor);
|
||||
return true;
|
||||
} else if (name.equals("getLong")) {
|
||||
}
|
||||
else if (name.equals("getLong")) {
|
||||
bits |= JLRF_GETLONG;
|
||||
// stack on arrival: <Field> <target instance>
|
||||
mv.visitMethodInsn(INVOKESTATIC, classname, jlrfGetLongMember, jlrfGetLongDescriptor);
|
||||
@@ -427,6 +452,7 @@ public class SystemClassReflectionRewriter {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This helper class will generate the fields/methods in the system classes that are being rewritten.
|
||||
*/
|
||||
@@ -537,61 +563,62 @@ class SystemClassReflectionGenerator implements Constants {
|
||||
// return 0;
|
||||
// }
|
||||
// }
|
||||
|
||||
// public static Method __sljlcgdcs;
|
||||
// private static Constructor[] __sljlcgdcs(Class<?> clazz) {
|
||||
// if (__sljlcgdcs == null) {
|
||||
// return clazz.getDeclaredConstructors();
|
||||
// }
|
||||
// try {
|
||||
// return (Constructor[])__sljlcgdcs.invoke(null,clazz);
|
||||
// } catch (Exception e) {
|
||||
// return null;
|
||||
// }
|
||||
// }
|
||||
|
||||
// public static Method __sljlrmi;
|
||||
// private static Object __sljlrmi(Method method, Object instance, Object[] args) throws InvocationTargetException, IllegalAccessException {
|
||||
// if (__sljlrmi == null) {
|
||||
// return method.invoke(instance,args);
|
||||
// }
|
||||
// try {
|
||||
// return __sljlrmi.invoke(null, method, instance, args);
|
||||
// } catch (Exception e) {
|
||||
// return null;
|
||||
// }
|
||||
// }
|
||||
|
||||
// public static Method __sljlrfg;
|
||||
// private static Object __sljlrfg(Field field, Object instance) throws IllegalArgumentException, IllegalAccessException {
|
||||
// if (__sljlrfg == null) {
|
||||
// return field.get(instance);
|
||||
// }
|
||||
// try {
|
||||
// return __sljlrfg.invoke(null, field,instance);
|
||||
// } catch (Exception e) {
|
||||
// return null;
|
||||
// }
|
||||
// }
|
||||
|
||||
// public static Method __sljlrfgl;
|
||||
// private static long __sljlrfgl(Field field, Object instance) throws IllegalArgumentException, IllegalAccessException {
|
||||
// if (__sljlrfgl == null) {
|
||||
// return field.getLong(instance);
|
||||
// }
|
||||
// try {
|
||||
// return (Long)__sljlrfgl.invoke(null, field, instance);
|
||||
// } catch (Exception e) {
|
||||
// return 0;
|
||||
// }
|
||||
// }
|
||||
// public static Method __sljlcgdcs;
|
||||
// private static Constructor[] __sljlcgdcs(Class<?> clazz) {
|
||||
// if (__sljlcgdcs == null) {
|
||||
// return clazz.getDeclaredConstructors();
|
||||
// }
|
||||
// try {
|
||||
// return (Constructor[])__sljlcgdcs.invoke(null,clazz);
|
||||
// } catch (Exception e) {
|
||||
// return null;
|
||||
// }
|
||||
// }
|
||||
|
||||
// public static Method __sljlrmi;
|
||||
// private static Object __sljlrmi(Method method, Object instance, Object[] args) throws InvocationTargetException, IllegalAccessException {
|
||||
// if (__sljlrmi == null) {
|
||||
// return method.invoke(instance,args);
|
||||
// }
|
||||
// try {
|
||||
// return __sljlrmi.invoke(null, method, instance, args);
|
||||
// } catch (Exception e) {
|
||||
// return null;
|
||||
// }
|
||||
// }
|
||||
|
||||
// public static Method __sljlrfg;
|
||||
// private static Object __sljlrfg(Field field, Object instance) throws IllegalArgumentException, IllegalAccessException {
|
||||
// if (__sljlrfg == null) {
|
||||
// return field.get(instance);
|
||||
// }
|
||||
// try {
|
||||
// return __sljlrfg.invoke(null, field,instance);
|
||||
// } catch (Exception e) {
|
||||
// return null;
|
||||
// }
|
||||
// }
|
||||
|
||||
// public static Method __sljlrfgl;
|
||||
// private static long __sljlrfgl(Field field, Object instance) throws IllegalArgumentException, IllegalAccessException {
|
||||
// if (__sljlrfgl == null) {
|
||||
// return field.getLong(instance);
|
||||
// }
|
||||
// try {
|
||||
// return (Long)__sljlrfgl.invoke(null, field, instance);
|
||||
// } catch (Exception e) {
|
||||
// return 0;
|
||||
// }
|
||||
// }
|
||||
|
||||
public static void generateJLRF_GetLong(ClassWriter cw, String classname) {
|
||||
FieldVisitor fv = cw.visitField(ACC_PUBLIC + ACC_STATIC, jlrfGetLongMember, "Ljava/lang/reflect/Method;", null, null);
|
||||
FieldVisitor fv = cw.visitField(ACC_PUBLIC + ACC_STATIC, jlrfGetLongMember, "Ljava/lang/reflect/Method;", null,
|
||||
null);
|
||||
fv.visitEnd();
|
||||
|
||||
MethodVisitor mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC, jlrfGetLongMember, jlrfGetLongDescriptor,
|
||||
null, new String[]{"java/lang/IllegalAccessException","java/lang/IllegalArgumentException"});
|
||||
null, new String[] { "java/lang/IllegalAccessException", "java/lang/IllegalArgumentException" });
|
||||
mv.visitCode();
|
||||
Label l0 = new Label();
|
||||
Label l1 = new Label();
|
||||
@@ -624,7 +651,7 @@ class SystemClassReflectionGenerator implements Constants {
|
||||
"(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;");
|
||||
mv.visitLabel(l1);
|
||||
mv.visitTypeInsn(CHECKCAST, "java/lang/Long");
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL,"java/lang/Long","longValue","()J");
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J");
|
||||
mv.visitInsn(LRETURN);
|
||||
mv.visitLabel(l2);
|
||||
mv.visitVarInsn(ASTORE, 2);
|
||||
@@ -651,10 +678,12 @@ class SystemClassReflectionGenerator implements Constants {
|
||||
*/
|
||||
public static void generateJLObjectStream_hasStaticInitializer(
|
||||
ClassWriter cw, String classname) {
|
||||
FieldVisitor fv = cw.visitField(ACC_PUBLIC + ACC_STATIC, jloObjectStream_hasInitializerMethod, "Ljava/lang/reflect/Method;", null, null);
|
||||
FieldVisitor fv = cw.visitField(ACC_PUBLIC + ACC_STATIC, jloObjectStream_hasInitializerMethod,
|
||||
"Ljava/lang/reflect/Method;", null, null);
|
||||
fv.visitEnd();
|
||||
|
||||
MethodVisitor mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC, jloObjectStream_hasInitializerMethod, "(Ljava/lang/Class;)Z", null, null);
|
||||
MethodVisitor mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC, jloObjectStream_hasInitializerMethod,
|
||||
"(Ljava/lang/Class;)Z", null, null);
|
||||
mv.visitCode();
|
||||
Label l0 = new Label();
|
||||
Label l1 = new Label();
|
||||
@@ -664,7 +693,7 @@ class SystemClassReflectionGenerator implements Constants {
|
||||
mv.visitFieldInsn(GETSTATIC, classname, jloObjectStream_hasInitializerMethod, "Ljava/lang/reflect/Method;");
|
||||
mv.visitInsn(ACONST_NULL);
|
||||
mv.visitInsn(ICONST_1);
|
||||
mv.visitTypeInsn(ANEWARRAY,"java/lang/Object");
|
||||
mv.visitTypeInsn(ANEWARRAY, "java/lang/Object");
|
||||
mv.visitInsn(DUP);
|
||||
mv.visitInsn(ICONST_0);
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
@@ -678,19 +707,20 @@ class SystemClassReflectionGenerator implements Constants {
|
||||
mv.visitLabel(l2);
|
||||
// If not a reloadable type, we'll end up here (the method we called threw IllegalStateException), just make that native method call
|
||||
mv.visitVarInsn(ASTORE, 1);
|
||||
mv.visitVarInsn(ALOAD,0);
|
||||
mv.visitMethodInsn(INVOKESTATIC, classname, "hasStaticInitializer","(Ljava/lang/Class;)Z");
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitMethodInsn(INVOKESTATIC, classname, "hasStaticInitializer", "(Ljava/lang/Class;)Z");
|
||||
mv.visitInsn(IRETURN);
|
||||
mv.visitMaxs(3, 1);
|
||||
mv.visitEnd();
|
||||
}
|
||||
|
||||
public static void generateJLRF_Get(ClassWriter cw, String classname) {
|
||||
FieldVisitor fv = cw.visitField(ACC_PUBLIC + ACC_STATIC, jlrfGetMember, "Ljava/lang/reflect/Method;", null, null);
|
||||
FieldVisitor fv = cw.visitField(ACC_PUBLIC + ACC_STATIC, jlrfGetMember, "Ljava/lang/reflect/Method;", null,
|
||||
null);
|
||||
fv.visitEnd();
|
||||
|
||||
MethodVisitor mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC, jlrfGetMember, jlrfGetDescriptor,
|
||||
null, new String[]{"java/lang/IllegalAccessException","java/lang/IllegalArgumentException"});
|
||||
null, new String[] { "java/lang/IllegalAccessException", "java/lang/IllegalArgumentException" });
|
||||
mv.visitCode();
|
||||
Label l0 = new Label();
|
||||
Label l1 = new Label();
|
||||
@@ -704,7 +734,8 @@ class SystemClassReflectionGenerator implements Constants {
|
||||
mv.visitLabel(l4);
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitVarInsn(ALOAD, 1);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/Field", "get", "(Ljava/lang/Object;)Ljava/lang/Object;", false);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/Field", "get", "(Ljava/lang/Object;)Ljava/lang/Object;",
|
||||
false);
|
||||
mv.visitInsn(ARETURN);
|
||||
mv.visitLabel(l0);
|
||||
mv.visitFieldInsn(GETSTATIC, classname, jlrfGetMember, "Ljava/lang/reflect/Method;");
|
||||
@@ -736,13 +767,15 @@ class SystemClassReflectionGenerator implements Constants {
|
||||
mv.visitMaxs(8, 4);
|
||||
mv.visitEnd();
|
||||
}
|
||||
|
||||
|
||||
public static void generateJLRM_Invoke(ClassWriter cw, String classname) {
|
||||
FieldVisitor fv = cw.visitField(ACC_PUBLIC + ACC_STATIC, jlrmInvokeMember, "Ljava/lang/reflect/Method;", null, null);
|
||||
FieldVisitor fv = cw.visitField(ACC_PUBLIC + ACC_STATIC, jlrmInvokeMember, "Ljava/lang/reflect/Method;", null,
|
||||
null);
|
||||
fv.visitEnd();
|
||||
|
||||
MethodVisitor mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC, jlrmInvokeMember, jlrmInvokeDescriptor,
|
||||
null, new String[]{"java/lang/IllegalAccessException","java/lang/reflect/InvocationTargetException"});
|
||||
null,
|
||||
new String[] { "java/lang/IllegalAccessException", "java/lang/reflect/InvocationTargetException" });
|
||||
mv.visitCode();
|
||||
Label l0 = new Label();
|
||||
Label l1 = new Label();
|
||||
@@ -757,7 +790,8 @@ class SystemClassReflectionGenerator implements Constants {
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitVarInsn(ALOAD, 1);
|
||||
mv.visitVarInsn(ALOAD, 2);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/Method", "invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;", false);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/Method", "invoke",
|
||||
"(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;", false);
|
||||
mv.visitInsn(ARETURN);
|
||||
mv.visitLabel(l0);
|
||||
mv.visitFieldInsn(GETSTATIC, classname, jlrmInvokeMember, "Ljava/lang/reflect/Method;");
|
||||
@@ -793,12 +827,14 @@ class SystemClassReflectionGenerator implements Constants {
|
||||
mv.visitMaxs(8, 4);
|
||||
mv.visitEnd();
|
||||
}
|
||||
|
||||
|
||||
public static void generateJLC_GetDeclaredConstructors(ClassWriter cw, String classname) {
|
||||
FieldVisitor fv = cw.visitField(ACC_PUBLIC + ACC_STATIC, jlcGetDeclaredConstructorsMember, "Ljava/lang/reflect/Method;", null, null);
|
||||
FieldVisitor fv = cw.visitField(ACC_PUBLIC + ACC_STATIC, jlcGetDeclaredConstructorsMember,
|
||||
"Ljava/lang/reflect/Method;", null, null);
|
||||
fv.visitEnd();
|
||||
|
||||
MethodVisitor mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC, jlcGetDeclaredConstructorsMember, jlcGetDeclaredConstructorsDescriptor,
|
||||
MethodVisitor mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC, jlcGetDeclaredConstructorsMember,
|
||||
jlcGetDeclaredConstructorsDescriptor,
|
||||
null, null);
|
||||
mv.visitCode();
|
||||
Label l0 = new Label();
|
||||
@@ -812,7 +848,8 @@ class SystemClassReflectionGenerator implements Constants {
|
||||
Label l4 = new Label();
|
||||
mv.visitLabel(l4);
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getDeclaredConstructors", "()[Ljava/lang/reflect/Constructor;", false);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getDeclaredConstructors",
|
||||
"()[Ljava/lang/reflect/Constructor;", false);
|
||||
mv.visitInsn(ARETURN);
|
||||
mv.visitLabel(l0);
|
||||
mv.visitFieldInsn(GETSTATIC, classname, jlcGetDeclaredConstructorsMember, "Ljava/lang/reflect/Method;");
|
||||
@@ -845,7 +882,8 @@ class SystemClassReflectionGenerator implements Constants {
|
||||
}
|
||||
|
||||
public static void generateJLCGMODS(ClassWriter cw, String classname) {
|
||||
FieldVisitor fv = cw.visitField(ACC_PUBLIC + ACC_STATIC, "__sljlcgmods", "Ljava/lang/reflect/Method;", null, null);
|
||||
FieldVisitor fv = cw.visitField(ACC_PUBLIC + ACC_STATIC, "__sljlcgmods", "Ljava/lang/reflect/Method;", null,
|
||||
null);
|
||||
fv.visitEnd();
|
||||
|
||||
MethodVisitor mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC, "__sljlcgmods", "(Ljava/lang/Class;)I",
|
||||
@@ -946,18 +984,21 @@ class SystemClassReflectionGenerator implements Constants {
|
||||
Label l6 = new Label();
|
||||
mv.visitLabel(l6);
|
||||
mv.visitVarInsn(ALOAD, 2);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/InvocationTargetException", "printStackTrace", "()V", false);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/InvocationTargetException", "printStackTrace", "()V",
|
||||
false);
|
||||
Label l7 = new Label();
|
||||
mv.visitLabel(l7);
|
||||
mv.visitVarInsn(ALOAD, 2);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/InvocationTargetException", "getCause", "()Ljava/lang/Throwable;", false);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/InvocationTargetException", "getCause",
|
||||
"()Ljava/lang/Throwable;", false);
|
||||
mv.visitTypeInsn(INSTANCEOF, "java/lang/NoSuchMethodException");
|
||||
Label l8 = new Label();
|
||||
mv.visitJumpInsn(IFEQ, l8);
|
||||
Label l9 = new Label();
|
||||
mv.visitLabel(l9);
|
||||
mv.visitVarInsn(ALOAD, 2);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/InvocationTargetException", "getCause", "()Ljava/lang/Throwable;", false);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/InvocationTargetException", "getCause",
|
||||
"()Ljava/lang/Throwable;", false);
|
||||
mv.visitTypeInsn(CHECKCAST, "java/lang/NoSuchMethodException");
|
||||
mv.visitInsn(ATHROW);
|
||||
mv.visitLabel(l3);
|
||||
@@ -1028,18 +1069,21 @@ class SystemClassReflectionGenerator implements Constants {
|
||||
Label l6 = new Label();
|
||||
mv.visitLabel(l6);
|
||||
mv.visitVarInsn(ALOAD, 2);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/InvocationTargetException", "printStackTrace", "()V", false);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/InvocationTargetException", "printStackTrace", "()V",
|
||||
false);
|
||||
Label l7 = new Label();
|
||||
mv.visitLabel(l7);
|
||||
mv.visitVarInsn(ALOAD, 2);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/InvocationTargetException", "getCause", "()Ljava/lang/Throwable;", false);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/InvocationTargetException", "getCause",
|
||||
"()Ljava/lang/Throwable;", false);
|
||||
mv.visitTypeInsn(INSTANCEOF, "java/lang/NoSuchMethodException");
|
||||
Label l8 = new Label();
|
||||
mv.visitJumpInsn(IFEQ, l8);
|
||||
Label l9 = new Label();
|
||||
mv.visitLabel(l9);
|
||||
mv.visitVarInsn(ALOAD, 2);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/InvocationTargetException", "getCause", "()Ljava/lang/Throwable;", false);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/InvocationTargetException", "getCause",
|
||||
"()Ljava/lang/Throwable;", false);
|
||||
mv.visitTypeInsn(CHECKCAST, "java/lang/NoSuchMethodException");
|
||||
mv.visitInsn(ATHROW);
|
||||
mv.visitLabel(l3);
|
||||
@@ -1120,14 +1164,16 @@ class SystemClassReflectionGenerator implements Constants {
|
||||
Label l7 = new Label();
|
||||
mv.visitLabel(l7);
|
||||
mv.visitVarInsn(ALOAD, 3);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/InvocationTargetException", "getCause", "()Ljava/lang/Throwable;", false);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/InvocationTargetException", "getCause",
|
||||
"()Ljava/lang/Throwable;", false);
|
||||
mv.visitTypeInsn(INSTANCEOF, "java/lang/NoSuchMethodException");
|
||||
Label l8 = new Label();
|
||||
mv.visitJumpInsn(IFEQ, l8);
|
||||
Label l9 = new Label();
|
||||
mv.visitLabel(l9);
|
||||
mv.visitVarInsn(ALOAD, 3);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/InvocationTargetException", "getCause", "()Ljava/lang/Throwable;", false);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/InvocationTargetException", "getCause",
|
||||
"()Ljava/lang/Throwable;", false);
|
||||
mv.visitTypeInsn(CHECKCAST, "java/lang/NoSuchMethodException");
|
||||
mv.visitInsn(ATHROW);
|
||||
mv.visitLabel(l3);
|
||||
@@ -1153,9 +1199,11 @@ class SystemClassReflectionGenerator implements Constants {
|
||||
public static void generateJLCMethod(ClassWriter cw, String classname, String operation) {
|
||||
if (operation.equals("getDeclaredMethod")) {
|
||||
generateJLCMethod(cw, classname, "__sljlcgdm", "getDeclaredMethod");
|
||||
} else if (operation.equals("getMethod")) {
|
||||
}
|
||||
else if (operation.equals("getMethod")) {
|
||||
generateJLCMethod(cw, classname, "__sljlcgm", "getMethod");
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
throw new IllegalStateException("nyi:" + operation);
|
||||
}
|
||||
}
|
||||
@@ -1163,9 +1211,11 @@ class SystemClassReflectionGenerator implements Constants {
|
||||
public static void generateJLC(ClassWriter cw, String classname, String operation) {
|
||||
if (operation.equals("getDeclaredField")) {
|
||||
generateJLCGDF(cw, classname, "__sljlcgdf", "getDeclaredField");
|
||||
} else if (operation.equals("getField")) {
|
||||
}
|
||||
else if (operation.equals("getField")) {
|
||||
generateJLCGDF(cw, classname, "__sljlcgf", "getField");
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
throw new IllegalStateException("nyi:" + operation);
|
||||
}
|
||||
}
|
||||
@@ -1192,7 +1242,8 @@ class SystemClassReflectionGenerator implements Constants {
|
||||
mv.visitLabel(l5);
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitVarInsn(ALOAD, 1);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", methodname, "(Ljava/lang/String;)Ljava/lang/reflect/Field;", false);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", methodname,
|
||||
"(Ljava/lang/String;)Ljava/lang/reflect/Field;", false);
|
||||
mv.visitInsn(ARETURN);
|
||||
mv.visitLabel(l0);
|
||||
mv.visitFieldInsn(GETSTATIC, classname, fieldname, "Ljava/lang/reflect/Method;");
|
||||
@@ -1217,14 +1268,16 @@ class SystemClassReflectionGenerator implements Constants {
|
||||
Label l6 = new Label();
|
||||
mv.visitLabel(l6);
|
||||
mv.visitVarInsn(ALOAD, 2);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/InvocationTargetException", "getCause", "()Ljava/lang/Throwable;", false);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/InvocationTargetException", "getCause",
|
||||
"()Ljava/lang/Throwable;", false);
|
||||
mv.visitTypeInsn(INSTANCEOF, "java/lang/NoSuchFieldException");
|
||||
Label l7 = new Label();
|
||||
mv.visitJumpInsn(IFEQ, l7);
|
||||
Label l8 = new Label();
|
||||
mv.visitLabel(l8);
|
||||
mv.visitVarInsn(ALOAD, 2);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/InvocationTargetException", "getCause", "()Ljava/lang/Throwable;", false);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/InvocationTargetException", "getCause",
|
||||
"()Ljava/lang/Throwable;", false);
|
||||
mv.visitTypeInsn(CHECKCAST, "java/lang/NoSuchFieldException");
|
||||
mv.visitInsn(ATHROW);
|
||||
mv.visitLabel(l3);
|
||||
@@ -1244,9 +1297,11 @@ class SystemClassReflectionGenerator implements Constants {
|
||||
public static void generateJLCGetXXXMethods(ClassWriter cw, String classname, String variant) {
|
||||
if (variant.equals("getDeclaredMethods")) {
|
||||
generateJLCGDMS(cw, classname, "__sljlcgdms", "getDeclaredMethods");
|
||||
} else if (variant.equals("getMethods")) {
|
||||
}
|
||||
else if (variant.equals("getMethods")) {
|
||||
generateJLCGDMS(cw, classname, "__sljlcgms", "getMethods");
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
throw new IllegalStateException(variant);
|
||||
}
|
||||
}
|
||||
@@ -1256,7 +1311,8 @@ class SystemClassReflectionGenerator implements Constants {
|
||||
FieldVisitor fv = cw.visitField(ACC_PUBLIC + ACC_STATIC, field, "Ljava/lang/reflect/Method;", null, null);
|
||||
fv.visitEnd();
|
||||
|
||||
MethodVisitor mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC, field, "(Ljava/lang/Class;)[Ljava/lang/reflect/Method;", null,
|
||||
MethodVisitor mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC, field,
|
||||
"(Ljava/lang/Class;)[Ljava/lang/reflect/Method;", null,
|
||||
null);
|
||||
mv.visitCode();
|
||||
Label l0 = new Label();
|
||||
@@ -1304,7 +1360,8 @@ class SystemClassReflectionGenerator implements Constants {
|
||||
FieldVisitor fv = cw.visitField(ACC_PUBLIC_STATIC, "__sljlcgdfs", "Ljava/lang/reflect/Method;", null, null);
|
||||
fv.visitEnd();
|
||||
|
||||
MethodVisitor mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC, "__sljlcgdfs", "(Ljava/lang/Class;)[Ljava/lang/reflect/Field;",
|
||||
MethodVisitor mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC, "__sljlcgdfs",
|
||||
"(Ljava/lang/Class;)[Ljava/lang/reflect/Field;",
|
||||
null, null);
|
||||
mv.visitCode();
|
||||
Label l0 = new Label();
|
||||
@@ -1354,4 +1411,4 @@ class SystemClassReflectionGenerator implements Constants {
|
||||
// mv.visitLdcInsn(message);
|
||||
// mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V");
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded;
|
||||
|
||||
import java.io.File;
|
||||
@@ -29,9 +30,9 @@ import org.springsource.loaded.agent.ReloadDecision;
|
||||
|
||||
/**
|
||||
* This is not a 'default' plugin, it must be registered by specifying the following on the springloaded option:
|
||||
* "plugins=org.springsource.loaded.SystemPropertyConfiguredIsReloadableTypePlugin". The behaviour of this plugin is configured by a
|
||||
* system property that is constantly checked (not cached), this property determines whether files in certain paths are reloadable
|
||||
* or not.
|
||||
* "plugins=org.springsource.loaded.SystemPropertyConfiguredIsReloadableTypePlugin". The behaviour of this plugin is
|
||||
* configured by a system property that is constantly checked (not cached), this property determines whether files in
|
||||
* certain paths are reloadable or not.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.7.3
|
||||
@@ -43,8 +44,10 @@ public class SystemPropertyConfiguredIsReloadableTypePlugin implements IsReloada
|
||||
static {
|
||||
boolean value = false;
|
||||
try {
|
||||
value = System.getProperty("springloaded.directoriesContainingReloadableCode.debug", "false").equalsIgnoreCase("true");
|
||||
} catch (Exception e) {
|
||||
value = System.getProperty("springloaded.directoriesContainingReloadableCode.debug", "false").equalsIgnoreCase(
|
||||
"true");
|
||||
}
|
||||
catch (Exception e) {
|
||||
|
||||
}
|
||||
debug = value;
|
||||
@@ -57,11 +60,14 @@ public class SystemPropertyConfiguredIsReloadableTypePlugin implements IsReloada
|
||||
}
|
||||
|
||||
private List<String> includes = new ArrayList<String>();
|
||||
|
||||
private List<String> excludes = new ArrayList<String>();
|
||||
|
||||
private String mostRecentReloadableDirs = null;
|
||||
|
||||
// TODO need try/catch protection when calling plugins, in case of bad ones
|
||||
public ReloadDecision shouldBeMadeReloadable(TypeRegistry typeRegistry, String typename, ProtectionDomain protectionDomain, byte[] bytes) {
|
||||
public ReloadDecision shouldBeMadeReloadable(TypeRegistry typeRegistry, String typename,
|
||||
ProtectionDomain protectionDomain, byte[] bytes) {
|
||||
if (debug) {
|
||||
System.out.println("SystemPropertyConfiguredIsReloadableTypePlugin: entered, for typename " + typename);
|
||||
}
|
||||
@@ -74,7 +80,8 @@ public class SystemPropertyConfiguredIsReloadableTypePlugin implements IsReloada
|
||||
}
|
||||
if (reloadableDirs == null) {
|
||||
return ReloadDecision.PASS;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
if (mostRecentReloadableDirs != reloadableDirs) {
|
||||
synchronized (includes) {
|
||||
if (mostRecentReloadableDirs != reloadableDirs) {
|
||||
@@ -87,7 +94,8 @@ public class SystemPropertyConfiguredIsReloadableTypePlugin implements IsReloada
|
||||
boolean isNot = nextDir.charAt(0) == '!';
|
||||
if (isNot) {
|
||||
excludes.add(nextDir.substring(1));
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
includes.add(nextDir);
|
||||
}
|
||||
}
|
||||
@@ -105,7 +113,8 @@ public class SystemPropertyConfiguredIsReloadableTypePlugin implements IsReloada
|
||||
// if (debug) {
|
||||
// System.out.println("SystemPropertyConfiguredIsReloadableTypePlugin: " + typename + " does not have a codeSource");
|
||||
// }
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
// May have to do something special for CGLIB types
|
||||
// These will have a type name of something like: grails/plugin/springsecurity/SpringSecurityService$$EnhancerByCGLIB$$8f956be2
|
||||
// But a codesource location of file:/Users/aclement/.m2/repository/org/springframework/spring-core/3.2.5.RELEASE/spring-core-3.2.5.RELEASE.jar
|
||||
@@ -125,7 +134,8 @@ public class SystemPropertyConfiguredIsReloadableTypePlugin implements IsReloada
|
||||
}
|
||||
|
||||
if (debug) {
|
||||
System.out.println("SystemPropertyConfiguredIsReloadableTypePlugin: " + typename + " codeSource.getLocation() is "
|
||||
System.out.println("SystemPropertyConfiguredIsReloadableTypePlugin: " + typename
|
||||
+ " codeSource.getLocation() is "
|
||||
+ codeSource.getLocation());
|
||||
}
|
||||
}
|
||||
@@ -144,7 +154,7 @@ public class SystemPropertyConfiguredIsReloadableTypePlugin implements IsReloada
|
||||
return ReloadDecision.NO;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for (String include : includes) {
|
||||
if (path.contains(include)) {
|
||||
if (debug) {
|
||||
@@ -155,7 +165,7 @@ public class SystemPropertyConfiguredIsReloadableTypePlugin implements IsReloada
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// StringTokenizer st = new StringTokenizer(reloadableDirs, ",");
|
||||
// while (st.hasMoreTokens()) {
|
||||
// String nextDir = st.nextToken();
|
||||
@@ -178,17 +188,22 @@ public class SystemPropertyConfiguredIsReloadableTypePlugin implements IsReloada
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
} catch (URISyntaxException e) {
|
||||
}
|
||||
catch (URISyntaxException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IllegalArgumentException iae) {
|
||||
}
|
||||
catch (IllegalArgumentException iae) {
|
||||
// grails-9654
|
||||
// On File.<init>() call:
|
||||
// IAE: URI is not hierarchical
|
||||
if (debug) {
|
||||
try {
|
||||
System.out.println("IllegalArgumentException: URI is not hierarchical, uri is "+codeSource.getLocation().toURI());
|
||||
} catch (URISyntaxException use) {
|
||||
System.out.println("IllegalArgumentException: URI is not hierarchical, uri is "+codeSource.getLocation());
|
||||
System.out.println("IllegalArgumentException: URI is not hierarchical, uri is "
|
||||
+ codeSource.getLocation().toURI());
|
||||
}
|
||||
catch (URISyntaxException use) {
|
||||
System.out.println("IllegalArgumentException: URI is not hierarchical, uri is "
|
||||
+ codeSource.getLocation());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded;
|
||||
|
||||
import java.util.HashMap;
|
||||
@@ -23,48 +24,73 @@ import org.objectweb.asm.tree.FieldNode;
|
||||
import org.objectweb.asm.tree.MethodNode;
|
||||
|
||||
/**
|
||||
* Encapsulates what has changed between two versions of a type - it is used to determine if a reload is possible and also passed on
|
||||
* events related to reloading so that the plugins can tailor their actions based on what prevented reloading. The various
|
||||
* <tt>hasXXX</tt> and <tt>getXXX</tt> methods should be used to query it.
|
||||
* Encapsulates what has changed between two versions of a type - it is used to determine if a reload is possible and
|
||||
* also passed on events related to reloading so that the plugins can tailor their actions based on what prevented
|
||||
* reloading. The various <tt>hasXXX</tt> and <tt>getXXX</tt> methods should be used to query it.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public class TypeDelta {
|
||||
|
||||
private long changed;
|
||||
|
||||
private final static long CHANGED_VERSION = 0x00000001;
|
||||
|
||||
private final static long CHANGED_ACCESS = 0x00000002;
|
||||
|
||||
private final static long CHANGED_SUPERNAME = 0x00000004;
|
||||
|
||||
private final static long CHANGED_INTERFACES = 0x00000008;
|
||||
|
||||
private final static long CHANGED_NAME = 0x00000010;
|
||||
|
||||
private final static long CHANGED_SIGNATURE = 0x00000020;
|
||||
private final static long CHANGED_TYPE_MASK = CHANGED_VERSION | CHANGED_ACCESS | CHANGED_SUPERNAME | CHANGED_INTERFACES
|
||||
|
||||
private final static long CHANGED_TYPE_MASK = CHANGED_VERSION | CHANGED_ACCESS | CHANGED_SUPERNAME
|
||||
| CHANGED_INTERFACES
|
||||
| CHANGED_NAME | CHANGED_SIGNATURE;
|
||||
|
||||
private final static long CHANGED_NEWFIELDS = 0x00000040;
|
||||
|
||||
private final static long CHANGED_LOSTFIELDS = 0x00000080;
|
||||
|
||||
private final static long CHANGED_CHANGEDFIELDS = 0x00000100;
|
||||
|
||||
private final static long CHANGED_FIELD_MASK = CHANGED_NEWFIELDS | CHANGED_LOSTFIELDS | CHANGED_CHANGEDFIELDS;
|
||||
|
||||
private final static long CHANGED_NEWMETHODS = 0x00000200;
|
||||
|
||||
private final static long CHANGED_LOSTMETHODS = 0x00000400;
|
||||
|
||||
private final static long CHANGED_CHANGEDMETHODS = 0x0000800;
|
||||
|
||||
private final static long CHANGED_METHOD_MASK = CHANGED_NEWMETHODS | CHANGED_LOSTMETHODS | CHANGED_CHANGEDMETHODS;
|
||||
|
||||
private final static long CHANGES = CHANGED_TYPE_MASK | CHANGED_FIELD_MASK | CHANGED_METHOD_MASK;
|
||||
|
||||
public int oAccess, nAccess;
|
||||
|
||||
public int oVersion, nVersion;
|
||||
|
||||
public String oName, nName;
|
||||
|
||||
public String oSignature, nSignature;
|
||||
|
||||
public String oSuperName, nSuperName;
|
||||
|
||||
public List<String> oInterfaces, nInterfaces;
|
||||
|
||||
Map<String, FieldNode> brandNewFields;
|
||||
|
||||
Map<String, FieldNode> lostFields;
|
||||
|
||||
Map<String, FieldDelta> changedFields;
|
||||
|
||||
Map<String, MethodNode> brandNewMethods;
|
||||
|
||||
Map<String, MethodNode> lostMethods;
|
||||
|
||||
Map<String, MethodDelta> changedMethods;
|
||||
|
||||
public String toString() {
|
||||
@@ -262,4 +288,4 @@ public class TypeDelta {
|
||||
return changedMethods;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,14 +13,15 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Encapsulates the information about a type relevant to reloading. The TypeDescriptor for a type is sometimes extracted whilst
|
||||
* performing some other operation (eg. {@link InterfaceExtractor}) but can also be retrieved directly using
|
||||
* Encapsulates the information about a type relevant to reloading. The TypeDescriptor for a type is sometimes extracted
|
||||
* whilst performing some other operation (eg. {@link InterfaceExtractor}) but can also be retrieved directly using
|
||||
* {@link TypeDescriptorExtractor}.
|
||||
*
|
||||
* @author Andy Clement
|
||||
@@ -29,28 +30,43 @@ import java.util.List;
|
||||
public class TypeDescriptor implements Constants {
|
||||
|
||||
private final int modifiers;
|
||||
|
||||
final String typename; // slashed
|
||||
|
||||
final String supertypeName; // slashed
|
||||
|
||||
final String[] superinterfaceNames; // slashed // empty array if there are none
|
||||
|
||||
private final MethodMember[] constructors; // empty array if there are none (but this doesn't ever happen!)
|
||||
|
||||
private final MethodMember[] methods; // empty array if there are none
|
||||
|
||||
private final MethodMember[] nonprivateMethods; // empty array if there are none
|
||||
|
||||
private final FieldMember[] fields; // empty array if there are none
|
||||
|
||||
private final FieldMember[] fieldsRequiringAccessors; // empty array if there are none
|
||||
|
||||
private List<String> finalInHierarchy; // nameAndDescriptor strings for methods final in the hierarchy (e.g. ordinal()I for an enum)
|
||||
|
||||
private final TypeRegistry registry;
|
||||
|
||||
private final boolean isReloadable;
|
||||
|
||||
private final boolean hasClinit;
|
||||
|
||||
private final static int IS_GROOVY_TYPE = 0x0001;
|
||||
|
||||
private int bits = 0x0000;
|
||||
|
||||
private ReloadableType reloadableType;
|
||||
|
||||
private int nextId = 0;
|
||||
|
||||
public TypeDescriptor(String slashedTypeName, String supertypeName, String[] superinterfaceNames, int modifiers,
|
||||
List<? extends MethodMember> constructors, List<MethodMember> methods, List<? extends FieldMember> fields,
|
||||
List<? extends FieldMember> fieldsRequiringAccessors, boolean isReloadable, TypeRegistry registry, boolean hasClinit,
|
||||
List<? extends FieldMember> fieldsRequiringAccessors, boolean isReloadable, TypeRegistry registry,
|
||||
boolean hasClinit,
|
||||
List<String> finalInHierarchy) {
|
||||
this.typename = slashedTypeName;
|
||||
this.supertypeName = supertypeName;
|
||||
@@ -58,10 +74,12 @@ public class TypeDescriptor implements Constants {
|
||||
this.finalInHierarchy = finalInHierarchy;
|
||||
this.modifiers = modifiers;
|
||||
this.fields = fields.size() == 0 ? FieldMember.NONE : fields.toArray(new FieldMember[fields.size()]);
|
||||
this.fieldsRequiringAccessors = fieldsRequiringAccessors.size() == 0 ? FieldMember.NONE : fieldsRequiringAccessors
|
||||
.toArray(new FieldMember[fieldsRequiringAccessors.size()]);
|
||||
this.constructors = constructors.size() == 0 ? MethodMember.NONE : constructors.toArray(new MethodMember[constructors
|
||||
.size()]);
|
||||
this.fieldsRequiringAccessors = fieldsRequiringAccessors.size() == 0 ? FieldMember.NONE
|
||||
: fieldsRequiringAccessors
|
||||
.toArray(new FieldMember[fieldsRequiringAccessors.size()]);
|
||||
this.constructors = constructors.size() == 0 ? MethodMember.NONE
|
||||
: constructors.toArray(new MethodMember[constructors
|
||||
.size()]);
|
||||
this.methods = methods.size() == 0 ? MethodMember.NONE : methods.toArray(new MethodMember[methods.size()]);
|
||||
this.nonprivateMethods = filterNonPrivateMethods(this.methods);
|
||||
this.isReloadable = isReloadable;
|
||||
@@ -82,7 +100,8 @@ public class TypeDescriptor implements Constants {
|
||||
}
|
||||
if (result == null) {
|
||||
return MethodMember.NONE;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
return result.toArray(new MethodMember[result.size()]);
|
||||
}
|
||||
}
|
||||
@@ -136,8 +155,8 @@ public class TypeDescriptor implements Constants {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this descriptor defines the specified method. A strict check on all aspects of the method - names/exceptions/flags,
|
||||
* etc.
|
||||
* Check if this descriptor defines the specified method. A strict check on all aspects of the method -
|
||||
* names/exceptions/flags, etc.
|
||||
*
|
||||
* @param method the method to check the existence of in this type descriptor
|
||||
* @return true if this descriptor defines the specified method
|
||||
@@ -153,8 +172,8 @@ public class TypeDescriptor implements Constants {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this descriptor defines a method with the specified name and descriptor. Return the method if it is found.
|
||||
* Modifiers, generic signature and exceptions are ignored in this search.
|
||||
* Check if this descriptor defines a method with the specified name and descriptor. Return the method if it is
|
||||
* found. Modifiers, generic signature and exceptions are ignored in this search.
|
||||
*
|
||||
* @param name the member name
|
||||
* @param descriptor the member descriptor (e.g. (Ljava/lang/String;)I)
|
||||
@@ -292,7 +311,8 @@ public class TypeDescriptor implements Constants {
|
||||
|
||||
public String toString() {
|
||||
StringBuilder s = new StringBuilder();
|
||||
s.append("TypeDescriptor: name=" + typename + " superclass=" + supertypeName + " superinterfaces=" + interfacesToString());
|
||||
s.append("TypeDescriptor: name=" + typename + " superclass=" + supertypeName + " superinterfaces="
|
||||
+ interfacesToString());
|
||||
s.append(" flags=0x" + Integer.toHexString(modifiers).toUpperCase()).append("\n");
|
||||
s.append("Fields: #" + fields.length + "\n" + fieldsToString());
|
||||
s.append("Constructors:#" + constructors.length + "\n" + methodsToString(constructors));
|
||||
@@ -312,7 +332,8 @@ public class TypeDescriptor implements Constants {
|
||||
private String interfacesToString() {
|
||||
if (superinterfaceNames == null) {
|
||||
return "";
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
StringBuilder s = new StringBuilder();
|
||||
for (String superinterfaceName : superinterfaceNames) {
|
||||
s.append(superinterfaceName);
|
||||
@@ -326,7 +347,8 @@ public class TypeDescriptor implements Constants {
|
||||
StringBuilder s = new StringBuilder();
|
||||
int count = 0;
|
||||
for (MethodMember method : methods) {
|
||||
s.append(" method #" + Utils.toPaddedNumber((count++), 3)).append(' ').append(method.toString()).append(" ")
|
||||
s.append(" method #" + Utils.toPaddedNumber((count++), 3)).append(' ').append(method.toString()).append(
|
||||
" ")
|
||||
.append(method.bitsToString()).append('\n');
|
||||
}
|
||||
return s.toString();
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded;
|
||||
|
||||
import java.lang.reflect.Modifier;
|
||||
@@ -28,8 +29,8 @@ import org.objectweb.asm.MethodVisitor;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
|
||||
/**
|
||||
* A type descriptor describes the type, methods, fields, etc - two type descriptors are comparable to discover what has changed
|
||||
* between versions.
|
||||
* A type descriptor describes the type, methods, fields, etc - two type descriptors are comparable to discover what has
|
||||
* changed between versions.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
@@ -37,7 +38,7 @@ import org.objectweb.asm.Opcodes;
|
||||
public class TypeDescriptorExtractor {
|
||||
|
||||
private final static boolean DEBUG_TYPE_DESCRIPTOR_EXTRACTOR = false;
|
||||
|
||||
|
||||
private TypeRegistry registry;
|
||||
|
||||
public TypeDescriptorExtractor(TypeRegistry registry) {
|
||||
@@ -57,32 +58,45 @@ public class TypeDescriptorExtractor {
|
||||
class ExtractionVisitor extends ClassVisitor implements Opcodes {
|
||||
|
||||
private boolean isReloadableType;
|
||||
|
||||
private int flags;
|
||||
|
||||
private String typename;
|
||||
|
||||
private String superclassName;
|
||||
|
||||
private String[] interfaceNames;
|
||||
|
||||
private boolean isGroovy = false;
|
||||
|
||||
private boolean isEnum = false;
|
||||
|
||||
private boolean hasClinit = false;
|
||||
|
||||
// TODO [perf - reduce garbage] make these collections lazily initialize
|
||||
private List<MethodMember> constructors = new ArrayList<MethodMember>();
|
||||
|
||||
private List<MethodMember> methods = new ArrayList<MethodMember>();
|
||||
|
||||
private List<FieldMember> fieldsRequiringAccessors = new ArrayList<FieldMember>();
|
||||
|
||||
private List<FieldMember> fields = new ArrayList<FieldMember>();
|
||||
|
||||
private List<String> finalInHierarchy = new ArrayList<String>();
|
||||
|
||||
|
||||
public ExtractionVisitor(boolean isReloadableType) {
|
||||
super(ASM5);
|
||||
this.isReloadableType = isReloadableType;
|
||||
}
|
||||
|
||||
|
||||
public TypeDescriptor getTypeDescriptor() {
|
||||
if (isReloadableType) {
|
||||
computeCatchersAndSuperdispatchers();
|
||||
}
|
||||
computeFieldsRequiringAccessors();
|
||||
computeClashes();
|
||||
TypeDescriptor td = new TypeDescriptor(typename, superclassName, interfaceNames, flags, constructors, methods, fields,
|
||||
TypeDescriptor td = new TypeDescriptor(typename, superclassName, interfaceNames, flags, constructors,
|
||||
methods, fields,
|
||||
fieldsRequiringAccessors, isReloadableType, registry, hasClinit, finalInHierarchy);
|
||||
if (isGroovy) {
|
||||
td.setIsGroovyType(true);
|
||||
@@ -91,10 +105,10 @@ public class TypeDescriptorExtractor {
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if there are clashes. A clash is where a static method takes the reloadable type as its first parameter
|
||||
* but in all other ways is the same as an existing instance method. For example this instance method A.foo(String) clashes
|
||||
* with this static method A.foo(A, String). 'clashing' means the executor will have to do something to avoid a duplicate
|
||||
* method problem and we'll have to differentiate between the two.
|
||||
* Determine if there are clashes. A clash is where a static method takes the reloadable type as its first
|
||||
* parameter but in all other ways is the same as an existing instance method. For example this instance method
|
||||
* A.foo(String) clashes with this static method A.foo(A, String). 'clashing' means the executor will have to do
|
||||
* something to avoid a duplicate method problem and we'll have to differentiate between the two.
|
||||
*/
|
||||
private void computeClashes() {
|
||||
String clashDescriptorPrefix = "(L" + typename + ";";
|
||||
@@ -108,7 +122,8 @@ public class TypeDescriptorExtractor {
|
||||
// really might be a clash
|
||||
String instanceParams = member2.descriptor;
|
||||
instanceParams = instanceParams.substring(1, instanceParams.indexOf(')') + 1);
|
||||
String staticParams = desc.substring(clashDescriptorPrefix.length(), desc.indexOf(')') + 1);
|
||||
String staticParams = desc.substring(clashDescriptorPrefix.length(),
|
||||
desc.indexOf(')') + 1);
|
||||
if (instanceParams.equals(staticParams)) {
|
||||
// CLASH
|
||||
member.bits |= MethodMember.BIT_CLASH;
|
||||
@@ -143,18 +158,19 @@ public class TypeDescriptorExtractor {
|
||||
}
|
||||
|
||||
/**
|
||||
* Algorithm: Go up the superclass hierarchy for a type and determine what should be caught in this type (see
|
||||
* 'catchers' in notes.md). Methods that are private, static or final do *not* get a catcher. This method
|
||||
* also computes superdispatchers - see 'superdispatchers' in notes.md
|
||||
* Algorithm: Go up the superclass hierarchy for a type and determine what should be caught in this type (see
|
||||
* 'catchers' in notes.md). Methods that are private, static or final do *not* get a catcher. This method also
|
||||
* computes superdispatchers - see 'superdispatchers' in notes.md
|
||||
*
|
||||
*/
|
||||
private void walkHierarchyForCatchersAndSuperDispatchers(String superclass, List<String> superDispatchers, List<String> finalInHierarchy) {
|
||||
TypeDescriptor supertypeDescriptor = superclass==null?null:findTypeDescriptor(registry, superclass);
|
||||
private void walkHierarchyForCatchersAndSuperDispatchers(String superclass, List<String> superDispatchers,
|
||||
List<String> finalInHierarchy) {
|
||||
TypeDescriptor supertypeDescriptor = superclass == null ? null : findTypeDescriptor(registry, superclass);
|
||||
if (DEBUG_TYPE_DESCRIPTOR_EXTRACTOR) {
|
||||
System.out.println("Computing catchers on "+this.typename+" from superclass "+superclass);
|
||||
System.out.println("Computing catchers on " + this.typename + " from superclass " + superclass);
|
||||
}
|
||||
boolean isReloadable = supertypeDescriptor.isReloadable();
|
||||
for (MethodMember method: supertypeDescriptor.getMethods()) {
|
||||
for (MethodMember method : supertypeDescriptor.getMethods()) {
|
||||
if (shouldCreateSuperDispatcherFor(method) && !superDispatchers.contains(method.nameAndDescriptor)) {
|
||||
// need a public super dispatcher - so that we can reach that super method
|
||||
// from a reloaded instance of this type
|
||||
@@ -185,17 +201,19 @@ public class TypeDescriptorExtractor {
|
||||
}
|
||||
MethodMember catcherCopy = method.catcherCopyOf();
|
||||
if (DEBUG_TYPE_DESCRIPTOR_EXTRACTOR) {
|
||||
System.out.println("Adding catcher for "+method.nameAndDescriptor);
|
||||
System.out.println("Adding catcher for " + method.nameAndDescriptor);
|
||||
}
|
||||
methods.add(catcherCopy);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
if (method.isFinal()) {
|
||||
finalInHierarchy.add(method.getNameAndDescriptor());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (supertypeDescriptor.supertypeName != null) {
|
||||
walkHierarchyForCatchersAndSuperDispatchers(supertypeDescriptor.supertypeName, superDispatchers, finalInHierarchy);
|
||||
walkHierarchyForCatchersAndSuperDispatchers(supertypeDescriptor.supertypeName, superDispatchers,
|
||||
finalInHierarchy);
|
||||
}
|
||||
if (Modifier.isAbstract(this.flags) && !this.isEnum/* && !Modifier.isInterface(this.flags)*/) {
|
||||
// abstract class may be missing methods that it can implement from the interfaces
|
||||
@@ -204,7 +222,7 @@ public class TypeDescriptorExtractor {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Compute and add the catch methods and super dispatch methods that apply to this type.
|
||||
*/
|
||||
@@ -212,7 +230,7 @@ public class TypeDescriptorExtractor {
|
||||
if (Modifier.isInterface(this.flags)) { // Don't need catchers in interfaces
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// TODO [review design] review the need to create catchers for methods where the supertype is reloadable.
|
||||
// Can we just add them to the topmost reloadable type?
|
||||
List<String> doNotCatch = new ArrayList<String>();
|
||||
@@ -226,7 +244,7 @@ public class TypeDescriptorExtractor {
|
||||
}
|
||||
}
|
||||
if (DEBUG_TYPE_DESCRIPTOR_EXTRACTOR) {
|
||||
System.out.println("For "+this.typename+" setting finalsInHierarchy to "+doNotCatch);
|
||||
System.out.println("For " + this.typename + " setting finalsInHierarchy to " + doNotCatch);
|
||||
}
|
||||
finalInHierarchy.addAll(doNotCatch);
|
||||
}
|
||||
@@ -234,11 +252,11 @@ public class TypeDescriptorExtractor {
|
||||
// TODO should clone and finalize be in here?
|
||||
private boolean shouldCreateSuperDispatcherFor(MethodMember method) {
|
||||
return method.isProtected() && !(
|
||||
(method.getName().equals("finalize") && method.getDescriptor().equals("()V")) ||
|
||||
(method.getName().equals("finalize") && method.getDescriptor().equals("()V")) ||
|
||||
(method.getName().equals("clone") && method.getDescriptor().equals("()Ljava/lang/Object;")));
|
||||
}
|
||||
|
||||
private void addCatchersForNonImplementedMethodsFrom(String interfacename,List<String> finalInNonReloadableType) {
|
||||
private void addCatchersForNonImplementedMethodsFrom(String interfacename, List<String> finalInNonReloadableType) {
|
||||
TypeDescriptor interfaceDescriptor = findTypeDescriptor(registry, interfacename);
|
||||
for (MethodMember method : interfaceDescriptor.getMethods()) {
|
||||
// If this class doesn't implement this interface method, add it
|
||||
@@ -251,19 +269,20 @@ public class TypeDescriptorExtractor {
|
||||
}
|
||||
if (!found && !finalInNonReloadableType.contains(method.getNameAndDescriptor())) {
|
||||
if (DEBUG_TYPE_DESCRIPTOR_EXTRACTOR) {
|
||||
Log.log("adding catcher for ["+method+"] from ["+interfacename+"] to ["+this.typename+"]");
|
||||
Log.log("adding catcher for [" + method + "] from [" + interfacename + "] to [" + this.typename
|
||||
+ "]");
|
||||
}
|
||||
methods.add(method.catcherCopyOfWithAbstractRemoved());
|
||||
}
|
||||
}
|
||||
for (String interfaceName : interfaceDescriptor.superinterfaceNames) {
|
||||
addCatchersForNonImplementedMethodsFrom(interfaceName,finalInNonReloadableType);
|
||||
addCatchersForNonImplementedMethodsFrom(interfaceName, finalInNonReloadableType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Protected fields in reloadable parents of a class need an accessor adding to the reloadable
|
||||
* type so that the fields can be reached from the executor.
|
||||
* Protected fields in reloadable parents of a class need an accessor adding to the reloadable type so that the
|
||||
* fields can be reached from the executor.
|
||||
*/
|
||||
private void computeFieldsRequiringAccessors() {
|
||||
String type = superclassName;
|
||||
@@ -291,21 +310,23 @@ public class TypeDescriptorExtractor {
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a method gets a catcher. Deliberately not catching final methods, static methods, private methods or
|
||||
* finalize()V.
|
||||
* Determine if a method gets a catcher. Deliberately not catching final methods, static methods, private
|
||||
* methods or finalize()V.
|
||||
*
|
||||
* @return true if it should be caught
|
||||
*/
|
||||
private boolean shouldCatchMethod(MethodMember method) {
|
||||
return !(method.isPrivateOrStaticOrFinal() || method.getName().endsWith(Constants.methodSuffixSuperDispatcher) ||
|
||||
(method.getName().equals("finalize") && method.getDescriptor().equals("()V")));
|
||||
return !(method.isPrivateOrStaticOrFinal()
|
||||
|| method.getName().endsWith(Constants.methodSuffixSuperDispatcher) || (method.getName().equals(
|
||||
"finalize") && method.getDescriptor().equals("()V")));
|
||||
}
|
||||
|
||||
public void visit(int version, int flags, String name, String signature, String superclassName, String[] interfaceNames) {
|
||||
public void visit(int version, int flags, String name, String signature, String superclassName,
|
||||
String[] interfaceNames) {
|
||||
this.flags = flags;
|
||||
this.superclassName = superclassName;
|
||||
this.interfaceNames = interfaceNames;
|
||||
if (superclassName!=null && superclassName.equals("java/lang/Enum")) {
|
||||
if (superclassName != null && superclassName.equals("java/lang/Enum")) {
|
||||
this.isEnum = true;
|
||||
}
|
||||
this.typename = name;
|
||||
@@ -317,7 +338,7 @@ public class TypeDescriptorExtractor {
|
||||
|
||||
public void visitAttribute(Attribute attribute) {
|
||||
}
|
||||
|
||||
|
||||
public void visitInnerClass(String name, String outername, String innerName, int access) {
|
||||
if (name.equals(typename)) {
|
||||
this.flags = access;
|
||||
@@ -333,17 +354,21 @@ public class TypeDescriptorExtractor {
|
||||
}
|
||||
|
||||
/**
|
||||
* Visit a method in the class and build an appropriate representation for it to include in the extracted output.
|
||||
* Visit a method in the class and build an appropriate representation for it to include in the extracted
|
||||
* output.
|
||||
*/
|
||||
public MethodVisitor visitMethod(int flags, String name, String descriptor, String genericSignature, String[] exceptions) {
|
||||
public MethodVisitor visitMethod(int flags, String name, String descriptor, String genericSignature,
|
||||
String[] exceptions) {
|
||||
if (name.charAt(0) != '<') {
|
||||
methods.add(new MethodMember(flags, name, descriptor, genericSignature, exceptions));
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
if (name.equals("<init>")) {
|
||||
//Even though constructors are not reloadable at present, we need to add them to type descriptors to know
|
||||
//about their original modifiers (these are promoted to public to allow executors access to them).
|
||||
constructors.add(new MethodMember(flags, name, descriptor, genericSignature, exceptions));
|
||||
} else if (name.equals("<clinit>")) {
|
||||
}
|
||||
else if (name.equals("<clinit>")) {
|
||||
hasClinit = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -45,7 +46,8 @@ import org.objectweb.asm.tree.TypeInsnNode;
|
||||
import org.objectweb.asm.tree.VarInsnNode;
|
||||
|
||||
/**
|
||||
* Compute the differences between two versions of a type as a series of deltas. Entry point is the computeDifferences method.
|
||||
* Compute the differences between two versions of a type as a series of deltas. Entry point is the computeDifferences
|
||||
* method.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
@@ -96,7 +98,8 @@ public class TypeDiffComputer implements Opcodes {
|
||||
}
|
||||
if (found == null) {
|
||||
td.addNewMethod(nMethod);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
oMethods.remove(found);
|
||||
}
|
||||
}
|
||||
@@ -129,7 +132,8 @@ public class TypeDiffComputer implements Opcodes {
|
||||
if (found == null) {
|
||||
// this is a new field
|
||||
td.addNewField(nField);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
oFields.remove(found);
|
||||
}
|
||||
}
|
||||
@@ -141,8 +145,8 @@ public class TypeDiffComputer implements Opcodes {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the properties of the field - if they have changed at all then record what kind of change for the field. Thinking the
|
||||
* type delta should have a map from names to a delta describing (capturing) the change.
|
||||
* Check the properties of the field - if they have changed at all then record what kind of change for the field.
|
||||
* Thinking the type delta should have a map from names to a delta describing (capturing) the change.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private static void computeAnyFieldDifferences(FieldNode oField, FieldNode nField, TypeDelta td) {
|
||||
@@ -168,8 +172,8 @@ public class TypeDiffComputer implements Opcodes {
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if there any differences between the methods supplied. A MethodDelta object is built to record any differences and
|
||||
* stored against the type delta.
|
||||
* Determine if there any differences between the methods supplied. A MethodDelta object is built to record any
|
||||
* differences and stored against the type delta.
|
||||
*
|
||||
* @param oMethod 'old' method
|
||||
* @param nMethod 'new' method
|
||||
@@ -185,7 +189,8 @@ public class TypeDiffComputer implements Opcodes {
|
||||
InsnList nInstructions = nMethod.instructions;
|
||||
if (oInstructions.size() != nInstructions.size()) {
|
||||
md.setInstructionsChanged(oInstructions.toArray(), nInstructions.toArray());
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
// TODO Just interested in constructors right now - should add others
|
||||
if (oMethod.name.charAt(0) == '<') {
|
||||
String oInvokeSpecialDescriptor = null;
|
||||
@@ -218,7 +223,8 @@ public class TypeDiffComputer implements Opcodes {
|
||||
if (oUninitCount == 0) {
|
||||
// this is the one!
|
||||
oInvokeSpecialDescriptor = mi.desc;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
oUninitCount--;
|
||||
}
|
||||
}
|
||||
@@ -229,7 +235,8 @@ public class TypeDiffComputer implements Opcodes {
|
||||
if (nUninitCount == 0) {
|
||||
// this is the one!
|
||||
nInvokeSpecialDescriptor = mi.desc;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
nUninitCount--;
|
||||
}
|
||||
}
|
||||
@@ -240,7 +247,8 @@ public class TypeDiffComputer implements Opcodes {
|
||||
if (nInvokeSpecialDescriptor != null) {
|
||||
md.setInvokespecialChanged(oInvokeSpecialDescriptor, nInvokeSpecialDescriptor);
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
if (!oInvokeSpecialDescriptor.equals(nInvokeSpecialDescriptor)) {
|
||||
md.setInvokespecialChanged(oInvokeSpecialDescriptor, nInvokeSpecialDescriptor);
|
||||
}
|
||||
@@ -262,83 +270,83 @@ public class TypeDiffComputer implements Opcodes {
|
||||
return false;
|
||||
}
|
||||
switch (o.getType()) {
|
||||
case (AbstractInsnNode.INSN): // 0
|
||||
if (!sameInsnNode(o, n)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case (AbstractInsnNode.INT_INSN): // 1
|
||||
if (!sameIntInsnNode(o, n)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case (AbstractInsnNode.VAR_INSN): // 2
|
||||
if (!sameVarInsn(o, n)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case (AbstractInsnNode.TYPE_INSN):// 3
|
||||
if (!sameTypeInsn(o, n)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case (AbstractInsnNode.FIELD_INSN): // 4
|
||||
if (!sameFieldInsn(o, n)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case (AbstractInsnNode.METHOD_INSN): // 5
|
||||
if (!sameMethodInsnNode(o, n)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case (AbstractInsnNode.JUMP_INSN): // 6
|
||||
if (!sameJumpInsnNode(o, n)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case (AbstractInsnNode.LABEL): // 7
|
||||
if (!sameLabelNode(o, n)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case (AbstractInsnNode.LDC_INSN): // 8
|
||||
if (!sameLdcInsnNode(o, n)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case (AbstractInsnNode.IINC_INSN): // 9
|
||||
if (!sameIincInsn(o, n)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case (AbstractInsnNode.TABLESWITCH_INSN): // 10
|
||||
if (!sameTableSwitchInsn(o, n)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case (AbstractInsnNode.LOOKUPSWITCH_INSN): // 11
|
||||
if (!sameLookupSwitchInsn(o, n)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case (AbstractInsnNode.MULTIANEWARRAY_INSN): // 12
|
||||
if (!sameMultiANewArrayInsn(o, n)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case (AbstractInsnNode.FRAME): // 13
|
||||
if (!sameFrameInsn(o, n)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case (AbstractInsnNode.LINE): // 14
|
||||
if (!sameLineNumberNode(o, n)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("nyi " + o.getType());
|
||||
case (AbstractInsnNode.INSN): // 0
|
||||
if (!sameInsnNode(o, n)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case (AbstractInsnNode.INT_INSN): // 1
|
||||
if (!sameIntInsnNode(o, n)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case (AbstractInsnNode.VAR_INSN): // 2
|
||||
if (!sameVarInsn(o, n)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case (AbstractInsnNode.TYPE_INSN):// 3
|
||||
if (!sameTypeInsn(o, n)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case (AbstractInsnNode.FIELD_INSN): // 4
|
||||
if (!sameFieldInsn(o, n)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case (AbstractInsnNode.METHOD_INSN): // 5
|
||||
if (!sameMethodInsnNode(o, n)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case (AbstractInsnNode.JUMP_INSN): // 6
|
||||
if (!sameJumpInsnNode(o, n)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case (AbstractInsnNode.LABEL): // 7
|
||||
if (!sameLabelNode(o, n)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case (AbstractInsnNode.LDC_INSN): // 8
|
||||
if (!sameLdcInsnNode(o, n)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case (AbstractInsnNode.IINC_INSN): // 9
|
||||
if (!sameIincInsn(o, n)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case (AbstractInsnNode.TABLESWITCH_INSN): // 10
|
||||
if (!sameTableSwitchInsn(o, n)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case (AbstractInsnNode.LOOKUPSWITCH_INSN): // 11
|
||||
if (!sameLookupSwitchInsn(o, n)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case (AbstractInsnNode.MULTIANEWARRAY_INSN): // 12
|
||||
if (!sameMultiANewArrayInsn(o, n)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case (AbstractInsnNode.FRAME): // 13
|
||||
if (!sameFrameInsn(o, n)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case (AbstractInsnNode.LINE): // 14
|
||||
if (!sameLineNumberNode(o, n)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("nyi " + o.getType());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -626,7 +634,7 @@ public class TypeDiffComputer implements Opcodes {
|
||||
// }
|
||||
if (oldClassNode.access != newClassNode.access) {
|
||||
// Is it only because of 0x20000 - that appears to represent Deprecated!
|
||||
if ((oldClassNode.access & 0xffff) != (newClassNode.access&0xffff)) {
|
||||
if ((oldClassNode.access & 0xffff) != (newClassNode.access & 0xffff)) {
|
||||
td.setTypeAccessChange(oldClassNode.access, newClassNode.access);
|
||||
}
|
||||
}
|
||||
@@ -648,22 +656,26 @@ public class TypeDiffComputer implements Opcodes {
|
||||
if (newClassNode.superName != null) {
|
||||
td.setTypeSuperNameChange(oldClassNode.superName, newClassNode.superName);
|
||||
}
|
||||
} else if (newClassNode.superName == null) {
|
||||
}
|
||||
else if (newClassNode.superName == null) {
|
||||
if (oldClassNode.superName != null) {
|
||||
td.setTypeSuperNameChange(oldClassNode.superName, newClassNode.superName);
|
||||
}
|
||||
} else if (!oldClassNode.superName.equals(newClassNode.superName)) {
|
||||
}
|
||||
else if (!oldClassNode.superName.equals(newClassNode.superName)) {
|
||||
td.setTypeSuperNameChange(oldClassNode.superName, newClassNode.superName);
|
||||
}
|
||||
if (oldClassNode.interfaces.size() == 0) {
|
||||
if (newClassNode.interfaces.size() != 0) {
|
||||
td.setTypeInterfacesChange(oldClassNode.interfaces, newClassNode.interfaces);
|
||||
}
|
||||
} else if (newClassNode.interfaces.size() == 0) {
|
||||
}
|
||||
else if (newClassNode.interfaces.size() == 0) {
|
||||
if (oldClassNode.interfaces.size() != 0) {
|
||||
td.setTypeInterfacesChange(oldClassNode.interfaces, newClassNode.interfaces);
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
if (oldClassNode.interfaces.size() != newClassNode.interfaces.size()) {
|
||||
td.setTypeInterfacesChange(oldClassNode.interfaces, newClassNode.interfaces);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded;
|
||||
|
||||
/**
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded;
|
||||
|
||||
import java.lang.reflect.Modifier;
|
||||
@@ -33,11 +34,12 @@ import org.springsource.loaded.Utils.ReturnType;
|
||||
/**
|
||||
* Rewrites a class such that it is amenable to reloading. This involves:
|
||||
* <ul>
|
||||
* <li>In every method, introduce logic to check it it the latest version of that method - if it isn't dispatch to the latest
|
||||
* <li>In every method, introduce logic to check it it the latest version of that method - if it isn't dispatch to the
|
||||
* latest
|
||||
* <li>Creates additional methods to aid with field setting/getting
|
||||
* <li>Creates additional fields to help reloading (reloadable type instance, new field value holders)
|
||||
* <li>Creates catchers for inherited methods. Catchers are simply passed through unless a new version of the class provides an
|
||||
* implementation
|
||||
* <li>Creates catchers for inherited methods. Catchers are simply passed through unless a new version of the class
|
||||
* provides an implementation
|
||||
* </ul>
|
||||
*
|
||||
* @author Andy Clement
|
||||
@@ -57,13 +59,21 @@ public class TypeRewriter implements Constants {
|
||||
static class RewriteClassAdaptor extends ClassVisitor implements Constants {
|
||||
|
||||
private ClassWriter cw;
|
||||
|
||||
private String slashedname;
|
||||
|
||||
private ReloadableType rtype;
|
||||
|
||||
private TypeDescriptor typeDescriptor;
|
||||
|
||||
private boolean clinitDone = false;
|
||||
|
||||
private String supertypeName;
|
||||
|
||||
private boolean isInterface;
|
||||
|
||||
private boolean isEnum;
|
||||
|
||||
private boolean isGroovy;
|
||||
|
||||
public RewriteClassAdaptor(ReloadableType rtype) {
|
||||
@@ -71,7 +81,7 @@ public class TypeRewriter implements Constants {
|
||||
}
|
||||
|
||||
public RewriteClassAdaptor(ReloadableType rtype, ClassWriter classWriter) {
|
||||
super(ASM5,classWriter);
|
||||
super(ASM5, classWriter);
|
||||
this.rtype = rtype;
|
||||
this.slashedname = rtype.getSlashedName();
|
||||
this.cw = (ClassWriter) cv;
|
||||
@@ -122,7 +132,8 @@ public class TypeRewriter implements Constants {
|
||||
TypeRegistry typeRegistry = rtype.getTypeRegistry();
|
||||
if (!typeRegistry.isReloadableTypeName(typeDescriptor.getSupertypeName())) {
|
||||
return true;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -143,7 +154,8 @@ public class TypeRewriter implements Constants {
|
||||
* Create the static field getter method which ensures the static state manager is initialized.
|
||||
*/
|
||||
private void createStaticFieldGetterMethod() {
|
||||
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC_STATIC, mStaticFieldGetterName, "(Ljava/lang/String;)Ljava/lang/Object;",
|
||||
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC_STATIC, mStaticFieldGetterName,
|
||||
"(Ljava/lang/String;)Ljava/lang/Object;",
|
||||
null, null);
|
||||
mv.visitFieldInsn(GETSTATIC, slashedname, fStaticFieldsName, lStaticStateManager);
|
||||
Label l2 = new Label();
|
||||
@@ -170,7 +182,8 @@ public class TypeRewriter implements Constants {
|
||||
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "___init___", desc, null, null);
|
||||
mv.visitFieldInsn(GETSTATIC, slashedname, fReloadableTypeFieldName, lReloadableType);
|
||||
mv.visitInsn(ICONST_1);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, tReloadableType, "getLatestDispatcherInstance", "(Z)Ljava/lang/Object;");
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, tReloadableType, "getLatestDispatcherInstance",
|
||||
"(Z)Ljava/lang/Object;");
|
||||
mv.visitTypeInsn(CHECKCAST, Utils.getInterfaceName(slashedname));
|
||||
String desc2 = new StringBuffer("(L").append(slashedname).append(";").append(desc.substring(1)).toString();
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
@@ -185,7 +198,8 @@ public class TypeRewriter implements Constants {
|
||||
private void createManagedConstructors() {
|
||||
String slashedSupertypeName = typeDescriptor.getSupertypeName();
|
||||
if (slashedSupertypeName.equals("java/lang/Enum")) { // assert isEnum
|
||||
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "(Ljava/lang/String;ILorg/springsource/loaded/C;)V", null,
|
||||
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>",
|
||||
"(Ljava/lang/String;ILorg/springsource/loaded/C;)V", null,
|
||||
null);
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitVarInsn(ALOAD, 1);
|
||||
@@ -209,25 +223,29 @@ public class TypeRewriter implements Constants {
|
||||
mv.visitVarInsn(ALOAD, 0); // this (uninitialized)
|
||||
mv.visitVarInsn(ALOAD, 1); // 'owner'
|
||||
mv.visitVarInsn(ALOAD, 2); // 'this'
|
||||
mv.visitMethodInsn(INVOKESPECIAL, "groovy/lang/Closure", "<init>", "(Ljava/lang/Object;Ljava/lang/Object;)V");
|
||||
mv.visitMethodInsn(INVOKESPECIAL, "groovy/lang/Closure", "<init>",
|
||||
"(Ljava/lang/Object;Ljava/lang/Object;)V");
|
||||
mv.visitInsn(RETURN);
|
||||
mv.visitMaxs(3, 4);
|
||||
mv.visitEnd();
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "(Lorg/springsource/loaded/C;)V", null, null);
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
if (slashedSupertypeName.equals("java/lang/Object")) {
|
||||
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
|
||||
mv.visitInsn(RETURN);
|
||||
mv.visitMaxs(1, 2);
|
||||
} else if (slashedSupertypeName.equals("java/lang/Enum")) { // assert isEnum
|
||||
}
|
||||
else if (slashedSupertypeName.equals("java/lang/Enum")) { // assert isEnum
|
||||
// Call Enum.<init>(null,0)
|
||||
mv.visitInsn(ACONST_NULL);
|
||||
mv.visitLdcInsn(0);
|
||||
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Enum", "<init>", "(Ljava/lang/String;I)V");
|
||||
mv.visitInsn(RETURN);
|
||||
mv.visitMaxs(3, 3);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
ReloadableType superRtype = rtype.getTypeRegistry().getReloadableType(slashedSupertypeName);
|
||||
if (superRtype == null) {
|
||||
// This means we are crossing a reloadable boundary (this type is reloadable, the supertype is not).
|
||||
@@ -237,7 +255,8 @@ public class TypeRewriter implements Constants {
|
||||
MethodMember ctor = superDescriptor.getConstructor("()V");
|
||||
if (ctor != null) {
|
||||
mv.visitMethodInsn(INVOKESPECIAL, slashedSupertypeName, "<init>", "()V");
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
String warningMessage = "SERIOUS WARNING (current limitation): At reloadable boundary of "
|
||||
+ typeDescriptor.getDottedName()
|
||||
+ " supertype="
|
||||
@@ -258,9 +277,11 @@ public class TypeRewriter implements Constants {
|
||||
|
||||
// throw new IllegalStateException("at reloadable boundary, not sure how to construct " + supertypeName);
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
mv.visitInsn(ACONST_NULL);
|
||||
mv.visitMethodInsn(INVOKESPECIAL, slashedSupertypeName, "<init>", "(Lorg/springsource/loaded/C;)V");
|
||||
mv.visitMethodInsn(INVOKESPECIAL, slashedSupertypeName, "<init>",
|
||||
"(Lorg/springsource/loaded/C;)V");
|
||||
}
|
||||
mv.visitInsn(RETURN);
|
||||
mv.visitMaxs(2, 2);
|
||||
@@ -283,7 +304,8 @@ public class TypeRewriter implements Constants {
|
||||
* </pre></code>
|
||||
*/
|
||||
private void createStaticFieldSetterMethod() {
|
||||
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC_STATIC, mStaticFieldSetterName, mStaticFieldSetterDescriptor, null, null);
|
||||
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC_STATIC, mStaticFieldSetterName, mStaticFieldSetterDescriptor,
|
||||
null, null);
|
||||
mv.visitFieldInsn(GETSTATIC, slashedname, fStaticFieldsName, lStaticStateManager);
|
||||
Label l2 = new Label();
|
||||
mv.visitJumpInsn(IFNONNULL, l2);
|
||||
@@ -316,7 +338,8 @@ public class TypeRewriter implements Constants {
|
||||
* </pre></code>
|
||||
*/
|
||||
private void createInstanceFieldGetterMethod() {
|
||||
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, mInstanceFieldGetterName, mInstanceFieldGetterDescriptor, null, null);
|
||||
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, mInstanceFieldGetterName, mInstanceFieldGetterDescriptor,
|
||||
null, null);
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitFieldInsn(GETFIELD, slashedname, fInstanceFieldsName, lInstanceStateManager);
|
||||
Label l1 = new Label();
|
||||
@@ -350,7 +373,8 @@ public class TypeRewriter implements Constants {
|
||||
* Create a field setter for instance fields, signature of: public void r$set(Object,Object,String)
|
||||
*/
|
||||
private void createInstanceFieldSetterMethod() {
|
||||
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, mInstanceFieldSetterName, mInstanceFieldSetterDescriptor, null, null);
|
||||
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, mInstanceFieldSetterName, mInstanceFieldSetterDescriptor,
|
||||
null, null);
|
||||
final int lvNewValue = 1;
|
||||
final int lvInstance = 2;
|
||||
final int lvName = 3;
|
||||
@@ -391,7 +415,8 @@ public class TypeRewriter implements Constants {
|
||||
}
|
||||
|
||||
private void createInstanceStateManagerInstance() {
|
||||
FieldVisitor f = cw.visitField(ACC_PUBLIC | ACC_TRANSIENT, fInstanceFieldsName, lInstanceStateManager, null, null);
|
||||
FieldVisitor f = cw.visitField(ACC_PUBLIC | ACC_TRANSIENT, fInstanceFieldsName, lInstanceStateManager,
|
||||
null, null);
|
||||
f.visitEnd();
|
||||
}
|
||||
|
||||
@@ -402,8 +427,8 @@ public class TypeRewriter implements Constants {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the reloadable type field, which can later answer questions about changes or be used to access the latest version
|
||||
* of a type/method.
|
||||
* Create the reloadable type field, which can later answer questions about changes or be used to access the
|
||||
* latest version of a type/method.
|
||||
*/
|
||||
private void createReloadableTypeField() {
|
||||
int acc = isInterface ? ACC_PUBLIC_STATIC_FINAL : ACC_PUBLIC_STATIC; //ACC_PRIVATE_STATIC;
|
||||
@@ -412,8 +437,10 @@ public class TypeRewriter implements Constants {
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodVisitor visitMethod(int flags, String name, String descriptor, String signature, String[] exceptions) {
|
||||
MethodVisitor mv = super.visitMethod(promoteIfNecessary(flags,name), name, descriptor, signature, exceptions);
|
||||
public MethodVisitor visitMethod(int flags, String name, String descriptor, String signature,
|
||||
String[] exceptions) {
|
||||
MethodVisitor mv = super.visitMethod(promoteIfNecessary(flags, name), name, descriptor, signature,
|
||||
exceptions);
|
||||
MethodVisitor newMethodVisitor = getMethodVisitor(name, descriptor, mv);
|
||||
return newMethodVisitor;
|
||||
}
|
||||
@@ -424,13 +451,16 @@ public class TypeRewriter implements Constants {
|
||||
if (name.charAt(1) == 'c') { // <clinit>
|
||||
clinitDone = true;
|
||||
newMethodVisitor = new MethodPrepender(mv, new ClinitPrepender(mv));
|
||||
} else { // <init>
|
||||
newMethodVisitor = new AugmentingConstructorAdapter(mv, descriptor, slashedname, isTopmostReloadable());
|
||||
}
|
||||
else { // <init>
|
||||
newMethodVisitor = new AugmentingConstructorAdapter(mv, descriptor, slashedname,
|
||||
isTopmostReloadable());
|
||||
// want to create a copy of the constructor called ___init___ so it is reachable from the executor of the subtype.
|
||||
// All it really needs to do is call through the dispatcher to the executor for the relevant constructor.
|
||||
// this will force a reload of the supertype (to create the super executor) - that is something we can address later
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
// what about just copying if isgroovy and name.startsWith("$get");
|
||||
// Can't do this for $getStaticMetaClass so let us use $get$$ for now, until we discover why that lets us down...
|
||||
// if (isGroovy && name.startsWith("$get$$")) {
|
||||
@@ -443,7 +473,7 @@ public class TypeRewriter implements Constants {
|
||||
}
|
||||
|
||||
// Default visibility elements need promotion to public so that they can be seen from the executor
|
||||
private int promoteIfNecessary(int flags,String name) {
|
||||
private int promoteIfNecessary(int flags, String name) {
|
||||
int newflags = Utils.promoteDefaultOrProtectedToPublic(flags, isEnum, name);
|
||||
return newflags;
|
||||
}
|
||||
@@ -457,12 +487,14 @@ public class TypeRewriter implements Constants {
|
||||
if ((access & ACC_FINAL) != 0) {
|
||||
if (name.equals("serialVersionUID")) {
|
||||
modAccess = (access & ~(ACC_PRIVATE | ACC_PROTECTED)) | ACC_PUBLIC;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
// remove final
|
||||
modAccess = (access & ~(ACC_PRIVATE | ACC_PROTECTED)) | ACC_PUBLIC;
|
||||
modAccess = modAccess & ~ACC_FINAL;
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
// remove final
|
||||
modAccess = (access & ~(ACC_PRIVATE | ACC_PROTECTED)) | ACC_PUBLIC;
|
||||
modAccess = modAccess & ~ACC_FINAL;
|
||||
@@ -471,8 +503,11 @@ public class TypeRewriter implements Constants {
|
||||
}
|
||||
|
||||
static class FieldHolder {
|
||||
|
||||
final int access;
|
||||
|
||||
final String name;
|
||||
|
||||
final String desc;
|
||||
|
||||
public FieldHolder(int access, String name, String desc) {
|
||||
@@ -498,9 +533,10 @@ public class TypeRewriter implements Constants {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a basic dynamic dispatch handler. To support changes to interfaces, a new method is added to all reloadable
|
||||
* interfaces and this needs an implementation. This method generates the implementation which delegates to the reloadable
|
||||
* interface for the type. As interfaces can't get static methods we only have to worry about instance methods here.
|
||||
* Create a basic dynamic dispatch handler. To support changes to interfaces, a new method is added to all
|
||||
* reloadable interfaces and this needs an implementation. This method generates the implementation which
|
||||
* delegates to the reloadable interface for the type. As interfaces can't get static methods we only have to
|
||||
* worry about instance methods here.
|
||||
*/
|
||||
private void generateDynamicDispatchHandler() {
|
||||
final int indexThis = 0;
|
||||
@@ -510,8 +546,10 @@ public class TypeRewriter implements Constants {
|
||||
|
||||
if (isInterface) {
|
||||
cw.visitMethod(ACC_PUBLIC_ABSTRACT, mDynamicDispatchName, mDynamicDispatchDescriptor, null, null);
|
||||
} else {
|
||||
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, mDynamicDispatchName, mDynamicDispatchDescriptor, null, null);
|
||||
}
|
||||
else {
|
||||
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, mDynamicDispatchName, mDynamicDispatchDescriptor, null,
|
||||
null);
|
||||
|
||||
// Sometimes we come into the dynamic dispatcher because we are handling an INVOKEINTERFACE for a method
|
||||
// not defined on the original interface. In these cases we will find that fetchLatest() returns null because
|
||||
@@ -538,7 +576,8 @@ public class TypeRewriter implements Constants {
|
||||
|
||||
// 3. call it
|
||||
// return dispatchable.__execute(parameters,this,nameAndDescriptor)
|
||||
mv.visitMethodInsn(INVOKEINTERFACE, "org/springsource/loaded/__DynamicallyDispatchable", mDynamicDispatchName,
|
||||
mv.visitMethodInsn(INVOKEINTERFACE, "org/springsource/loaded/__DynamicallyDispatchable",
|
||||
mDynamicDispatchName,
|
||||
mDynamicDispatchDescriptor);
|
||||
mv.visitInsn(ARETURN);
|
||||
}
|
||||
@@ -573,9 +612,10 @@ public class TypeRewriter implements Constants {
|
||||
}
|
||||
|
||||
/**
|
||||
* Catcher methods are 'empty' methods added to subtypes to 'catch' any virtual dispatch calls that would otherwise be
|
||||
* missed. Catchers then check to see if the type on which they are defined now provides an implementation of the method in
|
||||
* question - if it does then it is called, otherwise the catcher simply calls the supertype.
|
||||
* Catcher methods are 'empty' methods added to subtypes to 'catch' any virtual dispatch calls that would
|
||||
* otherwise be missed. Catchers then check to see if the type on which they are defined now provides an
|
||||
* implementation of the method in question - if it does then it is called, otherwise the catcher simply calls
|
||||
* the supertype.
|
||||
* <p>
|
||||
* Catchers typically have the same visibility as the methods for which they exist, unless those methods are
|
||||
* protected/default, in which case the catcher is made public. This enables them to be seen from the executor.
|
||||
@@ -589,10 +629,10 @@ public class TypeRewriter implements Constants {
|
||||
for (FieldMember field : fms) {
|
||||
createProtectedFieldGetterSetter(field);
|
||||
}
|
||||
|
||||
|
||||
MethodMember[] methods = typeDescriptor.getMethods();
|
||||
|
||||
for (MethodMember method: methods) {
|
||||
for (MethodMember method : methods) {
|
||||
if (!MethodMember.isSuperDispatcher(method)) {
|
||||
continue;
|
||||
}
|
||||
@@ -600,17 +640,19 @@ public class TypeRewriter implements Constants {
|
||||
String name = method.getName();
|
||||
String descriptor = method.getDescriptor();
|
||||
if (GlobalConfiguration.verboseMode && log.isLoggable(Level.FINEST)) {
|
||||
log.finest("Creating super dispatcher for method "+name+descriptor+" in type "+slashedname);
|
||||
log.finest("Creating super dispatcher for method " + name + descriptor + " in type " + slashedname);
|
||||
}
|
||||
// Create a superdispatcher for this method
|
||||
MethodVisitor mv = cw.visitMethod(Modifier.PUBLIC, method.getName(), method.getDescriptor(), null, method.getExceptions());
|
||||
MethodVisitor mv = cw.visitMethod(Modifier.PUBLIC, method.getName(), method.getDescriptor(), null,
|
||||
method.getExceptions());
|
||||
int ps = Utils.getParameterCount(method.getDescriptor());
|
||||
ReturnType methodReturnType = Utils.getReturnTypeDescriptor(method.getDescriptor());
|
||||
int lvarIndex = 0;
|
||||
mv.visitVarInsn(ALOAD, lvarIndex++); // load this
|
||||
Utils.createLoadsBasedOnDescriptor(mv, descriptor, lvarIndex);
|
||||
String targetMethod = method.getName().substring(0,method.getName().lastIndexOf("_$"));
|
||||
mv.visitMethodInsn(Opcodes.INVOKESPECIAL,typeDescriptor.getSupertypeName(),targetMethod,method.getDescriptor());
|
||||
String targetMethod = method.getName().substring(0, method.getName().lastIndexOf("_$"));
|
||||
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, typeDescriptor.getSupertypeName(), targetMethod,
|
||||
method.getDescriptor());
|
||||
Utils.addCorrectReturnInstruction(mv, methodReturnType, false);
|
||||
int maxs = ps + 1;
|
||||
if (methodReturnType.isDoubleSlot()) {
|
||||
@@ -619,7 +661,7 @@ public class TypeRewriter implements Constants {
|
||||
mv.visitMaxs(maxs, maxs);
|
||||
mv.visitEnd();
|
||||
}
|
||||
|
||||
|
||||
for (MethodMember method : methods) {
|
||||
if (!MethodMember.isCatcher(method)) {
|
||||
continue;
|
||||
@@ -633,12 +675,14 @@ public class TypeRewriter implements Constants {
|
||||
if (Modifier.isProtected(flags)) {
|
||||
flags = flags - Modifier.PROTECTED + Modifier.PUBLIC;
|
||||
}
|
||||
MethodVisitor mv = cw.visitMethod(flags, method.getName(), method.getDescriptor(), null, method.getExceptions());
|
||||
MethodVisitor mv = cw.visitMethod(flags, method.getName(), method.getDescriptor(), null,
|
||||
method.getExceptions());
|
||||
|
||||
// 2. Ask the type if anything has changed from first load
|
||||
mv.visitFieldInsn(Opcodes.GETSTATIC, slashedname, fReloadableTypeFieldName, lReloadableType);
|
||||
mv.visitLdcInsn(method.getId());
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, tReloadableType, "fetchLatestIfExists", "(I)Ljava/lang/Object;");
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, tReloadableType, "fetchLatestIfExists",
|
||||
"(I)Ljava/lang/Object;");
|
||||
|
||||
// If the return value is null, there is no implementation
|
||||
mv.visitInsn(DUP);
|
||||
@@ -672,7 +716,8 @@ public class TypeRewriter implements Constants {
|
||||
mv.visitInsn(DUP);
|
||||
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/AbstractMethodError", "<init>", "()V");
|
||||
mv.visitInsn(ATHROW);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
mv.visitVarInsn(ALOAD, 0); // load this
|
||||
Utils.createLoadsBasedOnDescriptor(mv, method.getDescriptor(), 1);
|
||||
mv.visitMethodInsn(INVOKESPECIAL, supertypeName, method.getName(), method.getDescriptor());
|
||||
@@ -692,33 +737,34 @@ public class TypeRewriter implements Constants {
|
||||
private void insertCorrectLoad(MethodVisitor mv, ReturnType rt, int slot) {
|
||||
if (rt.isPrimitive()) {
|
||||
switch (rt.descriptor.charAt(0)) {
|
||||
case 'Z':
|
||||
case 'S':
|
||||
case 'I':
|
||||
case 'B':
|
||||
case 'C':
|
||||
mv.visitVarInsn(ILOAD, slot);
|
||||
break;
|
||||
case 'F':
|
||||
mv.visitVarInsn(FLOAD, slot);
|
||||
break;
|
||||
case 'J':
|
||||
mv.visitVarInsn(LLOAD, slot);
|
||||
break;
|
||||
case 'D':
|
||||
mv.visitVarInsn(DLOAD, slot);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException(rt.descriptor);
|
||||
case 'Z':
|
||||
case 'S':
|
||||
case 'I':
|
||||
case 'B':
|
||||
case 'C':
|
||||
mv.visitVarInsn(ILOAD, slot);
|
||||
break;
|
||||
case 'F':
|
||||
mv.visitVarInsn(FLOAD, slot);
|
||||
break;
|
||||
case 'J':
|
||||
mv.visitVarInsn(LLOAD, slot);
|
||||
break;
|
||||
case 'D':
|
||||
mv.visitVarInsn(DLOAD, slot);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException(rt.descriptor);
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
mv.visitVarInsn(ALOAD, slot);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For the fields that need it (protected fields from a non-reloadable supertype), create the getters and setters so that
|
||||
* the executor can read/write them.
|
||||
* For the fields that need it (protected fields from a non-reloadable supertype), create the getters and
|
||||
* setters so that the executor can read/write them.
|
||||
*
|
||||
*/
|
||||
private void createProtectedFieldGetterSetter(FieldMember field) {
|
||||
@@ -726,22 +772,26 @@ public class TypeRewriter implements Constants {
|
||||
String name = field.name;
|
||||
ReturnType rt = ReturnType.getReturnType(descriptor);
|
||||
if (field.isStatic()) {
|
||||
MethodVisitor mv = cw.visitMethod(Modifier.PUBLIC | Modifier.STATIC, Utils.getProtectedFieldGetterName(name), "()"
|
||||
+ descriptor, null, null);
|
||||
MethodVisitor mv = cw.visitMethod(Modifier.PUBLIC | Modifier.STATIC,
|
||||
Utils.getProtectedFieldGetterName(name), "()"
|
||||
+ descriptor, null, null);
|
||||
mv.visitFieldInsn(GETSTATIC, slashedname, name, descriptor);
|
||||
Utils.addCorrectReturnInstruction(mv, rt, false);
|
||||
mv.visitMaxs(rt.isDoubleSlot() ? 2 : 1, 0);
|
||||
mv.visitEnd();
|
||||
|
||||
mv = cw.visitMethod(Modifier.PUBLIC | Modifier.STATIC, Utils.getProtectedFieldSetterName(name), "(" + descriptor
|
||||
mv = cw.visitMethod(Modifier.PUBLIC | Modifier.STATIC, Utils.getProtectedFieldSetterName(name), "("
|
||||
+ descriptor
|
||||
+ ")V", null, null);
|
||||
insertCorrectLoad(mv, rt, 0);
|
||||
mv.visitFieldInsn(PUTSTATIC, slashedname, name, descriptor);
|
||||
mv.visitInsn(RETURN);
|
||||
mv.visitMaxs(rt.isDoubleSlot() ? 2 : 1, 1);
|
||||
mv.visitEnd();
|
||||
} else {
|
||||
MethodVisitor mv = cw.visitMethod(Modifier.PUBLIC, Utils.getProtectedFieldGetterName(name), "()" + descriptor,
|
||||
}
|
||||
else {
|
||||
MethodVisitor mv = cw.visitMethod(Modifier.PUBLIC, Utils.getProtectedFieldGetterName(name), "()"
|
||||
+ descriptor,
|
||||
null, null);
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitFieldInsn(GETFIELD, slashedname, name, descriptor);
|
||||
@@ -749,7 +799,8 @@ public class TypeRewriter implements Constants {
|
||||
mv.visitMaxs(rt.isDoubleSlot() ? 2 : 1, 1);
|
||||
mv.visitEnd();
|
||||
|
||||
mv = cw.visitMethod(Modifier.PUBLIC, Utils.getProtectedFieldSetterName(name), "(" + descriptor + ")V", null, null);
|
||||
mv = cw.visitMethod(Modifier.PUBLIC, Utils.getProtectedFieldSetterName(name), "(" + descriptor + ")V",
|
||||
null, null);
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
insertCorrectLoad(mv, rt, 1);
|
||||
mv.visitFieldInsn(PUTFIELD, slashedname, name, descriptor);
|
||||
@@ -765,10 +816,12 @@ public class TypeRewriter implements Constants {
|
||||
class ClinitPrepender implements Prepender, Constants {
|
||||
|
||||
MethodVisitor mv;
|
||||
|
||||
private final static String descriptorFor_getReloadableType = "(II)"+lReloadableType;
|
||||
private final static String descriptorFor_associateReloadableType = "("+lReloadableType+"Ljava/lang/Class;)V";
|
||||
|
||||
|
||||
private final static String descriptorFor_getReloadableType = "(II)" + lReloadableType;
|
||||
|
||||
private final static String descriptorFor_associateReloadableType = "(" + lReloadableType
|
||||
+ "Ljava/lang/Class;)V";
|
||||
|
||||
ClinitPrepender(MethodVisitor mv) {
|
||||
this.mv = mv;
|
||||
}
|
||||
@@ -779,13 +832,14 @@ public class TypeRewriter implements Constants {
|
||||
// TODO optimization: could collapse ints into one but this snippet isn't put in many places
|
||||
mv.visitLdcInsn(rtype.getTypeRegistryId());
|
||||
mv.visitLdcInsn(rtype.getId());
|
||||
mv.visitMethodInsn(INVOKESTATIC, tRegistryType, "getReloadableType", descriptorFor_getReloadableType, false);
|
||||
|
||||
mv.visitMethodInsn(INVOKESTATIC, tRegistryType, "getReloadableType", descriptorFor_getReloadableType,
|
||||
false);
|
||||
|
||||
mv.visitFieldInsn(PUTSTATIC, slashedname, fReloadableTypeFieldName, lReloadableType);
|
||||
// mv.visitFieldInsn(GETSTATIC, slashedname, fReloadableTypeFieldName, lReloadableType);
|
||||
// mv.visitLdcInsn(Type.getObjectType(rtype.getSlashedSupertypeName()));//Type("L" + rtype.getSlashedSupertypeName() + ";")); // faster way?
|
||||
// mv.visitMethodInsn(INVOKEVIRTUAL, tReloadableType, "setSuperclass", "(Ljava/lang/Class;)V");
|
||||
|
||||
|
||||
// only in the top most type - what about interfaces??
|
||||
if (GlobalConfiguration.fieldRewriting) {
|
||||
mv.visitFieldInsn(GETSTATIC, slashedname, fStaticFieldsName, lStaticStateManager);
|
||||
@@ -822,13 +876,17 @@ public class TypeRewriter implements Constants {
|
||||
class AugmentingMethodAdapter extends MethodVisitor implements Opcodes {
|
||||
|
||||
int methodId;
|
||||
|
||||
String name;
|
||||
|
||||
String descriptor;
|
||||
|
||||
MethodMember method;
|
||||
|
||||
ReturnType returnType;
|
||||
|
||||
public AugmentingMethodAdapter(MethodVisitor mv, String name, String descriptor) {
|
||||
super(ASM5,mv);
|
||||
super(ASM5, mv);
|
||||
this.name = name;
|
||||
this.method = rtype.getMethod(name, descriptor);
|
||||
this.methodId = method.getId();
|
||||
@@ -868,7 +926,8 @@ public class TypeRewriter implements Constants {
|
||||
TypeDescriptor superDescriptor = rtype.getTypeRegistry().getDescriptorFor(supertypeName);
|
||||
if (!superDescriptor.definesNonPrivate(name + descriptor)) {
|
||||
insertThrowNoSuchMethodError();
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
insertInvokeSpecialToCallSuperMethod();
|
||||
}
|
||||
mv.visitLabel(wasOne);
|
||||
@@ -880,7 +939,8 @@ public class TypeRewriter implements Constants {
|
||||
int lvarIndex = 0;
|
||||
if (!isStaticMethod) {
|
||||
mv.visitVarInsn(ALOAD, lvarIndex++);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
mv.visitInsn(ACONST_NULL);
|
||||
}
|
||||
Utils.createLoadsBasedOnDescriptor(mv, descriptor, lvarIndex);
|
||||
@@ -922,14 +982,19 @@ public class TypeRewriter implements Constants {
|
||||
class AugmentingConstructorAdapter extends MethodVisitor implements Opcodes {
|
||||
|
||||
int ctorId;
|
||||
|
||||
String name;
|
||||
|
||||
String descriptor;
|
||||
|
||||
MethodMember method;
|
||||
|
||||
String type;
|
||||
|
||||
boolean isTopMost;
|
||||
|
||||
public AugmentingConstructorAdapter(MethodVisitor mv, String descriptor, String type, boolean isTopMost) {
|
||||
super(ASM5,mv);
|
||||
super(ASM5, mv);
|
||||
this.descriptor = descriptor;
|
||||
this.type = type;
|
||||
this.isTopMost = isTopMost;
|
||||
@@ -965,8 +1030,10 @@ public class TypeRewriter implements Constants {
|
||||
mv.visitVarInsn(ALOAD, 1);
|
||||
mv.visitVarInsn(ILOAD, 2);
|
||||
mv.visitInsn(ACONST_NULL);
|
||||
mv.visitMethodInsn(INVOKESPECIAL, slashedname, "<init>", "(Ljava/lang/String;ILorg/springsource/loaded/C;)V");
|
||||
} else {
|
||||
mv.visitMethodInsn(INVOKESPECIAL, slashedname, "<init>",
|
||||
"(Ljava/lang/String;ILorg/springsource/loaded/C;)V");
|
||||
}
|
||||
else {
|
||||
mv.visitInsn(ACONST_NULL);
|
||||
mv.visitMethodInsn(INVOKESPECIAL, slashedname, "<init>", "(Lorg/springsource/loaded/C;)V");
|
||||
}
|
||||
@@ -1024,7 +1091,8 @@ public class TypeRewriter implements Constants {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc, final boolean itf) {
|
||||
public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc,
|
||||
final boolean itf) {
|
||||
super.visitMethodInsn(opcode, owner, name, desc, itf);
|
||||
if (opcode == INVOKESPECIAL) {
|
||||
unitializedObjectsCount--;
|
||||
@@ -1053,6 +1121,7 @@ public class TypeRewriter implements Constants {
|
||||
}
|
||||
|
||||
interface Prepender {
|
||||
|
||||
void prepend();
|
||||
}
|
||||
|
||||
@@ -1061,7 +1130,7 @@ public class TypeRewriter implements Constants {
|
||||
Prepender appender;
|
||||
|
||||
public MethodPrepender(MethodVisitor mv, Prepender appender) {
|
||||
super(ASM5,mv);
|
||||
super(ASM5, mv);
|
||||
this.appender = appender;
|
||||
}
|
||||
|
||||
@@ -1075,4 +1144,4 @@ public class TypeRewriter implements Constants {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded;
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,11 +13,12 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded;
|
||||
|
||||
/**
|
||||
* UnableToReloadEventProcessor Plugins are called when a type cannot be reloaded due to an unsupported change. For information on
|
||||
* registering them, see {@link Plugin}
|
||||
* UnableToReloadEventProcessor Plugins are called when a type cannot be reloaded due to an unsupported change. For
|
||||
* information on registering them, see {@link Plugin}
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.7.3
|
||||
@@ -25,16 +26,17 @@ package org.springsource.loaded;
|
||||
public interface UnableToReloadEventProcessorPlugin extends Plugin {
|
||||
|
||||
/**
|
||||
* Called when a type cannot be reloaded, due to a change being made that is not supported by the agent, for example when the
|
||||
* set of interfaces for a type is changed. Note, the class is only truly defined to the VM once, and so the Class object (clazz
|
||||
* parameter) is always the same for the same type (ignoring multiple classloader situations). It is passed here so that plugins
|
||||
* processing events can clear any cached state related to it. The encodedTimestamp is an encoding of the ID that the agent has
|
||||
* assigned to this reloaded version of this type. The TypeDelta (a work in progress) captures details about what changed in the
|
||||
* type that could not be reloaded.
|
||||
* Called when a type cannot be reloaded, due to a change being made that is not supported by the agent, for example
|
||||
* when the set of interfaces for a type is changed. Note, the class is only truly defined to the VM once, and so
|
||||
* the Class object (clazz parameter) is always the same for the same type (ignoring multiple classloader
|
||||
* situations). It is passed here so that plugins processing events can clear any cached state related to it. The
|
||||
* encodedTimestamp is an encoding of the ID that the agent has assigned to this reloaded version of this type. The
|
||||
* TypeDelta (a work in progress) captures details about what changed in the type that could not be reloaded.
|
||||
*
|
||||
* @param typename the (dotted) type name, for example java.lang.String
|
||||
* @param clazz the Class object that has been reloaded
|
||||
* @param typeDelta encapsulates information about the changes made in this version of the type that prevented the reload
|
||||
* @param typeDelta encapsulates information about the changes made in this version of the type that prevented the
|
||||
* reload
|
||||
* @param encodedTimestamp an encoded time stamp for this version, containing chars (A-Za-z0-9)
|
||||
*/
|
||||
void unableToReloadEvent(String typename, Class<?> clazz, TypeDelta typeDelta, String encodedTimestamp);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -13,12 +13,13 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded;
|
||||
|
||||
/**
|
||||
* Interface implemented by all dispatchers so code can be generated to call the dynamic executor regardless of the dispatcher
|
||||
* instance the code is actually working with. The method name here lines up with that defined in Constants - see
|
||||
* mDynamicDispatchName.
|
||||
* Interface implemented by all dispatchers so code can be generated to call the dynamic executor regardless of the
|
||||
* dispatcher instance the code is actually working with. The method name here lines up with that defined in Constants -
|
||||
* see mDynamicDispatchName.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded.agent;
|
||||
|
||||
import java.security.ProtectionDomain;
|
||||
@@ -34,8 +35,9 @@ public class CglibPlugin implements LoadtimeInstrumentationPlugin {
|
||||
private static Logger log = Logger.getLogger(CglibPlugin.class.getName());
|
||||
|
||||
// implementing LoadtimeInstrumentationPlugin
|
||||
public boolean accept(String slashedTypeName, ClassLoader classLoader, ProtectionDomain protectionDomain, byte[] bytes) {
|
||||
if (slashedTypeName==null) {
|
||||
public boolean accept(String slashedTypeName, ClassLoader classLoader, ProtectionDomain protectionDomain,
|
||||
byte[] bytes) {
|
||||
if (slashedTypeName == null) {
|
||||
return false;
|
||||
}
|
||||
// Sometimes the package prefix for cglib types is changed, for example:
|
||||
@@ -48,11 +50,11 @@ public class CglibPlugin implements LoadtimeInstrumentationPlugin {
|
||||
|
||||
public byte[] modify(String slashedClassName, ClassLoader classLoader, byte[] bytes) {
|
||||
if (GlobalConfiguration.verboseMode && log.isLoggable(Level.INFO)) {
|
||||
log.info("Modifying "+slashedClassName);
|
||||
log.info("Modifying " + slashedClassName);
|
||||
}
|
||||
// if (slashedClassName.equals("net/sf/cglib/core/AbstractClassGenerator")) {
|
||||
return CglibPluginCapturing.catchGenerate(bytes);
|
||||
|
||||
|
||||
// Not currently worrying about FastClass:
|
||||
// } else {
|
||||
// net/sf/cglib/reflect/FastClass
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded.agent;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
@@ -26,8 +27,9 @@ import org.objectweb.asm.MethodVisitor;
|
||||
import org.springsource.loaded.Constants;
|
||||
|
||||
/**
|
||||
* This bytecode rewriter intercepts calls to generate made in the CGLIB framework and allows us to record what generator is called
|
||||
* to create the proxy for some type. The same generator can then be driven again if the type is reloaded.
|
||||
* This bytecode rewriter intercepts calls to generate made in the CGLIB framework and allows us to record what
|
||||
* generator is called to create the proxy for some type. The same generator can then be driven again if the type is
|
||||
* reloaded.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.8.3
|
||||
@@ -35,9 +37,11 @@ import org.springsource.loaded.Constants;
|
||||
public class CglibPluginCapturing extends ClassVisitor implements Constants {
|
||||
|
||||
public static Map<Class<?>, Object[]> clazzToGeneratorStrategyAndClassGeneratorMap = new HashMap<Class<?>, Object[]>();
|
||||
|
||||
public static Map<Class<?>, Object[]> clazzToGeneratorStrategyAndFastClassGeneratorMap = new HashMap<Class<?>, Object[]>();
|
||||
|
||||
public String prefix;
|
||||
|
||||
|
||||
public static byte[] catchGenerate(byte[] bytesIn) {
|
||||
ClassReader cr = new ClassReader(bytesIn);
|
||||
CglibPluginCapturing ca = new CglibPluginCapturing();
|
||||
@@ -47,17 +51,17 @@ public class CglibPluginCapturing extends ClassVisitor implements Constants {
|
||||
}
|
||||
|
||||
private CglibPluginCapturing() {
|
||||
super(ASM5,new ClassWriter(0)); // TODO review 0 here
|
||||
super(ASM5, new ClassWriter(0)); // TODO review 0 here
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
|
||||
// The name could be a repackaged form of cglib:
|
||||
// net/sf/cglib/core/AbstractClassGenerator
|
||||
// org/springframework/cglib/core/AbstractClassGenerator
|
||||
int index = name.indexOf("/cglib");
|
||||
prefix = name.substring(0,index);
|
||||
super.visit(version,access,name,signature,superName,interfaces);
|
||||
prefix = name.substring(0, index);
|
||||
super.visit(version, access, name, signature, superName, interfaces);
|
||||
}
|
||||
|
||||
public byte[] getBytes() {
|
||||
@@ -68,7 +72,8 @@ public class CglibPluginCapturing extends ClassVisitor implements Constants {
|
||||
if (name.equals("create")) {
|
||||
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
|
||||
return new CreateMethodInterceptor(mv);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
return super.visitMethod(access, name, desc, signature, exceptions);
|
||||
}
|
||||
}
|
||||
@@ -76,7 +81,7 @@ public class CglibPluginCapturing extends ClassVisitor implements Constants {
|
||||
class CreateMethodInterceptor extends MethodVisitor implements Constants {
|
||||
|
||||
public CreateMethodInterceptor(MethodVisitor mv) {
|
||||
super(ASM5,mv);
|
||||
super(ASM5, mv);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -84,11 +89,13 @@ public class CglibPluginCapturing extends ClassVisitor implements Constants {
|
||||
}
|
||||
|
||||
/**
|
||||
* Recognize a call to 'generate' being made. When we see it add some extra code after it that calls the record method in
|
||||
* this type so that we can remember the generator used (and drive it again later when the related type is reloaded).
|
||||
* Recognize a call to 'generate' being made. When we see it add some extra code after it that calls the record
|
||||
* method in this type so that we can remember the generator used (and drive it again later when the related
|
||||
* type is reloaded).
|
||||
*/
|
||||
@Override
|
||||
public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc, final boolean itf) {
|
||||
public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc,
|
||||
final boolean itf) {
|
||||
super.visitMethodInsn(opcode, owner, name, desc, itf);
|
||||
if (name.equals("generate")) {
|
||||
// Code that calls generate:
|
||||
@@ -97,8 +104,8 @@ public class CglibPluginCapturing extends ClassVisitor implements Constants {
|
||||
// ALOAD 0
|
||||
// INVOKEINTERFACE net/sf/cglib/core/GeneratorStrategy.generate(Lnet/sf/cglib/core/ClassGenerator;)[B
|
||||
mv.visitVarInsn(ALOAD, 0); // AbstractClassGenerator instance
|
||||
mv.visitFieldInsn(GETFIELD, prefix+"/cglib/core/AbstractClassGenerator", "strategy",
|
||||
"L"+prefix+"/cglib/core/GeneratorStrategy;");
|
||||
mv.visitFieldInsn(GETFIELD, prefix + "/cglib/core/AbstractClassGenerator", "strategy",
|
||||
"L" + prefix + "/cglib/core/GeneratorStrategy;");
|
||||
mv.visitVarInsn(ALOAD, 0); // AbstractClassGenerator instance
|
||||
mv.visitMethodInsn(INVOKESTATIC, "org/springsource/loaded/agent/CglibPluginCapturing", "record",
|
||||
"(Ljava/lang/Object;Ljava/lang/Object;)V", false);//Lnet/sf/cglib/core/GeneratorStrategy;Lnet/sf/cglib/core/AbstractClassGenerator);");
|
||||
@@ -108,9 +115,10 @@ public class CglibPluginCapturing extends ClassVisitor implements Constants {
|
||||
}
|
||||
|
||||
/**
|
||||
* The classloader for class artifacts is used to load the generated classes for call sites. We need to rewrite these classes
|
||||
* because they may be either calling something that disappears on a later reload (so need to fail appropriately) or calling
|
||||
* something that isnt there on the first load - in this latter case they are changed to route the dynamic executor method.
|
||||
* The classloader for class artifacts is used to load the generated classes for call sites. We need to rewrite
|
||||
* these classes because they may be either calling something that disappears on a later reload (so need to fail
|
||||
* appropriately) or calling something that isnt there on the first load - in this latter case they are changed to
|
||||
* route the dynamic executor method.
|
||||
*
|
||||
* @param a the GeneratorStrategy being used
|
||||
* @param b the AbstractClassGenerator
|
||||
@@ -128,19 +136,22 @@ public class CglibPluginCapturing extends ClassVisitor implements Constants {
|
||||
Class<?> clazz = (Class<?>) f.get(b);
|
||||
// System.out.println("Recording pair " + clazz.getName() + " > " + b);
|
||||
clazzToGeneratorStrategyAndClassGeneratorMap.put(clazz, new Object[] { a, b });
|
||||
} catch (Throwable re) {
|
||||
}
|
||||
catch (Throwable re) {
|
||||
re.printStackTrace();
|
||||
}
|
||||
} else if (generatorName.endsWith(".cglib.reflect.FastClass$Generator")) {
|
||||
}
|
||||
else if (generatorName.endsWith(".cglib.reflect.FastClass$Generator")) {
|
||||
try {
|
||||
Field f = b.getClass().getDeclaredField("type");
|
||||
f.setAccessible(true);
|
||||
Class<?> clazz = (Class<?>) f.get(b);
|
||||
// System.out.println("Recording pair (fastclass) " + clazz.getName() + " > " + b);
|
||||
clazzToGeneratorStrategyAndFastClassGeneratorMap.put(clazz, new Object[] { a, b });
|
||||
} catch (Throwable re) {
|
||||
}
|
||||
catch (Throwable re) {
|
||||
re.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded.agent;
|
||||
|
||||
import java.lang.instrument.ClassFileTransformer;
|
||||
@@ -48,7 +49,8 @@ public class ClassPreProcessorAgentAdapter implements ClassFileTransformer {
|
||||
try {
|
||||
preProcessor = new SpringLoadedPreProcessor();
|
||||
preProcessor.initialize();
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new ExceptionInInitializerError("could not initialize JSR163 preprocessor due to: " + e.toString());
|
||||
}
|
||||
}
|
||||
@@ -61,12 +63,15 @@ public class ClassPreProcessorAgentAdapter implements ClassFileTransformer {
|
||||
* @param bytes the bytecode before weaving
|
||||
* @return the weaved bytecode
|
||||
*/
|
||||
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
|
||||
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
|
||||
ProtectionDomain protectionDomain,
|
||||
byte[] bytes) throws IllegalClassFormatException {
|
||||
try {
|
||||
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) {
|
||||
log.info("> (loader=" + loader + " className=" + className + ", classBeingRedefined=" + classBeingRedefined
|
||||
+ ", protectedDomain=" + (protectionDomain != null) + ", bytes= " + (bytes == null ? "null" : bytes.length));
|
||||
log.info("> (loader=" + loader + " className=" + className + ", classBeingRedefined="
|
||||
+ classBeingRedefined
|
||||
+ ", protectedDomain=" + (protectionDomain != null) + ", bytes= "
|
||||
+ (bytes == null ? "null" : bytes.length));
|
||||
}
|
||||
|
||||
// TODO determine if this is the right behaviour for hot code replace:
|
||||
@@ -100,7 +105,8 @@ public class ClassPreProcessorAgentAdapter implements ClassFileTransformer {
|
||||
// System.err.println("transform(" + loader.getClass().getName() + ",classname=" + className +
|
||||
// ",classBeingRedefined=" + classBeingRedefined + ",protectionDomain=" + protectionDomain + ")");
|
||||
return preProcessor.preProcess(loader, className, protectionDomain, bytes);
|
||||
} catch (Throwable t) {
|
||||
}
|
||||
catch (Throwable t) {
|
||||
new RuntimeException("Reloading agent exited via exception, please raise a jira", t).printStackTrace();
|
||||
return bytes;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded.agent;
|
||||
|
||||
import org.objectweb.asm.ClassVisitor;
|
||||
@@ -29,18 +30,19 @@ import org.springsource.loaded.Constants;
|
||||
public class ClassVisitingConstructorAppender extends ClassVisitor implements Constants {
|
||||
|
||||
private String calleeOwner;
|
||||
|
||||
private String calleeName;
|
||||
|
||||
/**
|
||||
* This ClassAdapter will visit a class and within the constructors it will add a call to the specified method (assumed static)
|
||||
* just before each constructor returns. The target of the call should be a collecting method that will likely do something with
|
||||
* the instances later on class reload.
|
||||
* This ClassAdapter will visit a class and within the constructors it will add a call to the specified method
|
||||
* (assumed static) just before each constructor returns. The target of the call should be a collecting method that
|
||||
* will likely do something with the instances later on class reload.
|
||||
*
|
||||
* @param owner the owning class of the method to call
|
||||
* @param name the method to call
|
||||
*/
|
||||
public ClassVisitingConstructorAppender(String owner, String name) {
|
||||
super(ASM5,new ClassWriter(0)); // TODO review 0 here
|
||||
super(ASM5, new ClassWriter(0)); // TODO review 0 here
|
||||
this.calleeOwner = owner;
|
||||
this.calleeName = name;
|
||||
}
|
||||
@@ -53,19 +55,20 @@ public class ClassVisitingConstructorAppender extends ClassVisitor implements Co
|
||||
if (name.equals("<init>")) {
|
||||
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
|
||||
return new ConstructorAppender(mv);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
return super.visitMethod(access, name, desc, signature, exceptions);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This constructor appender includes a couple of instructions at the end of each constructor it is asked to visit. It
|
||||
* recognizes the end by observing a RETURN instruction. The instructions are inserted just before the RETURN.
|
||||
* This constructor appender includes a couple of instructions at the end of each constructor it is asked to visit.
|
||||
* It recognizes the end by observing a RETURN instruction. The instructions are inserted just before the RETURN.
|
||||
*/
|
||||
class ConstructorAppender extends MethodVisitor implements Constants {
|
||||
|
||||
public ConstructorAppender(MethodVisitor mv) {
|
||||
super(ASM5,mv);
|
||||
super(ASM5, mv);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -78,4 +81,4 @@ public class ClassVisitingConstructorAppender extends ClassVisitor implements Co
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded.agent;
|
||||
|
||||
import org.objectweb.asm.ClassVisitor;
|
||||
@@ -31,7 +32,7 @@ public class FalseReturner extends ClassVisitor implements Constants {
|
||||
private String methodname;
|
||||
|
||||
public FalseReturner(String methodname) {
|
||||
super(ASM5,new ClassWriter(0)); // TODO review 0 here
|
||||
super(ASM5, new ClassWriter(0)); // TODO review 0 here
|
||||
this.methodname = methodname;
|
||||
}
|
||||
|
||||
@@ -59,7 +60,8 @@ public class FalseReturner extends ClassVisitor implements Constants {
|
||||
mv.visitEnd();
|
||||
return mv;
|
||||
// return new FalseReturnerMV(mv);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
return super.visitMethod(access, name, desc, signature, exceptions);
|
||||
}
|
||||
}
|
||||
@@ -71,4 +73,4 @@ public class FalseReturner extends ClassVisitor implements Constants {
|
||||
// class FakeMethodVisitor implements MethodVisitor, Constants {
|
||||
//
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded.agent;
|
||||
|
||||
import java.io.File;
|
||||
@@ -27,9 +28,10 @@ import org.springsource.loaded.GlobalConfiguration;
|
||||
import org.springsource.loaded.TypeRegistry;
|
||||
|
||||
/**
|
||||
* A simple watcher for the file system. Uses a thread to keep an eye on a number of files and calls back registered interested
|
||||
* parties when a change is observed. The thread only starts when there is something to watch. The thread is given a name indicating
|
||||
* the classloader for which it is watching files. Once it starts to watch files the name will be enhanced to indicate how many.
|
||||
* A simple watcher for the file system. Uses a thread to keep an eye on a number of files and calls back registered
|
||||
* interested parties when a change is observed. The thread only starts when there is something to watch. The thread is
|
||||
* given a name indicating the classloader for which it is watching files. Once it starts to watch files the name will
|
||||
* be enhanced to indicate how many.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
@@ -73,8 +75,8 @@ public class FileSystemWatcher {
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new file to the list of those being monitored. If the file is something that can be watched, then this method will
|
||||
* cause the thread to start (if it hasn't already been started).
|
||||
* Add a new file to the list of those being monitored. If the file is something that can be watched, then this
|
||||
* method will cause the thread to start (if it hasn't already been started).
|
||||
*
|
||||
* @param fileToMonitor the file to start monitor
|
||||
*/
|
||||
@@ -95,23 +97,34 @@ public class FileSystemWatcher {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class Watcher implements Runnable {
|
||||
|
||||
private static Logger log = Logger.getLogger(Watcher.class.getName());
|
||||
|
||||
|
||||
long lastScanTime;
|
||||
|
||||
// TODO configurable scan interval?
|
||||
private static long interval = 1100;// ms
|
||||
|
||||
List<File> watchListFiles = new ArrayList<File>();
|
||||
|
||||
List<Long> watchListLMTs = new ArrayList<Long>();
|
||||
|
||||
FileChangeListener listener;
|
||||
|
||||
private boolean timeToStop = false;
|
||||
|
||||
public boolean paused = false;
|
||||
|
||||
private Thread thread = null;
|
||||
|
||||
private int typeRegistryId;
|
||||
|
||||
private String classloadername;
|
||||
|
||||
private int registryLivenessCount = 0;
|
||||
|
||||
private static int registryLivenessCountInterval = 300;
|
||||
|
||||
public Watcher(FileChangeListener listener, int typeRegistryId, String classloadername) {
|
||||
@@ -125,8 +138,8 @@ class Watcher implements Runnable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new File that the thread should start watching. If the file does not exist nothing happens (this may be because a class
|
||||
* has been generated on the fly and really there is nothing to watch on disk).
|
||||
* Add a new File that the thread should start watching. If the file does not exist nothing happens (this may be
|
||||
* because a class has been generated on the fly and really there is nothing to watch on disk).
|
||||
*
|
||||
* @param fileToWatch the new file to watch
|
||||
* @return true if the file is now being watched, false otherwise
|
||||
@@ -137,13 +150,14 @@ class Watcher implements Runnable {
|
||||
}
|
||||
synchronized (this) {
|
||||
if (GlobalConfiguration.verboseMode && log.isLoggable(Level.INFO)) {
|
||||
log.info("Now watching "+fileToWatch);
|
||||
log.info("Now watching " + fileToWatch);
|
||||
}
|
||||
int insertionPos = findPosition(fileToWatch);
|
||||
if (insertionPos == -1) {
|
||||
watchListFiles.add(fileToWatch);
|
||||
watchListLMTs.add(fileToWatch.lastModified());
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
watchListFiles.add(insertionPos, fileToWatch);
|
||||
watchListLMTs.add(insertionPos, fileToWatch.lastModified());
|
||||
}
|
||||
@@ -174,7 +188,7 @@ class Watcher implements Runnable {
|
||||
else if (GlobalConfiguration.assertsMode && cmp == 0) {
|
||||
// Are we watching the same file twice, that is bad!
|
||||
if (file2.getAbsoluteFile().toString().equals(file.getAbsoluteFile().toString())) {
|
||||
log.severe("Watching the same file twice: "+file.getAbsoluteFile().toString());
|
||||
log.severe("Watching the same file twice: " + file.getAbsoluteFile().toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -196,7 +210,8 @@ class Watcher implements Runnable {
|
||||
}
|
||||
try {
|
||||
Thread.sleep(interval);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
}
|
||||
if (!paused) {
|
||||
List<File> changedFiles = new ArrayList<File>();
|
||||
@@ -207,7 +222,8 @@ class Watcher implements Runnable {
|
||||
long lastModTime = file.lastModified();
|
||||
if (lastModTime > watchListLMTs.get(f)) {
|
||||
if (GlobalConfiguration.verboseMode && log.isLoggable(Level.INFO)) {
|
||||
log.info("Observed last modification time change for "+file+" (lastScanTime="+lastScanTime+")");
|
||||
log.info("Observed last modification time change for " + file + " (lastScanTime="
|
||||
+ lastScanTime + ")");
|
||||
}
|
||||
watchListLMTs.set(f, lastModTime);
|
||||
changedFiles.add(file);
|
||||
@@ -215,7 +231,7 @@ class Watcher implements Runnable {
|
||||
}
|
||||
lastScanTime = System.currentTimeMillis();
|
||||
}
|
||||
for (File changedFile: changedFiles) {
|
||||
for (File changedFile : changedFiles) {
|
||||
determineChangesSince(changedFile, lastScanTime);
|
||||
}
|
||||
}
|
||||
@@ -230,7 +246,7 @@ class Watcher implements Runnable {
|
||||
private void determineChangesSince(File file, long lastScanTime) {
|
||||
try {
|
||||
if (GlobalConfiguration.verboseMode && log.isLoggable(Level.INFO)) {
|
||||
log.info("Firing file changed event "+file);
|
||||
log.info("Firing file changed event " + file);
|
||||
}
|
||||
listener.fileChanged(file);
|
||||
if (file.isDirectory()) {
|
||||
@@ -238,18 +254,21 @@ class Watcher implements Runnable {
|
||||
for (File f : filesOfInterest) {
|
||||
if (f.isDirectory()) {
|
||||
determineChangesSince(f, lastScanTime);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
if (GlobalConfiguration.verboseMode && log.isLoggable(Level.INFO)) {
|
||||
log.info("Observed last modification time change for "+f+" (lastScanTime="+lastScanTime+")");
|
||||
log.info("Firing file changed event "+file);
|
||||
log.info("Observed last modification time change for " + f + " (lastScanTime="
|
||||
+ lastScanTime + ")");
|
||||
log.info("Firing file changed event " + file);
|
||||
}
|
||||
listener.fileChanged(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
}
|
||||
catch (Throwable t) {
|
||||
if (log.isLoggable(Level.SEVERE)) {
|
||||
log.log(Level.SEVERE,"FileWatcher caught serious error, see cause",t);
|
||||
log.log(Level.SEVERE, "FileWatcher caught serious error, see cause", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded.agent;
|
||||
|
||||
import java.lang.ref.Reference;
|
||||
@@ -48,7 +49,8 @@ public class GrailsPlugin implements LoadtimeInstrumentationPlugin, ReloadEventP
|
||||
/**
|
||||
* @return true for types this plugin would like to change on startup
|
||||
*/
|
||||
public boolean accept(String slashedTypeName, ClassLoader classLoader, ProtectionDomain protectionDomain, byte[] bytes) {
|
||||
public boolean accept(String slashedTypeName, ClassLoader classLoader, ProtectionDomain protectionDomain,
|
||||
byte[] bytes) {
|
||||
// TODO take classloader into account?
|
||||
return false;//DefaultClassPropertyFetcher.equals(slashedTypeName);
|
||||
}
|
||||
@@ -66,6 +68,7 @@ public class GrailsPlugin implements LoadtimeInstrumentationPlugin, ReloadEventP
|
||||
}
|
||||
|
||||
private Field classPropertyFetcher_clazz;
|
||||
|
||||
private Method classPropertyFetcher_init;
|
||||
|
||||
public void reloadEvent(String typename, Class<?> reloadedClazz, String versionsuffix) {
|
||||
@@ -93,14 +96,16 @@ public class GrailsPlugin implements LoadtimeInstrumentationPlugin, ReloadEventP
|
||||
classPropertyFetcher_init.setAccessible(true);
|
||||
classPropertyFetcher_init.invoke(instance);
|
||||
if (GlobalConfiguration.debugplugins) {
|
||||
System.err.println("GrailsPlugin: re-initing classPropertyFetcher instance for " + clazz.getName()
|
||||
System.err.println("GrailsPlugin: re-initing classPropertyFetcher instance for "
|
||||
+ clazz.getName()
|
||||
+ " " + System.identityHashCode(instance));
|
||||
}
|
||||
// System.out.println("re-initing " + reloadedClazz.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded.agent;
|
||||
|
||||
import java.security.ProtectionDomain;
|
||||
@@ -28,14 +29,14 @@ import org.springsource.loaded.ReloadEventProcessorPlugin;
|
||||
* So far the GroovyPlugin can do two different things - configurable through the 'allowCompilableCallSites' flag.
|
||||
*
|
||||
* <p>
|
||||
* If the flag is false: The plugin intercepts two of the main system types in groovy and turns OFF call site compilation. Without
|
||||
* this compilation the compiler will not be generating classes, it will instead be using reflection all the time. This is simpler
|
||||
* to handle (as we intercept reflection) but performance == thesuck.
|
||||
* If the flag is false: The plugin intercepts two of the main system types in groovy and turns OFF call site
|
||||
* compilation. Without this compilation the compiler will not be generating classes, it will instead be using
|
||||
* reflection all the time. This is simpler to handle (as we intercept reflection) but performance == thesuck.
|
||||
* <p>
|
||||
* If the flag is true: The plugin leaves groovy to compile call sites. We intercept the define method in the classloader used to
|
||||
* define these generated call site classes and ensure they are rewritten correctly. Note there is an alternative here of getting
|
||||
* the SpringLoadedPreProcessor to recognize these special classloaders and just instrument them that way. However, if we let the
|
||||
* plugin do it it is easier to test!
|
||||
* If the flag is true: The plugin leaves groovy to compile call sites. We intercept the define method in the
|
||||
* classloader used to define these generated call site classes and ensure they are rewritten correctly. Note there is
|
||||
* an alternative here of getting the SpringLoadedPreProcessor to recognize these special classloaders and just
|
||||
* instrument them that way. However, if we let the plugin do it it is easier to test!
|
||||
* <p>
|
||||
* To see the difference in these approaches, check the numbers in the Groovy Benchmark tests.
|
||||
*
|
||||
@@ -49,15 +50,17 @@ public class GroovyPlugin implements LoadtimeInstrumentationPlugin, ReloadEventP
|
||||
// GroovySunClassLoader - can make the final field non final so it can be set to null (it is checked as part of the isCompilable methodin the callsitegenerator)
|
||||
// CallSiteGenerator - make isCompilable return false, which means we will never generate a direct call to a method that may not yet be on the target
|
||||
// implementing LoadtimeInstrumentationPlugin
|
||||
public boolean accept(String slashedTypeName, ClassLoader classLoader, ProtectionDomain protectionDomain, byte[] bytes) {
|
||||
public boolean accept(String slashedTypeName, ClassLoader classLoader, ProtectionDomain protectionDomain,
|
||||
byte[] bytes) {
|
||||
// TODO take classloader into account?
|
||||
if (slashedTypeName==null) {
|
||||
if (slashedTypeName == null) {
|
||||
return false;
|
||||
}
|
||||
if (!allowCompilableCallSites) {
|
||||
return slashedTypeName.equals("org/codehaus/groovy/runtime/callsite/GroovySunClassLoader")
|
||||
|| slashedTypeName.equals("org/codehaus/groovy/runtime/callsite/CallSiteGenerator");
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
if (slashedTypeName.equals("org/codehaus/groovy/reflection/ClassLoaderForClassArtifacts")) {
|
||||
return true;
|
||||
}
|
||||
@@ -68,7 +71,8 @@ public class GroovyPlugin implements LoadtimeInstrumentationPlugin, ReloadEventP
|
||||
public byte[] modify(String slashedClassName, ClassLoader classLoader, byte[] bytes) {
|
||||
if (allowCompilableCallSites) {
|
||||
return modifyDefineInClassLoaderForClassArtifacts(bytes);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
// Deactivate compilation
|
||||
if (slashedClassName.equals("org/codehaus/groovy/runtime/callsite/GroovySunClassLoader")) {
|
||||
// if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) {
|
||||
@@ -81,7 +85,8 @@ public class GroovyPlugin implements LoadtimeInstrumentationPlugin, ReloadEventP
|
||||
cr.accept(ca, 0);
|
||||
byte[] newbytes = ca.getBytes();
|
||||
return newbytes;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
// must be the CallSiteGenerator
|
||||
ClassReader cr = new ClassReader(bytes);
|
||||
FalseReturner ca = new FalseReturner("isCompilable");
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded.agent;
|
||||
|
||||
/**
|
||||
@@ -28,4 +29,4 @@ public class Impossible extends RuntimeException {
|
||||
super("This is completely unexpected!", cause);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded.agent;
|
||||
|
||||
import java.beans.BeanInfo;
|
||||
@@ -33,8 +34,9 @@ import org.springsource.loaded.ReloadEventProcessorPlugin;
|
||||
|
||||
|
||||
/**
|
||||
* Reloading plugin for 'poking' JVM classes that are known to cache reflective state. Some of the behaviour is switched ON based on
|
||||
* which classes are loaded. For example the Introspector clearing logic is only activated if the Introspector gets loaded.
|
||||
* Reloading plugin for 'poking' JVM classes that are known to cache reflective state. Some of the behaviour is switched
|
||||
* ON based on which classes are loaded. For example the Introspector clearing logic is only activated if the
|
||||
* Introspector gets loaded.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.7.3
|
||||
@@ -44,62 +46,73 @@ public class JVMPlugin implements ReloadEventProcessorPlugin, LoadtimeInstrument
|
||||
private boolean pluginBroken = false;
|
||||
|
||||
private boolean introspectorLoaded = false;
|
||||
|
||||
private boolean threadGroupContextLoaded = false;
|
||||
|
||||
private Field beanInfoCacheField;
|
||||
|
||||
private Field declaredMethodCacheField;
|
||||
|
||||
private Method putMethod;
|
||||
|
||||
private Class<?> threadGroupContextClass;
|
||||
|
||||
private Field threadGroupContext_contextsField; /* Map<ThreadGroup,ThreadGroupContext> */
|
||||
|
||||
private Method threadGroupContext_removeBeanInfoMethod; /* removeBeanInfo(Class<?> type) { */
|
||||
|
||||
|
||||
|
||||
private void tidySerialization(Class<?> reloadedClass) {
|
||||
// if (true) return;
|
||||
// if (true) return;
|
||||
try {
|
||||
Class<?> clazz = Class.forName("java.io.ObjectStreamClass$Caches");
|
||||
Field localDescsField = clazz.getDeclaredField("localDescs");
|
||||
localDescsField.setAccessible(true);
|
||||
ConcurrentMap cm = (ConcurrentMap)localDescsField.get(null);
|
||||
ConcurrentMap cm = (ConcurrentMap) localDescsField.get(null);
|
||||
// TODO [serialization] a bit extreme to wipe out everything
|
||||
cm.clear();
|
||||
// For some reason clearing the reflectors damages serialization - is it not a true cache?
|
||||
// Field reflectorsField = clazz.getDeclaredField("reflectors");
|
||||
// reflectorsField.setAccessible(true);
|
||||
// cm = (ConcurrentMap)reflectorsField.get(null);
|
||||
// cm.clear();
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new IllegalStateException(e);
|
||||
} catch (SecurityException e) {
|
||||
throw new IllegalStateException(e);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new IllegalStateException(e);
|
||||
} catch (IllegalAccessException e) {
|
||||
// Field reflectorsField = clazz.getDeclaredField("reflectors");
|
||||
// reflectorsField.setAccessible(true);
|
||||
// cm = (ConcurrentMap)reflectorsField.get(null);
|
||||
// cm.clear();
|
||||
}
|
||||
catch (ClassNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
||||
|
||||
// private static class Caches {
|
||||
// /** cache mapping local classes -> descriptors */
|
||||
// static final ConcurrentMap<WeakClassKey,Reference<?>> localDescs =
|
||||
// new ConcurrentHashMap<>();
|
||||
//
|
||||
// /** cache mapping field group/local desc pairs -> field reflectors */
|
||||
// static final ConcurrentMap<FieldReflectorKey,Reference<?>> reflectors =
|
||||
// new ConcurrentHashMap<>();
|
||||
//
|
||||
// /** queue for WeakReferences to local classes */
|
||||
// private static final ReferenceQueue<Class<?>> localDescsQueue =
|
||||
// new ReferenceQueue<>();
|
||||
// /** queue for WeakReferences to field reflectors keys */
|
||||
// private static final ReferenceQueue<Class<?>> reflectorsQueue =
|
||||
// new ReferenceQueue<>();
|
||||
// }
|
||||
catch (NoSuchFieldException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
catch (SecurityException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
catch (IllegalAccessException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
||||
|
||||
// private static class Caches {
|
||||
// /** cache mapping local classes -> descriptors */
|
||||
// static final ConcurrentMap<WeakClassKey,Reference<?>> localDescs =
|
||||
// new ConcurrentHashMap<>();
|
||||
//
|
||||
// /** cache mapping field group/local desc pairs -> field reflectors */
|
||||
// static final ConcurrentMap<FieldReflectorKey,Reference<?>> reflectors =
|
||||
// new ConcurrentHashMap<>();
|
||||
//
|
||||
// /** queue for WeakReferences to local classes */
|
||||
// private static final ReferenceQueue<Class<?>> localDescsQueue =
|
||||
// new ReferenceQueue<>();
|
||||
// /** queue for WeakReferences to field reflectors keys */
|
||||
// private static final ReferenceQueue<Class<?>> reflectorsQueue =
|
||||
// new ReferenceQueue<>();
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@SuppressWarnings({ "restriction", "unchecked" })
|
||||
public void reloadEvent(String typename, Class<?> clazz, String encodedTimestamp) {
|
||||
if (pluginBroken) {
|
||||
@@ -137,22 +150,24 @@ public class JVMPlugin implements ReloadEventProcessorPlugin, LoadtimeInstrument
|
||||
}
|
||||
map.remove(clazz);
|
||||
}
|
||||
|
||||
// Set<sun.awt.AppContext> appcontexts = sun.awt.AppContext.getAppContexts();
|
||||
// for (sun.awt.AppContext appcontext: appcontexts) {
|
||||
// map = (Map<Class<?>, BeanInfo>) appcontext.get(key);
|
||||
// if (map != null) {
|
||||
// if (GlobalConfiguration.debugplugins) {
|
||||
// System.err.println("JVMPlugin: clearing out BeanInfo for " + clazz.getName());
|
||||
// }
|
||||
// map.remove(clazz);
|
||||
// }
|
||||
// }
|
||||
|
||||
// Set<sun.awt.AppContext> appcontexts = sun.awt.AppContext.getAppContexts();
|
||||
// for (sun.awt.AppContext appcontext: appcontexts) {
|
||||
// map = (Map<Class<?>, BeanInfo>) appcontext.get(key);
|
||||
// if (map != null) {
|
||||
// if (GlobalConfiguration.debugplugins) {
|
||||
// System.err.println("JVMPlugin: clearing out BeanInfo for " + clazz.getName());
|
||||
// }
|
||||
// map.remove(clazz);
|
||||
// }
|
||||
// }
|
||||
Introspector.flushFromCaches(clazz);
|
||||
} catch (NoSuchFieldException nsfe) {
|
||||
}
|
||||
catch (NoSuchFieldException nsfe) {
|
||||
// this can happen on Java7 as the field isn't there any more, see the code above.
|
||||
System.out.println("Reloading: JVMPlugin: warning: unable to clear BEANINFO_CACHE, cant find field");
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
@@ -170,14 +185,17 @@ public class JVMPlugin implements ReloadEventProcessorPlugin, LoadtimeInstrument
|
||||
putMethod.setAccessible(true);
|
||||
|
||||
if (GlobalConfiguration.debugplugins) {
|
||||
System.err.println("JVMPlugin: clearing out declaredMethodCache in Introspector for class " + clazz.getName());
|
||||
System.err.println("JVMPlugin: clearing out declaredMethodCache in Introspector for class "
|
||||
+ clazz.getName());
|
||||
}
|
||||
putMethod.invoke(theCache, clazz, null);
|
||||
} catch (NoSuchFieldException nsfe) {
|
||||
}
|
||||
catch (NoSuchFieldException nsfe) {
|
||||
pluginBroken = true;
|
||||
System.out
|
||||
.println("Reloading: JVMPlugin: warning: unable to clear declaredMethodCache, cant find field (JDK update may fix it)");
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
@@ -193,7 +211,8 @@ public class JVMPlugin implements ReloadEventProcessorPlugin, LoadtimeInstrument
|
||||
if (threadGroupContextClass != null) {
|
||||
if (threadGroupContext_contextsField == null) {
|
||||
threadGroupContext_contextsField = threadGroupContextClass.getDeclaredField("contexts");
|
||||
threadGroupContext_removeBeanInfoMethod = threadGroupContextClass.getDeclaredMethod("removeBeanInfo",
|
||||
threadGroupContext_removeBeanInfoMethod = threadGroupContextClass.getDeclaredMethod(
|
||||
"removeBeanInfo",
|
||||
Class.class);
|
||||
}
|
||||
if (threadGroupContext_contextsField != null) {
|
||||
@@ -201,7 +220,8 @@ public class JVMPlugin implements ReloadEventProcessorPlugin, LoadtimeInstrument
|
||||
Object threadGroupContext_contextsField_value = threadGroupContext_contextsField.get(null);
|
||||
if (threadGroupContext_contextsField_value == null) {
|
||||
beanInfoCacheCleared = true;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
if (threadGroupContext_contextsField_value instanceof Map) {
|
||||
// Indicates Java 7 up to rev21
|
||||
Map<?, ?> m = (Map<?, ?>) threadGroupContext_contextsField_value;
|
||||
@@ -211,19 +231,20 @@ public class JVMPlugin implements ReloadEventProcessorPlugin, LoadtimeInstrument
|
||||
threadGroupContext_removeBeanInfoMethod.invoke(o, clazz);
|
||||
}
|
||||
beanInfoCacheCleared = true;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
// At update Java7u21 it changes
|
||||
Class weakIdentityMapClazz = threadGroupContext_contextsField.getType();
|
||||
Field tableField = weakIdentityMapClazz.getDeclaredField("table");
|
||||
tableField.setAccessible(true);
|
||||
Reference<?>[] refs = (Reference[])tableField.get(threadGroupContext_contextsField_value);
|
||||
Reference<?>[] refs = (Reference[]) tableField.get(threadGroupContext_contextsField_value);
|
||||
Field valueField = null;
|
||||
if (refs!=null) {
|
||||
for (int i=0; i<refs.length; i++) {
|
||||
if (refs != null) {
|
||||
for (int i = 0; i < refs.length; i++) {
|
||||
Reference<?> r = refs[i];
|
||||
Object o = (r==null?null:r.get());
|
||||
if (o!=null) {
|
||||
if (valueField==null) {
|
||||
Object o = (r == null ? null : r.get());
|
||||
if (o != null) {
|
||||
if (valueField == null) {
|
||||
valueField = r.getClass().getDeclaredField("value");
|
||||
}
|
||||
valueField.setAccessible(true);
|
||||
@@ -234,22 +255,25 @@ public class JVMPlugin implements ReloadEventProcessorPlugin, LoadtimeInstrument
|
||||
}
|
||||
}
|
||||
beanInfoCacheCleared = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
}
|
||||
catch (Throwable t) {
|
||||
System.err.println("Unexpected problem clearing ThreadGroupContext beaninfo: ");
|
||||
t.printStackTrace();
|
||||
}
|
||||
return beanInfoCacheCleared;
|
||||
}
|
||||
|
||||
public boolean accept(String slashedTypeName, ClassLoader classLoader, ProtectionDomain protectionDomain, byte[] bytes) {
|
||||
if (slashedTypeName!=null) {
|
||||
public boolean accept(String slashedTypeName, ClassLoader classLoader, ProtectionDomain protectionDomain,
|
||||
byte[] bytes) {
|
||||
if (slashedTypeName != null) {
|
||||
if (slashedTypeName.equals("java/beans/Introspector")) {
|
||||
introspectorLoaded = true;
|
||||
} else if (slashedTypeName.equals("java/beans/ThreadGroupContext")) {
|
||||
}
|
||||
else if (slashedTypeName.equals("java/beans/ThreadGroupContext")) {
|
||||
threadGroupContextLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded.agent;
|
||||
|
||||
import org.objectweb.asm.ClassVisitor;
|
||||
@@ -33,7 +34,7 @@ import org.springsource.loaded.TypeRegistry;
|
||||
public class ModifyDefineInClassLoaderForClassArtifactsType extends ClassVisitor implements Constants {
|
||||
|
||||
public ModifyDefineInClassLoaderForClassArtifactsType() {
|
||||
super(ASM5,new ClassWriter(0)); // TODO review 0 here
|
||||
super(ASM5, new ClassWriter(0)); // TODO review 0 here
|
||||
}
|
||||
|
||||
public byte[] getBytes() {
|
||||
@@ -44,7 +45,8 @@ public class ModifyDefineInClassLoaderForClassArtifactsType extends ClassVisitor
|
||||
if (name.equals("define")) {
|
||||
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
|
||||
return new DefineClassModifierVisitor(mv);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
return super.visitMethod(access, name, desc, signature, exceptions);
|
||||
}
|
||||
}
|
||||
@@ -52,7 +54,7 @@ public class ModifyDefineInClassLoaderForClassArtifactsType extends ClassVisitor
|
||||
class DefineClassModifierVisitor extends MethodVisitor implements Constants {
|
||||
|
||||
public DefineClassModifierVisitor(MethodVisitor mv) {
|
||||
super(ASM5,mv);
|
||||
super(ASM5, mv);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -60,7 +62,8 @@ public class ModifyDefineInClassLoaderForClassArtifactsType extends ClassVisitor
|
||||
mv.visitVarInsn(ALOAD, 0); // ClassLoaderForClassArtifacts this
|
||||
mv.visitVarInsn(ALOAD, 1); // String name
|
||||
mv.visitVarInsn(ALOAD, 2); // byte[] bytes
|
||||
mv.visitMethodInsn(INVOKESTATIC, "org/springsource/loaded/agent/ModifyDefineInClassLoaderForClassArtifactsType",
|
||||
mv.visitMethodInsn(INVOKESTATIC,
|
||||
"org/springsource/loaded/agent/ModifyDefineInClassLoaderForClassArtifactsType",
|
||||
"modify", "(Ljava/lang/ClassLoader;Ljava/lang/String;[B)[B", false);
|
||||
mv.visitVarInsn(ASTORE, 2);
|
||||
}
|
||||
@@ -81,15 +84,17 @@ public class ModifyDefineInClassLoaderForClassArtifactsType extends ClassVisitor
|
||||
// java_lang_class$getDeclaredFields (i.e. a system class that cant change, so rewriting is unnecessary...)
|
||||
if (typeRegistry != null) {
|
||||
bytes = typeRegistry.methodCallRewrite(bytes);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
if (GlobalConfiguration.verboseMode) {
|
||||
System.out.println("No type registry found for parent classloader: " + parent);
|
||||
}
|
||||
bytes = MethodInvokerRewriter.rewrite(null, bytes, true);
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
bytes = MethodInvokerRewriter.rewrite(null, bytes, true);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded.agent;
|
||||
|
||||
import java.lang.reflect.Modifier;
|
||||
@@ -34,14 +35,14 @@ public class NonFinalizer extends ClassVisitor implements Constants {
|
||||
private String fieldname;
|
||||
|
||||
/**
|
||||
* This ClassAdapter will visit a class and within the constructors it will add a call to the specified method (assumed static)
|
||||
* just before each constructor returns. The target of the call should be a collecting method that will likely do something with
|
||||
* the instances later on class reload.
|
||||
* This ClassAdapter will visit a class and within the constructors it will add a call to the specified method
|
||||
* (assumed static) just before each constructor returns. The target of the call should be a collecting method that
|
||||
* will likely do something with the instances later on class reload.
|
||||
*
|
||||
* @param fieldname the name of the field to be made non final
|
||||
*/
|
||||
public NonFinalizer(String fieldname) {
|
||||
super(ASM5,new ClassWriter(0)); // TODO review 0 here
|
||||
super(ASM5, new ClassWriter(0)); // TODO review 0 here
|
||||
this.fieldname = fieldname;
|
||||
}
|
||||
|
||||
@@ -53,7 +54,8 @@ public class NonFinalizer extends ClassVisitor implements Constants {
|
||||
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
|
||||
if (name.equals(fieldname)) {
|
||||
return super.visitField(access & (~Modifier.FINAL), name, desc, signature, value);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
return super.visitField(access, name, desc, signature, value);
|
||||
}
|
||||
}
|
||||
@@ -68,8 +70,8 @@ public class NonFinalizer extends ClassVisitor implements Constants {
|
||||
// }
|
||||
|
||||
/**
|
||||
* This constructor appender includes a couple of instructions at the end of each constructor it is asked to visit. It
|
||||
* recognizes the end by observing a RETURN instruction. The instructions are inserted just before the RETURN.
|
||||
* This constructor appender includes a couple of instructions at the end of each constructor it is asked to visit.
|
||||
* It recognizes the end by observing a RETURN instruction. The instructions are inserted just before the RETURN.
|
||||
*/
|
||||
// class ConstructorAppender extends MethodAdapter implements Constants {
|
||||
//
|
||||
@@ -87,4 +89,4 @@ public class NonFinalizer extends ClassVisitor implements Constants {
|
||||
// }
|
||||
//
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,13 +13,16 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded.agent;
|
||||
|
||||
import org.objectweb.asm.ClassReader;
|
||||
|
||||
public class PluginUtils {
|
||||
|
||||
/**
|
||||
* If adding instance tracking, the classToCall must implement: <tt>public static void recordInstance(Object obj)</tt>.
|
||||
* If adding instance tracking, the classToCall must implement:
|
||||
* <tt>public static void recordInstance(Object obj)</tt>.
|
||||
*
|
||||
* @param bytes the bytes for the class to which instance tracking is being added
|
||||
* @param classToCall the class to call when a new instance is created
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded.agent;
|
||||
|
||||
/**
|
||||
@@ -33,4 +34,4 @@ public enum ReloadDecision {
|
||||
* PASS means the plugin has no opinion, leave it to other plugins to decide
|
||||
*/
|
||||
PASS
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded.agent;
|
||||
|
||||
import java.io.File;
|
||||
@@ -37,6 +38,7 @@ public class ReloadableFileChangeListener implements FileChangeListener {
|
||||
private static Logger log = Logger.getLogger(ReloadableFileChangeListener.class.getName());
|
||||
|
||||
private TypeRegistry typeRegistry;
|
||||
|
||||
private Map<File, ReloadableType> correspondingReloadableTypes = new HashMap<File, ReloadableType>();
|
||||
|
||||
public ReloadableFileChangeListener(TypeRegistry typeRegistry) {
|
||||
@@ -55,4 +57,4 @@ public class ReloadableFileChangeListener implements FileChangeListener {
|
||||
correspondingReloadableTypes.put(file, rtype);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,14 +13,15 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded.agent;
|
||||
|
||||
import java.lang.instrument.ClassFileTransformer;
|
||||
import java.lang.instrument.Instrumentation;
|
||||
|
||||
/**
|
||||
* Basic agent implementation. This agent is declared in the META-INF/MANIFEST.MF file - that is how
|
||||
* it is 'plugged in' to the JVM when '-javaagent:springloaded.jar' is used.
|
||||
* Basic agent implementation. This agent is declared in the META-INF/MANIFEST.MF file - that is how it is 'plugged in'
|
||||
* to the JVM when '-javaagent:springloaded.jar' is used.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
@@ -39,7 +40,7 @@ public class SpringLoadedAgent {
|
||||
instrumentation = inst;
|
||||
instrumentation.addTransformer(transformer);
|
||||
}
|
||||
|
||||
|
||||
public static void agentmain(String options, Instrumentation inst) {
|
||||
if (instrumentation != null) {
|
||||
return;
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded.agent;
|
||||
|
||||
import java.io.File;
|
||||
@@ -44,12 +45,13 @@ import org.springsource.loaded.ri.ReflectiveInterceptor;
|
||||
import org.springsource.loaded.support.Java8;
|
||||
|
||||
/**
|
||||
* The entry point for the agent - all classes that can be modified will be passed into preProcess(). They have to be dealt with in
|
||||
* one of these ways:
|
||||
* The entry point for the agent - all classes that can be modified will be passed into preProcess(). They have to be
|
||||
* dealt with in one of these ways:
|
||||
* <ul>
|
||||
* <li>reloadable types need their bytecode rewriting so that they can be modified later
|
||||
* <li>'framework' types (not loaded by the system classloader) need their reflection calls rewritten
|
||||
* <li>system classes also need their reflection calls modified but in a different way (they cannot have dependencies on types they cannot see)
|
||||
* <li>system classes also need their reflection calls modified but in a different way (they cannot have dependencies on
|
||||
* types they cannot see)
|
||||
* </ul>
|
||||
*
|
||||
* @author Andy Clement
|
||||
@@ -58,6 +60,7 @@ import org.springsource.loaded.support.Java8;
|
||||
public class SpringLoadedPreProcessor implements Constants {
|
||||
|
||||
private static Logger log = Logger.getLogger(SpringLoadedPreProcessor.class.getName());
|
||||
|
||||
private static List<Plugin> plugins = null;
|
||||
|
||||
// Global control to turn off the agent, used when testing
|
||||
@@ -88,21 +91,21 @@ public class SpringLoadedPreProcessor implements Constants {
|
||||
systemClassesContainingReflection.add("java/lang/reflect/Proxy");
|
||||
// So that javabeans introspection is intercepter
|
||||
systemClassesContainingReflection.add("java/beans/Introspector");
|
||||
|
||||
|
||||
// Related to serialization
|
||||
// TODO [serialization] Caches in ObjectStreamClass for descriptors, need clearing on reload
|
||||
systemClassesContainingReflection.add("java/io/ObjectStreamClass");
|
||||
systemClassesContainingReflection.add("java/io/ObjectStreamClass$EntryFuture");
|
||||
|
||||
|
||||
// Don't need this right now, instead we are not removing 'final' from the serialVersionUID
|
||||
// // Need to catch at least the call to access the serialVersionUID made in getDeclaredSUID()
|
||||
// systemClassesContainingReflection.add("java/io/ObjectStreamClass$2");
|
||||
}
|
||||
|
||||
/**
|
||||
* Main entry point to Spring Loaded when it is running as an agent. This method will use the classLoader and the class name in
|
||||
* order to determine whether the type should be made reloadable. Non-reloadable types will at least get their call sites
|
||||
* rewritten.
|
||||
* Main entry point to Spring Loaded when it is running as an agent. This method will use the classLoader and the
|
||||
* class name in order to determine whether the type should be made reloadable. Non-reloadable types will at least
|
||||
* get their call sites rewritten.
|
||||
*
|
||||
* @param classLoader the classloader loading this type
|
||||
* @param slashedClassName the slashed class name (e.g. java/lang/String) being loaded
|
||||
@@ -110,7 +113,8 @@ public class SpringLoadedPreProcessor implements Constants {
|
||||
* @param bytes the class bytes for the class being loaded
|
||||
* @return potentially modified bytes
|
||||
*/
|
||||
public byte[] preProcess(ClassLoader classLoader, String slashedClassName, ProtectionDomain protectionDomain, byte[] bytes) {
|
||||
public byte[] preProcess(ClassLoader classLoader, String slashedClassName, ProtectionDomain protectionDomain,
|
||||
byte[] bytes) {
|
||||
if (disabled) {
|
||||
return bytes;
|
||||
}
|
||||
@@ -128,7 +132,7 @@ public class SpringLoadedPreProcessor implements Constants {
|
||||
tryToEnsureSystemClassesInitialized(slashedClassName);
|
||||
|
||||
TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(classLoader);
|
||||
|
||||
|
||||
if (GlobalConfiguration.verboseMode && log.isLoggable(Level.INFO)) {
|
||||
logPreProcess(classLoader, slashedClassName, typeRegistry);
|
||||
}
|
||||
@@ -140,20 +144,25 @@ public class SpringLoadedPreProcessor implements Constants {
|
||||
// TODO [perf] why are we not using the cache here, is it because the list is so short?
|
||||
RewriteResult rr = SystemClassReflectionRewriter.rewrite(slashedClassName, bytes);
|
||||
if (GlobalConfiguration.verboseMode && log.isLoggable(Level.INFO)) {
|
||||
log.info("System class rewritten: name="+slashedClassName+" rewrite summary="+rr.summarize());
|
||||
log.info("System class rewritten: name=" + slashedClassName + " rewrite summary="
|
||||
+ rr.summarize());
|
||||
}
|
||||
systemClassesRequiringInitialization.put(slashedClassName, rr.bits);
|
||||
return rr.bytes;
|
||||
} catch (Exception re) {
|
||||
}
|
||||
catch (Exception re) {
|
||||
re.printStackTrace();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
else if (slashedClassName.equals("java/lang/invoke/InnerClassLambdaMetafactory")) {
|
||||
bytes = Java8.enhanceInnerClassLambdaMetaFactory(bytes);
|
||||
return bytes;
|
||||
} else if ((GlobalConfiguration.investigateSystemClassReflection || GlobalConfiguration.rewriteAllSystemClasses) &&
|
||||
SystemClassReflectionInvestigator.investigate(slashedClassName, bytes, GlobalConfiguration.investigateSystemClassReflection) > 0) {
|
||||
}
|
||||
else if ((GlobalConfiguration.investigateSystemClassReflection || GlobalConfiguration.rewriteAllSystemClasses)
|
||||
&&
|
||||
SystemClassReflectionInvestigator.investigate(slashedClassName, bytes,
|
||||
GlobalConfiguration.investigateSystemClassReflection) > 0) {
|
||||
// This block can help when you suspect there is a system class using reflection and that
|
||||
// class isn't on the 'shortlist' (in systemClassesContainingReflection). Basically turn on the
|
||||
// options to trigger this investigation then add them to the shortlist if it looks like they need rewriting.
|
||||
@@ -177,16 +186,16 @@ public class SpringLoadedPreProcessor implements Constants {
|
||||
// 3. If YES, make the type reloadable (including rewriting call sites)
|
||||
|
||||
boolean isReloadableTypeName = typeRegistry.isReloadableTypeName(slashedClassName, protectionDomain, bytes);
|
||||
|
||||
|
||||
if (isReloadableTypeName && GlobalConfiguration.explainMode && log.isLoggable(Level.INFO)) {
|
||||
log.info("[explanation] Based on the name, type "+slashedClassName+" is considered to be reloadable");
|
||||
log.info("[explanation] Based on the name, type " + slashedClassName + " is considered to be reloadable");
|
||||
}
|
||||
|
||||
|
||||
// logging causes a ClassCircularity problem when reporting on:
|
||||
// SL: Type 'org/codehaus/groovy/grails/cli/logging/GrailsConsolePrintStream' is not being made reloadable
|
||||
// if (GlobalConfiguration.verboseMode && isReloadableTypeName) {
|
||||
// Log.log("Type '"+slashedClassName+"' is preliminarily being considered a reloadable type");
|
||||
// }
|
||||
// if (GlobalConfiguration.verboseMode && isReloadableTypeName) {
|
||||
// Log.log("Type '"+slashedClassName+"' is preliminarily being considered a reloadable type");
|
||||
// }
|
||||
if (isReloadableTypeName) {
|
||||
if (!firstReloadableTypeHit) {
|
||||
firstReloadableTypeHit = true;
|
||||
@@ -224,20 +233,21 @@ public class SpringLoadedPreProcessor implements Constants {
|
||||
}
|
||||
currentRegistry = currentRegistry.getParentRegistry();
|
||||
}
|
||||
// if (typeRegistry.isReloadableTypeName(originalType)) {
|
||||
// if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) {
|
||||
// log.info("Type " + originalType + " is reloadable, so making CGLIB type " + slashedClassName
|
||||
// + " reloadable");
|
||||
// }
|
||||
// makeReloadableAnyway = true;
|
||||
// }
|
||||
// if (typeRegistry.isReloadableTypeName(originalType)) {
|
||||
// if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) {
|
||||
// log.info("Type " + originalType + " is reloadable, so making CGLIB type " + slashedClassName
|
||||
// + " reloadable");
|
||||
// }
|
||||
// makeReloadableAnyway = true;
|
||||
// }
|
||||
}
|
||||
|
||||
int cglibIndex2 = makeReloadableAnyway?-1:slashedClassName.indexOf("$$FastClassByCGLIB");
|
||||
int cglibIndex2 = makeReloadableAnyway ? -1 : slashedClassName.indexOf("$$FastClassByCGLIB");
|
||||
if (cglibIndex2 != -1) {
|
||||
String originalType = slashedClassName.substring(0, cglibIndex2);
|
||||
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) {
|
||||
log.info("Appears to be a CGLIB FastClass type, checking if type " + originalType + " is reloadable");
|
||||
log.info("Appears to be a CGLIB FastClass type, checking if type " + originalType
|
||||
+ " is reloadable");
|
||||
}
|
||||
TypeRegistry currentRegistry = typeRegistry;
|
||||
while (currentRegistry != null) {
|
||||
@@ -248,16 +258,16 @@ public class SpringLoadedPreProcessor implements Constants {
|
||||
}
|
||||
currentRegistry = currentRegistry.getParentRegistry();
|
||||
}
|
||||
// if (typeRegistry.isReloadableTypeName(originalType)) {
|
||||
// if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) {
|
||||
// log.info("Type " + originalType + " is reloadable, so making CGLIB type " + slashedClassName
|
||||
// + " reloadable");
|
||||
// }
|
||||
// makeReloadableAnyway = true;
|
||||
// }
|
||||
// if (typeRegistry.isReloadableTypeName(originalType)) {
|
||||
// if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) {
|
||||
// log.info("Type " + originalType + " is reloadable, so making CGLIB type " + slashedClassName
|
||||
// + " reloadable");
|
||||
// }
|
||||
// makeReloadableAnyway = true;
|
||||
// }
|
||||
}
|
||||
|
||||
int proxyIndex = makeReloadableAnyway?-1:slashedClassName.indexOf("$Proxy");
|
||||
int proxyIndex = makeReloadableAnyway ? -1 : slashedClassName.indexOf("$Proxy");
|
||||
if (proxyIndex == 0 || (proxyIndex > 0 && slashedClassName.charAt(proxyIndex - 1) == '/')) {
|
||||
// Determine if the interfaces being implemented are reloadable
|
||||
String[] interfacesImplemented = Utils.discoverInterfaces(bytes);
|
||||
@@ -272,9 +282,9 @@ public class SpringLoadedPreProcessor implements Constants {
|
||||
}
|
||||
currentRegistry = currentRegistry.getParentRegistry();
|
||||
}
|
||||
// if (typeRegistry.isReloadableTypeName(interfacesImplemented[i])) {
|
||||
// makeReloadableAnyway = true;
|
||||
// }
|
||||
// if (typeRegistry.isReloadableTypeName(interfacesImplemented[i])) {
|
||||
// makeReloadableAnyway = true;
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -284,14 +294,15 @@ public class SpringLoadedPreProcessor implements Constants {
|
||||
// not find it in the InnerClassLoader, but find it in the super classloader, and it'll be the wrong one).
|
||||
// I wonder if the more general rule should be that
|
||||
// all classloaders below one loading reloadable stuff should also load reloadable stuff.
|
||||
if (!makeReloadableAnyway && classLoader.getClass().getName().endsWith("GroovyClassLoader$InnerLoader")) {
|
||||
if (!makeReloadableAnyway
|
||||
&& classLoader.getClass().getName().endsWith("GroovyClassLoader$InnerLoader")) {
|
||||
makeReloadableAnyway = true;
|
||||
}
|
||||
|
||||
if (!makeReloadableAnyway) {
|
||||
// can't watch it for updates (it comes from a jar perhaps) so just rewrite call sites and return
|
||||
if (GlobalConfiguration.verboseMode) {
|
||||
Log.log("Cannot watch "+slashedClassName+": not making it reloadable");
|
||||
Log.log("Cannot watch " + slashedClassName + ": not making it reloadable");
|
||||
}
|
||||
if (needsClientSideRewriting(slashedClassName)) {
|
||||
bytes = typeRegistry.methodCallRewriteUseCacheIfAvailable(slashedClassName, bytes);
|
||||
@@ -304,23 +315,27 @@ public class SpringLoadedPreProcessor implements Constants {
|
||||
// it is not a candidate for being made reloadable (maybe it is an annotation type)
|
||||
// but we still need to rewrite call sites.
|
||||
bytes = typeRegistry.methodCallRewrite(bytes);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
if (GlobalConfiguration.fileSystemMonitoring && watchPath != null) {
|
||||
typeRegistry.monitorForUpdates(rtype, watchPath);
|
||||
}
|
||||
return rtype.bytesLoaded;
|
||||
}
|
||||
} catch (RuntimeException re) {
|
||||
}
|
||||
catch (RuntimeException re) {
|
||||
log.throwing("SpringLoadedPreProcessor", "preProcess", re);
|
||||
throw re;
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
try {
|
||||
// TODO what happens across classloader boundaries? (for regular code and reflective calls)
|
||||
if (needsClientSideRewriting(slashedClassName)) {
|
||||
bytes = typeRegistry.methodCallRewriteUseCacheIfAvailable(slashedClassName, bytes);
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
}
|
||||
catch (Throwable t) {
|
||||
log.log(Level.SEVERE, "Unexpected problem transforming call sites", t);
|
||||
}
|
||||
}
|
||||
@@ -359,12 +374,15 @@ public class SpringLoadedPreProcessor implements Constants {
|
||||
}
|
||||
int bits = me.getValue();
|
||||
try {
|
||||
Class<?> clazz = SpringLoadedPreProcessor.class.getClassLoader().loadClass(classname.replace('/', '.'));
|
||||
Class<?> clazz = SpringLoadedPreProcessor.class.getClassLoader().loadClass(
|
||||
classname.replace('/', '.'));
|
||||
injectReflectiveInterceptorMethods(slashedClassName, bits, clazz);
|
||||
toRemoveList.add(classname);
|
||||
} catch (ClassCircularityError cce) {
|
||||
}
|
||||
catch (ClassCircularityError cce) {
|
||||
// See comment above. 'assume' this is OK, the initialization will happen again next time around.
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
@@ -378,7 +396,8 @@ public class SpringLoadedPreProcessor implements Constants {
|
||||
/**
|
||||
* This method tries to inject the ReflectiveInterceptor methods into any system types that have been rewritten.
|
||||
*/
|
||||
private void injectReflectiveInterceptorMethods(String slashedClassName, int bits, Class<?> clazz) throws NoSuchFieldException,
|
||||
private void injectReflectiveInterceptorMethods(String slashedClassName, int bits, Class<?> clazz)
|
||||
throws NoSuchFieldException,
|
||||
IllegalAccessException, NoSuchMethodException {
|
||||
// TODO log the bits
|
||||
if ((bits & Constants.JLC_GETDECLAREDFIELDS) != 0) {
|
||||
@@ -434,34 +453,37 @@ public class SpringLoadedPreProcessor implements Constants {
|
||||
if ((bits & Constants.JLC_GETDECLAREDCONSTRUCTORS) != 0) {
|
||||
Field f = clazz.getDeclaredField(jlcGetDeclaredConstructorsMember);
|
||||
f.setAccessible(true);
|
||||
f.set(null,method_jlcgdcs);
|
||||
f.set(null, method_jlcgdcs);
|
||||
}
|
||||
if ((bits & Constants.JLRF_GET) != 0) {
|
||||
Field f = clazz.getDeclaredField(jlrfGetMember);
|
||||
f.setAccessible(true);
|
||||
f.set(null,method_jlrfg);
|
||||
f.set(null, method_jlrfg);
|
||||
}
|
||||
if ((bits & Constants.JLRF_GETLONG) != 0) {
|
||||
Field f = clazz.getDeclaredField(jlrfGetLongMember);
|
||||
f.setAccessible(true);
|
||||
f.set(null,method_jlrfgl);
|
||||
f.set(null, method_jlrfgl);
|
||||
}
|
||||
if ((bits & Constants.JLRM_INVOKE) != 0) {
|
||||
Field f = clazz.getDeclaredField(jlrmInvokeMember);
|
||||
f.setAccessible(true);
|
||||
f.set(null,method_jlrmi);
|
||||
f.set(null, method_jlrmi);
|
||||
}
|
||||
if ((bits & Constants.JLOS_HASSTATICINITIALIZER) != 0) {
|
||||
Field f = clazz.getDeclaredField(jloObjectStream_hasInitializerMethod);
|
||||
f.setAccessible(true);
|
||||
f.set(null,method_jloObjectStream_hasInitializerMethod);
|
||||
f.set(null, method_jloObjectStream_hasInitializerMethod);
|
||||
}
|
||||
}
|
||||
|
||||
private static final Class<?> EMPTY_CLASS_ARRAY_CLAZZ = Class[].class;
|
||||
|
||||
// TODO threads
|
||||
private static boolean prepared = false;
|
||||
private static Method method_jlcgdfs, method_jlcgdf, method_jlcgf, method_jlcgdms, method_jlcgdm, method_jlcgm, method_jlcgdc,
|
||||
|
||||
private static Method method_jlcgdfs, method_jlcgdf, method_jlcgf, method_jlcgdms, method_jlcgdm, method_jlcgm,
|
||||
method_jlcgdc,
|
||||
method_jlcgc, method_jlcgmods, method_jlcgms, method_jlcgdcs, method_jlrfg, method_jlrfgl, method_jlrmi,
|
||||
method_jloObjectStream_hasInitializerMethod;
|
||||
|
||||
@@ -478,18 +500,22 @@ public class SpringLoadedPreProcessor implements Constants {
|
||||
method_jlcgdms = clazz.getDeclaredMethod("jlClassGetDeclaredMethods", Class.class);
|
||||
method_jlcgdm = clazz.getDeclaredMethod("jlClassGetDeclaredMethod", Class.class, String.class,
|
||||
EMPTY_CLASS_ARRAY_CLAZZ);
|
||||
method_jlcgm = clazz.getDeclaredMethod("jlClassGetMethod", Class.class, String.class, EMPTY_CLASS_ARRAY_CLAZZ);
|
||||
method_jlcgdc = clazz.getDeclaredMethod("jlClassGetDeclaredConstructor", Class.class, EMPTY_CLASS_ARRAY_CLAZZ);
|
||||
method_jlcgm = clazz.getDeclaredMethod("jlClassGetMethod", Class.class, String.class,
|
||||
EMPTY_CLASS_ARRAY_CLAZZ);
|
||||
method_jlcgdc = clazz.getDeclaredMethod("jlClassGetDeclaredConstructor", Class.class,
|
||||
EMPTY_CLASS_ARRAY_CLAZZ);
|
||||
method_jlcgc = clazz.getDeclaredMethod("jlClassGetConstructor", Class.class, EMPTY_CLASS_ARRAY_CLAZZ);
|
||||
method_jlcgmods = clazz.getDeclaredMethod("jlClassGetModifiers", Class.class);
|
||||
method_jlcgms = clazz.getDeclaredMethod("jlClassGetMethods", Class.class);
|
||||
|
||||
method_jlcgdcs = clazz.getDeclaredMethod("jlClassGetDeclaredConstructors",Class.class);
|
||||
method_jlrfg = clazz.getDeclaredMethod("jlrFieldGet",Field.class,Object.class);
|
||||
method_jlrfgl = clazz.getDeclaredMethod("jlrFieldGetLong",Field.class,Object.class);
|
||||
method_jlrmi = clazz.getDeclaredMethod("jlrMethodInvoke",Method.class,Object.class,Object[].class);
|
||||
method_jloObjectStream_hasInitializerMethod = clazz.getDeclaredMethod("jlosHasStaticInitializer",Class.class);
|
||||
} catch (NoSuchMethodException nsme) {
|
||||
|
||||
method_jlcgdcs = clazz.getDeclaredMethod("jlClassGetDeclaredConstructors", Class.class);
|
||||
method_jlrfg = clazz.getDeclaredMethod("jlrFieldGet", Field.class, Object.class);
|
||||
method_jlrfgl = clazz.getDeclaredMethod("jlrFieldGetLong", Field.class, Object.class);
|
||||
method_jlrmi = clazz.getDeclaredMethod("jlrMethodInvoke", Method.class, Object.class, Object[].class);
|
||||
method_jloObjectStream_hasInitializerMethod = clazz.getDeclaredMethod("jlosHasStaticInitializer",
|
||||
Class.class);
|
||||
}
|
||||
catch (NoSuchMethodException nsme) {
|
||||
// cant happen, a-hahaha
|
||||
throw new Impossible(nsme);
|
||||
}
|
||||
@@ -498,17 +524,18 @@ public class SpringLoadedPreProcessor implements Constants {
|
||||
}
|
||||
|
||||
private static boolean needsClientSideRewriting(String slashedClassName) {
|
||||
if (slashedClassName!=null && slashedClassName.charAt(0)=='o' && slashedClassName.startsWith("org/springsource/loaded")) {
|
||||
if (slashedClassName != null && slashedClassName.charAt(0) == 'o'
|
||||
&& slashedClassName.startsWith("org/springsource/loaded")) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine where to watch for changes based on the protectionDomain. Relying on the protectionDomain may prove fragile though,
|
||||
* as it is up to the classloader in question to create it. Some classloaders will create one protectionDomain per 'directory'
|
||||
* containing class files (and so the slashedClassName must be appended to the codesource). Some classloaders have a
|
||||
* protectiondomain per class.
|
||||
* Determine where to watch for changes based on the protectionDomain. Relying on the protectionDomain may prove
|
||||
* fragile though, as it is up to the classloader in question to create it. Some classloaders will create one
|
||||
* protectionDomain per 'directory' containing class files (and so the slashedClassName must be appended to the
|
||||
* codesource). Some classloaders have a protectiondomain per class.
|
||||
*
|
||||
* @param protectionDomain the protection domain passed in to the defineclass call
|
||||
* @param slashedClassName the slashed class name currently being defined
|
||||
@@ -522,14 +549,16 @@ public class SpringLoadedPreProcessor implements Constants {
|
||||
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.WARNING)) {
|
||||
log.warning("Changes to type cannot be tracked: " + slashedClassName + " - no protection domain");
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
try {
|
||||
CodeSource codeSource = protectionDomain.getCodeSource();
|
||||
if (codeSource == null || codeSource.getLocation() == null) {
|
||||
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.WARNING)) {
|
||||
log.warning("null codesource for " + slashedClassName);
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.FINEST)) {
|
||||
log.finest("Codesource.getLocation()=" + codeSource.getLocation());
|
||||
}
|
||||
@@ -541,45 +570,53 @@ public class SpringLoadedPreProcessor implements Constants {
|
||||
try {
|
||||
file = new File(codeSource.getLocation().getFile());
|
||||
uri = file.toURI();
|
||||
} catch (IllegalArgumentException iae) {
|
||||
}
|
||||
catch (IllegalArgumentException iae) {
|
||||
boolean recovered = false;
|
||||
if (iae.toString().indexOf("URI is not hierarchical")!=-1) {
|
||||
if (iae.toString().indexOf("URI is not hierarchical") != -1) {
|
||||
// try another approach...
|
||||
String uristring = uri.toString();
|
||||
if (uristring.startsWith("file:../")) {
|
||||
file = new File(uristring.substring(8)).getAbsoluteFile();
|
||||
} else if (uristring.startsWith("file:./")) {
|
||||
}
|
||||
else if (uristring.startsWith("file:./")) {
|
||||
file = new File(uristring.substring(7)).getAbsoluteFile();
|
||||
}
|
||||
if (file!=null && file.exists()) {
|
||||
if (file != null && file.exists()) {
|
||||
recovered = true;
|
||||
}
|
||||
}
|
||||
if (!recovered) {
|
||||
System.out.println("Unable to watch file: classname = "+slashedClassName+" codesource location = "+codeSource.getLocation()+" ex = "+iae.toString());
|
||||
System.out.println("Unable to watch file: classname = " + slashedClassName
|
||||
+ " codesource location = " + codeSource.getLocation() + " ex = " + iae.toString());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (file.isDirectory()) {
|
||||
file = new File(file, slashedClassName + ".class");
|
||||
} else if (file.getName().endsWith(".class")) {
|
||||
}
|
||||
else if (file.getName().endsWith(".class")) {
|
||||
// great! nothing to do
|
||||
} else if (file.getName().endsWith(".jar")) {
|
||||
}
|
||||
else if (file.getName().endsWith(".jar")) {
|
||||
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.WARNING)) {
|
||||
log.warning("unable to watch this jar file entry: " + slashedClassName.replace('/', '.')
|
||||
+ ". Computed location=" + file.toString());
|
||||
}
|
||||
return null;
|
||||
} else if (file.toString().equals("/groovy/script") || file.toString().equals("\\groovy\\script")) {
|
||||
}
|
||||
else if (file.toString().equals("/groovy/script") || file.toString().equals("\\groovy\\script")) {
|
||||
// nothing to do, compiled/loaded by a GroovyClassLoader$InnerLoader - there is nothing to watch. If the type is to be
|
||||
// reloaded we will have to be told via an alternate route
|
||||
return null;
|
||||
} else if (!file.toString().endsWith(".class")) {
|
||||
}
|
||||
else if (!file.toString().endsWith(".class")) {
|
||||
// GRAILS-9076: it ended in .groovy
|
||||
// GRAILS-9069/GRAILS-9070: it was /groovy/shell
|
||||
// something other than a class, no point in watching it
|
||||
return null;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
throw new UnsupportedOperationException("unable to watch " + slashedClassName.replace('/', '.')
|
||||
+ ". Computed location=" + file.toString());
|
||||
}
|
||||
@@ -588,7 +625,8 @@ public class SpringLoadedPreProcessor implements Constants {
|
||||
log.info("Watched location for changes to " + slashedClassName + " is " + watchPath);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new IllegalStateException("Unexpected problem processing URI ", e);
|
||||
}
|
||||
}
|
||||
@@ -600,7 +638,8 @@ public class SpringLoadedPreProcessor implements Constants {
|
||||
if (clname.indexOf('.') != -1) {
|
||||
clname = clname.substring(clname.lastIndexOf('.') + 1);
|
||||
}
|
||||
log.info("SpringLoaded preprocessing: classname="+slashedClassName+" classloader="+clname+" typeRegistry="+typeRegistry);
|
||||
log.info("SpringLoaded preprocessing: classname=" + slashedClassName + " classloader=" + clname
|
||||
+ " typeRegistry=" + typeRegistry);
|
||||
}
|
||||
|
||||
public static List<Plugin> getGlobalPlugins() {
|
||||
@@ -621,15 +660,19 @@ public class SpringLoadedPreProcessor implements Constants {
|
||||
if (extraGlobalPlugins != null) {
|
||||
for (String globalPlugin : extraGlobalPlugins) {
|
||||
try {
|
||||
Class<?> pluginClass = Class.forName(globalPlugin, false, SpringLoadedPreProcessor.class.getClassLoader());
|
||||
Class<?> pluginClass = Class.forName(globalPlugin, false,
|
||||
SpringLoadedPreProcessor.class.getClassLoader());
|
||||
plugins.add((Plugin) pluginClass.newInstance());
|
||||
} catch (ClassNotFoundException e) {
|
||||
}
|
||||
catch (ClassNotFoundException e) {
|
||||
System.err.println("Unexpected problem loading global plugin:" + globalPlugin);
|
||||
e.printStackTrace(System.err);
|
||||
} catch (InstantiationException e) {
|
||||
}
|
||||
catch (InstantiationException e) {
|
||||
System.err.println("Unexpected problem loading global plugin:" + globalPlugin);
|
||||
e.printStackTrace(System.err);
|
||||
} catch (IllegalAccessException e) {
|
||||
}
|
||||
catch (IllegalAccessException e) {
|
||||
System.err.println("Unexpected problem loading global plugin:" + globalPlugin);
|
||||
e.printStackTrace(System.err);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded.agent;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
@@ -34,11 +35,11 @@ import org.springsource.loaded.ReloadEventProcessorPlugin;
|
||||
* First stab at the Spring plugin for Spring-Loaded. Notes...<br>
|
||||
* <ul>
|
||||
* <li>On reload, removes the Class entry in
|
||||
* org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.methodResolverCache. This enables us to add/changes
|
||||
* request mappings in controllers.
|
||||
* <li>That was for Roo, if we create a simple spring template project and run it, this doesn't work. It seems we need to redrive
|
||||
* detectHandlers() on the DefaultAnnotationHandlerMapping type which will rediscover the URL mappings and add them into the handler
|
||||
* list. We don't clear old ones out (yet) but the old mappings appear not to work anyway.
|
||||
* org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.methodResolverCache. This enables us to
|
||||
* add/changes request mappings in controllers.
|
||||
* <li>That was for Roo, if we create a simple spring template project and run it, this doesn't work. It seems we need
|
||||
* to redrive detectHandlers() on the DefaultAnnotationHandlerMapping type which will rediscover the URL mappings and
|
||||
* add them into the handler list. We don't clear old ones out (yet) but the old mappings appear not to work anyway.
|
||||
* </ul>
|
||||
*
|
||||
* @author Andy Clement
|
||||
@@ -51,28 +52,38 @@ public class SpringPlugin implements LoadtimeInstrumentationPlugin, ReloadEventP
|
||||
private static Logger log = Logger.getLogger(SpringPlugin.class.getName());
|
||||
|
||||
private static boolean debug = true;
|
||||
|
||||
|
||||
// TODO [gc] what about GC here - how do we know when they are finished with?
|
||||
public static List<Object> annotationMethodHandlerAdapterInstances = new ArrayList<Object>();
|
||||
|
||||
public static List<Object> defaultAnnotationHandlerMappingInstances = new ArrayList<Object>();
|
||||
|
||||
public static List<Object> requestMappingHandlerMappingInstances = new ArrayList<Object>();
|
||||
|
||||
public static List<Object> localVariableTableParameterNameDiscovererInstances = null;
|
||||
|
||||
|
||||
public static boolean support305 = true;
|
||||
|
||||
private Field classCacheField; // From CachedIntrospectionResults (Spring <= 4.0.x)
|
||||
|
||||
private Field strongClassCacheField; // From CachedIntrospectionResults (Spring >= 4.1.0)
|
||||
|
||||
private Field softClassCacheField; // From CachedIntrospectionResults (Spring >= 4.1.0)
|
||||
private Field declaredMethodsCacheField; // From ReflectionUtils
|
||||
|
||||
private Field declaredMethodsCacheField; // From ReflectionUtils
|
||||
|
||||
private Field parameterNamesCacheField; // From LocalVariableTableParameterNameDiscoverer
|
||||
|
||||
private boolean cachedIntrospectionResultsClassLoaded = false;
|
||||
|
||||
private boolean reflectionUtilsClassLoaded = false;
|
||||
|
||||
private Class<?> cachedIntrospectionResultsClass = null;
|
||||
|
||||
private Class<?> reflectionUtilsClass = null;
|
||||
|
||||
public boolean accept(String slashedTypeName, ClassLoader classLoader, ProtectionDomain protectionDomain, byte[] bytes) {
|
||||
public boolean accept(String slashedTypeName, ClassLoader classLoader, ProtectionDomain protectionDomain,
|
||||
byte[] bytes) {
|
||||
// TODO take classloader into account?
|
||||
if (slashedTypeName == null) {
|
||||
return false;
|
||||
@@ -88,12 +99,14 @@ public class SpringPlugin implements LoadtimeInstrumentationPlugin, ReloadEventP
|
||||
if (slashedTypeName.equals("org/springframework/util/ReflectionUtils")) {
|
||||
reflectionUtilsClassLoaded = true;
|
||||
}
|
||||
return slashedTypeName.equals("org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter") ||
|
||||
slashedTypeName.equals("org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping") || // 3.1
|
||||
return slashedTypeName.equals("org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter")
|
||||
||
|
||||
slashedTypeName.equals("org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping")
|
||||
|| // 3.1
|
||||
(support305 && slashedTypeName
|
||||
.equals("org/springframework/web/servlet/mvc/annotation/DefaultAnnotationHandlerMapping"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
public byte[] modify(String slashedClassName, ClassLoader classLoader, byte[] bytes) {
|
||||
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) {
|
||||
@@ -102,17 +115,18 @@ public class SpringPlugin implements LoadtimeInstrumentationPlugin, ReloadEventP
|
||||
if (slashedClassName.equals("org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter")) {
|
||||
return bytesWithInstanceCreationCaptured(bytes, THIS_CLASS,
|
||||
"recordAnnotationMethodHandlerAdapterInstance");
|
||||
}
|
||||
}
|
||||
else if (slashedClassName.equals("org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping")) {
|
||||
// springmvc spring 3.1 - doesnt work on 3.1 post M2 snapshots
|
||||
return bytesWithInstanceCreationCaptured(bytes, THIS_CLASS,
|
||||
"recordRequestMappingHandlerMappingInstance");
|
||||
}
|
||||
else if (slashedClassName.equals("org/springframework/core/LocalVariableTableParameterNameDiscoverer")) {
|
||||
return bytesWithInstanceCreationCaptured(bytes, THIS_CLASS,"recordLocalVariableTableParameterNameDiscoverer");
|
||||
return bytesWithInstanceCreationCaptured(bytes, THIS_CLASS,
|
||||
"recordLocalVariableTableParameterNameDiscoverer");
|
||||
}
|
||||
else { // "org/springframework/web/servlet/mvc/annotation/DefaultAnnotationHandlerMapping"
|
||||
// springmvc spring 3.0
|
||||
// springmvc spring 3.0
|
||||
return bytesWithInstanceCreationCaptured(bytes, THIS_CLASS,
|
||||
"recordDefaultAnnotationHandlerMappingInstance");
|
||||
}
|
||||
@@ -136,9 +150,10 @@ public class SpringPlugin implements LoadtimeInstrumentationPlugin, ReloadEventP
|
||||
|
||||
static {
|
||||
try {
|
||||
String debugString = System.getProperty("springloaded.plugins.spring.debug","false");
|
||||
String debugString = System.getProperty("springloaded.plugins.spring.debug", "false");
|
||||
debug = Boolean.valueOf(debugString);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
// likely security exception
|
||||
}
|
||||
}
|
||||
@@ -161,8 +176,9 @@ public class SpringPlugin implements LoadtimeInstrumentationPlugin, ReloadEventP
|
||||
}
|
||||
|
||||
/**
|
||||
* The Spring class LocalVariableTableParameterNameDiscoverer holds a cache of parameter names discovered for members within
|
||||
* classes and needs clearing if the class changes.
|
||||
* The Spring class LocalVariableTableParameterNameDiscoverer holds a cache of parameter names discovered for
|
||||
* members within classes and needs clearing if the class changes.
|
||||
*
|
||||
* @param clazz the class being reloaded, which may exist in a parameter name discoverer cache
|
||||
*/
|
||||
private void clearLocalVariableTableParameterNameDiscovererCache(Class<?> clazz) {
|
||||
@@ -176,20 +192,24 @@ public class SpringPlugin implements LoadtimeInstrumentationPlugin, ReloadEventP
|
||||
try {
|
||||
parameterNamesCacheField = localVariableTableParameterNameDiscovererInstances
|
||||
.get(0).getClass().getDeclaredField("parameterNamesCache");
|
||||
} catch (NoSuchFieldException nsfe) {
|
||||
log.log(Level.SEVERE, "Unexpectedly cannot find parameterNamesCache field on LocalVariableTableParameterNameDiscoverer class");
|
||||
}
|
||||
catch (NoSuchFieldException nsfe) {
|
||||
log.log(Level.SEVERE,
|
||||
"Unexpectedly cannot find parameterNamesCache field on LocalVariableTableParameterNameDiscoverer class");
|
||||
}
|
||||
}
|
||||
for (Object instance: localVariableTableParameterNameDiscovererInstances) {
|
||||
for (Object instance : localVariableTableParameterNameDiscovererInstances) {
|
||||
parameterNamesCacheField.setAccessible(true);
|
||||
try {
|
||||
Map<?,?> parameterNamesCache = (Map<?,?>) parameterNamesCacheField.get(instance);
|
||||
Map<?, ?> parameterNamesCache = (Map<?, ?>) parameterNamesCacheField.get(instance);
|
||||
Object o = parameterNamesCache.remove(clazz);
|
||||
if (debug) {
|
||||
System.out.println("ParameterNamesCache: Removed "+clazz.getName()+" from cache?"+(o!=null));
|
||||
System.out.println("ParameterNamesCache: Removed " + clazz.getName() + " from cache?" + (o != null));
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
log.log(Level.SEVERE, "Unexpected IllegalAccessException trying to access parameterNamesCache field on LocalVariableTableParameterNameDiscoverer class");
|
||||
}
|
||||
catch (IllegalAccessException e) {
|
||||
log.log(Level.SEVERE,
|
||||
"Unexpected IllegalAccessException trying to access parameterNamesCache field on LocalVariableTableParameterNameDiscoverer class");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -208,7 +228,8 @@ public class SpringPlugin implements LoadtimeInstrumentationPlugin, ReloadEventP
|
||||
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) {
|
||||
log.info("cleared a cache entry? " + (ret != null));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.log(Level.SEVERE, "Unexpected problem accessing methodResolverCache on " + o, e);
|
||||
}
|
||||
}
|
||||
@@ -227,21 +248,24 @@ public class SpringPlugin implements LoadtimeInstrumentationPlugin, ReloadEventP
|
||||
if (declaredMethodsCacheField == null) {
|
||||
try {
|
||||
declaredMethodsCacheField = reflectionUtilsClass.getDeclaredField("declaredMethodsCache");
|
||||
} catch(NoSuchFieldException e) {
|
||||
}
|
||||
catch (NoSuchFieldException e) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
if(declaredMethodsCacheField != null) {
|
||||
if (declaredMethodsCacheField != null) {
|
||||
declaredMethodsCacheField.setAccessible(true);
|
||||
Map m = (Map) declaredMethodsCacheField.get(null);
|
||||
Object o = m.remove(clazz);
|
||||
if (GlobalConfiguration.debugplugins) {
|
||||
System.err.println("SpringPlugin: clearing ReflectionUtils.declaredMethodsCache for " + clazz.getName() + " removed=" + o);
|
||||
System.err.println("SpringPlugin: clearing ReflectionUtils.declaredMethodsCache for "
|
||||
+ clazz.getName() + " removed=" + o);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
if (GlobalConfiguration.debugplugins) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
@@ -262,38 +286,43 @@ public class SpringPlugin implements LoadtimeInstrumentationPlugin, ReloadEventP
|
||||
if (classCacheField == null && strongClassCacheField == null) {
|
||||
try {
|
||||
classCacheField = cachedIntrospectionResultsClass.getDeclaredField("classCache");
|
||||
} catch(NoSuchFieldException e) {
|
||||
}
|
||||
catch (NoSuchFieldException e) {
|
||||
strongClassCacheField = cachedIntrospectionResultsClass.getDeclaredField("strongClassCache");
|
||||
softClassCacheField = cachedIntrospectionResultsClass.getDeclaredField("softClassCache");
|
||||
}
|
||||
|
||||
}
|
||||
if(classCacheField != null) {
|
||||
if (classCacheField != null) {
|
||||
classCacheField.setAccessible(true);
|
||||
Map m = (Map) classCacheField.get(null);
|
||||
Object o = m.remove(clazz);
|
||||
if (GlobalConfiguration.debugplugins) {
|
||||
System.err.println("SpringPlugin: clearing CachedIntrospectionResults.classCache for " + clazz.getName() + " removed=" + o);
|
||||
System.err.println("SpringPlugin: clearing CachedIntrospectionResults.classCache for "
|
||||
+ clazz.getName() + " removed=" + o);
|
||||
}
|
||||
}
|
||||
if(strongClassCacheField != null) {
|
||||
if (strongClassCacheField != null) {
|
||||
strongClassCacheField.setAccessible(true);
|
||||
Map m = (Map) strongClassCacheField.get(null);
|
||||
Object o = m.remove(clazz);
|
||||
if (GlobalConfiguration.debugplugins) {
|
||||
System.err.println("SpringPlugin: clearing CachedIntrospectionResults.strongClassCache for " + clazz.getName() + " removed=" + o);
|
||||
System.err.println("SpringPlugin: clearing CachedIntrospectionResults.strongClassCache for "
|
||||
+ clazz.getName() + " removed=" + o);
|
||||
}
|
||||
}
|
||||
if(softClassCacheField != null) {
|
||||
if (softClassCacheField != null) {
|
||||
softClassCacheField.setAccessible(true);
|
||||
Map m = (Map) softClassCacheField.get(null);
|
||||
Object o = m.remove(clazz);
|
||||
if (GlobalConfiguration.debugplugins) {
|
||||
System.err.println("SpringPlugin: clearing CachedIntrospectionResults.softClassCache for " + clazz.getName() + " removed=" + o);
|
||||
System.err.println("SpringPlugin: clearing CachedIntrospectionResults.softClassCache for "
|
||||
+ clazz.getName() + " removed=" + o);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
if (GlobalConfiguration.debugplugins) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
@@ -313,7 +342,8 @@ public class SpringPlugin implements LoadtimeInstrumentationPlugin, ReloadEventP
|
||||
Method method_detectHandlers = clazz_AbstractDetectingUrlHandlerMapping.getDeclaredMethod("detectHandlers");
|
||||
method_detectHandlers.setAccessible(true);
|
||||
method_detectHandlers.invoke(o);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
// if debugging then print it
|
||||
if (GlobalConfiguration.debugplugins) {
|
||||
e.printStackTrace();
|
||||
@@ -347,16 +377,19 @@ public class SpringPlugin implements LoadtimeInstrumentationPlugin, ReloadEventP
|
||||
Method method_initHandlerMethods = clazz_AbstractHandlerMethodMapping.getDeclaredMethod("initHandlerMethods");
|
||||
method_initHandlerMethods.setAccessible(true);
|
||||
method_initHandlerMethods.invoke(o);
|
||||
} catch (NoSuchFieldException nsfe) {
|
||||
}
|
||||
catch (NoSuchFieldException nsfe) {
|
||||
if (debug) {
|
||||
if (nsfe.getMessage().equals("handlerMethods")) {
|
||||
System.out.println("problem resetting request mapping handlers - unable to find field 'handlerMethods' on type 'AbstractHandlerMethodMapping' - you probably are not on Spring 3.1");
|
||||
}
|
||||
else {
|
||||
System.out.println("problem resetting request mapping handlers - NoSuchFieldException: "+nsfe.getMessage());
|
||||
System.out.println("problem resetting request mapping handlers - NoSuchFieldException: "
|
||||
+ nsfe.getMessage());
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
if (GlobalConfiguration.debugplugins) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
@@ -369,8 +402,8 @@ public class SpringPlugin implements LoadtimeInstrumentationPlugin, ReloadEventP
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify the supplied bytes such that constructors are intercepted and will invoke the specified class/method so that the
|
||||
* instances can be tracked.
|
||||
* Modify the supplied bytes such that constructors are intercepted and will invoke the specified class/method so
|
||||
* that the instances can be tracked.
|
||||
*
|
||||
* @return modified bytes for the class
|
||||
*/
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded.infra;
|
||||
|
||||
import java.util.logging.LogRecord;
|
||||
@@ -28,14 +29,15 @@ public class SLFormatter extends java.util.logging.Formatter {
|
||||
StringBuilder s = new StringBuilder();
|
||||
s.append(record.getLevel());
|
||||
String message = super.formatMessage(record);
|
||||
|
||||
if (!(message.startsWith(">") || message.startsWith("<"))) {
|
||||
|
||||
if (!(message.startsWith(">") || message.startsWith("<"))) {
|
||||
s.append(":");
|
||||
String sourceClassName = record.getSourceClassName();
|
||||
int idx;
|
||||
if ((idx = sourceClassName.lastIndexOf('.')) == -1) {
|
||||
s.append(record.getSourceClassName());
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
s.append(record.getSourceClassName().substring(idx + 1));
|
||||
}
|
||||
s.append(".");
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded.infra;
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded.jvm;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
@@ -26,7 +27,7 @@ import org.springsource.loaded.ri.ReflectiveInterceptor;
|
||||
|
||||
/**
|
||||
* Utility class containing operations that are "JVM" specific and may need porting when changing JVMs.
|
||||
*
|
||||
*
|
||||
* @author Kris De Volder
|
||||
* @since 0.5.0
|
||||
*/
|
||||
@@ -38,54 +39,63 @@ public class JVM {
|
||||
private static Constructor<Method> jlrMethodCtor = (Constructor<Method>) Method.class.getDeclaredConstructors()[0];
|
||||
|
||||
private static Method jlrMethodCopy;
|
||||
private static Method jlrMethodGetRoot;
|
||||
|
||||
|
||||
private static Method jlrMethodGetRoot;
|
||||
|
||||
static {
|
||||
try {
|
||||
jlrMethodCopy = Method.class.getDeclaredMethod("copy");
|
||||
jlrMethodCopy.setAccessible(true);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.log(Level.SEVERE, "Problems getting 'Method.copy()' method. Incompatible JVM?", e);
|
||||
}
|
||||
try {
|
||||
jlrMethodGetRoot = Method.class.getDeclaredMethod("getRoot");
|
||||
jlrMethodGetRoot.setAccessible(true);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
// Possibly on a JDK level that didn't have this?
|
||||
}
|
||||
}
|
||||
|
||||
private static Field jlrFieldRootField;
|
||||
|
||||
private static Method jlrFieldCopy;
|
||||
static {
|
||||
try {
|
||||
jlrFieldCopy = Field.class.getDeclaredMethod("copy");
|
||||
jlrFieldCopy.setAccessible(true);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.log(Level.SEVERE, "Problems getting 'Field.copy()' method. Incompatible JVM?", e);
|
||||
}
|
||||
try {
|
||||
jlrFieldRootField = Field.class.getDeclaredField("root");
|
||||
jlrFieldRootField.setAccessible(true);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
// Possibly on a JDK level that didn't have this?
|
||||
}
|
||||
}
|
||||
|
||||
private static Method jlrConstructorGetRoot;
|
||||
private static Method jlrConstructorGetRoot;
|
||||
|
||||
private static Method jlrConstructorCopy;
|
||||
static {
|
||||
try {
|
||||
|
||||
|
||||
jlrConstructorCopy = Constructor.class.getDeclaredMethod("copy");
|
||||
jlrConstructorCopy.setAccessible(true);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.log(Level.SEVERE, "Problems getting 'Constructor.copy()' method. Incompatible JVM?", e);
|
||||
}
|
||||
try {
|
||||
jlrConstructorGetRoot = Constructor.class.getDeclaredMethod("getRoot");
|
||||
jlrConstructorGetRoot.setAccessible(true);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
// Possibly on a JDK level that didn't have this?
|
||||
}
|
||||
}
|
||||
@@ -95,7 +105,8 @@ public class JVM {
|
||||
try {
|
||||
jlrMethodModifiers = Method.class.getDeclaredField("modifiers");
|
||||
jlrMethodModifiers.setAccessible(true);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.log(Level.SEVERE, "Problems getting Field 'Method.modifiers' method. Incompatible JVM?", e);
|
||||
}
|
||||
}
|
||||
@@ -105,7 +116,8 @@ public class JVM {
|
||||
try {
|
||||
jlrConstructorModifiers = Constructor.class.getDeclaredField("modifiers");
|
||||
jlrConstructorModifiers.setAccessible(true);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.log(Level.SEVERE, "Problems getting Field 'Constructor.modifiers' method. Incompatible JVM?", e);
|
||||
}
|
||||
}
|
||||
@@ -115,7 +127,8 @@ public class JVM {
|
||||
try {
|
||||
jlrFieldModifiers = Field.class.getDeclaredField("modifiers");
|
||||
jlrFieldModifiers.setAccessible(true);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.log(Level.SEVERE, "Problems getting Field 'Field.modifiers' method. Incompatible JVM?", e);
|
||||
}
|
||||
}
|
||||
@@ -130,7 +143,8 @@ public class JVM {
|
||||
* Create a new Method object from scratch. This Method object is 'fake' and will not be "invokable". ReflectionInterceptor will
|
||||
* be responsible to make sure user code calling 'invoke' on this object will be intercepted and handled appropriately.
|
||||
*/
|
||||
public static Method newMethod(Class<?> clazz, String name, Class<?>[] params, Class<?> returnType, Class<?>[] exceptions,
|
||||
public static Method newMethod(Class<?> clazz, String name, Class<?>[] params, Class<?> returnType,
|
||||
Class<?>[] exceptions,
|
||||
int modifiers, String signature) {
|
||||
// This is what the constructor looks like:
|
||||
// Method(Class declaringClass, String name, Class[] parameterTypes, Class returnType,
|
||||
@@ -139,9 +153,11 @@ public class JVM {
|
||||
Method returnMethod;
|
||||
try {
|
||||
jlrMethodCtor.setAccessible(true);
|
||||
returnMethod = jlrMethodCtor.newInstance(clazz, name, params, returnType, exceptions, modifiers, 0, signature, null,
|
||||
returnMethod = jlrMethodCtor.newInstance(clazz, name, params, returnType, exceptions, modifiers, 0,
|
||||
signature, null,
|
||||
null, null);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
//This shouldn't happen...
|
||||
ReflectiveInterceptor.log.log(Level.SEVERE, "Internal Error", e);
|
||||
throw new Error(e);
|
||||
@@ -161,7 +177,8 @@ public class JVM {
|
||||
}
|
||||
}
|
||||
return (Method) jlrMethodCopy.invoke(method);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.log(Level.SEVERE, "Problems copying method. Incompatible JVM?", e);
|
||||
return method; // return original as the best we can do
|
||||
}
|
||||
@@ -171,13 +188,14 @@ public class JVM {
|
||||
try {
|
||||
if (jlrFieldRootField != null) {
|
||||
jlrFieldRootField.setAccessible(true);
|
||||
Field possibleRoot = (Field)jlrFieldRootField.get(field);
|
||||
Field possibleRoot = (Field) jlrFieldRootField.get(field);
|
||||
if (possibleRoot != null) {
|
||||
field = possibleRoot;
|
||||
}
|
||||
}
|
||||
return (Field) jlrFieldCopy.invoke(field);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.log(Level.SEVERE, "Problems copying field. Incompatible JVM?", e);
|
||||
return field; // return original as the best we can do
|
||||
}
|
||||
@@ -192,7 +210,8 @@ public class JVM {
|
||||
}
|
||||
}
|
||||
return (Constructor<?>) jlrConstructorCopy.invoke(c);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.log(Level.SEVERE, "Problems copying constructor. Incompatible JVM?", e);
|
||||
return c; // return original as the best we can do
|
||||
}
|
||||
@@ -201,7 +220,8 @@ public class JVM {
|
||||
public static void setMethodModifiers(Method method, int modifiers) {
|
||||
try {
|
||||
jlrMethodModifiers.setInt(method, modifiers);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.log(Level.SEVERE, "Couldn't set correct modifiers on reflected method: " + method, e);
|
||||
}
|
||||
}
|
||||
@@ -209,7 +229,8 @@ public class JVM {
|
||||
public static void setConstructorModifiers(Constructor<?> c, int modifiers) {
|
||||
try {
|
||||
jlrConstructorModifiers.setInt(c, modifiers);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.log(Level.SEVERE, "Couldn't set correct modifiers on reflected constructor: " + c, e);
|
||||
}
|
||||
}
|
||||
@@ -217,7 +238,8 @@ public class JVM {
|
||||
public static void setFieldModifiers(Field field, int mods) {
|
||||
try {
|
||||
jlrFieldModifiers.setInt(field, mods);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.log(Level.SEVERE, "Couldn't set correct modifiers on reflected field: " + field, e);
|
||||
}
|
||||
}
|
||||
@@ -232,7 +254,8 @@ public class JVM {
|
||||
// byte[] annotations)
|
||||
try {
|
||||
return jlFieldCtor.newInstance(declaring, name, type, mods, 0, sig, null);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new IllegalStateException("Problem creating reloadable Field: " + declaring.getName() + "." + name, e);
|
||||
}
|
||||
}
|
||||
@@ -241,7 +264,8 @@ public class JVM {
|
||||
private static final Constructor<Constructor<?>> jlConstructorCtor = (Constructor<Constructor<?>>) Constructor.class
|
||||
.getDeclaredConstructors()[0];
|
||||
|
||||
public static Constructor<?> newConstructor(Class<?> clazz, Class<?>[] params, Class<?>[] exceptions, int modifiers,
|
||||
public static Constructor<?> newConstructor(Class<?> clazz, Class<?>[] params, Class<?>[] exceptions,
|
||||
int modifiers,
|
||||
String signature) {
|
||||
jlConstructorCtor.setAccessible(true);
|
||||
// This is what the constructor looks like:
|
||||
@@ -255,7 +279,8 @@ public class JVM {
|
||||
// byte[] parameterAnnotations)
|
||||
try {
|
||||
return jlConstructorCtor.newInstance(clazz, params, exceptions, modifiers, 0, signature, null, null);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
StringBuffer msg = new StringBuffer("Problem creating reloadable Constructor: ");
|
||||
msg.append(clazz.getName());
|
||||
msg.append("(");
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded.pluginhelpers;
|
||||
|
||||
import org.objectweb.asm.AnnotationVisitor;
|
||||
@@ -52,7 +53,7 @@ public class EmptyCtor extends ClassVisitor implements Constants {
|
||||
}
|
||||
|
||||
private EmptyCtor(String... descriptors) {
|
||||
super(ASM5,new ClassWriter(0)); // TODO review 0 here
|
||||
super(ASM5, new ClassWriter(0)); // TODO review 0 here
|
||||
this.descriptors = descriptors;
|
||||
}
|
||||
|
||||
@@ -75,7 +76,8 @@ public class EmptyCtor extends ClassVisitor implements Constants {
|
||||
if (name.equals("<init>") && isInterestingDescriptor(desc)) {
|
||||
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
|
||||
return new Emptier(mv);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
return super.visitMethod(access, name, desc, signature, exceptions);
|
||||
}
|
||||
}
|
||||
@@ -127,10 +129,10 @@ public class EmptyCtor extends ClassVisitor implements Constants {
|
||||
|
||||
public void visitMethodInsn(int opcode, String owner, String name, String desc) {
|
||||
}
|
||||
|
||||
public void visitMethodInsn(int opcode, String owner, String name,
|
||||
String desc, boolean itf) {
|
||||
}
|
||||
|
||||
public void visitMethodInsn(int opcode, String owner, String name,
|
||||
String desc, boolean itf) {
|
||||
}
|
||||
|
||||
public void visitJumpInsn(int opcode, Label label) {
|
||||
}
|
||||
@@ -174,4 +176,4 @@ public class EmptyCtor extends ClassVisitor implements Constants {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded.ri;
|
||||
|
||||
import java.lang.reflect.Modifier;
|
||||
@@ -27,10 +28,12 @@ import java.util.List;
|
||||
public class DynamicLookup {
|
||||
|
||||
private String name;
|
||||
|
||||
private String methodDescriptor;
|
||||
|
||||
/**
|
||||
* Create an object capable of performing a dynamic method lookup in some MethodProvider
|
||||
*
|
||||
* @param name method name
|
||||
* @param methodDescriptor method descriptor (e.g. (Ljava/lang/String;)V)
|
||||
*/
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded.ri;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
@@ -39,12 +40,15 @@ public class Exceptions {
|
||||
String valueString;
|
||||
if (value == null) {
|
||||
valueString = "null value";
|
||||
} else if (valueType.isPrimitive()) {
|
||||
}
|
||||
else if (valueType.isPrimitive()) {
|
||||
valueString = "(" + valueType.getName() + ")" + value;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
valueString = value == null ? "null value" : value.getClass().getName();
|
||||
}
|
||||
return new IllegalAccessException("Can not set final " + fieldType + " field " + fieldQName + " to " + valueString);
|
||||
return new IllegalAccessException("Can not set final " + fieldType + " field " + fieldQName + " to "
|
||||
+ valueString);
|
||||
}
|
||||
|
||||
static IllegalArgumentException illegalSetFieldTypeException(Field field, Class<?> valueType, Object value) {
|
||||
@@ -59,12 +63,15 @@ public class Exceptions {
|
||||
String valueStr;
|
||||
if (valueType == null) {
|
||||
valueStr = "null value";
|
||||
} else if (valueType.isPrimitive()) {
|
||||
}
|
||||
else if (valueType.isPrimitive()) {
|
||||
valueStr = "(" + valueType.getName() + ")" + value;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
valueStr = valueType.getName();
|
||||
}
|
||||
return new IllegalArgumentException("Can not set " + modStr + fieldType + " field " + fieldQName + " to " + valueStr);
|
||||
return new IllegalArgumentException("Can not set " + modStr + fieldType + " field " + fieldQName + " to "
|
||||
+ valueStr);
|
||||
}
|
||||
|
||||
public static NoSuchFieldError noSuchFieldError(Field field) {
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded.ri;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
@@ -33,8 +34,9 @@ import org.springsource.loaded.jvm.JVM;
|
||||
/**
|
||||
* This class contains code that is used as support infrastructure to implement Field lookup algorithms.
|
||||
*
|
||||
* Mainly, it provides an abstraction to allows Java classes and reloadable types to be treated as instances of a common abstraction
|
||||
* "FieldProvider" and then implement algorithms to find fields in those providers independent of how the fields are being provided.
|
||||
* Mainly, it provides an abstraction to allows Java classes and reloadable types to be treated as instances of a common
|
||||
* abstraction "FieldProvider" and then implement algorithms to find fields in those providers independent of how the
|
||||
* fields are being provided.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
* @since 0.5.0
|
||||
@@ -123,6 +125,7 @@ public class FieldLookup {
|
||||
public static class ReloadedTypeFieldRef extends FieldRef {
|
||||
|
||||
private ReloadableType rtype;
|
||||
|
||||
private FieldMember f;
|
||||
|
||||
public ReloadedTypeFieldRef(ReloadableType rtype, FieldMember f) {
|
||||
@@ -139,7 +142,8 @@ public class FieldLookup {
|
||||
Class<?> type;
|
||||
try {
|
||||
type = Utils.toClass(Type.getType(f.getDescriptor()), rtype.typeRegistry.getClassLoader());
|
||||
} catch (ClassNotFoundException e) {
|
||||
}
|
||||
catch (ClassNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
return JVM.newField(declaring, type, f.getModifiers(), f.getName(), f.getGenericSignature());
|
||||
@@ -174,10 +178,12 @@ public class FieldLookup {
|
||||
public static FieldProvider create(TypeRegistry typeRegistry, String slashyName) {
|
||||
if (typeRegistry.isReloadableTypeName(slashyName)) {
|
||||
return create(typeRegistry.getReloadableType(slashyName));
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
try {
|
||||
return create(Utils.toClass(Type.getObjectType(slashyName), typeRegistry.getClassLoader()));
|
||||
} catch (ClassNotFoundException e) {
|
||||
}
|
||||
catch (ClassNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
@@ -189,6 +195,7 @@ public class FieldLookup {
|
||||
}
|
||||
|
||||
public static class ReloadableTypeFieldProvider extends FieldProvider {
|
||||
|
||||
private ReloadableType rtype;
|
||||
|
||||
public ReloadableTypeFieldProvider(ReloadableType rtype) {
|
||||
@@ -213,10 +220,12 @@ public class FieldLookup {
|
||||
Field jf = rtype.getClazz().getDeclaredField(f.getName());
|
||||
ReflectiveInterceptor.fixModifier(rtype.getLatestTypeDescriptor(), jf);
|
||||
return new JavaFieldRef(jf);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
//Already reloaded
|
||||
return new ReloadedTypeFieldRef(rtype, f);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded.ri;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded.ri;
|
||||
|
||||
import java.util.List;
|
||||
@@ -26,6 +27,7 @@ import java.util.List;
|
||||
public class GetDeclaredMethodLookup {
|
||||
|
||||
private String name;
|
||||
|
||||
private String paramsDescriptor;
|
||||
|
||||
/*
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded.ri;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded.ri;
|
||||
|
||||
import java.lang.reflect.Modifier;
|
||||
@@ -29,6 +30,7 @@ import org.springsource.loaded.Utils;
|
||||
public class GetMethodLookup {
|
||||
|
||||
private String name;
|
||||
|
||||
private String paramsDescriptor;
|
||||
|
||||
/*
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded.ri;
|
||||
|
||||
import java.lang.reflect.Modifier;
|
||||
@@ -33,7 +34,8 @@ public class GetMethodsLookup {
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect all public methods from methodProvider and its supertypes into the 'found' hasmap, indexed by "name+descriptor".
|
||||
* Collect all public methods from methodProvider and its supertypes into the 'found' hasmap, indexed by
|
||||
* "name+descriptor".
|
||||
*/
|
||||
private void collectAll(MethodProvider methodProvider, Map<String, Invoker> found) {
|
||||
// We do this in inverse order as in 'GetMethodLookup'. This is because GetMethodLookup
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded.ri;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
@@ -22,9 +23,9 @@ import java.lang.reflect.Modifier;
|
||||
/**
|
||||
* An invoker represents the result of a method lookup operation in the type hierarchy.
|
||||
* <p>
|
||||
* It encapsulates a reference to a resolved method implementation in a reloadable or non-reloadable type and provides an 'invoke'
|
||||
* method suitable for invoking that method implementation, and a 'createJavaMethod' to create a Java {@link Method} instance that
|
||||
* can be used to represent the method in the Java reflection API.
|
||||
* It encapsulates a reference to a resolved method implementation in a reloadable or non-reloadable type and provides
|
||||
* an 'invoke' method suitable for invoking that method implementation, and a 'createJavaMethod' to create a Java
|
||||
* {@link Method} instance that can be used to represent the method in the Java reflection API.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
* @since 0.5.0
|
||||
@@ -33,7 +34,8 @@ public abstract class Invoker {
|
||||
|
||||
private Method cachedMethod; //Cached for cases where we get call getJavaMethod multiple times.
|
||||
|
||||
public abstract Object invoke(Object target, Object... params) throws IllegalArgumentException, IllegalAccessException,
|
||||
public abstract Object invoke(Object target, Object... params) throws IllegalArgumentException,
|
||||
IllegalAccessException,
|
||||
InvocationTargetException;
|
||||
|
||||
public abstract int getModifiers();
|
||||
@@ -43,7 +45,8 @@ public abstract class Invoker {
|
||||
public abstract String getMethodDescriptor();
|
||||
|
||||
public String toString() {
|
||||
return "Invoker(" + Modifier.toString(getModifiers()) + " " + getClassName() + "." + getName() + getMethodDescriptor()
|
||||
return "Invoker(" + Modifier.toString(getModifiers()) + " " + getClassName() + "." + getName()
|
||||
+ getMethodDescriptor()
|
||||
+ ")";
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded.ri;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded.ri;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
@@ -24,11 +25,11 @@ import org.springsource.loaded.MethodMember;
|
||||
|
||||
|
||||
/**
|
||||
* Creating Java Method objects for a given MethodMember is rather expensive because it typically involves getting. The declared
|
||||
* methods of a Class and searching for one that matches the method signature. This is most problematic when we are trying to get a
|
||||
* Method for an array of MethodMembers, because in this case we will end up repeating the process multiple times. A JavaMethodCache
|
||||
* instance can cache Method objects from the first time we iterate the declared methods of a class so subsequently we can just get
|
||||
* the other methods from the cache.
|
||||
* Creating Java Method objects for a given MethodMember is rather expensive because it typically involves getting. The
|
||||
* declared methods of a Class and searching for one that matches the method signature. This is most problematic when we
|
||||
* are trying to get a Method for an array of MethodMembers, because in this case we will end up repeating the process
|
||||
* multiple times. A JavaMethodCache instance can cache Method objects from the first time we iterate the declared
|
||||
* methods of a class so subsequently we can just get the other methods from the cache.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
* @since 0.5.0
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded.ri;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
@@ -23,8 +24,8 @@ import org.springsource.loaded.jvm.JVM;
|
||||
|
||||
|
||||
/**
|
||||
* Implementation of Invoker that wraps a {@link Method} object. It is assumed that this Method object is from a non-reloadable
|
||||
* Class so it shouldn't need any kind of special handling.
|
||||
* Implementation of Invoker that wraps a {@link Method} object. It is assumed that this Method object is from a
|
||||
* non-reloadable Class so it shouldn't need any kind of special handling.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
* @since 0.5.0
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded.ri;
|
||||
|
||||
import java.util.Collection;
|
||||
@@ -26,8 +27,8 @@ import org.springsource.loaded.Utils;
|
||||
|
||||
|
||||
/**
|
||||
* To manage the complexity of the different cases created by a variety of different types of contexts where we can do 'method
|
||||
* lookup' we need an abstraction to represent them all.
|
||||
* To manage the complexity of the different cases created by a variety of different types of contexts where we can do
|
||||
* 'method lookup' we need an abstraction to represent them all.
|
||||
* <p>
|
||||
* This class provides that abstraction.
|
||||
*
|
||||
@@ -49,7 +50,8 @@ public abstract class MethodProvider {
|
||||
ClassLoader pcl = tr.getClassLoader().getParent();
|
||||
if (pcl == null) {
|
||||
break;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
tr = TypeRegistry.getTypeRegistryFor(pcl);
|
||||
if (tr == null) {
|
||||
break;
|
||||
@@ -86,11 +88,13 @@ public abstract class MethodProvider {
|
||||
}
|
||||
}
|
||||
return create(class1);
|
||||
} catch (ClassNotFoundException e) {
|
||||
}
|
||||
catch (ClassNotFoundException e) {
|
||||
throw new IllegalStateException("We have a type descriptor for '" + typeDescriptor.getName()
|
||||
+ " but no corresponding Java class", e);
|
||||
}
|
||||
} catch (RuntimeException re) {
|
||||
}
|
||||
catch (RuntimeException re) {
|
||||
re.printStackTrace();
|
||||
throw re;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded.ri;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
@@ -30,7 +31,9 @@ import org.springsource.loaded.jvm.JVM;
|
||||
public class OriginalClassInvoker extends Invoker {
|
||||
|
||||
private Class<?> clazz;
|
||||
|
||||
private MethodMember method;
|
||||
|
||||
private JavaMethodCache methodCache;
|
||||
|
||||
public OriginalClassInvoker(Class<?> clazz, MethodMember methodMember, JavaMethodCache methodCache) {
|
||||
@@ -87,4 +90,4 @@ public class OriginalClassInvoker extends Invoker {
|
||||
public String getClassName() {
|
||||
return clazz.getName();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded.ri;
|
||||
|
||||
import java.util.Collection;
|
||||
@@ -29,8 +30,8 @@ import org.springsource.loaded.Utils;
|
||||
|
||||
|
||||
/**
|
||||
* Concrete implementation of MethodProvider that provides methods for a Reloadable Type, taking into account any changes made to
|
||||
* the type by reloading.
|
||||
* Concrete implementation of MethodProvider that provides methods for a Reloadable Type, taking into account any
|
||||
* changes made to the type by reloading.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
* @since 0.5.0
|
||||
@@ -50,7 +51,8 @@ public class ReloadableTypeMethodProvider extends TypeDescriptorMethodProvider {
|
||||
if (rtype.getLiveVersion() == null) {
|
||||
//Should be possible to call the original method
|
||||
return new OriginalClassInvoker(rtype.getClazz(), methodMember, rtype.getJavaMethodCache());
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
//Should be calling executor method
|
||||
return ReloadedTypeInvoker.create(this, methodMember);
|
||||
}
|
||||
@@ -72,7 +74,7 @@ public class ReloadableTypeMethodProvider extends TypeDescriptorMethodProvider {
|
||||
@Override
|
||||
public List<Invoker> getDeclaredMethods() {
|
||||
if (rtype.invokersCache_getDeclaredMethods != null) {
|
||||
// if (TypeRegistry.nothingReloaded && rtype.invokersCache_getDeclaredMethods != null) {
|
||||
// if (TypeRegistry.nothingReloaded && rtype.invokersCache_getDeclaredMethods != null) {
|
||||
// use the cached version, it will not change if a reload hasn't occurred
|
||||
return rtype.invokersCache_getDeclaredMethods;
|
||||
}
|
||||
@@ -83,7 +85,7 @@ public class ReloadableTypeMethodProvider extends TypeDescriptorMethodProvider {
|
||||
|
||||
@Override
|
||||
public Collection<Invoker> getMethods() {
|
||||
// if ((TypeRegistry.nothingReloaded || !rtype.isAffectedByReload()) &&
|
||||
// if ((TypeRegistry.nothingReloaded || !rtype.isAffectedByReload()) &&
|
||||
if (rtype.invokersCache_getMethods != null) {
|
||||
return rtype.invokersCache_getMethods;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded.ri;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
@@ -37,6 +38,7 @@ import org.springsource.loaded.jvm.JVM;
|
||||
public abstract class ReloadedTypeInvoker extends Invoker {
|
||||
|
||||
ReloadableType rtype;
|
||||
|
||||
private MethodMember methodMember;
|
||||
|
||||
private ReloadedTypeInvoker(ReloadableTypeMethodProvider declaringType, MethodMember methodMember) {
|
||||
@@ -49,7 +51,8 @@ public abstract class ReloadedTypeInvoker extends Invoker {
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract Object invoke(Object target, Object... params) throws IllegalArgumentException, IllegalAccessException,
|
||||
public abstract Object invoke(Object target, Object... params) throws IllegalArgumentException,
|
||||
IllegalAccessException,
|
||||
InvocationTargetException;
|
||||
|
||||
/**
|
||||
@@ -68,8 +71,10 @@ public abstract class ReloadedTypeInvoker extends Invoker {
|
||||
Class<?>[] exceptions = Utils.slashedNamesToClasses(methodMember.getExceptions(), classLoader);
|
||||
return JVM.newMethod(clazz, name, params, returnType, exceptions, methodMember.getModifiers(),
|
||||
methodMember.getGenericSignature());
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new IllegalStateException("Couldn't create j.l.r.Method for " + clazz.getName() + "." + methodDescriptor, e);
|
||||
}
|
||||
catch (ClassNotFoundException e) {
|
||||
throw new IllegalStateException("Couldn't create j.l.r.Method for " + clazz.getName() + "."
|
||||
+ methodDescriptor, e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,25 +102,31 @@ public abstract class ReloadedTypeInvoker extends Invoker {
|
||||
if (Modifier.isStatic(methodMember.getModifiers())) {
|
||||
// Since static methods don't change parameter lists, they just invoke the executor
|
||||
return new ReloadedTypeInvoker(declaringType, methodMember) {
|
||||
|
||||
@Override
|
||||
public Object invoke(Object target, Object... params) throws IllegalArgumentException, IllegalAccessException,
|
||||
public Object invoke(Object target, Object... params) throws IllegalArgumentException,
|
||||
IllegalAccessException,
|
||||
InvocationTargetException {
|
||||
CurrentLiveVersion clv = rtype.getLiveVersion();
|
||||
Method executor = clv.getExecutorMethod(methodMember);
|
||||
return executor.invoke(target, params);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
// Non static method invokers need to add target as a first param
|
||||
return new ReloadedTypeInvoker(declaringType, methodMember) {
|
||||
|
||||
@Override
|
||||
public Object invoke(Object target, Object... params) throws IllegalArgumentException, IllegalAccessException,
|
||||
public Object invoke(Object target, Object... params) throws IllegalArgumentException,
|
||||
IllegalAccessException,
|
||||
InvocationTargetException {
|
||||
CurrentLiveVersion clv = rtype.getLiveVersion();
|
||||
Method executor = clv.getExecutorMethod(methodMember);
|
||||
if (params == null) {
|
||||
return executor.invoke(null, target);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
Object[] ps = new Object[params.length + 1];
|
||||
System.arraycopy(params, 0, ps, 1, params.length);
|
||||
ps[0] = target;
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded.ri;
|
||||
|
||||
import java.util.List;
|
||||
@@ -26,10 +27,12 @@ import java.util.List;
|
||||
public class StaticLookup {
|
||||
|
||||
private String name;
|
||||
|
||||
private String methodDescriptor;
|
||||
|
||||
/**
|
||||
* Create an object capable of performing a dynamic method lookup in some MethodProvider
|
||||
*
|
||||
* @param name the method name
|
||||
* @param methodDescriptor the method descriptor (e.g. (Ljava/lang/String;)I)
|
||||
*/
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded.ri;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -60,11 +61,13 @@ public abstract class TypeDescriptorMethodProvider extends MethodProvider {
|
||||
if (superName == null) {
|
||||
//This happens only for type Object... Code unreachable unless Object is reloadable
|
||||
return null;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
ReloadableType rsuper = registry.getReloadableType(superName);
|
||||
if (rsuper != null) {
|
||||
return MethodProvider.create(rsuper);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
TypeDescriptor dsuper = registry.getDescriptorFor(superName);
|
||||
return MethodProvider.create(registry, dsuper);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded.support;
|
||||
|
||||
import java.lang.invoke.CallSite;
|
||||
@@ -33,88 +34,81 @@ import org.springsource.loaded.ReloadableType;
|
||||
* @author Andy Clement
|
||||
* @since 1.2
|
||||
*/
|
||||
public class Java8 {
|
||||
|
||||
/**
|
||||
public class Java8 {
|
||||
|
||||
/**
|
||||
* Notes:
|
||||
*
|
||||
* Useful to have an example of how this code behaves. Here is a bit of code:
|
||||
*
|
||||
* class basic.LambdaA {
|
||||
* interface Foo { int m(); }
|
||||
* static int run() {
|
||||
* Foo f = null;
|
||||
* f = () -> 77;
|
||||
* return f.m();
|
||||
* }
|
||||
* }
|
||||
* class basic.LambdaA { interface Foo { int m(); } static int run() { Foo f = null; f = () -> 77; return f.m(); } }
|
||||
*
|
||||
* Here is a bootstrap method entry in the constant pool:
|
||||
*
|
||||
* 0: #31 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:
|
||||
* (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;
|
||||
* Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
|
||||
* Method arguments:
|
||||
* #32 ()I
|
||||
* #33 invokestatic basic/LambdaA.lambda$run$0:()I
|
||||
* #32 ()I
|
||||
* 0: #31 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:
|
||||
* (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/
|
||||
* lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;
|
||||
* Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; Method arguments: #32
|
||||
* ()I #33 invokestatic basic/LambdaA.lambda$run$0:()I #32 ()I
|
||||
*
|
||||
* At the invokedynamic site:
|
||||
* bsmId = 0
|
||||
* nameAndDescriptor = m()Lbasic/LambdaA$Foo;
|
||||
*
|
||||
* When invoking the metafactory bootstrap method the first two parameters are stacked by the VM automatically, namely the MethodHandles$Lookup
|
||||
* instance (caller) and the first String (invokedName). What the VM actually sees is this:
|
||||
* At the invokedynamic site: bsmId = 0 nameAndDescriptor = m()Lbasic/LambdaA$Foo;
|
||||
*
|
||||
* metaFactory parameters:
|
||||
* 0:MethodHandles$Lookup caller = basic.LambdaA
|
||||
* 1:String invokedName = "m"
|
||||
* 2:MethodType invokedType = "()Foo"
|
||||
* 3:MethodType samMethodType = "()int"
|
||||
* 4:MethodHandle implMethod = (actually a DirectMethodHandle where memberName is "basic.LambdaA.lambda$run$0()int/invokeStatic")
|
||||
* 5:MethodType instantiatedMethodType = "()int"
|
||||
* When invoking the metafactory bootstrap method the first two parameters are stacked by the VM automatically,
|
||||
* namely the MethodHandles$Lookup instance (caller) and the first String (invokedName). What the VM actually sees
|
||||
* is this:
|
||||
*
|
||||
* With all that information then the calls in this case are relatively straightforward:
|
||||
* CallSite callsite = LambdaMetafactory.metafactory(caller, invokedName, invokedType, samMethodType, implMethod, instantiatedMethodType);
|
||||
* callsite.dynamicInvoker().invokeWithArguments((Object[])null);
|
||||
*/
|
||||
|
||||
* metaFactory parameters: 0:MethodHandles$Lookup caller = basic.LambdaA 1:String invokedName = "m" 2:MethodType
|
||||
* invokedType = "()Foo" 3:MethodType samMethodType = "()int" 4:MethodHandle implMethod = (actually a
|
||||
* DirectMethodHandle where memberName is "basic.LambdaA.lambda$run$0()int/invokeStatic") 5:MethodType
|
||||
* instantiatedMethodType = "()int"
|
||||
*
|
||||
* With all that information then the calls in this case are relatively straightforward: CallSite callsite =
|
||||
* LambdaMetafactory.metafactory(caller, invokedName, invokedType, samMethodType, implMethod,
|
||||
* instantiatedMethodType); callsite.dynamicInvoker().invokeWithArguments((Object[])null);
|
||||
*/
|
||||
|
||||
/**
|
||||
* Programmatic emulation of INVOKEDYNAMIC so initialize the callsite via use of the bootstrap method then
|
||||
* invoke the result.
|
||||
* Programmatic emulation of INVOKEDYNAMIC so initialize the callsite via use of the bootstrap method then invoke
|
||||
* the result.
|
||||
*
|
||||
* @param executorClass the executor that will contain the lambda function, null if not yet reloaded
|
||||
* @param handle bootstrap method handle
|
||||
* @param bsmArgs bootstrap method arguments
|
||||
* @param lookup The MethodHandles.Lookup object that can be used to find types
|
||||
* @param lookup The MethodHandles.Lookup object that can be used to find types
|
||||
* @param indyNameAndDescriptor Method name and descriptor at invokedynamic site
|
||||
* @param indyParams parameters when the invokedynamic call is made
|
||||
* @return the result of the invokedynamic call
|
||||
*/
|
||||
public static Object emulateInvokeDynamic(ReloadableType rtype, Class<?> executorClass, Handle handle, Object[] bsmArgs, Object lookup, String indyNameAndDescriptor, Object[] indyParams) {
|
||||
public static Object emulateInvokeDynamic(ReloadableType rtype, Class<?> executorClass, Handle handle,
|
||||
Object[] bsmArgs, Object lookup, String indyNameAndDescriptor, Object[] indyParams) {
|
||||
try {
|
||||
CallSite callsite = callLambdaMetaFactory(rtype, bsmArgs,lookup,indyNameAndDescriptor,executorClass);
|
||||
CallSite callsite = callLambdaMetaFactory(rtype, bsmArgs, lookup, indyNameAndDescriptor, executorClass);
|
||||
return callsite.dynamicInvoker().invokeWithArguments(indyParams);
|
||||
} catch (Throwable t) {
|
||||
}
|
||||
catch (Throwable t) {
|
||||
throw new RuntimeException(t);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO [perf] How about a table of CallSites indexed by invokedynamic number through the class file. Computed on first reference but cleared on reload. Possibly extend this to all invoke types!
|
||||
|
||||
// TODO [lambda] Need to handle altMetaFactory which is used when the lambdas are more 'complex' (e.g. Serializable)
|
||||
public static CallSite callLambdaMetaFactory(ReloadableType rtype, Object[] bsmArgs, Object lookup, String indyNameAndDescriptor,Class<?> executorClass) throws Exception {
|
||||
MethodHandles.Lookup caller = (MethodHandles.Lookup)lookup;
|
||||
public static CallSite callLambdaMetaFactory(ReloadableType rtype, Object[] bsmArgs, Object lookup,
|
||||
String indyNameAndDescriptor, Class<?> executorClass) throws Exception {
|
||||
MethodHandles.Lookup caller = (MethodHandles.Lookup) lookup;
|
||||
|
||||
ClassLoader callerLoader = caller.lookupClass().getClassLoader();
|
||||
|
||||
int descriptorStart = indyNameAndDescriptor.indexOf('(');
|
||||
String invokedName = indyNameAndDescriptor.substring(0,descriptorStart);
|
||||
MethodType invokedType = MethodType.fromMethodDescriptorString(indyNameAndDescriptor.substring(descriptorStart), callerLoader);
|
||||
|
||||
// Use bsmArgs to build the parameters
|
||||
MethodType samMethodType = MethodType.fromMethodDescriptorString((String)(((Type)bsmArgs[0]).getDescriptor()), callerLoader);
|
||||
String invokedName = indyNameAndDescriptor.substring(0, descriptorStart);
|
||||
MethodType invokedType = MethodType.fromMethodDescriptorString(
|
||||
indyNameAndDescriptor.substring(descriptorStart), callerLoader);
|
||||
|
||||
Handle bsmArgsHandle = (Handle)bsmArgs[1];
|
||||
// Use bsmArgs to build the parameters
|
||||
MethodType samMethodType = MethodType.fromMethodDescriptorString(
|
||||
(String) (((Type) bsmArgs[0]).getDescriptor()), callerLoader);
|
||||
|
||||
Handle bsmArgsHandle = (Handle) bsmArgs[1];
|
||||
String owner = bsmArgsHandle.getOwner();
|
||||
String name = bsmArgsHandle.getName();
|
||||
String descriptor = bsmArgsHandle.getDesc();
|
||||
@@ -134,7 +128,8 @@ public class Java8 {
|
||||
implMethod = caller.findSpecial(caller.lookupClass(), name, implMethodType, caller.lookupClass());
|
||||
}
|
||||
else {
|
||||
implMethod = caller.findStatic(caller.lookupClass(), name, MethodType.fromMethodDescriptorString("(L"+owner+";"+descriptor.substring(1),callerLoader));
|
||||
implMethod = caller.findStatic(caller.lookupClass(), name, MethodType.fromMethodDescriptorString(
|
||||
"(L" + owner + ";" + descriptor.substring(1), callerLoader));
|
||||
}
|
||||
break;
|
||||
case Opcodes.H_INVOKEVIRTUAL:
|
||||
@@ -145,29 +140,33 @@ public class Java8 {
|
||||
implMethod = caller.findVirtual(caller.lookupClass(), name, implMethodType);
|
||||
}
|
||||
else {
|
||||
implMethod = caller.findStatic(caller.lookupClass(), name, MethodType.fromMethodDescriptorString("(L"+owner+";"+descriptor.substring(1),callerLoader));
|
||||
implMethod = caller.findStatic(caller.lookupClass(), name, MethodType.fromMethodDescriptorString(
|
||||
"(L" + owner + ";" + descriptor.substring(1), callerLoader));
|
||||
}
|
||||
break;
|
||||
case Opcodes.H_INVOKEINTERFACE:
|
||||
Handle h = (Handle)bsmArgs[1];
|
||||
Handle h = (Handle) bsmArgs[1];
|
||||
String interfaceOwner = h.getOwner();
|
||||
// TODO Should there not be a more direct way to this than classloading?
|
||||
// TODO What about when this is a method added to the interface on a reload? It won't really exist, should we point
|
||||
// to the executor? or something else? (maybe just directly the real method that will satisfy the interface - if it can be worked out)
|
||||
Class<?> interfaceClass = callerLoader.loadClass(interfaceOwner.replace('/','.')); // interface type, eg StreamB$Foo
|
||||
Class<?> interfaceClass = callerLoader.loadClass(interfaceOwner.replace('/', '.')); // interface type, eg StreamB$Foo
|
||||
implMethod = caller.findVirtual(interfaceClass, name, implMethodType);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("nyi "+bsmArgsHandle.getTag());
|
||||
throw new IllegalStateException("nyi " + bsmArgsHandle.getTag());
|
||||
}
|
||||
|
||||
MethodType instantiatedMethodType = MethodType.fromMethodDescriptorString((String)(((Type)bsmArgs[2]).getDescriptor()), callerLoader);
|
||||
|
||||
return LambdaMetafactory.metafactory(caller, invokedName, invokedType, samMethodType, implMethod, instantiatedMethodType);
|
||||
MethodType instantiatedMethodType = MethodType.fromMethodDescriptorString(
|
||||
(String) (((Type) bsmArgs[2]).getDescriptor()), callerLoader);
|
||||
|
||||
return LambdaMetafactory.metafactory(caller, invokedName, invokedType, samMethodType, implMethod,
|
||||
instantiatedMethodType);
|
||||
}
|
||||
|
||||
/**
|
||||
* The metafactory we are enhancing is responsible for generating the anonymous classes that will call the lambda methods in our type
|
||||
* The metafactory we are enhancing is responsible for generating the anonymous classes that will call the lambda
|
||||
* methods in our type
|
||||
*
|
||||
* @param bytes the class bytes for the InnerClassLambdaMetaFactory that is going to be modified
|
||||
* @return the class bytes for the modified InnerClassLambdaMetaFactory
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded.test.infra;
|
||||
|
||||
import java.io.File;
|
||||
@@ -35,9 +36,13 @@ import org.springsource.loaded.Utils;
|
||||
public class ClassPrinter extends ClassVisitor implements Opcodes {
|
||||
|
||||
private PrintStream destination;
|
||||
|
||||
private boolean includeBytecode;
|
||||
|
||||
private int includeFlags = 0x0000;
|
||||
|
||||
public final static int INCLUDE_BYTECODE = 0x0001;
|
||||
|
||||
public final static int INCLUDE_LINE_NUMBERS = 0x0002;
|
||||
|
||||
public static void main(String[] argv) throws Exception {
|
||||
@@ -63,20 +68,20 @@ public class ClassPrinter extends ClassVisitor implements Opcodes {
|
||||
public static void print(byte[] bytes) {
|
||||
print(bytes, true);
|
||||
}
|
||||
|
||||
|
||||
public static void print(byte[] bytes, int includeFlags) {
|
||||
ClassReader reader = new ClassReader(bytes);
|
||||
reader.accept(new ClassPrinter(System.out, includeFlags), 0);
|
||||
}
|
||||
|
||||
|
||||
public static void print(byte[] bytes, boolean includeBytecode) {
|
||||
ClassReader reader = new ClassReader(bytes);
|
||||
reader.accept(new ClassPrinter(System.out, includeBytecode?INCLUDE_BYTECODE:0), 0);
|
||||
reader.accept(new ClassPrinter(System.out, includeBytecode ? INCLUDE_BYTECODE : 0), 0);
|
||||
}
|
||||
|
||||
public static void print(PrintStream printStream, byte[] bytes, boolean includeBytecode) {
|
||||
ClassReader reader = new ClassReader(bytes);
|
||||
reader.accept(new ClassPrinter(printStream, includeBytecode?INCLUDE_BYTECODE:0), 0);
|
||||
reader.accept(new ClassPrinter(printStream, includeBytecode ? INCLUDE_BYTECODE : 0), 0);
|
||||
}
|
||||
|
||||
public static void print(String message, byte[] bytes, boolean includeBytecode) {
|
||||
@@ -248,9 +253,10 @@ public class ClassPrinter extends ClassVisitor implements Opcodes {
|
||||
|
||||
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
|
||||
StringBuilder s = new StringBuilder();
|
||||
s.append("METHOD: " + toHex(access, 4) + "(" + toAccessForMember(access) + ") " + name + desc + " " + fromArray(exceptions));
|
||||
s.append("METHOD: " + toHex(access, 4) + "(" + toAccessForMember(access) + ") " + name + desc + " "
|
||||
+ fromArray(exceptions));
|
||||
destination.println(s.toString().trim());
|
||||
return (includeFlags&INCLUDE_BYTECODE)!=0 ? new MethodPrinter(destination,includeFlags) : null;
|
||||
return (includeFlags & INCLUDE_BYTECODE) != 0 ? new MethodPrinter(destination, includeFlags) : null;
|
||||
}
|
||||
|
||||
private String fromArray(Object[] os) {
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springsource.loaded.test.infra;
|
||||
|
||||
import java.io.PrintStream;
|
||||
@@ -34,7 +35,9 @@ import org.springsource.loaded.Utils;
|
||||
public class MethodPrinter extends MethodVisitor implements Opcodes {
|
||||
|
||||
PrintStream to;
|
||||
|
||||
int includeFlags;
|
||||
|
||||
List<Label> labels = new ArrayList<Label>();
|
||||
|
||||
private String toString(Label label) {
|
||||
@@ -45,9 +48,9 @@ public class MethodPrinter extends MethodVisitor implements Opcodes {
|
||||
labels.add(label);
|
||||
return "L" + labels.indexOf(label);
|
||||
}
|
||||
|
||||
|
||||
public MethodPrinter(PrintStream destination) {
|
||||
this(destination,0);
|
||||
this(destination, 0);
|
||||
}
|
||||
|
||||
public MethodPrinter(PrintStream destination, int includeFlags) {
|
||||
@@ -59,27 +62,31 @@ public class MethodPrinter extends MethodVisitor implements Opcodes {
|
||||
public void visitCode() {
|
||||
to.print(" CODE\n");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) {
|
||||
to.println(" INVOKEDYNAMIC " + name+"."+desc+" bsm="+toString(bsm));
|
||||
to.println(" INVOKEDYNAMIC " + name + "." + desc + " bsm=" + toString(bsm));
|
||||
}
|
||||
|
||||
private String toString(Handle bsm) {
|
||||
return "#"+bsm.getTag()+" "+bsm.getOwner()+"."+bsm.getName()+bsm.getDesc();
|
||||
return "#" + bsm.getTag() + " " + bsm.getOwner() + "." + bsm.getName() + bsm.getDesc();
|
||||
}
|
||||
|
||||
// TODO include 'itf' flag in output (maybe only if true)
|
||||
public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
|
||||
if (opcode == Opcodes.INVOKESTATIC) {
|
||||
to.println(" INVOKESTATIC " + owner + "." + name + desc);
|
||||
} else if (opcode == Opcodes.INVOKESPECIAL) {
|
||||
}
|
||||
else if (opcode == Opcodes.INVOKESPECIAL) {
|
||||
to.println(" INVOKESPECIAL " + owner + "." + name + desc);
|
||||
} else if (opcode == Opcodes.INVOKEVIRTUAL) {
|
||||
}
|
||||
else if (opcode == Opcodes.INVOKEVIRTUAL) {
|
||||
to.println(" INVOKEVIRTUAL " + owner + "." + name + desc);
|
||||
} else if (opcode == Opcodes.INVOKEINTERFACE) {
|
||||
}
|
||||
else if (opcode == Opcodes.INVOKEINTERFACE) {
|
||||
to.println(" INVOKEINTERFACE " + owner + "." + name + desc);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
throw new IllegalStateException(":" + opcode);
|
||||
}
|
||||
}
|
||||
@@ -126,7 +133,7 @@ public class MethodPrinter extends MethodVisitor implements Opcodes {
|
||||
}
|
||||
|
||||
public void visitAttribute(Attribute attr) {
|
||||
to.println(" ATTRIBUTE: "+attr);
|
||||
to.println(" ATTRIBUTE: " + attr);
|
||||
}
|
||||
|
||||
public void visitEnd() {
|
||||
@@ -135,13 +142,17 @@ public class MethodPrinter extends MethodVisitor implements Opcodes {
|
||||
public void visitFieldInsn(int opcode, String owner, String name, String desc) {
|
||||
if (opcode == Opcodes.GETSTATIC) {
|
||||
to.println(" GETSTATIC " + owner + "." + name + " " + desc);
|
||||
} else if (opcode == Opcodes.PUTSTATIC) {
|
||||
}
|
||||
else if (opcode == Opcodes.PUTSTATIC) {
|
||||
to.println(" PUTSTATIC " + owner + "." + name + " " + desc);
|
||||
} else if (opcode == Opcodes.GETFIELD) {
|
||||
}
|
||||
else if (opcode == Opcodes.GETFIELD) {
|
||||
to.println(" GETFIELD " + owner + "." + name + " " + desc);
|
||||
} else if (opcode == Opcodes.PUTFIELD) {
|
||||
}
|
||||
else if (opcode == Opcodes.PUTFIELD) {
|
||||
to.println(" PUTFIELD " + owner + "." + name + " " + desc);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
throw new IllegalStateException(":" + opcode);
|
||||
}
|
||||
}
|
||||
@@ -173,8 +184,8 @@ public class MethodPrinter extends MethodVisitor implements Opcodes {
|
||||
}
|
||||
|
||||
public void visitLineNumber(int line, Label start) {
|
||||
if ((includeFlags & ClassPrinter.INCLUDE_LINE_NUMBERS)!=0) {
|
||||
to.println(" LINE:"+line);
|
||||
if ((includeFlags & ClassPrinter.INCLUDE_LINE_NUMBERS) != 0) {
|
||||
to.println(" LINE:" + line);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,13 +214,17 @@ public class MethodPrinter extends MethodVisitor implements Opcodes {
|
||||
public void visitTypeInsn(int opcode, String type) {
|
||||
if (opcode == Opcodes.NEW) { // 187
|
||||
to.println(" NEW " + type);
|
||||
} else if (opcode == Opcodes.ANEWARRAY) { // 189
|
||||
}
|
||||
else if (opcode == Opcodes.ANEWARRAY) { // 189
|
||||
to.println(" ANEWARRAY " + type);
|
||||
} else if (opcode == Opcodes.CHECKCAST) { // 192
|
||||
}
|
||||
else if (opcode == Opcodes.CHECKCAST) { // 192
|
||||
to.println(" CHECKCAST " + type);
|
||||
} else if (opcode == Opcodes.INSTANCEOF) { // 193
|
||||
}
|
||||
else if (opcode == Opcodes.INSTANCEOF) { // 193
|
||||
to.println(" INSTANCEOF " + type);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
throw new IllegalStateException(":" + opcode);
|
||||
}
|
||||
}
|
||||
@@ -217,23 +232,31 @@ public class MethodPrinter extends MethodVisitor implements Opcodes {
|
||||
public void visitVarInsn(int opcode, int var) {
|
||||
if (opcode == Opcodes.ALOAD) {
|
||||
to.println(" ALOAD " + var);
|
||||
} else if (opcode == Opcodes.ASTORE) {
|
||||
}
|
||||
else if (opcode == Opcodes.ASTORE) {
|
||||
to.println(" ASTORE " + var);
|
||||
} else if (opcode == Opcodes.ILOAD) {
|
||||
}
|
||||
else if (opcode == Opcodes.ILOAD) {
|
||||
to.println(" ILOAD " + var);
|
||||
} else if (opcode == FLOAD) {
|
||||
}
|
||||
else if (opcode == FLOAD) {
|
||||
to.println(" FLOAD " + var);
|
||||
} else if (opcode == LLOAD) {
|
||||
}
|
||||
else if (opcode == LLOAD) {
|
||||
to.println(" LLOAD " + var);
|
||||
} else if (opcode == DLOAD) {
|
||||
}
|
||||
else if (opcode == DLOAD) {
|
||||
to.println(" DLOAD " + var);
|
||||
} else if (opcode == ISTORE) {
|
||||
}
|
||||
else if (opcode == ISTORE) {
|
||||
to.println(" ISTORE " + var);
|
||||
} else if (opcode == LSTORE) {
|
||||
}
|
||||
else if (opcode == LSTORE) {
|
||||
to.println(" LSTORE " + var);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
throw new IllegalStateException(":" + opcode);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user