/*
* 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;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.lang.reflect.InvocationTargetException;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.springsource.loaded.ISMgr;
import org.springsource.loaded.MethodInvokerRewriter;
import org.springsource.loaded.ReloadableType;
import org.springsource.loaded.TypeRegistry;
import org.springsource.loaded.test.infra.ClassPrinter;
import org.springsource.loaded.test.infra.Result;
import org.springsource.loaded.test.infra.ResultException;
/**
* Tests that change a type by modifying/adding/removing fields.
*
* @author Andy Clement
* @since 1.0
*/
public class FieldReloadingTests extends SpringLoadedTests {
@Before
public void setup() throws Exception {
super.setup();
}
// Field 'i' is added to Add on a reload and interacted with
@Test
public void newFieldAdded() throws Exception {
TypeRegistry r = getTypeRegistry("fields.Add");
ReloadableType add = loadType(r, "fields.Add");
Class<?> addClazz = add.getClazz();
Object addInstance = addClazz.newInstance();
assertEquals(0, runOnInstance(addClazz, addInstance, "getValue").returnValue);
// ClassPrinter.print(add.bytesLoaded, true);
add.loadNewVersion("2", retrieveRename("fields.Add", "fields.Add002"));
assertEquals(0, runOnInstance(addClazz, addInstance, "getValue").returnValue);
runOnInstance(addClazz, addInstance, "setValue", 45);
assertEquals(45, runOnInstance(addClazz, addInstance, "getValue").returnValue);
assertEquals(45, add.getField(addInstance, "i", false));
}
// Variant of the first test but uses a new instance after reloading
@Test
public void newFieldAddedInstance() throws Exception {
TypeRegistry r = getTypeRegistry("fields.Add");
ReloadableType add = loadType(r, "fields.Add");
Class<?> addClazz = add.getClazz();
Object addInstance = addClazz.newInstance();
assertEquals(0, runOnInstance(addClazz, addInstance, "getValue").returnValue);
ClassPrinter.print(add.bytesLoaded, true);
add.loadNewVersion("2", retrieveRename("fields.Add", "fields.Add002"));
addInstance = addClazz.newInstance();
assertEquals(0, runOnInstance(addClazz, addInstance, "getValue").returnValue);
runOnInstance(addClazz, addInstance, "setValue", 45);
assertEquals(45, runOnInstance(addClazz, addInstance, "getValue").returnValue);
assertEquals(45, add.getField(addInstance, "i", false));
}
/**
* Do an early set (via FieldReaderWriter) after a reload, so that no prior setup will have been done on the values map for that
* type. This checks the logic in FieldReaderWriter.setValue()
*/
@Test
public void earlySet() throws Exception {
String s = "fields.S";
TypeRegistry r = getTypeRegistry(s);
ReloadableType add = loadType(r, s);
Class<?> addClazz = add.getClazz();
Object addInstance = addClazz.newInstance();
// assertEquals(0, runOnInstance(addClazz, addInstance, "getValue").returnValue);
add.loadNewVersion("2", retrieveRename(s, s + "2"));
add.setField(addInstance, "i", false, 1234);
assertEquals(1234, runOnInstance(addClazz, addInstance, "getValue").returnValue);
// runOnInstance(addClazz, addInstance, "setValue", 45);
//
// assertEquals(45, runOnInstance(addClazz, addInstance, "getValue").returnValue);
// assertEquals(45, add.getField(addInstance, "i", false));
}
/**
* Simple test working with double slotters - double and long
*/
@Test
public void doubleSlotFields() throws Exception {
String d = "fields.TwoSlot";
TypeRegistry r = getTypeRegistry(d);
ReloadableType add = loadType(r, d);
Class<?> clazz = add.getClazz();
Object instance = clazz.newInstance();
assertEquals(34.5, runOnInstance(clazz, instance, "getDouble").returnValue);
assertEquals(123L, runOnInstance(clazz, instance, "getLong").returnValue);
runOnInstance(clazz, instance, "setDouble", 1323d);
runOnInstance(clazz, instance, "setLong", 23l);
add.loadNewVersion("2", retrieveRename(d, d + "2"));
assertEquals(0d, runOnInstance(clazz, instance, "getDouble").returnValue);
assertEquals(0L, runOnInstance(clazz, instance, "getLong").returnValue);
runOnInstance(clazz, instance, "setDouble", 1323d);
runOnInstance(clazz, instance, "setLong", 23l);
assertEquals(1323d, runOnInstance(clazz, instance, "getDouble").returnValue);
assertEquals(23L, runOnInstance(clazz, instance, "getLong").returnValue);
}
/**
* In this test there are some fields in a reloadable type shadowing some in a non-reloadable supertype. When the reloadable
* ones are removed the supertype ones become visible, check they are correctly accessible.
*/
@Test
public void setWithFieldsRevealedOnReloadHierarchyNonStatic() throws Exception {
// String a = "fields.A"; // not reloadable!
String b = "fields.B";
String c = "fields.C";
TypeRegistry r = getTypeRegistry(b + "," + c);
ReloadableType btype = loadType(r, b);
ReloadableType ctype = loadType(r, c);
Class<?> cclazz = ctype.getClazz();
Object cinstance = cclazz.newInstance();
assertEquals(2, runOnInstance(cclazz, cinstance, "getInt").returnValue);
assertEquals("fromB", runOnInstance(cclazz, cinstance, "getString").returnValue);
runOnInstance(cclazz, cinstance, "setInt", 22);
runOnInstance(cclazz, cinstance, "setString", "fromBfromB");
assertEquals(22, runOnInstance(cclazz, cinstance, "getInt").returnValue);
assertEquals("fromBfromB", runOnInstance(cclazz, cinstance, "getString").returnValue);
ctype.setField(cinstance, "i", false, 345);
assertEquals(345, runOnInstance(cclazz, cinstance, "getInt").returnValue);
assertEquals(345, ctype.getField(cinstance, "i", false));
btype.loadNewVersion("2", retrieveRename(b, b + "2"));
assertEquals(1, runOnInstance(cclazz, cinstance, "getInt").returnValue);
assertEquals("fromA", runOnInstance(cclazz, cinstance, "getString").returnValue);
runOnInstance(cclazz, cinstance, "setInt", 22);
runOnInstance(cclazz, cinstance, "setString", "fromBfromB");
assertEquals(22, runOnInstance(cclazz, cinstance, "getInt").returnValue);
assertEquals("fromBfromB", runOnInstance(cclazz, cinstance, "getString").returnValue);
}
/**
* In this test there are some fields in a reloadable type shadowing some in a non-reloadable supertype. When the reloadable
* ones are removed the supertype ones become visible, check they are correctly accessible.
*/
@Test
public void setWithFieldsRevealedOnReloadHierarchyStatic() throws Exception {
// String a = "fields.A"; // not reloadable!
String b = "fields.Ba";
String c = "fields.Ca";
TypeRegistry r = getTypeRegistry(b + "," + c);
ReloadableType btype = loadType(r, b);
ReloadableType ctype = loadType(r, c);
Class<?> cclazz = ctype.getClazz();
Object cinstance = cclazz.newInstance();
assertEquals(2, runOnInstance(cclazz, cinstance, "getInt").returnValue);
assertEquals("fromB", runOnInstance(cclazz, cinstance, "getString").returnValue);
runOnInstance(cclazz, cinstance, "setInt", 22);
runOnInstance(cclazz, cinstance, "setString", "fromBfromB");
assertEquals(22, runOnInstance(cclazz, cinstance, "getInt").returnValue);
assertEquals("fromBfromB", runOnInstance(cclazz, cinstance, "getString").returnValue);
ctype.setField(null, "i", true, 11111);
assertEquals(11111, ctype.getField(null, "i", true));
assertEquals(11111, runOnInstance(cclazz, cinstance, "getInt").returnValue);
btype.loadNewVersion("2", retrieveRename(b, b + "2"));
assertEquals(1, runOnInstance(cclazz, cinstance, "getInt").returnValue);
assertEquals("fromA", runOnInstance(cclazz, cinstance, "getString").returnValue);
runOnInstance(cclazz, cinstance, "setInt", 22);
runOnInstance(cclazz, cinstance, "setString", "fromBfromB");
assertEquals(22, runOnInstance(cclazz, cinstance, "getInt").returnValue);
assertEquals("fromBfromB", runOnInstance(cclazz, cinstance, "getString").returnValue);
ctype.setField(null, "i", true, 12221);
assertEquals(12221, ctype.getField(null, "i", true));
assertEquals(12221, runOnInstance(cclazz, cinstance, "getInt").returnValue);
}
// All the other primitive field types
@Test
public void newFieldAdded2() throws Exception {
TypeRegistry r = getTypeRegistry("fields.AddB");
ReloadableType add = loadType(r, "fields.AddB");
Class<?> addClazz = add.getClazz();
Object addInstance = addClazz.newInstance();
assertEquals((short) 0, runOnInstance(addClazz, addInstance, "getShort").returnValue);
assertEquals(0L, runOnInstance(addClazz, addInstance, "getLong").returnValue);
assertEquals(false, runOnInstance(addClazz, addInstance, "getBoolean").returnValue);
assertEquals('a', runOnInstance(addClazz, addInstance, "getChar").returnValue);
assertEquals(0d, runOnInstance(addClazz, addInstance, "getDouble").returnValue);
assertEquals(0f, runOnInstance(addClazz, addInstance, "getFloat").returnValue);
add.loadNewVersion("2", retrieveRename("fields.AddB", "fields.AddB002"));
assertEquals((short) 0, runOnInstance(addClazz, addInstance, "getShort").returnValue);
assertEquals(0L, runOnInstance(addClazz, addInstance, "getLong").returnValue);
assertEquals(false, runOnInstance(addClazz, addInstance, "getBoolean").returnValue);
assertEquals((char) 0, runOnInstance(addClazz, addInstance, "getChar").returnValue);
assertEquals(0d, runOnInstance(addClazz, addInstance, "getDouble").returnValue);
assertEquals(0f, runOnInstance(addClazz, addInstance, "getFloat").returnValue);
runOnInstance(addClazz, addInstance, "setShort", (short) 451);
runOnInstance(addClazz, addInstance, "setLong", 328L);
runOnInstance(addClazz, addInstance, "setBoolean", true);
runOnInstance(addClazz, addInstance, "setChar", 'z');
runOnInstance(addClazz, addInstance, "setDouble", 2222d);
runOnInstance(addClazz, addInstance, "setFloat", 12f);
assertEquals((short) 451, runOnInstance(addClazz, addInstance, "getShort").returnValue);
assertEquals(328L, runOnInstance(addClazz, addInstance, "getLong").returnValue);
assertEquals(true, runOnInstance(addClazz, addInstance, "getBoolean").returnValue);
assertEquals('z', runOnInstance(addClazz, addInstance, "getChar").returnValue);
assertEquals(2222d, runOnInstance(addClazz, addInstance, "getDouble").returnValue);
assertEquals(12f, runOnInstance(addClazz, addInstance, "getFloat").returnValue);
}
// public void fieldRemoved() throws Exception {
// TypeRegistry r = getTypeRegistry("fields.Removed");
// ReloadableType add = loadType(r, "fields.Removed");
// Class<?> addClazz = add.getClazz();
// Object addInstance = addClazz.newInstance();
// assertEquals(0, runOnInstance(addClazz, addInstance, "getValue").returnValue);
// runOnInstance(addClazz, addInstance, "setValue", 45);
// assertEquals(45, runOnInstance(addClazz, addInstance, "getValue").returnValue);
//
// add.loadNewVersion("2", retrieveRename("fields.Removed", "fields.Removed002"));
//
// assertEquals(0, runOnInstance(addClazz, addInstance, "getValue").returnValue);
//
// runOnInstance(addClazz, addInstance, "setValue", 45);
//
// assertEquals(45, runOnInstance(addClazz, addInstance, "getValue").returnValue);
// }
// @Test
// public void fieldOverloading() throws Exception {
// TypeRegistry r = getTypeRegistry("fields..*");
//
// ReloadableType one = loadType(r, "fields.One");
// ReloadableType two = loadType(r, "fields.Two");
//
// Class<?> oneClazz = one.getClazz();
// Object oneInstance = oneClazz.newInstance();
// Class<?> twoClazz = two.getClazz();
// Object twoInstance = twoClazz.newInstance();
//
// // Field 'a' is only defined in One and 'inherited' by Two
// assertEquals("a from One", runOnInstance(oneClazz, oneInstance, "getOneA").returnValue);
// assertEquals("a from One", runOnInstance(twoClazz, twoInstance, "getTwoA").returnValue);
//
// runOnInstance(oneClazz, oneInstance, "setOneA", "abcde");
// assertEquals("abcde", runOnInstance(oneClazz, oneInstance, "getOneA").returnValue);
//
// runOnInstance(twoClazz, twoInstance, "setOneA", "abcde");
// assertEquals("abcde", runOnInstance(twoClazz, twoInstance, "getTwoA").returnValue);
//
// // Field 'b' is defined in One and Two
// assertEquals("b from One", runOnInstance(oneClazz, oneInstance, "getOneB").returnValue);
// assertEquals("b from Two", runOnInstance(twoClazz, twoInstance, "getTwoB").returnValue);
//
// // Field 'c' is private in One and public in Two
// assertEquals("c from One", runOnInstance(oneClazz, oneInstance, "getOneC").returnValue);
// assertEquals("c from Two", runOnInstance(twoClazz, twoInstance, "getTwoC").returnValue);
//
// // Now... set the private field 'c' in One then try to access the field c in both One and Two
// // Should be different if the FieldAccessor is preserving things correctly
// runOnInstance(twoClazz, twoInstance, "setOneC", "abcde");
// assertEquals("abcde", runOnInstance(twoClazz, twoInstance, "getOneC").returnValue);
// assertEquals("c from Two", runOnInstance(twoClazz, twoInstance, "getTwoC").returnValue);
// }
//
// @Test
// public void invokevirtual() throws Exception {
// TypeRegistry typeRegistry = getTypeRegistry("virtual.CalleeOne");
//
// // The first target does not define toString()
// ReloadableType target = typeRegistry.addType("virtual.CalleeOne", loadBytesForClass("virtual.CalleeOne"));
//
// Class<?> callerClazz = loadit("virtual.CallerOne", loadBytesForClass("virtual.CallerOne"));
//
// // Run the initial version which does not define toString()
// Result result = runUnguarded(callerClazz, "run");
// // something like virtual.CalleeOne@4cee32
// assertTrue(((String) result.returnValue).startsWith("virtual.CalleeOne@"));
//
// // Load a version that does define toString()
// target.loadNewVersion("002", retrieveRename("virtual.CalleeOne", "virtual.CalleeOne002"));
// result = runUnguarded(callerClazz, "run");
// assertEquals("abcd", result.returnValue);
//
// // Load a version that does not define toString()
// target.loadNewVersion("003", retrieveRename("virtual.CalleeOne", "virtual.CalleeOne003"));
// result = runUnguarded(callerClazz, "run");
// // something like virtual.CalleeOne@4cee32
// assertTrue(((String) result.returnValue).startsWith("virtual.CalleeOne@"));
// }
// TODO [perf][crit] reduce boxing by having variants of set/get for all primitives and calling them
// instead of the generic Object one checking fields ok after rewriting
@Test
public void intArrayFieldAccessing() throws Exception {
TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader);
// Configure it directly such that data.Pear is considered reloadable
configureForTesting(typeRegistry, "data.Pear");
// ReloadableType pear =
typeRegistry.addType("data.Pear", loadBytesForClass("data.Pear"));
byte[] callerbytes = loadBytesForClass("data.Banana");
byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, callerbytes);
Class<?> callerClazz = loadit("data.Banana", rewrittenBytes);
Object o = callerClazz.newInstance();
Result result = runOnInstance(callerClazz, o, "getIntArrayField");
System.out.println(result);
Assert.assertTrue(result.returnValue instanceof int[]);
Assert.assertEquals(3, ((int[]) result.returnValue)[1]);
result = runOnInstance(callerClazz, o, "setIntArrayField", new Object[] { new int[] { 44, 55, 66 } });
result = runOnInstance(callerClazz, o, "getIntArrayField");
Assert.assertTrue(result.returnValue instanceof int[]);
Assert.assertEquals(55, ((int[]) result.returnValue)[1]);
}
@Test
public void staticIntArrayFieldAccessing() throws Exception {
TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader);
// Configure it directly such that data.Pear is considered reloadable
configureForTesting(typeRegistry, "data.Pear");
typeRegistry.addType("data.Pear", loadBytesForClass("data.Pear"));
byte[] callerbytes = loadBytesForClass("data.Banana");
byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, callerbytes);
Class<?> callerClazz = loadit("data.Banana", rewrittenBytes);
Object o = callerClazz.newInstance();
Result result = runOnInstance(callerClazz, o, "getStaticIntArrayField");
Assert.assertTrue(result.returnValue instanceof int[]);
Assert.assertEquals(33, ((int[]) result.returnValue)[1]);
result = runOnInstance(callerClazz, o, "setStaticIntArrayField", new Object[] { new int[] { 44, 55, 66 } });
result = runOnInstance(callerClazz, o, "getStaticIntArrayField");
Assert.assertTrue(result.returnValue instanceof int[]);
Assert.assertEquals(55, ((int[]) result.returnValue)[1]);
}
// The problem with default visibility fields is that the executors cannot see them. So we create accessors for
// these fields in the target types. If two types in a hierarchy declare the same field, then these accessor methods will be in an
// override relationship. The overriding accessors will be subject to virtual dispatch - but the runtime type should be
// correct so that the right one is called.
@Test
public void defaultFields() throws Exception {
TypeRegistry tr = getTypeRegistry("accessors..*");
ReloadableType top = tr.addType("accessors.DefaultFields", loadBytesForClass("accessors.DefaultFields"));
ReloadableType bottom = tr.addType("accessors.DefaultFieldsSub", loadBytesForClass("accessors.DefaultFieldsSub"));
ClassPrinter.print(top.bytesLoaded);
Object topInstance = top.getClazz().newInstance();
Result result = runOnInstance(top.getClazz(), topInstance, "a");
assertEquals(1, result.returnValue);
Object botInstance = bottom.getClazz().newInstance();
result = runOnInstance(bottom.getClazz(), botInstance, "a");
assertEquals(1, result.returnValue);
result = runOnInstance(bottom.getClazz(), botInstance, "b");
assertEquals(2, result.returnValue);
}
// Tests allowing for inherited fields from a non-reloadable type. Reload a new version of the class
// that shadows the method in the non-reloadable type.
@Test
public void inheritedNonReloadable1() throws Exception {
String top = "fields.ReloadableTop";
TypeRegistry tr = getTypeRegistry(top);
ReloadableType rTop = tr.addType(top, loadBytesForClass(top));
Class<?> cTop = rTop.getClazz();
Object iTop = cTop.newInstance();
// The field 'I' is in code that is not reloadable.
// First lets use the accessors that are also not in reloadable code
assertEquals(5, runOnInstance(cTop, iTop, "getI_NotReloadable").returnValue);
runOnInstance(cTop, iTop, "setI_ReloadableTop", 4);
assertEquals(4, runOnInstance(cTop, iTop, "getI_NotReloadable").returnValue);
// Now use accessors that are in reloadable code
assertEquals(4, runOnInstance(cTop, iTop, "getI_ReloadableTop").returnValue);
runOnInstance(cTop, iTop, "setI_ReloadableTop", 3);
assertEquals(3, runOnInstance(cTop, iTop, "getI_ReloadableTop").returnValue);
// Now reload the ReloadableTop class and introduce that field i which will shadow the one in the supertype
rTop.loadNewVersion("2", retrieveRename(top, top + "002"));
assertEquals(0, runOnInstance(cTop, iTop, "getI_ReloadableTop").returnValue);
runOnInstance(cTop, iTop, "setI_ReloadableTop", 44);
assertEquals(44, runOnInstance(cTop, iTop, "getI_ReloadableTop").returnValue);
// Now access the older version of the field still there on the non-reloadable type
assertEquals(3, runOnInstance(cTop, iTop, "getI_NotReloadable").returnValue);
}
// Now a pair of types with one field initially. New field added - original should continue to be accessed
// through the GETFIELD, the new one via the map
@Test
public void inheritedNonReloadable2() throws Exception {
String top = "fields.X";
TypeRegistry tr = getTypeRegistry(top);
ReloadableType rTop = tr.addType(top, loadBytesForClass(top));
Class<?> cTop = rTop.getClazz();
Object iTop = cTop.newInstance();
assertEquals(5, runOnInstance(cTop, iTop, "getI").returnValue);
runOnInstance(cTop, iTop, "setI", 4);
assertEquals(4, runOnInstance(cTop, iTop, "getI").returnValue);
// // Now reload the ReloadableTop class and introduce that field i which will shadow the one in the supertype
rTop.loadNewVersion("2", retrieveRename(top, top + "002"));
assertEquals(0, runOnInstance(cTop, iTop, "getJ").returnValue);
runOnInstance(cTop, iTop, "setJ", 44);
assertEquals(44, runOnInstance(cTop, iTop, "getJ").returnValue);
assertEquals(4, runOnInstance(cTop, iTop, "getI").returnValue);
ISMgr fa = getFieldAccessor(iTop);
assertContains("j=44", fa.toString());
assertDoesNotContain("i=", fa.toString());
}
@Test
public void changingFieldFromNonstaticToStatic() throws Exception {
String y = "fields.Y";
TypeRegistry tr = getTypeRegistry(y);
ReloadableType rtype = tr.addType(y, loadBytesForClass(y));
Class<?> clazz = rtype.getClazz();
Object instance = clazz.newInstance();
Object instance2 = clazz.newInstance();
assertEquals(5, runOnInstance(clazz, instance, "getJ").returnValue);
runOnInstance(clazz, instance, "setJ", 4);
assertEquals(4, runOnInstance(clazz, instance, "getJ").returnValue);
rtype.loadNewVersion("2", retrieveRename(y, y + "002"));
runOnInstance(clazz, instance, "setJ", 4);
assertEquals(4, runOnInstance(clazz, instance, "getJ").returnValue);
String staticFieldMapData = getStaticFieldsMap(clazz);
assertContains("j=4", staticFieldMapData);
runOnInstance(clazz, instance2, "setJ", 404);
assertEquals(404, runOnInstance(clazz, instance, "getJ").returnValue);
assertEquals(404, runOnInstance(clazz, instance2, "getJ").returnValue);
}
/**
* An instance field is accessed from a subtype, then the supertype is reloaded where the field has been made static. Should be
* an error when the subtype attempts to access it again.
*/
@Test
public void changingFieldFromNonstaticToStaticWithSubtypes() throws Exception {
String y = "fields.Ya";
String z = "fields.Za";
TypeRegistry tr = getTypeRegistry(y + "," + z);
ReloadableType rtype = tr.addType(y, loadBytesForClass(y));
ReloadableType ztype = tr.addType(z, loadBytesForClass(z));
Class<?> clazz = ztype.getClazz();
Object instance = clazz.newInstance();
assertEquals(5, runOnInstance(clazz, instance, "getJ").returnValue);
runOnInstance(clazz, instance, "setJ", 4);
assertEquals(4, runOnInstance(clazz, instance, "getJ").returnValue);
rtype.loadNewVersion("2", retrieveRename(y, y + "002"));
try {
runOnInstance(clazz, instance, "setJ", 4);
fail("should not have worked, field has changed from non-static to static");
} catch (ResultException re) {
assertTrue(re.getCause() instanceof InvocationTargetException);
assertTrue(re.getCause().getCause() instanceof IncompatibleClassChangeError);
assertEquals("Expected non-static field fields.Za.j", re.getCause().getCause().getMessage());
}
// Now should be an IncompatibleClassChangeError
try {
runOnInstance(clazz, instance, "getJ");
fail("should not have worked, field has changed from non-static to static");
} catch (ResultException re) {
assertTrue(re.getCause() instanceof InvocationTargetException);
assertTrue(re.getCause().getCause() instanceof IncompatibleClassChangeError);
assertEquals("Expected non-static field fields.Za.j", re.getCause().getCause().getMessage());
}
}
/**
* A static field is accessed from a subtype, then the supertype is reloaded where the field has been made non-static. Should be
* an error when the subtype attempts to access it again.
*/
@Test
public void changingFieldFromStaticToNonstaticWithSubtypes() throws Exception {
String y = "fields.Yb";
String z = "fields.Zb";
TypeRegistry tr = getTypeRegistry(y + "," + z);
ReloadableType rtype = tr.addType(y, loadBytesForClass(y));
ReloadableType ztype = tr.addType(z, loadBytesForClass(z));
Class<?> clazz = ztype.getClazz();
Object instance = clazz.newInstance();
assertEquals(5, runOnInstance(clazz, instance, "getJ").returnValue);
runOnInstance(clazz, instance, "setJ", 4);
assertEquals(4, runOnInstance(clazz, instance, "getJ").returnValue);
rtype.loadNewVersion("2", retrieveRename(y, y + "002"));
// Now should be an IncompatibleClassChangeError
try {
runOnInstance(clazz, instance, "setJ", 4);
fail("Should not have worked, field has changed from static to non-static");
} catch (ResultException re) {
assertTrue(re.getCause() instanceof InvocationTargetException);
assertTrue(re.getCause().getCause() instanceof IncompatibleClassChangeError);
// When compiled with AspectJ vs Eclipse JDT the GETSTATIC actually varies.
// With AspectJ it is: PUTSTATIC fields/Yb.j : I
// With JDT (4.3) it is: PUTSTATIC fields/Zb.j : I
// hence the error is different
assertEquals("Expected static field fields.Zb.j", re.getCause().getCause().getMessage());
}
// Now should be an IncompatibleClassChangeError
try {
runOnInstance(clazz, instance, "getJ");
fail("Should not have worked, field has changed from static to non-static");
} catch (ResultException re) {
assertTrue(re.getCause() instanceof InvocationTargetException);
assertTrue(re.getCause().getCause() instanceof IncompatibleClassChangeError);
// When compiled with AspectJ vs Eclipse JDT the GETSTATIC actually varies.
// With AspectJ it is: GETSTATIC fields/Yb.j : I
// With JDT (4.3) it is: GETSTATIC fields/Zb.j : I
// hence the error is different
assertEquals("Expected static field fields.Zb.j", re.getCause().getCause().getMessage());
}
}
/**
* Load a hierarchy of types. There is a field 'i' in both P and Q. R returns 'i'. Initially it returns Q.i but once Q has been
* reloaded it should be returning P.i
*/
@Test
public void removingFieldsShadowingSuperFields() throws Exception {
String p = "fields.P";
String q = "fields.Q";
String r = "fields.R";
TypeRegistry tr = getTypeRegistry(p + "," + q + "," + r);
// ReloadableType ptype =
tr.addType(p, loadBytesForClass(p));
ReloadableType qtype = tr.addType(q, loadBytesForClass(q));
ReloadableType rtype = tr.addType(r, loadBytesForClass(r));
Class<?> rClazz = rtype.getClazz();
Object rInstance = rClazz.newInstance();
assertEquals(2, runOnInstance(rClazz, rInstance, "getI").returnValue);
qtype.loadNewVersion("2", retrieveRename(q, q + "2"));
assertEquals(1, runOnInstance(rClazz, rInstance, "getI").returnValue);
}
/**
* Similar to previous but now fields.Pa is not reloadable.
*/
@Test
public void removingFieldsShadowingSuperFields2() throws Exception {
// String p = "fields.Pa";
String q = "fields.Qa";
String r = "fields.Ra";
TypeRegistry tr = getTypeRegistry(q + "," + r);
// ReloadableType ptype = tr.addType(p, loadBytesForClass(p));
ReloadableType qtype = tr.addType(q, loadBytesForClass(q));
ReloadableType rtype = tr.addType(r, loadBytesForClass(r));
Class<?> rClazz = rtype.getClazz();
Object rInstance = rClazz.newInstance();
assertEquals(2, runOnInstance(rClazz, rInstance, "getI").returnValue);
qtype.loadNewVersion("2", retrieveRename(q, q + "2"));
assertEquals(1, runOnInstance(rClazz, rInstance, "getI").returnValue);
}
/**
* Here the field in Qc is shadowing (same name) a field in the interface. Once the new version of Qc is loaded that doesn't
* define the field, this exposes the field in the interface but it is static and as we are running a GETFIELD operation in Rc,
* it is illegal (IncompatibleClassChangeError)
*/
@Test
public void removingFieldsShadowingInterfaceFields1() throws Exception {
// String i = "fields.Ic";
String q = "fields.Qc";
String r = "fields.Rc";
TypeRegistry tr = getTypeRegistry(q + "," + r);
ReloadableType qtype = tr.addType(q, loadBytesForClass(q));
ReloadableType rtype = tr.addType(r, loadBytesForClass(r));
Class<?> rClazz = rtype.getClazz();
Object rInstance = rClazz.newInstance();
assertEquals(2, runOnInstance(rClazz, rInstance, "getNumber").returnValue);
qtype.loadNewVersion("2", retrieveRename(q, q + "2"));
try {
runOnInstance(rClazz, rInstance, "getNumber");
fail();
} catch (ResultException re) {
Throwable e = (re).getCause();
assertTrue(e.getCause() instanceof IncompatibleClassChangeError);
assertEquals("Expected non-static field fields.Rc.number", e.getCause().getMessage());
}
}
/**
* Both 'normal' reloadable field access and reflective field access will use the same set methods on ReloadableType. In the
* reflection case the right FieldAccessor must be discovered, in the normal case it is passed in. This test checks that using
* either route behaves - exercising the method directly and not through reflection.
*/
@Test
public void accessingFieldsThroughReloadableType() throws Exception {
String p = "fields.P";
String q = "fields.Q";
String r = "fields.R";
TypeRegistry tr = getTypeRegistry(p + "," + q + "," + r);
ReloadableType ptype = tr.addType(p, loadBytesForClass(p));
ReloadableType qtype = tr.addType(q, loadBytesForClass(q));
ReloadableType rtype = tr.addType(r, loadBytesForClass(r));
Class<?> rClazz = rtype.getClazz();
Object rInstance = rClazz.newInstance();
// Before reloading, check both routes get to the same field:
assertEquals(2, runOnInstance(rClazz, rInstance, "getI").returnValue);
assertEquals(2, rtype.getField(rInstance, "i", false));
// Now mess with it via one route and check result via the other:
rtype.setField(rInstance, "i", false, 44);
assertEquals(44, runOnInstance(rClazz, rInstance, "getI").returnValue);
qtype.loadNewVersion("2", retrieveRename(q, q + "2"));
// After reloading, check both routes get to the same field:
assertEquals(1, runOnInstance(rClazz, rInstance, "getI").returnValue);
assertEquals(1, ptype.getField(rInstance, "i", false));
// Now mess with it via one route and check result via the other:
rtype.setField(rInstance, "i", false, 357);
assertEquals(357, runOnInstance(rClazz, rInstance, "getI").returnValue);
}
/**
* Fields are changed from int>String and String>int. When this happens we should remove the int value we have and treat the
* field as unset.
*/
@Test
public void changingFieldType() throws Exception {
String p = "fields.T";
TypeRegistry tr = getTypeRegistry(p);
ReloadableType type = tr.addType(p, loadBytesForClass(p));
Class<?> rClazz = type.getClazz();
Object rInstance = rClazz.newInstance();
// Before reloading, check both routes get to the same field
assertEquals(345, runOnInstance(rClazz, rInstance, "getI").returnValue);
assertEquals(345, type.getField(rInstance, "i", false));
assertEquals("stringValue", runOnInstance(rClazz, rInstance, "getJ").returnValue);
assertEquals("stringValue", type.getField(rInstance, "j", false));
type.loadNewVersion("2", retrieveRename(p, p + "2"));
// On the reload the field changed type, so we now check it was reset to default value
assertEquals(null, runOnInstance(rClazz, rInstance, "getI").returnValue);
assertEquals(null, type.getField(rInstance, "i", false));
assertEquals(0, runOnInstance(rClazz, rInstance, "getJ").returnValue);
assertEquals(0, type.getField(rInstance, "j", false));
}
/**
* Two types in a hierarchy, the field is initially accessed in the supertype but an interface that the subtype implements then
* introduces that field on a reload. Although it is introduced in the interface, the bytecode reference to the field is
* directly to that in the supertype - so the interface one will not be found (the subtype would be pointing at the interface
* one if B was recompiled with this setup).
*/
@Test
public void introducingStaticFieldInInterface() throws Exception {
String a = "sfields.A";
String b = "sfields.B";
String i = "sfields.I";
TypeRegistry tr = getTypeRegistry(a + "," + b + "," + i);
ReloadableType itype = tr.addType(i, loadBytesForClass(i));
ReloadableType atype = tr.addType(a, loadBytesForClass(a));
ReloadableType btype = tr.addType(b, loadBytesForClass(b));
Class<?> bClazz = btype.getClazz();
Object bInstance = bClazz.newInstance();
// No changes, should find i in the supertype A
assertEquals(45, runOnInstance(bClazz, bInstance, "getI").returnValue);
assertEquals(45, atype.getField(bInstance, "i", true));
// Reload i which now has a definition of the field
itype.loadNewVersion("2", retrieveRename(i, i + "2"));
// Introduced in the interface but the instruction was GETSTATIC A.i so we won't find it on the interface
// Even if we did find it on the interface the value would be zero because the staticinitializer for I2
// has not run
assertEquals(45, runOnInstance(bClazz, bInstance, "getI").returnValue);
assertEquals(45, atype.getField(bInstance, "i", true));
}
/**
* In this test several fields are setup and queried, then on reload their type is changed - ensure the results are as expected.
*/
@Test
public void testingCompatibilityOnFieldTypeChanges() throws Exception {
String a = "fields.TypeChanging";
String b = "fields.Middle";
TypeRegistry tr = getTypeRegistry(a + "," + b);
ReloadableType type = tr.addType(a, loadBytesForClass(a));
tr.addType(b, loadBytesForClass(b));
Class<?> clazz = type.getClazz();
Object instance = clazz.newInstance();
// Should be fine
assertEquals("SubInstance", toString(runOnInstance(clazz, instance, "getSuper").returnValue));
assertEquals(1, runOnInstance(clazz, instance, "getI").returnValue);
assertEquals(34L, runOnInstance(clazz, instance, "getLong").returnValue);
assertEquals(true, runOnInstance(clazz, instance, "getBoolean").returnValue);
assertEquals((short) 32, runOnInstance(clazz, instance, "getShort").returnValue);
assertEquals('a', runOnInstance(clazz, instance, "getChar").returnValue);
assertEquals(2.0f, runOnInstance(clazz, instance, "getFloat").returnValue);
assertEquals(3.141d, runOnInstance(clazz, instance, "getDouble").returnValue);
assertEquals((byte) 0xff, runOnInstance(clazz, instance, "getByte").returnValue);
assertEquals("{1,2,3}", toString(runOnInstance(clazz, instance, "getWasArray").returnValue));
assertEquals("abc", toString(runOnInstance(clazz, instance, "getWasNotArray").returnValue));
// This changes all the field types
type.loadNewVersion("2", retrieveRename(a, a + "2"));
assertEquals("SubInstance", toString(runOnInstance(clazz, instance, "getSuper").returnValue));
assertEquals(1, runOnInstance(clazz, instance, "getI").returnValue);
assertEquals(34L, runOnInstance(clazz, instance, "getLong").returnValue);
assertEquals(true, runOnInstance(clazz, instance, "getBoolean").returnValue);
assertEquals((short) 32, runOnInstance(clazz, instance, "getShort").returnValue);
assertEquals('a', runOnInstance(clazz, instance, "getChar").returnValue);
assertEquals(2.0f, runOnInstance(clazz, instance, "getFloat").returnValue);
assertEquals(3.141d, runOnInstance(clazz, instance, "getDouble").returnValue);
assertEquals((byte) 0xff, runOnInstance(clazz, instance, "getByte").returnValue);
assertEquals("0", toString(runOnInstance(clazz, instance, "getWasArray").returnValue));
assertEquals(null, toString(runOnInstance(clazz, instance, "getWasNotArray").returnValue));
}
public String toString(Object o) {
if (o == null) {
return null;
}
if (o instanceof int[]) {
int[] is = (int[]) o;
StringBuilder s = new StringBuilder();
s.append("{");
for (int i = 0; i < is.length; i++) {
if (i > 0) {
s.append(',');
}
s.append(is[i]);
}
s.append('}');
return s.toString();
} else {
return o.toString();
}
}
// bogus test - the inlining of constants means that second version of the class already returns the value...
// @Test
// public void newFieldOnInterface() throws Exception {
// String intface = "sfields.X";
// String impl = "sfields.Y";
// TypeRegistry tr = getTypeRegistry(intface + "," + impl);
// ReloadableType intType = tr.addType(intface, loadBytesForClass(intface));
// ReloadableType implType = tr.addType(impl, loadBytesForClass(impl));
//
// Class<?> implClazz = implType.getClazz();
// Object implInstance = implClazz.newInstance();
//
// assertEquals(34, runOnInstance(implClazz, implInstance, "getnum").returnValue);
//
// intType.loadNewVersion("2", retrieveRename(intface, intface + "2"));
// implType.loadNewVersion("2", retrieveRename(impl, impl + "2", "sfields.X2:sfields.X"));
//
// assertEquals(34, runOnInstance(implClazz, implInstance, "getnum").returnValue);
// }
@Test
public void modifyingStaticFieldsInHierarchy() throws Exception {
String c = "sfields.C";
String d = "sfields.D";
String e = "sfields.E";
TypeRegistry tr = getTypeRegistry(c + "," + d + "," + e);
// ReloadableType ctype =
tr.addType(c, loadBytesForClass(c));
ReloadableType dtype = tr.addType(d, loadBytesForClass(d));
ReloadableType etype = tr.addType(e, loadBytesForClass(e));
Class<?> eClazz = etype.getClazz();
Object eInstance = eClazz.newInstance();
// No changes, the field in D should be used
assertEquals(4, runOnInstance(eClazz, eInstance, "getI").returnValue);
assertEquals(4, etype.getField(null, "i", true));
etype.setField(null, "i", true, 123);
assertEquals(123, runOnInstance(eClazz, eInstance, "getI").returnValue);
// Reload d which removes the field and makes the variant in C visible
dtype.loadNewVersion("2", retrieveRename(d, d + "2"));
assertEquals(3, runOnInstance(eClazz, eInstance, "getI").returnValue);
assertEquals(3, etype.getField(null, "i", true));
// Now interact with that field in C that has become visible
etype.setField(null, "i", true, 12345);
assertEquals(12345, runOnInstance(eClazz, eInstance, "getI").returnValue);
assertEquals(12345, etype.getField(null, "i", true));
// Use the setter
runOnInstance(eClazz, eInstance, "setI", 6789);
assertEquals(6789, runOnInstance(eClazz, eInstance, "getI").returnValue);
assertEquals(6789, etype.getField(null, "i", true));
}
/**
* Switch some fields from their primitive forms to their boxed forms, check data is preserved, then switch them back and check
* data is preserved.
*/
@Test
public void switchToFromBoxing() throws Exception {
String e = "fields.E";
TypeRegistry tr = getTypeRegistry(e);
ReloadableType type = tr.addType(e, loadBytesForClass(e));
Class<?> clazz = type.getClazz();
Object rInstance = clazz.newInstance();
// Before reloading, check both routes get to the same field
assertEquals(100, runOnInstance(clazz, rInstance, "getInt").returnValue);
assertEquals(100, type.getField(rInstance, "i", false));
assertEquals((short) 200, runOnInstance(clazz, rInstance, "getShort").returnValue);
assertEquals((short) 200, type.getField(rInstance, "s", false));
assertEquals(324L, runOnInstance(clazz, rInstance, "getLong").returnValue);
assertEquals(324L, type.getField(rInstance, "j", false));
assertEquals(2.5d, runOnInstance(clazz, rInstance, "getDouble").returnValue);
assertEquals(2.5d, type.getField(rInstance, "d", false));
assertEquals(true, runOnInstance(clazz, rInstance, "getBoolean").returnValue);
assertEquals(true, type.getField(rInstance, "z", false));
assertEquals(32f, runOnInstance(clazz, rInstance, "getFloat").returnValue);
assertEquals(32f, type.getField(rInstance, "f", false));
assertEquals('a', runOnInstance(clazz, rInstance, "getChar").returnValue);
assertEquals('a', type.getField(rInstance, "c", false));
assertEquals((byte) 255, runOnInstance(clazz, rInstance, "getByte").returnValue);
assertEquals((byte) 255, type.getField(rInstance, "b", false));
type.loadNewVersion("2", retrieveRename(e, e + "2"));
// Now all boxed versions
assertEquals(100, runOnInstance(clazz, rInstance, "getInt").returnValue);
assertEquals(100, type.getField(rInstance, "i", false));
assertEquals((short) 200, runOnInstance(clazz, rInstance, "getShort").returnValue);
assertEquals((short) 200, type.getField(rInstance, "s", false));
assertEquals(324L, runOnInstance(clazz, rInstance, "getLong").returnValue);
assertEquals(324L, type.getField(rInstance, "j", false));
assertEquals(2.5d, runOnInstance(clazz, rInstance, "getDouble").returnValue);
assertEquals(2.5d, type.getField(rInstance, "d", false));
assertEquals(true, runOnInstance(clazz, rInstance, "getBoolean").returnValue);
assertEquals(true, type.getField(rInstance, "z", false));
assertEquals(32f, runOnInstance(clazz, rInstance, "getFloat").returnValue);
assertEquals(32f, type.getField(rInstance, "f", false));
assertEquals('a', runOnInstance(clazz, rInstance, "getChar").returnValue);
assertEquals('a', type.getField(rInstance, "c", false));
assertEquals((byte) 255, runOnInstance(clazz, rInstance, "getByte").returnValue);
assertEquals((byte) 255, type.getField(rInstance, "b", false));
// revert to unboxed
type.loadNewVersion("3", loadBytesForClass(e));
assertEquals(100, runOnInstance(clazz, rInstance, "getInt").returnValue);
assertEquals(100, type.getField(rInstance, "i", false));
assertEquals((short) 200, runOnInstance(clazz, rInstance, "getShort").returnValue);
assertEquals((short) 200, type.getField(rInstance, "s", false));
assertEquals(324L, runOnInstance(clazz, rInstance, "getLong").returnValue);
assertEquals(324L, type.getField(rInstance, "j", false));
assertEquals(2.5d, runOnInstance(clazz, rInstance, "getDouble").returnValue);
assertEquals(2.5d, type.getField(rInstance, "d", false));
assertEquals(true, runOnInstance(clazz, rInstance, "getBoolean").returnValue);
assertEquals(true, type.getField(rInstance, "z", false));
assertEquals(32f, runOnInstance(clazz, rInstance, "getFloat").returnValue);
assertEquals(32f, type.getField(rInstance, "f", false));
assertEquals('a', runOnInstance(clazz, rInstance, "getChar").returnValue);
assertEquals('a', type.getField(rInstance, "c", false));
assertEquals((byte) 255, runOnInstance(clazz, rInstance, "getByte").returnValue);
assertEquals((byte) 255, type.getField(rInstance, "b", false));
}
@Test
public void ctorReloadWithNewField() throws Exception {
String y = "ctors.JR";
TypeRegistry tr = getTypeRegistry(y);
ReloadableType rtype = tr.addType(y, loadBytesForClass(y));
Class<?> clazz = rtype.getClazz();
Object instance = runStaticUnguarded(clazz, "getInstance").returnValue;
assertEquals("hello", runOnInstance(clazz, instance, "printMessage").returnValue);
rtype.loadNewVersion("2", retrieveRename(y, y + "2"));
assertEquals("goodbye", runOnInstance(clazz, instance, "printMessage").returnValue);
Object instance2 = runStaticUnguarded(clazz,"getInstance").returnValue;
Object ret = runOnInstance(clazz,instance2,"getFieldReflectively").returnValue;
assertEquals(34,ret);
ret = runOnInstance(clazz,instance2,"setFieldReflectively",99).returnValue;
ret = runOnInstance(clazz,instance2,"getFieldReflectively").returnValue;
assertEquals(99,ret);
}
}