Various changes:

- Fix for GRAILS-10411 (super dispatchers)
- Better forked JVM test harness
- Work in progress on improved logging/explain mode
This commit is contained in:
Andrew Clement
2014-01-31 13:22:49 -08:00
parent 64cd7e84dc
commit 2337f84fdd
53 changed files with 915 additions and 235 deletions

View File

@@ -150,4 +150,5 @@ public interface Constants extends Opcodes {
static final String jlcgms = "__sljlcgms";
static final String jlcgmsDescriptor = "(Ljava/lang/Class;)[Ljava/lang/reflect/Method;";
static final String methodSuffixSuperDispatcher = "_$superdispatcher$";
}

View File

@@ -164,7 +164,7 @@ public class DispatcherBuilder {
}
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
if (MethodMember.isCatcher(method) || MethodMember.isSuperDispatcher(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?
}

View File

@@ -25,11 +25,10 @@ import java.util.logging.Logger;
import org.springsource.loaded.agent.SpringPlugin;
/**
* Encapsulates configurable elements - these are set (to values other than the defaults) in TypeRegistry when the system property
* springloaded configuration is processed. 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.
* springloaded configuration is processed. Some of the options are only used by testcases to make the testcases easier to write and
* more straightforward.
*
* @author Andy Clement
* @since 0.5.0
@@ -59,6 +58,12 @@ public class GlobalConfiguration {
* verbose mode can trigger extra messages. Enable with 'verbose=true'
*/
public static boolean verboseMode = false;
/**
* Can be turned on to enable users to determine the decision process around why
* something is not reloadable.
*/
public static boolean explainMode = false;
/**
* Global control for runtime logging
@@ -263,9 +268,13 @@ public class GlobalConfiguration {
printUsage();
}
else if (kv.equals("verbose")) {
Log.log("verbose mode on, configuration is:"+value);
Log.log("[verbose mode on] Full configuration is:"+value);
verboseMode = true;
}
else if (kv.equals("explain")) {
Log.log("[explain mode on] Reporting on the decision making process within SpringLoaded");
explainMode = true;
}
}
}
}

View File

@@ -120,6 +120,12 @@ public class IncrementalTypeDescriptor implements Constants {
latest.bits |= MethodMember.IS_NEW;
newOrChangedMethods.add(latest);
}
// TODO [perf] not convinced this can occur? Think it through
if (MethodMember.isSuperDispatcher(original) && !MethodMember.isSuperDispatcher(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;

View File

@@ -95,6 +95,11 @@ class MethodCopier extends MethodAdapter implements Constants {
}
return false;
}
private TypeDescriptor getType(String type) {
TypeDescriptor typeDescriptor = this.typeDescriptor.getTypeRegistry().getDescriptorFor(type);
return typeDescriptor;
}
@Override
public void visitFieldInsn(final int opcode, final String owner, final String name, final String desc) {
@@ -125,24 +130,42 @@ class MethodCopier extends MethodAdapter implements Constants {
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 (opcode == INVOKESPECIAL && name.charAt(0) != '<' && !name.startsWith("r$")) {
if (owner.equals(classname)) {
// private method call
// leaving the invokespecial alone will cause a verify error
String descriptor = Utils.insertExtraParameter(owner, desc);
super.visitMethodInsn(INVOKESTATIC, Utils.getExecutorName(classname, suffix), name, descriptor);
return;
} else {
// super call
// TODO Check if this is true: we can just call the catcher directly if there was one, there is no need
// for a superdispatcher
// Only need to redirect to the superdispatcher if it was a protected method
TypeDescriptor supertypeDescriptor = getType(owner);
MethodMember target = supertypeDescriptor.getByNameAndDescriptor(name+desc);
if (target!=null && target.isProtected()) {
// A null target means that method is not in the supertype, so didn't get a superdispatcher
super.visitMethodInsn(INVOKESPECIAL,classname,name+methodSuffixSuperDispatcher,desc);
} else {
super.visitMethodInsn(opcode, owner, name, desc);
}
return;
}
if (!done) {
super.visitMethodInsn(opcode, owner, name, desc);
}
// 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

View File

@@ -36,9 +36,9 @@ public class MethodMember extends AbstractMember {
// computed up front:
public final static int BIT_CATCHER = 0x001;
public final static int BIT_CLASH = 0x0002;
// identifies a catcher method placed into an abstract class (where a method from a super interface hasn't been implemented)
public final static int BIT_CATCHER_INTERFACE = 0x004;
public final static int BIT_SUPERDISPATCHER = 0x0008;
// computed on incremental members to indicate what changed:
public final static int MADE_STATIC = 0x0010;
@@ -170,6 +170,26 @@ public class MethodMember extends AbstractMember {
copy.bits |= MethodMember.BIT_CATCHER;
return copy;
}
public MethodMember superDispatcherFor() {
int newModifiers = modifiers & ~Modifier.NATIVE;
if (name.equals("clone") && (modifiers & Modifier.NATIVE) != 0) {
newModifiers = Modifier.PUBLIC;
} else if ((modifiers & Modifier.PROTECTED) != 0) {
// 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+"_$superdispatcher$", descriptor, signature, exceptions);
copy.bits |= MethodMember.BIT_SUPERDISPATCHER;
return copy;
}
public MethodMember catcherCopyOfWithAbstractRemoved() {
int newModifiers = modifiers & ~(Modifier.NATIVE | Modifier.ABSTRACT);
@@ -221,6 +241,10 @@ public class MethodMember extends AbstractMember {
public static boolean isClash(MethodMember method) {
return (method.bits & MethodMember.BIT_CLASH) != 0;
}
public static boolean isSuperDispatcher(MethodMember method) {
return (method.bits & BIT_SUPERDISPATCHER) != 0;
}
public static boolean isCatcher(MethodMember method) {
return (method.bits & BIT_CATCHER) != 0;
@@ -242,6 +266,9 @@ public class MethodMember extends AbstractMember {
if ((bits & BIT_CLASH) != 0) {
s.append("clash ");
}
if ((bits & BIT_SUPERDISPATCHER) != 0) {
s.append("superdispatcher ");
}
if ((bits & MADE_STATIC) != 0) {
s.append("made_static ");
}

View File

@@ -41,9 +41,8 @@ import org.springsource.loaded.infra.UsedByGeneratedCode;
import org.springsource.loaded.ri.Invoker;
import org.springsource.loaded.ri.JavaMethodCache;
/**
* Represents a type that is reloadable.
* Represents a type that has been processed such that it can be reloaded at runtime.
*
* @author Andy Clement
* @since 0.5.0
@@ -51,7 +50,9 @@ import org.springsource.loaded.ri.JavaMethodCache;
public class ReloadableType {
// TODO when a field is shadowed or renamed and the old one never accessed again, it may be holding onto something and prevent it from GC.
// Thinking about a solution that involves a tag in the FieldAccessor object so that we can check whether a 'repair' is needed on a field accessor (because the type has been reloaded and the map in the accessor hasnt been repaired yet)
// Thinking about a solution that involves a tag in the FieldAccessor object so that we can
// check whether a 'repair' is needed on a field accessor (because the type has been reloaded and
// the map in the accessor hasnt been repaired yet)
private static Logger log = Logger.getLogger(ReloadableType.class.getName());
/** The registry maintaining this reloadable type */
@@ -142,6 +143,9 @@ public class ReloadableType {
Utils.assertDotted(dottedtypename);
}
this.id = id;
if (GlobalConfiguration.verboseMode && log.isLoggable(Level.INFO)) {
log.info("New reloadable type: "+dottedtypename+ " (allocatedId="+id+") "+typeRegistry.toString());
}
this.typeRegistry = typeRegistry;
this.dottedtypename = dottedtypename;
this.slashedtypename = dottedtypename.replace('.', '/');
@@ -269,12 +273,9 @@ public class ReloadableType {
*/
public boolean loadNewVersion(String versionsuffix, byte[] newbytedata) {
javaMethodCache = null;
// int size = newbytedata.length;
// InputStream is = typeRegistry.getClassLoader().getResourceAsStream(this.slashedtypename + ".class");
// byte[] bs = Utils.loadFromStream(is);
//
// System.out.println(">> loadNewVersion " + versionsuffix + " bytesin=" + size
// + " bytesdiscovered through getResourceAsStream" + bs.length);
if (log.isLoggable(Level.INFO)) {
log.info("Loading new version of "+slashedtypename+", identifying suffix "+versionsuffix+", new data length is "+newbytedata.length+"bytes");
}
// If we find our parent classloader has a weavingTransformer
newbytedata = retransform(newbytedata);
@@ -1023,7 +1024,8 @@ public class ReloadableType {
// Did the type originally define it:
MethodMember[] mms = rtype.getTypeDescriptor().getMethods();
for (MethodMember mm : mms) {
if (mm.getNameAndDescriptor().equals(nameAndDescriptor) && !MethodMember.isCatcher(mm)) {
// TODO don't need superdispatcher check, name won't match will it...
if (mm.getNameAndDescriptor().equals(nameAndDescriptor) && !MethodMember.isCatcher(mm) && !MethodMember.isSuperDispatcher(mm)) {
// the original version does implement it
found = true;
break;

View File

@@ -18,9 +18,6 @@ package org.springsource.loaded;
/**
* API for directly interacting with SpringLoaded.
*
* <p>
* tag: API
*
* @author Andy Clement
* @since 0.8.0
*/
@@ -30,12 +27,12 @@ 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
* @param newbytes the data bytecode data to reload as the new version
* @return int return code: 0 is success. 1 is unknown classloader, 2 is unknown type (possibly not yet loaded). 3 is reload
* event failed. 4 is exception occurred.
*/
public static int loadNewVersionOfType(Class<?> clazz, byte[] newbytedata) {
return loadNewVersionOfType(clazz.getClassLoader(), clazz.getName(), newbytedata);
public static int loadNewVersionOfType(Class<?> clazz, byte[] newbytes) {
return loadNewVersionOfType(clazz.getClassLoader(), clazz.getName(), newbytes);
}
/**
@@ -43,11 +40,11 @@ public class SpringLoaded {
*
* @param classLoader the classloader that was used to load the original form of the type
* @param dottedClassname the dotted name of the type being reloaded, e.g. com.foo.Bar
* @param newbytedata the data bytecode data to reload as the new version
* @param newbytes the data bytecode data to reload as the new version
* @return int return code: 0 is success. 1 is unknown classloader, 2 is unknown type (possibly not yet loaded). 3 is reload
* event failed. 4 is exception occurred.
*/
public static int loadNewVersionOfType(ClassLoader classLoader, String dottedClassname, byte[] newbytedata) {
public static int loadNewVersionOfType(ClassLoader classLoader, String dottedClassname, byte[] newbytes) {
try {
// Obtain the type registry of interest
TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(classLoader);
@@ -61,7 +58,7 @@ public class SpringLoaded {
}
// Create a unique version tag for this reload attempt
String tag = Utils.encode(System.currentTimeMillis());
boolean reloaded = reloadableType.loadNewVersion(tag, newbytedata);
boolean reloaded = reloadableType.loadNewVersion(tag, newbytes);
return reloaded ? 0 : 3;
} catch (Exception e) {
e.printStackTrace();

View File

@@ -74,7 +74,7 @@ public class TypeDescriptorExtractor {
public TypeDescriptor getTypeDescriptor() {
if (isReloadableType) {
computeCatchers();
computeCatchersAndSuperdispatchers();
}
computeFieldsRequiringAccessors();
computeClashes();
@@ -141,7 +141,7 @@ public class TypeDescriptorExtractor {
* 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() {
private void computeCatchersAndSuperdispatchers() {
// 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>();
@@ -151,6 +151,7 @@ public class TypeDescriptorExtractor {
if (Modifier.isInterface(this.flags)) {
return;
}
List<String> superDispatcherAddedFor = new ArrayList<String>();
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
@@ -158,6 +159,14 @@ public class TypeDescriptorExtractor {
// permgen, and simplification of stack traces
// if (!supertypeDescriptor.isReloadable()) {
for (MethodMember method : supertypeDescriptor.getMethods()) {
if (shouldCreateSuperDispatcherFor(method) && !superDispatcherAddedFor.contains(method.nameAndDescriptor)) {
// need a public super dispatcher - so that we can reach that super method
// from a reloaded instance of this type
MethodMember superdispatcher = method.superDispatcherFor();
methods.add(superdispatcher);
superDispatcherAddedFor.add(method.nameAndDescriptor);
}
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
@@ -202,6 +211,14 @@ public class TypeDescriptorExtractor {
finalInHierarchy.addAll(shouldNotCatch);
}
// TODO should clone and finalize be in here?
private boolean shouldCreateSuperDispatcherFor(MethodMember method) {
return method.isProtected() && !(
(method.getName().equals("finalize") && method.getDescriptor().equals("()V")) ||
(method.getName().equals("clone") && method.getDescriptor().equals("()Ljava/lang/Object;")));
}
private void addCatchersForNonImplementedMethodsFrom(String interfacename) {
TypeDescriptor interfaceDescriptor = findTypeDescriptor(registry, interfacename);
for (MethodMember method : interfaceDescriptor.getMethods()) {
@@ -257,7 +274,7 @@ public class TypeDescriptorExtractor {
* @return true if it should be caught
*/
private boolean shouldCatchMethod(MethodMember method) {
return !(method.isPrivateStaticFinal() || (method.getName().equals("finalize") && method.getDescriptor().equals("()V")));
return !(method.isPrivateStaticFinal() || method.getName().endsWith(Constants.methodSuffixSuperDispatcher) || (method.getName().equals("finalize") && method.getDescriptor().equals("()V")));
}
public void visit(int version, int flags, String name, String signature, String superclassName, String[] interfaceNames) {

View File

@@ -58,16 +58,16 @@ import org.springsource.loaded.infra.UsedByGeneratedCode;
* @since 0.5.0
*/
public class TypeRegistry {
public static boolean nothingReloaded = true;
private static Logger log = Logger.getLogger(TypeRegistry.class.getName());
/**
* Types in these packages are not reloadable by default ('inclusions' must be specified to override this default).
*/
private final static String[][] ignorablePackagePrefixes;
private static Logger log = Logger.getLogger(TypeRegistry.class.getName());
// The first time something gets reloaded this is flipped
public static boolean nothingReloaded = true;
static {
ignorablePackagePrefixes = new String[26][];
ignorablePackagePrefixes['a' - 'a'] = new String[] { "antlr/" };
@@ -80,6 +80,7 @@ public class TypeRegistry {
}
// @formatter:off
// These classloaders do not get a type registry (do not load reloadable types!)
private final static String[] STANDARD_EXCLUDED_LOADERS = new String[] {
// TODO DIFF rules for excluding this loader? is it necessary to usually exclude under tcserver?
// sun.misc.Launcher$AppClassLoader
@@ -109,9 +110,8 @@ public class TypeRegistry {
private int maxClassDefinitions;
/**
* Map from a classloader to the type registry created to process reloadable types loaded by it.
*
* <p>
* Map from each classloader to the type registry responsible for that loader.
* <p><b>Note:</b>
* Notice that this is a WeakHashMap - the keys are 'weak'. That means a reference in the map doesn't prevent GC of the
* ClassLoader. Once the ClassLoader is gone we don't need that TypeRegistry any more. It isn't WeakReference<TypeRegistry>
* because we do need those things around whilst the ClassLoader is around. Although there is a reference from a ReloadableType
@@ -130,6 +130,7 @@ public class TypeRegistry {
private Map<String, String> rebasePaths = new HashMap<String, String>();
private List<String> pluginClassNames = new ArrayList<String>();
List<Plugin> localPlugins = new ArrayList<Plugin>();
/**
@@ -183,7 +184,7 @@ public class TypeRegistry {
* Create a TypeRegistry for a specified classloader. On creation an id number is allocated for the registry which can then be
* used as shorthand reference to the registry in rewritten code. A sub-classloader is created to handle loading generated
* artifacts - by using a child classloader it can be discarded after a number of reloadings have occurred to recover memory.
* This constructor is only used by the factory method getTypeRegistryFor.
* This constructor is only used by the factory method getTypeRegistryFor().
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
private TypeRegistry(ClassLoader classloader) {
@@ -296,15 +297,16 @@ public class TypeRegistry {
if (cached != null) {
return cached;
}
// TODO cheaper/faster to go up the typeregistry hierarchy?
// This will not work for a generated class, what should we do in that case?
byte[] data = Utils.loadClassAsBytes2(classLoader.get(), slashedname);
byte[] data = Utils.loadSlashedClassAsBytes(classLoader.get(), slashedname);
// As the caller did not say, we need to work it out:
boolean isReloadableType = isReloadableTypeName(slashedname);
TypeDescriptor td = extractor.extract(data, isReloadableType);
if (isReloadableType) {
if (!slashedname.endsWith("Top"))
reloadableTypeDescriptorCache.put(slashedname, td);
reloadableTypeDescriptorCache.put(slashedname, td);
} else {
typeDescriptorCache.put(slashedname, td);
}
@@ -316,7 +318,7 @@ public class TypeRegistry {
if (cached != null) {
return cached;
}
byte[] data = Utils.loadClassAsBytes2(classLoader.get(), slashedname);
byte[] data = Utils.loadSlashedClassAsBytes(classLoader.get(), slashedname);
// As the caller did not say, we need to work it out:
boolean isReloadableType = isReloadableTypeName(slashedname);
TypeDescriptor td = extractor.extract(data, isReloadableType);
@@ -574,6 +576,9 @@ public class TypeRegistry {
if (candidates != null) {
for (String ignorablePackagePrefix : candidates) {
if (slashedName.startsWith(ignorablePackagePrefix)) {
if (GlobalConfiguration.explainMode && log.isLoggable(Level.INFO)) {
log.info("WhyNotReloadable? The type "+slashedName+" is using a package name '"+ignorablePackagePrefix+"' which is considered infrastructure and types within it are not made reloadable");
}
return false;
}
}
@@ -664,14 +669,17 @@ public class TypeRegistry {
* @return true if the type is reloadable, false otherwise
*/
public boolean isReloadableTypeName(String slashedName, ProtectionDomain protectionDomain, byte[] bytes) {
// if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.FINEST)) {
// log.log(Level.FINEST, "> isReloadableTypeName(" + slashedName + ")");
// }
if (GlobalConfiguration.verboseMode && log.isLoggable(Level.FINER)) {
log.finer("entering TypeRegistry.isReloadableTypeName(" + slashedName + ")");
}
if (GlobalConfiguration.assertsOn) {
Utils.assertSlashed(slashedName);
}
if (GlobalConfiguration.isProfiling) {
if (slashedName.startsWith("com/yourkit")) {
if (GlobalConfiguration.explainMode && log.isLoggable(Level.FINER)) {
log.finer("[explanation] The type "+slashedName+" is considered part of yourkit and is not being made reloadable");
}
return false;
}
}
@@ -698,8 +706,14 @@ public class TypeRegistry {
for (IsReloadableTypePlugin plugin : SpringLoadedPreProcessor.getIsReloadableTypePlugins()) {
ReloadDecision decision = plugin.shouldBeMadeReloadable(this,slashedName, protectionDomain, bytes);
if (decision == ReloadDecision.YES) {
if (GlobalConfiguration.explainMode && log.isLoggable(Level.FINER)) {
log.finer("[explanation] The plugin "+plugin.getClass().getName()+" determined type "+slashedName+" is reloadable");
}
return true;
} else if (decision == ReloadDecision.NO) {
if (GlobalConfiguration.explainMode && log.isLoggable(Level.FINER)) {
log.finer("[explanation] The plugin "+plugin.getClass().getName()+" determined type "+slashedName+" is not reloadable");
}
return false;
}
}
@@ -708,8 +722,14 @@ public class TypeRegistry {
// No inclusions, so unless it matches an exclusion, it will be included
if (exclusionPatterns.isEmpty()) {
if (couldBeReloadable(slashedName)) {
if (GlobalConfiguration.explainMode && log.isLoggable(Level.FINER)) {
log.finer("[explanation] The class "+slashedName+" is currently considered reloadable. It matches no exclusions, is accessible from this classloader and is not in a jar/zip.");
}
return true;
} else {
if (GlobalConfiguration.explainMode && log.isLoggable(Level.FINER)) {
log.finer("[explanation] The class "+slashedName+" is not going to be treated as reloadable.");
}
return false;
}
} else {
@@ -1146,7 +1166,7 @@ public class TypeRegistry {
}
// ignore catchers because the dynamic __execute method wont have an implementation of them, we should
// just keep looking for the real thing
if (method != null && MethodMember.isCatcher(method)) {
if (method != null && (MethodMember.isCatcher(method) || MethodMember.isSuperDispatcher(method))) {
method = null;
}
} else {
@@ -1204,7 +1224,7 @@ public class TypeRegistry {
}
// ignore catchers because the dynamic __execute method wont have an implementation of them, we should
// just keep looking for the real thing
if (m != null && MethodMember.isCatcher(m)) {
if (m != null && (MethodMember.isCatcher(m) || MethodMember.isSuperDispatcher(m))) {
m = null;
}
} else {
@@ -1526,8 +1546,8 @@ public class TypeRegistry {
*/
@UsedByGeneratedCode
public static ReloadableType getReloadableType(int typeRegistryId, int typeId) {
if (GlobalConfiguration.logging && log.isLoggable(Level.INFO)) {
log.info("> TypeRegistry.getReloadableType(" + typeRegistryId + "," + typeId + ")");
if (GlobalConfiguration.verboseMode && log.isLoggable(Level.INFO)) {
log.info(">TypeRegistry.getReloadableType(typeRegistryId=" + typeRegistryId + ",typeId=" + typeId + ")");
}
TypeRegistry typeRegistry = registryInstances[typeRegistryId].get();
if (typeRegistry == null) {
@@ -1536,20 +1556,21 @@ public class TypeRegistry {
}
ReloadableType reloadableType = typeRegistry.getReloadableType(typeId);
if (reloadableType == null) {
throw new IllegalStateException("Type registry does not know about type id " + typeId);
throw new IllegalStateException("The type registry "+typeRegistry+" does not know about type id " + typeId);
}
reloadableType.setResolved();
if (GlobalConfiguration.logging && log.isLoggable(Level.INFO)) {
log.info("< TypeRegistry.getReloadableType(" + typeRegistryId + "," + typeId + ") returning " + reloadableType);
if (GlobalConfiguration.verboseMode && log.isLoggable(Level.INFO)) {
log.info("<TypeRegistry.getReloadableType(typeRegistryId=" + typeRegistryId + ",typeId=" + typeId + ") returning " + reloadableType);
}
return reloadableType;
}
public String toString() {
StringBuilder s = new StringBuilder();
s.append("TypeReg id=");
s.append("TypeRegistry(id=");
s.append(System.identityHashCode(this));
s.append(" loader=" + classLoader.get().getClass().getName());
s.append(",loader=" + classLoader.get().getClass().getName());
s.append(")");
return s.toString();
}

View File

@@ -590,7 +590,37 @@ public class TypeRewriter implements Constants {
for (FieldMember field : fms) {
createProtectedFieldGetterSetter(field);
}
MethodMember[] methods = typeDescriptor.getMethods();
for (MethodMember method: methods) {
if (!MethodMember.isSuperDispatcher(method)) {
continue;
}
// TODO topmost test?
String name = method.getName();
String descriptor = method.getDescriptor();
if (GlobalConfiguration.verboseMode && log.isLoggable(Level.FINEST)) {
log.finest("Creating super dispatcher for method "+name+descriptor+" in type "+slashedname);
}
// Create a superdispatcher for this method
MethodVisitor mv = cw.visitMethod(Modifier.PUBLIC, method.getName(), method.getDescriptor(), null, method.getExceptions());
int ps = Utils.getParameterCount(method.getDescriptor());
ReturnType methodReturnType = Utils.getReturnTypeDescriptor(method.getDescriptor());
int lvarIndex = 0;
mv.visitVarInsn(ALOAD, lvarIndex++); // load this
Utils.createLoadsBasedOnDescriptor(mv, descriptor, lvarIndex);
String targetMethod = method.getName().substring(0,method.getName().lastIndexOf("_$"));
mv.visitMethodInsn(Opcodes.INVOKESPECIAL,typeDescriptor.getSupertypeName(),targetMethod,method.getDescriptor());
Utils.addCorrectReturnInstruction(mv, methodReturnType, false);
int maxs = ps + 1;
if (methodReturnType.isDoubleSlot()) {
maxs++;
}
mv.visitMaxs(maxs, maxs);
mv.visitEnd();
}
for (MethodMember method : methods) {
if (!MethodMember.isCatcher(method)) {
continue;

View File

@@ -16,7 +16,10 @@
package org.springsource.loaded;
import java.io.BufferedInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -731,7 +734,7 @@ public class Utils implements Opcodes, Constants {
* @param dottedclassname the dot separated classname without .class suffix
* @return the byte data defining that class
*/
public static byte[] loadClassAsBytes(ClassLoader loader, String dottedclassname) {
public static byte[] loadDottedClassAsBytes(ClassLoader loader, String dottedclassname) {
if (GlobalConfiguration.assertsOn) {
if (dottedclassname.endsWith(".class")) {
throw new IllegalStateException(".class suffixed name should not be passed:" + dottedclassname);
@@ -755,7 +758,7 @@ public class Utils implements Opcodes, Constants {
* @param slashedclassname the dot separated classname without .class suffix
* @return the byte data defining that class
*/
public static byte[] loadClassAsBytes2(ClassLoader loader, String slashedclassname) {
public static byte[] loadSlashedClassAsBytes(ClassLoader loader, String slashedclassname) {
if (GlobalConfiguration.assertsOn) {
if (slashedclassname.endsWith(".class")) {
throw new IllegalStateException(".class suffixed name should not be passed:" + slashedclassname);
@@ -770,6 +773,29 @@ public class Utils implements Opcodes, Constants {
}
return Utils.loadBytesFromStream(is);
}
public static byte[] load(File file) {
try {
FileInputStream fis = new FileInputStream(file);
byte[] data = loadBytesFromStream(fis);
fis.close();
return data;
} catch (IOException ioe) {
ioe.printStackTrace();
return null;
}
}
public static void write(File file, byte[] data) {
try {
FileOutputStream fos = new FileOutputStream(file);
DataOutputStream dos = new DataOutputStream(fos);
dos.write(data);
dos.close();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
/**
* Load all the byte data from an input stream.

View File

@@ -19,11 +19,13 @@ import java.io.File;
import java.io.FileFilter;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.springsource.loaded.FileChangeListener;
import org.springsource.loaded.GlobalConfiguration;
import org.springsource.loaded.TypeRegistry;
/**
* A simple watcher for the file system. Uses a thread to keep an eye on a number of files and calls back registered interested
* parties when a change is observed. The thread only starts when there is something to watch. The thread is given a name indicating
@@ -95,6 +97,8 @@ public class FileSystemWatcher {
class Watcher implements Runnable {
private static Logger log = Logger.getLogger(Watcher.class.getName());
long lastScanTime;
// TODO configurable scan interval?
@@ -222,6 +226,9 @@ class Watcher implements Runnable {
if (f.isDirectory()) {
determineChangesSince(f, lastScanTime);
} else {
if (GlobalConfiguration.verboseMode && log.isLoggable(Level.INFO)) {
log.info("file change observed: "+f);
}
listener.fileChanged(f);
}
}

View File

@@ -42,14 +42,13 @@ 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:
* one of these 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
* <li>reloadable types need their bytecode rewriting so that they can be modified later
* <li>'framework' types (not loaded by the system classloader) need their reflection calls rewritten
* <li>system classes also need their reflection calls modified but in a different way (they cannot have dependencies on types they cannot see)
* </ul>
*
* @author Andy Clement
@@ -63,10 +62,6 @@ public class SpringLoadedPreProcessor implements Constants {
// 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;
@@ -74,9 +69,13 @@ public class SpringLoadedPreProcessor implements Constants {
// to the VM. This records the list of those that have not yet been initialized.
private Map<String, Integer> systemClassesRequiringInitialization = new HashMap<String, Integer>();
// Once the first reloadabletype is hit, we can start initializing the system classes with reflective interceptors.
// Doing it early can lead to hangs
private static boolean firstReloadableTypeHit = false;
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)
// the generated suuport types)
GlobalConfiguration.directlyDefineTypes = false;
GlobalConfiguration.fileSystemMonitoring = true;
systemClassesContainingReflection = new ArrayList<String>();
@@ -98,14 +97,12 @@ public class SpringLoadedPreProcessor implements Constants {
* order to determine whether the type should be made reloadable. Non-reloadable types will at least get their call sites
* rewritten.
*
* @return modified bytes
* @return potentially 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()) {
@@ -120,44 +117,45 @@ public class SpringLoadedPreProcessor implements Constants {
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 (GlobalConfiguration.verboseMode && log.isLoggable(Level.INFO)) {
logPreProcess(classLoader, slashedClassName, typeRegistry);
}
if (typeRegistry == null) { // A null type registry indicates nothing is being made reloadable for the classloader
if (classLoader == null) { // Indicates loading of a system class
if (systemClassesContainingReflection.contains(slashedClassName)) {
try {
// TODO [perf] why are we not using the cache here, is it because the list is so short?
RewriteResult rr = SystemClassReflectionRewriter.rewrite(slashedClassName, bytes);
// System.err.println("Type " + slashedClassName + " rewrite summary: " + rr.summarize());
if (GlobalConfiguration.verboseMode && log.isLoggable(Level.FINER)) {
log.finer("System class rewritten: name="+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;
// }
// This block can help when you suspect there is a system class using reflection and that
// class isn't on the 'shortlist' (in systemClassesContainingReflection). Currently we skip
// this for performance, we could make it optional baed on a configuration option
// } 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)
// What happens here? The aim is to determine if the type should be made reloadable.
// 1. If NO, but something in this classloader might be, then rewrite the call sites.
// 2. If NO, and nothing in this classloader might be, return the original bytes.
// 3. If YES, make the type reloadable (including rewriting call sites)
boolean isReloadableTypeName = typeRegistry.isReloadableTypeName(slashedClassName, protectionDomain, bytes);
@@ -542,34 +540,12 @@ public class SpringLoadedPreProcessor implements Constants {
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) {
private void logPreProcess(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);
}
log.info("SpringLoaded preprocessing: classname="+slashedClassName+" classloader="+clname+" typeRegistry="+typeRegistry);
}
public static List<Plugin> getGlobalPlugins() {

View File

@@ -26,17 +26,23 @@ 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(record.getLevel());
String message = super.formatMessage(record);
if (!(message.startsWith(">") || message.startsWith("<"))) {
s.append(":");
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(".");
s.append(record.getSourceMethodName());
s.append(":");
s.append(super.formatMessage(record));
s.append(message);
s.append("\n");
return s.toString();
}

View File

@@ -44,7 +44,8 @@ public abstract class TypeDescriptorMethodProvider extends MethodProvider {
MethodMember[] methods = typeDescriptor.getMethods();
List<Invoker> invokers = new ArrayList<Invoker>();
for (MethodMember method : methods) {
if (((MethodMember.BIT_CATCHER | MethodMember.WAS_DELETED) & method.bits) == 0) {
// TODO [perf] create constant for this check?
if (((MethodMember.BIT_CATCHER | MethodMember.BIT_SUPERDISPATCHER | MethodMember.WAS_DELETED) & method.bits) == 0) {
invokers.add(invokerFor(method));
}
}