Bug 508524 - [hcr] Changing code in unrelated project triggers
"Hot Code Replace Failed" dialog
Filter types from unrelated projects from HCR.
Change-Id: I2ad7350cd34e32f8f1ad14e5679c10f659e69be3
Signed-off-by: Andrey Loskutov <loskutov@gmx.de>
diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AbstractDebugTest.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AbstractDebugTest.java
index 45d6540..b106fc4 100644
--- a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AbstractDebugTest.java
+++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AbstractDebugTest.java
@@ -185,6 +185,7 @@
public static final String ONE_SEVEN_PROJECT_NAME = "OneSeven";
public static final String ONE_EIGHT_PROJECT_NAME = "OneEight";
public static final String BOUND_JRE_PROJECT_NAME = "BoundJRE";
+ public static final String CLONE_SUFFIX = "Clone";
final String[] LAUNCH_CONFIG_NAMES_1_4 = {"LargeSourceFile", "LotsOfFields", "Breakpoints", "InstanceVariablesTests", "LocalVariablesTests", "LocalVariableTests2", "StaticVariablesTests",
"DropTests", "ThrowsNPE", "ThrowsException", "org.eclipse.debug.tests.targets.Watchpoint",
@@ -399,6 +400,7 @@
cfgs.add(createLaunchConfiguration(jp, "a.b.c.bug403028"));
cfgs.add(createLaunchConfiguration(jp, "a.b.c.bug484686"));
cfgs.add(createLaunchConfiguration(jp, "a.b.c.GenericMethodEntryTest"));
+ cfgs.add(createLaunchConfiguration(jp, "org.eclipse.debug.tests.targets.HcrClass", true));
loaded15 = true;
waitForBuild();
}
@@ -1183,6 +1185,21 @@
}
/**
+ * Launches the type with the given name, and waits for a breakpoint-caused
+ * suspend event in that program. Returns the thread in which the suspend
+ * event occurred.
+ *
+ * @param mainTypeName the program to launch
+ * @param register whether to register the launch
+ * @return thread in which the first suspend event occurred
+ */
+ protected IJavaThread launchToBreakpoint(IJavaProject project, String mainTypeName, String launchName, boolean register) throws Exception {
+ ILaunchConfiguration config = getLaunchConfiguration(project, launchName);
+ assertNotNull("Could not locate launch configuration for " + mainTypeName, config); //$NON-NLS-1$
+ return launchToBreakpoint(config, register);
+ }
+
+ /**
* Launches the given configuration in debug mode, and waits for a breakpoint-caused
* suspend event in that program. Returns the thread in which the suspend
* event occurred.
@@ -2446,8 +2463,19 @@
* Creates a shared launch configuration for the type with the given name.
*/
protected ILaunchConfiguration createLaunchConfiguration(IJavaProject project, String mainTypeName) throws Exception {
+ return createLaunchConfiguration(project, mainTypeName, false);
+ }
+
+ /**
+ * Creates a shared launch configuration for the type with the given name.
+ *
+ * @param clone
+ * true if the launch config name should be different from the main type name
+ */
+ protected ILaunchConfiguration createLaunchConfiguration(IJavaProject project, String mainTypeName, boolean clone) throws Exception {
ILaunchConfigurationType type = getLaunchManager().getLaunchConfigurationType(IJavaLaunchConfigurationConstants.ID_JAVA_APPLICATION);
- ILaunchConfigurationWorkingCopy config = type.newInstance(project.getProject().getFolder(LAUNCHCONFIGURATIONS), mainTypeName);
+ String configName = clone ? mainTypeName + CLONE_SUFFIX : mainTypeName;
+ ILaunchConfigurationWorkingCopy config = type.newInstance(project.getProject().getFolder(LAUNCHCONFIGURATIONS), configName);
config.setAttribute(IJavaLaunchConfigurationConstants.ATTR_MAIN_TYPE_NAME, mainTypeName);
config.setAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, project.getElementName());
Set<String> modes = new HashSet<>();
diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/core/HcrTests.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/core/HcrTests.java
index 70a3a7d..55cd091 100644
--- a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/core/HcrTests.java
+++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/core/HcrTests.java
@@ -10,12 +10,17 @@
*******************************************************************************/
package org.eclipse.jdt.debug.tests.core;
+import static org.junit.Assert.assertNotEquals;
+
import org.eclipse.debug.core.DebugEvent;
import org.eclipse.debug.core.DebugException;
import org.eclipse.jdt.core.IBuffer;
import org.eclipse.jdt.core.ICompilationUnit;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.debug.core.IJavaDebugTarget;
import org.eclipse.jdt.debug.core.IJavaHotCodeReplaceListener;
+import org.eclipse.jdt.debug.core.IJavaLineBreakpoint;
import org.eclipse.jdt.debug.core.IJavaStackFrame;
import org.eclipse.jdt.debug.core.IJavaThread;
import org.eclipse.jdt.debug.core.IJavaVariable;
@@ -1214,4 +1219,74 @@
JDIDebugModel.removeHotCodeReplaceListener(listener);
}
}
+
+ /**
+ * Tests that HCR is NOT triggered if the code change is happened on an unrelated type, see bug 508524 and 5188
+ */
+ public void testNoHcrOnUnrelatedType() throws Exception {
+ String typeName = "org.eclipse.debug.tests.targets.HcrClass";
+ IJavaProject unrelatedProject = get14Project();
+ IJavaProject debuggedProject = get15Project();
+ IType type1 = unrelatedProject.findType(typeName);
+ IType type2 = debuggedProject.findType(typeName);
+ assertNotEquals(type1, type2);
+
+ // Types FQNs are same
+ assertEquals(type1.getFullyQualifiedName(), type2.getFullyQualifiedName());
+
+ // Paths are same, except the project part
+ assertEquals(type1.getResource().getFullPath().removeFirstSegments(1), type2.getResource().getFullPath().removeFirstSegments(1));
+
+ final int lineNumber = 39;
+ IJavaLineBreakpoint bp1 = createLineBreakpoint(type1, lineNumber);
+ IJavaLineBreakpoint bp2 = createLineBreakpoint(type2, lineNumber);
+ assertNotEquals(bp1, bp2);
+
+ HCRListener listener = new HCRListener();
+ HCRListener listener2 = new HCRListener();
+ JDIDebugModel.addHotCodeReplaceListener(listener);
+ IJavaThread thread = null;
+ try {
+ // We start debugging on the one project but do modifications on the unrelated one!
+ thread = launchToBreakpoint(debuggedProject, typeName, typeName + CLONE_SUFFIX, true);
+ assertNotNull("Breakpoint not hit within timeout period", thread);
+
+ IJavaDebugTarget target = (IJavaDebugTarget) thread.getDebugTarget();
+ if (target.supportsHotCodeReplace()) {
+ target.addHotCodeReplaceListener(listener2);
+ // look at the value of 'x' - it should be "One"
+ IJavaStackFrame frame = (IJavaStackFrame) thread.getTopStackFrame();
+ IJavaVariable variable = findVariable(frame, "x");
+ assertNotNull("Could not find 'x'", variable);
+ assertEquals("value of 'x' should be 'One'", "One", variable.getValue().getValueString());
+ removeAllBreakpoints();
+ // now modify the source in *unrelated* project => NO HCR should happen, even if type names are same!
+ ICompilationUnit cu = getCompilationUnit(unrelatedProject, "src", "org.eclipse.debug.tests.targets", "HcrClass.java");
+ cu = cu.getPrimary();
+ if (!cu.isWorkingCopy()) {
+ cu = cu.getWorkingCopy(null);
+ }
+ assertTrue("HcrClass.java does not exist", cu.exists());
+ IBuffer buffer = cu.getBuffer();
+ String contents = buffer.getContents();
+ int index = contents.indexOf("\"One\"");
+ assertTrue("Could not find code to replace", index > 0);
+ String newCode = contents.substring(0, index) + "\"Two\"" + contents.substring(index + 5);
+ buffer.setContents(newCode);
+
+ // save contents
+ cu.commitWorkingCopy(true, null);
+ waitForBuild();
+ assertFalse("Specific listener should not have been notified", listener2.waitNotification());
+ assertFalse("General listener should not have been notified", listener.wasNotified());
+ } else {
+ System.err.println("Warning: HCR test skipped since target VM does not support HCR.");
+ }
+ }
+ finally {
+ terminateAndRemove(thread);
+ removeAllBreakpoints();
+ JDIDebugModel.removeHotCodeReplaceListener(listener);
+ }
+ }
}
diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/core/JavaDebugTargetTests.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/core/JavaDebugTargetTests.java
index 82681a9..b6b3e07 100644
--- a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/core/JavaDebugTargetTests.java
+++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/core/JavaDebugTargetTests.java
@@ -10,9 +10,14 @@
*******************************************************************************/
package org.eclipse.jdt.debug.tests.core;
+import static org.junit.Assert.assertNotEquals;
+
import java.lang.reflect.Method;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.debug.core.IJavaDebugTarget;
+import org.eclipse.jdt.debug.core.IJavaLineBreakpoint;
import org.eclipse.jdt.debug.core.IJavaThread;
import org.eclipse.jdt.debug.tests.AbstractDebugTest;
import org.eclipse.jdt.internal.debug.core.model.JDIDebugTarget;
@@ -111,6 +116,62 @@
}
}
+ /**
+ * Tests that debug target ignores breakpoints from unrelated projects, see bugs 5188 and 508524
+ */
+ public void testSupportsResource() throws Exception {
+ String typeName = "org.eclipse.debug.tests.targets.HcrClass";
+ IJavaProject project1 = get14Project();
+ IJavaProject project2 = get15Project();
+ IType type1 = project1.findType(typeName);
+ IType type2 = project2.findType(typeName);
+ assertNotEquals(type1, type2);
+
+ // Types FQNs are same
+ assertEquals(type1.getFullyQualifiedName(), type2.getFullyQualifiedName());
+
+ // Paths are same, except the project part
+ assertEquals(type1.getResource().getFullPath().removeFirstSegments(1), type2.getResource().getFullPath().removeFirstSegments(1));
+
+ final int lineNumber = 21;
+ IJavaLineBreakpoint bp1 = createLineBreakpoint(type1, lineNumber);
+ IJavaLineBreakpoint bp2 = createLineBreakpoint(type2, lineNumber);
+ assertNotEquals(bp1, bp2);
+
+ IJavaThread thread = null;
+ try {
+ // Launch the first project config: the breakpoint from second one shouldn't be supported
+ thread = launchToBreakpoint(project1, typeName, typeName, true);
+ assertNotNull("Breakpoint not hit within timeout period", thread);
+ JDIDebugTarget target = (JDIDebugTarget) thread.getDebugTarget();
+ assertTrue(target.isAvailable());
+ assertEquals(1, target.getBreakpoints().size());
+ assertEquals(bp1, target.getBreakpoints().get(0));
+ assertTrue(target.supportsResource(() -> typeName, type1.getResource()));
+ assertFalse(target.supportsResource(() -> typeName, type2.getResource()));
+ terminateAndRemove(thread);
+ // Line above *deletes all breakpoints!*
+
+ bp1 = createLineBreakpoint(type1, lineNumber);
+ bp2 = createLineBreakpoint(type2, lineNumber);
+ assertNotEquals(bp1, bp2);
+
+ // Launch the second project config: the breakpoint from first one shouldn't be supported
+ thread = launchToBreakpoint(project2, typeName, typeName + CLONE_SUFFIX, true);
+ assertNotNull("Breakpoint not hit within timeout period", thread);
+ target = (JDIDebugTarget) thread.getDebugTarget();
+ assertTrue(target.isAvailable());
+ assertEquals(1, target.getBreakpoints().size());
+ assertEquals(bp2, target.getBreakpoints().get(0));
+ assertFalse(target.supportsResource(() -> typeName, type1.getResource()));
+ assertTrue(target.supportsResource(() -> typeName, type2.getResource()));
+ }
+ finally {
+ terminateAndRemove(thread);
+ removeAllBreakpoints();
+ }
+ }
+
static private class JDIDebugTargetProxy {
private JDIDebugTarget target;
diff --git a/org.eclipse.jdt.debug.tests/testsource-j2se-1.5/org/eclipse/debug/tests/targets/HcrClass.java b/org.eclipse.jdt.debug.tests/testsource-j2se-1.5/org/eclipse/debug/tests/targets/HcrClass.java
new file mode 100644
index 0000000..9d0b89f
--- /dev/null
+++ b/org.eclipse.jdt.debug.tests/testsource-j2se-1.5/org/eclipse/debug/tests/targets/HcrClass.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2005 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.debug.tests.targets;
+
+/**
+ * Class used to test hot code replace
+ */
+public class HcrClass {
+
+ protected String instVar = null;
+
+ public static void main(String[] args) {
+ new HcrClass().one();
+ }
+
+ public void one() {
+ instVar = "One";
+ two();
+ }
+
+ public void two() {
+ three();
+ }
+
+ public void three() {
+ four();
+ }
+
+ public void four() {
+ String x = instVar;
+ System.out.println(x);
+ }
+
+}
diff --git a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/hcr/JavaHotCodeReplaceManager.java b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/hcr/JavaHotCodeReplaceManager.java
index c1b48cd..ef7576a 100644
--- a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/hcr/JavaHotCodeReplaceManager.java
+++ b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/hcr/JavaHotCodeReplaceManager.java
@@ -469,8 +469,15 @@
// unloaded types on a per-target basis.
List<IResource> resourcesToReplace = new ArrayList<>(resources);
List<String> qualifiedNamesToReplace = new ArrayList<>(qualifiedNames);
- filterUnloadedTypes(target, resourcesToReplace,
- qualifiedNamesToReplace);
+
+ // Make sure we only try to replace types from related projects
+ filterUnrelatedResources(target, resourcesToReplace, qualifiedNamesToReplace);
+ if (qualifiedNamesToReplace.isEmpty()) {
+ // If none of the changed types are related to our target, do nothing.
+ continue;
+ }
+
+ filterUnloadedTypes(target, resourcesToReplace, qualifiedNamesToReplace);
if (qualifiedNamesToReplace.isEmpty()) {
// If none of the changed types are loaded, do nothing.
continue;
@@ -541,6 +548,18 @@
fDeltaCache.clear();
}
+ private void filterUnrelatedResources(JDIDebugTarget target, List<IResource> resourcesToReplace, List<String> qualifiedNamesToReplace) {
+ Iterator<IResource> resources = resourcesToReplace.iterator();
+ Iterator<String> names = qualifiedNamesToReplace.iterator();
+ while (resources.hasNext()) {
+ boolean supported = target.supportsResource(() -> names.next(), resources.next());
+ if (!supported) {
+ resources.remove();
+ names.remove();
+ }
+ }
+ }
+
/**
* Returns whether the given exception, which occurred during HCR, should be
* logged. We anticipate that we can get IncompatibleThreadStateExceptions
diff --git a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/JDIDebugTarget.java b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/JDIDebugTarget.java
index 5c49532..f76bac1 100644
--- a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/JDIDebugTarget.java
+++ b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/JDIDebugTarget.java
@@ -23,6 +23,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicInteger;
import org.eclipse.core.resources.IFile;
@@ -1391,7 +1392,15 @@
return true;
}
- IResource resource = marker.getResource();
+ return supportsResource(() -> jBreakpoint.getTypeName(), marker.getResource());
+ }
+
+
+ public boolean supportsResource(Callable<String> typeNameSupplier, IResource resource) {
+ if (fScope == null) {
+ // No checks, everything in scope: the filtering is disabled
+ return true;
+ }
// Java exception breakpoints have wsp root as resource
if(resource == null || resource == ResourcesPlugin.getWorkspace().getRoot()) {
return true;
@@ -1427,7 +1436,7 @@
// This can be also an incomplete resource mapping.
// Try to see if the type available multiple times in workspace
try {
- String typeName = jBreakpoint.getTypeName();
+ String typeName = typeNameSupplier.call();
if(typeName != null){
Boolean known = knownTypes.get(typeName);
if(known != null){
@@ -1437,7 +1446,8 @@
knownTypes.put(typeName, Boolean.valueOf(supportedBreakpoint));
return supportedBreakpoint;
}
- } catch (CoreException e) {
+ }
+ catch (Exception e) {
logError(e);
}
// we don't know why computation failed, so let assume the breakpoint is supported.