move to springloaded

This commit is contained in:
Rob Winch
2014-01-16 21:49:54 -06:00
parent 416ce6a3bc
commit 5361830d56
212 changed files with 5 additions and 7 deletions

View File

@@ -0,0 +1,10 @@
Manifest-Version: 1.0
Specification-Title: SpringLoaded Agent
Specification-Version: 1.0.0
Specification-Vendor: SpringSource
Implementation-Title: org.springsource.loaded
Implementation-Version: 1.0.0
Implementation-Vendor: SpringSource
Premain-Class: org.springsource.loaded.agent.SpringLoadedAgent
Agent-Class: org.springsource.loaded.agent.SpringLoadedAgent
Can-Redefine-Classes: true

View File

@@ -0,0 +1,116 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded;
import java.lang.reflect.Modifier;
/**
* Simple implementation of Member which could represent a method, field or constructor.
*
* @author Andy Clement
* @since 0.5.0
*/
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) {
this.modifiers = modifiers;
this.name = name;
this.descriptor = descriptor;
this.signature = signature;
this.isPrivate = Modifier.isPrivate(modifiers);
}
/**
* @return the name of the member
*/
public final String getName() {
return name;
}
/**
* @return the member descriptor. methods/constructors: "()Ljava/lang/String;" fields: "Ljava/lang/String;"
*/
public final String getDescriptor() {
return descriptor;
}
/**
* @return the generics related signature. May be null if this method is non-generic.
*/
public String getGenericSignature() {
return signature;
}
/**
* @return the modifiers of the member
*/
public final int getModifiers() {
return modifiers;
}
/**
* @return the allocated ID for this member
*/
public final int getId() {
if (id == -1) {
throw new IllegalStateException("id not yet allocated");
}
return id;
}
/**
* @param id the id number to assign to this member for later quick reference.
*/
public final void setId(int id) {
this.id = id;
}
// helpers
public final boolean isStatic() {
return Modifier.isStatic(getModifiers());
}
public final boolean isFinal() {
return Modifier.isFinal(getModifiers());
}
public final boolean isPrivate() {
return isPrivate;
}
public final boolean isProtected() {
return Modifier.isProtected(getModifiers());
}
public final boolean isPublic() {
return Modifier.isPublic(getModifiers());
}
public boolean isPrivateStaticFinal() {
return (modifiers & ACC_PRIVATE_STATIC_FINAL) != 0;
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded;
/**
* Represents '*' type pattern.
*
* @author Andy Clement
* @since 0.5.0
*/
public class AnyTypePattern extends TypePattern {
public AnyTypePattern() {
}
protected boolean internalMatches(String input) {
return true;
}
public String toString() {
return "text:*";
}
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded;
/**
*
* @author Andy Clement
* @since 0.8.2
*/
public class Asserts {
public static boolean assertNotDotted(String name) {
if (name.indexOf('.') != -1) {
throw new IllegalStateException("Did not expect a dotted name " + name);
}
return true;
}
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded;
// TODO [moredoc]
/**
* @author Andy Clement
* @since 0.5.0
*/
// marker for generated ctors
public class C {
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded;
import java.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).
*/
public class ChildClassLoader extends URLClassLoader {
private static URL[] NO_URLS = new URL[0];
private int definedCount = 0;
public ChildClassLoader(ClassLoader classloader) {
super(NO_URLS, classloader);
}
public Class<?> defineClass(String name, byte[] bytes) {
definedCount++;
return super.defineClass(name, bytes, 0, bytes.length);
}
public int getDefinedCount() {
return definedCount;
}
}

View File

@@ -0,0 +1,252 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded;
import java.util.HashMap;
import java.util.Map;
import org.objectweb.asm.ClassAdapter;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodAdapter;
import org.objectweb.asm.MethodVisitor;
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.
*
* @author Andy Clement
* @since 0.5.0
*/
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.
*
* @param dottedNewName dotted name, e.g. com.foo.Bar
* @param classbytes the bytecode for the class to be renamed
* @param retargets retarget rules for references, of the form "a.b:b.a","c.d:d.c"
* @return bytecode for the modified class
*/
public static byte[] rename(String dottedNewName, byte[] classbytes, String... retargets) {
ClassReader fileReader = new ClassReader(classbytes);
RenameAdapter renameAdapter = new RenameAdapter(dottedNewName, retargets);
fileReader.accept(renameAdapter, 0);
byte[] renamed = renameAdapter.getBytes();
return renamed;
}
static class RenameAdapter extends ClassAdapter 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(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('.', '/'));
}
}
}
public byte[] getBytes() {
return cw.toByteArray();
}
private String retargetIfNecessary(String string) {
String value = retargets.get(string);
return value == null ? string : value;
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
oldname = name;
if (superName != null) {
superName = retargetIfNecessary(superName);
}
if (interfaces != null) {
for (int i = 0; i < interfaces.length; i++) {
interfaces[i] = retargetIfNecessary(interfaces[i]);
}
}
super.visit(version, access, newname, signature, superName, interfaces);
}
@Override
public void visitInnerClass(String name, String outername, String innerName, int access) {
super.visitInnerClass(renameRetargetIfNecessary(name), renameRetargetIfNecessary(outername), renameRetargetIfNecessary(innerName), access);
}
private String renameRetargetIfNecessary(String string) {
String value = retargets.get(string);
if (value!=null) {
return value;
}
if (string.indexOf(oldname) != -1) {
return string.replace(oldname, newname);
}
return string;
}
@Override
public MethodVisitor visitMethod(int flags, String name, String descriptor, String signature, String[] exceptions) {
if (descriptor.indexOf(oldname) != -1) {
descriptor = descriptor.replace(oldname, newname);
} else {
for (String s : retargets.keySet()) {
if (descriptor.indexOf(s) != -1) {
descriptor = descriptor.replace(s, retargets.get(s));
}
}
}
MethodVisitor mv = super.visitMethod(flags, name, descriptor, signature, exceptions);
return new RenameMethodAdapter(mv, oldname, newname);
}
@Override
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
if (desc.indexOf(oldname) != -1) {
desc = desc.replace(oldname, newname);
} else {
for (String s : retargets.keySet()) {
if (desc.indexOf(s) != -1) {
desc = desc.replace(s, retargets.get(s));
}
}
}
return super.visitField(access, name, desc, signature, value);
}
class RenameMethodAdapter extends MethodAdapter implements Opcodes {
String oldname;
String newname;
public RenameMethodAdapter(MethodVisitor mv, String oldname, String newname) {
super(mv);
this.oldname = oldname;
this.newname = newname;
}
public void visitFieldInsn(int opcode, String owner, String name, String desc) {
if (owner.equals(oldname)) {
owner = newname;
} else {
String retarget = retargets.get(owner);
if (retarget != null) {
owner = retarget;
}
}
if (desc.indexOf(oldname) != -1) {
desc = desc.replace(oldname, newname);
} else {
desc = checkIfShouldBeRewritten(desc);
}
mv.visitFieldInsn(opcode, owner, name, desc);
}
public void visitTypeInsn(int opcode, String type) {
if (type.equals(oldname)) {
type = newname;
} else {
String retarget = retargets.get(type);
if (retarget != null) {
type = retarget;
} else {
if (type.startsWith("[")) {
if (type.indexOf(oldname) != -1) {
type = type.replaceFirst(oldname, newname);
}
}
}
}
mv.visitTypeInsn(opcode, type);
}
@Override
public void visitLdcInsn(Object 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 {
mv.visitLdcInsn(obj);
}
} else if (obj instanceof String) {
String s = (String) obj;
String retarget = retargets.get(s.replace('.', '/'));
if (retarget != null) {
mv.visitLdcInsn(retarget.replace('/', '.'));
} else {
String oldnameDotted = oldname.replace('/', '.');
if (s.equals(oldnameDotted)) {
String nname = newname.replace('/', '.');
mv.visitLdcInsn(nname);
return;
} else if (s.startsWith("[")) {
// might be array of oldname
if (s.indexOf(oldnameDotted) != -1) {
mv.visitLdcInsn(s.replaceFirst(oldnameDotted, newname.replace('/', '.')));
return;
}
}
mv.visitLdcInsn(obj);
}
} else {
mv.visitLdcInsn(obj);
}
}
public void visitMethodInsn(int opcode, String owner, String name, String desc) {
if (owner.equals(oldname)) {
owner = newname;
} else {
owner = retargetIfNecessary(owner);
}
if (desc.indexOf(oldname) != -1) {
desc = desc.replace(oldname, newname);
} else {
desc = checkIfShouldBeRewritten(desc);
}
mv.visitMethodInsn(opcode, owner, name, desc);
}
private String checkIfShouldBeRewritten(String desc) {
for (String s : retargets.keySet()) {
if (desc.indexOf(s) != -1) {
desc = desc.replace(s, retargets.get(s));
}
}
return desc;
}
}
}
}

View File

@@ -0,0 +1,332 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
// TODO does not yet support the new constant pool entry types that come with Java 7
// 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.
*
* @author Andy Clement
* @since 0.7.3
*/
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
public static void main(String[] args) throws Exception {
// File[] fs = new File("./bin").listFiles();
// File[] fs = new File("../testdata-groovy/bin").listFiles();
// checkThemAll(fs);
// System.out.println("total=" + total / 1000000d);
}
// static long total = 0;
// private static void checkThemAll(File[] fs) throws Exception {
// for (File f : fs) {
// if (f.isDirectory()) {
// checkThemAll(f.listFiles());
// } else if (f.getName().endsWith(".class")) {
// System.out.println(f);
// byte[] data = Utils.loadFromStream(new FileInputStream(f));
// long stime = System.nanoTime();
// List<String> ls = getReferencedClasses(data);
// // total += (System.nanoTime() - stime);
// System.out.println(ls);
// }
// }
// }
// ClassFile {
// u4 magic;
// u2 minor_version;
// u2 major_version;
// u2 constant_pool_count;
// cp_info constant_pool[constant_pool_count-1];
// u2 access_flags;
// u2 this_class;
// u2 super_class;
// u2 interfaces_count;
// u2 interfaces[interfaces_count];
// u2 fields_count;
// field_info fields[fields_count];
// u2 methods_count;
// method_info methods[methods_count];
// u2 attributes_count;
// attribute_info attributes[attributes_count];
// }
static List<String> getReferencedClasses(byte[] bytes) {
return new ConstantPoolChecker(bytes).referencedClasses;
}
// 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 ConstantPoolChecker(byte[] bytes) {
readConstantPool(bytes);
computeReferences();
}
public void computeReferences() {
for (int i = 0; i < cpsize; i++) {
switch (type[i]) {
case CONSTANT_Class:
int classindex = ((Integer) cpdata[i]);
String classname = (String) cpdata[classindex];
if (classname == null) {
throw new IllegalStateException();
}
referencedClasses.add(classname);
break;
// private final static byte CONSTANT_Utf8 = 1;
// private final static byte CONSTANT_Integer = 3;
// private final static byte CONSTANT_Float = 4;
// private final static byte CONSTANT_Long = 5;
// private final static byte CONSTANT_Double = 6;
// private final static byte CONSTANT_String = 8;
// private final static byte CONSTANT_Fieldref = 9;
// private final static byte CONSTANT_Methodref = 10;
// private final static byte CONSTANT_InterfaceMethodref = 11;
// private final static byte CONSTANT_NameAndType = 12;
}
}
}
public void readConstantPool(byte[] bytes) {
try {
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
DataInputStream dis = new DataInputStream(bais);
int magic = dis.readInt(); // magic 0xCAFEBABE
if (magic != 0xCAFEBABE) {
throw new IllegalStateException("not bytecode, magic was 0x" + Integer.toString(magic, 16));
}
dis.skip(4);
// dis.readShort(); // minor
// dis.readShort(); // major
cpsize = dis.readShort();
if (DEBUG) {
System.out.println("Constant Pool Size =" + cpsize);
}
cpdata = new Object[cpsize];
type = new int[cpsize];
// int max = cpsize - 1;
for (int cpentry = 1; cpentry < cpsize; cpentry++) {
boolean doubleSlot = processConstantPoolEntry(cpentry, dis);
if (doubleSlot) {
cpentry++;
}
}
} catch (Exception e) {
throw new IllegalStateException("Unexpected problem processing bytes for class", e);
}
}
private boolean processConstantPoolEntry(int index, DataInputStream dis) throws IOException {
byte b = dis.readByte();
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();
if (DEBUG) {
System.out.println(index + ":INTEGER[" + i + "]");
}
} else {
dis.skip(4);
}
break;
case CONSTANT_Float:
// CONSTANT_Float_info {
// u1 tag;
// u4 bytes;
// }
if (DEBUG) {
float f = dis.readFloat();
if (DEBUG) {
System.out.println(index + ":FLOAT[" + f + "]");
}
} else {
dis.skip(4);
}
break;
case CONSTANT_Long:
// CONSTANT_Long_info {
// u1 tag;
// u4 high_bytes;
// u4 low_bytes;
// }
if (DEBUG) {
long l = dis.readLong();
if (DEBUG) {
System.out.println(index + ":LONG[" + l + "]");
}
} else {
dis.skip(8);
}
return true;
case CONSTANT_Double:
// CONSTANT_Double_info {
// u1 tag;
// u4 high_bytes;
// u4 low_bytes;
// }
if (DEBUG) {
double d = dis.readDouble();
if (DEBUG) {
System.out.println(index + ":DOUBLE[" + d + "]");
}
} else {
dis.skip(8);
}
return true;
case CONSTANT_Class:
// CONSTANT_Class_info {
// u1 tag;
// u2 name_index;
// }
cpdata[index] = (int) dis.readShort();
if (DEBUG) {
System.out.println(index + ":CLASS[name_index=" + cpdata[index] + "]");
}
break;
case CONSTANT_String:
// CONSTANT_String_info {
// u1 tag;
// u2 string_index;
// }
if (DEBUG) {
cpdata[index] = (int) dis.readShort();
if (DEBUG) {
System.out.println(index + ":STRING[string_index=" + cpdata[index] + "]");
}
} else {
dis.skip(2);
}
break;
case CONSTANT_Fieldref:
// CONSTANT_Fieldref_info {
// u1 tag;
// u2 class_index;
// u2 name_and_type_index;
// }
if (DEBUG) {
cpdata[index] = new int[] { dis.readShort(), dis.readShort() };
if (DEBUG) {
System.out.println(index + ":FIELDREF[class_index=" + ((int[]) cpdata[index])[0] + ",name_and_type_index="
+ ((int[]) cpdata[index])[1] + "]");
}
} else {
dis.skip(4);
}
break;
case CONSTANT_Methodref:
// CONSTANT_Methodref_info {
// u1 tag;
// u2 class_index;
// u2 name_and_type_index;
// }
if (DEBUG) {
cpdata[index] = new int[] { dis.readShort(), dis.readShort() };
if (DEBUG) {
System.out.println(index + ":METHODREF[class_index=" + ((int[]) cpdata[index])[0] + ",name_and_type_index="
+ ((int[]) cpdata[index])[1] + "]");
}
} else {
dis.skip(4);
}
break;
case CONSTANT_InterfaceMethodref:
// CONSTANT_InterfaceMethodref_info {
// u1 tag;
// u2 class_index;
// u2 name_and_type_index;
// }
if (DEBUG) {
cpdata[index] = new int[] { dis.readShort(), dis.readShort() };
if (DEBUG) {
System.out.println(index + ":INTERFACEMETHODREF[class_index=" + ((int[]) cpdata[index])[0]
+ ",name_and_type_index=" + ((int[]) cpdata[index])[1] + "]");
}
} else {
dis.skip(4);
}
break;
case CONSTANT_NameAndType:
// The CONSTANT_NameAndType_info structure is used to represent a field or method, without indicating which class or interface type it belongs to:
// CONSTANT_NameAndType_info {
// u1 tag;
// u2 name_index;
// u2 descriptor_index;
// }
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;
}
}

View File

@@ -0,0 +1,339 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
// TODO try to recall why I created ConstantPoolChecker2, what was up with ConstantPoolChecker?
// http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html
/**
* Enables us to check things quickly in the constant pool. This version accumulates the class references and the method references,
* for classes that start with 'j' (we want to catch: java/lang). It skips everything it can and the end result is a list of class
* references and a list of method references. The former look like this 'a/b/C' whilst the latter look like this
* 'java/lang/Foo.bar' (the descriptor for the method is not included). Interface methods are skipped.
*
* @author Andy Clement
* @since 0.7.3
*/
public class ConstantPoolChecker2 {
private static final boolean DEBUG = false;
private final static byte CONSTANT_Utf8 = 1;
private final static byte CONSTANT_Integer = 3;
private final static byte CONSTANT_Float = 4;
private final static byte CONSTANT_Long = 5;
private final static byte CONSTANT_Double = 6;
private final static byte CONSTANT_Class = 7;
private final static byte CONSTANT_String = 8;
private final static byte CONSTANT_Fieldref = 9;
private final static byte CONSTANT_Methodref = 10;
private final static byte CONSTANT_InterfaceMethodref = 11;
private final static byte CONSTANT_NameAndType = 12;
// Test entry point just goes through all the code in the bin folder
public static void main(String[] args) throws Exception {
File[] fs = new File("./bin").listFiles();
// File[] fs = new File("../testdata-groovy/bin").listFiles();
// File[] fs = new File("/Users/aclement/grailsreload/foo/target/classes").listFiles();
checkThemAll(fs);
System.out.println("total=" + total / 1000000d);
}
private static void checkThemAll(File[] fs) throws Exception {
for (File f : fs) {
if (f.isDirectory()) {
checkThemAll(f.listFiles());
} else if (f.getName().endsWith(".class")) {
System.out.println(f);
byte[] data = Utils.loadFromStream(new FileInputStream(f));
long stime = System.nanoTime();
References refs = getReferences(data);
total += (System.nanoTime() - stime);
System.out.println(refs.referencedClasses);
System.out.println(refs.referencedMethods);
}
}
}
static long total = 0;
// ClassFile {
// u4 magic;
// u2 minor_version;
// u2 major_version;
// u2 constant_pool_count;
// cp_info constant_pool[constant_pool_count-1];
// u2 access_flags;
// u2 this_class;
// u2 super_class;
// u2 interfaces_count;
// u2 interfaces[interfaces_count];
// u2 fields_count;
// field_info fields[fields_count];
// u2 methods_count;
// method_info methods[methods_count];
// u2 attributes_count;
// attribute_info attributes[attributes_count];
// }
static References getReferences(byte[] bytes) {
ConstantPoolChecker2 cpc2 = new ConstantPoolChecker2(bytes);
return new References(cpc2.slashedclassname, cpc2.referencedClasses, cpc2.referencedMethods);
}
static class References {
String slashedClassName;
List<String> referencedClasses;
List<String> referencedMethods;
References(String slashedClassName, List<String> rc, List<String> rm) {
this.slashedClassName = slashedClassName;
this.referencedClasses = rc;
this.referencedMethods = rm;
}
}
// Filled with strings and int[]
private Object[] cpdata;
private int cpsize;
private int[] type;
// Does not need to be a set as there are no dups in the ConstantPool (for a class from a decent compiler...)
private List<String> referencedClasses = new ArrayList<String>();
private List<String> referencedMethods = new ArrayList<String>();
private String slashedclassname;
private ConstantPoolChecker2(byte[] bytes) {
readConstantPool(bytes);
computeReferences();
}
public void computeReferences() {
for (int i = 0; i < cpsize; i++) {
switch (type[i]) {
case CONSTANT_Class:
int classindex = ((Integer) cpdata[i]);
String classname = (String) cpdata[classindex];
if (classname == null) {
throw new IllegalStateException();
}
referencedClasses.add(classname);
break;
case CONSTANT_Methodref:
int[] indexes = (int[]) cpdata[i];
int classindex2 = indexes[0];
int nameAndTypeIndex = indexes[1];
StringBuilder s = new StringBuilder();
String theClassName = (String) cpdata[(Integer) cpdata[classindex2]];
if (theClassName.charAt(0) == 'j') {
s.append(theClassName);
s.append(".");
s.append((String) cpdata[(Integer) cpdata[nameAndTypeIndex]]);
referencedMethods.add(s.toString());
}
break;
// private final static byte CONSTANT_Utf8 = 1;
// private final static byte CONSTANT_Integer = 3;
// private final static byte CONSTANT_Float = 4;
// private final static byte CONSTANT_Long = 5;
// private final static byte CONSTANT_Double = 6;
// private final static byte CONSTANT_String = 8;
// private final static byte CONSTANT_Fieldref = 9;
// private final static byte CONSTANT_InterfaceMethodref = 11;
// private final static byte CONSTANT_NameAndType = 12;
}
}
}
public void readConstantPool(byte[] bytes) {
try {
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
DataInputStream dis = new DataInputStream(bais);
int magic = dis.readInt(); // magic 0xCAFEBABE
if (magic != 0xCAFEBABE) {
throw new IllegalStateException("not bytecode, magic was 0x" + Integer.toString(magic, 16));
}
dis.skip(4); // skip minor and major versions
cpsize = dis.readShort();
if (DEBUG) {
System.out.println("Constant Pool Size =" + cpsize);
}
cpdata = new Object[cpsize];
type = new int[cpsize];
for (int cpentry = 1; cpentry < cpsize; cpentry++) {
boolean doubleSlot = processConstantPoolEntry(cpentry, dis);
if (doubleSlot) {
cpentry++;
}
}
dis.skip(2); // access flags
int thisclassname = dis.readShort();
int classindex = ((Integer) cpdata[thisclassname]);
slashedclassname = (String) cpdata[classindex];
} catch (Exception e) {
throw new IllegalStateException("Unexpected problem processing bytes for class", e);
}
}
private boolean processConstantPoolEntry(int index, DataInputStream dis) throws IOException {
byte b = dis.readByte();
switch (b) {
case CONSTANT_Utf8:
// CONSTANT_Utf8_info { u1 tag; u2 length; u1 bytes[length]; }
cpdata[index] = dis.readUTF();
// type[index] = b;
if (DEBUG) {
System.out.println(index + ":UTF8[" + cpdata[index] + "]");
}
break;
case CONSTANT_Integer:
// CONSTANT_Integer_info { u1 tag; u4 bytes; }
if (DEBUG) {
int i = dis.readInt();
if (DEBUG) {
System.out.println(index + ":INTEGER[" + i + "]");
}
} else {
dis.skip(4);
}
break;
case CONSTANT_Float:
// CONSTANT_Float_info { u1 tag; u4 bytes; }
if (DEBUG) {
float f = dis.readFloat();
if (DEBUG) {
System.out.println(index + ":FLOAT[" + f + "]");
}
} else {
dis.skip(4);
}
break;
case CONSTANT_Long:
// CONSTANT_Long_info {
// u1 tag;
// u4 high_bytes;
// u4 low_bytes;
// }
if (DEBUG) {
long l = dis.readLong();
if (DEBUG) {
System.out.println(index + ":LONG[" + l + "]");
}
} else {
dis.skip(8);
}
return true;
case CONSTANT_Double:
// CONSTANT_Double_info {
// u1 tag;
// u4 high_bytes;
// u4 low_bytes;
// }
if (DEBUG) {
double d = dis.readDouble();
if (DEBUG) {
System.out.println(index + ":DOUBLE[" + d + "]");
}
} else {
dis.skip(8);
}
return true;
case CONSTANT_Class:
// CONSTANT_Class_info { u1 tag; u2 name_index; }
type[index] = b;
cpdata[index] = (int) dis.readShort();
if (DEBUG) {
System.out.println(index + ":CLASS[name_index=" + cpdata[index] + "]");
}
break;
case CONSTANT_String:
// CONSTANT_String_info { u1 tag; u2 string_index; }
if (DEBUG) {
cpdata[index] = (int) dis.readShort();
if (DEBUG) {
System.out.println(index + ":STRING[string_index=" + cpdata[index] + "]");
}
} else {
dis.skip(2);
}
break;
case CONSTANT_Fieldref:
// CONSTANT_Fieldref_info { u1 tag; u2 class_index; u2 name_and_type_index; }
if (DEBUG) {
cpdata[index] = new int[] { dis.readShort(), dis.readShort() };
if (DEBUG) {
System.out.println(index + ":FIELDREF[class_index=" + ((int[]) cpdata[index])[0] + ",name_and_type_index="
+ ((int[]) cpdata[index])[1] + "]");
}
} else {
dis.skip(4);
}
break;
case CONSTANT_Methodref:
// CONSTANT_Methodref_info { u1 tag; u2 class_index; u2 name_and_type_index; }
type[index] = b;
//if (DEBUG) {
cpdata[index] = new int[] { dis.readShort(), dis.readShort() };
if (DEBUG) {
System.out.println(index + ":METHODREF[class_index=" + ((int[]) cpdata[index])[0] + ",name_and_type_index="
+ ((int[]) cpdata[index])[1] + "]");
}
// } else {
// dis.skip(4);
// }
break;
case CONSTANT_InterfaceMethodref:
// CONSTANT_InterfaceMethodref_info {
// u1 tag;
// u2 class_index;
// u2 name_and_type_index;
// }
if (DEBUG) {
cpdata[index] = new int[] { dis.readShort(), dis.readShort() };
if (DEBUG) {
System.out.println(index + ":INTERFACEMETHODREF[class_index=" + ((int[]) cpdata[index])[0]
+ ",name_and_type_index=" + ((int[]) cpdata[index])[1] + "]");
}
} else {
dis.skip(4);
}
break;
case CONSTANT_NameAndType:
// The CONSTANT_NameAndType_info structure is used to represent a field or method, without indicating which class or interface type it belongs to:
// CONSTANT_NameAndType_info { u1 tag; u2 name_index; u2 descriptor_index; }
// type[index] = b;
cpdata[index] = (int) dis.readShort();// new int[] { dis.readShort(), dis.readShort() };
dis.skip(2); // skip the descriptor for now
if (DEBUG) {
System.out.println(index + ":NAMEANDTYPE[name_index=" + ((int[]) cpdata[index])[0] + ",descriptor_index="
+ ((int[]) cpdata[index])[1] + "]");
}
break;
default:
throw new IllegalStateException("Entry: " + index + " " + Byte.toString(b));
}
return false;
}
}

View File

@@ -0,0 +1,153 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.regex.Pattern;
import org.objectweb.asm.Opcodes;
/**
* Common constants used throughout Spring Loaded.
*
* @author Andy Clement
* @since 0.5.0
*/
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";
// TODO change r$ to _sl or sl throughout?
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 mChangedForInvokeVirtualName = "ivicheck";
static String mChangedForInvokeSpecialName = "ispcheck";
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_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.
static Pattern executorClassNamePattern = Pattern.compile("\\$\\$E[0-9,a-z,A-Z]+$");
static final String jlObject = "java/lang/Object";
//
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;
// For rewritten reflection in system classes, these are used:
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;";
}

View File

@@ -0,0 +1,154 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodAdapter;
import org.objectweb.asm.MethodVisitor;
/**
* @author Andy Clement
* @since 0.5.0
*/
class ConstructorCopier extends MethodAdapter 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
// one being invoked due to some early object construction prior to the real INVOKESPECIAL running. By tracking
// 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(mv);
this.typeDescriptor = typeDescriptor;
this.suffix = suffix;
this.classname = classname;
}
@Override
public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) {
// 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 {
super.visitLocalVariable(name, desc, signature, start, end, index);
}
}
@Override
public void visitFieldInsn(final int opcode, final String owner, final String name, final String desc) {
super.visitFieldInsn(opcode, owner, name, desc);
}
@Override
public void visitTypeInsn(final int opcode, final String type) {
if (opcode == NEW) {
unitializedObjectsCount++;
}
super.visitTypeInsn(opcode, type);
}
@Override
public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc) {
// If this is an invokespecial, first determine if it is the one of interest (the one calling our super constructor)
if (opcode == INVOKESPECIAL && name.charAt(0) == '<') {
if (unitializedObjectsCount != 0) {
unitializedObjectsCount--;
} 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 {
// Need to replace this INVOKESPECIAL call.
String supertypename = typeDescriptor.getSupertypeName();
ReloadableType superRtype = typeDescriptor.getReloadableType().getTypeRegistry()
.getReloadableSuperType(supertypename);
if (superRtype == null) {
// supertype was not reloadable. This either means it really isn't (doesn't match what we consider reloadable)
// or it just hasn't been loaded yet.
// In a real scenario supertypes will get loaded first always and this can't happen (the latter case) - it happens in tests
// because they don't actively load all their bits and pieces in a hierarchical way. Given that on a reloadable boundary
// the magic ctors are setup to call a default ctor, we can assume that above the boundary the object has been initialized.
// this means we don't need to call a super __init__ or __execute...
if (typeDescriptor.getReloadableType().getTypeRegistry().isReloadableTypeName(supertypename)) {
superRtype = typeDescriptor.getReloadableType().getTypeRegistry()
.getReloadableSuperType(supertypename);
throw new IllegalStateException("The supertype " + supertypename.replace('/', '.')
+ " has not been loaded as a reloadabletype");
}
Utils.insertPopsForAllParameters(mv, desc);
mv.visitInsn(POP); // pop 'this'
} else {
// Check the original form of the supertype for a constructor to call
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
Utils.collapseStackToArray(mv, desc);
// now the stack is the instance then the params
mv.visitInsn(SWAP);
mv.visitInsn(DUP_X1);
// no stack is instance then params then instance
mv.visitLdcInsn("<init>" + desc);
mv.visitMethodInsn(INVOKESPECIAL, typeDescriptor.getSupertypeName(), mDynamicDispatchName,
mDynamicDispatchDescriptor);
mv.visitInsn(POP);
} else {
// it did exist in the original, so there will be parallel constructor
mv.visitMethodInsn(INVOKESPECIAL, typeDescriptor.getSupertypeName(), mInitializerName, desc);
}
}
}
state = postInvokeSpecial;
return;
}
}
}
// Is it a private method call?
// TODO r$ check here because we use invokespecial to avoid virtual dispatch on field changes...
if (opcode == INVOKESPECIAL && name.charAt(0) != '<' && owner.equals(classname) && !name.startsWith("r$")) {
// leaving the invokespecial alone will cause a verify error
String descriptor = Utils.insertExtraParameter(owner, desc);
super.visitMethodInsn(INVOKESTATIC, Utils.getExecutorName(classname, suffix), name, descriptor);
} else {
boolean done = false;
// TODO dup of code in method copier - can we refactor?
if (opcode == INVOKESTATIC) {
MethodMember mm = typeDescriptor.getByDescriptor(name, desc);
if (mm != null && mm.isPrivate()) {
super.visitMethodInsn(INVOKESTATIC, Utils.getExecutorName(classname, suffix), name, desc);
done = true;
}
}
if (!done) {
super.visitMethodInsn(opcode, owner, name, desc);
}
}
}
}

View File

