Bug 560521 - HCR under OT/Equinox reuses old bytes
diff --git a/plugins/org.eclipse.objectteams.otequinox/META-INF/MANIFEST.MF b/plugins/org.eclipse.objectteams.otequinox/META-INF/MANIFEST.MF
index dd91c83..d54572d 100644
--- a/plugins/org.eclipse.objectteams.otequinox/META-INF/MANIFEST.MF
+++ b/plugins/org.eclipse.objectteams.otequinox/META-INF/MANIFEST.MF
@@ -8,9 +8,9 @@
Bundle-Vendor: %providerName
Bundle-Localization: plugin
Require-Bundle: org.eclipse.core.runtime,
- org.eclipse.objectteams.runtime;bundle-version="[2.5.0,3.0.0)";visibility:=reexport,
+ org.eclipse.objectteams.runtime;bundle-version="[2.8.0,3.0.0)";visibility:=reexport,
org.eclipse.objectteams.otre;bundle-version="[2.5.0,3.0.0)",
- org.eclipse.objectteams.otredyn;bundle-version="[1.3.1,2.0.0)",
+ org.eclipse.objectteams.otredyn;bundle-version="[1.4.0,2.0.0)",
org.eclipse.osgi;bundle-version="[3.10.0,4.0.0)",
org.eclipse.jdt.annotation;bundle-version="[2.2.0,3.0.0)";resolution:=optional,
org.objectweb.asm;bundle-version="[7.0.0,8.0.0)"
diff --git a/plugins/org.eclipse.objectteams.otequinox/agentSrc/org/eclipse/objectteams/otequinox/OTEquinoxAgent.java b/plugins/org.eclipse.objectteams.otequinox/agentSrc/org/eclipse/objectteams/otequinox/OTEquinoxAgent.java
index 59c3819..5aa37d3 100644
--- a/plugins/org.eclipse.objectteams.otequinox/agentSrc/org/eclipse/objectteams/otequinox/OTEquinoxAgent.java
+++ b/plugins/org.eclipse.objectteams.otequinox/agentSrc/org/eclipse/objectteams/otequinox/OTEquinoxAgent.java
@@ -51,9 +51,10 @@
if (bytesBeingRedefined.containsKey(classBeingRedefined))
return null; // our own transformer triggered the redefine, don' process again
try {
- if (isDefaultClassLoader(loader)) {
+ if (isEquinoxClassLoader(loader)) {
String classNameDot = className.replace('/', '.'); // transformer expects dot-based names!
boolean modified = false;
+ registerClassBeingDefined(classBeingRedefined, loader);
for (Object hook : getClassLoadingHooks(loader)) {
// TODO(SH): fetch remaining arguments?
byte[] newBytes = processClass(hook, classNameDot, classfileBuffer, null/*classpathEntry*/, null/*entry*/, getClasspathManager(loader));
@@ -88,22 +89,37 @@
// ===== The following members provide reflection based access to otherwise inaccessible classes from org.eclipse.osgi =====
// cached reflection members:
- private Class<?> dclClass;
+ private Class<?> eclClass;
private Method getConfiguration;
private Method getHookRegistry;
private Method getClassLoaderHooks;
private Method getClasspathManager;
private Method processClass;
- private boolean isDefaultClassLoader(ClassLoader loader) {
+ private Method registerClassBeingDefined; // method of classRepoClass
+
+ // make the classBeingRedefined known to org.eclipse.objectteams.otredyn.bytecode.ClassRepository:
+ private void registerClassBeingDefined(Class<?> classBeingRedefined, ClassLoader loader) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, ClassNotFoundException, NoSuchMethodException, SecurityException {
+ Class<?> classRepo = OTEquinoxAgent.classRepoClass;
+ if (classRepo == null) {
+ System.err.println("ClassRepo has not been registered");
+ return;
+ }
+ Method meth = this.registerClassBeingDefined;
+ if (meth == null)
+ this.registerClassBeingDefined = meth = classRepo.getDeclaredMethod("registerClassBeingRedefined", Class.class);
+ meth.invoke(null, classBeingRedefined);
+ }
+
+ private boolean isEquinoxClassLoader(ClassLoader loader) {
/* Emulates:
* return loader instanceof EquinoxClassLoader;
*/
Class<?> clazz = loader.getClass();
- if (this.dclClass != null) {
- return this.dclClass == clazz;
+ if (this.eclClass != null) {
+ return this.eclClass == clazz;
} else if (clazz.getName().equals("org.eclipse.osgi.internal.loader.EquinoxClassLoader")) {
- this.dclClass = clazz;
+ this.eclClass = clazz;
return true;
}
return false;
@@ -117,7 +133,7 @@
* return conf.getHookRegistry().getClassLoaderHooks();
*/
if (this.getConfiguration == null) {
- this.getConfiguration = this.dclClass.getDeclaredMethod("getConfiguration", (Class[])null);
+ this.getConfiguration = this.eclClass.getDeclaredMethod("getConfiguration", (Class[])null);
this.getConfiguration.setAccessible(true);
}
Object conf = this.getConfiguration.invoke(loader, (Object[])null);
@@ -136,7 +152,7 @@
* return ((DefaultClassLoader)loader).getClasspathManager();
*/
if (this.getClasspathManager == null)
- this.getClasspathManager = this.dclClass.getMethod("getClasspathManager", (Class[]) null);
+ this.getClasspathManager = this.eclClass.getMethod("getClasspathManager", (Class[]) null);
return this.getClasspathManager.invoke(loader, (Object[]) null);
}
@@ -163,6 +179,15 @@
}
}
+ static Class<?> classRepoClass;
+ /**
+ * Reflectively accessed by org.eclipse.objectteams.internal.osgi.weaving.DelegatingTransformer:
+ * Here OTDRE makes its ClassRepository class known to us (for reflective call-back):
+ */
+ public static void wireClassRepo(Class<?> classRepoClass) {
+ OTEquinoxAgent.classRepoClass = classRepoClass;
+ }
+
private static Instrumentation instrumentation;
private static Map<Class<?>,byte[]> bytesBeingRedefined = new HashMap<>(); // to avoid reentrant transform requests, if our transformer triggers a redefine
@@ -171,7 +196,8 @@
inst.addTransformer(new OTEquinoxTransformerDelegate());
OTEquinoxAgent.instrumentation = inst;
}
-
+
+ /** Reflectively accessed by org.eclipse.objectteams.internal.osgi.weaving.DelegatingTransformer. */
public static void redefine(ClassDefinition[] definitions) throws ClassNotFoundException, UnmodifiableClassException {
if (instrumentation == null)
throw new UnmodifiableClassException("Can't redefine classes, no instrumentation set.");
diff --git a/plugins/org.eclipse.objectteams.otequinox/src/org/eclipse/objectteams/internal/osgi/weaving/DelegatingTransformer.java b/plugins/org.eclipse.objectteams.otequinox/src/org/eclipse/objectteams/internal/osgi/weaving/DelegatingTransformer.java
index db2b6b1..3a44cfe 100644
--- a/plugins/org.eclipse.objectteams.otequinox/src/org/eclipse/objectteams/internal/osgi/weaving/DelegatingTransformer.java
+++ b/plugins/org.eclipse.objectteams.otequinox/src/org/eclipse/objectteams/internal/osgi/weaving/DelegatingTransformer.java
@@ -32,6 +32,7 @@
import org.eclipse.objectteams.internal.osgi.weaving.OTWeavingHook.WeavingReason;
import org.eclipse.objectteams.internal.osgi.weaving.OTWeavingHook.WeavingScheme;
import org.eclipse.objectteams.internal.osgi.weaving.Util.ProfileKind;
+import org.eclipse.objectteams.otredyn.bytecode.ClassRepository;
import org.eclipse.objectteams.otredyn.bytecode.IRedefineStrategy;
import org.eclipse.objectteams.otredyn.bytecode.RedefineStrategyFactory;
import org.eclipse.objectteams.otredyn.transformer.IWeavingContext;
@@ -98,6 +99,7 @@
public OTDRETransformer(IWeavingContext weavingContext) {
RedefineStrategyFactory.setRedefineStrategy(new OTEquinoxRedefineStrategy());
transformer = new org.eclipse.objectteams.otredyn.transformer.jplis.ObjectTeamsTransformer(weavingContext);
+ reflectivelyWireClassRepo();
}
@Override
public void readOTAttributes(@NonNull String className, @NonNull InputStream inputStream, @NonNull String fileName, Bundle bundle) throws ClassFormatError, IOException {
@@ -157,6 +159,17 @@
}
}
+ public void reflectivelyWireClassRepo() {
+ // make ClassRepository.class known to the debug agent, so we can communicate for HCR
+ try {
+ Class<?> agentClass = ClassLoader.getSystemClassLoader().loadClass(OT_EQUINOX_DEBUG_AGENT);
+ java.lang.reflect.Method wire = agentClass.getDeclaredMethod("wireClassRepo", Class.class);
+ wire.invoke(null, new Object[]{ ClassRepository.class });
+ } catch (Throwable t) {
+ t.printStackTrace(); // FIXME
+ }
+ }
+
static @Nullable ClassLoader getBundleLoader(final @Nullable Bundle bundle) {
if (bundle == null) return null;
return new ClassLoader() {
diff --git a/plugins/org.eclipse.objectteams.otredyn/META-INF/MANIFEST.MF b/plugins/org.eclipse.objectteams.otredyn/META-INF/MANIFEST.MF
index ca509cb..879656f 100644
--- a/plugins/org.eclipse.objectteams.otredyn/META-INF/MANIFEST.MF
+++ b/plugins/org.eclipse.objectteams.otredyn/META-INF/MANIFEST.MF
@@ -3,7 +3,7 @@
Bundle-Name: %pluginName
Bundle-SymbolicName: org.eclipse.objectteams.otredyn
Automatic-Module-Name: org.eclipse.objectteams.otredyn
-Bundle-Version: 1.3.7.qualifier
+Bundle-Version: 1.4.0.qualifier
Bundle-Vendor: %providerName
Bundle-Localization: plugin
Export-Package: org.eclipse.objectteams.otredyn.bytecode,
@@ -14,7 +14,7 @@
org.eclipse.objectteams.otredyn.util
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Bundle-ClassPath: .
-Require-Bundle: org.eclipse.objectteams.runtime;bundle-version="[2.7.3,3.0.0)",
+Require-Bundle: org.eclipse.objectteams.runtime;bundle-version="[2.8.0,3.0.0)",
org.objectweb.asm;bundle-version="[7.1.0,8.0.0)",
org.objectweb.asm.tree;bundle-version="[7.1.0,8.0.0)",
org.objectweb.asm.commons;bundle-version="[7.1.0,8.0.0)",
diff --git a/plugins/org.eclipse.objectteams.otredyn/src/org/eclipse/objectteams/otredyn/bytecode/AbstractBoundClass.java b/plugins/org.eclipse.objectteams.otredyn/src/org/eclipse/objectteams/otredyn/bytecode/AbstractBoundClass.java
index 81f872c..e023ba8 100644
--- a/plugins/org.eclipse.objectteams.otredyn/src/org/eclipse/objectteams/otredyn/bytecode/AbstractBoundClass.java
+++ b/plugins/org.eclipse.objectteams.otredyn/src/org/eclipse/objectteams/otredyn/bytecode/AbstractBoundClass.java
@@ -1260,7 +1260,10 @@
protected abstract void prepareForFirstStaticTransformation();
public abstract boolean isFirstTransformation();
-
+
+ /** During HCR we need to start with a fresh set of bytes and re-perform all transformations. */
+ public abstract void restartTransformation();
+
public boolean isLoaded() { return isLoaded; }
protected abstract void createDispatchCodeInOrgMethod(Method boundMethod,
diff --git a/plugins/org.eclipse.objectteams.otredyn/src/org/eclipse/objectteams/otredyn/bytecode/AbstractTeam.java b/plugins/org.eclipse.objectteams.otredyn/src/org/eclipse/objectteams/otredyn/bytecode/AbstractTeam.java
index 6f959e7..5a3d332 100644
--- a/plugins/org.eclipse.objectteams.otredyn/src/org/eclipse/objectteams/otredyn/bytecode/AbstractTeam.java
+++ b/plugins/org.eclipse.objectteams.otredyn/src/org/eclipse/objectteams/otredyn/bytecode/AbstractTeam.java
@@ -112,4 +112,6 @@
tsubBases.add(classRepository.getBoundClass(tsubRoleName.replace('/', '.'), tsubRoleName.replace('.', '/'), this.loader));
return tsubBases;
}
+
+ protected abstract void setBytecode(byte[] classBytes);
}
diff --git a/plugins/org.eclipse.objectteams.otredyn/src/org/eclipse/objectteams/otredyn/bytecode/ClassRepository.java b/plugins/org.eclipse.objectteams.otredyn/src/org/eclipse/objectteams/otredyn/bytecode/ClassRepository.java
index 19138b7..1dd1fe0 100644
--- a/plugins/org.eclipse.objectteams.otredyn/src/org/eclipse/objectteams/otredyn/bytecode/ClassRepository.java
+++ b/plugins/org.eclipse.objectteams.otredyn/src/org/eclipse/objectteams/otredyn/bytecode/ClassRepository.java
@@ -35,6 +35,23 @@
*/
public abstract class ClassRepository implements IClassRepository {
private static ClassRepository instance;
+
+ private static ThreadLocal<Class<?>> classBeingRedefined = new ThreadLocal<>();
+
+ /**
+ * Reflectively invoked by org.eclipse.objectteams.otequinox.OTEquinoxAgent:
+ * During HCR the agent informs as about a class about to be hot-swapped.
+ */
+ public static void registerClassBeingRedefined(Class<?> clazz) {
+ classBeingRedefined.set(clazz);
+ }
+ public static Class<?> popClassBeingRedefined(String className) {
+ Class<?> clazz = classBeingRedefined.get();
+ classBeingRedefined.set(null);
+ if (clazz != null && clazz.getName().equals(className))
+ return clazz;
+ return null;
+ }
protected ClassRepository() {
@@ -99,9 +116,12 @@
* if id1.equals(id2) then getBoundClass(..., id1) == getBoundClass(..., id2)
* @param className the name of the class
* @param id a globally unique identifier for the class
+ * @param classBytes bytecode before weaving, possibly for hotswapping
+ * @param loader class loader for this class
+ * @param isHCR true if invoked during hot code replace, in which case transformation must restart using the new bytes
* @return
*/
- public synchronized AbstractBoundClass getBoundClass(String className, String id, byte[] classBytes, ClassLoader loader)
+ public synchronized AbstractBoundClass getBoundClass(String className, String id, byte[] classBytes, ClassLoader loader, boolean isHCR)
{
AbstractTeam clazz = boundClassMap.get(id);
// set the bytecode in the BytecodeProvider
@@ -109,7 +129,10 @@
bytecodeProvider.setBytecode(id, classBytes);
if (clazz == null) {
clazz = createClass(className, id, bytecodeProvider, loader);
- }
+ } else if (isHCR) {
+ clazz.setBytecode(classBytes);
+ clazz.restartTransformation();
+ }
boundClassMap.put(id, clazz);
diff --git a/plugins/org.eclipse.objectteams.otredyn/src/org/eclipse/objectteams/otredyn/bytecode/asm/AsmWritableBoundClass.java b/plugins/org.eclipse.objectteams.otredyn/src/org/eclipse/objectteams/otredyn/bytecode/asm/AsmWritableBoundClass.java
index 4cf30cf..a3b6d97 100644
--- a/plugins/org.eclipse.objectteams.otredyn/src/org/eclipse/objectteams/otredyn/bytecode/asm/AsmWritableBoundClass.java
+++ b/plugins/org.eclipse.objectteams.otredyn/src/org/eclipse/objectteams/otredyn/bytecode/asm/AsmWritableBoundClass.java
@@ -567,6 +567,10 @@
return isFirstTransformation;
}
+ public void restartTransformation() {
+ this.isFirstTransformation = true;
+ }
+
@Override
protected boolean isSuperWeavable(boolean considerSupers) {
if (this.superIsWeavable == null)
diff --git a/plugins/org.eclipse.objectteams.otredyn/src/org/eclipse/objectteams/otredyn/transformer/jplis/ObjectTeamsTransformer.java b/plugins/org.eclipse.objectteams.otredyn/src/org/eclipse/objectteams/otredyn/transformer/jplis/ObjectTeamsTransformer.java
index cc78601..cc7c8ea 100644
--- a/plugins/org.eclipse.objectteams.otredyn/src/org/eclipse/objectteams/otredyn/transformer/jplis/ObjectTeamsTransformer.java
+++ b/plugins/org.eclipse.objectteams.otredyn/src/org/eclipse/objectteams/otredyn/transformer/jplis/ObjectTeamsTransformer.java
@@ -107,6 +107,12 @@
if (clazz == null)
clazz = classRepo.getBoundClass(sourceClassName, classId, loader);
+ boolean isHCR = false;
+ if (classBeingRedefined == null) {
+ classBeingRedefined = ClassRepository.popClassBeingRedefined(sourceClassName);
+ if (classBeingRedefined != null)
+ isHCR = true;
+ }
synchronized(clazz) { // all modifications done in this critical section
if (classBeingRedefined == null && !clazz.isFirstTransformation()) {
if (PWR_DEBUG) System.out.println("\tweave1");
@@ -119,7 +125,7 @@
try {
clazz.startTransaction();
clazz = classRepo.getBoundClass(
- className, classId, classfileBuffer, loader);
+ className, classId, classfileBuffer, loader, isHCR);
clazz.setWeavingContext(this.weavingContext);
if (!clazz.isInterface())
classRepo.linkClassWithSuperclass(clazz);
@@ -201,7 +207,7 @@
byte[] bytes = new byte[available];
new DataInputStream(inputStream).readFully(bytes);
clazz = ClassRepository.getInstance().getBoundClass(
- className, classId, bytes, loader);
+ className, classId, bytes, loader, false);
if (!clazz.isInterface())
ClassRepository.getInstance().linkClassWithSuperclass(clazz);
if (!clazz.isInterface() || clazz.isRole())
diff --git a/plugins/org.eclipse.objectteams.runtime/src/org/eclipse/objectteams/otredyn/runtime/IClassRepository.java b/plugins/org.eclipse.objectteams.runtime/src/org/eclipse/objectteams/otredyn/runtime/IClassRepository.java
index 5d456cc..03142ef 100644
--- a/plugins/org.eclipse.objectteams.runtime/src/org/eclipse/objectteams/otredyn/runtime/IClassRepository.java
+++ b/plugins/org.eclipse.objectteams.runtime/src/org/eclipse/objectteams/otredyn/runtime/IClassRepository.java
@@ -46,10 +46,13 @@
* instance for the same id. More formally:
* if id1.equals(id2) then getBoundClass(..., id1) == getBoundClass(..., id2)
* @param className the name of the class
- * @param id a globally unique identifier for the class
+ * @param id a globally unique identifier for the class
+ * @param classBytes bytecode before weaving, possibly for hotswapping
+ * @param loader class loader for this class
+ * @param isHCR true if invoked during hot code replace, in which case transformation must restart using the new bytes
* @return
*/
- public IBoundClass getBoundClass(String className, String id, byte[] classBytes, ClassLoader loader);
+ public IBoundClass getBoundClass(String className, String id, byte[] classBytes, ClassLoader loader, boolean isHCR);
/**
* Returns a instance of AbstractTeam for the
diff --git a/releng/map/otdt.map.in b/releng/map/otdt.map.in
index c962d27..71815a9 100644
--- a/releng/map/otdt.map.in
+++ b/releng/map/otdt.map.in
@@ -22,7 +22,7 @@
plugin@org.eclipse.objectteams.runtime=GIT,repo=git://git.eclipse.org/gitroot/objectteams/org.eclipse.objectteams.git,path=plugins/org.eclipse.objectteams.runtime
plugin@org.eclipse.objectteams.otre=GIT,repo=git://git.eclipse.org/gitroot/objectteams/org.eclipse.objectteams.git,path=plugins/org.eclipse.objectteams.otre,tag=builds/201806120901
-plugin@org.eclipse.objectteams.otredyn=GIT,repo=git://git.eclipse.org/gitroot/objectteams/org.eclipse.objectteams.git,path=plugins/org.eclipse.objectteams.otredyn,tag=builds/201912111741
+plugin@org.eclipse.objectteams.otredyn=GIT,repo=git://git.eclipse.org/gitroot/objectteams/org.eclipse.objectteams.git,path=plugins/org.eclipse.objectteams.otredyn
!the following is also referenced in otdt.doc/buildDoc.xml (plugin-name without version):
plugin@org.eclipse.objectteams.otequinox=GIT,repo=git://git.eclipse.org/gitroot/objectteams/org.eclipse.objectteams.git,path=plugins/org.eclipse.objectteams.otequinox
fragment@org.eclipse.objectteams.otequinox.turbo=GIT,repo=git://git.eclipse.org/gitroot/objectteams/org.eclipse.objectteams.git,path=plugins/org.eclipse.objectteams.otequinox.turbo,tag=builds/201506091717