Bug 559742 - Overenthusiastic Repeated exception occurrence

Change-Id: I14d2552b61cc74d0359cd278662d240eb283c312
diff --git a/org.eclipse.jdt.debug.tests/testprograms/ErrorRecurrence.java b/org.eclipse.jdt.debug.tests/testprograms/ErrorRecurrence.java
new file mode 100644
index 0000000..916db7c
--- /dev/null
+++ b/org.eclipse.jdt.debug.tests/testprograms/ErrorRecurrence.java
@@ -0,0 +1,28 @@
+
+public class ErrorRecurrence {
+
+	public static void main(String[] args) {
+		m0(); // L5
+	}
+	static void m0() {
+		try {
+			m1();
+		} finally {
+			System.out.println("finally");
+		} // L12
+	}
+
+	static void m1() {
+		try {
+			m2();
+		} catch (Error e) {
+			System.out.println("caught");
+			throw e; // L20
+		}
+	}
+
+	static void m2() {
+		System.out.println("before throw");
+		throw new Error(); // L26
+	}
+}
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 ea193de..9aa6ac1 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
@@ -205,7 +205,8 @@
 			"org.eclipse.debug.tests.targets.HcrClass5", "org.eclipse.debug.tests.targets.HcrClass6", "org.eclipse.debug.tests.targets.HcrClass7", "org.eclipse.debug.tests.targets.HcrClass8",
 			"org.eclipse.debug.tests.targets.HcrClass9", "TestContributedStepFilterClass", "TerminateAll_01", "TerminateAll_02", "StepResult1",
 			"StepResult2", "StepResult3", "StepUncaught", "TriggerPoint_01", "BulkThreadCreationTest", "MethodExitAndException",