@@ -0,0 +1,294 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.MethodNode;
/**
* Captures the information about the reloaded parts of a type that vary each time a new version is loaded.
*
* @author Andy Clement
* @since 0.5.0
*/
public class CurrentLiveVersion {
private static Logger log = Logger.getLogger(CurrentLiveVersion.class.getName());
// Which reloadable type this represents the live version of
final ReloadableType reloadableType;
// Type descriptor for this live version
final TypeDescriptor typeDescriptor;
// 'stamp' (i.e. suffix) for this version
final String versionstamp;
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 '"
+ versionstamp + "'");
}
this.reloadableType = reloadableType;
this.typeDescriptor = reloadableType.getTypeRegistry().getExtractor().extract(newbytedata, true);
this.versionstamp = versionstamp;
if (GlobalConfiguration.assertsOn) {
if (!this.typeDescriptor.getName().equals(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) : 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,
newbytedata);
if (GlobalConfiguration.classesToDump != null
&& GlobalConfiguration.classesToDump.contains(reloadableType.getSlashedName())) {
Utils.dump(Utils.getExecutorName(reloadableType.getName(), versionstamp).replace('.', '/'), this.executor);
}
if (!typeDescriptor.isInterface()) {
this.dispatcherName = Utils.getDispatcherName(reloadableType.getName(), versionstamp);
this.executorName = Utils.getExecutorName(reloadableType.getName(), versionstamp);
this.dispatcher = DispatcherBuilder.createFor(reloadableType, incrementalTypeDescriptor, versionstamp);
}
reloadableType.typeRegistry.checkChildClassLoader(reloadableType);
define();
}
/**
* 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;
haveLookedForStaticInitializer = false;
if (!typeDescriptor.isInterface()) {
try {
dispatcherClass = reloadableType.typeRegistry.defineClass(dispatcherName, dispatcher, false);
} 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 {
t.printStackTrace();
}
}
}
try {
executorClass = reloadableType.typeRegistry.defineClass(executorName, executor, false);
} 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 {
t.printStackTrace();
}
}
if (!typeDescriptor.isInterface()) {
try {
dispatcherInstance = dispatcherClass.newInstance();
} catch (InstantiationException e) {
throw new RuntimeException("Unable to build dispatcher class instance", e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Unable to build dispatcher class instance", e);
}
}
}
public MethodMember getReloadableMethod(String name, String descriptor) {
// Look through the methods on the latest loaded version and find the method we want
MethodMember[] methods = incrementalTypeDescriptor.getLatestTypeDescriptor().getMethods();
for (MethodMember rmethod : methods) {
if (rmethod.getName().equals(name)) {
if (descriptor.equals(rmethod.getDescriptor())) {
return rmethod;
}
}
}
return null;
}
// TODO should be caching the result in the MethodMember objects for speed
public Method getExecutorMethod(MethodMember methodMember) {
String executorDescriptor;
String name;
//What to search for:
if (methodMember.isConstructor()) {
name = Constants.mInitializerName;
} else {
name = methodMember.getName();
}
executorDescriptor = getExecutorDescriptor(methodMember);
//Search for it:
if (executorClass != null) {
Method[] executorMethods = executorClass.getDeclaredMethods();
for (Method executor : executorMethods) {
if (executor.getName().equals(name) && Type.getMethodDescriptor(executor).equals(executorDescriptor)) {
return executor;
}
}
}
return null;
}
private String getExecutorDescriptor(MethodMember methodMember) {
Type[] params = Type.getArgumentTypes(methodMember.getDescriptor());
Type[] newParametersArray = params;
if (!methodMember.isStatic()) {
newParametersArray = new Type[params.length + 1];
System.arraycopy(params, 0, newParametersArray, 1, params.length);
newParametersArray[0] = Type.getType(reloadableType.getClazz());
}
String executorDescriptor = Type.getMethodDescriptor(Type.getReturnType(methodMember.getDescriptor()), newParametersArray);
return executorDescriptor;
}
@Override
public String toString() {
return "CurrentLiveVersion [reloadableType=" + reloadableType + ", typeDescriptor=" + typeDescriptor + ", versionstamp="
+ versionstamp + ", dispatcherName=" + dispatcherName + ", executorName=" + executorName + "]";
}
public Class<?> getExecutorClass() {
return executorClass;
}
public String getVersionStamp() {
return versionstamp;
}
public Field getExecutorField(String name) throws SecurityException, NoSuchFieldException {
return executorClass.getDeclaredField(name);
}
public TypeDelta getTypeDelta() {
return typeDelta;
}
public void setTypeDelta(TypeDelta td) {
typeDelta = td;
}
public boolean hasClinit() {
return typeDescriptor.hasClinit();
}
public boolean hasConstructorChanged(String descriptor) {
MethodMember mm = typeDescriptor.getConstructor(descriptor);
return hasConstructorChanged(mm);
}
public boolean hasConstructorChanged(MethodMember mm) {
if (mm == null) {
return true;
}
// need to look at the delta
if (typeDelta.haveMethodsChangedOrBeenAddedOrRemoved()) {
if (typeDelta.haveMethodsChanged()) {
MethodDelta md = typeDelta.changedMethods.get(mm.name + mm.descriptor);
if (md != null) {
return true;
}
}
if (typeDelta.haveMethodsBeenAdded()) {
MethodNode mn = typeDelta.brandNewMethods.get(mm.name + mm.descriptor);
if (mn != null) {
return true;
}
}
if (typeDelta.haveMethodsBeenDeleted()) {
MethodNode mn = typeDelta.lostMethods.get(mm.name + mm.descriptor);
if (mn != null) {
return true;
}
}
}
return false;
}
// TODO can we speed this up?
public boolean hasConstructorChanged(int ctorId) {
// need to find the constructor that id is for
MethodMember mm = typeDescriptor.getConstructor(ctorId);
return hasConstructorChanged(mm);
}
public void clearClassloaderLinks() {
this.executorClass = null;
this.dispatcherClass = null;
}
public void reloadMostRecentDispatcherAndExecutor() {
define();
}
public Object getDispatcherInstance() {
// TODO Auto-generated method stub
return null;
}
public void runStaticInitializer() {
if (!haveLookedForStaticInitializer) {
try {
staticInitializer = this.getExecutorClass().getDeclaredMethod(Constants.mStaticInitializerName);
} catch (NoSuchMethodException e) {
// some types don't have a static initializer, that is OK
}
haveLookedForStaticInitializer = true;
}
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());
e.printStackTrace(); // TODO remove when happy
}
}
}
}

View File

@@ -0,0 +1,329 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded;
import java.util.ArrayList;
import java.util.List;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
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.
*
* @author Andy Clement
* @since 0.5.0
*/
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 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) {
ClassReader fileReader = new ClassReader(rtype.interfaceBytes);
DispatcherBuilderVisitor dispatcherVisitor = new DispatcherBuilderVisitor(rtype, newVersionTypeDescriptor, versionstamp);
fileReader.accept(dispatcherVisitor, 0);
return dispatcherVisitor.getBytes();
}
/**
* Whilst visiting the interface, the implementation is created.
*/
static class DispatcherBuilderVisitor implements ClassVisitor, Opcodes, Constants {
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) {
this.classname = rtype.getSlashedName();
this.typeDescriptor = typeDescriptor;
this.suffix = suffix;
this.rtype = rtype;
this.executorClassName = Utils.getExecutorName(classname, suffix);
}
public byte[] getBytes() {
return cw.toByteArray();
}
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" });
generateDefaultConstructor();
}
private void generateDefaultConstructor() {
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
mv.visitInsn(RETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
}
private void generateClinitDispatcher() {
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, mStaticInitializerName, "()V", null, null);
mv.visitCode();
mv.visitMethodInsn(INVOKESTATIC, executorClassName, mStaticInitializerName, "()V");
mv.visitInsn(RETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
}
public AnnotationVisitor visitAnnotation(String arg0, boolean arg1) {
return null;
}
public void visitAttribute(Attribute arg0) {
}
public void visitEnd() {
}
public FieldVisitor visitField(int arg0, String arg1, String arg2, String arg3, Object arg4) {
return null;
}
public void visitInnerClass(String arg0, String arg1, String arg2, int arg3) {
}
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>")) {
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.
*/
private void generateDynamicDispatchMethod(String name, String descriptor, String signature, String[] exceptions) {
final int indexDispatcherInstance = 0;
final int indexArgs = 1;
final int indexTarget = 2;
final int indexNameAndDescriptor = 3;
// 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);
mv.visitCode();
// Entries required here for all methods that exist in the new version but didn't exist in the original version
// There should be no entries for catchers
int maxStack = 0;
// Basically generate a long if..else sequence for each method
List<MethodMember> methods = new ArrayList<MethodMember>(typeDescriptor.getNewOrChangedMethods());
// these are added because we may be calling through the dynamic dispatcher if calling from an invokeinterface - the invokeinterface
// will call __execute on the interface, which is then implemented by the real class - but it may be that the
// actual type implementing the interface already implements that method - if the dispatcher doesn't recognize
// it then we may go bang
// System.out.println("Generating __execute in type " + classname);
for (MethodMember m : typeDescriptor.getOriginal().getMethods()) {
methods.add(m);
}
for (MethodMember method : methods) {
if (MethodMember.isCatcher(method)) { // for reason above, may also need to consider catchers here - what if an interface is changed to add a toString() method, for example
continue;
// would the implementation for a catcher call the super catcher?
}
// System.out.println("Generating handler for " + method.name);
String nameWithDescriptor = new StringBuilder(method.name).append(method.descriptor).toString();
// 2. Load the input name+descriptor and compare it with this method:
mv.visitVarInsn(ALOAD, 3);
mv.visitLdcInsn(nameWithDescriptor);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "equals", "(Ljava/lang/Object;)Z");
Label label = new Label();
mv.visitJumpInsn(IFEQ, label); // means if false
// 3. Generate the code that will call the method on the executor:
if (!method.isStatic()) {
mv.visitVarInsn(Opcodes.ALOAD, 2);
mv.visitTypeInsn(CHECKCAST, classname);
}
String callDescriptor = method.isStatic() ? method.descriptor : Utils.insertExtraParameter(classname,
method.descriptor);
int pcount = Utils.getParameterCount(method.descriptor);
if (pcount > maxStack) {
pcount = maxStack;
}
// 4. Unpack parameter array to fit the descriptor for that method
Utils.generateInstructionsToUnpackArrayAccordingToDescriptor(mv, method.descriptor, 1);
ReturnType returnType = Utils.getReturnTypeDescriptor(method.descriptor);
mv.visitMethodInsn(Opcodes.INVOKESTATIC, executorClassName, method.name, callDescriptor);
if (returnType.isVoid()) {
mv.visitInsn(ACONST_NULL);
} else if (returnType.isPrimitive()) {
Utils.insertBoxInsns(mv, returnType.descriptor);
}
mv.visitInsn(Opcodes.ARETURN);
mv.visitLabel(label);
}
for (MethodMember ctor : typeDescriptor.getLatestTypeDescriptor().getConstructors()) {
String nameWithDescriptor = new StringBuilder(ctor.name).append(ctor.descriptor).toString();
// 2. Load the input name+descriptor and compare it with this method:
// if (nameAndDescriptor.equals(xxx)) {
mv.visitVarInsn(ALOAD, indexNameAndDescriptor);
mv.visitLdcInsn(nameWithDescriptor);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "equals", "(Ljava/lang/Object;)Z");
Label label = new Label();
mv.visitJumpInsn(IFEQ, label); // means if false
// 3. Generate the code that will call the method on the executor:
mv.visitVarInsn(Opcodes.ALOAD, 2);
mv.visitTypeInsn(CHECKCAST, classname);
String callDescriptor = Utils.insertExtraParameter(classname, ctor.descriptor);
int pcount = Utils.getParameterCount(ctor.descriptor);
if (pcount > maxStack) {
pcount = maxStack;
}
// 4. Unpack parameter array to fit the descriptor for that method
Utils.generateInstructionsToUnpackArrayAccordingToDescriptor(mv, ctor.descriptor, 1);
// ReturnType returnType = Utils.getReturnTypeDescriptor(method.descriptor);
mv.visitMethodInsn(Opcodes.INVOKESTATIC, executorClassName, "___init___", callDescriptor);
// if (returnType.isVoid()) {
mv.visitInsn(ACONST_NULL);
// } else if (returnType.isPrimitive()) {
// Utils.insertBoxInsns(mv, returnType.descriptor);
// }
mv.visitInsn(Opcodes.ARETURN);
mv.visitLabel(label);
}
// 5. Throw exception as dynamic dispatcher has been called for something it shouldn't have
// At this point we failed to find it as a method we can dispatch to our executor, so we want
// to pass it 'up' to our supertype. We need to get the dispatcher for our superclass
// and then call the __execute() on it, assuming that it will be able to handle this request.
// alternative 1: use the dispatcher for the superclass
// Determine the supertype
String slashedSupertypeName = rtype.getTypeDescriptor().getSupertypeName();
// getDispatcher will give us the dispatcher for the supertype
mv.visitFieldInsn(Opcodes.GETSTATIC, slashedSupertypeName, fReloadableTypeFieldName, lReloadableType);
mv.visitMethodInsn(INVOKEVIRTUAL, tReloadableType, "getDispatcher",
"()Lorg/springsource/loaded/__DynamicallyDispatchable;");
// alternative 2: find the right dispatcher - i.e. who in the super hierarchy provides that nameAndDescriptor
// now invoke the dynamic dispatch call on that dispatcher
mv.visitVarInsn(ALOAD, indexArgs);
mv.visitVarInsn(ALOAD, indexTarget);
mv.visitVarInsn(ALOAD, indexNameAndDescriptor);
mv.visitMethodInsn(INVOKEINTERFACE, tDynamicallyDispatchable, mDynamicDispatchName, mDynamicDispatchDescriptor);
mv.visitInsn(ARETURN);
// mv.visitTypeInsn(NEW, "java/lang/IllegalStateException");
// mv.visitInsn(DUP);
// mv.visitVarInsn(ALOAD, 3);
// mv.visitMethodInsn(INVOKESPECIAL, "java/lang/IllegalStateException", "<init>", "(Ljava/lang/String;)V");
// mv.visitInsn(ATHROW);
mv.visitMaxs(maxStack, 6);
mv.visitEnd();
}
/**
* 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.
// The original descriptor is this descriptor with the first parameter trimmed off.
boolean isClinit = name.equals("___clinit___");
String originalDescriptor = isClinit ? descriptor : Utils.stripFirstParameter(descriptor);
MethodMember method = null;
// Detect if the name has been modified for clash avoidance reasons
if (name.equals("___init___")) {
// it is a ctor
method = rtype.getConstructor(originalDescriptor);
} else {
if (isClinit) {
generateClinitDispatcher();
return;
} 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 {
method = rtype.getMethod(name, originalDescriptor);
}
}
}
boolean isStatic = method.isStatic();
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, 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);
// For a static method the first parameter can be ignored
int params = Utils.getParameterCount(descriptor);
String callDescriptor = isStatic ? originalDescriptor : descriptor;
Utils.createLoadsBasedOnDescriptor(mv, callDescriptor, isStatic ? 2 : 1);
mv.visitMethodInsn(INVOKESTATIC, executorClassName, name, callDescriptor);
Utils.addCorrectReturnInstruction(mv, returnTypeDescriptor, false);
mv.visitMaxs(params, params + 1);
mv.visitEnd();
}
public void visitOuterClass(String arg0, String arg1, String arg2) {
}
public void visitSource(String arg0, String arg1) {
}
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
/**
* Empty implementation that can be subclassed to pick up default implementations of most methods.
*
* @author Andy Clement
* @since 0.7.3
*/
public class EmptyClassVisitor implements ClassVisitor, Constants {
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
}
public void visitSource(String source, String debug) {
}
public void visitOuterClass(String owner, String name, String desc) {
}
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
return null;
}
public void visitAttribute(Attribute attr) {
}
public void visitInnerClass(String name, String outerName, String innerName, int access) {
}
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
return null;
}
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
return null;
}
public void visitEnd() {
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded;
/**
* Represents an exact type pattern.
*
* @author Andy Clement
* @since 0.5.0
*/
public class ExactTypePattern extends TypePattern {
private String pattern;
/**
* @param pattern type pattern of the form com.foo.Bar
*/
public ExactTypePattern(String pattern) {
this.pattern = pattern;
}
protected boolean internalMatches(String input) {
boolean b = input.equals(pattern);
return b;
}
public String toString() {
return "text:" + pattern;
}
}

View File

@@ -0,0 +1,198 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded;
import java.lang.reflect.Modifier;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
/**
* The executor embodies the new implementation of the type after it has been reloaded.
* <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.
* <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).
* <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.
*
* @author Andy Clement
* @since 0.5.0
*/
public class ExecutorBuilder {
private TypeRegistry typeRegistry;
ExecutorBuilder(TypeRegistry typeRegistry) {
this.typeRegistry = typeRegistry;
}
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,
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.
*/
static class ExecutorBuilderVisitor implements ClassVisitor, Constants {
private ClassWriter cw = new ClassWriter(0);
private String classname;
private String suffix;
protected TypeDescriptor typeDescriptor;
public ExecutorBuilderVisitor(String classname, String suffix, TypeDescriptor typeDescriptor) {
this.classname = classname;
this.suffix = suffix;
this.typeDescriptor = typeDescriptor;
}
public byte[] getBytes() {
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);
}
// For type level annotation copying
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
AnnotationVisitor av = cw.visitAnnotation(desc, visible);
return new CopyingAnnotationVisitor(av);
}
// Fields are copied solely to provide a place to hang annotations
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
return cw.visitField(access, name, desc, signature, value);
}
// 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) {
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 {
// 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
// 2. tweak the parameters
MethodMember method = typeDescriptor.getByDescriptor(name, descriptor);
if (MethodMember.isClash(method)) {
name = "__" + name;
}
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC_STATIC, name, descriptor, signature, exceptions);
return new MethodCopier(mv, typeDescriptor.isInterface(), descriptor, typeDescriptor, classname, suffix);
}
} else {
// constructor
if (name.charAt(1) != 'c') {
// regular constructor
// want to create the ___init___ handler for this constructor
descriptor = Utils.insertExtraParameter(classname, descriptor);
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC_STATIC, mInitializerName, descriptor, signature, exceptions);
ConstructorCopier cc = new ConstructorCopier(mv, typeDescriptor, suffix, classname);
return cc;
} else {
// static initializer
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC_STATIC, mStaticInitializerName, descriptor, signature, exceptions);
return new MethodCopier(mv, typeDescriptor.isInterface(), descriptor, typeDescriptor, classname, suffix);
}
}
}
public void visitSource(String sourcefile, String debug) {
cw.visitSource(sourcefile, debug);
}
private static class CopyingAnnotationVisitor implements AnnotationVisitor {
private AnnotationVisitor av;
public CopyingAnnotationVisitor(AnnotationVisitor av) {
this.av = av;
}
public void visit(String name, Object value) {
av.visit(name, value);
}
public AnnotationVisitor visitAnnotation(String name, String desc) {
AnnotationVisitor localav = av.visitAnnotation(name, desc);
return new CopyingAnnotationVisitor(localav);
}
public AnnotationVisitor visitArray(String name) {
AnnotationVisitor localav = av.visitArray(name);
return new CopyingAnnotationVisitor(localav);
}
public void visitEnd() {
av.visitEnd();
}
public void visitEnum(String name, String desc, String value) {
av.visitEnum(name, desc, value);
}
}
public void visitOuterClass(String arg0, String arg1, String arg2) {
// nothing to do
}
public void visitAttribute(Attribute attr) {
// nothing to do
}
public void visitEnd() {
// nothing to do
}
public void visitInnerClass(String arg0, String arg1, String arg2, int arg3) {
// nothing to do
}
}
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded;
/**
* Encapsulates what has changed about a field on a reload.
*
* @author Andy Clement
* @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) {
this.name = name;
}
public void setTypeChanged(String oldDesc, String newDesc) {
this.oDesc = oldDesc;
this.nDesc = newDesc;
this.changed |= CHANGED_TYPE;
}
public void setAnnotationsChanged(String annotationChanges) {
this.annotationChanges = annotationChanges;
this.changed |= CHANGED_ANNOTATIONS;
}
public boolean hasAnyChanges() {
return (changed & CHANGED_MASK) != 0;
}
public void setAccessChanged(int oldAccess, int newAccess) {
this.oAccess = oldAccess;
this.nAccess = newAccess;
this.changed |= CHANGED_ACCESS;
}
public String toString() {
StringBuilder s = new StringBuilder();
s.append("FieldDelta[field:").append(name);
if ((changed & CHANGED_TYPE) != 0) {
s.append(" type:").append(oDesc).append(">").append(nDesc);
}
if ((changed & CHANGED_ACCESS) != 0) {
s.append(" access:").append(oAccess).append(">").append(nAccess);
}
if ((changed & CHANGED_ANNOTATIONS) != 0) {
s.append(" annotations:").append(annotationChanges);
}
s.append("]");
return s.toString();
}
}

View File

@@ -0,0 +1,86 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded;
/**
* Describes a field, created during TypeDescriptor construction.
*
* @author Andy Clement
* @since 0.5.0
*/
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) {
super(modifiers, name, descriptor, signature);
this.typename = typename;
}
public String getDeclaringTypeName() {
return typename;
}
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("0x").append(Integer.toHexString(modifiers));
sb.append(" ").append(descriptor).append(" ").append(name);
if (signature != null) {
sb.append(" [").append(signature).append("]");
}
return sb.toString().trim();
}
public boolean equals(Object other) {
if (!(other instanceof FieldMember)) {
return false;
}
FieldMember o = (FieldMember) other;
if (!name.equals(o.name)) {
return false;
}
if (modifiers != o.modifiers) {
return false;
}
if (!descriptor.equals(o.descriptor)) {
return false;
}
if (signature == null && o.signature != null) {
return false;
}
if (signature != null && o.signature == null) {
return false;
}
if (signature != null) {
if (!signature.equals(o.signature)) {
return false;
}
}
return true;
}
public int hashCode() {
int result = modifiers;
result = result * 37 + name.hashCode();
result = result * 37 + descriptor.hashCode();
if (signature != null) {
result = result * 37 + signature.hashCode();
}
return result;
}
}

View File

@@ -0,0 +1,464 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
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.
*
* @author Andy Clement
* @since 0.5.0
*/
public class FieldReaderWriter {
private static Logger log = Logger.getLogger(FieldReaderWriter.class.getName());
/**
* The type descriptor for the type that defines the field we want to access
*/
protected TypeDescriptor typeDescriptor;
protected FieldMember theField;
public FieldReaderWriter(FieldMember theField, TypeDescriptor typeDescriptor) {
this.theField = theField;
this.typeDescriptor = typeDescriptor;
assert theField.typename == typeDescriptor.typename;
}
protected 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.
*
* @param instance the object instance upon which to set the field
* @param newValue the new value for that field
* @param the optional state manager for this instance, which will be looked up (expensive) if not passed in
*/
public void setValue(Object instance, Object newValue, ISMgr stateManager) throws IllegalAccessException {
if (typeDescriptor.isReloadable()) {
if (stateManager == null) {
// Look it up using reflection
stateManager = findInstanceStateManager(instance);
}
String declaringTypeName = typeDescriptor.getName();
Map<String, Object> typeLevelValues = stateManager.getMap().get(declaringTypeName);
if (typeLevelValues == null) {
// first time we've accessed this type for an instance field
typeLevelValues = new HashMap<String, Object>();
stateManager.getMap().put(declaringTypeName, typeLevelValues);
}
typeLevelValues.put(theField.getName(), newValue);
} 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() + "."
+ theField.getName());
} else {
findAndSetFieldValueInHierarchy(instance, newValue);
}
}
}
public void setStaticFieldValue(Class<?> clazz, Object newValue, SSMgr stateManager) throws IllegalAccessException {
if (clazz == null) {
throw new IllegalStateException();
}
// First decision - is the field part of a reloadable type or not? The typeDescriptor here is the actual owner
// of the field, at this point we *know* this class has this field.
if (typeDescriptor.isReloadable()) {
if (stateManager == null) {
// need to go and find it, there *will* be one but it will be slow to retrieve (reflection)
stateManager = findStaticStateManager(clazz);
}
String declaringTypeName = typeDescriptor.getName();
Map<String, Object> typeLevelValues = stateManager.getMap().get(declaringTypeName);
if (typeLevelValues == null) {
typeLevelValues = new HashMap<String, Object>();
stateManager.getMap().put(declaringTypeName, typeLevelValues);
}
typeLevelValues.put(theField.getName(), newValue);
} 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()
+ " 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.
*
* @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)
*/
public Object getValue(Object instance, ISMgr stateManager) throws IllegalAccessException, IllegalArgumentException {
Object result = null;
String fieldname = theField.getName();
if (typeDescriptor.isReloadable()) {
if (stateManager == null) {
// find it using reflection
stateManager = findInstanceStateManager(instance);
}
String declaringTypeName = typeDescriptor.getName();
Map<String, Object> typeLevelValues = stateManager.getMap().get(declaringTypeName);
boolean knownField = false;
if (typeLevelValues != null) {
knownField = typeLevelValues.containsKey(fieldname);
}
if (knownField) {
result = typeLevelValues.get(fieldname);
}
// If a field has been deleted it may 'reveal' a field in a supertype. The revealed field may be in a type
// not yet dealt with. In this case typeLevelValues may be null (type not seen before) or the typelevelValues
// may not have heard of our field name. In these cases we need to go and find the field and 'relocate' it
// into our map, where it will be processed from now on.
if (typeLevelValues == null || !knownField) {
FieldMember fieldOnOriginalType = typeDescriptor.getReloadableType().getTypeRegistry()
.getReloadableType(declaringTypeName).getTypeDescriptor().getField(fieldname);
if (fieldOnOriginalType != null) {
// Copy the field into the map - that is where it will live from now on
ReloadableType rt = typeDescriptor.getReloadableType();
try {
Field f = rt.getClazz().getDeclaredField(fieldname);
f.setAccessible(true);
result = f.get(instance);
if (typeLevelValues == null) {
typeLevelValues = new HashMap<String, Object>();
stateManager.getMap().put(declaringTypeName, typeLevelValues);
}
typeLevelValues.put(fieldname, result);
} catch (Exception e) {
throw new IllegalStateException("Unexpectedly unable to access field " + fieldname + " on class "
+ rt.getClazz(), e);
}
} 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) {
typeLevelValues = new HashMap<String, Object>();
stateManager.getMap().put(declaringTypeName, typeLevelValues);
}
typeLevelValues.put(fieldname, result);
return result;
}
}
if (result != null) {
result = Utils.checkCompatibility(typeDescriptor.getTypeRegistry(), result, theField.getDescriptor());
if (result == null) {
// Was not compatible, forget it
typeLevelValues.remove(fieldname);
}
}
result = Utils.toResultCheckIfNull(result, theField.getDescriptor());
} 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() + "."
+ fieldname);
} else {
result = findAndGetFieldValueInHierarchy(instance);
}
}
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.FINER)) {
log.finer("<getValue() value of " + theField + " is " + result);
}
return result;
}
public Object getStaticFieldValue(Class<?> clazz, SSMgr stateManager) throws IllegalAccessException, IllegalArgumentException {
Object result = null;
if (clazz == null) {
throw new IllegalStateException();
}
// First decision - is the field part of a reloadable type or not? The typeDescriptor here is the actual owner
// of the field, at this point we *know* this class has this field.
if (typeDescriptor.isReloadable()) {
if (stateManager == null) {
// need to go and find it, there *will* be one but it will be slow to retrieve (reflection)
stateManager = findStaticStateManager(clazz);
if (stateManager == null) {
return Utils.toResultCheckIfNull(null, theField.descriptor);
}
}
String declaringTypeName = typeDescriptor.getName();
Map<String, Object> typeLevelValues = stateManager.getMap().get(declaringTypeName);
String fieldname = theField.getName();
boolean knownField = false;
if (typeLevelValues != null) {
knownField = typeLevelValues.containsKey(fieldname);
}
if (knownField) {
result = typeLevelValues.get(fieldname);
}
// If a field has been deleted it may 'reveal' a field in a supertype. The revealed field may be in a type
// not yet dealt with. In this case typeLevelValues may be null (type not seen before) or the typelevelValues
// may not have heard of our field name. In these cases we need to go and find the field and 'relocate' it
// into our map, where it will be processed from now on.
// These revealed fields are not necessarily in the original form of the type so cannot always be accessed via reflection
if (typeLevelValues == null || !knownField) {
FieldMember fieldOnOriginalType = typeDescriptor.getReloadableType().getTypeRegistry()
.getReloadableType(declaringTypeName).getTypeDescriptor().getField(fieldname);
if (fieldOnOriginalType != null) { // && fieldOnOriginalType.isStatic()
// can use reflection
ReloadableType rt = typeDescriptor.getReloadableType();
try {
Field f = rt.getClazz().getDeclaredField(theField.getName());
if (!Modifier.isStatic(f.getModifiers())) {
// need to default it anyway, cant see that original value
// TODO this is a dup of the code below, refactor
result = Utils.toResultCheckIfNull(null, theField.getDescriptor());
if (typeLevelValues == null) {
typeLevelValues = new HashMap<String, Object>();
stateManager.getMap().put(declaringTypeName, typeLevelValues);
}
typeLevelValues.put(fieldname, result);
return result;
}
f.setAccessible(true);
// TODO can fail on this next line if the field we've found is non-static
result = f.get(null);
if (typeLevelValues == null) {
typeLevelValues = new HashMap<String, Object>();
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 "
+ rt.getClazz(), e);
}
} 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) {
typeLevelValues = new HashMap<String, Object>();
stateManager.getMap().put(declaringTypeName, typeLevelValues);
}
typeLevelValues.put(fieldname, result);
return result;
}
}
// A problem that can occur is if a fields type is changed on a reload. If the field
// was previously written to we can then retrieve it and attempt to pass it back to the caller
// and they'll get something unexpected.
if (result != null) {
result = Utils.checkCompatibility(typeDescriptor.getTypeRegistry(), result, theField.getDescriptor());
if (result == null) {
typeLevelValues.remove(theField.getName());
}
}
result = Utils.toResultCheckIfNull(result, theField.getDescriptor());
} 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(),
theField.getName());
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 "
+ typeDescriptor.getDottedName());
}
}
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.FINER)) {
log.finer("<getValue() value of " + theField + " is " + result);
}
return result;
}
/**
* Discover the named field in the hierarchy using the standard rules of resolution.
*
* @param clazz the class upon which to start looking
* @param typeWanted where the field is!
* @param name the name of the field to find
* @return the jlrField object representing that field, or null if not found
*/
private Field locateFieldByReflection(Class<?> clazz, String typeWanted, boolean isInterface, String name) {
if (clazz.getName().equals(typeWanted)) {
Field[] fs = clazz.getDeclaredFields();
if (fs != null) {
for (Field f : fs) {
if (f.getName().equals(name)) {
return f;
}
}
}
}
// Check interfaces
if (!isInterface) { // not worth looking!
Class<?>[] interfaces = clazz.getInterfaces();
if (interfaces != null) {
for (Class<?> intface : interfaces) {
Field f = locateFieldByReflection(intface, typeWanted, isInterface, name);
if (f != null) {
return f;
}
}
}
}
// Check superclass
Class<?> superclass = clazz.getSuperclass();
if (superclass == null) {
return null;
} 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.
*
* @param instance the object instance on which to look
* @return the discovered state manager
*/
private ISMgr findInstanceStateManager(Object instance) {
Class<?> clazz = typeDescriptor.getReloadableType().getClazz();
try {
Field fieldAccessorField = clazz.getField(Constants.fInstanceFieldsName);
if (fieldAccessorField == null) {
throw new IllegalStateException("Cant find field accessor for type " + clazz.getName());
}
ISMgr stateManager = (ISMgr) fieldAccessorField.get(instance);
if (stateManager == null) {
// 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);
stateManager = (ISMgr) fieldAccessorField.get(instance);
// For some reason it didn't stick!
if (stateManager == null) {
throw new IllegalStateException("The class '" + clazz.getName()
+ "' has a null instance state manager object, instance is " + instance);
}
}
return stateManager;
} 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.
*
* @param clazz the class on which to look
* @return the discovered state manager
*/
private SSMgr findStaticStateManager(Class<?> clazz) {
try {
Field stateManagerField = clazz.getField(Constants.fStaticFieldsName);
if (stateManagerField == null) {
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>
if (stateManager == null) {
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);
}
}
public boolean isStatic() {
return theField.isStatic();
}
/**
* 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
*/
private Object findAndGetFieldValueInHierarchy(Object instance) {
Class<?> clazz = instance.getClass();
String fieldname = theField.getName();
String searchName = typeDescriptor.getName().replace('/', '.');
while (clazz != null && !clazz.getName().equals(searchName)) {
clazz = clazz.getSuperclass();
}
if (clazz == null) {
throw new IllegalStateException("Failed to find " + searchName + " in hierarchy of " + instance.getClass());
}
try {
Field f = clazz.getDeclaredField(fieldname);
f.setAccessible(true);
return f.get(instance);
} 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.
*
* @param instance the object instance upon which the field is being set
* @param newValue the new value for the field
*/
private void findAndSetFieldValueInHierarchy(Object instance, Object newValue) {
Class<?> clazz = instance.getClass();
String fieldname = theField.getName();
String searchName = typeDescriptor.getName().replace('/', '.');
while (clazz != null && !clazz.getName().equals(searchName)) {
clazz = clazz.getSuperclass();
}
if (clazz == null) {
throw new IllegalStateException("Failed to find " + searchName + " in hierarchy of " + instance.getClass());
}
try {
Field f = clazz.getDeclaredField(fieldname);
f.setAccessible(true);
f.set(instance, newValue);
} catch (Exception e) {
throw new IllegalStateException("Unexpectedly could not access field named " + fieldname + " on class "
+ clazz.getName());
}
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded;
import java.io.File;
/**
* Call back interface for the FileSystemWatcher.
*
* @author Andy Clement
* @since 0.5.0
*/
public interface FileChangeListener {
void fileChanged(File file);
void register(ReloadableType rtype, File file);
}

View File

@@ -0,0 +1,321 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.springsource.loaded.agent.SpringPlugin;
/**
* Captures configurable elements - these are set (to values other than the defaults) in TypeRegistry when the system property
* springloaded.configuration is processed. It is possible to tweak them during testcases to simplify what is being tested - the
* test should reset them to their original values on completion.
*
* @author Andy Clement
* @since 0.5.0
*/
public class GlobalConfiguration {
private static Logger log = Logger.getLogger(GlobalConfiguration.class.getName());
/**
* Are references to fields being modified - covering both the GETS/SETS and the reflective references.
*/
public final static boolean fieldRewriting = true;
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.
*/
public static boolean fileSystemMonitoring = false;
/**
* Global control for loadtime logging
*/
public static boolean logging = false;
/**
* verbose mode can trigger extra messages. Enable with 'verbose=true'
*/
public static boolean verboseMode = false;
/**
* Global control for runtime logging
*/
public static boolean isRuntimeLogging = false;
public static boolean callsideRewritingOn = true;
/**
* Allows a cache to be cleaned up as the agent starts (effectively starting with a new cache, if 'caching' is true)
*/
public static boolean cleanCache = false;
/**
* Determine whether on disk caching will be used.
*/
public static boolean isCaching = false;
/**
* A well known profile (e.g. grails) can tweak a lot of the default options in a particular way.
*/
public static String profile = null;
/**
* The base directory in which to create any cache (.slcache folder). If null then user.home will be used.
*/
public static String cacheDir = null;
public final static boolean logNonInterceptedReflectiveCalls = false;
/**
* Global control for checking assertions
*/
public final static boolean assertsOn = false;
public final static boolean isProfiling = false;
public static boolean directlyDefineTypes = true;
public final static boolean interceptReflection = true;
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.
*/
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.
*/
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.
*/
public static Properties globalConfigurationProperties;
/**
* List of slashed classnames for types we should 'dump' during processing (for debugging purposes).
*/
public static List<String> classesToDump;
public static int maxClassDefinitions = 100;
/**
* List of dotted classnames representing classnames of plugins that should be loaded.
*/
public static List<String> pluginClassnameList;
public final static boolean debugplugins;
/**
* Look for a springloaded system property and initialize the 'default system wide' configuration based upon it.
*/
static {
// classesToDump = new ArrayList<String>();
// classesToDump.add("Demo");
globalConfigurationProperties = new Properties();
// Load global configuration
boolean debugPlugins = false;
try {
boolean specifiedCaching = false;
String value = System.getProperty("springloaded");
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.FINEST)) {
log.finest("GlobalConfiguration: being configured from '" + value + "'");
}
// value is a ';' separated list of configuration options which either may be name=value settings or directives (just a name)
if (value != null) {
StringTokenizer st = new StringTokenizer(value, ";");
while (st.hasMoreTokens()) {
String kv = st.nextToken();
int equals = kv.indexOf('=');
if (equals != -1) {
// key=value
String key = kv.substring(0, equals);
// Supported settings:
// dump=XX,YYY,ZZZ
// - this option lists classes for which we should dump the bytecode, names are dotted
if (key.equals("dump")) {
String classList = kv.substring(equals + 1);
StringTokenizer clSt = new StringTokenizer(classList, ",");
classesToDump = new ArrayList<String>();
while (clSt.hasMoreTokens()) {
classesToDump.add(clSt.nextToken().replace('.', '/'));
}
if (isRuntimeLogging && log.isLoggable(Level.INFO)) {
log.info("configuration: dumping: " + classesToDump);
}
// } else if (key.equals("interceptReflection")) { // global setting
// interceptReflection = kv.substring(equals + 1).equalsIgnoreCase("true");
// if (isRuntimeLogging && log.isLoggable(Level.INFO)) {
// log.info("configuration: interceptReflection = " + interceptReflection);
// }
} else if (key.equals("cleanCache")) {
cleanCache = kv.substring(equals + 1).equalsIgnoreCase("true");
} else if (key.equals("caching")) {
specifiedCaching = true;
isCaching = kv.substring(equals + 1).equalsIgnoreCase("true");
} else if (key.equals("debugplugins")) {
debugPlugins = true;
} else if (key.equals("profile")) {
profile = kv.substring(equals + 1);
} else if (key.equals("cacheDir")) {
cacheDir = kv.substring(equals + 1);
} 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);
}
// } else if (key.equals("logNonInterceptedReflectiveCalls")) { // global setting
// logNonInterceptedReflectiveCalls = kv.substring(equals + 1).equalsIgnoreCase("true");
// if (isRuntimeLogging && log.isLoggable(Level.INFO)) {
// log.info("configuration: logNonInterceptedReflectiveCalls = " + logNonInterceptedReflectiveCalls);
// }
} 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
dumpFolder = kv.substring(equals + 1);
if (isRuntimeLogging && log.isLoggable(Level.INFO)) {
log.info("configuration: dumpFolder = " + dumpFolder);
}
} 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")) {
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")) {
GlobalConfiguration.verboseMode = kv.substring(equals + 1).equalsIgnoreCase("true");
GlobalConfiguration.reloadMessages = verboseMode;
} 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")) {
globalConfigurationProperties.put("inclusions", kv.substring(equals + 1));
} else if (key.equals("exclusions")) {
globalConfigurationProperties.put("exclusions", kv.substring(equals + 1));
} else if (key.equals("plugins")) {
// plugins=com.myplugin.Plugin,com.somethingelse.SomeOtherPlugin
String pluginList = kv.substring(equals + 1);
StringTokenizer pluginListTokenizer = new StringTokenizer(pluginList, ",");
pluginClassnameList = new ArrayList<String>();
while (pluginListTokenizer.hasMoreTokens()) {
pluginClassnameList.add(pluginListTokenizer.nextToken());
}
}
} else {
// directive
}
}
}
// Profile support. A profile is a shortcut for configuring a bunch of options
if (profile != null) {
if (profile.equals("grails")) {
// Configure options based on a grails profile
// turn on caching if we have a cacheDir set or can put one in the .grails folder under user.home
if (cacheDir == null) {
try {
String userhome = System.getProperty("user.home");
if (userhome != null) {
cacheDir = new StringBuilder(userhome).append(File.separator).append(".grails").toString();
new File(cacheDir).mkdir();
}
} 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);
}
}
if (!specifiedCaching) {
if (cacheDir != null) {
isCaching = true;
}
}
if (pluginClassnameList == null) {
pluginClassnameList = new ArrayList<String>();
}
pluginClassnameList.add("org.springsource.loaded.SystemPropertyConfiguredIsReloadableTypePlugin");
// turn off the 3.0 reloading, for now (just because it hasn't been tested)
SpringPlugin.support305 = false;
}
} else {
if (isCaching) {
try {
String userhome = System.getProperty("user.home");
if (userhome != null) {
cacheDir = userhome;
}
} catch (Throwable t) {
System.err.println("looks like user.home is not set: cannot create cache.");
t.printStackTrace(System.err);
}
}
}
if (isCaching) {
// Ensure cache folder exists
try {
File cacheDirFile = new File(cacheDir);
if (!cacheDirFile.exists()) {
boolean created = cacheDirFile.mkdirs();
if (!created) {
System.err.println("Caching deactivated: failed to create cache directory: " + cacheDir);
isCaching = false;
}
} 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) {
System.err.println("Unexpected problem creating specified cachedir: " + cacheDir);
e.printStackTrace();
}
}
} catch (Throwable t) {
System.err.println("Unexpected problem reading global configuration setting:" + t.toString());
t.printStackTrace();
}
debugplugins = debugPlugins;
}
}

View File

@@ -0,0 +1,208 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
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.
*
* @author Andy Clement
* @since 0.5.0
*/
public class ISMgr {
private static Logger log = Logger.getLogger(ISMgr.class.getName());
Map<String, Map<String, Object>> values = new HashMap<String, Map<String, Object>>();
// TODO rtype in here means no need to have it on the getValue calls
public ISMgr(Object instance, ReloadableType rtype) {
// System.out.println("Instance passed to ISMgr " + instance + " rtype=" + rtype);
if (rtype.getTypeDescriptor().isGroovyType()) {
rtype.trackLiveInstance(instance);
}
}
/**
* 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 name the name of the field
*
* @return the value of the field
*/
public Object getValue(ReloadableType rtype, Object instance, String name) throws IllegalAccessException {
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.FINER)) {
log.finer(">getValue(rtype=" + rtype + ",instance=" + instance + ",name=" + name + ")");
}
Object result = null;
// Quick look from here to top most reloadable type:
FieldMember field = rtype.findInstanceField(name);
if (field == null) {
// If the field is now null, there are two possible reasons:
// 1. The field does not exist in the hierarchy at all
// 2. The field is on a type just above our topmost reloadable type
FieldReaderWriter frw = rtype.locateField(name);
if (frw == null) {
// 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
+ ": clinit running late?");
return null;
}
result = frw.getValue(instance, this);
} else {
if (field.isStatic()) {
throw new IncompatibleClassChangeError("Expected non-static field " + rtype.dottedtypename + "." + field.getName());
}
String declaringTypeName = field.getDeclaringTypeName();
Map<String, Object> typeLevelValues = values.get(declaringTypeName);
boolean knownField = false;
if (typeLevelValues != null) {
knownField = typeLevelValues.containsKey(name);
}
if (knownField) {
result = typeLevelValues.get(name);
}
// If a field has been deleted it may 'reveal' a field in a supertype. The revealed field may be in a type
// not yet dealt with. In this case typeLevelValues may be null (type not seen before) or the typelevelValues
// may not have heard of our field name. In these cases we need to go and find the field and 'relocate' it
// into our map, where it will be processed from now on.
// These revealed fields are not necessarily in the original form of the type so cannot be accessed via reflection
if (typeLevelValues == null || !knownField) {
// Determine whether we need to use reflection or not:
// '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())
.getTypeDescriptor().getField(name);
if (fieldOnOriginalType != null) {
// The field was on the original type, use reflection
ReloadableType rt = rtype.getTypeRegistry().getReloadableType(field.getDeclaringTypeName());
try {
Field f = rt.getClazz().getDeclaredField(name);
f.setAccessible(true);
result = f.get(instance);
if (typeLevelValues == null) {
typeLevelValues = new HashMap<String, Object>();
values.put(declaringTypeName, typeLevelValues);
}
typeLevelValues.put(name, result);
} catch (Exception e) {
throw new IllegalStateException("Unexpectedly unable to access field " + name + " on type "
+ rt.getClazz().getName(), e);
}
} 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) {
typeLevelValues = new HashMap<String, Object>();
values.put(declaringTypeName, typeLevelValues);
}
typeLevelValues.put(name, result);
return result;
}
}
if (result != null) {
result = Utils.checkCompatibility(rtype.getTypeRegistry(), result, field.getDescriptor());
if (result == null) {
typeLevelValues.remove(field.getName());
}
}
result = Utils.toResultCheckIfNull(result, field.getDescriptor());
}
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.FINER)) {
log.finer("<getValue() value of " + name + " is " + result);
}
return result;
}
/**
* Set the value of a field.
*
* @param rtype the reloadabletype
* @param instance the instance upon which to set the field
* @param name the name of the field
*/
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
FieldMember fieldmember = rtype.findInstanceField(name);
if (fieldmember == null) {
// If the field is null, there are two possible reasons:
// 1. The field does not exist in the hierarchy at all
// 2. The field is on a type just above our topmost reloadable type
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
+ ": clinit running late?");
return;
}
frw.setValue(instance, value, this);
} else {
if (fieldmember.isStatic()) {
throw new IncompatibleClassChangeError("Expected non-static field " + rtype.dottedtypename + "."
+ fieldmember.getName());
}
Map<String, Object> typeValues = values.get(fieldmember.getDeclaringTypeName());
if (typeValues == null) {
typeValues = new HashMap<String, Object>();
values.put(fieldmember.getDeclaringTypeName(), typeValues);
}
typeValues.put(name, value);
}
}
private String valuesToString() {
StringBuilder s = new StringBuilder();
s.append("InstanceState:" + System.identityHashCode(this)).append("\n");
for (Map.Entry<String, Map<String, Object>> entry : values.entrySet()) {
s.append("Type " + entry.getKey()).append("\n");
for (Map.Entry<String, Object> entry2 : entry.getValue().entrySet()) {
s.append(" " + entry2.getKey() + "=" + entry2.getValue()).append("\n");
}
}
return s.toString();
}
public String toString() {
return valuesToString();
}
Map<String, Map<String, Object>> getMap() {
return values;
}
}

View File

