Fix virtual method variant of invokedynamic reloading

Issue: #87
This commit is contained in:
Andy Clement
2015-06-28 19:30:52 -07:00
parent 773d25019b
commit 1eb2a1263c
13 changed files with 598 additions and 86 deletions

View File

@@ -44,15 +44,15 @@ import org.springsource.loaded.ri.JavaMethodCache;
/**
* Represents a type that has been processed such that it can be reloaded at runtime.
*
*
* @author Andy Clement
* @since 0.5.0
*/
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
// 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());
@@ -140,13 +140,14 @@ public class ReloadableType {
return clazz;
}
@Override
public String toString() {
return dottedtypename;
}
/**
* Construct a new ReloadableType with the specified name and the specified initial bytecode.
*
*
* @param dottedtypename the dotted name
* @param initialBytes the bytecode for the initial version
* @param id for this reloadable type, allocated by the registry
@@ -190,7 +191,7 @@ public class ReloadableType {
/**
* Gets the 'orignal' method corresponding to given name and method descriptor. This only considers methods that
* exist in the first (non-reloaded) version of the type.
*
*
* @param name method name
* @param descriptor method descriptor (e.g (Ljava/lang/String;)I)
* @return the MethodMember or an exception if not found
@@ -221,7 +222,7 @@ public class ReloadableType {
* This method will attempt to apply any pre-existing transforms to the provided bytecode, if it is thought to be
* necessary. Currently 'necessary' is determined by finding ourselves running under tcServer and Spring Insight
* being turned on.
*
*
* @param bytes the new bytes to be possibly transformed.
* @return either the original bytes or a transformed set of bytes
*/
@@ -300,7 +301,7 @@ public class ReloadableType {
/**
* Load a new version of this type, using the specified suffix to tag the newly generated artifact class names.
*
*
* @param versionsuffix the String suffix to append to classnames being created for the reloaded class
* @param newbytedata the class bytes for the new version of this class
* @return true if the reload succeeded
@@ -328,16 +329,17 @@ public class ReloadableType {
// Not allowed to change the type
reload = false;
s = new StringBuilder("Spring Loaded: Cannot reload new version of ").append(this.dottedtypename).append(
"\n");
s = new StringBuilder("Spring Loaded: Cannot reload new version of ").append(
this.dottedtypename).append(
"\n");
if (td.hasTypeAccessChanged()) {
s.append(" Reason: Type modifiers changed\n");
s.append(" Reason: Type modifiers changed from=0x" + Integer.toHexString(td.oAccess) + " to=0x"
+ Integer.toHexString(td.nAccess) + "\n");
cantReload = true;
}
if (td.hasTypeSupertypeChanged()) {
s.append(" Reason: Supertype changed from ").append(td.oSuperName).append(" to ").append(
td.nSuperName)
.append("\n");
td.nSuperName).append("\n");
cantReload = true;
}
if (td.hasTypeInterfacesChanged()) {
@@ -370,8 +372,8 @@ public class ReloadableType {
}
}
if (!justGroovyObjectMoved) {
s.append(" Reason: Interfaces changed from ").append(td.oInterfaces).append(" to ")
.append(td.nInterfaces).append("\n");
s.append(" Reason: Interfaces changed from ").append(td.oInterfaces).append(" to ").append(
td.nInterfaces).append("\n");
cantReload = true;
}
}
@@ -535,17 +537,19 @@ public class ReloadableType {
/**
* Go through proxies we know about in this registry and see if any of them are for the type we have just reloaded.
* If they are, regenerate them and reload them.
*
*
* @param versionsuffix the suffix to use when reloading the proxies (it matches what is being used to reload the
* type)
*/
private void reloadProxiesIfNecessary(String versionsuffix) {
ReloadableType proxy = typeRegistry.cglibProxies.get(this.slashedtypename);
if (proxy != null) {
Object[] strategyAndGeneratorPair = CglibPluginCapturing.clazzToGeneratorStrategyAndClassGeneratorMap.get(getClazz());
Object[] strategyAndGeneratorPair = CglibPluginCapturing.clazzToGeneratorStrategyAndClassGeneratorMap.get(
getClazz());
if (strategyAndGeneratorPair == null) {
if (log.isLoggable(Level.SEVERE)) {
log.severe("Unable to find regeneration methods for cglib proxies - proxies will be out of date for this type");
log.severe(
"Unable to find regeneration methods for cglib proxies - proxies will be out of date for this type");
}
return;
}
@@ -572,9 +576,10 @@ public class ReloadableType {
proxy = typeRegistry.cglibProxiesFastClass.get(this.slashedtypename);
if (proxy != null) {
Object[] strategyAndFCGeneratorPair = CglibPluginCapturing.clazzToGeneratorStrategyAndFastClassGeneratorMap
.get(getClazz());
strategyAndFCGeneratorPair = CglibPluginCapturing.clazzToGeneratorStrategyAndFastClassGeneratorMap.get(getClazz());
Object[] strategyAndFCGeneratorPair = CglibPluginCapturing.clazzToGeneratorStrategyAndFastClassGeneratorMap.get(
getClazz());
strategyAndFCGeneratorPair = CglibPluginCapturing.clazzToGeneratorStrategyAndFastClassGeneratorMap.get(
getClazz());
// System.out.println("need to reload fastclass " + proxy + " os=" + os);
if (strategyAndFCGeneratorPair != null) {
Object a = strategyAndFCGeneratorPair[0];
@@ -617,8 +622,8 @@ public class ReloadableType {
}
}
catch (Throwable t) {
new RuntimeException("Unexpected problem trying to reload proxy for interface " + this.dottedtypename, t)
.printStackTrace();
new RuntimeException("Unexpected problem trying to reload proxy for interface " + this.dottedtypename,
t).printStackTrace();
}
}
@@ -744,7 +749,7 @@ public class ReloadableType {
Class<?> class_ClassInfo = typeRegistry.getClass_ClassInfo();
Field field_globalClassSet = class_ClassInfo.getDeclaredField("globalClassSet");
field_globalClassSet.setAccessible(true);
Object/*ClassInfoSet*/instance_classInfoSet = field_globalClassSet.get(null);
Object/*ClassInfoSet*/ instance_classInfoSet = field_globalClassSet.get(null);
Method method_ClassInfoSetRemove = instance_classInfoSet.getClass().getMethod("remove", Object.class);
Object retval = method_ClassInfoSetRemove.invoke(instance_classInfoSet, this.clazz);
@@ -853,7 +858,7 @@ public class ReloadableType {
/**
* Gets the method corresponding to given name and descriptor, taking into consideration changes that have happened
* by reloading.
*
*
* @param name the member name
* @param descriptor the member descriptor (e.g. (Ljava/lang/String;)I)
* @return the MethodMember for that name and descriptor. Null if not found on a live version, or an exception if
@@ -870,7 +875,7 @@ public class ReloadableType {
/**
* Gets the method corresponding to given name and descriptor, from the original type descriptor.
*
*
* @param nameAndDescriptor the method name and descriptor (e.g. foo(Ljava/lang/String;)I)
* @return the MethodMember for the name and descriptor if it exists, otherwise null
*/
@@ -973,7 +978,7 @@ public class ReloadableType {
RewriteClassAdaptor rca = (RewriteClassAdaptor) cv;
if (rca.isEnum && rca.fieldcount > GlobalConfiguration.enumLimit) {
// that is too many fields, marking this as not reloadable
// TODO ...
// TODO ...
}
TypeRewriter.RewriteClassAdaptor a = (TypeRewriter.RewriteClassAdaptor) rca.getClassVisitor();
return ((ClassWriter) a.getClassVisitor()).toByteArray();
@@ -1028,7 +1033,7 @@ public class ReloadableType {
/**
* Check if the specified method is different to the original form from the type as loaded.
*
*
* @param methodId the ID of the method currently executing
* @return 0 if the method cannot have changed. 1 if the method has changed. 2 if the method has been deleted in a
* new version.
@@ -1116,7 +1121,7 @@ public class ReloadableType {
/**
* Intended to handle dynamic dispatch. This will determine the right type to handle the specified method and return
* a dispatcher that can handle it.
*
*
* @param instance the target instance for the invocation
* @param nameAndDescriptor an encoded method name and descriptor, e.g. foo(Ljava/langString;)V
* @return a dispatcher that can handle the method indicated
@@ -1255,7 +1260,7 @@ public class ReloadableType {
* the non-reloadable types. This method also avoids interfaces because it is looking for instance fields. This is
* slightly naughty but if we assume the code we are reloading is valid code, it should never be referring to
* interface fields.
*
*
* @param name the name of the field to locate
* @return the FieldMember or null if the field is not found
*/
@@ -1283,7 +1288,7 @@ public class ReloadableType {
* Search for a static field from this type upwards, as far as the topmost reloadable types. This is searching for a
* field, it is not checking the result. It is up to the caller to check they have not ended up with an instance
* field and throw the appropriate exception.
*
*
* @param name the name of the field to look for
* @return a FieldMember for the named field or null if not found
*/
@@ -1320,7 +1325,7 @@ public class ReloadableType {
/**
* Attempt to set the value of a field on an instance to the specified value.
*
*
* @param instance the object upon which to set the field (maybe null for static fields)
* @param fieldname the name of the field
* @param isStatic whether the field is static
@@ -1349,7 +1354,8 @@ public class ReloadableType {
}
}
private Set<WeakReference<Object>> liveInstances = Collections.synchronizedSet(new HashSet<WeakReference<Object>>());
private Set<WeakReference<Object>> liveInstances = Collections.synchronizedSet(
new HashSet<WeakReference<Object>>());
private ReferenceQueue<Object> liveInstancesRQ = new ReferenceQueue<Object>();
@@ -1361,7 +1367,7 @@ public class ReloadableType {
/**
* Attempt to set the value of a field on an instance to the specified value. Simply locate the field, which returns
* an object capable of reading/writing it, then use that to retrieve the value.
*
*
* @param instance the object upon which to set the field (maybe null for static fields)
* @param fieldname the name of the field
* @param isStatic whether the field is static or not
@@ -1607,7 +1613,7 @@ public class ReloadableType {
* Return the ReloadableType representing the superclass of this type. If the supertype is not reloadable, this
* method will return null. The ReloadableType that is returned may not be within the same type registry, if the
* supertype was loaded by a different classloader.
*
*
* @return the ReloadableType for the supertype or null if it is not reloadable
*/
public ReloadableType getSuperRtype() {

View File

@@ -26,11 +26,14 @@ import java.lang.reflect.Method;
import org.objectweb.asm.Handle;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.springsource.loaded.CurrentLiveVersion;
import org.springsource.loaded.MethodMember;
import org.springsource.loaded.ReloadableType;
import org.springsource.loaded.TypeRegistry;
/**
* This class encapsulates dependencies on Java 8 APIs (e.g. LambdaMetafactory).
*
*
* @author Andy Clement
* @since 1.2
*/
@@ -38,30 +41,30 @@ public class Java8 {
/**
* Notes:
*
*
* Useful to have an example of how this code behaves. Here is a bit of code:
*
*
* class basic.LambdaA { interface Foo { int m(); } static int run() { Foo f = null; f = () -> 77; return f.m(); } }
*
*
* Here is a bootstrap method entry in the constant pool:
*
*
* 0: #31 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:
* (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/
* lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;
* Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; Method arguments: #32
* ()I #33 invokestatic basic/LambdaA.lambda$run$0:()I #32 ()I
*
*
* At the invokedynamic site: bsmId = 0 nameAndDescriptor = m()Lbasic/LambdaA$Foo;
*
*
* When invoking the metafactory bootstrap method the first two parameters are stacked by the VM automatically,
* namely the MethodHandles$Lookup instance (caller) and the first String (invokedName). What the VM actually sees
* is this:
*
*
* metaFactory parameters: 0:MethodHandles$Lookup caller = basic.LambdaA 1:String invokedName = "m" 2:MethodType
* invokedType = "()Foo" 3:MethodType samMethodType = "()int" 4:MethodHandle implMethod = (actually a
* DirectMethodHandle where memberName is "basic.LambdaA.lambda$run$0()int/invokeStatic") 5:MethodType
* instantiatedMethodType = "()int"
*
*
* With all that information then the calls in this case are relatively straightforward: CallSite callsite =
* LambdaMetafactory.metafactory(caller, invokedName, invokedType, samMethodType, implMethod,
* instantiatedMethodType); callsite.dynamicInvoker().invokeWithArguments((Object[])null);
@@ -70,7 +73,7 @@ public class Java8 {
/**
* Programmatic emulation of INVOKEDYNAMIC so initialize the callsite via use of the bootstrap method then invoke
* the result.
*
*
* @param executorClass the executor that will contain the lambda function, null if not yet reloaded
* @param handle bootstrap method handle
* @param bsmArgs bootstrap method arguments
@@ -104,9 +107,9 @@ public class Java8 {
MethodType invokedType = MethodType.fromMethodDescriptorString(
indyNameAndDescriptor.substring(descriptorStart), callerLoader);
// Use bsmArgs to build the parameters
// Use bsmArgs to build the parameters
MethodType samMethodType = MethodType.fromMethodDescriptorString(
(String) (((Type) bsmArgs[0]).getDescriptor()), callerLoader);
(((Type) bsmArgs[0]).getDescriptor()), callerLoader);
Handle bsmArgsHandle = (Handle) bsmArgs[1];
String owner = bsmArgsHandle.getOwner();
@@ -121,7 +124,7 @@ public class Java8 {
implMethod = caller.findStatic(caller.lookupClass(), name, implMethodType);
break;
case Opcodes.H_INVOKESPECIAL:
// If there is an executor, the lambda function is actually modified from 'private instance' to 'public static' so adjust lookup. The method
// If there is an executor, the lambda function is actually modified from 'private instance' to 'public static' so adjust lookup. The method
// will be static with a new leading parameter.
if (executorClass == null) {
// TODO is final parameter here correct?
@@ -133,15 +136,56 @@ public class Java8 {
}
break;
case Opcodes.H_INVOKEVIRTUAL:
// If there is an executor, the lambda function is actually modified from 'private instance' to 'public static' so adjust lookup. The method
// will be static with a new leading parameter.
if (executorClass == null) {
// TODO when can this scenario occur? Aren't we only here if reloading has happened?
implMethod = caller.findVirtual(caller.lookupClass(), name, implMethodType);
// There is a possibility to 'shortcut' here. Basically we are trying to resolve a callsite reference
// to the method that satisfies it. The easiest option is to just find the method on the originally
// loaded version of the target class and return that. A more optimal shortcut could return the
// method on the executor class if the target has been reloaded (effectively bypassing the method
// on the originally loaded version since we know that it will be acting as a pass through). But this
// opens up a can of worms related to visibility. The executor is loaded into the child classloader,
// and if the caller has not been reloaded it will not be able to 'see' the executor (since it is in
// a child classloader). So, basically keep this dumb (but reliable) for now.
TypeRegistry typeRegistry = rtype.getTypeRegistry();
ReloadableType ownerRType = typeRegistry.getReloadableType(owner);
if (null == ownerRType || !ownerRType.hasBeenReloaded()) {
// target containing the reference/lambdaMethod has not been reloaded, no need to get over
// complicated.
Class<?> ownerClazz = ownerRType.getClazz();
implMethod = caller.findVirtual(ownerClazz, name, implMethodType);
}
else {
implMethod = caller.findStatic(caller.lookupClass(), name, MethodType.fromMethodDescriptorString(
"(L" + owner + ";" + descriptor.substring(1), callerLoader));
MethodMember targetReferenceMethodMember = ownerRType.getCurrentMethod(name, descriptor);
String targetReferenceDescriptor = targetReferenceMethodMember.getDescriptor();
MethodType targetReferenceMethodType = MethodType.fromMethodDescriptorString(
targetReferenceDescriptor, callerLoader);
Class<?> targetReferenceClass = ownerRType.getClazz();
MethodMember currentMethod = ownerRType.getCurrentMethod(name, descriptor);
if (currentMethod.original == null) {
// null means this method did not exist on the original version of the target.
// Assert that the caller must have been reloaded, otherwise how would it
// have a reference to something that did not exist on the first version of the type. In that
// case we know we can return the method on the executor class because both the reloaded
// caller and reloaded target are in the same child classloader (no visibility problem).
if (!rtype.hasBeenReloaded()) {
throw new IllegalStateException(
"Assertion violated: When a method added on reload is being referenced"
+ "in target type '" + ownerRType.getName()
+ "', expected the caller to also have been reloaded: '"
+ rtype.getName() + "'");
}
CurrentLiveVersion ownerLiveVersion = ownerRType.getLiveVersion();
Class<?> ownerExecutorClass = ownerLiveVersion.getExecutorClass();
Method executorMethod = ownerLiveVersion.getExecutorMethod(currentMethod);
String methodDescriptor = Type.getType(executorMethod).getDescriptor();
MethodType type = MethodType.fromMethodDescriptorString(methodDescriptor, callerLoader);
implMethod = caller.findStatic(ownerExecutorClass, name, type);
}
else {
// This finds the reference method on the originally loaded class. It will pass through
// to the actual code on the reloaded version.
implMethod = caller.findVirtual(targetReferenceClass, name, targetReferenceMethodType);
}
}
break;
case Opcodes.H_INVOKEINTERFACE:
@@ -150,7 +194,7 @@ public class Java8 {
// TODO Should there not be a more direct way to this than classloading?
// TODO What about when this is a method added to the interface on a reload? It won't really exist, should we point
// to the executor? or something else? (maybe just directly the real method that will satisfy the interface - if it can be worked out)
Class<?> interfaceClass = callerLoader.loadClass(interfaceOwner.replace('/', '.')); // interface type, eg StreamB$Foo
Class<?> interfaceClass = callerLoader.loadClass(interfaceOwner.replace('/', '.')); // interface type, eg StreamB$Foo
implMethod = caller.findVirtual(interfaceClass, name, implMethodType);
break;
default:
@@ -158,16 +202,17 @@ public class Java8 {
}
MethodType instantiatedMethodType = MethodType.fromMethodDescriptorString(
(String) (((Type) bsmArgs[2]).getDescriptor()), callerLoader);
(((Type) bsmArgs[2]).getDescriptor()), callerLoader);
return LambdaMetafactory.metafactory(caller, invokedName, invokedType, samMethodType, implMethod,
instantiatedMethodType);
}
/**
* The metafactory we are enhancing is responsible for generating the anonymous classes that will call the lambda
* methods in our type
*
*
* @param bytes the class bytes for the InnerClassLambdaMetaFactory that is going to be modified
* @return the class bytes for the modified InnerClassLambdaMetaFactory
*/

View File

@@ -27,7 +27,7 @@ import org.springsource.loaded.test.infra.Result;
/**
* Test reloading of Java 8.
*
*
* @author Andy Clement
* @since 1.2
*/
@@ -294,6 +294,116 @@ public class Java8Tests extends SpringLoadedTests {
assertEquals("fooab", r.returnValue);
}
// https://github.com/spring-projects/spring-loaded/issues/87
@Test
public void lambdaMethodReference() throws Exception {
String t = "basic.LambdaM";
TypeRegistry typeRegistry = getTypeRegistry("basic..*");
byte[] sc = loadBytesForClass(t);
ReloadableType rtype = typeRegistry.addType(t, sc);
Class<?> simpleClass = rtype.getClazz();
Result r = runUnguarded(simpleClass, "run");
r = runUnguarded(simpleClass, "run");
assertEquals("{5=test3}", r.returnValue);
rtype.loadNewVersion("2", retrieveRename(t, t + "2"));//, t + "2$Foo:" + t + "$Foo"));
r = runUnguarded(simpleClass, "run");
assertEquals("{10=test3}", r.returnValue);
}
// https://github.com/spring-projects/spring-loaded/issues/87
// This variant reloads both pieces
@Test
public void lambdaMethodReferenceInAnotherClass() throws Exception {
TypeRegistry typeRegistry = getTypeRegistry("basic..*");
byte[] sc = loadBytesForClass("basic.LambdaN");
ReloadableType rtype = typeRegistry.addType("basic.LambdaN", sc);
byte[] helperBytes = loadBytesForClass("basic.HelperN");
ReloadableType htype = typeRegistry.addType("basic.HelperN", helperBytes);
Class<?> simpleClass = rtype.getClazz();
Result r = runUnguarded(simpleClass, "run");
r = runUnguarded(simpleClass, "run");
assertEquals("{15=test3}", r.returnValue);
rtype.loadNewVersion(sc);
htype.loadNewVersion(helperBytes);
r = runUnguarded(simpleClass, "run");
assertEquals("{15=test3}", r.returnValue);
}
// This variant reloads only the caller
@Test
public void lambdaMethodReferenceInAnotherClass2() throws Exception {
TypeRegistry typeRegistry = getTypeRegistry("basic..*");
byte[] sc = loadBytesForClass("basic.LambdaN");
ReloadableType rtype = typeRegistry.addType("basic.LambdaN", sc);
byte[] helperBytes = loadBytesForClass("basic.HelperN");
typeRegistry.addType("basic.HelperN", helperBytes);
Class<?> simpleClass = rtype.getClazz();
Result r = runUnguarded(simpleClass, "run");
r = runUnguarded(simpleClass, "run");
assertEquals("{15=test3}", r.returnValue);
rtype.loadNewVersion(sc);
// htype.loadNewVersion(helperBytes); // don't reload the helper
r = runUnguarded(simpleClass, "run");
assertEquals("{15=test3}", r.returnValue);
}
// This variant reloads only the helper (target)
@Test
public void lambdaMethodReferenceInAnotherClass3() throws Exception {
TypeRegistry typeRegistry = getTypeRegistry("basic..*");
byte[] sc = loadBytesForClass("basic.LambdaN");
ReloadableType rtype = typeRegistry.addType("basic.LambdaN", sc);
byte[] helperBytes = loadBytesForClass("basic.HelperN");
ReloadableType htype = typeRegistry.addType("basic.HelperN", helperBytes);
Class<?> simpleClass = rtype.getClazz();
Result r = runUnguarded(simpleClass, "run");
r = runUnguarded(simpleClass, "run");
assertEquals("{15=test3}", r.returnValue);
// rtype.loadNewVersion(sc);
htype.loadNewVersion(helperBytes);
// try {
r = runUnguarded(simpleClass, "run");
assertEquals("{15=test3}", r.returnValue);
// fail("did not expect that to work");
// }
// catch (Exception e) {
// e.printStackTrace();
// // Caused by: java.lang.NoClassDefFoundError: basic/HelperN$$E2
// // at basic.LambdaN$$Lambda$8/2085857771.apply(Unknown Source)
// // at java.util.stream.Collectors.lambda$toMap$172(Collectors.java:1320)
// // at java.util.stream.Collectors$$Lambda$5/1521118594.accept(Unknown Source)
// // That happens because LambdaN, which has not been reloaded, is loaded by classloader X, the computed
// // method to satisfy the lambda is in the executor for the helper, which is in a child classloader - that
// // is not visible from the one that loaded LambdaN.
// // However, part of the resolution process in the Java8 handling forces LambdaN to reload, so next
// // time we go in, the class can be seen because LambdaN$$E2 is in the same classloader. That is
// // why when we repeat what we just did, it'll work
// }
// r = runUnguarded(simpleClass, "run");
// assertEquals("{15=test3}", r.returnValue);
}
@Test
public void streamWithLambda() throws Exception {
String t = "basic.StreamA";

View File

@@ -113,7 +113,8 @@ public class ReloadingJVM {
"/bin/java -noverify -javaagent:" + agentJarLocation + " -cp " + javaclasspath + " "
+ AGENT_OPTION_STRING +
" " + OPTS + " "
+ ReloadingJVMCommandProcess.class.getName(), new String[] { OPTS });
+ ReloadingJVMCommandProcess.class.getName(),
new String[] { OPTS });
writer = new DataOutputStream(process.getOutputStream());
reader = new DataInputStream(process.getInputStream());
readerErrors = new DataInputStream(process.getErrorStream());
@@ -172,9 +173,9 @@ public class ReloadingJVM {
@Override
public String toString() {
StringBuilder s = new StringBuilder("==STDOUT==\n").append(stdout).append("\n").append("==STDERR==\n").append(
stderr)
.append("\n==========\n");
StringBuilder s = new StringBuilder("==STDOUT==\n").append(stdout).append("\n").append(
"==STDERR==\n").append(
stderr).append("\n==========\n");
return s.toString();
}
}
@@ -261,22 +262,27 @@ public class ReloadingJVM {
return sendAndReceive("run " + classname);
}
public void copyToTestdataDirectory(String classname) {
if (DEBUG_CLIENT_SIDE) {
System.out.println("(client) copying class to test data directory: " + classname);
public void copyToTestdataDirectory(String... classnames) {
for (String classname : classnames) {
if (DEBUG_CLIENT_SIDE) {
System.out.println("(client) copying class to test data directory: " + classname);
}
String classfile = classname.replaceAll("\\.", File.separator) + ".class";
File f = new File("../testdata/bin", classfile);
if (!f.exists()) {
f = new File("../testdata-groovy/bin", classfile);
}
if (!f.exists()) {
f = new File("../testdata-java8/bin", classfile);
}
byte[] data = Utils.load(f);
// Ensure directories exist
int dotPos = classname.lastIndexOf(".");
if (dotPos != -1) {
new File(testdataDirectory, classname.substring(0, dotPos).replaceAll("\\.", File.separator)).mkdirs();
}
Utils.write(new File(testdataDirectory, classfile), data);
}
String classfile = classname.replaceAll("\\.", File.separator) + ".class";
File f = new File("../testdata/bin", classfile);
if (!f.exists()) {
f = new File("../testdata-groovy/bin", classfile);
}
byte[] data = Utils.load(f);
// Ensure directories exist
int dotPos = classname.lastIndexOf(".");
if (dotPos != -1) {
new File(testdataDirectory, classname.substring(0, dotPos).replaceAll("\\.", File.separator)).mkdirs();
}
Utils.write(new File(testdataDirectory, classfile), data);
}
/**

View File

@@ -76,7 +76,7 @@ import org.springsource.loaded.test.infra.TestClassLoader;
/**
* Abstract root test class containing helper functions.
*
*
* @author Andy Clement
* @since 1.0
*/
@@ -100,6 +100,9 @@ public abstract class SpringLoadedTests implements Constants {
// TODO [java8] replace this with project dependency when Java8 is out
protected String Java8CodeJar = findJar("../testdata-java8/build/libs", "testdata-java8");
protected String TestDataJava8Path = TestUtils.getPathToClasses("../testdata-java8/");
protected String GroovyrtJar = "../testdata-groovy/groovy-all-1.8.6.jar";
protected Result result;
@@ -111,7 +114,8 @@ public abstract class SpringLoadedTests implements Constants {
public void setup() throws Exception {
SpringLoadedPreProcessor.disabled = true;
NameRegistry.reset();
binLoader = new TestClassLoader(toURLs(TestDataPath, TestDataAspectJPath, AspectjrtJar, CodeJar, Java8CodeJar),
binLoader = new TestClassLoader(
toURLs(TestDataPath, TestDataAspectJPath, AspectjrtJar, CodeJar, Java8CodeJar, TestDataJava8Path),
this.getClass().getClassLoader());
}
@@ -127,7 +131,7 @@ public abstract class SpringLoadedTests implements Constants {
/**
* Convert an array of string paths to an array of URLs
*
*
* @param paths the string paths
* @return the converted URLs
*/
@@ -472,7 +476,7 @@ public abstract class SpringLoadedTests implements Constants {
/**
* retargets are "from.this.thing:to.this.thing"
*
*
* @param newName
* @param name
* @param retargets of the form "this.from:this.to"
@@ -672,8 +676,8 @@ public abstract class SpringLoadedTests implements Constants {
// if (attrs = !null) {
// sb.append("attrs(").append(toStringAttributes(attrs)).append(") ");
// }
sb.append("0x").append(Integer.toHexString(fieldNode.access)).append("(")
.append(ClassPrinter.toAccessForMember(fieldNode.access)).append(") ");
sb.append("0x").append(Integer.toHexString(fieldNode.access)).append("(").append(
ClassPrinter.toAccessForMember(fieldNode.access)).append(") ");
sb.append(fieldNode.name).append(" ");
sb.append(fieldNode.desc).append(" ");
if (fieldNode.signature != null) {
@@ -710,7 +714,7 @@ public abstract class SpringLoadedTests implements Constants {
/**
* From asm:
*
*
* The name value pairs of this annotation. Each name value pair is stored as two consecutive elements in the list.
* The name is a {@link String}, and the value may be a {@link Byte}, {@link Boolean}, {@link Character},
* {@link Short}, {@link Integer}, {@link Long}, {@link Float}, {@link Double}, {@link String} or
@@ -999,7 +1003,7 @@ public abstract class SpringLoadedTests implements Constants {
/**
* Create a type registry, configure it with the specified reloadable type/packages and return it.
*
*
* @return new TypeRegistry
*/
protected TypeRegistry getTypeRegistry(String includePatterns) {

View File

@@ -76,6 +76,110 @@ public class SpringLoadedTestsInSeparateJVM extends SpringLoadedTests {
assertStdout("Hello World!\n", output);
}
@Test
public void testReferenceInstanceMethodOfObject() throws Exception {
JVMOutput jo = null;
jvm.copyToTestdataDirectory("basic.LambdaL");
jvm.copyToTestdataDirectory("basic.LambdaL$Foo");
// jvm.newInstance("l", "basic.LambdaL", true);
jo = jvm.run("basic.LambdaL");
// Total output:
// original static initializer
// original instance
// in first foo
// fooa
assertStdoutContains("in first foo", jo);
assertStdoutContains("fooa", jo);
jvm.updateClass("basic.LambdaL", loadBytesForClass("basic.LambdaL"));
pause(2);
// Run the same thing as before:
jo = jvm.run("basic.LambdaL");
assertStdoutContains("in first foo", jo);
assertStdoutContains("fooa", jo);
// New version: Foo interface has one method argument
jvm.updateClass("basic.LambdaL$Foo",
retrieveRename("basic.LambdaL$Foo", "basic.LambdaL2$Foo"));
waitForReloadToOccur();
jvm.updateClass("basic.LambdaL",
retrieveRename("basic.LambdaL", "basic.LambdaL2", "basic.LambdaL2$Foo:basic.LambdaL$Foo"));
waitForReloadToOccur();
// Run the new version
jo = jvm.run("basic.LambdaL");
assertStdoutContains("in second foo", jo);
assertStdoutContains("fooab", jo);
}
@Test
public void testStaticMethodReference() throws Exception {
JVMOutput jo = null;
jvm.copyToTestdataDirectory("basic.StaticMethodReference", "basic.StaticMethodReference$Foo",
"basic.StaticMethodReference$Bar");
jo = jvm.run("basic.StaticMethodReference");
assertStdoutContains("in 1st static Method", jo);
assertStdoutContains("staticsa", jo);
// Reload itself
jvm.updateClass("basic.StaticMethodReference", loadBytesForClass("basic.StaticMethodReference"));
waitForReloadToOccur();
jo = jvm.run("basic.StaticMethodReference");
assertStdoutContains("in 1st static Method", jo);
assertStdoutContains("staticsa", jo);
jvm.updateClass("basic.StaticMethodReference$Foo",
retrieveRename("basic.StaticMethodReference$Foo", "basic.StaticMethodReference2$Foo"));
jvm.updateClass("basic.StaticMethodReference$Bar",
retrieveRename("basic.StaticMethodReference$Bar", "basic.StaticMethodReference2$Bar"));
jvm.updateClass("basic.StaticMethodReference",
retrieveRename("basic.StaticMethodReference", "basic.StaticMethodReference2",
"basic.StaticMethodReference2$Foo:basic.StaticMethodReference$Foo",
"basic.StaticMethodReference2$Bar:basic.StaticMethodReference$Bar"));
waitForReloadToOccur();
jo = jvm.run("basic.StaticMethodReference");
assertStdoutContains("in 2nd static Method", jo);
assertStdoutContains("staticsasb", jo);
// // New version: Foo interface has one method argument
// jvm.updateClass("basic.LambdaL$Foo",
// retrieveRename("basic.LambdaL$Foo", "basic.LambdaL2$Foo"));
// waitForReloadToOccur();
//
// jvm.updateClass("basic.LambdaL",
// retrieveRename("basic.LambdaL", "basic.LambdaL2", "basic.LambdaL2$Foo:basic.LambdaL$Foo"));
// waitForReloadToOccur();
//
// // Run the new version
// jo = jvm.run("basic.LambdaL");
// assertStdoutContains("in second foo", jo);
// assertStdoutContains("fooab", jo);
// compile("/original/", "/original/basic/StaticMethodReference.java.file");
//+ JVMOutput output = jvm.sendAndReceive("run basic.StaticMethodReference");
//+
//+ assertStdoutContains("in 1st static Method", output);
//+ assertStdoutContains("staticsa", output);
//+
//+ compile("/modified/", "/modified/basic/StaticMethodReference.java.file");
//+ jvm.reload("basic.StaticMethodReference");
//+ waitForReloadToOccur();
//+
//+ output = jvm.sendAndReceive("run basic.StaticMethodReference");
//+ assertStdoutContains("in 2nd static Method", output);
//+ assertStdoutContains("staticsasb", output);
//+ }
//
}
@Test
public void serialization() throws Exception {
jvm.copyToTestdataDirectory("remote.Serialize");
@@ -146,7 +250,8 @@ public class SpringLoadedTestsInSeparateJVM extends SpringLoadedTests {
@Test
public void testCreatingAndInvokingMethodsOnInstance() throws Exception {
assertStderrContains("creating new instance 'a' of type 'jvmtwo.Runner'", jvm.newInstance("a", "jvmtwo.Runner"));
assertStderrContains("creating new instance 'a' of type 'jvmtwo.Runner'",
jvm.newInstance("a", "jvmtwo.Runner"));
assertStdout("jvmtwo.Runner.run1() running", jvm.call("a", "run1"));
}

View File

@@ -0,0 +1,34 @@
package basic;
public class LambdaL {
static {
System.out.println("original static initializer");
}
public LambdaL() {
System.out.println("original instance");
}
public interface Foo { String m(String s); }
public String getFoo(String s) {
System.out.println("in first foo");
return "foo"+s;
}
public static void main(String[] args) {
run();
}
public static String run() {
String res= new LambdaL().run2();
System.out.println(res);
return res;
}
public String run2() {
Foo f = this::getFoo;
return f.m("a");
}
}

View File

@@ -0,0 +1,27 @@
package basic;
public class LambdaL2 {
public interface Foo { String m(String s, String t); }
public String getFoo(String s, String t) {
System.out.println("in second foo");
return "foo"+s+t;
}
public static void main(String[] args) {
run();
}
public static String run() {
String res= new LambdaL2().run2();
System.out.println(res);
return res;
}
public String run2() {
Foo f = this::getFoo;
return f.m("a","b");
}
}

View File

@@ -0,0 +1,32 @@
package basic;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
public class LambdaM {
// public interface Foo { String m(String s); }
// public String getFoo(String s) {
// return "foo"+s;
// }
public static void main(String[] args) {
run();
}
public static String run() {
return new LambdaM().run2();
}
public String run2() {
List<String> list = new ArrayList<>();
list.add("test3");
Map m = list.stream().collect(Collectors.toMap(String::length, Function.identity()));
return m.toString();
}
}

View File

@@ -0,0 +1,30 @@
package basic;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
public class LambdaM2 {
public static void main(String[] args) {
run();
}
public static String run() {
return new LambdaM2().run2();
}
public String run2() {
List<String> list = new ArrayList<>();
list.add("test3");
Map m = list.stream().collect(Collectors.toMap(this::foo, Function.identity()));
return m.toString();
}
public int foo(String input) {
return input.length()*2;
}
}

View File

@@ -0,0 +1,35 @@
package basic;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
public class LambdaN {
public static void main(String[] args) {
run();
}
public static String run() {
return new LambdaN().run2();
}
public String run2() {
List<String> list = new ArrayList<>();
HelperN h = new HelperN();
list.add("test3");
Map m = list.stream().collect(Collectors.toMap(h::foo, Function.identity()));
return m.toString();
}
}
class HelperN {
public int foo(String input) {
return input.length()*3;
}
}

View File

@@ -0,0 +1,39 @@
package basic;
public class StaticMethodReference {
public static String staticMethod(String s1) {
System.out.println("in 1st static Method");
return "static" + s1;
}
public interface Bar { String sm(String s);}
public interface Foo { String m(String s); }
public String getFoo(String s) {
return "foo"+s;
}
public static void main(String[] args) {
run();
}
public static String run() {
StaticMethodReference l = new StaticMethodReference();
String r = l.run3();
System.out.println(r);
return l.run2();
}
public String run2() {
Foo f = this::getFoo;
return f.m("a");
}
public String run3() {
Bar b = StaticMethodReference::staticMethod;
return b.sm("sa");
}
}

View File

@@ -0,0 +1,39 @@
package basic;
public class StaticMethodReference2 {
public static String staticMethod(String s1, String s2) {
System.out.println("in 2nd static Method");
return "static" + s1 + s2;
}
public interface Bar { String sm(String s, String s1);}
public interface Foo { String m(String s); }
public String getFoo(String s) {
return "foo"+s;
}
public static void main(String[] args) {
run();
}
public static String run() {
StaticMethodReference2 l = new StaticMethodReference2();
String r = l.run3();
System.out.println(r);
return l.run2();
}
public String run2() {
Foo f = this::getFoo;
return f.m("a");
}
public String run3() {
Bar b = StaticMethodReference2::staticMethod;
return b.sm("sa", "sb");
}
}