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