@@ -0,0 +1,287 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded;
import java.util.ArrayList;
import java.util.HashMap;
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.
*
* @author Andy Clement
* @since 0.5.0
*/
public class IncrementalTypeDescriptor implements Constants {
private TypeDescriptor initialTypeDescriptor;
private TypeDescriptor latestTypeDescriptor;
private int bits;
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) {
reinitialize();
this.initialTypeDescriptor = initialTypeDescriptor;
}
public TypeDescriptor getLatestTypeDescriptor() {
return latestTypeDescriptor;
}
public void setLatestTypeDescriptor(TypeDescriptor typeDescriptor) {
reinitialize();
this.latestTypeDescriptor = typeDescriptor;
}
/**
* When first setup or a new latest descriptor passed to us, forget what we know.
*/
private void reinitialize() {
// trigger recomputation
bits = 0x0000;
}
/**
* Return the list of 'new or changed' methods. New or changed is characterised as: *
* <ul>
* <li>methods that never used to exist but do now
* <li>methods where name and descriptor are the same but something else has changed (see MethodMember.equals())
* <li>method was represented as a catcher in the original, but is now 'real'
* </ul>
* It does not include catchers.
*/
public List<MethodMember> getNewOrChangedMethods() {
compute();
return newOrChangedMethods;
}
/**
* Return the list of 'new or changed' constructors. New or changed is characterized as:
* <ul>
* <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>
*/
public List<MethodMember> getNewOrChangedConstructors() {
compute();
return newOrChangedConstructors;
}
public List<MethodMember> getDeletedMethods() {
compute();
return deletedMethods;
}
private void compute() {
if ((bits & BIT_COMPUTED_DIFF) != 0) {
return;
}
latestMethods = new HashMap<String, MethodMember>();
newOrChangedMethods = new ArrayList<MethodMember>();
deletedMethods = new ArrayList<MethodMember>();
// Process the methods in the latest copy, compared to the original
for (MethodMember latest : latestTypeDescriptor.getMethods()) {
// Did this method exist in the original? Ask by name and descriptor
MethodMember original = initialTypeDescriptor.getByDescriptor(latest.getName(), latest.getDescriptor());
// If it did not exist, tag it
if (original == null) {
latest.bits |= MethodMember.IS_NEW;
newOrChangedMethods.add(latest);
} else {
if (!original.equals(latest)) { // check more than just name/descriptor
newOrChangedMethods.add(latest);
}
// If originally it was a catcher and now it is no longer a catcher (an impl has been provided), record it
if (MethodMember.isCatcher(original) && !MethodMember.isCatcher(latest)) {
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;
}
latest.original = original;
// Keep track of important changes:
if (original.modifiers != latest.modifiers) {
// Determine if a change was made from static to non-static or vice versa
boolean wasStatic = original.isStatic();
boolean isStatic = latest.isStatic();
if (wasStatic != isStatic) {
if (wasStatic) {
// has been made non-static
latest.bits |= MethodMember.MADE_NON_STATIC;
} else {
// has been made static
latest.bits |= MethodMember.MADE_STATIC;
}
}
// Determine if a change was made with regards visibility
int oldVisibility = original.modifiers & ACC_PUBLIC_PRIVATE_PROTECTED;
int newVisibility = latest.modifiers & ACC_PUBLIC_PRIVATE_PROTECTED;
if (oldVisibility != newVisibility) {
latest.bits |= MethodMember.VISIBILITY_CHANGE;
}
}
// TODO do we care about exceptions changing? It doesn't make it a new method.
// TODO if we do, upgrade this check to remember the precise changes?
// int oExceptionsLength = original.exceptions == null ? 0 : original.exceptions.length;
// int nExceptionsLength = latest.exceptions == null ? 0 : latest.exceptions.length;
// if (oExceptionsLength != nExceptionsLength) {
// latest.bits |= MethodMember.EXCEPTIONS_CHANGE;
// } else {
// for (int i = 0; i < oExceptionsLength; i++) {
// if (!original.exceptions[i].equals(latest.exceptions[i])) {
// latest.bits |= MethodMember.EXCEPTIONS_CHANGE;
// }
// }
// }
}
String nadKey = new StringBuilder(latest.getName()).append(latest.getDescriptor()).toString();
latestMethods.put(nadKey, latest);
}
for (MethodMember initialMethod : initialTypeDescriptor.getMethods()) {
if (MethodMember.isCatcher(initialMethod)) {
continue;
}
if (!latestTypeDescriptor.defines(initialMethod)) {
deletedMethods.add(initialMethod);
}
}
bits |= BIT_COMPUTED_DIFF;
}
public boolean mustUseExecutorForThisMethod(int methodId) {
// Rule1: if it is a new method, we must use the executor
compute();
// If it is a catcher method that now has an implementation, we must use the executor
MethodMember method = initialTypeDescriptor.getMethod(methodId);
if (MethodMember.isCatcher(method)) {
// Has it now been provided?? If it has not we can just return immediately
boolean found = false;
for (MethodMember method2 : newOrChangedMethods) {
if (method2.shouldReplace(method)) { // modifiers? what of static/nonstatic et al
//We should not consider modifiers or exceptions in this test!
// otherwise we will end up not finding a method that really should replace / override
// the catcher.
found = true;
if (MethodMember.isCatcher(method2)) {
return false;
}
}
}
if (!found) {
// not provided! New type descriptor doesn't include catchers
return false;
}
}
return true;
}
public boolean hasBeenDeleted(int methodId) {
compute();
MethodMember method = initialTypeDescriptor.getMethod(methodId);
boolean a = false;
for (MethodMember m : deletedMethods) {
if (m.equals(method)) {
a = true;
break;
}
}
// alternative mechanism
// boolean b = true;
// for (MethodMember m : this.latestTypeDescriptor.getMethods()) {
// if (m.equals(method)) {
// b = wasDeleted(m);
// break;
// }
// }
return a;
}
public MethodMember getFromLatestByDescriptor(String nameAndDescriptor) {
compute();
return latestMethods.get(nameAndDescriptor);
}
// For checking the bitflags:
/**
* @return true if the method is brand new after a reload (i.e. was never defined in the original type)
*/
public static boolean isBrandNewMethod(MethodMember mm) {
return (mm.bits & MethodMember.IS_NEW) != 0;
}
public static boolean hasChanged(MethodMember mm) {
return (mm.bits & 0x7fffffff) != 0;
}
public static boolean isCatcher(MethodMember method) {
return (method.bits & MethodMember.BIT_CATCHER) != 0;
}
public static boolean isNowNonStatic(MethodMember method) {
return (method.bits & MethodMember.MADE_NON_STATIC) != 0;
}
public static boolean isNowStatic(MethodMember method) {
return (method.bits & MethodMember.MADE_STATIC) != 0;
}
public static boolean hasVisibilityChanged(MethodMember method) {
return (method.bits & MethodMember.VISIBILITY_CHANGE) != 0;
}
public static boolean wasDeleted(MethodMember method) {
return (method.bits & MethodMember.WAS_DELETED) != 0;
}
public TypeDescriptor getOriginal() {
return this.initialTypeDescriptor;
}
public String toString() {
return toString(false);
}
public String toString(boolean compute) {
StringBuilder s = new StringBuilder();
s.append("Original:\n").append(this.initialTypeDescriptor).append("\nCurrent:\n").append(this.latestTypeDescriptor);
s.append('\n');
if (compute) {
compute();
s.append("Deleted methods: ").append(deletedMethods).append("\n");
s.append("New or changed methods: ").append(newOrChangedMethods).append("\n");
}
return s.toString();
}
}

View File

@@ -0,0 +1,168 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
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>
* For example:<br>
*
* <tt> <pre>
* class Foo {
* public String foo(int i) {}
* }
* </pre></tt>
*
* will cause creation of an interface method:
*
* <tt> <pre>
* String foo(Foo instance, int i) {}
* </pre></tt>
*
* @author Andy Clement
* @since 0.5.0
*/
public class InterfaceExtractor {
@SuppressWarnings("unused")
private TypeRegistry registry;
public InterfaceExtractor(TypeRegistry registry) {
this.registry = registry;
}
/**
* Extract the fixed interface for a class and a type descriptor with more details on the methods
*/
public static byte[] extract(byte[] classbytes, TypeRegistry registry, TypeDescriptor typeDescriptor) {
return new InterfaceExtractor(registry).extract(classbytes, typeDescriptor);
}
public byte[] extract(byte[] classbytes, TypeDescriptor typeDescriptor) {
ClassReader fileReader = new ClassReader(classbytes);
ExtractorVisitor extractorVisitor = new ExtractorVisitor(typeDescriptor);
fileReader.accept(extractorVisitor, 0);
return extractorVisitor.getBytes();
}
class ExtractorVisitor implements ClassVisitor, Constants {
private TypeDescriptor typeDescriptor;
private ClassWriter interfaceWriter = new ClassWriter(0);
private String slashedtypename;
public ExtractorVisitor(TypeDescriptor typeDescriptor) {
this.typeDescriptor = typeDescriptor;
}
public byte[] getBytes() {
return interfaceWriter.toByteArray();
}
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);
this.slashedtypename = name;
}
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>
// It is a constructor
String newDescriptor = createDescriptorWithPrefixedParameter(descriptor);
// Need a modified name
name = "___init___";
interfaceWriter.visitMethod(ACC_PUBLIC_ABSTRACT, name, newDescriptor, signature, exceptions);
}
} else {
String newDescriptor = createDescriptorWithPrefixedParameter(descriptor);
// generic signature is erased
MethodMember method = typeDescriptor.getByDescriptor(name, descriptor);
if (MethodMember.isClash(method)) {
name = "__" + name;
}
interfaceWriter.visitMethod(ACC_PUBLIC_ABSTRACT, name, newDescriptor, null, exceptions);
}
return null;
}
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
return null;
}
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
return null;
}
public void visitInnerClass(String name, String outerName, String innerName, int access) {
// nothing to do
}
public void visitOuterClass(String owner, String name, String desc) {
// nothing to do
}
public void visitSource(String source, String debug) {
// nothing to do
}
public void visitAttribute(Attribute attr) {
// nothing to do
}
public void visitEnd() {
// Must add a method on the interface for the dynamic invocation method
String descriptor = mDynamicDispatchDescriptor;
interfaceWriter.visitMethod(ACC_PUBLIC_ABSTRACT, mDynamicDispatchName, descriptor, null, null);
interfaceWriter.visitMethod(ACC_PUBLIC_ABSTRACT, mStaticInitializerName, "()V", null, null);
// Go through catchers on the type descriptor and add the methods to the interface
for (MethodMember method : typeDescriptor.getMethods()) {
if (!MethodMember.isCatcher(method)) {
continue;
}
descriptor = createDescriptorWithPrefixedParameter(method.getDescriptor());
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"
*
* @return new descriptor with extra leading parameter
*/
private String createDescriptorWithPrefixedParameter(String descriptor) {
StringBuilder newDescriptor = new StringBuilder();
newDescriptor.append("(L").append(slashedtypename).append(";");
newDescriptor.append(descriptor, 1, descriptor.length());
return newDescriptor.toString();
}
private boolean isClinitOrInit(String name) {
return name.charAt(0) == '<';
}
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded;
import java.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.
*
* @author Andy Clement
* @since 0.7.1
*/
public interface IsReloadableTypePlugin extends Plugin {
/**
* @param typename slashed type name (e.g. java/lang/String)
* @param protectionDomain
* @param bytes the classfile data
*/
ReloadDecision shouldBeMadeReloadable(String typename, ProtectionDomain protectionDomain, byte[] bytes);
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded;
import java.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}
*
* @author Andy Clement
* @since 0.5.0
*/
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.
*
* @param slashedTypeName the type name, slashed form (e.g. java/lang/String)
* @param classLoader the classloader loading the type
* @param protectionDomain
* @param bytes the classfile contents for the type
* @return true if this plugin wants to change the bytes for the named type
*/
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.
*
* @param slashedTypeName the type name, slashed form (e.g. java/lang/String)
* @param classLoader the classloader loading the type
* @param bytes the classfile contents for the type
* @return the new (modified) bytes for the class
*/
byte[] modify(String slashedClassName, ClassLoader classLoader, byte[] bytes);
}

View File

@@ -0,0 +1,196 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodAdapter;
import org.objectweb.asm.MethodVisitor;
import org.springsource.loaded.Utils.ReturnType;
/**
*
* @author Andy Clement
* @since 0.5.0
*/
class MethodCopier extends MethodAdapter 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,
String suffix) {
super(mv);
this.isInterface = isInterface;
this.descriptor = descriptor;
this.typeDescriptor = typeDescriptor;
this.classname = classname;
this.suffix = suffix;
this.hasFieldsRequiringAccessors = this.typeDescriptor.getFieldsRequiringAccessors().length != 0;
}
@Override
public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) {
// 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 {
super.visitLocalVariable(name, desc, signature, start, end, index);
}
}
private FieldMember findFieldIfRequiresAccessorUsage(String owner, String name) {
FieldMember[] fms = this.typeDescriptor.getFieldsRequiringAccessors();
for (FieldMember fm : fms) {
if (fm.getName().equals(name) && (owner.equals(classname) || isOneOfOurSupertypes(owner))) { // && fm.getDeclaringTypeName().equals(owner)) {
// possibly a match - testcase scenario:
// 'owner=prot/SubThree' - this is what the FIELD instruction is working on
// 'dfm=prot/Three' - this is the type that declared the field
// 'classname=prot/SubThree' - this is the type we are currently operating on
// in our other case though (with JDK Proxies)
// owner=java/lang/reflect/Proxy
// dfm=java/lang/reflect/Proxy
// classname=$Proxy6
// (this is the funky InvocationHandler field called 'h' in Proxy)
return fm;
}
}
return null;
}
/**
* 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
*/
private boolean isOneOfOurSupertypes(String type) {
String stypeName = typeDescriptor.getSupertypeName();
while (stypeName != null) {
// TODO [bug] should stop at the first one that has a field in it? and check the field is protected, yada yada yada
if (stypeName.equals(type)) {
return true;
}
stypeName = typeDescriptor.getTypeRegistry().getDescriptorFor(stypeName).getSupertypeName();
}
return false;
}
@Override
public void visitFieldInsn(final int opcode, final String owner, final String name, final String desc) {
if (hasFieldsRequiringAccessors) {
// Check if this field reference needs redirecting to an accessor
FieldMember fm = findFieldIfRequiresAccessorUsage(owner, name);
if (fm != null) {
switch (opcode) {
case GETFIELD:
mv.visitMethodInsn(INVOKEVIRTUAL, classname, Utils.getProtectedFieldGetterName(name), "()" + desc);
return;
case PUTFIELD:
mv.visitMethodInsn(INVOKEVIRTUAL, classname, Utils.getProtectedFieldSetterName(name), "(" + desc + ")V");
return;
case GETSTATIC:
mv.visitMethodInsn(INVOKESTATIC, classname, Utils.getProtectedFieldGetterName(name), "()" + desc);
return;
case PUTSTATIC:
mv.visitMethodInsn(INVOKESTATIC, classname, Utils.getProtectedFieldSetterName(name), "(" + desc + ")V");
return;
}
}
}
super.visitFieldInsn(opcode, owner, name, desc);
}
@Override
public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc) {
// Is it a private method call?
// TODO r$ check here because we use invokespecial to avoid virtual dispatch on field changes...
if (opcode == INVOKESPECIAL && name.charAt(0) != '<' && owner.equals(classname) && !name.startsWith("r$")) {
// leaving the invokespecial alone will cause a verify error
String descriptor = Utils.insertExtraParameter(owner, desc);
super.visitMethodInsn(INVOKESTATIC, Utils.getExecutorName(classname, suffix), name, descriptor);
} else {
// Might be a private static method
boolean done = false;
if (opcode == INVOKESTATIC) {
MethodMember mm = typeDescriptor.getByDescriptor(name, desc);
if (mm != null && mm.isPrivate()) {
super.visitMethodInsn(INVOKESTATIC, Utils.getExecutorName(classname, suffix), name, desc);
done = true;
}
}
if (!done) {
super.visitMethodInsn(opcode, owner, name, desc);
}
}
}
@Override
public void visitEnd() {
if (isInterface) {
// Create 'dummy methods' for an interface implementation
createDummyMethodBody();
super.visitEnd();
}
}
private void createDummyMethodBody() {
ReturnType returnType = Utils.getReturnTypeDescriptor(descriptor);
int descriptorSize = Utils.getSize(descriptor);
if (returnType.isVoid()) {
super.visitInsn(RETURN);
super.visitMaxs(1, descriptorSize);
} 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);
}
} else {
// reference type
super.visitInsn(ACONST_NULL);
super.visitInsn(ARETURN);
super.visitMaxs(1, descriptorSize);
}
}
}

View File

@@ -0,0 +1,102 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded;
import org.objectweb.asm.tree.AbstractInsnNode;
/**
* Encapsulates what has changed about a method when it is reloaded, compared to the original form.
*
* @author Andy Clement
* @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
| 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) {
this.name = name;
this.desc = desc;
}
public void setAnnotationsChanged(String annotationChanges) {
this.annotationChanges = annotationChanges;
this.changed |= CHANGED_ANNOTATIONS;
}
public boolean hasAnyChanges() {
return (changed & CHANGED_MASK) != 0;
}
public boolean hasInvokeSpecialChanged() {
return (changed & CHANGED_INVOKESPECIAL) != 0;
}
public boolean hasCodeChanged() {
return (changed & CHANGED_CODE) != 0;
}
public void setAccessChanged(int oldAccess, int newAccess) {
this.oAccess = oldAccess;
this.nAccess = newAccess;
this.changed |= CHANGED_ACCESS;
}
public String toString() {
StringBuilder s = new StringBuilder();
s.append("MethodDelta[method:").append(name).append(desc);
if ((changed & CHANGED_ACCESS) != 0) {
s.append(" access:").append(oAccess).append(">").append(nAccess);
}
if ((changed & CHANGED_ANNOTATIONS) != 0) {
s.append(" annotations:").append(annotationChanges);
}
s.append("]");
return s.toString();
}
public void setInstructionsChanged(AbstractInsnNode[] oInstructions, AbstractInsnNode[] nInstructions) {
this.changed |= CHANGED_INSTRUCTIONS;
}
public void setInvokespecialChanged(String oInvokeSpecialDescriptor, String nInvokeSpecialDescriptor) {
this.changed |= CHANGED_INVOKESPECIAL;
this.oInvokespecialDescriptor = oInvokeSpecialDescriptor;
this.nInvokespecialDescriptor = nInvokeSpecialDescriptor;
}
public void setCodeChanged(AbstractInsnNode[] oInstructions, AbstractInsnNode[] nInstructions) {
this.changed |= CHANGED_CODE;
this.oInstructions = oInstructions;
this.nInstructions = nInstructions;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,283 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded;
import java.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.
*
* @author Andy Clement
* @since 0.5.0
*/
public class MethodMember extends AbstractMember {
final static MethodMember[] NONE = new MethodMember[0];
protected final String[] exceptions;
public int bits;
// 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;
// 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)
public MethodMember original;
public final String nameAndDescriptor;
public Method cachedMethod;
protected MethodMember(int modifiers, String name, String descriptor, String signature, String[] exceptions) {
super(modifiers, name, descriptor, signature);
this.exceptions = perhapsSortIfNecessary(exceptions);
this.nameAndDescriptor = new StringBuilder(name).append(descriptor).toString();
}
private String[] perhapsSortIfNecessary(String[] exceptions) {
if (exceptions == null) {
return Constants.NO_STRINGS;
}
// Arrays.sort(exceptions);
return exceptions;
}
public String[] getExceptions() {
return exceptions;
}
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("0x").append(Integer.toHexString(modifiers));
sb.append(" ").append(name).append(descriptor);
if (exceptions.length != 0) {
sb.append(" throws ");
for (String ex : exceptions) {
sb.append(ex).append(" ");
}
}
return sb.toString().trim();
}
public String getParamDescriptor() {
// more likely to be at the end, lets go back from there
for (int pos = descriptor.length() - 1; pos > 0; pos--) {
if (descriptor.charAt(pos) == ')') {
return descriptor.substring(0, pos + 1);
}
}
throw new IllegalStateException("Method has invalid descriptor: " + descriptor);
}
public boolean hasReturnValue() {
return descriptor.charAt(descriptor.length() - 1) != 'V';
}
public boolean equals(Object other) {
if (!(other instanceof MethodMember)) {
return false;
}
MethodMember o = (MethodMember) other;
if (!name.equals(o.name)) {
return false;
}
if (modifiers != o.modifiers) {
return false;
}
if (!descriptor.equals(o.descriptor)) {
return false;
}
if (exceptions.length != o.exceptions.length) {
return false;
}
if (signature == null && o.signature != null) {
return false;
}
if (signature != null && o.signature == null) {
return false;
}
if (signature != null) {
if (!signature.equals(o.signature)) {
return false;
}
}
for (int i = 0; i < exceptions.length; i++) {
if (!exceptions[i].equals(o.exceptions[i])) {
return false;
}
}
return true;
}
public int hashCode() {
int result = modifiers;
result = result * 37 + name.hashCode();
result = result * 37 + descriptor.hashCode();
if (signature != null) {
result = result * 37 + signature.hashCode();
}
if (exceptions != null) {
for (String ex : exceptions) {
result = result * 37 + ex.hashCode();
}
}
return result;
}
public MethodMember catcherCopyOf() {
int newModifiers = modifiers & ~Modifier.NATIVE;
if (name.equals("clone") && (modifiers & Modifier.NATIVE) != 0) {
newModifiers = Modifier.PUBLIC;
} 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) {
// 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, descriptor, signature, exceptions);
copy.bits |= MethodMember.BIT_CATCHER;
return copy;
}
public MethodMember catcherCopyOfWithAbstractRemoved() {
int newModifiers = modifiers & ~(Modifier.NATIVE | Modifier.ABSTRACT);
if (name.equals("clone") && (modifiers & Modifier.NATIVE) != 0) {
newModifiers = Modifier.PUBLIC;
} 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) {
// 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, descriptor, signature, exceptions);
copy.bits |= MethodMember.BIT_CATCHER;
copy.bits |= MethodMember.BIT_CATCHER_INTERFACE;
return copy;
}
public boolean equalsApartFromModifiers(MethodMember other) {
if (!(other instanceof MethodMember)) {
return false;
}
MethodMember o = other;
if (!name.equals(o.name)) {
return false;
}
if (!descriptor.equals(o.descriptor)) {
return false;
}
// if (exceptions.length != o.exceptions.length) {
// return false;
// }
// for (int i = 0; i < exceptions.length; i++) {
// if (!exceptions[i].equals(o.exceptions[i])) {
// return false;
// }
// }
return true;
}
public String getNameAndDescriptor() {
return nameAndDescriptor;
}
public static boolean isClash(MethodMember method) {
return (method.bits & MethodMember.BIT_CLASH) != 0;
}
public static boolean isCatcher(MethodMember method) {
return (method.bits & BIT_CATCHER) != 0;
}
public static boolean isCatcherForInterfaceMethod(MethodMember method) {
return (method.bits & BIT_CATCHER_INTERFACE) != 0;
}
public static boolean isDeleted(MethodMember method) {
return (method.bits & WAS_DELETED) != 0;
}
public Object bitsToString() {
StringBuilder s = new StringBuilder();
if ((bits & BIT_CATCHER) != 0) {
s.append("catcher ");
}
if ((bits & BIT_CLASH) != 0) {
s.append("clash ");
}
if ((bits & MADE_STATIC) != 0) {
s.append("made_static ");
}
if ((bits & MADE_NON_STATIC) != 0) {
s.append("made_non_static ");
}
if ((bits & VISIBILITY_CHANGE) != 0) {
s.append("vis_change ");
}
if ((bits & IS_NEW) != 0) {
s.append("is_new ");
}
if ((bits & WAS_DELETED) != 0) {
s.append("is_new ");
}
return "[" + s.toString().trim() + "]";
}
/**
* Determine whether this method should replace the other method on reload. In accordance to how JVM works at class load time,
* this will be the case if this and other have the same Class, name, parameter types and return type. I.e. formally, in JVM
* bytecode (unlike source code) a method doesn't override a method with a different return type. When such a situation occurs
* in source code, the compiler will introduce a bridge method in bytecode.
*/
public boolean shouldReplace(MethodMember other) {
if (!name.equals(other.name)) {
return false;
}
if (!descriptor.equals(other.descriptor)) {
return false;
}
return true;
}
public boolean isConstructor() {
return name.equals("<init>");
}
}

View File

@@ -0,0 +1,102 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded;
/**
* 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
*/
public class NameRegistry {
private static int nextTypeId = 0;
private static int size = 10;
private static String[] allocatedIds = new String[size];
private NameRegistry() {
}
/**
* Typically used by tests to ensure it looks like a fresh NameRegistry is being used.
*/
public static void reset() {
nextTypeId = 0;
size = 10;
allocatedIds = new String[size];
}
/**
* 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
*/
public static int getIdFor(String slashedClassName) {
assert Asserts.assertNotDotted(slashedClassName);
for (int i = 0; i < nextTypeId; i++) {
if (allocatedIds[i].equals(slashedClassName)) {
return i;
}
}
return -1;
}
/**
* 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
*/
public static int getIdOrAllocateFor(String slashedClassName) {
int id = getIdFor(slashedClassName);
if (id == -1) {
id = allocateId(slashedClassName);
}
return id;
}
private synchronized static int allocateId(String slashedClassName) {
// Check again, in case two threads passed the -1 check in the getIdOrAllocateFor method
int id = getIdFor(slashedClassName);
if (id == -1) {
id = nextTypeId;
if (nextTypeId >= allocatedIds.length) {
size = size + 10;
// need to make more room
String[] newAllocatedIds = new String[size];
System.arraycopy(allocatedIds, 0, newAllocatedIds, 0, allocatedIds.length);
allocatedIds = newAllocatedIds;
}
allocatedIds[id] = slashedClassName;
nextTypeId++; // increase at the end once the value has been set in the array
}
return id;
}
public static String getTypenameById(int typeId) {
if (typeId > size) {
return null;
}
return allocatedIds[typeId];
}
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded;
/**
* 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
*
* @author Andy Clement
* @since 0.5.0
*/
public interface Plugin {
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded;
import org.springsource.loaded.agent.SpringLoadedPreProcessor;
/**
* Manages plugin interactions between the user and the agent. Allows registration/removal/etc of plugins
*
* <p>
* tag: API
*
* @author Andy Clement
* @since 0.7.2
*/
public class Plugins {
public static void registerGlobalPlugin(Plugin instance) {
SpringLoadedPreProcessor.registerGlobalPlugin(instance);
}
public static void unregisterGlobalPlugin(Plugin instance) {
SpringLoadedPreProcessor.unregisterGlobalPlugin(instance);
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded;
/**
* Represents a double dotted type pattern. For example: com.foo.bar..* - this has the same meaning as in AspectJ.
*
* @author Andy Clement
* @since 0.5.0
*/
public class PrefixTypePattern extends TypePattern {
private String pattern;
/**
* @param prefix prefix of the form 'com.foo.bar..*'
*/
public PrefixTypePattern(String pattern) {
this.pattern = pattern.substring(0, pattern.length() - 2); // chop off
// the '.*'
}
protected boolean internalMatches(String input) {
boolean b = input.startsWith(pattern);
return b;
}
public String toString() {
return "text:" + pattern + ".*";
}
}

View File

@@ -0,0 +1,153 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded;
import 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.
*
* @author Andy Clement
* @since 0.7.3
*/
public class QuickVisitor {
public static String[] getImplementedInterfaces(byte[] bytes) {
ClassReader fileReader = new ClassReader(bytes);
QuickVisitor1 qv = new QuickVisitor1();
try {
fileReader.accept(qv, ClassReader.SKIP_FRAMES);// TODO more flags to skip other things?
} catch (EarlyExitException eee) {
}
return qv.interfaces;
}
static class QuickVisitor1 extends EmptyClassVisitor {
String[] interfaces;
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
this.interfaces = interfaces;
// TODO is it truly easier to exit via exception than to visit the rest of it?
throw new EarlyExitException();
}
}
@SuppressWarnings("serial")
private static class EarlyExitException extends RuntimeException {
}
// public static int investigate(String slashedClassName, byte[] bytes) {
// ClassReader fileReader = new ClassReader(bytes);
// RewriteClassAdaptor classAdaptor = new RewriteClassAdaptor();
// fileReader.accept(classAdaptor, ClassReader.SKIP_FRAMES);
// return classAdaptor.hitCount;
// }
//
// static class RewriteClassAdaptor extends ClassAdapter implements Constants {
//
// int hitCount = 0;
// private ClassWriter cw;
// int bits = 0x0000;
// private String classname;
//
// private static boolean isInterceptable(String owner, String methodName) {
// return MethodInvokerRewriter.RewriteClassAdaptor.intercepted.contains(owner + "." + methodName);
// }
//
// public RewriteClassAdaptor() {
// // TODO should it also compute frames?
// super(new ClassWriter(ClassWriter.COMPUTE_MAXS));
// cw = (ClassWriter) cv;
// }
//
// public byte[] getBytes() {
// byte[] bytes = cw.toByteArray();
// return bytes;
// }
//
// public int getBits() {
// return bits;
// }
//
// @Override
// public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
// super.visit(version, access, name, signature, superName, interfaces);
// this.classname = name;
// }
//
// @Override
// 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);
// }
//
// class RewritingMethodAdapter extends MethodAdapter implements Opcodes, Constants {
//
// public RewritingMethodAdapter(MethodVisitor mv) {
// super(mv);
// }
//
// private boolean interceptReflection(String owner, String name, String desc) {
// if (isInterceptable(owner, name)) {
// hitCount++;
// System.out.println("SystemClassReflectionInvestigator: " + classname + " uses " + owner + "." + name + desc);
// }
// return false;
// }
//
// int unitializedObjectsCount = 0;
//
// @Override
// public void visitTypeInsn(final int opcode, final String type) {
// if (opcode == NEW) {
// unitializedObjectsCount++;
// }
// super.visitTypeInsn(opcode, type);
// }
//
// @Override
// public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc) {
// if (!GlobalConfiguration.interceptReflection || rewriteReflectiveCall(opcode, owner, name, desc)) {
// return;
// }
// if (opcode == INVOKESPECIAL) {
// unitializedObjectsCount--;
// }
// super.visitMethodInsn(opcode, owner, name, desc);
// }
//
// /**
// * Determine if a method call is a reflective call and an attempt should be made to rewrite it.
// *
// * @return true if the call was rewritten
// */
// private boolean rewriteReflectiveCall(int opcode, String owner, String name, String desc) {
// if (owner.length() > 10 && owner.charAt(8) == 'g'
// && (owner.startsWith("java/lang/reflect/") || owner.equals("java/lang/Class"))) {
// boolean rewritten = interceptReflection(owner, name, desc);
// if (rewritten) {
// return true;
// }
// }
// return false;
// }
//
// }
// }
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded;
// 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.
*
*
* @author Andy Clement
* @since 1.0.4
*/
public class RTH {
/**
* Collapse a String and int into an array
*/
public static Object[] collapse(String arg0, int arg1) {
return new Object[] { arg0, Integer.valueOf(arg1) };
}
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
/**
* A FieldReaderWriter implementation that simply uses reflection to set/get the fields.
*
* @author Andy Clement
* @since 0.5.0
*/
public class ReflectionFieldReaderWriter extends FieldReaderWriter {
private Field field;
public ReflectionFieldReaderWriter(Field findField) {
super();
this.field = findField;
}
@Override
public Object getStaticFieldValue(Class<?> type, SSMgr fieldAccessor) throws IllegalAccessException, IllegalArgumentException {
field.setAccessible(true);
return field.get(null);
}
@Override
public void setStaticFieldValue(Class<?> clazz, Object newValue, SSMgr fieldAccessor) throws IllegalAccessException {
field.setAccessible(true);
field.set(null, newValue);
}
@Override
public void setValue(Object instance, Object newValue, ISMgr fieldAccessor) throws IllegalAccessException {
field.setAccessible(true);
field.set(instance, newValue);
}
@Override
public Object getValue(Object instance, ISMgr fieldAccessor) throws IllegalAccessException, IllegalArgumentException {
field.setAccessible(true);
return field.get(instance);
}
@Override
public boolean isStatic() {
return Modifier.isStatic(field.getModifiers());
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded;
/**
* ReloadEventProcessor Plugins are called when a type is reloading. For information on registering them, see {@link Plugin}
*
* @author Andy Clement
* @since 0.5.0
*/
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.
*
* @param typename the (dotted) type name, for example java.lang.String
* @param clazz the Class object that has been reloaded
* @param encodedTimestamp an encoded time stamp for this version, containing chars (A-Za-z0-9)
* @return true if the static initializer should be re-run
*/
boolean shouldRerunStaticInitializer(String typename, Class<?> clazz, String encodedTimestamp);
// 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.
*
* @param typename the (dotted) type name, for example java.lang.String
* @param clazz the Class object that has been reloaded
* @param encodedTimestamp an encoded time stamp for this version, containing chars (A-Za-z0-9)
*/
void reloadEvent(String typename, Class<?> clazz, String encodedTimestamp);
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded;
/**
*
* @author Andy Clement
* @since 0.5.0
*/
@SuppressWarnings("serial")
public class ReloadException extends RuntimeException {
public ReloadException(String message, Exception cause) {
super(message, cause);
}
public ReloadException(String message) {
super(message);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,176 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
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).
*
* @author Andy Clement
* @since 0.5.0
*/
public class SSMgr {
private static Logger log = Logger.getLogger(SSMgr.class.getName());
Map<String, Map<String, Object>> values = new HashMap<String, Map<String, Object>>();
public Object getValue(ReloadableType rtype, String name) throws IllegalAccessException {
// System.out.println("SSMgr.getValue(rtype=" + rtype + ",name=" + name + ")");
Object result = null;
// quick look to see if it is nearby (searches up supertype hierarchy, but only up to the topmost reloadabletype)
FieldMember fieldmember = rtype.findStaticField(name);//InstanceField(name);
// Why can fieldmember be null?
// 1. Field really does not exist - shouldn't really be possible if the code is 'valid'
// 2. Field is inherited from a supertype (usually because a reload has occurred)
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
+ ": clinit running late?");
return null;
}
result = flr.getStaticFieldValue(rtype.getClazz(), this);
} else {
if (!fieldmember.isStatic()) {
throw new IncompatibleClassChangeError("Expected static field " + rtype.dottedtypename + "."
+ fieldmember.getName());
}
String declaringTypeName = fieldmember.getDeclaringTypeName();
Map<String, Object> typeLevelValues = values.get(declaringTypeName);
boolean knownField = false;
if (typeLevelValues != null) {
knownField = typeLevelValues.containsKey(name);
}
if (knownField) {
result = typeLevelValues.get(name);
}
// If a field has been deleted it may 'reveal' a field in a supertype. The revealed field may be in a type
// not yet dealt with. In this case typeLevelValues may be null (type not seen before) or the typelevelValues
// may not have heard of our field name. In these cases we need to go and find the field and 'relocate' it
// into our map, where it will be processed from now on.
// These revealed fields are not necessarily in the original form of the type so cannot always be accessed via reflection
if (typeLevelValues == null || !knownField) {
// TODO lookup performance?
FieldMember fieldOnOriginalType = rtype.getTypeRegistry().getReloadableType(declaringTypeName).getTypeDescriptor()
.getField(name);
if (fieldOnOriginalType != null) {
// Copy that field into the map... where it is going to live from now on
ReloadableType rt = rtype.getTypeRegistry().getReloadableType(fieldmember.getDeclaringTypeName());
try {
Field f = rt.getClazz().getDeclaredField(name);
f.setAccessible(true);
result = f.get(null);
if (typeLevelValues == null) {
typeLevelValues = new HashMap<String, Object>();
values.put(declaringTypeName, typeLevelValues);
}
typeLevelValues.put(name, result);
} catch (Exception e) {
throw new IllegalStateException("Unexpectedly unable to access field " + name + " on type "
+ rt.getClazz().getName(), e);
}
} 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) {
typeLevelValues = new HashMap<String, Object>();
values.put(declaringTypeName, typeLevelValues);
}
typeLevelValues.put(name, result);
return result;
}
}
if (result != null) {
result = Utils.checkCompatibility(rtype.getTypeRegistry(), result, fieldmember.getDescriptor());
if (result == null) {
typeLevelValues.remove(fieldmember.getName());
}
}
result = Utils.toResultCheckIfNull(result, fieldmember.getDescriptor());
}
// if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.FINER)) {
// log.finer("<getValue() value of " + name + " is " + result);
// }
return result;
}
// TODO ensure can't set field values on interfaces (constants)? (guess we should never encounter the code that tries it)
public void setValue(ReloadableType rtype, Object newValue, String name) throws IllegalAccessException {
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.FINEST)) {
log.finest("Static field set: " + rtype.getName() + "." + name + " " + newValue);
}
// TODO can setValue for static fields ignore interfaces? since we know all those fields will be final - what about the clinit calls to setup the fields though?
FieldMember fieldmember = rtype.findStaticField(name);//InstanceField(name);
if (fieldmember == null) {
// If the field is null, there are two possible reasons:
// 1. The field does not exist in the hierarchy at all
// 2. The field is on a type just above our topmost reloadable type
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
+ ": clinit running late?");
return;
}
frw.setStaticFieldValue(rtype.getClazz(), newValue, this);
} else {
if (!fieldmember.isStatic()) {
throw new IncompatibleClassChangeError("Expected static field " + rtype.dottedtypename + "."
+ fieldmember.getName());
}
Map<String, Object> typeValues = values.get(fieldmember.getDeclaringTypeName());//rtype.getName());
if (typeValues == null) {
typeValues = new HashMap<String, Object>();
values.put(fieldmember.getDeclaringTypeName(), typeValues);
}
typeValues.put(name, newValue);
}
}
private String valuesToString() {
StringBuilder s = new StringBuilder();
s.append("FieldAccessor:" + System.identityHashCode(this)).append("\n");
for (Map.Entry<String, Map<String, Object>> entry : values.entrySet()) {
s.append("Type " + entry.getKey()).append("\n");
for (Map.Entry<String, Object> entry2 : entry.getValue().entrySet()) {
s.append(" " + entry2.getKey() + "=" + entry2.getValue()).append("\n");
}
}
return s.toString();
}
public String toString() {
return valuesToString();
}
Map<String, Map<String, Object>> getMap() {
return values;
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded;
/**
* API for directly interacting with SpringLoaded.
*
* <p>
* tag: API
*
* @author Andy Clement
* @since 0.8.0
*/
public class SpringLoaded {
/**
* Force a reload of an existing type.
*
* @param clazz the class to be reloaded
* @param newbytedata 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.
*/
public static int loadNewVersionOfType(Class<?> clazz, byte[] newbytedata) {
return loadNewVersionOfType(clazz.getClassLoader(), clazz.getName(), newbytedata);
}
/**
* Force a reload of an existing type.
*
* @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 newbytedata 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.
*/
public static int loadNewVersionOfType(ClassLoader classLoader, String dottedClassname, byte[] newbytedata) {
try {
// Obtain the type registry of interest
TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(classLoader);
if (typeRegistry == null) {
return 1;
}
// Find the reloadable type
ReloadableType reloadableType = typeRegistry.getReloadableType(dottedClassname.replace('.', '/'));
if (reloadableType == null) {
return 2;
}
// Create a unique version tag for this reload attempt
String tag = Utils.encode(System.currentTimeMillis());
boolean reloaded = reloadableType.loadNewVersion(tag, newbytedata);
return reloaded ? 0 : 3;
} catch (Exception e) {
e.printStackTrace();
return 4;
}
}
}

View File

@@ -0,0 +1,133 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded;
import org.objectweb.asm.ClassAdapter;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodAdapter;
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).
*
* @author Andy Clement
* @since 0.7.3
*/
public class SystemClassReflectionInvestigator {
public static int investigate(String slashedClassName, byte[] bytes) {
ClassReader fileReader = new ClassReader(bytes);
RewriteClassAdaptor classAdaptor = new RewriteClassAdaptor();
fileReader.accept(classAdaptor, ClassReader.SKIP_FRAMES);
return classAdaptor.hitCount;
}
static class RewriteClassAdaptor extends ClassAdapter implements Constants {
int hitCount = 0;
private ClassWriter cw;
int bits = 0x0000;
private String classname;
private static boolean isInterceptable(String owner, String methodName) {
return MethodInvokerRewriter.RewriteClassAdaptor.intercepted.contains(owner + "." + methodName);
}
public RewriteClassAdaptor() {
// TODO should it also compute frames?
super(new ClassWriter(ClassWriter.COMPUTE_MAXS));
cw = (ClassWriter) cv;
}
public byte[] getBytes() {
byte[] bytes = cw.toByteArray();
return bytes;
}
public int getBits() {
return bits;
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
super.visit(version, access, name, signature, superName, interfaces);
this.classname = name;
}
@Override
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);
}
class RewritingMethodAdapter extends MethodAdapter implements Opcodes, Constants {
public RewritingMethodAdapter(MethodVisitor mv) {
super(mv);
}
private boolean interceptReflection(String owner, String name, String desc) {
if (isInterceptable(owner, name)) {
hitCount++;
System.out.println("SystemClassReflectionInvestigator: " + classname + " uses " + owner + "." + name + desc);
}
return false;
}
int unitializedObjectsCount = 0;
@Override
public void visitTypeInsn(final int opcode, final String type) {
if (opcode == NEW) {
unitializedObjectsCount++;
}
super.visitTypeInsn(opcode, type);
}
@Override
public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc) {
if (!GlobalConfiguration.interceptReflection || rewriteReflectiveCall(opcode, owner, name, desc)) {
return;
}
if (opcode == INVOKESPECIAL) {
unitializedObjectsCount--;
}
super.visitMethodInsn(opcode, owner, name, desc);
}
/**
* Determine if a method call is a reflective call and an attempt should be made to rewrite it.
*
* @return true if the call was rewritten
*/
private boolean rewriteReflectiveCall(int opcode, String owner, String name, String desc) {
if (owner.length() > 10 && owner.charAt(8) == 'g'
&& (owner.startsWith("java/lang/reflect/") || owner.equals("java/lang/Class"))) {
boolean rewritten = interceptReflection(owner, name, desc);
if (rewritten) {
return true;
}
}
return false;
}
}
}
}

