Skip to content

Commit 73b448d

Browse files
Skip the specified classes when breaking on exception or stepping (#334)
* Skip the specified classes when breaking on exception or stepping Signed-off-by: Jinbo Wang <[email protected]> * remove unused code Signed-off-by: Jinbo Wang <[email protected]> * Address review comments Signed-off-by: Jinbo Wang <[email protected]>
1 parent 2e30239 commit 73b448d

File tree

12 files changed

+518
-33
lines changed

12 files changed

+518
-33
lines changed

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/DebugSession.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,11 @@ public IWatchpoint createWatchPoint(String className, String fieldName, String a
8888

8989
@Override
9090
public void setExceptionBreakpoints(boolean notifyCaught, boolean notifyUncaught) {
91+
setExceptionBreakpoints(notifyCaught, notifyUncaught, null, null);
92+
}
93+
94+
@Override
95+
public void setExceptionBreakpoints(boolean notifyCaught, boolean notifyUncaught, String[] classFilters, String[] classExclusionFilters) {
9196
EventRequestManager manager = vm.eventRequestManager();
9297
ArrayList<ExceptionRequest> legacy = new ArrayList<>(manager.exceptionRequests());
9398
manager.deleteEventRequests(legacy);
@@ -108,6 +113,16 @@ public void setExceptionBreakpoints(boolean notifyCaught, boolean notifyUncaught
108113
// get only the uncaught exceptions
109114
ExceptionRequest request = manager.createExceptionRequest(null, notifyCaught, notifyUncaught);
110115
request.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD);
116+
if (classFilters != null) {
117+
for (String classFilter : classFilters) {
118+
request.addClassFilter(classFilter);
119+
}
120+
}
121+
if (classExclusionFilters != null) {
122+
for (String exclusionFilter : classExclusionFilters) {
123+
request.addClassExclusionFilter(exclusionFilter);
124+
}
125+
}
111126
request.enable();
112127
}
113128
}

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/DebugSettings.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,21 @@
1111

1212
package com.microsoft.java.debug.core;
1313

14+
import java.util.Collections;
15+
import java.util.Set;
16+
import java.util.concurrent.ConcurrentHashMap;
1417
import java.util.logging.Logger;
1518

1619
import com.google.gson.JsonSyntaxException;
1720
import com.google.gson.annotations.SerializedName;
1821
import com.microsoft.java.debug.core.protocol.JsonUtils;
22+
import com.microsoft.java.debug.core.protocol.Requests.ClassFilters;
23+
import com.microsoft.java.debug.core.protocol.Requests.StepFilters;
1924

2025
public final class DebugSettings {
2126
private static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME);
27+
private static Set<IDebugSettingChangeListener> listeners =
28+
Collections.newSetFromMap(new ConcurrentHashMap<IDebugSettingChangeListener, Boolean>());
2229
private static DebugSettings current = new DebugSettings();
2330

2431
public int maxStringLength = 0;
@@ -31,6 +38,9 @@ public final class DebugSettings {
3138
public String logLevel;
3239
public String javaHome;
3340
public HotCodeReplace hotCodeReplace = HotCodeReplace.MANUAL;
41+
public StepFilters stepFilters = new StepFilters();
42+
public ClassFilters exceptionFilters = new ClassFilters();
43+
public boolean exceptionFiltersUpdated = false;
3444

3545
public static DebugSettings getCurrent() {
3646
return current;
@@ -44,7 +54,11 @@ public static DebugSettings getCurrent() {
4454
*/
4555
public void updateSettings(String jsonSettings) {
4656
try {
57+
DebugSettings oldSettings = current;
4758
current = JsonUtils.fromJson(jsonSettings, DebugSettings.class);
59+
for (IDebugSettingChangeListener listener : listeners) {
60+
listener.update(oldSettings, current);
61+
}
4862
} catch (JsonSyntaxException ex) {
4963
logger.severe(String.format("Invalid json for debugSettings: %s, %s", jsonSettings, ex.getMessage()));
5064
}
@@ -54,6 +68,14 @@ private DebugSettings() {
5468

5569
}
5670

71+
public static boolean addDebugSettingChangeListener(IDebugSettingChangeListener listener) {
72+
return listeners.add(listener);
73+
}
74+
75+
public static boolean removeDebugSettingChangeListener(IDebugSettingChangeListener listener) {
76+
return listeners.remove(listener);
77+
}
78+
5779
public static enum HotCodeReplace {
5880
@SerializedName("manual")
5981
MANUAL,
@@ -62,4 +84,8 @@ public static enum HotCodeReplace {
6284
@SerializedName("never")
6385
NEVER
6486
}
87+
88+
public static interface IDebugSettingChangeListener {
89+
public void update(DebugSettings oldSettings, DebugSettings newSettings);
90+
}
6591
}

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/DebugUtility.java

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,21 @@ public static IDebugSession attach(VirtualMachineManager vmManager, String hostN
313313
* @return the new step request.
314314
*/
315315
public static StepRequest createStepOverRequest(ThreadReference thread, String[] stepFilters) {
316-
return createStepRequest(thread, StepRequest.STEP_LINE, StepRequest.STEP_OVER, stepFilters);
316+
return createStepOverRequest(thread, null, stepFilters);
317+
}
318+
319+
/**
320+
* Create a step over request on the specified thread.
321+
* @param thread
322+
* the target thread.
323+
* @param classFilters
324+
* restricts the step event to those matching the given class patterns when stepping.
325+
* @param classExclusionFilters
326+
* restricts the step event to those not matching the given class patterns when stepping.
327+
* @return the new step request.
328+
*/
329+
public static StepRequest createStepOverRequest(ThreadReference thread, String[] classFilters, String[] classExclusionFilters) {
330+
return createStepRequest(thread, StepRequest.STEP_LINE, StepRequest.STEP_OVER, classFilters, classExclusionFilters);
317331
}
318332

319333
/**
@@ -325,7 +339,21 @@ public static StepRequest createStepOverRequest(ThreadReference thread, String[]
325339
* @return the new step request.
326340
*/
327341
public static StepRequest createStepIntoRequest(ThreadReference thread, String[] stepFilters) {
328-
return createStepRequest(thread, StepRequest.STEP_LINE, StepRequest.STEP_INTO, stepFilters);
342+
return createStepIntoRequest(thread, null, stepFilters);
343+
}
344+
345+
/**
346+
* Create a step into request on the specified thread.
347+
* @param thread
348+
* the target thread.
349+
* @param classFilters
350+
* restricts the step event to those matching the given class patterns when stepping.
351+
* @param classExclusionFilters
352+
* restricts the step event to those not matching the given class patterns when stepping.
353+
* @return the new step request.
354+
*/
355+
public static StepRequest createStepIntoRequest(ThreadReference thread, String[] classFilters, String[] classExclusionFilters) {
356+
return createStepRequest(thread, StepRequest.STEP_LINE, StepRequest.STEP_INTO, classFilters, classExclusionFilters);
329357
}
330358

331359
/**
@@ -337,14 +365,33 @@ public static StepRequest createStepIntoRequest(ThreadReference thread, String[]
337365
* @return the new step request.
338366
*/
339367
public static StepRequest createStepOutRequest(ThreadReference thread, String[] stepFilters) {
340-
return createStepRequest(thread, StepRequest.STEP_LINE, StepRequest.STEP_OUT, stepFilters);
368+
return createStepOutRequest(thread, null, stepFilters);
341369
}
342370

343-
private static StepRequest createStepRequest(ThreadReference thread, int stepSize, int stepDepth, String[] stepFilters) {
371+
/**
372+
* Create a step out request on the specified thread.
373+
* @param thread
374+
* the target thread.
375+
* @param classFilters
376+
* restricts the step event to those matching the given class patterns when stepping.
377+
* @param classExclusionFilters
378+
* restricts the step event to those not matching the given class patterns when stepping.
379+
* @return the new step request.
380+
*/
381+
public static StepRequest createStepOutRequest(ThreadReference thread, String[] classFilters, String[] classExclusionFilters) {
382+
return createStepRequest(thread, StepRequest.STEP_LINE, StepRequest.STEP_OUT, classFilters, classExclusionFilters);
383+
}
384+
385+
private static StepRequest createStepRequest(ThreadReference thread, int stepSize, int stepDepth, String[] classFilters, String[] classExclusionFilters) {
344386
StepRequest request = thread.virtualMachine().eventRequestManager().createStepRequest(thread, stepSize, stepDepth);
345-
if (stepFilters != null) {
346-
for (String stepFilter : stepFilters) {
347-
request.addClassExclusionFilter(stepFilter);
387+
if (classFilters != null) {
388+
for (String classFilter : classFilters) {
389+
request.addClassFilter(classFilter);
390+
}
391+
}
392+
if (classExclusionFilters != null) {
393+
for (String exclusionFilter : classExclusionFilters) {
394+
request.addClassExclusionFilter(exclusionFilter);
348395
}
349396
}
350397
request.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD);

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/IDebugSession.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ public interface IDebugSession {
3434

3535
void setExceptionBreakpoints(boolean notifyCaught, boolean notifyUncaught);
3636

37+
void setExceptionBreakpoints(boolean notifyCaught, boolean notifyUncaught, String[] classFilters, String[] classExclusionFilters);
38+
3739
// TODO: createFunctionBreakpoint
3840

3941
Process process();

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/DebugAdapterContext.java

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,24 @@
1313

1414
import java.nio.charset.Charset;
1515
import java.nio.file.Path;
16+
import java.util.Arrays;
1617
import java.util.Collections;
18+
import java.util.LinkedHashSet;
1719
import java.util.Map;
20+
import java.util.Set;
1821

22+
import com.microsoft.java.debug.core.DebugSettings;
1923
import com.microsoft.java.debug.core.IDebugSession;
2024
import com.microsoft.java.debug.core.adapter.variables.IVariableFormatter;
2125
import com.microsoft.java.debug.core.adapter.variables.VariableFormatterFactory;
2226
import com.microsoft.java.debug.core.protocol.IProtocolServer;
2327
import com.microsoft.java.debug.core.protocol.Requests.StepFilters;
2428

29+
import org.apache.commons.lang3.ArrayUtils;
30+
2531
public class DebugAdapterContext implements IDebugAdapterContext {
2632
private static final int MAX_CACHE_ITEMS = 10000;
33+
private final StepFilters defaultFilters = new StepFilters();
2734
private Map<String, String> sourceMappingCache = Collections.synchronizedMap(new LRUCache<>(MAX_CACHE_ITEMS));
2835
private IProviderContext providerContext;
2936
private IProtocolServer server;
@@ -235,12 +242,28 @@ public String getMainClass() {
235242

236243
@Override
237244
public void setStepFilters(StepFilters stepFilters) {
245+
// For backward compatibility, merge the classNameFilters to skipClasses.
246+
if (stepFilters != null && ArrayUtils.isNotEmpty(stepFilters.classNameFilters)) {
247+
Set<String> patterns = new LinkedHashSet<>();
248+
if (ArrayUtils.isNotEmpty(stepFilters.skipClasses)) {
249+
patterns.addAll(Arrays.asList(stepFilters.skipClasses));
250+
}
251+
252+
patterns.addAll(Arrays.asList(stepFilters.classNameFilters));
253+
stepFilters.skipClasses = patterns.toArray(new String[0]);
254+
}
238255
this.stepFilters = stepFilters;
239256
}
240257

241258
@Override
242259
public StepFilters getStepFilters() {
243-
return stepFilters;
260+
if (stepFilters != null) {
261+
return stepFilters;
262+
} else if (DebugSettings.getCurrent().stepFilters != null) {
263+
return DebugSettings.getCurrent().stepFilters;
264+
}
265+
266+
return defaultFilters;
244267
}
245268

246269
@Override

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/RestartFrameHandler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ private void popStackFrames(IDebugAdapterContext context, ThreadReference thread
102102
}
103103

104104
private void stepInto(IDebugAdapterContext context, ThreadReference thread) {
105-
StepRequest request = DebugUtility.createStepIntoRequest(thread, context.getStepFilters().classNameFilters);
105+
StepRequest request = DebugUtility.createStepIntoRequest(thread, context.getStepFilters().allowClasses, context.getStepFilters().skipClasses);
106106
context.getDebugSession().getEventHub().stepEvents().filter(debugEvent -> request.equals(debugEvent.event.request())).take(1).subscribe(debugEvent -> {
107107
debugEvent.shouldResume = false;
108108
// Have to send two events to keep the UI sync with the step in operations:

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetExceptionBreakpointsRequestHandler.java

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,35 +17,56 @@
1717

1818
import org.apache.commons.lang3.ArrayUtils;
1919

20+
import com.microsoft.java.debug.core.DebugSettings;
21+
import com.microsoft.java.debug.core.IDebugSession;
22+
import com.microsoft.java.debug.core.DebugSettings.IDebugSettingChangeListener;
2023
import com.microsoft.java.debug.core.adapter.AdapterUtils;
2124
import com.microsoft.java.debug.core.adapter.ErrorCode;
2225
import com.microsoft.java.debug.core.adapter.IDebugAdapterContext;
2326
import com.microsoft.java.debug.core.adapter.IDebugRequestHandler;
2427
import com.microsoft.java.debug.core.protocol.Messages.Response;
2528
import com.microsoft.java.debug.core.protocol.Requests.Arguments;
29+
import com.microsoft.java.debug.core.protocol.Requests.ClassFilters;
2630
import com.microsoft.java.debug.core.protocol.Requests.Command;
2731
import com.microsoft.java.debug.core.protocol.Requests.SetExceptionBreakpointsArguments;
2832
import com.microsoft.java.debug.core.protocol.Types;
33+
import com.sun.jdi.event.VMDeathEvent;
34+
import com.sun.jdi.event.VMDisconnectEvent;
2935

30-
public class SetExceptionBreakpointsRequestHandler implements IDebugRequestHandler {
36+
public class SetExceptionBreakpointsRequestHandler implements IDebugRequestHandler, IDebugSettingChangeListener {
37+
private IDebugSession debugSession = null;
38+
private boolean isInitialized = false;
39+
private boolean notifyCaught = false;
40+
private boolean notifyUncaught = false;
3141

3242
@Override
3343
public List<Command> getTargetCommands() {
3444
return Arrays.asList(Command.SETEXCEPTIONBREAKPOINTS);
3545
}
3646

3747
@Override
38-
public CompletableFuture<Response> handle(Command command, Arguments arguments, Response response, IDebugAdapterContext context) {
48+
public synchronized CompletableFuture<Response> handle(Command command, Arguments arguments, Response response, IDebugAdapterContext context) {
3949
if (context.getDebugSession() == null) {
4050
return AdapterUtils.createAsyncErrorResponse(response, ErrorCode.EMPTY_DEBUG_SESSION, "Empty debug session.");
4151
}
4252

53+
if (!isInitialized) {
54+
isInitialized = true;
55+
debugSession = context.getDebugSession();
56+
DebugSettings.addDebugSettingChangeListener(this);
57+
debugSession.getEventHub().events().subscribe(debugEvent -> {
58+
if (debugEvent.event instanceof VMDeathEvent
59+
|| debugEvent.event instanceof VMDisconnectEvent) {
60+
DebugSettings.removeDebugSettingChangeListener(this);
61+
}
62+
});
63+
}
64+
4365
String[] filters = ((SetExceptionBreakpointsArguments) arguments).filters;
4466
try {
45-
boolean notifyCaught = ArrayUtils.contains(filters, Types.ExceptionBreakpointFilter.CAUGHT_EXCEPTION_FILTER_NAME);
46-
boolean notifyUncaught = ArrayUtils.contains(filters, Types.ExceptionBreakpointFilter.UNCAUGHT_EXCEPTION_FILTER_NAME);
47-
48-
context.getDebugSession().setExceptionBreakpoints(notifyCaught, notifyUncaught);
67+
this.notifyCaught = ArrayUtils.contains(filters, Types.ExceptionBreakpointFilter.CAUGHT_EXCEPTION_FILTER_NAME);
68+
this.notifyUncaught = ArrayUtils.contains(filters, Types.ExceptionBreakpointFilter.UNCAUGHT_EXCEPTION_FILTER_NAME);
69+
setExceptionBreakpoints(context.getDebugSession(), this.notifyCaught, this.notifyUncaught);
4970
return CompletableFuture.completedFuture(response);
5071
} catch (Exception ex) {
5172
throw AdapterUtils.createCompletionException(
@@ -55,4 +76,21 @@ public CompletableFuture<Response> handle(Command command, Arguments arguments,
5576
}
5677
}
5778

79+
private void setExceptionBreakpoints(IDebugSession debugSession, boolean notifyCaught, boolean notifyUncaught) {
80+
ClassFilters exceptionFilters = DebugSettings.getCurrent().exceptionFilters;
81+
String[] classFilters = (exceptionFilters == null ? null : exceptionFilters.allowClasses);
82+
String[] classExclusionFilters = (exceptionFilters == null ? null : exceptionFilters.skipClasses);
83+
debugSession.setExceptionBreakpoints(notifyCaught, notifyUncaught, classFilters, classExclusionFilters);
84+
}
85+
86+
@Override
87+
public synchronized void update(DebugSettings oldSettings, DebugSettings newSettings) {
88+
try {
89+
if (newSettings != null && newSettings.exceptionFiltersUpdated) {
90+
setExceptionBreakpoints(debugSession, notifyCaught, notifyUncaught);
91+
}
92+
} catch (Exception ex) {
93+
DebugSettings.removeDebugSettingChangeListener(this);
94+
}
95+
}
5896
}

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/StepRequestHandler.java

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,14 @@ public CompletableFuture<Response> handle(Command command, Arguments arguments,
7676

7777
if (command == Command.STEPIN) {
7878
threadState.pendingStepRequest = DebugUtility.createStepIntoRequest(thread,
79-
context.getStepFilters().classNameFilters);
79+
context.getStepFilters().allowClasses,
80+
context.getStepFilters().skipClasses);
8081
} else if (command == Command.STEPOUT) {
8182
threadState.pendingStepRequest = DebugUtility.createStepOutRequest(thread,
82-
context.getStepFilters().classNameFilters);
83+
context.getStepFilters().allowClasses,
84+
context.getStepFilters().skipClasses);
8385
} else {
84-
threadState.pendingStepRequest = DebugUtility.createStepOverRequest(thread,
85-
context.getStepFilters().classNameFilters);
86+
threadState.pendingStepRequest = DebugUtility.createStepOverRequest(thread, null);
8687
}
8788
threadState.pendingStepRequest.enable();
8889
DebugUtility.resumeThread(thread);
@@ -133,21 +134,14 @@ private void handleDebugEvent(DebugEvent debugEvent, IDebugSession debugSession,
133134
if (threadState.pendingStepType == Command.STEPIN) {
134135
int currentStackDepth = thread.frameCount();
135136
Location currentStepLocation = getTopFrame(thread).location();
136-
// Check if the step into operation stepped through the filtered code and stopped at an un-filtered location.
137-
if (threadState.stackDepth + 1 < thread.frameCount()) {
138-
// Create another stepOut request to return back where we started the step into.
139-
threadState.pendingStepRequest = DebugUtility.createStepOutRequest(thread,
140-
context.getStepFilters().classNameFilters);
141-
threadState.pendingStepRequest.enable();
142-
debugEvent.shouldResume = true;
143-
return;
144-
}
137+
145138
// If the ending step location is filtered, or same as the original location where the step into operation is originated,
146139
// do another step of the same kind.
147140
if (shouldFilterLocation(threadState.stepLocation, currentStepLocation, context)
148141
|| shouldDoExtraStepInto(threadState.stackDepth, threadState.stepLocation, currentStackDepth, currentStepLocation)) {
149142
threadState.pendingStepRequest = DebugUtility.createStepIntoRequest(thread,
150-
context.getStepFilters().classNameFilters);
143+
context.getStepFilters().allowClasses,
144+
context.getStepFilters().skipClasses);
151145
threadState.pendingStepRequest.enable();
152146
debugEvent.shouldResume = true;
153147
return;
@@ -169,7 +163,8 @@ private boolean isStepFiltersConfigured(StepFilters filters) {
169163
if (filters == null) {
170164
return false;
171165
}
172-
return ArrayUtils.isNotEmpty(filters.classNameFilters) || filters.skipConstructors
166+
return ArrayUtils.isNotEmpty(filters.allowClasses) || ArrayUtils.isNotEmpty(filters.skipClasses)
167+
|| ArrayUtils.isNotEmpty(filters.classNameFilters) || filters.skipConstructors
173168
|| filters.skipStaticInitializers || filters.skipSynthetics;
174169
}
175170

0 commit comments

Comments
 (0)