-			"Bug534319earlyStart", "Bug534319lateStart", "Bug534319singleThread", "Bug534319startBetwen", "MethodCall", "Bug538303", "Bug540243", "OutSync", "OutSync2", "ConsoleOutputUmlaut" };
+			"Bug534319earlyStart", "Bug534319lateStart", "Bug534319singleThread", "Bug534319startBetwen", "MethodCall", "Bug538303", "Bug540243",
+			"OutSync", "OutSync2", "ConsoleOutputUmlaut", "ErrorRecurrence" };
 
 	/**
 	 * the default timeout
diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AutomatedSuite.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AutomatedSuite.java
index 8ed5ac9..8b73956 100644
--- a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AutomatedSuite.java
+++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AutomatedSuite.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2019 IBM Corporation and others.
+ * Copyright (c) 2000, 2020 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -39,6 +39,7 @@
 import org.eclipse.jdt.debug.tests.breakpoints.PatternBreakpointTests;
 import org.eclipse.jdt.debug.tests.breakpoints.PreLaunchBreakpointTest;
 import org.eclipse.jdt.debug.tests.breakpoints.RunToLineTests;
+import org.eclipse.jdt.debug.tests.breakpoints.SpecialExceptionBreakpointTests;
 import org.eclipse.jdt.debug.tests.breakpoints.SuspendVMBreakpointsTests;
 import org.eclipse.jdt.debug.tests.breakpoints.TargetPatternBreakpointTests;
 import org.eclipse.jdt.debug.tests.breakpoints.TestToggleBreakpointsTarget;
@@ -343,6 +344,7 @@
 		addTest(new TestSuite(BreakpointWorkingSetTests.class));
 		addTest(new TestSuite(MethodBreakpointTests.class));
 		addTest(new TestSuite(ExceptionBreakpointTests.class));
+		addTest(new TestSuite(SpecialExceptionBreakpointTests.class));
 		addTest(new TestSuite(WatchpointTests.class));
 		addTest(new TestSuite(PatternBreakpointTests.class));
 		addTest(new TestSuite(TargetPatternBreakpointTests.class));
diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/breakpoints/SpecialExceptionBreakpointTests.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/breakpoints/SpecialExceptionBreakpointTests.java
new file mode 100644
index 0000000..dfdcc5a
--- /dev/null
+++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/breakpoints/SpecialExceptionBreakpointTests.java
@@ -0,0 +1,179 @@
+/*******************************************************************************
+ * Copyright (c) 2020 GK Software SE and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     Stephan Herrmann - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jdt.debug.tests.breakpoints;
+
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.runtime.preferences.IEclipsePreferences;
+import org.eclipse.core.runtime.preferences.InstanceScope;
+import org.eclipse.debug.core.DebugException;
+import org.eclipse.debug.core.model.IBreakpoint;
+import org.eclipse.jdt.debug.core.IJavaExceptionBreakpoint;
+import org.eclipse.jdt.debug.core.IJavaExceptionBreakpoint.SuspendOnRecurrenceStrategy;
+import org.eclipse.jdt.debug.core.IJavaStackFrame;
+import org.eclipse.jdt.debug.core.IJavaThread;
+import org.eclipse.jdt.debug.core.JDIDebugModel;
+import org.eclipse.jdt.debug.tests.AbstractDebugTest;
+import org.eclipse.jdt.internal.debug.core.JDIDebugPlugin;
+import org.eclipse.jdt.internal.debug.ui.IJDIPreferencesConstants;
+import org.eclipse.jdt.internal.debug.ui.JDIDebugUIPlugin;
+import org.eclipse.jdt.internal.debug.ui.JavaDebugOptionsManager;
+import org.eclipse.jface.preference.IPreferenceStore;
+
+public class SpecialExceptionBreakpointTests extends AbstractDebugTest {
+
+	private boolean fDefaultSuspendOnUncaught;
+	private boolean fDefaultSuspendOnCompilationErrors;
+	private SuspendOnRecurrenceStrategy fDefaultRecurrenceStrategy;
+
+	public SpecialExceptionBreakpointTests(String name) {
+		super(name);
+	}
+
+	@Override
+	protected void setUp() throws Exception {
+		super.setUp();
+		IPreferenceStore uiPrefStore = JDIDebugUIPlugin.getDefault().getPreferenceStore();
+		fDefaultSuspendOnUncaught = uiPrefStore.getBoolean(IJDIPreferencesConstants.PREF_SUSPEND_ON_UNCAUGHT_EXCEPTIONS);
+		fDefaultSuspendOnCompilationErrors = uiPrefStore.getBoolean(IJDIPreferencesConstants.PREF_SUSPEND_ON_COMPILATION_ERRORS);
+		IEclipsePreferences prefs = InstanceScope.INSTANCE.getNode(JDIDebugPlugin.getUniqueIdentifier());
+		String val = prefs.get(JDIDebugModel.PREF_SUSPEND_ON_RECURRENCE_STRATEGY, SuspendOnRecurrenceStrategy.RECURRENCE_UNCONFIGURED.name());
+		fDefaultRecurrenceStrategy = SuspendOnRecurrenceStrategy.valueOf(val);
+	}
+
+	@Override
+	protected void tearDown() throws Exception {
+		super.tearDown();
+		setPreferences(fDefaultSuspendOnUncaught, fDefaultSuspendOnCompilationErrors, fDefaultRecurrenceStrategy);
+	}
+
+	void setPreferences(boolean suspUncaught, boolean suspCompErr, SuspendOnRecurrenceStrategy recurr) {
+		IPreferenceStore uiPrefStore = JDIDebugUIPlugin.getDefault().getPreferenceStore();
+		uiPrefStore.setValue(IJDIPreferencesConstants.PREF_SUSPEND_ON_UNCAUGHT_EXCEPTIONS, suspUncaught);
+		uiPrefStore.setValue(IJDIPreferencesConstants.PREF_SUSPEND_ON_COMPILATION_ERRORS, suspCompErr);
+		IEclipsePreferences prefs = InstanceScope.INSTANCE.getNode(JDIDebugPlugin.getUniqueIdentifier());
+		prefs.put(JDIDebugModel.PREF_SUSPEND_ON_RECURRENCE_STRATEGY, recurr.name());
+	}
+
+	public void testCaughtException_compErr() throws Exception {
+		caughtException(true);
+	}
+	public void testCaughtException() throws Exception {
+		caughtException(false);
+	}
+	void caughtException(boolean suspendOnCompErr) throws Exception {
+		setPreferences(true, suspendOnCompErr, SuspendOnRecurrenceStrategy.SUSPEND_ALWAYS);
+
+		String typeName = "ErrorRecurrence";
+		IJavaExceptionBreakpoint ex = createExceptionBreakpoint("java.lang.Error", true, true);
+
+		IJavaThread thread = null;
+		try {
+			thread = launchToBreakpoint(typeName);
+			assertNotNull("1. Breakpoint not hit within timeout period", thread);
+			assertEquals("Line of 1. suspend", 26, thread.getTopStackFrame().getLineNumber());
+			assertExceptionBreakpointHit(thread, ex);
+
+			thread = resume(thread);
+			assertExceptionBreakpointHit(thread, ex);
+			assertNotNull("2. Breakpoint not hit within timeout period", thread);
+			assertEquals("Line of 2. suspend", 20, thread.getTopStackFrame().getLineNumber());
+
+			thread = resume(thread);
+			assertExceptionBreakpointHit(thread, ex);
+			assertNotNull("3. Breakpoint not hit within timeout period", thread);
+			assertEquals("Line of 3. suspend", 12, thread.getTopStackFrame().getLineNumber());
+
+			resumeAndExit(thread);
+			ex.delete();
+		} finally {
+			terminateAndRemove(thread);
+			removeAllBreakpoints();
+		}
+	}
+
+	public void testCaughtExceptionSkip_uncaught_compErr() throws Exception {
+		caughtExceptionSkip_uncaught(true);
+	}
+	public void testCaughtExceptionSkip_uncaught() throws Exception {
+		caughtExceptionSkip_uncaught(false);
+	}
+	void caughtExceptionSkip_uncaught(boolean suspendOnCompErr) throws Exception {
+		setPreferences(true, suspendOnCompErr, SuspendOnRecurrenceStrategy.SKIP_RECURRENCES);
+
+		String typeName = "ErrorRecurrence";
+		IJavaExceptionBreakpoint ex = createExceptionBreakpoint("java.lang.Error", true, true);
+
+		IJavaThread thread = null;
+		try {
+			thread = launchToBreakpoint(typeName);
+			assertNotNull("1. Breakpoint not hit within timeout period", thread);
+			assertEquals("Line of 1. suspend", 26, thread.getTopStackFrame().getLineNumber());
+			assertExceptionBreakpointHit(thread, ex);
+
+			// L20 skipped recurrence
+
+			// L12: uncaught:
+			thread = resume(thread);
+			assertExceptionBreakpointHit(thread, ex);
+			assertNotNull("2. Breakpoint not hit within timeout period", thread);
+			assertEquals("Line of 2. suspend", 12, thread.getTopStackFrame().getLineNumber());
+
+			resumeAndExit(thread);
+			ex.delete();
+		} finally {
+			terminateAndRemove(thread);
+			removeAllBreakpoints();
+		}
+	}
+
+	public void testCaughtExceptionSkip_compErr() throws Exception {
+		caughtExceptionSkip(true);
+	}
+	public void testCaughtExceptionSkip() throws Exception {
+		caughtExceptionSkip(false);
+	}
+	void caughtExceptionSkip(boolean suspendOnCompErr) throws Exception {
+		setPreferences(false, suspendOnCompErr, SuspendOnRecurrenceStrategy.SKIP_RECURRENCES);
+
+		String typeName = "ErrorRecurrence";
+		IJavaExceptionBreakpoint ex = createExceptionBreakpoint("java.lang.Error", true, true);
+
+		IJavaThread thread = null;
+		try {
+			thread = launchToBreakpoint(typeName);
+			assertNotNull("1. Breakpoint not hit within timeout period", thread);
+			assertEquals("Line of 1. suspend", 26, thread.getTopStackFrame().getLineNumber());
+			assertExceptionBreakpointHit(thread, ex);
+
+			// L20 skipped recurrence
+
+			// L12 suspend on uncaught disabled
+			resumeAndExit(thread);
+			ex.delete();
+		} finally {
+			terminateAndRemove(thread);
+			removeAllBreakpoints();
+		}
+	}
+
+	private void assertExceptionBreakpointHit(IJavaThread thread, IJavaExceptionBreakpoint ex) throws DebugException {
+		IMarker problem = JavaDebugOptionsManager.getDefault().getProblem((IJavaStackFrame) thread.getTopStackFrame());
+		if (problem != null) {
+			fail("unexpected problem marker "+problem);
+		}
+		IBreakpoint hit = getBreakpoint(thread);
+		assertNotNull("suspended, but not by breakpoint", hit);
+		assertEquals("suspended, but not by expected exception", ex.getExceptionTypeName(), ((IJavaExceptionBreakpoint) hit).getExceptionTypeName());
+	}
+}
diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JavaDebugOptionsManager.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JavaDebugOptionsManager.java
index 63c1ff9..d1fe3b7 100644
--- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JavaDebugOptionsManager.java
+++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JavaDebugOptionsManager.java
@@ -583,6 +583,15 @@
 	@Override
 	public int breakpointHit(IJavaThread thread, IJavaBreakpoint breakpoint) {
 		if (thread instanceof JDIThread && breakpoint instanceof IJavaExceptionBreakpoint) {
+			try {
+				String[] breakpointListeners = breakpoint.getBreakpointListeners();
+				if (breakpointListeners.length == 1
+						&& SuspendOnCompilationErrorListener.ID_COMPILATION_ERROR_LISTENER.equals(breakpointListeners[0])) {
+					return DONT_CARE; // not a user breakpoint
+				}
+			} catch (CoreException e1) {
+				// continue
+			}
 			if (shouldSkipSubsequentOccurrence((JDIThread) thread, (IJavaExceptionBreakpoint) breakpoint)) {
 				return DONT_SUSPEND;
 			}