View File

@@ -0,0 +1,977 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.objectweb.asm.ClassAdapter;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodAdapter;
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.
* <p>
* 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:
*
* <p>
* Due to ReflectionNavigator:
* <ul>
* <li>getDeclaredFields
* <li>getDeclaredField
* <li>getField
* <li>getModifiers
* <li>getDeclaredConstructor
* <li>getDeclaredMethods
* <li>getDeclaredMethod</li>
* <p>
* Due to ProxyGenerator
* <ul>
* <li>getMethods
* </ul>
*
* <p>
* This class modifiers 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
*/
public class SystemClassReflectionRewriter {
private static Logger log = Logger.getLogger(SystemClassReflectionRewriter.class.getName());
public static RewriteResult rewrite(String slashedClassName, byte[] bytes) {
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) {
log.info("SystemClassReflectionRewriter running for " + slashedClassName);
}
ClassReader fileReader = new ClassReader(bytes);
RewriteClassAdaptor classAdaptor = new RewriteClassAdaptor();
// TODO always skip frames? or just for javassist things?
fileReader.accept(classAdaptor, ClassReader.SKIP_FRAMES);
return new RewriteResult(classAdaptor.getBytes(), classAdaptor.getBits());
}
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
public final int bits;
public RewriteResult(byte[] bytes, int bits) {
this.bytes = bytes;
this.bits = bits;
}
public String summarize() {
StringBuilder s = new StringBuilder();
s.append((bits & JLC_GETDECLAREDCONSTRUCTOR) != 0 ? "getDeclaredConstructor()" : "");
s.append((bits & JLC_GETCONSTRUCTOR) != 0 ? "getConstructor()" : "");
s.append((bits & JLC_GETMODIFIERS) != 0 ? "getModifiers()" : "");
s.append((bits & JLC_GETDECLAREDFIELDS) != 0 ? "getDeclaredFields() " : "");
s.append((bits & JLC_GETDECLAREDFIELD) != 0 ? "getDeclaredField() " : "");
s.append((bits & JLC_GETFIELD) != 0 ? "getField() " : "");
s.append((bits & JLC_GETDECLAREDMETHODS) != 0 ? "getDeclaredMethods() " : "");
s.append((bits & JLC_GETDECLAREDMETHOD) != 0 ? "getDeclaredMethod() " : "");
s.append((bits & JLC_GETMETHOD) != 0 ? "getMethod() " : "");
s.append((bits & JLC_GETMETHODS) != 0 ? "getMethods() " : "");
return s.toString().trim();
}
}
static class RewriteClassAdaptor extends ClassAdapter implements Constants {
private ClassWriter cw;
int bits = 0x0000;
private String classname;
// enum SpecialRewrite { NotSpecial, java_io_ObjectStreamClass_2 };
// private SpecialRewrite special = SpecialRewrite.NotSpecial;
// TODO [perf] lookup like this really the fastest way?
private static boolean isInterceptable(String owner, String methodName) {
String s = new StringBuilder(owner).append(".").append(methodName).toString();
return MethodInvokerRewriter.RewriteClassAdaptor.intercepted.contains(s);
}
public RewriteClassAdaptor() {
// TODO should it also compute frames?
super(new ClassWriter(ClassWriter.COMPUTE_MAXS));
cw = (ClassWriter) cv;
}
public byte[] getBytes() {
byte[] bytes = cw.toByteArray();
return bytes;
}
public int getBits() {
return bits;
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
super.visit(version, access, name, signature, superName, interfaces);
this.classname = name;
// if (classname.equals("java/io/ObjectStreamClass$2")) {
// special = SpecialRewrite.java_io_ObjectStreamClass_2;
// }
}
@Override
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);
}
@Override
public void visitEnd() {
addExtraMethodsAndFields();
super.visitEnd();
}
private void addExtraMethodsAndFields() {
if ((bits & JLC_GETDECLAREDFIELDS) != 0) {
SystemClassReflectionGenerator.generateJLCGDFS(cw, classname);
}
if ((bits & JLC_GETDECLAREDFIELD) != 0) {
SystemClassReflectionGenerator.generateJLC(cw, classname, "getDeclaredField");
}
if ((bits & JLC_GETFIELD) != 0) {
SystemClassReflectionGenerator.generateJLC(cw, classname, "getField");
}
if ((bits & JLC_GETDECLAREDMETHODS) != 0) {
SystemClassReflectionGenerator.generateJLCGetXXXMethods(cw, classname, "getDeclaredMethods");
}
if ((bits & JLC_GETDECLAREDMETHOD) != 0) {
SystemClassReflectionGenerator.generateJLCMethod(cw, classname, "getDeclaredMethod");
}
if ((bits & JLC_GETMETHOD) != 0) {
SystemClassReflectionGenerator.generateJLCMethod(cw, classname, "getMethod");
}
if ((bits & JLC_GETMODIFIERS) != 0) {
SystemClassReflectionGenerator.generateJLCGMODS(cw, classname);
}
if ((bits & JLC_GETDECLAREDCONSTRUCTOR) != 0) {
SystemClassReflectionGenerator.generateJLCGDC(cw, classname);
}
if ((bits & JLC_GETMETHODS) != 0) {
SystemClassReflectionGenerator.generateJLCGetXXXMethods(cw, classname, "getMethods");
}
if ((bits & JLC_GETCONSTRUCTOR) != 0) {
SystemClassReflectionGenerator.generateJLCGC(cw, classname);
}
}
class RewritingMethodAdapter extends MethodAdapter implements Opcodes, Constants {
public RewritingMethodAdapter(MethodVisitor mv) {
super(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:
* <ul>
* <li>whether to rewrite it
* <li>what method should be called instead
* </ul>
*
* @return true if the call was modified/intercepted
*/
private boolean interceptReflection(String owner, String name, String desc) {
if (isInterceptable(owner, name)) {
return callReflectiveInterceptor(owner, name, desc, mv);
}
return false;
}
int unitializedObjectsCount = 0;
@Override
public void visitTypeInsn(final int opcode, final String type) {
if (opcode == NEW) {
unitializedObjectsCount++;
}
super.visitTypeInsn(opcode, type);
}
@Override
public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc) {
if (!GlobalConfiguration.interceptReflection || rewriteReflectiveCall(opcode, owner, name, desc)) {
return;
}
if (opcode == INVOKESPECIAL) {
unitializedObjectsCount--;
}
// if (special!=SpecialRewrite.NotSpecial) {
// // Special cases:
// if (special==SpecialRewrite.java_io_ObjectStreamClass_2) {
// // The class java.io.ObjectStreamClass is loaded too early for us to modify and yet it uses reflection.
// // That means we need to modify calls to that class instead.
//
// // The anonymous inner type $2 makes a call to
// // 66: invokestatic #10; //Method java/io/ObjectStreamClass.access$700:(Ljava/lang/Class;)Ljava/lang/Long;
// // which is the accessor method for the private method 'Long getDeclaredSUID()' in ObjectStreamClass. Redirect this
// // method to a helper that can retrieve the suid and be rewritten.
// // TODO skip descriptor check, surely name is enough?
//// if (owner.equals("java/io/ObjectStreamClass") && name.equals("access$700") && desc.equals("(Ljava/lang/Class;)Ljava/lang/Long;")) {
//// // 1. retrieve the serialVersionUID field
//// mv.visitLdcInsn("serialVersionUID");
//// bits|=JLC_GETDECLAREDFIELD;
//// mv.visitMethodInsn(INVOKESTATIC,classname, jlcgdf, jlcgdfDescriptor);
////
////
//// }
////// private static Long getDeclaredSUID(Class cl) {
////// try {
////// Field f = cl.getDeclaredField("serialVersionUID");
////// int mask = Modifier.STATIC | Modifier.FINAL;
////// if ((f.getModifiers() & mask) == mask) {
////// f.setAccessible(true);
////// return Long.valueOf(f.getLong(null));
////// }
////// } catch (Exception ex) {
////// }
////// return null;
////// }
// }
// }
super.visitMethodInsn(opcode, owner, name, desc);
}
/**
* Determine if a method call is a reflective call and an attempt should be made to rewrite it.
*
* @return true if the call was rewritten
*/
private boolean rewriteReflectiveCall(int opcode, String owner, String name, String desc) {
if (owner.length() > 10 && owner.charAt(8) == 'g'
&& (owner.startsWith("java/lang/reflect/") || owner.equals("java/lang/Class"))) {
boolean rewritten = interceptReflection(owner, name, desc);
if (rewritten) {
return true;
}
}
return false;
}
private boolean callReflectiveInterceptor(String owner, String name, String desc, MethodVisitor mv) {
if (owner.equals("java/lang/Class")) {
if (name.equals("getDeclaredFields")) {
// stack on arrival: <Class instance>
bits |= JLC_GETDECLAREDFIELDS;
mv.visitMethodInsn(INVOKESTATIC, classname, jlcgdfs, jlcgdfsDescriptor);
return true;
} else if (name.equals("getDeclaredField")) {
// stack on arrival: <Class instance> <String fieldname>
bits |= JLC_GETDECLAREDFIELD;
mv.visitMethodInsn(INVOKESTATIC, classname, jlcgdf, jlcgdfDescriptor);
return true;
} else if (name.equals("getField")) {
// stack on arrival: <Class instance> <String fieldname>
bits |= JLC_GETFIELD;
mv.visitMethodInsn(INVOKESTATIC, classname, jlcgf, jlcgfDescriptor);
return true;
} else if (name.equals("getDeclaredMethods")) {
// stack on arrival: <Class instance>
bits |= JLC_GETDECLAREDMETHODS;
mv.visitMethodInsn(INVOKESTATIC, classname, jlcgdms, jlcgdmsDescriptor);
return true;
} else if (name.equals("getDeclaredMethod")) {
// stack on arrival: <Class instance> <String methodname> <Class[] paramTypes>
bits |= JLC_GETDECLAREDMETHOD;
mv.visitMethodInsn(INVOKESTATIC, classname, jlcgdm, jlcgdmDescriptor);
return true;
} else if (name.equals("getMethod")) {
// stack on arrival: <Class instance> <String methodname> <Class[] paramTypes>
bits |= JLC_GETMETHOD;
mv.visitMethodInsn(INVOKESTATIC, classname, jlcgm, jlcgmDescriptor);
return true;
} else if (name.equals("getDeclaredConstructor")) {
// stack on arrival: <Class instance> <Class[] paramTypes>
bits |= JLC_GETDECLAREDCONSTRUCTOR;
mv.visitMethodInsn(INVOKESTATIC, classname, jlcgdc, jlcgdcDescriptor);
return true;
} else if (name.equals("getConstructor")) {
// stack on arrival: <Class instance> <Class[] paramTypes>
bits |= JLC_GETCONSTRUCTOR;
mv.visitMethodInsn(INVOKESTATIC, classname, jlcgc, jlcgcDescriptor);
return true;
} else if (name.equals("getModifiers")) {
// stack on arrival: <Class instance>
bits |= JLC_GETMODIFIERS;
mv.visitMethodInsn(INVOKESTATIC, classname, jlcgmods, jlcgmodsDescriptor);
return true;
} else if (name.equals("getMethods")) {
// stack on arrival: <class instance>
bits |= JLC_GETMETHODS;
mv.visitMethodInsn(INVOKESTATIC, classname, jlcgms, jlcgmsDescriptor);
return true;
} 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")) {
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;
}
}
System.err.println("!!! SystemClassReflectionRewriter: nyi for " + owner + "." + name);
return false;
//throw new IllegalStateException("nyi for " + owner + "." + name);
}
}
}
}
/**
* This helper class will generate the fields/methods in the system classes that are being rewritten.
*/
class SystemClassReflectionGenerator implements Constants {
// public static Method __sljlcgdfs;
// @SuppressWarnings("unused")
// private static Field[] __sljlcgdfs(Class<?> clazz) {
// if (__sljlcgdfs == null) {
// return clazz.getDeclaredFields();
// }
// try {
// return (Field[]) __sljlcgdfs.invoke(null, clazz);
// } catch (Exception e) {
// return null;
// }
// }
// public static Method __sljlcgdms;
// @SuppressWarnings("unused")
// private static Method[] __sljlcgdms(Class<?> clazz) {
// if (__sljlcgdms == null) {
// return clazz.getDeclaredMethods();
// }
// try {
// return (Method[]) __sljlcgdms.invoke(null, clazz);
// } catch (Exception e) {
// return null;
// }
// }
// public static Method __sljlcgdf;
// @SuppressWarnings("unused")
// private static Field __sljlcgdf(Class<?> clazz, String fieldname) throws NoSuchFieldException {
// if (__sljlcgdf == null) {
// return clazz.getDeclaredField(fieldname);
// }
// try {
// return (Field) __sljlcgdf.invoke(null, clazz, fieldname);
// } catch (InvocationTargetException ite) {
// if (ite.getCause() instanceof NoSuchFieldException) {
// throw (NoSuchFieldException) ite.getCause();
// }
// } catch (Exception e) {
// }
// return null;
// }
// public static Method __sljlcgdm;
//
// @SuppressWarnings("unused")
// private static Method __sljlcgdm(Class<?> clazz, String methodname, Class... parameterTypes) throws NoSuchMethodException {
// if (__sljlcgdm == null) {
// return clazz.getDeclaredMethod(methodname, parameterTypes);
// }
// try {
// // if (parameterTypes == null) {
// return (Method) __sljlcgdm.invoke(null, clazz, methodname, parameterTypes);
// // } else {
// // Object[] params = new Object[2 + parameterTypes.length];
// // System.arraycopy(parameterTypes, 0, params, 2, parameterTypes.length);
// // params[0] = clazz;
// // params[1] = methodname;
// // return (Method) __sljlcgdm.invoke(null, clazz, methodname, parameterTypes);
// // }
// } catch (InvocationTargetException ite) {
// ite.printStackTrace();
// if (ite.getCause() instanceof NoSuchMethodException) {
// throw (NoSuchMethodException) ite.getCause();
// }
// } catch (Exception e) {
// e.printStackTrace();
// }
// return null;
// }
// public static Method __sljlcgdc;
//
// @SuppressWarnings("unused")
// private static Constructor __sljlcgdc(Class<?> clazz, Class... parameterTypes) throws NoSuchMethodException {
// if (__sljlcgdc == null) {
// return clazz.getDeclaredConstructor(parameterTypes);
// }
// try {
// return (Constructor) __sljlcgdc.invoke(null, clazz, parameterTypes);
// } catch (InvocationTargetException ite) {
// ite.printStackTrace();
// if (ite.getCause() instanceof NoSuchMethodException) {
// throw (NoSuchMethodException) ite.getCause();
// }
// } catch (Exception e) {
// e.printStackTrace();
// }
// return null;
// }
// public static Method __sljlcgmods;
//
// @SuppressWarnings("unused")
// private static int __sljlcgmods(Class<?> clazz) {
// if (__sljlcgmods == null) {
// return clazz.getModifiers();
// }
// try {
// return (Integer) __sljlcgmods.invoke(null, clazz);
// } catch (Exception e) {
// e.printStackTrace();
// return 0;
// }
// }
public static void generateJLCGMODS(ClassWriter cw, String classname) {
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",
"(Ljava/lang/Class<*>;)I", null);
mv.visitCode();
Label l0 = new Label();
Label l1 = new Label();
Label l2 = new Label();
mv.visitTryCatchBlock(l0, l1, l2, "java/lang/Exception");
Label l3 = new Label();
mv.visitLabel(l3);
mv.visitFieldInsn(GETSTATIC, classname, "__sljlcgmods", "Ljava/lang/reflect/Method;");
mv.visitJumpInsn(IFNONNULL, l0);
Label l4 = new Label();
mv.visitLabel(l4);
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getModifiers", "()I");
mv.visitInsn(IRETURN);
mv.visitLabel(l0);
mv.visitFieldInsn(GETSTATIC, classname, "__sljlcgmods", "Ljava/lang/reflect/Method;");
mv.visitInsn(ACONST_NULL);
mv.visitInsn(ICONST_1);
mv.visitTypeInsn(ANEWARRAY, "java/lang/Object");
mv.visitInsn(DUP);
mv.visitInsn(ICONST_0);
mv.visitVarInsn(ALOAD, 0);
mv.visitInsn(AASTORE);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/Method", "invoke",
"(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;");
mv.visitTypeInsn(CHECKCAST, "java/lang/Integer");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I");
mv.visitLabel(l1);
mv.visitInsn(IRETURN);
mv.visitLabel(l2);
mv.visitVarInsn(ASTORE, 1);
Label l5 = new Label();
mv.visitLabel(l5);
mv.visitVarInsn(ALOAD, 1);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Exception", "printStackTrace", "()V");
Label l6 = new Label();
mv.visitLabel(l6);
mv.visitInsn(ICONST_0);
mv.visitInsn(IRETURN);
Label l7 = new Label();
mv.visitLabel(l7);
// mv.visitLocalVariable("clazz", "Ljava/lang/Class;", "Ljava/lang/Class<*>;", l3, l7, 0);
// mv.visitLocalVariable("e", "Ljava/lang/Exception;", null, l5, l7, 1);
mv.visitMaxs(6, 2);
mv.visitEnd();
}
public static void generateJLCGDC(ClassWriter cw, String classname) {
FieldVisitor fv = cw.visitField(ACC_PUBLIC + ACC_STATIC, "__sljlcgdc", "Ljava/lang/reflect/Method;", null, null);
fv.visitEnd();
MethodVisitor mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC + ACC_VARARGS, "__sljlcgdc",
"(Ljava/lang/Class;[Ljava/lang/Class;)Ljava/lang/reflect/Constructor;", null,
new String[] { "java/lang/NoSuchMethodException" });
mv.visitCode();
Label l0 = new Label();
Label l1 = new Label();
Label l2 = new Label();
mv.visitTryCatchBlock(l0, l1, l2, "java/lang/reflect/InvocationTargetException");
Label l3 = new Label();
mv.visitTryCatchBlock(l0, l1, l3, "java/lang/Exception");
Label l4 = new Label();
mv.visitLabel(l4);
mv.visitFieldInsn(GETSTATIC, classname, "__sljlcgdc", "Ljava/lang/reflect/Method;");
mv.visitJumpInsn(IFNONNULL, l0);
Label l5 = new Label();
mv.visitLabel(l5);
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 1);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getDeclaredConstructor",
"([Ljava/lang/Class;)Ljava/lang/reflect/Constructor;");
mv.visitInsn(ARETURN);
mv.visitLabel(l0);
mv.visitFieldInsn(GETSTATIC, classname, "__sljlcgdc", "Ljava/lang/reflect/Method;");
mv.visitInsn(ACONST_NULL);
mv.visitInsn(ICONST_2);
mv.visitTypeInsn(ANEWARRAY, "java/lang/Object");
mv.visitInsn(DUP);
mv.visitInsn(ICONST_0);
mv.visitVarInsn(ALOAD, 0);
mv.visitInsn(AASTORE);
mv.visitInsn(DUP);
mv.visitInsn(ICONST_1);
mv.visitVarInsn(ALOAD, 1);
mv.visitInsn(AASTORE);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/Method", "invoke",
"(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;");
mv.visitTypeInsn(CHECKCAST, "java/lang/reflect/Constructor");
mv.visitLabel(l1);
mv.visitInsn(ARETURN);
mv.visitLabel(l2);
mv.visitVarInsn(ASTORE, 2);
Label l6 = new Label();
mv.visitLabel(l6);
mv.visitVarInsn(ALOAD, 2);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/InvocationTargetException", "printStackTrace", "()V");
Label l7 = new Label();
mv.visitLabel(l7);
mv.visitVarInsn(ALOAD, 2);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/InvocationTargetException", "getCause", "()Ljava/lang/Throwable;");
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;");
mv.visitTypeInsn(CHECKCAST, "java/lang/NoSuchMethodException");
mv.visitInsn(ATHROW);
mv.visitLabel(l3);
mv.visitVarInsn(ASTORE, 2);
Label l10 = new Label();
mv.visitLabel(l10);
// mv.visitVarInsn(ALOAD, 2);
// mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Exception", "printStackTrace", "()V");
mv.visitLabel(l8);
mv.visitInsn(ACONST_NULL);
mv.visitInsn(ARETURN);
Label l11 = new Label();
mv.visitLabel(l11);
// mv.visitLocalVariable("clazz", "Ljava/lang/Class;", "Ljava/lang/Class<*>;", l4, l11, 0);
// mv.visitLocalVariable("parameterTypes", "[Ljava/lang/Class;", null, l4, l11, 1);
// mv.visitLocalVariable("ite", "Ljava/lang/reflect/InvocationTargetException;", null, l6, l3, 2);
// mv.visitLocalVariable("e", "Ljava/lang/Exception;", null, l10, l8, 2);
mv.visitMaxs(6, 3);
mv.visitEnd();
}
public static void generateJLCGC(ClassWriter cw, String classname) {
FieldVisitor fv = cw.visitField(ACC_PUBLIC + ACC_STATIC, "__sljlcgc", "Ljava/lang/reflect/Method;", null, null);
fv.visitEnd();
MethodVisitor mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC + ACC_VARARGS, "__sljlcgc",
"(Ljava/lang/Class;[Ljava/lang/Class;)Ljava/lang/reflect/Constructor;", null,
new String[] { "java/lang/NoSuchMethodException" });
mv.visitCode();
Label l0 = new Label();
Label l1 = new Label();
Label l2 = new Label();
mv.visitTryCatchBlock(l0, l1, l2, "java/lang/reflect/InvocationTargetException");
Label l3 = new Label();
mv.visitTryCatchBlock(l0, l1, l3, "java/lang/Exception");
Label l4 = new Label();
mv.visitLabel(l4);
mv.visitFieldInsn(GETSTATIC, classname, "__sljlcgc", "Ljava/lang/reflect/Method;");
mv.visitJumpInsn(IFNONNULL, l0);
Label l5 = new Label();
mv.visitLabel(l5);
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 1);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getConstructor",
"([Ljava/lang/Class;)Ljava/lang/reflect/Constructor;");
mv.visitInsn(ARETURN);
mv.visitLabel(l0);
mv.visitFieldInsn(GETSTATIC, classname, "__sljlcgc", "Ljava/lang/reflect/Method;");
mv.visitInsn(ACONST_NULL);
mv.visitInsn(ICONST_2);
mv.visitTypeInsn(ANEWARRAY, "java/lang/Object");
mv.visitInsn(DUP);
mv.visitInsn(ICONST_0);
mv.visitVarInsn(ALOAD, 0);
mv.visitInsn(AASTORE);
mv.visitInsn(DUP);
mv.visitInsn(ICONST_1);
mv.visitVarInsn(ALOAD, 1);
mv.visitInsn(AASTORE);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/Method", "invoke",
"(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;");
mv.visitTypeInsn(CHECKCAST, "java/lang/reflect/Constructor");
mv.visitLabel(l1);
mv.visitInsn(ARETURN);
mv.visitLabel(l2);
mv.visitVarInsn(ASTORE, 2);
Label l6 = new Label();
mv.visitLabel(l6);
mv.visitVarInsn(ALOAD, 2);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/InvocationTargetException", "printStackTrace", "()V");
Label l7 = new Label();
mv.visitLabel(l7);
mv.visitVarInsn(ALOAD, 2);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/InvocationTargetException", "getCause", "()Ljava/lang/Throwable;");
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;");
mv.visitTypeInsn(CHECKCAST, "java/lang/NoSuchMethodException");
mv.visitInsn(ATHROW);
mv.visitLabel(l3);
mv.visitVarInsn(ASTORE, 2);
Label l10 = new Label();
mv.visitLabel(l10);
// mv.visitVarInsn(ALOAD, 2);
// mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Exception", "printStackTrace", "()V");
mv.visitLabel(l8);
mv.visitInsn(ACONST_NULL);
mv.visitInsn(ARETURN);
Label l11 = new Label();
mv.visitLabel(l11);
// mv.visitLocalVariable("clazz", "Ljava/lang/Class;", "Ljava/lang/Class<*>;", l4, l11, 0);
// mv.visitLocalVariable("parameterTypes", "[Ljava/lang/Class;", null, l4, l11, 1);
// mv.visitLocalVariable("ite", "Ljava/lang/reflect/InvocationTargetException;", null, l6, l3, 2);
// mv.visitLocalVariable("e", "Ljava/lang/Exception;", null, l10, l8, 2);
mv.visitMaxs(6, 3);
mv.visitEnd();
}
public static void generateJLCMethod(ClassWriter cw, String classname, String membername, String methodname) {
FieldVisitor fv = cw.visitField(ACC_PUBLIC + ACC_STATIC, membername, "Ljava/lang/reflect/Method;", null, null);
fv.visitEnd();
MethodVisitor mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC + ACC_VARARGS, membername,
"(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;", null,
new String[] { "java/lang/NoSuchMethodException" });
mv.visitCode();
Label l0 = new Label();
Label l1 = new Label();
Label l2 = new Label();
mv.visitTryCatchBlock(l0, l1, l2, "java/lang/reflect/InvocationTargetException");
Label l3 = new Label();
mv.visitTryCatchBlock(l0, l1, l3, "java/lang/Exception");
Label l4 = new Label();
mv.visitLabel(l4);
mv.visitFieldInsn(GETSTATIC, classname, membername, "Ljava/lang/reflect/Method;");
mv.visitJumpInsn(IFNONNULL, l0);
Label l5 = new Label();
mv.visitLabel(l5);
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 1);
mv.visitVarInsn(ALOAD, 2);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", methodname,
"(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;");
mv.visitInsn(ARETURN);
mv.visitLabel(l0);
mv.visitFieldInsn(GETSTATIC, classname, membername, "Ljava/lang/reflect/Method;");
mv.visitInsn(ACONST_NULL);
mv.visitInsn(ICONST_3);
mv.visitTypeInsn(ANEWARRAY, "java/lang/Object");
mv.visitInsn(DUP);
mv.visitInsn(ICONST_0);
mv.visitVarInsn(ALOAD, 0);
mv.visitInsn(AASTORE);
mv.visitInsn(DUP);
mv.visitInsn(ICONST_1);
mv.visitVarInsn(ALOAD, 1);
mv.visitInsn(AASTORE);
mv.visitInsn(DUP);
mv.visitInsn(ICONST_2);
mv.visitVarInsn(ALOAD, 2);
mv.visitInsn(AASTORE);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/Method", "invoke",
"(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;");
mv.visitTypeInsn(CHECKCAST, "java/lang/reflect/Method");
mv.visitLabel(l1);
mv.visitInsn(ARETURN);
mv.visitLabel(l2);
mv.visitVarInsn(ASTORE, 3);
Label l6 = new Label();
mv.visitLabel(l6);
// Don't print the exception if just unwrapping it
// mv.visitVarInsn(ALOAD, 3);
// mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/InvocationTargetException", "printStackTrace", "()V");
Label l7 = new Label();
mv.visitLabel(l7);
mv.visitVarInsn(ALOAD, 3);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/InvocationTargetException", "getCause", "()Ljava/lang/Throwable;");
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;");
mv.visitTypeInsn(CHECKCAST, "java/lang/NoSuchMethodException");
mv.visitInsn(ATHROW);
mv.visitLabel(l3);
mv.visitVarInsn(ASTORE, 3);
Label l10 = new Label();
mv.visitLabel(l10);
mv.visitVarInsn(ALOAD, 3);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Exception", "printStackTrace", "()V");
mv.visitLabel(l8);
mv.visitInsn(ACONST_NULL);
mv.visitInsn(ARETURN);
Label l11 = new Label();
mv.visitLabel(l11);
// mv.visitLocalVariable("clazz", "Ljava/lang/Class;", "Ljava/lang/Class<*>;", l4, l11, 0);
// mv.visitLocalVariable("methodname", "Ljava/lang/String;", null, l4, l11, 1);
// mv.visitLocalVariable("parameterTypes", "[Ljava/lang/Class;", null, l4, l11, 2);
// mv.visitLocalVariable("ite", "Ljava/lang/reflect/InvocationTargetException;", null, l6, l3, 3);
// mv.visitLocalVariable("e", "Ljava/lang/Exception;", null, l10, l8, 3);
mv.visitMaxs(6, 4);
mv.visitEnd();
}
public static void generateJLCMethod(ClassWriter cw, String classname, String operation) {
if (operation.equals("getDeclaredMethod")) {
generateJLCMethod(cw, classname, "__sljlcgdm", "getDeclaredMethod");
} else if (operation.equals("getMethod")) {
generateJLCMethod(cw, classname, "__sljlcgm", "getMethod");
} else {
throw new IllegalStateException("nyi:" + operation);
}
}
public static void generateJLC(ClassWriter cw, String classname, String operation) {
if (operation.equals("getDeclaredField")) {
generateJLCGDF(cw, classname, "__sljlcgdf", "getDeclaredField");
} else if (operation.equals("getField")) {
generateJLCGDF(cw, classname, "__sljlcgf", "getField");
} else {
throw new IllegalStateException("nyi:" + operation);
}
}
public static void generateJLCGDF(ClassWriter cw, String classname, String fieldname, String methodname) {
FieldVisitor fv = cw.visitField(ACC_PUBLIC_STATIC, fieldname, "Ljava/lang/reflect/Method;", null, null);
fv.visitEnd();
MethodVisitor mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC, fieldname,
"(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/reflect/Field;", null,
new String[] { "java/lang/NoSuchFieldException" });
mv.visitCode();
Label l0 = new Label();
Label l1 = new Label();
Label l2 = new Label();
mv.visitTryCatchBlock(l0, l1, l2, "java/lang/reflect/InvocationTargetException");
Label l3 = new Label();
mv.visitTryCatchBlock(l0, l1, l3, "java/lang/Exception");
Label l4 = new Label();
mv.visitLabel(l4);
mv.visitFieldInsn(GETSTATIC, classname, fieldname, "Ljava/lang/reflect/Method;");
mv.visitJumpInsn(IFNONNULL, l0);
Label l5 = new Label();
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;");
mv.visitInsn(ARETURN);
mv.visitLabel(l0);
mv.visitFieldInsn(GETSTATIC, classname, fieldname, "Ljava/lang/reflect/Method;");
mv.visitInsn(ACONST_NULL);
mv.visitInsn(ICONST_2);
mv.visitTypeInsn(ANEWARRAY, "java/lang/Object");
mv.visitInsn(DUP);
mv.visitInsn(ICONST_0);
mv.visitVarInsn(ALOAD, 0);
mv.visitInsn(AASTORE);
mv.visitInsn(DUP);
mv.visitInsn(ICONST_1);
mv.visitVarInsn(ALOAD, 1);
mv.visitInsn(AASTORE);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/Method", "invoke",
"(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;");
mv.visitTypeInsn(CHECKCAST, "java/lang/reflect/Field");
mv.visitLabel(l1);
mv.visitInsn(ARETURN);
mv.visitLabel(l2);
mv.visitVarInsn(ASTORE, 2);
Label l6 = new Label();
mv.visitLabel(l6);
mv.visitVarInsn(ALOAD, 2);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/InvocationTargetException", "getCause", "()Ljava/lang/Throwable;");
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;");
mv.visitTypeInsn(CHECKCAST, "java/lang/NoSuchFieldException");
mv.visitInsn(ATHROW);
mv.visitLabel(l3);
mv.visitVarInsn(ASTORE, 2);
mv.visitLabel(l7);
mv.visitInsn(ACONST_NULL);
mv.visitInsn(ARETURN);
Label l9 = new Label();
mv.visitLabel(l9);
// mv.visitLocalVariable("clazz", "Ljava/lang/Class;", "Ljava/lang/Class<*>;", l4, l9, 0);
// mv.visitLocalVariable("fieldname", "Ljava/lang/String;", null, l4, l9, 1);
// mv.visitLocalVariable("ite", "Ljava/lang/reflect/InvocationTargetException;", null, l6, l3, 2);
mv.visitMaxs(6, 3);
mv.visitEnd();
}
public static void generateJLCGetXXXMethods(ClassWriter cw, String classname, String variant) {
if (variant.equals("getDeclaredMethods")) {
generateJLCGDMS(cw, classname, "__sljlcgdms", "getDeclaredMethods");
} else if (variant.equals("getMethods")) {
generateJLCGDMS(cw, classname, "__sljlcgms", "getMethods");
} else {
throw new IllegalStateException(variant);
}
}
// TODO remove extraneous visits to things like lvar names
public static void generateJLCGDMS(ClassWriter cw, String classname, String field, String methodname) {
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,
null);
mv.visitCode();
Label l0 = new Label();
Label l1 = new Label();
Label l2 = new Label();
mv.visitTryCatchBlock(l0, l1, l2, "java/lang/Exception");
Label l3 = new Label();
mv.visitLabel(l3);
mv.visitFieldInsn(GETSTATIC, classname, field, "Ljava/lang/reflect/Method;");
mv.visitJumpInsn(IFNONNULL, l0);
Label l4 = new Label();
mv.visitLabel(l4);
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", methodname, "()[Ljava/lang/reflect/Method;");
mv.visitInsn(ARETURN);
mv.visitLabel(l0);
mv.visitFieldInsn(GETSTATIC, classname, field, "Ljava/lang/reflect/Method;");
mv.visitInsn(ACONST_NULL);
mv.visitInsn(ICONST_1);
mv.visitTypeInsn(ANEWARRAY, "java/lang/Object");
mv.visitInsn(DUP);
mv.visitInsn(ICONST_0);
mv.visitVarInsn(ALOAD, 0);
mv.visitInsn(AASTORE);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/Method", "invoke",
"(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;");
mv.visitTypeInsn(CHECKCAST, "[Ljava/lang/reflect/Method;");
mv.visitLabel(l1);
mv.visitInsn(ARETURN);
mv.visitLabel(l2);
mv.visitVarInsn(ASTORE, 1);
Label l5 = new Label();
mv.visitLabel(l5);
mv.visitInsn(ACONST_NULL);
mv.visitInsn(ARETURN);
Label l6 = new Label();
mv.visitLabel(l6);
// mv.visitLocalVariable("clazz", "Ljava/lang/Class;", "Ljava/lang/Class<*>;", l3, l6, 0);
// mv.visitLocalVariable("e", "Ljava/lang/Exception;", null, l5, l6, 1);
mv.visitMaxs(6, 2);
mv.visitEnd();
}
public static void generateJLCGDFS(ClassWriter cw, String classname) {
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;",
null, null);
mv.visitCode();
Label l0 = new Label();
Label l1 = new Label();
Label l2 = new Label();
mv.visitTryCatchBlock(l0, l1, l2, "java/lang/Exception");
Label l3 = new Label();
mv.visitLabel(l3);
mv.visitFieldInsn(GETSTATIC, classname, "__sljlcgdfs", "Ljava/lang/reflect/Method;");
mv.visitJumpInsn(IFNONNULL, l0);
Label l4 = new Label();
mv.visitLabel(l4);
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getDeclaredFields", "()[Ljava/lang/reflect/Field;");
mv.visitInsn(ARETURN);
mv.visitLabel(l0);
mv.visitFieldInsn(GETSTATIC, classname, "__sljlcgdfs", "Ljava/lang/reflect/Method;");
mv.visitInsn(ACONST_NULL);
mv.visitInsn(ICONST_1);
mv.visitTypeInsn(ANEWARRAY, "java/lang/Object");
mv.visitInsn(DUP);
mv.visitInsn(ICONST_0);
mv.visitVarInsn(ALOAD, 0);
mv.visitInsn(AASTORE);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/Method", "invoke",
"(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;");
mv.visitTypeInsn(CHECKCAST, "[Ljava/lang/reflect/Field;");
mv.visitLabel(l1);
mv.visitInsn(ARETURN);
mv.visitLabel(l2);
mv.visitVarInsn(ASTORE, 1);
Label l5 = new Label();
mv.visitLabel(l5);
mv.visitInsn(ACONST_NULL);
mv.visitInsn(ARETURN);
Label l6 = new Label();
mv.visitLabel(l6);
// mv.visitLocalVariable("clazz", "Ljava/lang/Class;", "Ljava/lang/Class<*>;", l3, l6, 0);
// mv.visitLocalVariable("e", "Ljava/lang/Exception;", null, l5, l6, 1);
mv.visitMaxs(6, 2);
mv.visitEnd();
}
// Can be useful for debugging, insert printlns
// private static void insertPrintln(MethodVisitor mv, String message) {
// mv.visitFieldInsn(GETSTATIC, "java/lang/System", "err", "Ljava/io/PrintStream;");
// mv.visitLdcInsn(message);
// mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V");
// }
}

View File

@@ -0,0 +1,183 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded;
import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.CodeSource;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
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.
*
* @author Andy Clement
* @since 0.7.3
*/
public class SystemPropertyConfiguredIsReloadableTypePlugin implements IsReloadableTypePlugin {
public final static boolean debug;
static {
boolean value = false;
try {
value = System.getProperty("springloaded.directoriesContainingReloadableCode.debug", "false").equalsIgnoreCase("true");
} catch (Exception e) {
}
debug = value;
}
public SystemPropertyConfiguredIsReloadableTypePlugin() {
if (debug) {
System.out.println("SystemPropertyConfiguredIsReloadableTypePlugin: instantiated");
}
}
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(String typename, ProtectionDomain protectionDomain, byte[] bytes) {
if (debug) {
System.out.println("SystemPropertyConfiguredIsReloadableTypePlugin: entered, for typename " + typename);
}
if (protectionDomain == null) {
return ReloadDecision.PASS;
}
String reloadableDirs = System.getProperty("springloaded.directoriesContainingReloadableCode");
if (debug) {
System.out.println("SystemPropertyConfiguredIsReloadableTypePlugin: reloadableDirs=" + reloadableDirs);
}
if (reloadableDirs == null) {
return ReloadDecision.PASS;
} else {
if (mostRecentReloadableDirs != reloadableDirs) {
synchronized (includes) {
if (mostRecentReloadableDirs != reloadableDirs) {
includes.clear();
excludes.clear();
// update our cached information
StringTokenizer st = new StringTokenizer(reloadableDirs, ",");
while (st.hasMoreTokens()) {
String nextDir = st.nextToken();
boolean isNot = nextDir.charAt(0) == '!';
if (isNot) {
excludes.add(nextDir.substring(1));
} else {
includes.add(nextDir);
}
}
mostRecentReloadableDirs = reloadableDirs;
}
}
}
}
// Typical example:
// typename = com/vmware/rabbit/HomeController
// codeSource.getLocation() = file:/Users/aclement/springsource/tc-server-developer-2.1.1.RELEASE/spring-insight-instance/wtpwebapps/hello-rabbit-client/WEB-INF/classes/com/vmware/rabbit/HomeController.class
CodeSource codeSource = protectionDomain.getCodeSource();
if (codeSource == null || codeSource.getLocation() == null) {
return ReloadDecision.NO; // nothing to watch...
// if (debug) {
// System.out.println("SystemPropertyConfiguredIsReloadableTypePlugin: " + typename + " does not have a codeSource");
// }
} else {
if (debug) {
System.out.println("SystemPropertyConfiguredIsReloadableTypePlugin: " + typename + " codeSource.getLocation() is "
+ codeSource.getLocation());
}
}
try {
URI uri = codeSource.getLocation().toURI();
File file = new File(uri);
String path = file.toString();
synchronized (includes) {
for (String exclude : excludes) {
if (path.contains(exclude)) {
if (debug) {
System.out.println("SystemPropertyConfiguredIsReloadableTypePlugin: " + typename
+ " is not being made reloadable");
}
return ReloadDecision.NO;
}
}
for (String include : includes) {
if (path.contains(include)) {
if (debug) {
System.out.println("SystemPropertyConfiguredIsReloadableTypePlugin: " + typename
+ " is being made reloadable");
}
return ReloadDecision.YES;
}
}
}
// StringTokenizer st = new StringTokenizer(reloadableDirs, ",");
// while (st.hasMoreTokens()) {
// String nextDir = st.nextToken();
// boolean isNot = nextDir.charAt(0) == '!';
// if (isNot)
// nextDir = nextDir.substring(1);
// if (path.contains(nextDir)) {
// if (isNot) {
// if (debug) {
// System.out.println("SystemPropertyConfiguredIsReloadableTypePlugin: " + typename
// + " is not being made reloadable");
// }
// return ReloadDecision.NO;
// } else {
// if (debug) {
// System.out.println("SystemPropertyConfiguredIsReloadableTypePlugin: " + typename
// + " is being made reloadable");
// }
// return ReloadDecision.YES;
// }
// }
// }
} catch (URISyntaxException e) {
e.printStackTrace();
} 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());
}
}
}
if (debug) {
System.out.println("SystemPropertyConfiguredIsReloadableTypePlugin: " + typename + " is being PASSed on");
}
return ReloadDecision.PASS;
}
}

View File

@@ -0,0 +1,265 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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.
*
* @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
| 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() {
StringBuilder s = new StringBuilder();
s.append("TypeDelta Summary\n");
// type declaration
s.append("TypeDeclaration changes:\n");
if (hasTypeVersionChanged()) {
s.append("typeversion changed: o=" + oVersion + " n=" + nVersion + "\n");
}
if (hasTypeAccessChanged()) {
s.append("typeaccess changed: o=" + oAccess + " n=" + nAccess + "\n");
}
if (hasTypeSupertypeChanged()) {
s.append("typesupertype changed: o=" + oSuperName + " n=" + nSuperName + "\n");
}
if (hasTypeInterfacesChanged()) {
s.append("typeinterfaces changed: o=" + oInterfaces + " n=" + nInterfaces + "\n");
}
if (hasTypeNameChanged()) {
s.append("typename changed: o=" + oName + " n=" + nName + "\n");
}
if (hasTypeSignatureChanged()) {
s.append("typesignature changed: o=" + oSignature + " n=" + nSignature + "\n");
}
// ...
return s.toString();
}
void setTypeAccessChange(int oldAccess, int newAccess) {
this.oAccess = oldAccess;
this.nAccess = newAccess;
this.changed |= CHANGED_ACCESS;
}
void setTypeNameChange(String oldName, String newName) {
this.oName = oldName;
this.nName = newName;
this.changed |= CHANGED_NAME;
}
void setTypeSignatureChange(String oldSignature, String newSignature) {
this.oSignature = oldSignature;
this.nSignature = newSignature;
this.changed |= CHANGED_SIGNATURE;
}
// public void setTypeVersionChange(int oldVersion, int newVersion) {
// this.oVersion = oldVersion;
// this.nVersion = newVersion;
// this.changed |= CHANGED_VERSION;
// }
void setTypeSuperNameChange(String oldSuperName, String newSuperName) {
this.oSuperName = oldSuperName;
this.nSuperName = newSuperName;
this.changed |= CHANGED_SUPERNAME;
}
void setTypeInterfacesChange(List<String> oldInterfaces, List<String> newInterfaces) {
this.oInterfaces = oldInterfaces;
this.nInterfaces = newInterfaces;
this.changed |= CHANGED_INTERFACES;
}
void addNewField(FieldNode nField) {
if (brandNewFields == null) {
brandNewFields = new HashMap<String, FieldNode>();
}
brandNewFields.put(nField.name, nField);
this.changed |= CHANGED_NEWFIELDS;
}
void addLostField(FieldNode lField) {
if (lostFields == null) {
lostFields = new HashMap<String, FieldNode>();
}
lostFields.put(lField.name, lField);
this.changed |= CHANGED_LOSTFIELDS;
}
void addChangedField(FieldDelta fd) {
if (changedFields == null) {
changedFields = new HashMap<String, FieldDelta>();
}
changedFields.put(fd.name, fd);
this.changed |= CHANGED_CHANGEDFIELDS;
}
void addNewMethod(MethodNode nMethod) {
if (brandNewMethods == null) {
brandNewMethods = new HashMap<String, MethodNode>();
}
brandNewMethods.put(nMethod.name + nMethod.desc, nMethod);
this.changed |= CHANGED_NEWMETHODS;
}
void addLostMethod(MethodNode nMethod) {
if (lostMethods == null) {
lostMethods = new HashMap<String, MethodNode>();
}
lostMethods.put(nMethod.name + nMethod.desc, nMethod);
this.changed |= CHANGED_LOSTMETHODS;
}
void addChangedMethod(MethodDelta md) {
if (changedMethods == null) {
changedMethods = new HashMap<String, MethodDelta>();
}
changedMethods.put(md.name + md.desc, md);
this.changed |= CHANGED_CHANGEDMETHODS;
}
public boolean hasTypeDeclarationChanged() {
return (changed & CHANGED_TYPE_MASK) != 0;
}
public boolean hasTypeNameChanged() {
return (changed & CHANGED_NAME) != 0;
}
public boolean hasTypeVersionChanged() {
return (changed & CHANGED_VERSION) != 0;
}
public boolean hasTypeAccessChanged() {
return (changed & CHANGED_ACCESS) != 0;
}
public boolean hasTypeSupertypeChanged() {
return (changed & CHANGED_SUPERNAME) != 0;
}
/**
* @return true if the list of interfaces implemented by this type has changed
*/
public boolean hasTypeInterfacesChanged() {
return (changed & CHANGED_INTERFACES) != 0;
}
public boolean hasTypeSignatureChanged() {
return (changed & CHANGED_SIGNATURE) != 0;
}
public boolean hasAnythingChanged() {
return (changed & CHANGES) != 0;
}
public boolean hasNewFields() {
return (changed & CHANGED_NEWFIELDS) != 0;
}
public boolean hasLostFields() {
return (changed & CHANGED_LOSTFIELDS) != 0;
}
public boolean haveFieldsChangedOrBeenAddedOrRemoved() {
return (changed & CHANGED_FIELD_MASK) != 0;
}
public boolean haveFieldsChanged() {
return (changed & CHANGED_CHANGEDFIELDS) != 0;
}
public boolean haveMethodsChanged() {
return (changed & CHANGED_CHANGEDMETHODS) != 0;
}
public boolean haveMethodsChangedOrBeenAddedOrRemoved() {
return (changed & CHANGED_METHOD_MASK) != 0;
}
public boolean haveMethodsBeenAdded() {
return (changed & CHANGED_NEWMETHODS) != 0;
}
public boolean haveMethodsBeenDeleted() {
return (changed & CHANGED_LOSTMETHODS) != 0;
}
public Map<String, FieldNode> getNewFields() {
return brandNewFields;
}
public Map<String, FieldNode> getLostFields() {
return lostFields;
}
public Map<String, FieldDelta> getChangedFields() {
return changedFields;
}
public Map<String, MethodDelta> getChangedMethods() {
return changedMethods;
}
}

View File

@@ -0,0 +1,330 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded;
import java.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
* {@link TypeDescriptorExtractor}.
*
* @author Andy Clement
* @since 0.5.0
*/
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<String> finalInHierarchy) {
this.typename = slashedTypeName;
this.supertypeName = supertypeName;
this.superinterfaceNames = (superinterfaceNames == null ? NO_STRINGS : superinterfaceNames);
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.methods = methods.size() == 0 ? MethodMember.NONE : methods.toArray(new MethodMember[methods.size()]);
this.nonprivateMethods = filterNonPrivateMethods(this.methods);
this.isReloadable = isReloadable;
this.registry = registry;
this.hasClinit = hasClinit;
allocateIds();
}
private static MethodMember[] filterNonPrivateMethods(MethodMember[] allMethods) {
List<MethodMember> result = null;
for (MethodMember method : allMethods) {
if (!method.isPrivate()) {
if (result == null) {
result = new ArrayList<MethodMember>();
}
result.add(method);
}
}
if (result == null) {
return MethodMember.NONE;
} else {
return result.toArray(new MethodMember[result.size()]);
}
}
private void allocateIds() {
// Give the methods awareness of their index
for (MethodMember method : methods) {
method.setId(nextId++);
}
}
public MethodMember[] getMethods() {
return methods;
}
public MethodMember[] getConstructors() {
return constructors;
}
public FieldMember[] getFields() {
return fields;
}
public FieldMember[] getFieldsRequiringAccessors() {
return fieldsRequiringAccessors;
}
public int getModifiers() {
return modifiers;
}
/**
* @return the (slashed) type name
*/
public String getName() {
return typename;
}
/**
* @return the (slashed) supertype name
*/
public String getSupertypeName() {
return supertypeName;
}
/**
* @return array of (slashed) superinterface names (or an empty array if none)
*/
public String[] getSuperinterfacesName() {
return superinterfaceNames;
}
/**
* Check if this descriptor defines the specified method. A strict check on all aspects of the method - names/exceptions/flags,
* etc.
*
* @return true if this descriptor defines the specified method.
*/
public boolean defines(MethodMember method) {
for (MethodMember existingMethod : methods) {
// make sure it *really* defines it (i.e. it is not a catcher)
if (!MethodMember.isCatcher(existingMethod) && existingMethod.equals(method)) {
return true;
}
}
return false;
}
/**
* 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.
*/
public MethodMember getByDescriptor(String name, String descriptor) {
for (MethodMember existingMethod : methods) {
if (existingMethod.getName().equals(name) && existingMethod.getDescriptor().equals(descriptor)) {
return existingMethod;
}
}
return null;
}
public MethodMember getByNameAndDescriptor(String nameAndDescriptor) {
for (MethodMember existingMethod : methods) {
if (nameAndDescriptor.startsWith(existingMethod.getName())
&& nameAndDescriptor.endsWith(existingMethod.getDescriptor())) {
return existingMethod;
}
}
return null;
}
/**
* @return true if this type descriptor has been created for a reloadable type
*/
public boolean isReloadable() {
return isReloadable;
}
public MethodMember getMethod(int methodId) {
// Should never be an AIOOBE if the woven code is behaving
return methods[methodId];
}
public MethodMember getConstructor(int ctorId) {
// Should never be an AIOOBE if the woven code is behaving
return constructors[ctorId];
}
/**
* @return true if the type is an interface
*/
public boolean isInterface() {
return (modifiers & ACC_INTERFACE) != 0;
}
/**
* @return true if the type is an annotation
*/
public boolean isAnnotation() {
return (modifiers & ACC_ANNOTATION) != 0;
}
/**
* @return true if the type is an enum
*/
public boolean isEnum() {
return (modifiers & ACC_ENUM) != 0;
}
public boolean definesNonPrivate(String nameAndDescriptor) {
for (MethodMember existingMethod : nonprivateMethods) {
if (existingMethod.nameAndDescriptor.equals(nameAndDescriptor)) {
return true;
}
}
return false;
}
public boolean isFinalInHierarchy(String nad) {
return finalInHierarchy.contains(nad);
}
/**
* Search for a field on this type descriptor - do not try supertypes. This lookup does not differentiate between
* static/instance fields.
*
* @param name
* @return a FieldMember if the field is found, otherwise null
*/
public FieldMember getField(String name) {
for (FieldMember field : fields) {
if (field.getName().equals(name)) {
return field;
}
}
return null;
}
public ReloadableType getReloadableType() {
if (!isReloadable) {
return null;
}
if (reloadableType == null) {
reloadableType = registry.getReloadableType(this.typename);
if (reloadableType == null) {
throw new IllegalStateException("There is no ReloadableType instance for " + typename);
}
}
return reloadableType;
}
public TypeRegistry getTypeRegistry() {
return registry;
}
// could be worth caching if used for more than error messages...
public String getDottedName() {
return getName().replace('/', '.');
}
public MethodMember getConstructor(String desc) {
for (MethodMember ctor : constructors) {
String d = ctor.getDescriptor();
if (d.equals(desc)) {
return ctor;
}
}
return null;
}
public boolean isGroovyType() {
return (bits & IS_GROOVY_TYPE) != 0;
}
public void setIsGroovyType(boolean b) {
bits |= IS_GROOVY_TYPE;
}
public boolean hasClinit() {
return hasClinit;
}
public String toString() {
StringBuilder s = new StringBuilder();
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));
s.append("Methods:#" + methods.length + "\n" + methodsToString(methods));
return s.toString();
}
private String fieldsToString() {
StringBuilder s = new StringBuilder();
int count = 0;
for (FieldMember field : fields) {
s.append(" field #" + Utils.toPaddedNumber((count++), 3)).append(' ').append(field.toString()).append('\n');
}
return s.toString();
}
private String interfacesToString() {
if (superinterfaceNames == null) {
return "";
} else {
StringBuilder s = new StringBuilder();
for (String superinterfaceName : superinterfaceNames) {
s.append(superinterfaceName);
s.append(" ");
}
return s.toString().trim();
}
}
public String methodsToString(MethodMember[] methods) {
StringBuilder s = new StringBuilder();
int count = 0;
for (MethodMember method : methods) {
s.append(" method #" + Utils.toPaddedNumber((count++), 3)).append(' ').append(method.toString()).append(" ")
.append(method.bitsToString()).append('\n');
}
return s.toString();
}
}

View File

@@ -0,0 +1,320 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
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.
*
* @author Andy Clement
* @since 0.5.0
*/
public class TypeDescriptorExtractor {
private TypeRegistry registry;
public TypeDescriptorExtractor(TypeRegistry registry) {
this.registry = registry;
}
public TypeDescriptor extract(byte[] bytes, boolean isReloadableType) {
ClassReader fileReader = new ClassReader(bytes);
ExtractionVisitor extractionVisitor = new ExtractionVisitor(isReloadableType);
fileReader.accept(extractionVisitor, 0);
return extractionVisitor.getTypeDescriptor();
}
/**
* Visit a class and accumulate enough information to build a TypeDescriptor.
*/
class ExtractionVisitor implements ClassVisitor, Opcodes {
private boolean isReloadableType;
private int flags;
private String typename;
private String superclassName;
private String[] interfaceNames;
private boolean isGroovy = false;
private boolean hasClinit = false;
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) {
this.isReloadableType = isReloadableType;
}
public TypeDescriptor getTypeDescriptor() {
if (isReloadableType) {
computeCatchers();
}
computeFieldsRequiringAccessors();
computeClashes();
TypeDescriptor td = new TypeDescriptor(typename, superclassName, interfaceNames, flags, constructors, methods, fields,
fieldsRequiringAccessors, isReloadableType, registry, hasClinit, finalInHierarchy);
if (isGroovy) {
td.setIsGroovyType(true);
}
return td;
}
/**
* Determine if there are clashes. A clash is where a static method takes the this 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 + ";";
for (MethodMember member : methods) {
if (member.isStatic()) {
String desc = member.descriptor;
if (desc.startsWith(clashDescriptorPrefix)) {
// might be a clash, need to check the instance methods
for (MethodMember member2 : methods) {
if (member2.name.equals(member.name)) {
// 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);
if (instanceParams.equals(staticParams)) {
// CLASH
member.bits |= MethodMember.BIT_CLASH;
}
}
}
}
}
}
}
private TypeDescriptor getTypeDescriptorFor(String slashedname) {
return registry.getDescriptorFor(slashedname);
}
private TypeDescriptor findTypeDescriptor(TypeRegistry registry, String typename) {
// follow the pattern for a classloader: recurse up trying to find it, then recurse down trying to load it
TypeRegistry regToTry = registry;
TypeDescriptor td = regToTry.getDescriptorForReloadableType(typename);
while (td == null) {
regToTry = regToTry.getParentRegistry();
if (regToTry == null) {
break;
}
td = regToTry.getDescriptorForReloadableType(typename);
}
if (td == null) {
td = getTypeDescriptorFor(typename);
}
return td;
}
/**
* Create catcher methods for methods from our super-hierarchy that we don't yet override (but may after the initial define
* has happened).
*/
private void computeCatchers() {
// When walking up the hierarchy we may hit a 'final' method which means we must not catch it.
// The 'shouldNotCatch' list stores things we discover like this that should not be caught
List<String> shouldNotCatch = new ArrayList<String>();
String type = superclassName;
// Don't need catchers in interfaces
if (Modifier.isInterface(this.flags)) {
return;
}
while (type != null) {
TypeDescriptor supertypeDescriptor = findTypeDescriptor(registry, type);
// TODO review the need to create catchers for methods where the supertype is reloadable. In this situation we are already going to
// be intercepting the call side of these methods so we don't need the catcher. Could be a large performance increase and reduction in
// permgen, and simplification of stack traces
// if (!supertypeDescriptor.isReloadable()) {
for (MethodMember method : supertypeDescriptor.getMethods()) {
if (shouldCatchMethod(method) && !shouldNotCatch.contains(method.getNameAndDescriptor())) {
// don't need the catcher if method is already defined since when the existing method is rewritten
// it will be kind of morphed into a catcher
// TODO what about a private method that is overridden by a static method (same name/descriptor but not
// an overrides relationship)
// if (supertypeDescriptor.isGroovyType() && !isGroovy) {
// if (method.getName().startsWith("super$")) {
// continue;
// }
// }
MethodMember found = null;
for (MethodMember existingMethod : methods) {
if (existingMethod.equalsApartFromModifiers(method)) {
found = existingMethod;
break;
}
}
if (found != null) {
continue;
}
MethodMember catcherCopy = method.catcherCopyOf();
// System.out.println("catcher is " + catcherCopy + " is groovy type? " + this.isGroovy);
methods.add(catcherCopy);
} else {
if (method.isFinal()) {
shouldNotCatch.add(method.getNameAndDescriptor());
}
}
}
// }
type = supertypeDescriptor.supertypeName;
}
// ought to look in interfaces *if* we are an abstract class
if (Modifier.isAbstract(this.flags)/* && !Modifier.isInterface(this.flags)*/) {
// abstract class
for (String interfaceName : interfaceNames) {
addCatchersForNonImplementedMethodsFrom(interfaceName);
}
}
finalInHierarchy.addAll(shouldNotCatch);
}
private void addCatchersForNonImplementedMethodsFrom(String interfacename) {
TypeDescriptor interfaceDescriptor = findTypeDescriptor(registry, interfacename);
for (MethodMember method : interfaceDescriptor.getMethods()) {
// If this class doesn't implement this interface method, add it
boolean found = false;
for (MethodMember existingMethod : methods) {
if (existingMethod.equalsApartFromModifiers(method)) {
found = true;
break;
}
}
if (!found) {
methods.add(method.catcherCopyOfWithAbstractRemoved());
}
}
for (String interfaceName : interfaceDescriptor.superinterfaceNames) {
addCatchersForNonImplementedMethodsFrom(interfaceName);
}
}
/**
* Field
*/
private void computeFieldsRequiringAccessors() {
String type = superclassName;
while (type != null) {
TypeDescriptor supertypeDescriptor = findTypeDescriptor(registry, type);
if (!supertypeDescriptor.isReloadable()) {
for (FieldMember field : supertypeDescriptor.getFields()) {
if (field.isProtected()) {
boolean found = false;
for (FieldMember existingField : fields) {
if (existingField.getName().equals(field.getName())) {
// no need for accessor... this type defines a field that overrides it
found = true;
break;
}
}
if (!found) {
fieldsRequiringAccessors.add(field);
}
}
}
}
type = supertypeDescriptor.supertypeName;
}
}
/**
* 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.isPrivateStaticFinal() || (method.getName().equals("finalize") && method.getDescriptor().equals("()V")));
}
public void visit(int version, int flags, String name, String signature, String superclassName, String[] interfaceNames) {
this.flags = flags;
this.superclassName = superclassName;
this.interfaceNames = interfaceNames;
this.typename = name;
}
public AnnotationVisitor visitAnnotation(String classDesc, boolean isRuntime) {
return null;
}
public void visitAttribute(Attribute attribute) {
}
public void visitInnerClass(String name, String outername, String innerName, int access) {
if (name.equals(typename)) {
this.flags = access;
}
}
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
fields.add(new FieldMember(typename, access, name, desc, signature));
if (name.equals("$callSiteArray")) {
isGroovy = true;
}
return null;
}
// For each method, copy it into the new class making appropriate adjustments
/**
* 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) {
if (name.charAt(0) != '<') {
methods.add(new MethodMember(flags, name, descriptor, genericSignature, exceptions));
} 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>")) {
hasClinit = true;
}
}
return null;
}
public void visitOuterClass(String owner, String name, String desc) {
}
public void visitSource(String source, String debug) {
}
public void visitEnd() {
}
}
}

View File

@@ -0,0 +1,674 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.AnnotationNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.FieldNode;
import org.objectweb.asm.tree.IincInsnNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.IntInsnNode;
import org.objectweb.asm.tree.JumpInsnNode;
import org.objectweb.asm.tree.LabelNode;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.LineNumberNode;
import org.objectweb.asm.tree.LookupSwitchInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.MultiANewArrayInsnNode;
import org.objectweb.asm.tree.TableSwitchInsnNode;
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.
*
* @author Andy Clement
* @since 0.5.0
*/
public class TypeDiffComputer implements Opcodes {
public static TypeDelta computeDifferences(byte[] oldbytes, byte[] newbytes) {
ClassNode oldClassNode = new ClassNode();
new ClassReader(oldbytes).accept(oldClassNode, 0);
ClassNode newClassNode = new ClassNode();
new ClassReader(newbytes).accept(newClassNode, 0);
TypeDelta delta = computeDelta(oldClassNode, newClassNode);
return delta;
}
private static TypeDelta computeDelta(ClassNode oldClassNode, ClassNode newClassNode) {
// The type itself: (int version, int access, String name, String signature, String superName, String[] interfaces) {
TypeDelta td = new TypeDelta();
computeTypeDelta(oldClassNode, newClassNode, td);
computeFieldDelta(oldClassNode, newClassNode, td);
computeMethodDelta(oldClassNode, newClassNode, td);
// TODO delta: implement the rest of computeDelta. These methods from ClassVisitor should help in knowing what is left to do:
// public void visitSource(String source, String debug) {
// public void visitOuterClass(String owner, String name, String desc) {
// public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
// public void visitAttribute(Attribute attr) {
// public void visitInnerClass(String name, String outerName, String innerName, int access) {
// public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
// public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
// public void visitEnd() {
return td;
}
@SuppressWarnings("unchecked")
private static void computeMethodDelta(ClassNode oldClassNode, ClassNode newClassNode, TypeDelta td) {
List<MethodNode> nMethods = newClassNode.methods;
List<MethodNode> oMethods = new ArrayList<MethodNode>(oldClassNode.methods);
// Going through the new methods and comparing them to the old
if (nMethods != null) {
for (MethodNode nMethod : nMethods) {
MethodNode found = null;
for (MethodNode oMethod : oMethods) {
if (oMethod.name.equals(nMethod.name) && oMethod.desc.equals(nMethod.desc)) { // TODO modifiers compared?
found = oMethod;
computeAnyMethodDifferences(oMethod, nMethod, td);
}
}
if (found == null) {
td.addNewMethod(nMethod);
} else {
oMethods.remove(found);
}
}
}
for (MethodNode lostMethod : oMethods) {
td.addLostMethod(lostMethod);
}
}
@SuppressWarnings("unchecked")
private static void computeFieldDelta(ClassNode oldClassNode, ClassNode newClassNode, TypeDelta td) {
// int oSize = oldClassNode.fields.size();
int nSize = newClassNode.fields.size();
// Take a copy as we are going to delete entries in the next loop
List<FieldNode> oFields = new ArrayList<FieldNode>(oldClassNode.fields);
// Going through the new fields comparing them to the old
for (int n = 0; n < nSize; n++) {
FieldNode nField = (FieldNode) newClassNode.fields.get(n);
FieldNode found = null;
for (FieldNode oField : oFields) {
if (oField.name.equals(nField.name)) {
// found it!
found = oField;
// is it exactly the same?
computeAnyFieldDifferences(oField, nField, td);
}
}
if (found == null) {
// this is a new field
td.addNewField(nField);
} else {
oFields.remove(found);
}
}
// Those left in oFields were not in nFields so have been removed!
for (FieldNode lostField : oFields) {
td.addLostField(lostField);
}
}
/**
* 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) {
// Want to record things that are different between these two fields...
FieldDelta fd = new FieldDelta(oField.name);
if (oField.access != nField.access) {
// access changed
fd.setAccessChanged(oField.access, nField.access);
}
if (!oField.desc.equals(nField.desc)) {
// type changed
fd.setTypeChanged(oField.desc, nField.desc);
}
String annotationChange = compareAnnotations(oField.invisibleAnnotations, nField.invisibleAnnotations);
annotationChange = annotationChange + compareAnnotations(oField.visibleAnnotations, nField.visibleAnnotations);
if (annotationChange.length() != 0) {
fd.setAnnotationsChanged(annotationChange);
}
if (fd.hasAnyChanges()) {
// it needs recording
td.addChangedField(fd);
}
}
/**
* 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
* @param td the type delta where changes are currently being accumulated
*/
private static void computeAnyMethodDifferences(MethodNode oMethod, MethodNode nMethod, TypeDelta td) {
MethodDelta md = new MethodDelta(oMethod.name, oMethod.desc);
if (oMethod.access != nMethod.access) {
md.setAccessChanged(oMethod.access, nMethod.access);
}
// TODO annotations
InsnList oInstructions = oMethod.instructions;
InsnList nInstructions = nMethod.instructions;
if (oInstructions.size() != nInstructions.size()) {
md.setInstructionsChanged(oInstructions.toArray(), nInstructions.toArray());
} else {
// TODO Just interested in constructors right now - should add others
if (oMethod.name.charAt(0) == '<') {
String oInvokeSpecialDescriptor = null;
String nInvokeSpecialDescriptor = null;
int oUninitCount = 0;
int nUninitCount = 0;
boolean codeChange = false;
for (int i = 0, max = oInstructions.size(); i < max; i++) {
AbstractInsnNode oInstruction = oInstructions.get(i);
AbstractInsnNode nInstruction = nInstructions.get(i);
if (!codeChange) {
if (!sameInstruction(oInstruction, nInstruction)) {
codeChange = true;
}
}
if (oInstruction.getType() == AbstractInsnNode.TYPE_INSN) {
if (oInstruction.getOpcode() == Opcodes.NEW) {
oUninitCount++;
}
}
if (nInstruction.getType() == AbstractInsnNode.TYPE_INSN) {
if (nInstruction.getOpcode() == Opcodes.NEW) {
nUninitCount++;
}
}
if (oInstruction.getType() == AbstractInsnNode.METHOD_INSN) {
MethodInsnNode mi = (MethodInsnNode) oInstruction;
if (mi.getOpcode() == INVOKESPECIAL && mi.name.equals("<init>")) {
if (oUninitCount == 0) {
// this is the one!
oInvokeSpecialDescriptor = mi.desc;
} else {
oUninitCount--;
}
}
}
if (nInstruction.getType() == AbstractInsnNode.METHOD_INSN) {
MethodInsnNode mi = (MethodInsnNode) nInstruction;
if (mi.getOpcode() == INVOKESPECIAL && mi.name.equals("<init>")) {
if (nUninitCount == 0) {
// this is the one!
nInvokeSpecialDescriptor = mi.desc;
} else {
nUninitCount--;
}
}
}
}
// Has the invokespecial changed?
if (oInvokeSpecialDescriptor == null) {
if (nInvokeSpecialDescriptor != null) {
md.setInvokespecialChanged(oInvokeSpecialDescriptor, nInvokeSpecialDescriptor);
}
} else {
if (!oInvokeSpecialDescriptor.equals(nInvokeSpecialDescriptor)) {
md.setInvokespecialChanged(oInvokeSpecialDescriptor, nInvokeSpecialDescriptor);
}
}
if (codeChange) {
md.setCodeChanged(oInstructions.toArray(), nInstructions.toArray());
}
}
}
if (md.hasAnyChanges()) {
// it needs recording
td.addChangedMethod(md);
}
}
private static boolean sameInstruction(AbstractInsnNode o, AbstractInsnNode n) {
if (o.getType() != o.getType() || o.getOpcode() != n.getOpcode()) {
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());
}
return true;
}
private static boolean sameFrameInsn(AbstractInsnNode o, AbstractInsnNode n) {
// given that these nodes are computed based on everything else. if everything else is the same then these
// must be the same. A full comparison could be a little ugly as different frames can be equivalent (maybe
// the compiler produces an incremental frame on one run then a full frame on the next).
return true;
}
private static boolean sameMultiANewArrayInsn(AbstractInsnNode o, AbstractInsnNode n) {
if (!(n instanceof MultiANewArrayInsnNode)) {
return false;
}
MultiANewArrayInsnNode mnao = (MultiANewArrayInsnNode) o;
MultiANewArrayInsnNode mnan = (MultiANewArrayInsnNode) n;
if (!mnao.desc.equals(mnan.desc)) {
return false;
}
if (mnao.dims != mnan.dims) {
return false;
}
return true;
}
@SuppressWarnings("unchecked")
private static boolean sameLookupSwitchInsn(AbstractInsnNode o, AbstractInsnNode n) {
if (!(n instanceof LookupSwitchInsnNode)) {
return false;
}
LookupSwitchInsnNode lsio = (LookupSwitchInsnNode) o;
LookupSwitchInsnNode lsin = (LookupSwitchInsnNode) n;
if (sameLabels(lsio.dflt, lsin.dflt)) {
return false;
}
List<Integer> keyso = lsio.keys;
List<Integer> keysn = lsin.keys;
if (keyso.size() != keysn.size()) {
return false;
}
for (int i = 0, max = keyso.size(); i < max; i++) {
if (keyso.get(i) != keysn.get(i)) {
return false;
}
}
List<LabelNode> labelso = lsio.labels;
List<LabelNode> labelsn = lsin.labels;
if (labelso.size() != labelsn.size()) {
return false;
}
for (int i = 0, max = labelso.size(); i < max; i++) {
if (!sameLabelNode(labelso.get(i), labelsn.get(i))) {
return false;
}
}
return true;
}
@SuppressWarnings("unchecked")
private static boolean sameTableSwitchInsn(AbstractInsnNode o, AbstractInsnNode n) {
if (!(n instanceof TableSwitchInsnNode)) {
return false;
}
TableSwitchInsnNode tsio = (TableSwitchInsnNode) o;
TableSwitchInsnNode tsin = (TableSwitchInsnNode) n;
if (sameLabels(tsio.dflt, tsin.dflt)) {
return false;
}
if (tsio.min != tsin.min) {
return false;
}
if (tsio.max != tsin.max) {
return false;
}
List<LabelNode> labelso = tsio.labels;
List<LabelNode> labelsn = tsin.labels;
if (labelso.size() != labelsn.size()) {
return false;
}
for (int i = 0, max = labelso.size(); i < max; i++) {
if (!sameLabelNode(labelso.get(i), labelsn.get(i))) {
return false;
}
}
return true;
}
private static boolean sameLabels(LabelNode lno, LabelNode lnn) {
// TODO implement?
return false;
}
private static boolean sameFieldInsn(AbstractInsnNode o, AbstractInsnNode n) {
FieldInsnNode oi = (FieldInsnNode) o;
if (!(n instanceof FieldInsnNode)) {
return false;
}
FieldInsnNode ni = (FieldInsnNode) n;
return oi.name.equals(ni.name) && oi.desc.equals(ni.desc) && oi.owner.equals(ni.owner);
}
private static boolean sameMethodInsnNode(AbstractInsnNode o, AbstractInsnNode n) {
MethodInsnNode oi = (MethodInsnNode) o;
if (!(n instanceof MethodInsnNode)) {
return false;
}
MethodInsnNode ni = (MethodInsnNode) n;
return oi.name.equals(ni.name) && oi.desc.equals(ni.desc) && oi.owner.equals(ni.owner);
}
private static boolean sameVarInsn(AbstractInsnNode o, AbstractInsnNode n) {
VarInsnNode oi = (VarInsnNode) o;
if (!(n instanceof VarInsnNode)) {
return false;
}
VarInsnNode ni = (VarInsnNode) n;
return oi.var == ni.var;
}
private static boolean sameInsnNode(AbstractInsnNode o, AbstractInsnNode n) {
InsnNode oi = (InsnNode) o;
if (!(n instanceof InsnNode)) {
return false;
}
InsnNode ni = (InsnNode) n;
return oi.getOpcode() == ni.getOpcode();
}
private static boolean sameJumpInsnNode(AbstractInsnNode o, AbstractInsnNode n) {
// JumpInsnNode oJumpInsnNode = (JumpInsnNode) o;
if (!(n instanceof JumpInsnNode)) {
return false;
}
// JumpInsnNode nJumpInsnNode = (JumpInsnNode) n;
// TODO tricky to compare destinations when captured as labels with no exposed identifier/position
return true;
}
private static boolean sameLdcInsnNode(AbstractInsnNode o, AbstractInsnNode n) {
LdcInsnNode oi = (LdcInsnNode) o;
if (!(n instanceof LdcInsnNode)) {
return false;
}
LdcInsnNode ni = (LdcInsnNode) n;
Object ocst = oi.cst;
if (ocst instanceof Integer) {
if (!(ni.cst instanceof Integer)) {
return false;
}
return ((Integer) ocst).equals(ni.cst);
}
if (ocst instanceof Float) {
if (!(ni.cst instanceof Float)) {
return false;
}
return ((Float) ocst).equals(ni.cst);
}
if (ocst instanceof Long) {
if (!(ni.cst instanceof Long)) {
return false;
}
return ((Long) ocst).equals(ni.cst);
}
if (ocst instanceof Double) {
if (!(ni.cst instanceof Double)) {
return false;
}
return ((Double) ocst).equals(ni.cst);
}
if (ocst instanceof String) {
if (!(ni.cst instanceof String)) {
return false;
}
return ((String) ocst).equals(ni.cst);
}
// must be Type
return ((Type) ocst).equals(ni.cst);
}
private static boolean sameIntInsnNode(AbstractInsnNode o, AbstractInsnNode n) {
IntInsnNode oi = (IntInsnNode) o;
if (!(n instanceof IntInsnNode)) {
return false;
}
IntInsnNode ni = (IntInsnNode) n;
return oi.operand == ni.operand;
}
private static boolean sameLineNumberNode(AbstractInsnNode o, AbstractInsnNode n) {
LineNumberNode oi = (LineNumberNode) o;
if (!(n instanceof LineNumberNode)) {
return false;
}
LineNumberNode ni = (LineNumberNode) n;
return oi.line == ni.line;
// TODO check oi.start?
}
private static boolean sameIincInsn(AbstractInsnNode o, AbstractInsnNode n) {
IincInsnNode oi = (IincInsnNode) o;
if (!(n instanceof IincInsnNode)) {
return false;
}
IincInsnNode ni = (IincInsnNode) n;
return oi.var == ni.var && oi.incr == ni.incr;
}
private static boolean sameTypeInsn(AbstractInsnNode o, AbstractInsnNode n) {
TypeInsnNode oi = (TypeInsnNode) o;
if (!(n instanceof TypeInsnNode)) {
return false;
}
TypeInsnNode ni = (TypeInsnNode) n;
return oi.desc.equals(ni.desc);
}
/**
* Compare two labels to check they are the same.
*
* @param o 'old' label
* @param n 'new' label
* @return true if they are different
*/
private static boolean sameLabelNode(AbstractInsnNode o, AbstractInsnNode n) {
// LabelNode oi = (LabelNode) o;
if (!(n instanceof LabelNode)) {
return false;
}
// LabelNode ni = (LabelNode) n;
// TODO tricky to get right. Unfortunately the positions aren't always available - and we can't check if they are, we have to call the
// getOffset() method on label and catch an exception if they aren't
return true;
}
private static String compareAnnotations(List<AnnotationNode> oldAnnos, List<AnnotationNode> newAnnos) {
if (oldAnnos == null) {
if (newAnnos == null) {
return "";
}
oldAnnos = Collections.emptyList();
}
if (newAnnos == null) {
newAnnos = Collections.emptyList();
}
StringBuilder diff = new StringBuilder();
// Which have been removed
for (AnnotationNode o : oldAnnos) {
boolean found = false;
String oFormatted = Utils.annotationNodeFormat(o);
for (AnnotationNode n : newAnnos) {
String nFormatted = Utils.annotationNodeFormat(n);
if (oFormatted.equals(nFormatted)) {
found = true;
break;
}
}
if (!found) {
diff.append("-").append(oFormatted);
}
}
// Which have been added
for (AnnotationNode n : newAnnos) {
boolean found = false;
String nFormatted = Utils.annotationNodeFormat(n);
for (AnnotationNode o : oldAnnos) {
String oFormatted = Utils.annotationNodeFormat(o);
if (oFormatted.equals(nFormatted)) {
found = true;
break;
}
}
if (!found) {
diff.append("+").append(nFormatted);
}
}
return diff.toString();
}
@SuppressWarnings("unchecked")
private static void computeTypeDelta(ClassNode oldClassNode, ClassNode newClassNode, TypeDelta td) {
// if (oldClassNode.version != newClassNode.version) {
// td.setTypeVersionChange(oldClassNode.version, newClassNode.version);
// }
if (oldClassNode.access != newClassNode.access) {
td.setTypeAccessChange(oldClassNode.access, newClassNode.access);
}
if (!oldClassNode.name.equals(newClassNode.name)) {
td.setTypeNameChange(oldClassNode.name, newClassNode.name);
}
// if (oldClassNode.signature == null) {
// if (newClassNode.signature != null) {
// td.setTypeSignatureChange(oldClassNode.signature, newClassNode.signature);
// }
// } else if (newClassNode.signature == null) {
// if (oldClassNode.signature != null) {
// td.setTypeSignatureChange(oldClassNode.signature, newClassNode.signature);
// }
// } else if (!oldClassNode.signature.equals(newClassNode.signature)) {
// td.setTypeSignatureChange(oldClassNode.signature, newClassNode.signature);
// }
if (oldClassNode.superName == null) {
if (newClassNode.superName != null) {
td.setTypeSuperNameChange(oldClassNode.superName, newClassNode.superName);
}
} else if (newClassNode.superName == null) {
if (oldClassNode.superName != null) {
td.setTypeSuperNameChange(oldClassNode.superName, 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) {
if (oldClassNode.interfaces.size() != 0) {
td.setTypeInterfacesChange(oldClassNode.interfaces, newClassNode.interfaces);
}
} else {
if (oldClassNode.interfaces.size() != newClassNode.interfaces.size()) {
td.setTypeInterfacesChange(oldClassNode.interfaces, newClassNode.interfaces);
}
HashSet<String> oldInterfaceSet = new HashSet<String>(oldClassNode.interfaces);
HashSet<String> newInterfaceSet = new HashSet<String>(newClassNode.interfaces);
if (!oldInterfaceSet.equals(newInterfaceSet)) { // TODO expensive? keep the interfaces list sorted instead?
td.setTypeInterfacesChange(oldClassNode.interfaces, newClassNode.interfaces);
}
}
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded;
/**
* Similar to the AspectJ type pattern model - used for defining reloadable type inclusions/exclusions.
*
* @author Andy Clement
* @since 0.5.0
*/
public abstract class TypePattern {
public boolean matches(String dottedname) {
if (GlobalConfiguration.assertsOn) {
Utils.assertDotted(dottedname);
}
return internalMatches(dottedname);
}
protected abstract boolean internalMatches(String input);
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,30 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded;
/**
*
* @author Andy Clement
* @since 0.5.0
*/
@SuppressWarnings("serial")
public class UnableToLoadClassException extends RuntimeException {
public UnableToLoadClassException(String classname) {
super("Unable to find data for class '" + classname + "'");
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded;
/**
* 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
*/
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.
*
* @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 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

View File

@@ -0,0 +1,30 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded;
/**
* 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
*/
public interface __DynamicallyDispatchable {
Object __execute(Object[] parameters, Object instance, String nameAndDescriptor);
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded.agent;
import java.security.ProtectionDomain;
import org.springsource.loaded.LoadtimeInstrumentationPlugin;
/**
* The CGLIB plugin recognizes when elements of cglib are loaded and rewrites them to catch certain events occuring.
*
* @author Andy Clement
* @since 0.8.3
*/
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) {
return false;
}
return slashedTypeName.equals("net/sf/cglib/core/AbstractClassGenerator");
// || slashedTypeName.equals("net/sf/cglib/reflect/FastClass");
}
public byte[] modify(String slashedClassName, ClassLoader classLoader, byte[] bytes) {
// if (slashedClassName.equals("net/sf/cglib/core/AbstractClassGenerator")) {
return CglibPluginCapturing.catchGenerate(bytes);
// } else {
// net/sf/cglib/reflect/FastClass
// We must empty the FastClass constructor. Why? Due to current limitations with
// SpringLoaded we consider a constructor to have changed when the type is reloaded
// (so regardless of whether the code did actually change). Due to this the
// 'new' constructors driven once reloaded call super.<init>(). The default FastClass
// empty constructor throws an exception. We are just removing that throw.
// return bytes;//EmptyCtor.invoke(bytes, "()V");
// }
}
}

View File

@@ -0,0 +1,139 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded.agent;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import org.objectweb.asm.ClassAdapter;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodAdapter;
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.
*
* @author Andy Clement
* @since 0.8.3
*/
public class CglibPluginCapturing extends ClassAdapter implements Constants {
public static Map<Class<?>, Object[]> clazzToGeneratorStrategyAndClassGeneratorMap = new HashMap<Class<?>, Object[]>();
public static Map<Class<?>, Object[]> clazzToGeneratorStrategyAndFastClassGeneratorMap = new HashMap<Class<?>, Object[]>();
public static byte[] catchGenerate(byte[] bytesIn) {
ClassReader cr = new ClassReader(bytesIn);
CglibPluginCapturing ca = new CglibPluginCapturing();
cr.accept(ca, 0);
byte[] newbytes = ca.getBytes();
return newbytes;
}
private CglibPluginCapturing() {
super(new ClassWriter(0)); // TODO review 0 here
}
public byte[] getBytes() {
return ((ClassWriter) cv).toByteArray();
}
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
if (name.equals("create")) {
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
return new CreateMethodInterceptor(mv);
} else {
return super.visitMethod(access, name, desc, signature, exceptions);
}
}
static class CreateMethodInterceptor extends MethodAdapter implements Constants {
public CreateMethodInterceptor(MethodVisitor mv) {
super(mv);
}
@Override
public void visitCode() {
}
/**
* 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) {
super.visitMethodInsn(opcode, owner, name, desc);
if (name.equals("generate")) {
// Code that calls generate:
// ALOAD 0
// GETFIELD net/sf/cglib/core/AbstractClassGenerator.strategy : Lnet/sf/cglib/core/GeneratorStrategy;
// ALOAD 0
// INVOKEINTERFACE net/sf/cglib/core/GeneratorStrategy.generate(Lnet/sf/cglib/core/ClassGenerator;)[B
mv.visitVarInsn(ALOAD, 0); // AbstractClassGenerator instance
mv.visitFieldInsn(GETFIELD, "net/sf/cglib/core/AbstractClassGenerator", "strategy",
"Lnet/sf/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");//Lnet/sf/cglib/core/GeneratorStrategy;Lnet/sf/cglib/core/AbstractClassGenerator);");
}
}
}
/**
* 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 classloader
* @param name
* @param bytes
* @return
*/
public static void record(Object a, Object b) {
// a is a Lnet/sf/cglib/core/GeneratorStrategy;
// b is a Lnet/sf/cglib/core/AbstractClassGenerator (or specifically net/sf/cglib/reflect/FastClass$Generator)
// a is something like 'UndeclaredThrowableStrategy'
// b is an Enhancer: namePrefix="example.Simple" superclass=example.Simple
String generatorName = b.getClass().getName();
if (generatorName.equals("net.sf.cglib.proxy.Enhancer")) {
try {
Field f = b.getClass().getDeclaredField("superclass");
f.setAccessible(true);
Class<?> clazz = (Class<?>) f.get(b);
// System.out.println("Recording pair " + clazz.getName() + " > " + b);
clazzToGeneratorStrategyAndClassGeneratorMap.put(clazz, new Object[] { a, b });
} catch (Throwable re) {
re.printStackTrace();
}
} else if (generatorName.equals("net.sf.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) {
re.printStackTrace();
}
}
}
}

View File

@@ -0,0 +1,114 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded.agent;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.springsource.loaded.GlobalConfiguration;
import org.springsource.loaded.ReloadableType;
import org.springsource.loaded.TypeRegistry;
/**
* Class pre-processor.
*
* @author Andy Clement
* @since 0.5.0
*/
public class ClassPreProcessorAgentAdapter implements ClassFileTransformer {
private static Logger log = Logger.getLogger(ClassPreProcessorAgentAdapter.class.getName());
private static SpringLoadedPreProcessor preProcessor;
private static ClassPreProcessorAgentAdapter instance;
public ClassPreProcessorAgentAdapter() {
instance = this;
}
static {
try {
preProcessor = new SpringLoadedPreProcessor();
preProcessor.initialize();
} catch (Exception e) {
throw new ExceptionInInitializerError("could not initialize JSR163 preprocessor due to: " + e.toString());
}
}
/**
* @param loader the defining class loader
* @param className the name of class being loaded
* @param classBeingRedefined when hotswap is called
* @param protectionDomain
* @param bytes the bytecode before weaving
* @return the weaved bytecode
*/
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));
}
// TODO determine if this is the right behaviour for hot code replace:
// Handling class redefinition (hot code replace) - what to do depends on whether the type is a reloadable type or not
// If reloadable - return the class as originally defined, and treat this new input data as the new version to make live
// If not-reloadable - rewrite the call sites and attempt hot code replace
if (classBeingRedefined != null) {
// pretend no-one attempted the reload by returning original bytes. The 'watcher' for the class
// should see the changes and pick them up. Should we force it here?
TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(loader);
if (typeRegistry == null) {
return null;
}
boolean isRTN = typeRegistry.isReloadableTypeName(className);
if (isRTN) {
ReloadableType rtype = typeRegistry.getReloadableType(className, false);
// CurrentLiveVersion clv = rtype.getLiveVersion();
// String suffix = "0";
// if (clv != null) {
// suffix = clv.getVersionStamp() + "H";
// }
// rtype.loadNewVersion(suffix, bytes);
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) {
log.info("Tricking HCR for " + className);
}
return rtype.bytesLoaded; // returning original bytes
}
return null;
}
// System.err.println("transform(" + loader.getClass().getName() + ",classname=" + className +
// ",classBeingRedefined=" + classBeingRedefined + ",protectionDomain=" + protectionDomain + ")");
return preProcessor.preProcess(loader, className, protectionDomain, bytes);
} catch (Throwable t) {
new RuntimeException("Reloading agent exited via exception, please raise a jira", t).printStackTrace();
return bytes;
}
}
public static void reload(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] bytes) throws IllegalClassFormatException {
instance.transform(loader, className, classBeingRedefined, protectionDomain, bytes);
}
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded.agent;
import org.objectweb.asm.ClassAdapter;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodAdapter;
import org.objectweb.asm.MethodVisitor;
import org.springsource.loaded.Constants;
/**
*
* @author Andy Clement
* @since 0.5.0
*/
public class ClassVisitingConstructorAppender extends ClassAdapter 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.
*
* @param owner
* @param name
*/
public ClassVisitingConstructorAppender(String owner, String name) {
super(new ClassWriter(0)); // TODO review 0 here
this.calleeOwner = owner;
this.calleeName = name;
}
public byte[] getBytes() {
return ((ClassWriter) cv).toByteArray();
}
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
if (name.equals("<init>")) {
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
return new ConstructorAppender(mv);
} 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.
*/
class ConstructorAppender extends MethodAdapter implements Constants {
public ConstructorAppender(MethodVisitor mv) {
super(mv);
}
@Override
public void visitInsn(int opcode) {
if (opcode == RETURN) {
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESTATIC, calleeOwner, calleeName, "(Ljava/lang/Object;)V");
}
super.visitInsn(opcode);
}
}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded.agent;
import org.objectweb.asm.ClassAdapter;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.springsource.loaded.Constants;
/**
*
* @author Andy Clement
* @since 0.7.0
*/
public class FalseReturner extends ClassAdapter implements Constants {
private String methodname;
public FalseReturner(String methodname) {
super(new ClassWriter(0)); // TODO review 0 here
this.methodname = methodname;
}
public byte[] getBytes() {
return ((ClassWriter) cv).toByteArray();
}
// @Override
// public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
// if (name.equals(methodname)) {
// return super.visitField(access & (~Modifier.FINAL), name, desc, signature, value);
// } else {
// return super.visitField(access, name, desc, signature, value);
// }
// }
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
if (name.equals(methodname)) {
// return new FakeMethodVisitor();
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
mv.visitCode();
mv.visitInsn(ICONST_0);
mv.visitInsn(IRETURN);
mv.visitMaxs(3, 1);
mv.visitEnd();
return mv;
// return new FalseReturnerMV(mv);
} else {
return super.visitMethod(access, name, desc, signature, exceptions);
}
}
public boolean m() {
return false;
}
// class FakeMethodVisitor implements MethodVisitor, Constants {
//
// }
}

View File

@@ -0,0 +1,252 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded.agent;
import java.io.File;
import java.io.FileFilter;
import java.util.ArrayList;
import java.util.List;
import org.springsource.loaded.FileChangeListener;
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.
*
* @author Andy Clement
* @since 0.5.0
*/
public class FileSystemWatcher {
// the thread being managed
private Thread thread;
// whether the thread is running
private boolean threadRunning = false;
// The Watcher running inside the thread
private Watcher watchThread;
public FileSystemWatcher(FileChangeListener listener, int typeRegistryId, String classloadername) {
watchThread = new Watcher(listener, typeRegistryId, classloadername);
}
/**
* Start the thread if it isn't already started.
*/
private void ensureWatchThreadRunning() {
if (!threadRunning) {
thread = new Thread(watchThread);
thread.setDaemon(true);
thread.start();
watchThread.setThread(thread);
watchThread.updateName();
threadRunning = true;
}
}
/**
* Shutdown the thread.
*/
public void shutdown() {
if (threadRunning) {
watchThread.timeToStop();
}
}
/**
* 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
*/
public void register(File fileToMonitor) {
if (watchThread.addFile(fileToMonitor)) {
ensureWatchThreadRunning();
watchThread.updateName();
}
}
/**
* Enables the filesystem watching to be paused/unpaused.
*
* @param shouldBePaused watching should be paused?
*/
public void setPaused(boolean shouldBePaused) {
watchThread.paused = shouldBePaused;
}
}
class Watcher implements Runnable {
long lastScanTime;
// TODO configurable scan interval?
private static long interval = 1100;// ms
List<File> watchListFiles = new ArrayList<File>();
List<Long> watchListLMTs = new ArrayList<Long>();
// Map<File, Long> watchList = new ConcurrentHashMap<File, 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) {
this.listener = listener;
this.typeRegistryId = typeRegistryId;
this.classloadername = classloadername;
}
public void setThread(Thread thread) {
this.thread = thread;
}
/**
* 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
*/
public boolean addFile(File fileToWatch) {
if (!fileToWatch.exists()) {
return false;
}
synchronized (this) {
int insertionPos = findPosition(fileToWatch);
if (insertionPos == -1) {
watchListFiles.add(fileToWatch);
watchListLMTs.add(fileToWatch.lastModified());
} else {
watchListFiles.add(insertionPos, fileToWatch);
watchListLMTs.add(insertionPos, fileToWatch.lastModified());
}
return true;
}
}
public void updateName() {
if (thread != null) {
thread.setName("FileSystemWatcher: files=#" + watchListFiles.size() + " cl=" + classloadername);
}
}
private int findPosition(File file) {
String filename = file.getName();
int len = watchListFiles.size();
if (len == 0) {
return 0;
}
for (int f = 0; f < len; f++) {
File file2 = watchListFiles.get(f);
int cmp = file2.getName().compareTo(filename);
// as we are using 'names' we are only considering the last part, so foo/bar/Goo.class and foo/Goo.class look the same
// and will return cmp==0. Not really sure it matters about using fq names
if (cmp > 0) {
return f;
}
}
return -1;
}
public void run() {
while (!timeToStop) {
registryLivenessCount++;
if ((registryLivenessCount % registryLivenessCountInterval) == 0) {
// Time to check if the registry is still alive!
if (!TypeRegistry.typeRegistryExistsForId(typeRegistryId)) {
// System.out.println("TypeRegistry " + typeRegistryId + " gone, no point in thread continuing!");
return;
// } else {
// System.out.println("TypeRegistry " + typeRegistryId + " seems to still be around!");
}
registryLivenessCount = 0;
}
try {
Thread.sleep(interval);
} catch (Exception e) {
}
if (!paused) {
List<File> changedFiles = new ArrayList<File>();
synchronized (this) {
int len = watchListFiles.size();
for (int f = 0; f < len; f++) {
File file = watchListFiles.get(f);
long lastModTime = file.lastModified();
if (lastModTime > watchListLMTs.get(f)) {
// System.out.println("Watcher: " + lastScanTime + " change detected in " + file);
watchListLMTs.set(f, lastModTime);
changedFiles.add(file);
}
}
lastScanTime = System.currentTimeMillis();
}
for (File changedFile: changedFiles) {
determineChangesSince(changedFile, lastScanTime);
}
}
}
}
/*
* problem is that we check some file X, it hasn't changed - we then take longer than interval to check all the
* other files we are watching.
*/
private void determineChangesSince(File file, long lastScanTime) {
try {
listener.fileChanged(file);
if (file.isDirectory()) {
File[] filesOfInterest = file.listFiles(new RecentChangeFilter(lastScanTime));
for (File f : filesOfInterest) {
if (f.isDirectory()) {
determineChangesSince(f, lastScanTime);
} else {
listener.fileChanged(f);
}
}
}
} catch (Throwable t) {
new RuntimeException("FileWatcher caught serious error, see cause.", t).printStackTrace();
}
}
static class RecentChangeFilter implements FileFilter {
private long lastScanTime;
public RecentChangeFilter(long lastScanTime) {
this.lastScanTime = lastScanTime;
}
public boolean accept(File pathname) {
return (pathname.lastModified() > lastScanTime);
}
}
public void timeToStop() {
timeToStop = true;
}
}

View File

@@ -0,0 +1,111 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded.agent;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.List;
import org.springsource.loaded.GlobalConfiguration;
import org.springsource.loaded.LoadtimeInstrumentationPlugin;
import org.springsource.loaded.ReloadEventProcessorPlugin;
/**
*
*
* @author Andy Clement
* @since 0.7.3
*/
public class GrailsPlugin implements LoadtimeInstrumentationPlugin, ReloadEventProcessorPlugin {
// private static Logger log = Logger.getLogger(GrailsPlugin.class.getName());
private static final String DefaultClassPropertyFetcher = "org/codehaus/groovy/grails/commons/ClassPropertyFetcher";
private static List<WeakReference<Object>> classPropertyFetcherInstances = new ArrayList<WeakReference<Object>>();
private static ReferenceQueue<Object> rq = new ReferenceQueue<Object>();
/**
* @return true for types this plugin would like to change on startup
*/
public boolean accept(String slashedTypeName, ClassLoader classLoader, ProtectionDomain protectionDomain, byte[] bytes) {
// TODO take classloader into account?
return false;//DefaultClassPropertyFetcher.equals(slashedTypeName);
}
public byte[] modify(String slashedClassName, ClassLoader classLoader, byte[] bytes) {
return PluginUtils.addInstanceTracking(bytes, "org/springsource/loaded/agent/GrailsPlugin");
}
// called by the modified code
public static void recordInstance(Object obj) {
// obj will be a ClassPropertyFetcher instance
System.err.println("new instance queued " + System.identityHashCode(obj));
// TODO urgent - race condition here, can create Co-modification problem if adding whilst another thread is processing
classPropertyFetcherInstances.add(new WeakReference<Object>(obj, rq));
}
private Field classPropertyFetcher_clazz;
private Method classPropertyFetcher_init;
public void reloadEvent(String typename, Class<?> reloadedClazz, String versionsuffix) {
// Clear references to objects that have been GCd
// Do they ever get cleared out??
Reference<?> r = rq.poll();
while (r != null) {
classPropertyFetcherInstances.remove(r);
r = rq.poll();
}
try {
// Currently not needing to track classPropertyFetcherInstances
for (WeakReference<Object> ref : classPropertyFetcherInstances) {
Object instance = ref.get();
if (instance != null) {
if (classPropertyFetcher_clazz == null) {
classPropertyFetcher_clazz = instance.getClass().getDeclaredField("clazz");
}
classPropertyFetcher_clazz.setAccessible(true);
Class<?> clazz = (Class<?>) classPropertyFetcher_clazz.get(instance);
if (clazz == reloadedClazz) {
if (classPropertyFetcher_init == null) {
classPropertyFetcher_init = instance.getClass().getDeclaredMethod("init");
}
classPropertyFetcher_init.setAccessible(true);
classPropertyFetcher_init.invoke(instance);
if (GlobalConfiguration.debugplugins) {
System.err.println("GrailsPlugin: re-initing classPropertyFetcher instance for " + clazz.getName()
+ " " + System.identityHashCode(instance));
}
// System.out.println("re-initing " + reloadedClazz.getName());
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
public boolean shouldRerunStaticInitializer(String typename, Class<?> clazz, String encodedTimestamp) {
return false;
}
}

View File

@@ -0,0 +1,116 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded.agent;
import java.security.ProtectionDomain;
import org.objectweb.asm.ClassReader;
import org.springsource.loaded.LoadtimeInstrumentationPlugin;
import org.springsource.loaded.ReloadEventProcessorPlugin;
/**
* What does it do?
* <p>
* 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.
* <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!
* <p>
* To see the difference in these approaches, check the numbers in the Groovy Benchmark tests.
*
* @author Andy Clement
* @since 0.7.0
*/
public class GroovyPlugin implements LoadtimeInstrumentationPlugin, ReloadEventProcessorPlugin {
boolean allowCompilableCallSites = true;
// 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) {
// TODO take classloader into account?
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 {
if (slashedTypeName.equals("org/codehaus/groovy/reflection/ClassLoaderForClassArtifacts")) {
return true;
}
}
return false;
}
public byte[] modify(String slashedClassName, ClassLoader classLoader, byte[] bytes) {
if (allowCompilableCallSites) {
return modifyDefineInClassLoaderForClassArtifacts(bytes);
} else {
// Deactivate compilation
if (slashedClassName.equals("org/codehaus/groovy/runtime/callsite/GroovySunClassLoader")) {
// if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) {
// log.info("loadtime modifying " + slashedClassName);
// }
ClassReader cr = new ClassReader(bytes);
NonFinalizer ca = new NonFinalizer("sunVM");
// ClassVisitingConstructorAppender ca = new ClassVisitingConstructorAppender("org/springsource/loaded/agent/SpringPlugin",
// "recordInstance");
cr.accept(ca, 0);
byte[] newbytes = ca.getBytes();
return newbytes;
} else {
// must be the CallSiteGenerator
ClassReader cr = new ClassReader(bytes);
FalseReturner ca = new FalseReturner("isCompilable");
cr.accept(ca, 0);
byte[] newbytes = ca.getBytes();
return newbytes;
}
}
}
private byte[] modifyDefineInClassLoaderForClassArtifacts(byte[] bytes) {
ClassReader cr = new ClassReader(bytes);
ModifyDefineInClassLoaderForClassArtifactsType ca = new ModifyDefineInClassLoaderForClassArtifactsType();
cr.accept(ca, 0);
byte[] newbytes = ca.getBytes();
return newbytes;
}
// called by the modified code
public static void recordInstance(Object obj) {
}
// implementing CallbackPlugin
public void reloadEvent(String typename, Class<?> clazz, String versionsuffix) {
}
public boolean shouldRerunStaticInitializer(String typename, Class<?> clazz, String encodedTimestamp) {
return false;
}
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded.agent;
/**
* This exception is thrown when something completely unexpected happens (an assertion is violated).
*
* @author Andy Clement
* @since 0.7.3
*/
@SuppressWarnings("serial")
public class Impossible extends RuntimeException {
public Impossible(Exception cause) {
super("This is completely unexpected!", cause);
}
}

View File

@@ -0,0 +1,217 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded.agent;
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.security.ProtectionDomain;
import java.util.Collection;
import java.util.Map;
import org.springsource.loaded.GlobalConfiguration;
import org.springsource.loaded.LoadtimeInstrumentationPlugin;
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.
*
* @author Andy Clement
* @since 0.7.3
*/
public class JVMPlugin implements ReloadEventProcessorPlugin, LoadtimeInstrumentationPlugin {
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) { */
@SuppressWarnings({ "restriction", "unchecked" })
public void reloadEvent(String typename, Class<?> clazz, String encodedTimestamp) {
if (pluginBroken) {
return;
}
if (introspectorLoaded) {
// Clear out the Introspector BeanInfo cache entry that might exist for this class
boolean beanInfoCacheCleared = false;
// In Java7 the AppContext stuff is gone, replaced by a ThreadGroupContext.
// This code grabs the contexts map from the ThreadGroupContext object and clears out the bean info for the reloaded clazz
if (threadGroupContextLoaded) { // In Java 7
beanInfoCacheCleared = clearThreadGroupContext(clazz);
}
// GRAILS-9505 - had to introduce the flushFromCaches(). The appcontext we seem to be able to
// access from AppContext.getAppContext() isn't the same one the Introspector will be using
// so we can fail to clean up the cache. Strangely calling getAppContexts() and clearing them
// all (the code commented out below) doesn't fetch all the contexts. I'm sure it is a nuance of
// app context handling but for now the introspector call is sufficient.
// TODO doesn't this just only clear the beaninfocache for the thread the reload event
// is occurring on? which may not be the thread that was actually using the cache.
if (!beanInfoCacheCleared) {
try {
if (beanInfoCacheField == null) {
beanInfoCacheField = Introspector.class.getDeclaredField("BEANINFO_CACHE");
}
beanInfoCacheField.setAccessible(true);
Object key = beanInfoCacheField.get(null);
Map<Class<?>, BeanInfo> map = (Map<Class<?>, BeanInfo>) sun.awt.AppContext.getAppContext().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) {
// 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) {
e.printStackTrace();
}
}
// Clear out the declaredMethodCache that may exist for this class
try {
if (declaredMethodCacheField == null) {
declaredMethodCacheField = Introspector.class.getDeclaredField("declaredMethodCache");
}
declaredMethodCacheField.setAccessible(true);
Object theCache = declaredMethodCacheField.get(null);
if (putMethod == null) {
putMethod = theCache.getClass().getDeclaredMethod("put", Object.class, Object.class);
}
putMethod.setAccessible(true);
if (GlobalConfiguration.debugplugins) {
System.err.println("JVMPlugin: clearing out declaredMethodCache in Introspector for class " + clazz.getName());
}
putMethod.invoke(theCache, clazz, null);
} 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) {
e.printStackTrace();
}
}
}
private boolean clearThreadGroupContext(Class<?> clazz) {
boolean beanInfoCacheCleared = false;
try {
if (threadGroupContextClass == null) {
threadGroupContextClass = Class.forName("java.beans.ThreadGroupContext", true,
Introspector.class.getClassLoader());
}
if (threadGroupContextClass != null) {
if (threadGroupContext_contextsField == null) {
threadGroupContext_contextsField = threadGroupContextClass.getDeclaredField("contexts");
threadGroupContext_removeBeanInfoMethod = threadGroupContextClass.getDeclaredMethod("removeBeanInfo",
Class.class);
}
if (threadGroupContext_contextsField != null) {
threadGroupContext_contextsField.setAccessible(true);
Object threadGroupContext_contextsField_value = threadGroupContext_contextsField.get(null);
if (threadGroupContext_contextsField_value == null) {
beanInfoCacheCleared = true;
} else {
if (threadGroupContext_contextsField_value instanceof Map) {
// Indicates Java 7 up to rev21
Map<?, ?> m = (Map<?, ?>) threadGroupContext_contextsField_value;
Collection<?> threadGroupContexts = m.values();
for (Object o : threadGroupContexts) {
threadGroupContext_removeBeanInfoMethod.setAccessible(true);
threadGroupContext_removeBeanInfoMethod.invoke(o, clazz);
}
beanInfoCacheCleared = true;
} 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);
Field valueField = null;
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) {
valueField = r.getClass().getDeclaredField("value");
}
valueField.setAccessible(true);
Object threadGroupContext = valueField.get(r);
threadGroupContext_removeBeanInfoMethod.setAccessible(true);
threadGroupContext_removeBeanInfoMethod.invoke(threadGroupContext, clazz);
}
}
}
beanInfoCacheCleared = true;
}
}
}
}
} 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) {
if (slashedTypeName.equals("java/beans/Introspector")) {
introspectorLoaded = true;
} else if (slashedTypeName.equals("java/beans/ThreadGroupContext")) {
threadGroupContextLoaded = true;
}
}
return false;
}
public byte[] modify(String slashedClassName, ClassLoader classLoader, byte[] bytes) {
return null;
}
public boolean shouldRerunStaticInitializer(String typename, Class<?> clazz, String encodedTimestamp) {
return false;
}
}

View File

@@ -0,0 +1,101 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded.agent;
import org.objectweb.asm.ClassAdapter;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodAdapter;
import org.objectweb.asm.MethodVisitor;
import org.springsource.loaded.Constants;
import org.springsource.loaded.GlobalConfiguration;
import org.springsource.loaded.MethodInvokerRewriter;
import org.springsource.loaded.TypeRegistry;
/**
*
* @author Andy Clement
* @since 0.7.3
*/
public class ModifyDefineInClassLoaderForClassArtifactsType extends ClassAdapter implements Constants {
public ModifyDefineInClassLoaderForClassArtifactsType() {
super(new ClassWriter(0)); // TODO review 0 here
}
public byte[] getBytes() {
return ((ClassWriter) cv).toByteArray();
}
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
if (name.equals("define")) {
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
return new DefineClassModifierVisitor(mv);
} else {
return super.visitMethod(access, name, desc, signature, exceptions);
}
}
class DefineClassModifierVisitor extends MethodAdapter implements Constants {
public DefineClassModifierVisitor(MethodVisitor mv) {
super(mv);
}
@Override
public void visitCode() {
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",
"modify", "(Ljava/lang/ClassLoader;Ljava/lang/String;[B)[B");
mv.visitVarInsn(ASTORE, 2);
}
}
/**
* 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 classloader
* @param name
* @param bytes
* @return
*/
public static byte[] modify(ClassLoader classloader, String name, byte[] bytes) {
// System.out.println("Seen '" + name + "' being defined by " + classloader);
// ClassPrinter.print(bytes, true);
ClassLoader parent = classloader.getParent();
if (parent != null) {
TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(parent);
// classloader.getParent() can return null - I've seen it when the target of the call being compiled is
// java_lang_class$getDeclaredFields (i.e. a system class that cant change, so rewriting is unnecessary...)
if (typeRegistry != null) {
bytes = typeRegistry.methodCallRewrite(bytes);
} else {
if (GlobalConfiguration.verboseMode) {
System.out.println("No type registry found for parent classloader: " + parent);
}
bytes = MethodInvokerRewriter.rewrite(null, bytes, true);
}
} else {
bytes = MethodInvokerRewriter.rewrite(null, bytes, true);
}
return bytes;
}
}

View File

@@ -0,0 +1,91 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded.agent;
import java.lang.reflect.Modifier;
import org.objectweb.asm.ClassAdapter;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.springsource.loaded.Constants;
/**
* Makes a field or fields non final.
*
* @author Andy Clement
* @since 0.7.0
*/
public class NonFinalizer extends ClassAdapter 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.
*
* @param owner
* @param name
*/
public NonFinalizer(String fieldname) {
super(new ClassWriter(0)); // TODO review 0 here
this.fieldname = fieldname;
}
public byte[] getBytes() {
return ((ClassWriter) cv).toByteArray();
}
@Override
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 {
return super.visitField(access, name, desc, signature, value);
}
}
// public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
// if (name.equals("<init>")) {
// MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
// return new ConstructorAppender(mv);
// } 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.
*/
// class ConstructorAppender extends MethodAdapter implements Constants {
//
// public ConstructorAppender(MethodVisitor mv) {
// super(mv);
// }
//
// @Override
// public void visitInsn(int opcode) {
// if (opcode == RETURN) {
// mv.visitVarInsn(ALOAD, 0);
// mv.visitMethodInsn(INVOKESTATIC, calleeOwner, calleeName, "(Ljava/lang/Object;)V");
// }
// super.visitInsn(opcode);
// }
//
// }
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded.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>.
*
* @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
* @return the modified bytes for the class
*/
public static byte[] addInstanceTracking(byte[] bytes, String classToCall) {
ClassReader cr = new ClassReader(bytes);
ClassVisitingConstructorAppender ca = new ClassVisitingConstructorAppender(classToCall, "recordInstance");
cr.accept(ca, 0);
byte[] newbytes = ca.getBytes();
return newbytes;
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded.agent;
/**
*
* @author Andy Clement
* @since 0.7.1
*/
public enum ReloadDecision {
/**
* YES means the plugin thinks it should definetly be reloadable
*/
YES,
/**
* NO means the plugin thinks is should definetly not be reloadable
*/
NO,
/**
* PASS means the plugin has no opinion, leave it to other plugins to decide
*/
PASS
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded.agent;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.springsource.loaded.FileChangeListener;
import org.springsource.loaded.GlobalConfiguration;
import org.springsource.loaded.ReloadableType;
import org.springsource.loaded.TypeRegistry;
/**
*
* @author Andy Clement
* @since 0.5.0
*/
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) {
this.typeRegistry = typeRegistry;
}
public void fileChanged(File file) {
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) {
log.info("ReloadableFileChangeListener: change detected in " + file);
}
ReloadableType rtype = correspondingReloadableTypes.get(file);
typeRegistry.loadNewVersion(rtype, file);
}
public void register(ReloadableType rtype, File file) {
correspondingReloadableTypes.put(file, rtype);
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2010-2014 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded.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.
*
* @author Andy Clement
* @sinc 0.5.0
*/
public class SpringLoadedAgent {
private static ClassFileTransformer transformer = new ClassPreProcessorAgentAdapter();
private static Instrumentation instrumentation;
public static void premain(String options, Instrumentation inst) {
// Handle duplicate agents
if (instrumentation != null) {
return;
}
instrumentation = inst;
instrumentation.addTransformer(transformer);
}
public static void agentmain(String options, Instrumentation inst) {
if (instrumentation != null) {
return;
}
instrumentation = inst;
instrumentation.addTransformer(transformer);
}
/**
* Returns the Instrumentation instance
*/
public static Instrumentation getInstrumentation() {
if (instrumentation == null) {
throw new UnsupportedOperationException("Java 5 was not started with preMain -javaagent for SpringLoaded");
}
return instrumentation;
}
}

View File

@@ -0,0 +1,605 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded.agent;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.CodeSource;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.springsource.loaded.Constants;
import org.springsource.loaded.GlobalConfiguration;
import org.springsource.loaded.IsReloadableTypePlugin;
import org.springsource.loaded.LoadtimeInstrumentationPlugin;
import org.springsource.loaded.Plugin;
import org.springsource.loaded.ReloadableType;
import org.springsource.loaded.SystemClassReflectionRewriter;
import org.springsource.loaded.TypeRegistry;
import org.springsource.loaded.Utils;
import org.springsource.loaded.SystemClassReflectionRewriter.RewriteResult;
import org.springsource.loaded.ri.ReflectiveInterceptor;
/**
* The entry point for the agent - all classes that can be modified will be passed into preProcess(). They have to be dealt with in
* many different ways:
* <ul>
* <li>reloadable types need their bytecode rewriting
* <li>'framework' types (not loaded by the system classloader) need their reflection rewritten
* <li>system classes need their reflection rewritten in a slightly different way
* </ul>
*
* @author Andy Clement
* @since 0.5.0
*/
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
public static boolean disabled = false;
// Once the first reloadabletype is hit, we can start initializing the system class with reflective interceptors.
// Doing it early can lead to hangs
private static boolean firstReloadableTypeHit = false;
// These are system classes that contain reflection code and so need instrumenting when encountered.
private static List<String> systemClassesContainingReflection;
// Once the system classes have been encountered and instrumented, they need initialization once they have been defined
// to the VM. This records the list of those that have not yet been initialized.
private Map<String, Integer> systemClassesRequiringInitialization = new HashMap<String, Integer>();
public void initialize() {
// When spring loaded is running as an agent, it should not be defining types directly (this setting does not apply to
// the generated types)
GlobalConfiguration.directlyDefineTypes = false;
GlobalConfiguration.fileSystemMonitoring = true;
systemClassesContainingReflection = new ArrayList<String>();
// So that jaxb annotations will cause discovery of the correct properties:
systemClassesContainingReflection.add("com/sun/xml/internal/bind/v2/model/nav/ReflectionNavigator");
// So that proxies are generated with the right set of methods inside
systemClassesContainingReflection.add("sun/misc/ProxyGenerator");
// (at least) the call to getModifiers() needs interception
systemClassesContainingReflection.add("java/lang/reflect/Proxy");
// So that javabeans introspection is intercepter
systemClassesContainingReflection.add("java/beans/Introspector");
// 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.
*
* @return modified bytes
*/
public byte[] preProcess(ClassLoader classLoader, String slashedClassName, ProtectionDomain protectionDomain, byte[] bytes) {
if (disabled) {
return bytes;
}
// System.err.println("> SpringLoadedPreProcessor.preProcess(classLoader=" + classLoader + ",slashedClassName="
// + slashedClassName + ",...)");
// TODO need configurable debug here, ability to dump any code before/after
for (Plugin plugin : getGlobalPlugins()) {
if (plugin instanceof LoadtimeInstrumentationPlugin) {
LoadtimeInstrumentationPlugin loadtimeInstrumentationPlugin = (LoadtimeInstrumentationPlugin) plugin;
if (loadtimeInstrumentationPlugin.accept(slashedClassName, classLoader, protectionDomain, bytes)) {
bytes = loadtimeInstrumentationPlugin.modify(slashedClassName, classLoader, bytes);
}
}
}
tryToEnsureSystemClassesInitialized(slashedClassName);
TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(classLoader);
// if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.FINER)) {
// logEntryToPreprocess(classLoader, slashedClassName, typeRegistry);
// }
// NULL typeRegistry means we should not be fiddling in what this classLoader is loading
// TODO is that true? what about rewriting reflection code outside of the loader doing reloading?
if (typeRegistry == null) {
if (classLoader == null) {
if (systemClassesContainingReflection.contains(slashedClassName)) {
try {
RewriteResult rr = SystemClassReflectionRewriter.rewrite(slashedClassName, bytes);
// System.err.println("Type " + slashedClassName + " rewrite summary: " + rr.summarize());
systemClassesRequiringInitialization.put(slashedClassName, rr.bits);
return rr.bytes;
} catch (Exception re) {
re.printStackTrace();
}
// make conditional?
// } else {
// // We should really track whether this type is using reflection...
// if (SystemClassReflectionInvestigator.investigate(slashedClassName, bytes) > 0) {
// RewriteResult rr = SystemClassReflectionRewriter.rewrite(slashedClassName, bytes);
// System.err.println("Type " + slashedClassName + " rewrite summary: " + rr.summarize());
// systemClassesRequiringInitialization.put(slashedClassName, rr.bits);
// return rr.bytes;
// }
}
// } else if (needsClientSideRewriting(slashedClassName)) {
// bytes = typeRegistry.methodCallRewriteUseCacheIfAvailable(slashedClassName, bytes);
}
return bytes;
}
// What happens here?
// 1. Determine if the type should be made reloadable
// 2. If NO, but something in this classloader might be, then rewrite the call sites.
// 3. If NO, and nothing in this classloader might be, return the original bytes
// 4. If YES, make the type reloadable (including rewriting call sites)
if (typeRegistry.isReloadableTypeName(slashedClassName, protectionDomain, bytes)) {
if (!firstReloadableTypeHit) {
firstReloadableTypeHit = true;
// TODO move into the ctor for ReloadableType so that it can't block loading
tryToEnsureSystemClassesInitialized(slashedClassName);
}
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) {
log.info("processing " + slashedClassName + " as a reloadable type");
}
try {
// TODO decide one way or the other on slashed/dotted from preprocessor to infrastructure
String dottedClassName = slashedClassName.replace('/', '.');
String watchPath = getWatchPathFromProtectionDomain(protectionDomain, slashedClassName);
if (watchPath == null) {
// For a CGLIB generated type, we may still need to make the type reloadable. For example:
// type: com/vmware/rabbit/ApplicationContext$$EnhancerByCGLIB$$512eb60c
// codesource determined to be: file:/Users/aclement/springsource/tc-server-developer-2.1.1.RELEASE/spring-insight-instance/wtpwebapps/hello-rabbit-client/WEB-INF/lib/cglib-nodep-2.2.jar <no signer certificates>
// But if the type 'com/vmware/rabbit/ApplicationContext' is reloadable, then this should be too
boolean makeReloadableAnyway = false;
int cglibIndex = slashedClassName.indexOf("$$EnhancerByCGLIB");
if (cglibIndex != -1) {
String originalType = slashedClassName.substring(0, cglibIndex);
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) {
log.info("Appears to be a CGLIB type, checking if type " + originalType + " is reloadable");
}
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 = 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");
}
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 = 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);
if (interfacesImplemented != null) {
for (int i = 0; i < interfacesImplemented.length; i++) {
if (typeRegistry.isReloadableTypeName(interfacesImplemented[i])) {
makeReloadableAnyway = true;
}
}
}
}
// GRAILS-8098
// The scaffolding loader will load stuff in this innerloader - if we don't make the types in it reloadable then they will clash
// with the original (ordinary version) controller loaded by URLClassLoader (e.g. in an istcheck for some type we will
// 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")) {
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.isRuntimeLogging && log.isLoggable(Level.INFO)) {
log.info("can't watch " + slashedClassName + ": not making it reloadable");
}
if (needsClientSideRewriting(slashedClassName)) {
bytes = typeRegistry.methodCallRewriteUseCacheIfAvailable(slashedClassName, bytes);
}
return bytes;
}
}
ReloadableType rtype = typeRegistry.addType(dottedClassName, bytes);
if (rtype == null && GlobalConfiguration.callsideRewritingOn) {
// 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 {
if (GlobalConfiguration.fileSystemMonitoring && watchPath != null) {
typeRegistry.monitorForUpdates(rtype, watchPath);
}
return rtype.bytesLoaded;
}
} catch (RuntimeException re) {
log.throwing("SpringLoadedPreProcessor", "preProcess", re);
throw re;
}
} 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) {
log.log(Level.SEVERE, "Unexpected problem transforming call sites", t);
}
}
return bytes;
}
private void tryToEnsureSystemClassesInitialized(String slashedClassName) {
if (firstReloadableTypeHit && !systemClassesRequiringInitialization.isEmpty()) {
int lastSlash = slashedClassName.lastIndexOf('/');
String pkg = lastSlash == -1 ? null : slashedClassName.substring(0, lastSlash);
ensurePreparedForInjection();
List<String> toRemoveList = new ArrayList<String>();
for (Map.Entry<String, Integer> me : systemClassesRequiringInitialization.entrySet()) {
String classname = me.getKey();
// A ClassCircularityError can occur in the injectReflectiveInterceptorMethods() method below. Reason:
// ===
// CCE: "A class or interface could not be loaded because it would be its own superclass or superinterface"
// according to the Java Virtual Machine Specification (JVMS 2.17.2).
// The implementation of the virtual machine generally detects this by noting the
// beginning of an attempt to load a class and then noticing when the
// same task thread attempts to load that same class again before the original
// attempt has completed (is still in progress).
// ===
// So, if we attempt to 'fix up' a class here which has a relationship with the type we are currently
// loading, then it looks like a CCE. The crude initial fix is to avoid working on anything in the
// same package as us. This doesn't quite fix all the cases of course but addresses a chunk of them.
// One remaining case I can clearly see in the log is that java.beans.Introspector (which needs fixing up)
// uses a field of type com.sun.beans.WeakCache.
// A full list of the special relationships could be encoded here (don't touch X until Y,Z,etc loaded)
// but that will just get out of date so quickly. Given that it isn't necessarily a problem because
// the fixing up will be re-attempted again, the simplest thing would be just to avoid printing
// CCEs (but log all other issues).
if (pkg != null && classname.startsWith(pkg)) {
continue;
}
int bits = me.getValue();
try {
Class<?> clazz = SpringLoadedPreProcessor.class.getClassLoader().loadClass(classname.replace('/', '.'));
injectReflectiveInterceptorMethods(slashedClassName, bits, clazz);
toRemoveList.add(classname);
} catch (ClassCircularityError cce) {
// See comment above. 'assume' this is OK, the initialization will happen again next time around.
} catch (Exception e) {
e.printStackTrace();
}
}
for (String toRemove : toRemoveList) {
systemClassesRequiringInitialization.remove(toRemove); // TODO threads?
}
}
}
// TODO should cache these retrieved fields/methods for injection into types
/**
* 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,
IllegalAccessException, NoSuchMethodException {
// TODO log the bits
if ((bits & Constants.JLC_GETDECLAREDFIELDS) != 0) {
Field f = clazz.getDeclaredField("__sljlcgdfs");
f.setAccessible(true);
f.set(null, method_jlcgdfs);
}
if ((bits & Constants.JLC_GETDECLAREDFIELD) != 0) {
Field f = clazz.getDeclaredField(jlcgdf);
f.setAccessible(true);
f.set(null, method_jlcgdf);
}
if ((bits & Constants.JLC_GETFIELD) != 0) {
Field f = clazz.getDeclaredField(jlcgf);
f.setAccessible(true);
f.set(null, method_jlcgf);
}
if ((bits & Constants.JLC_GETDECLAREDMETHODS) != 0) {
Field f = clazz.getDeclaredField(jlcgdms);
f.setAccessible(true);
f.set(null, method_jlcgdms);
}
if ((bits & Constants.JLC_GETDECLAREDMETHOD) != 0) {
Field f = clazz.getDeclaredField(jlcgdm);
f.setAccessible(true);
f.set(null, method_jlcgdm);
}
if ((bits & Constants.JLC_GETMETHOD) != 0) {
Field f = clazz.getDeclaredField(jlcgm);
f.setAccessible(true);
f.set(null, method_jlcgm);
}
if ((bits & Constants.JLC_GETDECLAREDCONSTRUCTOR) != 0) {
Field f = clazz.getDeclaredField(jlcgdc);
f.setAccessible(true);
f.set(null, method_jlcgdc);
}
if ((bits & Constants.JLC_GETMODIFIERS) != 0) {
Field f = clazz.getDeclaredField(jlcgmods);
f.setAccessible(true);
f.set(null, method_jlcgmods);
}
if ((bits & Constants.JLC_GETMETHODS) != 0) {
Field f = clazz.getDeclaredField(jlcgms);
f.setAccessible(true);
f.set(null, method_jlcgms);
}
if ((bits & Constants.JLC_GETCONSTRUCTOR) != 0) {
Field f = clazz.getDeclaredField(jlcgc);
f.setAccessible(true);
f.set(null, method_jlcgc);
}
}
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,
method_jlcgc, method_jlcgmods, method_jlcgms;
/**
* Cache the Method objects that will be injected.
*/
private void ensurePreparedForInjection() {
if (!prepared) {
try {
Class<ReflectiveInterceptor> clazz = ReflectiveInterceptor.class;
method_jlcgdfs = clazz.getDeclaredMethod("jlClassGetDeclaredFields", Class.class);
method_jlcgdf = clazz.getDeclaredMethod("jlClassGetDeclaredField", Class.class, String.class);
method_jlcgf = clazz.getDeclaredMethod("jlClassGetField", Class.class, String.class);
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_jlcgc = clazz.getDeclaredMethod("jlClassGetConstructor", Class.class, EMPTY_CLASS_ARRAY_CLAZZ);
method_jlcgmods = clazz.getDeclaredMethod("jlClassGetModifiers", Class.class);
method_jlcgms = clazz.getDeclaredMethod("jlClassGetMethods", Class.class);
} catch (NoSuchMethodException nsme) {
// cant happen, a-hahaha
throw new Impossible(nsme);
}
prepared = true;
}
}
private static boolean needsClientSideRewriting(String slashedClassName) {
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.
*
* @param protectionDomain the protection domain passed in to the defineclass call
* @param slashedClassName the slashed class name currently being defined
* @return the path to watch for changes to this class
*/
private String getWatchPathFromProtectionDomain(ProtectionDomain protectionDomain, String slashedClassName) {
String watchPath = null;
// System.err.println("protectionDomain=" + protectionDomain + " slashedClassName=" + slashedClassName + " protdom="
// + protectionDomain + " codesource=" + (protectionDomain == null ? "null" : protectionDomain.getCodeSource()));
if (protectionDomain == null) {
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.WARNING)) {
log.warning("Changes to type cannot be tracked: " + slashedClassName + " - no protection domain");
}
} else {
try {
CodeSource codeSource = protectionDomain.getCodeSource();
if (codeSource.getLocation() == null) {
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.WARNING)) {
log.warning("null codesource for " + slashedClassName);
}
} else {
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.FINEST)) {
log.finest("Codesource.getLocation()=" + codeSource.getLocation());
}
// A 'URI is not hierarchical' message can come out when the File ctor is called. Cases seen
// so far:
// GRAILS-10384: relative URL file:../foo/bar - should have built it with new File().toURI.toURL() and not just new URL()
File file = null;
URI uri = null;
try {
uri = codeSource.getLocation().toURI();
file = new File(uri);
} catch (IllegalArgumentException iae) {
boolean recovered = false;
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:./")) {
file = new File(uristring.substring(7)).getAbsoluteFile();
}
if (file.exists()) {
recovered = true;
}
}
if (!recovered) {
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")) {
// great! nothing to do
} 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")) {
// 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")) {
// 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 {
throw new UnsupportedOperationException("unable to watch " + slashedClassName.replace('/', '.')
+ ". Computed location=" + file.toString());
}
watchPath = file.toString();
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) {
log.info("Watched location for changes to " + slashedClassName + " is " + watchPath);
}
}
} catch (URISyntaxException e) {
throw new IllegalStateException("Unexpected problem processing URI ", e);
}
}
return watchPath;
}
private static final String[] uninterestingPrefixes = new String[] { "org/codehaus/groovy/", "groovy/", "freemarker/",
"org/springframework/" };
/**
* Record expensive-to-compute log message about what we are doing.
*/
private void logEntryToPreprocess(ClassLoader classLoader, String slashedClassName, TypeRegistry typeRegistry) {
String clname = classLoader == null ? "null" : classLoader.getClass().getName();
if (clname.indexOf('.') != -1) {
clname = clname.substring(clname.lastIndexOf('.') + 1);
}
if (typeRegistry == null) {
// it is less interesting
log.finer("classname=" + slashedClassName + " classloader=" + classLoader + " typeregistry=" + typeRegistry);
} else {
boolean ignore = false;
for (String uninterestingPrefix : uninterestingPrefixes) {
if (slashedClassName.startsWith(uninterestingPrefix)) {
ignore = true;
break;
}
}
if (!ignore) {
log.info("classname=" + slashedClassName + " classloader=" + clname + " typeregistry=" + typeRegistry);
}
// more detailed log entry
log.finer("classname=" + slashedClassName + " classloader=" + classLoader + " typeregistry=" + typeRegistry);
}
}
public static List<Plugin> getGlobalPlugins() {
if (plugins == null) {
plugins = new ArrayList<Plugin>();
// Ordering is important here (for some of the plugins) - try to do the lowest level things first in case the higher level
// operations cause something to happen that will drive the lower level function. For example, the JVM plugin clears the
// Introspector class which is used by the Spring CachedIntrospectionResults class, which is used by the Grails ClassPropertyFetcher (
// through its calls to BeanUtils). If you don't clear the lower level things first then the higher level reinit operations will
// still see the old (incorrect) results.
plugins.add(new JVMPlugin());
plugins.add(new SpringPlugin());
plugins.add(new GroovyPlugin());
plugins.add(new CglibPlugin());
// Not used right now, grails mechanisms are clearing the state that this plugin is trying to
// plugins.add(new GrailsPlugin());
List<String> extraGlobalPlugins = GlobalConfiguration.pluginClassnameList;
if (extraGlobalPlugins != null) {
for (String globalPlugin : extraGlobalPlugins) {
try {
Class<?> pluginClass = Class.forName(globalPlugin, false, SpringLoadedPreProcessor.class.getClassLoader());
plugins.add((Plugin) pluginClass.newInstance());
} catch (ClassNotFoundException e) {
System.err.println("Unexpected problem loading global plugin:" + globalPlugin);
e.printStackTrace(System.err);
} catch (InstantiationException e) {
System.err.println("Unexpected problem loading global plugin:" + globalPlugin);
e.printStackTrace(System.err);
} catch (IllegalAccessException e) {
System.err.println("Unexpected problem loading global plugin:" + globalPlugin);
e.printStackTrace(System.err);
}
}
}
}
return plugins;
}
private static List<IsReloadableTypePlugin> isReloadableTypePlugins = null;
public static List<IsReloadableTypePlugin> getIsReloadableTypePlugins() {
if (isReloadableTypePlugins == null) {
synchronized (SpringLoadedPreProcessor.class) {
if (isReloadableTypePlugins == null) {
isReloadableTypePlugins = new ArrayList<IsReloadableTypePlugin>();
for (Plugin p : getGlobalPlugins()) {
if (p instanceof IsReloadableTypePlugin) {
isReloadableTypePlugins.add((IsReloadableTypePlugin) p);
}
}
}
}
}
return isReloadableTypePlugins;
}
public static void registerGlobalPlugin(Plugin instance) {
getGlobalPlugins(); // trigger initialization
plugins.add(instance);
isReloadableTypePlugins = null; // reset this cached value
}
public static void unregisterGlobalPlugin(Plugin instance) {
getGlobalPlugins(); // trigger initialization
plugins.remove(instance);
isReloadableTypePlugins = null; // reset this cached value
}
}

View File

@@ -0,0 +1,238 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded.agent;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.objectweb.asm.ClassReader;
import org.springsource.loaded.GlobalConfiguration;
import org.springsource.loaded.LoadtimeInstrumentationPlugin;
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.
* </ul>
*
* @author Andy Clement
* @since 0.5.0
*/
public class SpringPlugin implements LoadtimeInstrumentationPlugin, ReloadEventProcessorPlugin {
private static Logger log = Logger.getLogger(SpringPlugin.class.getName());
// TODO [gc] what about GC here - how do we know when they are finished with?
public static List<Object> instancesOf_AnnotationMethodHandlerAdapter = new ArrayList<Object>();
public static List<Object> instancesOf_DefaultAnnotationHandlerMapping = new ArrayList<Object>();
public static List<Object> instancesOf_RequestMappingHandlerMapping = new ArrayList<Object>();
public static boolean support305 = true;
private Field classCacheField;
private boolean cachedIntrospectionResultsClassLoaded = false;
private Class<?> cachedIntrospectionResultsClass = null;
public boolean accept(String slashedTypeName, ClassLoader classLoader, ProtectionDomain protectionDomain, byte[] bytes) {
// TODO take classloader into account?
if (slashedTypeName == null) {
return false;
}
// Just interested in whether this type got loaded
if (slashedTypeName.equals("org/springframework/beans/CachedIntrospectionResults")) {
cachedIntrospectionResultsClassLoaded = true;
}
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)) {
log.info("loadtime modifying " + slashedClassName);
}
if (slashedClassName.equals("org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter")) {
return bytesWithInstanceCreationCaptured(bytes, "org/springsource/loaded/agent/SpringPlugin",
"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, "org/springsource/loaded/agent/SpringPlugin",
"recordRequestMappingHandlerMappingInstance");
} else { // "org/springframework/web/servlet/mvc/annotation/DefaultAnnotationHandlerMapping"
// springmvc spring 3.0
return bytesWithInstanceCreationCaptured(bytes, "org/springsource/loaded/agent/SpringPlugin",
"recordDefaultAnnotationHandlerMappingInstance");
}
}
// called by the modified code
public static void recordAnnotationMethodHandlerAdapterInstance(Object obj) {
instancesOf_AnnotationMethodHandlerAdapter.add(obj);
}
public static void recordRequestMappingHandlerMappingInstance(Object obj) {
instancesOf_RequestMappingHandlerMapping.add(obj);
}
private static boolean debug = false;
// called by the modified code
public static void recordDefaultAnnotationHandlerMappingInstance(Object obj) {
if (debug) {
System.out.println("Recording new instance of DefaultAnnotationHandlerMappingInstance");
}
instancesOf_DefaultAnnotationHandlerMapping.add(obj);
}
public void reloadEvent(String typename, Class<?> clazz, String versionsuffix) {
removeClazzFromMethodResolverCache(clazz);
clearCachedIntrospectionResults(clazz);
reinvokeDetectHandlers(); // Spring 3.0
reinvokeInitHandlerMethods(); // Spring 3.1
}
private void removeClazzFromMethodResolverCache(Class<?> clazz) {
for (Object o : instancesOf_AnnotationMethodHandlerAdapter) {
try {
Field f = o.getClass().getDeclaredField("methodResolverCache");
f.setAccessible(true);
Map<?, ?> map = (Map<?, ?>) f.get(o);
Method removeMethod = Map.class.getDeclaredMethod("remove", Object.class);
Object ret = removeMethod.invoke(map, clazz);
if (GlobalConfiguration.debugplugins) {
System.err.println("SpringPlugin: clearing methodResolverCache for " + clazz.getName());
}
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) {
log.info("cleared a cache entry? " + (ret != null));
}
} catch (Exception e) {
log.log(Level.SEVERE, "Unexpected problem accessing methodResolverCache on " + o, e);
}
}
}
private void clearCachedIntrospectionResults(Class<?> clazz) {
if (cachedIntrospectionResultsClassLoaded) {
try {
// TODO not a fan of classloading like this
if (cachedIntrospectionResultsClass == null) {
// TODO what about two apps using reloading and diff versions of spring?
cachedIntrospectionResultsClass = clazz.getClassLoader().loadClass(
"org.springframework.beans.CachedIntrospectionResults");
}
if (classCacheField == null) {
classCacheField = cachedIntrospectionResultsClass.getDeclaredField("classCache");
}
classCacheField.setAccessible(true);
Map m = (Map) classCacheField.get(null);
Object o = m.remove(clazz);
if (GlobalConfiguration.debugplugins) {
System.err
.println("SpringPlugin: clearing CachedIntrospectionResults for " + clazz.getName() + " removed=" + o);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
private void reinvokeDetectHandlers() {
// want to call detectHandlers on the DefaultAnnotationHandlerMapping type
// protected void detectHandlers() throws BeansException { is defined on AbstractDetectingUrlHandlerMapping
for (Object o : instancesOf_DefaultAnnotationHandlerMapping) {
if (debug) {
System.out.println("Invoking detectHandlers on instance of DefaultAnnotationHandlerMappingInstance");
}
try {
Class<?> clazz_AbstractDetectingUrlHandlerMapping = o.getClass().getSuperclass();
Method method_detectHandlers = clazz_AbstractDetectingUrlHandlerMapping.getDeclaredMethod("detectHandlers");
method_detectHandlers.setAccessible(true);
method_detectHandlers.invoke(o);
} catch (Exception e) {
// if debugging then print it
if (GlobalConfiguration.debugplugins) {
e.printStackTrace();
}
}
}
}
@SuppressWarnings("rawtypes")
private void reinvokeInitHandlerMethods() {
// org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping (super AbstractHandlerMethodMapping) - call protected void initHandlerMethods() on it.
for (Object o : instancesOf_RequestMappingHandlerMapping) {
if (debug) {
System.out.println("Invoking initHandlerMethods on instance of RequestMappingHandlerMapping");
}
try {
Class<?> clazz_AbstractHandlerMethodMapping = o.getClass().getSuperclass().getSuperclass();
// private final Map<T, HandlerMethod> handlerMethods = new LinkedHashMap<T, HandlerMethod>();
Field field_handlerMethods = clazz_AbstractHandlerMethodMapping.getDeclaredField("handlerMethods");
field_handlerMethods.setAccessible(true);
Map m = (Map) field_handlerMethods.get(o);
m.clear();
Field field_urlMap = clazz_AbstractHandlerMethodMapping.getDeclaredField("urlMap");
field_urlMap.setAccessible(true);
m = (Map) field_urlMap.get(o);
m.clear();
Method method_initHandlerMethods = clazz_AbstractHandlerMethodMapping.getDeclaredMethod("initHandlerMethods");
method_initHandlerMethods.setAccessible(true);
method_initHandlerMethods.invoke(o);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public boolean shouldRerunStaticInitializer(String typename, Class<?> clazz, String encodedTimestamp) {
return false;
}
/**
* 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
*/
private byte[] bytesWithInstanceCreationCaptured(byte[] bytes, String classToCall, String methodToCall) {
ClassReader cr = new ClassReader(bytes);
ClassVisitingConstructorAppender ca = new ClassVisitingConstructorAppender(classToCall, methodToCall);
cr.accept(ca, 0);
byte[] newbytes = ca.getBytes();
return newbytes;
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded.infra;
import java.util.logging.LogRecord;
/**
*
* @author Andy Clement
* @since 0.5.0
*/
public class SLFormatter extends java.util.logging.Formatter {
public String format(LogRecord record) {
StringBuilder s = new StringBuilder();
String sourceClassName = record.getSourceClassName();
int idx;
if ((idx = sourceClassName.lastIndexOf('.')) == -1) {
s.append(record.getSourceClassName());
} else {
s.append(record.getSourceClassName().substring(idx + 1));
}
s.append(".");
s.append(record.getSourceMethodName());
s.append(":");
s.append(super.formatMessage(record));
s.append("\n");
return s.toString();
}
}

View File

@@ -0,0 +1,24 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded.infra;
/**
* @author Andy Clement
* @since 0.5.0
*/
public @interface UsedByGeneratedCode {
}

View File

@@ -0,0 +1,231 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded.jvm;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.logging.Level;
import java.util.logging.Logger;
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
*/
public class JVM {
public static Logger log = Logger.getLogger(JVM.class.getName());
@SuppressWarnings("unchecked")
private static Constructor<Method> jlrMethodCtor = (Constructor<Method>) Method.class.getDeclaredConstructors()[0];
private static Method jlrMethodCopy;
static {
try {
jlrMethodCopy = Method.class.getDeclaredMethod("copy");
jlrMethodCopy.setAccessible(true);
} catch (Exception e) {
log.log(Level.SEVERE, "Problems getting 'Method.copy()' method. Incompatible JVM?", e);
}
}
private static Method jlrFieldCopy;
static {
try {
jlrFieldCopy = Field.class.getDeclaredMethod("copy");
jlrFieldCopy.setAccessible(true);
} catch (Exception e) {
log.log(Level.SEVERE, "Problems getting 'Field.copy()' method. Incompatible JVM?", e);
}
}
private static Method jlrConstructorCopy;
static {
try {
jlrConstructorCopy = Constructor.class.getDeclaredMethod("copy");
jlrConstructorCopy.setAccessible(true);
} catch (Exception e) {
log.log(Level.SEVERE, "Problems getting 'Constructor.copy()' method. Incompatible JVM?", e);
}
}
private static Field jlrMethodModifiers;
static {
try {
jlrMethodModifiers = Method.class.getDeclaredField("modifiers");
jlrMethodModifiers.setAccessible(true);
} catch (Exception e) {
log.log(Level.SEVERE, "Problems getting Field 'Method.modifiers' method. Incompatible JVM?", e);
}
}
private static Field jlrConstructorModifiers;
static {
try {
jlrConstructorModifiers = Constructor.class.getDeclaredField("modifiers");
jlrConstructorModifiers.setAccessible(true);
} catch (Exception e) {
log.log(Level.SEVERE, "Problems getting Field 'Constructor.modifiers' method. Incompatible JVM?", e);
}
}
private static Field jlrFieldModifiers;
static {
try {
jlrFieldModifiers = Field.class.getDeclaredField("modifiers");
jlrFieldModifiers.setAccessible(true);
} catch (Exception e) {
log.log(Level.SEVERE, "Problems getting Field 'Field.modifiers' method. Incompatible JVM?", e);
}
}
@SuppressWarnings("restriction")
public static void ensureMemberAccess(Class<?> callerClass, Class<?> declaringClass, Object target, int mods)
throws IllegalAccessException {
sun.reflect.Reflection.ensureMemberAccess(callerClass, declaringClass, target, mods);
}
/**
* 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,
int modifiers, String signature) {
// This is what the constructor looks like:
// Method(Class declaringClass, String name, Class[] parameterTypes, Class returnType,
// Class[] checkedExceptions, int modifiers, int slot, String signature,
// byte[] annotations, byte[] parameterAnnotations, byte[] annotationDefault)
Method returnMethod;
try {
jlrMethodCtor.setAccessible(true);
returnMethod = jlrMethodCtor.newInstance(clazz, name, params, returnType, exceptions, modifiers, 0, signature, null,
null, null);
} catch (Exception e) {
//This shouldn't happen...
ReflectiveInterceptor.log.log(Level.SEVERE, "Internal Error", e);
throw new Error(e);
}
return returnMethod;
}
/**
* Creates a copy of a method object that is equivalent to the original.
*/
public static Method copyMethod(Method method) {
try {
return (Method) jlrMethodCopy.invoke(method);
} catch (Exception e) {
log.log(Level.SEVERE, "Problems copying method. Incompatible JVM?", e);
return method; // return original as the best we can do
}
}
public static Field copyField(Field field) {
try {
return (Field) jlrFieldCopy.invoke(field);
} catch (Exception e) {
log.log(Level.SEVERE, "Problems copying field. Incompatible JVM?", e);
return field; // return original as the best we can do
}
}
public static Constructor<?> copyConstructor(Constructor<?> c) {
try {
return (Constructor<?>) jlrConstructorCopy.invoke(c);
} catch (Exception e) {
log.log(Level.SEVERE, "Problems copying constructor. Incompatible JVM?", e);
return c; // return original as the best we can do
}
}
public static void setMethodModifiers(Method method, int modifiers) {
try {
jlrMethodModifiers.setInt(method, modifiers);
} catch (Exception e) {
log.log(Level.SEVERE, "Couldn't set correct modifiers on reflected method: " + method, e);
}
}
public static void setConstructorModifiers(Constructor<?> c, int modifiers) {
try {
jlrConstructorModifiers.setInt(c, modifiers);
} catch (Exception e) {
log.log(Level.SEVERE, "Couldn't set correct modifiers on reflected constructor: " + c, e);
}
}
public static void setFieldModifiers(Field field, int mods) {
try {
jlrFieldModifiers.setInt(field, mods);
} catch (Exception e) {
log.log(Level.SEVERE, "Couldn't set correct modifiers on reflected field: " + field, e);
}
}
@SuppressWarnings("unchecked")
private static final Constructor<Field> jlFieldCtor = (Constructor<Field>) Field.class.getDeclaredConstructors()[0];
public static Field newField(Class<?> declaring, Class<?> type, int mods, String name, String sig) {
jlFieldCtor.setAccessible(true);
// This is what the constructor looks like:
// Field(Class declaringClass,String name,Class type,int modifiers, int slot, String signature,
// byte[] annotations)
try {
return jlFieldCtor.newInstance(declaring, name, type, mods, 0, sig, null);
} catch (Exception e) {
throw new IllegalStateException("Problem creating reloadable Field: " + declaring.getName() + "." + name, e);
}
}
@SuppressWarnings("unchecked")
private static final Constructor<Constructor<?>> jlConstructorCtor = (Constructor<Constructor<?>>) Constructor.class
.getDeclaredConstructors()[0];
public static Constructor<?> newConstructor(Class<?> clazz, Class<?>[] params, Class<?>[] exceptions, int modifiers,
String signature) {
jlConstructorCtor.setAccessible(true);
// This is what the constructor looks like:
// Constructor(Class<T> declaringClass,
// Class[] parameterTypes,
// Class[] checkedExceptions,
// int modifiers,
// int slot,
// String signature,
// byte[] annotations,
// byte[] parameterAnnotations)
try {
return jlConstructorCtor.newInstance(clazz, params, exceptions, modifiers, 0, signature, null, null);
} catch (Exception e) {
StringBuffer msg = new StringBuffer("Problem creating reloadable Constructor: ");
msg.append(clazz.getName());
msg.append("(");
for (int i = 0; i < params.length; i++) {
if (i > 0) {
msg.append(", ");
}
msg.append(params[i].getName());
}
msg.append(")");
throw new IllegalStateException(msg.toString(), e);
}
}
}

View File

@@ -0,0 +1,172 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded.pluginhelpers;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.ClassAdapter;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.springsource.loaded.Constants;
import org.springsource.loaded.test.infra.ClassPrinter;
/**
* Modifies a class and empties the specified constructors (not a common thing to do!)
*
* @author Andy Clement
* @since 0.8.3
*/
public class EmptyCtor extends ClassAdapter implements Constants {
private String[] descriptors;
/**
* Empty the constructors with the specified descriptors.
*
* @param bytesIn input class as bytes
* @param descriptors descriptors of interest (e.g. "()V")
* @return modified class as byte array
*/
public static byte[] invoke(byte[] bytesIn, String... descriptors) {
ClassReader cr = new ClassReader(bytesIn);
EmptyCtor ca = new EmptyCtor(descriptors);
cr.accept(ca, 0);
byte[] newbytes = ca.getBytes();
return newbytes;
}
private EmptyCtor(String... descriptors) {
super(new ClassWriter(0)); // TODO review 0 here
this.descriptors = descriptors;
}
public byte[] getBytes() {
byte[] bs = ((ClassWriter) cv).toByteArray();
ClassPrinter.print(bs);
return bs;
}
private boolean isInterestingDescriptor(String desc) {
for (int i = 0, max = descriptors.length; i < max; i++) {
if (descriptors[i].equals(desc)) {
return true;
}
}
return false;
}
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
if (name.equals("<init>") && isInterestingDescriptor(desc)) {
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
return new Emptier(mv);
} else {
return super.visitMethod(access, name, desc, signature, exceptions);
}
}
static class Emptier implements MethodVisitor, Constants {
MethodVisitor mv;
public Emptier(MethodVisitor mv) {
this.mv = mv;
}
public AnnotationVisitor visitAnnotationDefault() {
return null;
}
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
return null;
}
public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) {
return null;
}
public void visitAttribute(Attribute attr) {
}
public void visitCode() {
}
public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) {
}
public void visitInsn(int opcode) {
}
public void visitIntInsn(int opcode, int operand) {
}
public void visitVarInsn(int opcode, int var) {
}
public void visitTypeInsn(int opcode, String type) {
}
public void visitFieldInsn(int opcode, String owner, String name, String desc) {
}
public void visitMethodInsn(int opcode, String owner, String name, String desc) {
}
public void visitJumpInsn(int opcode, Label label) {
}
public void visitLabel(Label label) {
}
public void visitLdcInsn(Object cst) {
}
public void visitIincInsn(int var, int increment) {
}
public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) {
}
public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
}
public void visitMultiANewArrayInsn(String desc, int dims) {
}
public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
}
public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) {
}
public void visitLineNumber(int line, Label start) {
}
public void visitMaxs(int maxStack, int maxLocals) {
mv.visitMaxs(1, 1); // TODO adjust visit max numbers based on descriptor length
}
public void visitEnd() {
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
mv.visitInsn(RETURN);
}
}
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded.ri;
import java.lang.reflect.Modifier;
import java.util.List;
/**
* Provides an implementation for dynamic method lookup in a given Method provider.
*
* @author Kris De Volder
* @since 0.5.0
*/
public class DynamicLookup {
private String name;
private String methodDescriptor;
/**
* Create an object capable of performing a dynamic method lookup in some MethodProvider
*/
public DynamicLookup(String name, String methodDescriptor) {
this.name = name;
this.methodDescriptor = methodDescriptor;
}
public Invoker lookup(MethodProvider methodProvider) {
List<Invoker> methods = methodProvider.getDeclaredMethods();
for (Invoker invoker : methods) {
if (matches(invoker)) {
return invoker;
}
}
// Try the superclass context
MethodProvider parent = methodProvider.getSuper();
if (parent != null) {
return lookup(parent);
}
return null;
}
protected boolean matches(Invoker invoker) {
return !Modifier.isPrivate(invoker.getModifiers()) && name.equals(invoker.getName())
&& methodDescriptor.equals(invoker.getMethodDescriptor());
}
}

View File

@@ -0,0 +1,114 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded.ri;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import org.objectweb.asm.Type;
/**
* Utility class to create correctly formatted Exceptions and Errors for different kinds of error conditions.
*
* @author Kris De Volder
* @since 0.5.0
*/
public class Exceptions {
static IllegalAccessException illegalSetFinalFieldException(Field field, Class<?> valueType, Object value) {
// Example of error when setting a primitive type final field:
// Can not set final short field reflection.nonrelfields.NonReloadableClassWithFields.nrlShort to (short)2
String fieldType = field.getType().getName();
String fieldQName = field.getDeclaringClass().getName() + "." + field.getName();
String valueString;
if (value == null) {
valueString = "null value";
} else if (valueType.isPrimitive()) {
valueString = "(" + valueType.getName() + ")" + value;
} else {
valueString = value == null ? "null value" : value.getClass().getName();
}
return new IllegalAccessException("Can not set final " + fieldType + " field " + fieldQName + " to " + valueString);
}
static IllegalArgumentException illegalSetFieldTypeException(Field field, Class<?> valueType, Object value) {
int mods = field.getModifiers() & (Modifier.FINAL | Modifier.STATIC);
String modStr = Modifier.toString(mods);
if (!modStr.equals("")) {
modStr = modStr + " ";
}
String fieldType = field.getType().getName();
String fieldQName = field.getDeclaringClass().getName() + "." + field.getName();
String valueStr;
if (valueType == null) {
valueStr = "null value";
} else if (valueType.isPrimitive()) {
valueStr = "(" + valueType.getName() + ")" + value;
} else {
valueStr = valueType.getName();
}
return new IllegalArgumentException("Can not set " + modStr + fieldType + " field " + fieldQName + " to " + valueStr);
}
public static NoSuchFieldError noSuchFieldError(Field field) {
return new NoSuchFieldError(field.getName());
}
public static NoSuchMethodError noSuchMethodError(Method method) {
return Exceptions.noSuchMethodError(method.getDeclaringClass().getName(), method.getName(),
Type.getMethodDescriptor(method));
}
public static NoSuchMethodError noSuchMethodError(String dottedClassName, String methodName, String methodDescriptor) {
return new NoSuchMethodError(dottedClassName + "." + methodName + methodDescriptor);
}
static NoSuchMethodException noSuchMethodException(Class<?> clazz, String name, Class<?>... params) {
return new NoSuchMethodException(clazz.getName() + "." + name + ReflectiveInterceptor.toParamString(params));
}
static NoSuchFieldException noSuchFieldException(String name) {
return new NoSuchFieldException(name);
}
public static IllegalArgumentException illegalGetFieldType(Field field, Class<?> returnType) {
String fieldQName = field.getDeclaringClass().getName() + "." + field.getName();
String returnTypeName = returnType.getName();
String fieldType = field.getType().getName();
return new IllegalArgumentException("Attempt to get " + fieldType + " field \"" + fieldQName
+ "\" with illegal data type conversion to " + returnTypeName);
}
public static NoSuchMethodException noSuchConstructorException(Class<?> clazz, Class<?>[] params) {
return noSuchMethodException(clazz, "<init>", params);
}
public static NoSuchMethodError noSuchConstructorError(Constructor<?> c) {
//Example error message from Sun JVM:
// Exception in thread "main" java.lang.NoSuchMethodError: blah.Target.<init>(CC)V
// at Main.main(Main.java:10)
return new NoSuchMethodError(c.getDeclaringClass().getName() + ".<init>" + Type.getConstructorDescriptor(c));
}
public static InstantiationException instantiation(Class<?> clazz) {
return new InstantiationException(clazz.getName());
}
}

View File

@@ -0,0 +1,250 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded.ri;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import org.objectweb.asm.Type;
import org.springsource.loaded.CurrentLiveVersion;
import org.springsource.loaded.FieldMember;
import org.springsource.loaded.GlobalConfiguration;
import org.springsource.loaded.ReloadableType;
import org.springsource.loaded.TypeRegistry;
import org.springsource.loaded.Utils;
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.
*
* @author Kris De Volder
* @since 0.5.0
*/
public class FieldLookup {
private static class JavaFieldRef extends FieldRef {
private Field f;
public JavaFieldRef(Field f) {
this.f = f;
}
@Override
public Field getField() {
return f;
}
@Override
public String getName() {
return f.getName();
}
@Override
public boolean isPublic() {
return Modifier.isPublic(f.getModifiers());
}
}
private static class JavaClassFieldProvider extends FieldProvider {
private Class<?> clazz;
public JavaClassFieldProvider(Class<?> clazz) {
this.clazz = clazz;
}
@Override
List<FieldRef> getFields() {
Field[] fields = clazz.getDeclaredFields();
List<FieldRef> refs = new ArrayList<FieldLookup.FieldRef>();
for (Field f : fields) {
refs.add(new JavaFieldRef(f));
}
return refs;
}
@Override
public boolean isInterface() {
return clazz.isInterface();
}
@Override
public FieldProvider[] getInterfaces() {
Class<?>[] itfs = clazz.getInterfaces();
FieldProvider[] provs = new FieldProvider[itfs.length];
for (int i = 0; i < itfs.length; i++) {
provs[i] = FieldProvider.create(itfs[i]);
}
return provs;
}
@Override
public FieldProvider getSuper() {
Class<?> supr = clazz.getSuperclass();
if (supr != null) {
FieldProvider.create(supr);
}
return null;
}
}
static abstract class FieldRef {
public abstract Field getField();
public abstract String getName();
public abstract boolean isPublic();
}
public static class ReloadedTypeFieldRef extends FieldRef {
private ReloadableType rtype;
private FieldMember f;
public ReloadedTypeFieldRef(ReloadableType rtype, FieldMember f) {
if (GlobalConfiguration.assertsOn) {
Utils.assertTrue(rtype.hasBeenReloaded(), "Not yet reloaded: " + rtype.getName());
}
this.rtype = rtype;
this.f = f;
}
@Override
public Field getField() {
Class<?> declaring = Utils.toClass(rtype);
Class<?> type;
try {
type = Utils.toClass(Type.getType(f.getDescriptor()), rtype.typeRegistry.getClassLoader());
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e);
}
return JVM.newField(declaring, type, f.getModifiers(), f.getName(), f.getGenericSignature());
}
@Override
public String getName() {
return f.getName();
}
@Override
public boolean isPublic() {
return f.isPublic();
}
}
protected static abstract class FieldProvider {
abstract List<FieldRef> getFields();
public abstract boolean isInterface();
public abstract FieldProvider[] getInterfaces();
public abstract FieldProvider getSuper();
public static FieldProvider create(ReloadableType rtype) {
return new ReloadableTypeFieldProvider(rtype);
}
public static FieldProvider create(TypeRegistry typeRegistry, String slashyName) {
if (typeRegistry.isReloadableTypeName(slashyName)) {
return create(typeRegistry.getReloadableType(slashyName));
} else {
try {
return create(Utils.toClass(Type.getObjectType(slashyName), typeRegistry.getClassLoader()));
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e);
}
}
}
public static FieldProvider create(Class<?> clazz) {
return new JavaClassFieldProvider(clazz);
}
}
public static class ReloadableTypeFieldProvider extends FieldProvider {
private ReloadableType rtype;
public ReloadableTypeFieldProvider(ReloadableType rtype) {
this.rtype = rtype;
}
@Override
List<FieldRef> getFields() {
FieldMember[] fields = rtype.getLatestTypeDescriptor().getFields();
List<FieldRef> refs = new ArrayList<FieldRef>(fields.length);
for (FieldMember f : fields) {
refs.add(fieldRef(rtype, f));
}
return refs;
}
private FieldRef fieldRef(ReloadableType rtype, FieldMember f) {
CurrentLiveVersion clv = rtype.getLiveVersion();
if (clv == null) {
//Not yet reloaded... use original field (with fixed mods)
try {
Field jf = rtype.getClazz().getDeclaredField(f.getName());
ReflectiveInterceptor.fixModifier(rtype.getLatestTypeDescriptor(), jf);
return new JavaFieldRef(jf);
} catch (Exception e) {
throw new IllegalStateException(e);
}
} else {
//Already reloaded
return new ReloadedTypeFieldRef(rtype, f);
}
}
@Override
public boolean isInterface() {
return rtype.getLatestTypeDescriptor().isInterface();
}
@Override
public FieldProvider[] getInterfaces() {
String[] superItfs = rtype.getLatestTypeDescriptor().getSuperinterfacesName();
FieldProvider[] superProvs = new FieldProvider[superItfs.length];
for (int i = 0; i < superItfs.length; i++) {
superProvs[i] = FieldProvider.create(rtype.typeRegistry, superItfs[i]);
}
return superProvs;
}
@Override
public FieldProvider getSuper() {
String supr = rtype.getLatestTypeDescriptor().getSupertypeName();
if (supr != null) {
return FieldProvider.create(rtype.typeRegistry, supr);
}
return null;
}
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded.ri;
import java.lang.reflect.Field;
import java.util.List;
import org.springsource.loaded.ReloadableType;
/**
* Implementation of filed lookup algorithm for Class.getDeclaredField.
*
* @author Kris De Volder
* @since 0.5.0
*/
public class GetDeclaredFieldLookup extends FieldLookup {
public static Field lookup(ReloadableType rtype, String name) {
FieldRef ref = lookup(FieldProvider.create(rtype), name);
if (ref == null) {
return null;
}
return ref.getField();
}
private static FieldRef lookup(FieldProvider provider, String name) {
List<FieldRef> fields = provider.getFields();
for (FieldRef f : fields) {
if (f.getName().equals(name)) {
return f;
}
}
return null;
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded.ri;
import java.util.List;
/**
* Provides an implementation for method lookup as suitable for 'Class.getDeclaredMethod'
*
* @author Kris De Volder
* @since 0.5.0
*/
public class GetDeclaredMethodLookup {
private String name;
private String paramsDescriptor;
/**
* Create an object capable of performing the lookup in some MethodProvider
*/
public GetDeclaredMethodLookup(String name, String paramsDescriptor) {
this.name = name;
this.paramsDescriptor = paramsDescriptor;
}
public Invoker lookup(MethodProvider methodProvider) {
List<Invoker> methods = methodProvider.getDeclaredMethods();
Invoker found = null;
for (Invoker invoker : methods) {
if (matches(invoker)) {
if (found == null || isMoreSpecificReturnTypeThan(invoker, found)) {
found = invoker;
}
}
}
return found;
}
/**
* @return true if m2 has a more specific return type than m1
*/
private boolean isMoreSpecificReturnTypeThan(Invoker m1, Invoker m2) {
//This uses 'Class.isAssigableFrom'. This is ok, assuming that inheritance hierarchy is not something that we are allowed
// to change on reloads.
Class<?> cls1 = m1.getReturnType();
Class<?> cls2 = m2.getReturnType();
return cls2.isAssignableFrom(cls1);
}
protected boolean matches(Invoker invoker) {
return name.equals(invoker.getName()) && paramsDescriptor.equals(invoker.getParamsDescriptor());
}
@Override
public String toString() {
return "GetDeclaredMethod( " + name + "." + paramsDescriptor + " )";
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded.ri;
import java.lang.reflect.Field;
import java.util.List;
import org.springsource.loaded.ReloadableType;
/**
* Implementation of FieldLookup algorithm for "Class.getField".
*
* @author Kris De Volder
* @since 0.5.0
*/
public class GetFieldLookup extends FieldLookup {
public static Field lookup(ReloadableType rtype, String name) {
FieldRef ref = lookup(FieldProvider.create(rtype), name);
if (ref == null) {
return null;
}
return ref.getField();
}
private static FieldRef lookup(FieldProvider provider, String name) {
List<FieldRef> fields = provider.getFields();
for (FieldRef f : fields) {
if (f.isPublic()) {
if (f.getName().equals(name)) {
return f;
}
}
}
// Didn't find in this type. Check interfaces.
FieldProvider[] itfs = provider.getInterfaces();
for (FieldProvider itf : itfs) {
FieldRef f = lookup(itf, name);
if (f != null) {
return f;
}
}
// Still didn't find... Check superclass but only if we are not an interface
if (!provider.isInterface()) {
FieldProvider supr = provider.getSuper();
if (supr != null) {
FieldRef f = lookup(supr, name);
if (f != null) {
return f;
}
}
}
//Not found
return null;
}
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded.ri;
import java.lang.reflect.Modifier;
import org.springsource.loaded.Utils;
/**
* Implements a 'lookup' strategy that finds methods in the fashion required by java.lang.Class.getMethod
*
* @author Kris De Volder
* @since 0.5.0
*/
public class GetMethodLookup {
private String name;
private String paramsDescriptor;
/**
* Create an object capable of performing the lookup
*/
public GetMethodLookup(String name, String paramsDescriptor) {
this.name = name;
this.paramsDescriptor = paramsDescriptor;
}
public GetMethodLookup(String name, Class<?>[] params) {
this(name, Utils.toParamDescriptor(params));
}
public Invoker lookup(MethodProvider methodProvider) {
Invoker method = methodProvider.getDeclaredMethod(name, paramsDescriptor);
if (method != null && Modifier.isPublic(method.getModifiers())) {
return method;
}
// Try the superclass context (but not for interfaces, we aren't supposed to include Object's methods
// in them!
if (!methodProvider.isInterface()) {
MethodProvider parent = methodProvider.getSuper();
if (parent != null) {
method = lookup(parent);
if (method != null) {
return method;
}
}
}
// Try the interfaces
MethodProvider[] itfs = methodProvider.getInterfaces();
for (MethodProvider itf : itfs) {
Invoker itfMethod = lookup(itf);
if (itfMethod != null) {
return itfMethod;
}
}
return null;
}
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded.ri;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
/**
* @author Kris De Volder
* @since 0.5.0
*
*/
public class GetMethodsLookup {
public Collection<Invoker> lookup(MethodProvider methodProvider) {
Map<String, Invoker> found = new HashMap<String, Invoker>();
collectAll(methodProvider, found);
return found.values();
}
/**
* 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
//is lazy and wants to stop when a method is found, but here we instead collect
//verything bottom up and 'overwrite' earlier results so the last one found is the
//one kept.
//First interfaces in inverse order...
MethodProvider[] itfs = methodProvider.getInterfaces();
for (int i = itfs.length - 1; i >= 0; i--) { // inverse order
collectAll(itfs[i], found);
}
//Then the superclass(es), but only if we're not an interface (interfaces do not report
// the methods of Object!
MethodProvider supr = methodProvider.getSuper();
if (supr != null && !methodProvider.isInterface()) {
collectAll(supr, found);
}
//Finally all our own public methods
for (Invoker method : methodProvider.getDeclaredMethods()) {
if (Modifier.isPublic(method.getModifiers())) {
found.put(method.getName() + method.getMethodDescriptor(), method);
}
}
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded.ri;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
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.
*
* @author Kris De Volder
* @since 0.5.0
*/
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,
InvocationTargetException;
public abstract int getModifiers();
public abstract String getName();
public abstract String getMethodDescriptor();
public String toString() {
return "Invoker(" + Modifier.toString(getModifiers()) + " " + getClassName() + "." + getName() + getMethodDescriptor()
+ ")";
}
public abstract String getClassName();
protected abstract Method createJavaMethod();
public String getParamsDescriptor() {
String methodDescriptor = getMethodDescriptor();
return methodDescriptor.substring(0, methodDescriptor.lastIndexOf(')') + 1);
}
public Class<?> getReturnType() {
return getJavaMethod().getReturnType();
}
public final Method getJavaMethod() {
if (cachedMethod == null) {
cachedMethod = createJavaMethod();
}
return cachedMethod;
}
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded.ri;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
/**
* An implementation of {@link MethodProvider} that provides methods by using the Java reflection API.
*
* @author Kris De Volder
* @since 0.5.0
*/
public class JavaClassMethodProvider extends MethodProvider {
private Class<?> clazz;
public JavaClassMethodProvider(Class<?> clazz) {
this.clazz = clazz;
}
@Override
public List<Invoker> getDeclaredMethods() {
Method[] jMethods = clazz.getDeclaredMethods();
List<Invoker> invokers = new ArrayList<Invoker>(jMethods.length);
for (Method jMethod : jMethods) {
invokers.add(new JavaMethodInvoker(this, jMethod));
}
return invokers;
}
@Override
public MethodProvider getSuper() {
Class<?> supr = clazz.getSuperclass();
if (supr == null) {
return null;
}
return MethodProvider.create(supr);
}
@Override
public MethodProvider[] getInterfaces() {
Class<?>[] jItfs = clazz.getInterfaces();
MethodProvider[] itfs = new MethodProvider[jItfs.length];
for (int i = 0; i < itfs.length; i++) {
itfs[i] = MethodProvider.create(jItfs[i]);
}
return itfs;
}
@Override
public String getSlashedName() {
return getDottedName().replace('.', '/');
}
@Override
public String getDottedName() {
return clazz.getName();
}
@Override
public boolean isInterface() {
return clazz.isInterface();
}
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded.ri;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import org.objectweb.asm.Type;
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.
*
* @author Kris De Volder
* @since 0.5.0
*/
public class JavaMethodCache {
//TODO: [...] This cache uses string+descriptor for key. It may be possible to cache method objects inside MethodMembers
// themselves, which would make for much quicker 'lookup'.
/**
* This class is used to initialise the cache in a thread safe manner. I.e. a fully filled map should be passed into the cache's
* initialize method, so that the 'isInitialized' method will not return true unless initialisation is complete and all entries
* are present.
*/
public static class Initializer {
//To build up initial map entries with 'put'
private Map<String, Method> cache = new HashMap<String, Method>();
protected void put(Method method) {
cache.put(method.getName() + Type.getMethodDescriptor(method), method);
}
}
/**
* Map indexed by name+descriptor.
*/
private Map<String, Method> cache = null;
public boolean isInitialized() {
return cache != null;
}
/**
* This method should be called to put all entries into the map.
*/
public void initialize(Initializer init) {
this.cache = init.cache;
init.cache = null; // Not strictly necessary, but prevents reuse of the initializer.
}
public Method get(MethodMember methodMember) {
return cache.get(methodMember.getNameAndDescriptor());
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded.ri;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import org.objectweb.asm.Type;
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.
*
* @author Kris De Volder
* @since 0.5.0
*/
public class JavaMethodInvoker extends Invoker {
private Method method;
public JavaMethodInvoker(@SuppressWarnings("unused") JavaClassMethodProvider provider, Method method) {
this.method = method;
}
@Override
public Object invoke(Object target, Object... params) throws IllegalArgumentException, IllegalAccessException,
InvocationTargetException {
return method.invoke(target, params);
}
@Override
public Method createJavaMethod() {
return JVM.copyMethod(method);
}
@Override
public int getModifiers() {
return method.getModifiers();
}
@Override
public String getName() {
return method.getName();
}
@Override
public String getMethodDescriptor() {
return Type.getMethodDescriptor(method);
}
@Override
public String getClassName() {
return method.getDeclaringClass().getName();
}
}

View File

@@ -0,0 +1,149 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded.ri;
import java.util.Collection;
import java.util.List;
import org.objectweb.asm.Type;
import org.springsource.loaded.ReloadableType;
import org.springsource.loaded.TypeDescriptor;
import org.springsource.loaded.TypeRegistry;
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.
* <p>
* This class provides that abstraction.
*
* @author Kris De Volder
* @since 0.5.0
*/
public abstract class MethodProvider {
public static MethodProvider create(ReloadableType rtype) {
return new ReloadableTypeMethodProvider(rtype);
}
public static MethodProvider create(TypeRegistry registry, TypeDescriptor typeDescriptor) {
if (typeDescriptor.isReloadable()) {
ReloadableType rtype = registry.getReloadableType(typeDescriptor.getName(), false);
if (rtype == null) {
TypeRegistry tr = registry;
while (rtype == null) {
ClassLoader pcl = tr.getClassLoader().getParent();
if (pcl == null) {
break;
} else {
tr = TypeRegistry.getTypeRegistryFor(pcl);
if (tr == null) {
break;
}
rtype = tr.getReloadableType(typeDescriptor.getName(), false);
}
}
}
if (rtype != null) {
return new ReloadableTypeMethodProvider(rtype);
}
// ReloadableType rtype = registry.getReloadableType(typeDescriptor.getName(), true);
// // TODO rtype can be null if this type hasn't been loaded yet for the first time, is that true?
// // e.g. CGLIB generated proxy for a service type in grails
// if (rtype != null) {
// return new ReloadableTypeMethodProvider(rtype);
// }
}
try {
try {
Type objectType = Type.getObjectType(typeDescriptor.getName());
// TODO doing things this way would mean we aren't 'guessing' the delegation strategy, we
// are instead allowing it to do its thing then looking for the right registry.
// Above we are guessing regular parent delegation.
Class<?> class1 = Utils.toClass(objectType, registry.getClassLoader());
if (typeDescriptor.isReloadable()) {
ClassLoader cl = class1.getClassLoader();
TypeRegistry tr = TypeRegistry.getTypeRegistryFor(cl);
ReloadableType rtype = tr.getReloadableType(typeDescriptor.getName(), true);
if (rtype != null) {
return new ReloadableTypeMethodProvider(rtype);
}
}
return create(class1);
} catch (ClassNotFoundException e) {
throw new IllegalStateException("We have a type descriptor for '" + typeDescriptor.getName()
+ " but no corresponding Java class", e);
}
} catch (RuntimeException re) {
re.printStackTrace();
throw re;
}
}
public static MethodProvider create(Class<?> clazz) {
return new JavaClassMethodProvider(clazz);
}
public abstract List<Invoker> getDeclaredMethods();
public abstract MethodProvider getSuper();
public abstract MethodProvider[] getInterfaces();
public abstract boolean isInterface();
public abstract String getSlashedName();
/**
* @return Full qualified name with "."
*/
public String getDottedName() {
return getSlashedName().replace('/', '.');
}
public Invoker dynamicLookup(int mods, String name, String methodDescriptor) {
return new DynamicLookup(name, methodDescriptor).lookup(this);
}
public Invoker staticLookup(int mods, String name, String methodDescriptor) {
return new StaticLookup(name, methodDescriptor).lookup(this);
}
public Invoker getMethod(String name, Class<?>[] params) {
return new GetMethodLookup(name, params).lookup(this);
}
public Invoker getDeclaredMethod(String name, String paramsDescriptor) {
return new GetDeclaredMethodLookup(name, paramsDescriptor).lookup(this);
}
public Invoker getDeclaredMethod(String name, Class<?>[] params) {
return getDeclaredMethod(name, Utils.toParamDescriptor(params));
}
public Collection<Invoker> getMethods() {
return new GetMethodsLookup().lookup(this);
}
@Override
public String toString() {
return "MethodProvider(" + getDottedName() + ")";
}
}

View File

@@ -0,0 +1,90 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded.ri;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import org.springsource.loaded.MethodMember;
import org.springsource.loaded.jvm.JVM;
/**
*
* @author Kris De Volder
* @since 0.5.0
*/
public class OriginalClassInvoker extends Invoker {
private Class<?> clazz;
private MethodMember method;
private JavaMethodCache methodCache;
public OriginalClassInvoker(Class<?> clazz, MethodMember methodMember, JavaMethodCache methodCache) {
this.clazz = clazz;
this.method = methodMember;
this.methodCache = methodCache;
}
@Override
public Object invoke(Object target, Object... params) throws IllegalArgumentException, IllegalAccessException,
InvocationTargetException {
Method method = getJavaMethod();
method.setAccessible(true); //Disable access checks, we do our own!
return method.invoke(target, params);
}
@Override
public Method createJavaMethod() {
Method retval = method.cachedMethod;
if (retval == null) {
if (!methodCache.isInitialized()) {
JavaMethodCache.Initializer init = new JavaMethodCache.Initializer();
Method[] methods = clazz.getDeclaredMethods();
for (Method m : methods) {
init.put(m);
}
methodCache.initialize(init);
}
retval = methodCache.get(method);
method.cachedMethod = retval;
if (retval.getModifiers() != method.getModifiers()) {
JVM.setMethodModifiers(retval, method.getModifiers());
}
}
return JVM.copyMethod(retval); // Since we got m from a cache we must copy to give it a fresh 'isAccessible' flag.
}
@Override
public int getModifiers() {
return method.getModifiers();
}
@Override
public String getName() {
return method.getName();
}
@Override
public String getMethodDescriptor() {
return method.getDescriptor();
}
@Override
public String getClassName() {
return clazz.getName();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,146 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded.ri;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springsource.loaded.GlobalConfiguration;
import org.springsource.loaded.MethodMember;
import org.springsource.loaded.ReloadableType;
import org.springsource.loaded.TypeDescriptor;
import org.springsource.loaded.TypeRegistry;
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.
*
* @author Kris De Volder
* @since 0.5.0
*/
public class ReloadableTypeMethodProvider extends TypeDescriptorMethodProvider {
ReloadableType rtype;
public ReloadableTypeMethodProvider(ReloadableType rtype) {
if (GlobalConfiguration.assertsOn) {
Utils.assertTrue(rtype != null, "ReloadableTypeMethodProvider rtype should not be null");
}
this.rtype = rtype;
}
protected Invoker invokerFor(final MethodMember methodMember) {
if (rtype.getLiveVersion() == null) {
//Should be possible to call the original method
return new OriginalClassInvoker(rtype.getClazz(), methodMember, rtype.getJavaMethodCache());
} else {
//Should be calling executor method
return ReloadedTypeInvoker.create(this, methodMember);
}
}
public TypeDescriptor getTypeDescriptor() {
return rtype.getLatestTypeDescriptor();
}
@Override
protected TypeRegistry getTypeRegistry() {
return rtype.getTypeRegistry();
}
public ReloadableType getRType() {
return rtype;
}
@Override
public List<Invoker> getDeclaredMethods() {
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;
}
List<Invoker> invokers = super.getDeclaredMethods();
rtype.invokersCache_getDeclaredMethods = invokers;
return invokers;
}
@Override
public Collection<Invoker> getMethods() {
if (TypeRegistry.nothingReloaded && rtype.invokersCache_getMethods != null) {
// use the cached version, it will not change if a reload hasn't occurred
return rtype.invokersCache_getMethods;
}
Collection<Invoker> invokers = super.getMethods();
rtype.invokersCache_getMethods = invokers;
return invokers;
}
@Override
public Invoker getMethod(String name, Class<?>[] params) {
String paramsDescriptor = Utils.toParamDescriptor(params);
if (TypeRegistry.nothingReloaded) {
// use the cache
// TODO manage memory for this cache
Map<String, Map<String, Invoker>> m = rtype.invokerCache_getMethod;
Map<String, Invoker> psToInvoker = m.get(name);
if (psToInvoker != null) {
if (psToInvoker.containsKey(paramsDescriptor)) {
return psToInvoker.get(paramsDescriptor);
}
}
}
Invoker invoker = super.getMethod(name, params);
if (TypeRegistry.nothingReloaded) {
Map<String, Map<String, Invoker>> m = rtype.invokerCache_getMethod;
Map<String, Invoker> psToInvoker = m.get(name);
if (psToInvoker == null) {
psToInvoker = new HashMap<String, Invoker>();
m.put(name, psToInvoker);
}
psToInvoker.put(paramsDescriptor, invoker);
}
return invoker;
}
@Override
public Invoker getDeclaredMethod(String name, String paramsDescriptor) {
if (TypeRegistry.nothingReloaded) {
// use the cache
// TODO manage memory for this cache
Map<String, Map<String, Invoker>> m = rtype.invokerCache_getDeclaredMethod;
Map<String, Invoker> psToInvoker = m.get(name);
if (psToInvoker != null) {
if (psToInvoker.containsKey(paramsDescriptor)) {
return psToInvoker.get(paramsDescriptor);
}
}
}
Invoker invoker = super.getDeclaredMethod(name, paramsDescriptor);
if (TypeRegistry.nothingReloaded) {
Map<String, Map<String, Invoker>> m = rtype.invokerCache_getDeclaredMethod;
Map<String, Invoker> psToInvoker = m.get(name);
if (psToInvoker == null) {
psToInvoker = new HashMap<String, Invoker>();
m.put(name, psToInvoker);
}
psToInvoker.put(paramsDescriptor, invoker);
}
return invoker;
}
}

View File

@@ -0,0 +1,128 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded.ri;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import org.objectweb.asm.Type;
import org.springsource.loaded.CurrentLiveVersion;
import org.springsource.loaded.GlobalConfiguration;
import org.springsource.loaded.MethodMember;
import org.springsource.loaded.ReloadableType;
import org.springsource.loaded.Utils;
import org.springsource.loaded.jvm.JVM;
/**
* Common super type for Invoker for a method on a reloaded type.
*
* @author Kris De Volder
* @since 0.5.0
*/
public abstract class ReloadedTypeInvoker extends Invoker {
ReloadableType rtype;
private MethodMember methodMember;
private ReloadedTypeInvoker(ReloadableTypeMethodProvider declaringType, MethodMember methodMember) {
this.methodMember = methodMember;
rtype = declaringType.getRType();
if (GlobalConfiguration.assertsOn) {
Utils.assertTrue(rtype.hasBeenReloaded(),
"This class is only equiped to provide invocation/method services for reloaded types");
}
}
@Override
public abstract Object invoke(Object target, Object... params) throws IllegalArgumentException, IllegalAccessException,
InvocationTargetException;
/**
* Create a 'mock' Java Method which is dependent on ReflectiveInterceptor to catch calls to invoke.
*/
@Override
public Method createJavaMethod() {
Class<?> clazz = rtype.getClazz();
String name = methodMember.getName();
String methodDescriptor = methodMember.getDescriptor();
ClassLoader classLoader = rtype.getTypeRegistry().getClassLoader();
try {
Class<?>[] params = Utils.toParamClasses(methodDescriptor, classLoader);
Class<?> returnType = Utils.toClass(Type.getReturnType(methodDescriptor), classLoader);
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);
}
}
@Override
public int getModifiers() {
return methodMember.getModifiers();
}
@Override
public String getName() {
return methodMember.getName();
}
@Override
public String getMethodDescriptor() {
return methodMember.getDescriptor();
}
@Override
public String getClassName() {
return rtype.getName();
}
public static Invoker create(ReloadableTypeMethodProvider declaringType, final MethodMember methodMember) {
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,
InvocationTargetException {
CurrentLiveVersion clv = rtype.getLiveVersion();
Method executor = clv.getExecutorMethod(methodMember);
return executor.invoke(target, params);
}
};
} 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,
InvocationTargetException {
CurrentLiveVersion clv = rtype.getLiveVersion();
Method executor = clv.getExecutorMethod(methodMember);
if (params == null) {
return executor.invoke(null, target);
} else {
Object[] ps = new Object[params.length + 1];
System.arraycopy(params, 0, ps, 1, params.length);
ps[0] = target;
return executor.invoke(null, ps);
}
}
};
}
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded.ri;
import java.util.List;
/**
* Provides an implementation for dynamic method lookup in a given Method provider.
*
* @author Kris De Volder
* @since 0.5.0
*/
public class StaticLookup {
private String name;
private String methodDescriptor;
/**
* Create an object capable of performing a dynamic method lookup in some MethodProvider
*/
public StaticLookup(String name, String methodDescriptor) {
this.name = name;
this.methodDescriptor = methodDescriptor;
}
public Invoker lookup(MethodProvider methodProvider) {
List<Invoker> methods = methodProvider.getDeclaredMethods();
for (Invoker invoker : methods) {
if (matches(invoker)) {
return invoker;
}
}
//Code below unreachable, because 'deleted' methods are checked for
//before the method lookup.
return null;
}
protected boolean matches(Invoker invoker) {
return name.equals(invoker.getName()) && methodDescriptor.equals(invoker.getMethodDescriptor());
}
}

View File

@@ -0,0 +1,93 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded.ri;
import java.util.ArrayList;
import java.util.List;
import org.springsource.loaded.MethodMember;
import org.springsource.loaded.ReloadableType;
import org.springsource.loaded.TypeDescriptor;
import org.springsource.loaded.TypeRegistry;
/**
* Abstract base class for implementation of MethodProvider that are capable of producing a {@link TypeDescriptor}
*
* @author Kris De Volder
* @since 0.5.0
*/
public abstract class TypeDescriptorMethodProvider extends MethodProvider {
protected abstract TypeDescriptor getTypeDescriptor();
protected abstract TypeRegistry getTypeRegistry();
protected abstract Invoker invokerFor(MethodMember methodMember);
@Override
public List<Invoker> getDeclaredMethods() {
TypeDescriptor typeDescriptor = getTypeDescriptor();
MethodMember[] methods = typeDescriptor.getMethods();
List<Invoker> invokers = new ArrayList<Invoker>();
for (MethodMember method : methods) {
if (((MethodMember.BIT_CATCHER | MethodMember.WAS_DELETED) & method.bits) == 0) {
invokers.add(invokerFor(method));
}
}
return invokers;
}
@Override
public MethodProvider getSuper() {
TypeRegistry registry = getTypeRegistry();
TypeDescriptor typeDesc = getTypeDescriptor();
String superName = typeDesc.getSupertypeName();
if (superName == null) {
//This happens only for type Object... Code unreachable unless Object is reloadable
return null;
} else {
ReloadableType rsuper = registry.getReloadableType(superName);
if (rsuper != null) {
return MethodProvider.create(rsuper);
} else {
TypeDescriptor dsuper = registry.getDescriptorFor(superName);
return MethodProvider.create(registry, dsuper);
}
}
}
@Override
public String getSlashedName() {
return getTypeDescriptor().getName();
}
@Override
public MethodProvider[] getInterfaces() {
TypeRegistry registry = getTypeRegistry();
String[] itfNames = getTypeDescriptor().getSuperinterfacesName();
MethodProvider[] itfs = new MethodProvider[itfNames.length];
for (int i = 0; i < itfNames.length; i++) {
itfs[i] = MethodProvider.create(registry, registry.getDescriptorFor(itfNames[i]));
}
return itfs;
}
@Override
public boolean isInterface() {
return getTypeDescriptor().isInterface();
}
}

View File

@@ -0,0 +1,262 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded.test.infra;
import java.io.File;
import java.io.FileInputStream;
import java.io.PrintStream;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.springsource.loaded.Utils;
/**
* @author Andy Clement
*/
public class ClassPrinter implements ClassVisitor, Opcodes {
private PrintStream destination;
private boolean includeBytecode;
public static void main(String[] argv) throws Exception {
ClassReader reader = new ClassReader(Utils.loadBytesFromStream(new FileInputStream(new File(argv[0]))));
reader.accept(new ClassPrinter(System.out, true), 0);
}
public ClassPrinter(PrintStream destination) {
this(destination, true);
}
public ClassPrinter(PrintStream destination, boolean includeBytecode) {
this.destination = destination;
this.includeBytecode = includeBytecode;
}
public static void print(String message, byte[] bytes) {
System.out.println(message);
print(bytes, true);
}
public static void print(byte[] bytes) {
print(bytes, true);
}
public static void print(byte[] bytes, boolean includeBytecode) {
ClassReader reader = new ClassReader(bytes);
reader.accept(new ClassPrinter(System.out, includeBytecode), 0);
}
public static void print(PrintStream printStream, byte[] bytes, boolean includeBytecode) {
ClassReader reader = new ClassReader(bytes);
reader.accept(new ClassPrinter(printStream, includeBytecode), 0);
}
public static void print(String message, byte[] bytes, boolean includeBytecode) {
System.out.println(message);
print(bytes, includeBytecode);
}
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
destination.println("CLASS: " + name + " v" + Integer.toString(version) + " " + toHex(access, 4) + "("
+ toAccessForClass(access) + ") super " + superName
+ (interfaces == null || interfaces.length == 0 ? "" : " interfaces" + toString(interfaces)));
}
private String toString(Object[] os) {
if (os == null) {
return "";
}
StringBuilder sb = new StringBuilder();
for (Object o : os) {
sb.append(o).append(" ");
}
return sb.toString();
}
private String toAccessForClass(int flags) {
StringBuilder sb = new StringBuilder();
if ((flags & Opcodes.ACC_PUBLIC) != 0) {
sb.append("public ");
}
if ((flags & Opcodes.ACC_PRIVATE) != 0) {
sb.append("private ");
}
if ((flags & Opcodes.ACC_PROTECTED) != 0) {
sb.append("protected ");
}
if ((flags & Opcodes.ACC_STATIC) != 0) {
sb.append("static ");
}
if ((flags & Opcodes.ACC_FINAL) != 0) {
sb.append("final ");
}
if ((flags & Opcodes.ACC_SYNCHRONIZED) != 0) {
sb.append("synchronized ");
}
if ((flags & Opcodes.ACC_BRIDGE) != 0) {
sb.append("bridge ");
}
if ((flags & Opcodes.ACC_VARARGS) != 0) {
sb.append("varargs ");
}
if ((flags & Opcodes.ACC_NATIVE) != 0) {
sb.append("native ");
}
if ((flags & Opcodes.ACC_ABSTRACT) != 0) {
sb.append("abstract ");
}
if ((flags & Opcodes.ACC_SYNTHETIC) != 0) {
sb.append("synthetic ");
}
if ((flags & Opcodes.ACC_DEPRECATED) != 0) {
sb.append("deprecated ");
}
if ((flags & Opcodes.ACC_INTERFACE) != 0) {
sb.append("interface ");
}
return sb.toString().trim();
}
public static String toAccessForMember(int flags) {
StringBuilder sb = new StringBuilder();
if ((flags & Opcodes.ACC_PUBLIC) != 0) {
sb.append("public ");
}
if ((flags & Opcodes.ACC_PRIVATE) != 0) {
sb.append("private ");
}
if ((flags & Opcodes.ACC_STATIC) != 0) {
sb.append("static ");
}
if ((flags & Opcodes.ACC_PROTECTED) != 0) {
sb.append("protected ");
}
if ((flags & Opcodes.ACC_FINAL) != 0) {
sb.append("final ");
}
if ((flags & Opcodes.ACC_SUPER) != 0) {
sb.append("super ");
}
if ((flags & Opcodes.ACC_INTERFACE) != 0) {
sb.append("interface ");
}
if ((flags & Opcodes.ACC_ABSTRACT) != 0) {
sb.append("abstract ");
}
if ((flags & Opcodes.ACC_SYNTHETIC) != 0) {
sb.append("synthetic ");
}
if ((flags & Opcodes.ACC_ANNOTATION) != 0) {
sb.append("annotation ");
}
if ((flags & Opcodes.ACC_ENUM) != 0) {
sb.append("enum ");
}
if ((flags & Opcodes.ACC_DEPRECATED) != 0) {
sb.append("deprecated ");
}
return sb.toString().trim();
}
private String toHex(int i, int len) {
StringBuilder sb = new StringBuilder("00000000");
sb.append(Integer.toHexString(i));
return "0x" + sb.substring(sb.length() - len);
}
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
destination.print("ANNOTATION " + desc + " vis?" + visible + " VALUE ");
return new AnnotationVisitorPrinter();
}
class AnnotationVisitorPrinter implements AnnotationVisitor {
public void visit(String name, Object value) {
destination.print(name + "=" + value + " ");
}
public void visitEnum(String name, String desc, String value) {
destination.print(name + "=" + desc + "." + value + " ");
}
public AnnotationVisitor visitAnnotation(String name, String desc) {
destination.print(name + "=" + desc + " ");
return new AnnotationVisitorPrinter();
}
public AnnotationVisitor visitArray(String name) {
destination.print(name + " ");
return new AnnotationVisitorPrinter();
}
public void visitEnd() {
destination.println();
}
}
public void visitAttribute(Attribute attr) {
}
public void visitEnd() {
destination.println();
}
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
StringBuilder sb = new StringBuilder();
sb.append("FIELD " + toHex(access, 4) + "(" + toAccessForMember(access) + ") " + name + " " + desc
+ (signature != null ? " " + signature : ""));
destination.println(sb.toString().trim());
return null;
}
public void visitInnerClass(String name, String outerName, String innerName, int access) {
destination.println("INNERCLASS: " + name + " " + outerName + " " + innerName + " " + access);
}
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));
destination.println(s.toString().trim());
return includeBytecode ? new MethodPrinter(destination) : null;
}
private String fromArray(Object[] os) {
if (os == null) {
return "";
}
StringBuilder sb = new StringBuilder();
for (Object o : os) {
sb.append(o).append(" ");
}
return sb.toString();
}
public void visitOuterClass(String owner, String name, String desc) {
destination.println("OUTERCLASS: " + owner + " " + name + " " + desc);
}
public void visitSource(String source, String debug) {
destination.println("SOURCE: " + source + " " + debug);
}
}

View File

@@ -0,0 +1,215 @@
/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded.test.infra;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.springsource.loaded.Utils;
/**
*
* @author Andy Clement
*/
public class MethodPrinter implements MethodVisitor, Opcodes {
PrintStream to;
List<Label> labels = new ArrayList<Label>();
private String toString(Label label) {
int idx = labels.indexOf(label);
if (idx != -1) {
return "L" + idx;
}
labels.add(label);
return "L" + labels.indexOf(label);
}
public MethodPrinter(PrintStream destination) {
this.to = destination;
}
public void visitCode() {
to.print(" CODE\n");
}
public void visitMethodInsn(int opcode, String owner, String name, String desc) {
if (opcode == Opcodes.INVOKESTATIC) {
to.println(" INVOKESTATIC " + owner + "." + name + desc);
} else if (opcode == Opcodes.INVOKESPECIAL) {
to.println(" INVOKESPECIAL " + owner + "." + name + desc);
} else if (opcode == Opcodes.INVOKEVIRTUAL) {
to.println(" INVOKEVIRTUAL " + owner + "." + name + desc);
} else if (opcode == Opcodes.INVOKEINTERFACE) {
to.println(" INVOKEINTERFACE " + owner + "." + name + desc);
} else {
throw new IllegalStateException(":" + opcode);
}
}
// -- pas de implemented
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
to.print("ANNOTATION " + desc + " vis?" + visible + " VALUE ");
return new AnnotationVisitorPrinter();
}
class AnnotationVisitorPrinter implements AnnotationVisitor {
public void visit(String name, Object value) {
to.print(name + "=" + value + " ");
}
public void visitEnum(String name, String desc, String value) {
to.print(name + "=" + desc + "." + value + " ");
}
public AnnotationVisitor visitAnnotation(String name, String desc) {
to.print(name + "=" + desc + " ");
return new AnnotationVisitorPrinter();
}
public AnnotationVisitor visitArray(String name) {
to.print(name + " ");
return new AnnotationVisitorPrinter();
}
public void visitEnd() {
to.println();
}
}
public AnnotationVisitor visitAnnotationDefault() {
return null;
}
public void visitAttribute(Attribute attr) {
}
public void visitEnd() {
}
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) {
to.println(" PUTSTATIC " + owner + "." + name + " " + desc);
} else if (opcode == Opcodes.GETFIELD) {
to.println(" GETFIELD " + owner + "." + name + " " + desc);
} else if (opcode == Opcodes.PUTFIELD) {
to.println(" PUTFIELD " + owner + "." + name + " " + desc);
} else {
throw new IllegalStateException(":" + opcode);
}
}
public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) {
}
public void visitIincInsn(int var, int increment) {
}
public void visitInsn(int opcode) {
to.println(" " + Utils.toOpcodeString(opcode));
}
public void visitIntInsn(int opcode, int operand) {
to.println(" " + Utils.toOpcodeString(opcode) + " " + operand);
}
public void visitJumpInsn(int opcode, Label label) {
to.println(" " + Utils.toOpcodeString(opcode) + " " + toString(label));
}
public void visitLabel(Label label) {
to.println(" " + toString(label));
}
public void visitLdcInsn(Object cst) {
to.println(" LDC " + cst);
}
public void visitLineNumber(int line, Label start) {
}
public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) {
}
public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
}
public void visitMaxs(int maxStack, int maxLocals) {
}
public void visitMultiANewArrayInsn(String desc, int dims) {
}
public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) {
return null;
}
public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) {
}
public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
}
public void visitTypeInsn(int opcode, String type) {
if (opcode == Opcodes.NEW) { // 187
to.println(" NEW " + type);
} else if (opcode == Opcodes.ANEWARRAY) { // 189
to.println(" ANEWARRAY " + type);
} else if (opcode == Opcodes.CHECKCAST) { // 192
to.println(" CHECKCAST " + type);
} else if (opcode == Opcodes.INSTANCEOF) { // 193
to.println(" INSTANCEOF " + type);
} else {
throw new IllegalStateException(":" + opcode);
}
}
public void visitVarInsn(int opcode, int var) {
if (opcode == Opcodes.ALOAD) {
to.println(" ALOAD " + var);
} else if (opcode == Opcodes.ASTORE) {
to.println(" ASTORE " + var);
} else if (opcode == Opcodes.ILOAD) {
to.println(" ILOAD " + var);
} else if (opcode == FLOAD) {
to.println(" FLOAD " + var);
} else if (opcode == LLOAD) {
to.println(" LLOAD " + var);
} else if (opcode == DLOAD) {
to.println(" DLOAD " + var);
} else if (opcode == ISTORE) {
to.println(" ISTORE " + var);
} else if (opcode == LSTORE) {
to.println(" LSTORE " + var);
} else {
throw new IllegalStateException(":" + opcode);
}
}
}