From faa0004a1aa24996707d3905be059e5c9af3bab5 Mon Sep 17 00:00:00 2001 From: Jinbo Wang Date: Fri, 1 Nov 2019 16:22:35 +0800 Subject: [PATCH 1/3] Support Data Breakpoint Signed-off-by: Jinbo Wang --- .../java/debug/core/DebugSession.java | 5 + .../java/debug/core/IDebugSession.java | 2 + .../debug/core/IEvaluatableBreakpoint.java | 10 +- .../java/debug/core/IWatchpoint.java | 36 +++ .../microsoft/java/debug/core/Watchpoint.java | 247 ++++++++++++++++++ .../debug/core/adapter/BreakpointManager.java | 102 +++++--- .../java/debug/core/adapter/DebugAdapter.java | 4 + .../core/adapter/DebugAdapterContext.java | 6 + .../core/adapter/IBreakpointManager.java | 72 +++++ .../core/adapter/IDebugAdapterContext.java | 2 + .../DataBreakpointInfoRequestHandler.java | 72 +++++ .../handler/InitializeRequestHandler.java | 1 + .../handler/SetBreakpointsRequestHandler.java | 25 +- .../SetDataBreakpointsRequestHandler.java | 165 ++++++++++++ .../java/debug/core/protocol/Requests.java | 21 ++ .../java/debug/core/protocol/Responses.java | 51 ++++ .../java/debug/core/protocol/Types.java | 81 ++++++ 17 files changed, 856 insertions(+), 46 deletions(-) create mode 100644 com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/IWatchpoint.java create mode 100644 com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/Watchpoint.java create mode 100644 com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IBreakpointManager.java create mode 100644 com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/DataBreakpointInfoRequestHandler.java create mode 100644 com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetDataBreakpointsRequestHandler.java diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/DebugSession.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/DebugSession.java index 289d36c76..a67afd221 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/DebugSession.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/DebugSession.java @@ -81,6 +81,11 @@ public IBreakpoint createBreakpoint(String className, int lineNumber, int hitCou return new EvaluatableBreakpoint(vm, this.getEventHub(), className, lineNumber, hitCount, condition, logMessage); } + @Override + public IWatchpoint createWatchPoint(String className, String fieldName, String accessType, String condition, int hitCount) { + return new Watchpoint(vm, this.getEventHub(), className, fieldName, accessType, condition, hitCount); + } + @Override public void setExceptionBreakpoints(boolean notifyCaught, boolean notifyUncaught) { EventRequestManager manager = vm.eventRequestManager(); diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/IDebugSession.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/IDebugSession.java index 46cdffc66..ec09aff29 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/IDebugSession.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/IDebugSession.java @@ -30,6 +30,8 @@ public interface IDebugSession { // breakpoints IBreakpoint createBreakpoint(String className, int lineNumber, int hitCount, String condition, String logMessage); + IWatchpoint createWatchPoint(String className, String fieldName, String accessType, String condition, int hitCount); + void setExceptionBreakpoints(boolean notifyCaught, boolean notifyUncaught); // TODO: createFunctionBreakpoint diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/IEvaluatableBreakpoint.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/IEvaluatableBreakpoint.java index 12150563f..6d465b7c2 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/IEvaluatableBreakpoint.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/IEvaluatableBreakpoint.java @@ -11,13 +11,21 @@ package com.microsoft.java.debug.core; -public interface IEvaluatableBreakpoint extends IBreakpoint { +public interface IEvaluatableBreakpoint { boolean containsEvaluatableExpression(); boolean containsConditionalExpression(); boolean containsLogpointExpression(); + String getCondition(); + + void setCondition(String condition); + + String getLogMessage(); + + void setLogMessage(String logMessage); + void setCompiledConditionalExpression(Object compiledExpression); Object getCompiledConditionalExpression(); diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/IWatchpoint.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/IWatchpoint.java new file mode 100644 index 000000000..a3a29a851 --- /dev/null +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/IWatchpoint.java @@ -0,0 +1,36 @@ +/******************************************************************************* +* Copyright (c) 2019 Microsoft 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: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core; + +import java.util.concurrent.CompletableFuture; + +public interface IWatchpoint extends IDebugResource { + String className(); + + String fieldName(); + + String accessType(); + + CompletableFuture install(); + + void putProperty(Object key, Object value); + + Object getProperty(Object key); + + int getHitCount(); + + void setHitCount(int hitCount); + + String getCondition(); + + void setCondition(String condition); +} diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/Watchpoint.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/Watchpoint.java new file mode 100644 index 000000000..cc44d070d --- /dev/null +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/Watchpoint.java @@ -0,0 +1,247 @@ +/******************************************************************************* +* Copyright (c) 2019 Microsoft 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: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import org.apache.commons.lang3.StringUtils; + +import com.sun.jdi.Field; +import com.sun.jdi.ReferenceType; +import com.sun.jdi.VMDisconnectedException; +import com.sun.jdi.VirtualMachine; +import com.sun.jdi.event.ClassPrepareEvent; +import com.sun.jdi.request.ClassPrepareRequest; +import com.sun.jdi.request.EventRequest; +import com.sun.jdi.request.WatchpointRequest; + +import io.reactivex.Observable; +import io.reactivex.disposables.Disposable; + +public class Watchpoint implements IWatchpoint, IEvaluatableBreakpoint { + private VirtualMachine vm = null; + private IEventHub eventHub = null; + private String className = null; + private String fieldName = null; + private String accessType = null; + private String condition = null; + private int hitCount; + private HashMap propertyMap = new HashMap<>(); + private Object compiledConditionalExpression = null; + + // IDebugResource + private List requests = new ArrayList<>(); + private List subscriptions = new ArrayList<>(); + + Watchpoint(VirtualMachine vm, IEventHub eventHub, String className, String fieldName) { + this(vm, eventHub, className, fieldName, "write"); + } + + Watchpoint(VirtualMachine vm, IEventHub eventHub, String className, String fieldName, String accessType) { + this(vm, eventHub, className, fieldName, accessType, null, 0); + } + + Watchpoint(VirtualMachine vm, IEventHub eventHub, String className, String fieldName, String accessType, String condition, int hitCount) { + this.vm = vm; + this.eventHub = eventHub; + this.className = className; + this.fieldName = fieldName; + this.accessType = accessType; + this.condition = condition; + this.hitCount = hitCount; + } + + @Override + public List requests() { + return requests; + } + + @Override + public List subscriptions() { + return subscriptions; + } + + @Override + public void close() throws Exception { + try { + vm.eventRequestManager().deleteEventRequests(requests()); + } catch (VMDisconnectedException ex) { + // ignore since removing breakpoints is meaningless when JVM is terminated. + } + subscriptions().forEach(subscription -> { + subscription.dispose(); + }); + requests.clear(); + subscriptions.clear(); + } + + @Override + public String className() { + return className; + } + + @Override + public String fieldName() { + return fieldName; + } + + @Override + public String accessType() { + return accessType; + } + + @Override + public String getCondition() { + return condition; + } + + @Override + public void setCondition(String condition) { + this.condition = condition; + setCompiledConditionalExpression(null); + } + + @Override + public int getHitCount() { + return hitCount; + } + + @Override + public void setHitCount(int hitCount) { + this.hitCount = hitCount; + + Observable.fromIterable(this.requests()) + .filter(request -> request instanceof WatchpointRequest) + .subscribe(request -> { + request.addCountFilter(hitCount); + request.enable(); + }); + } + + @Override + public void putProperty(Object key, Object value) { + propertyMap.put(key, value); + } + + @Override + public Object getProperty(Object key) { + return propertyMap.get(key); + } + + @Override + public CompletableFuture install() { + // It's possible that different class loaders create new class with the same name. + // Here to listen to future class prepare events to handle such case. + ClassPrepareRequest classPrepareRequest = vm.eventRequestManager().createClassPrepareRequest(); + classPrepareRequest.addClassFilter(className); + classPrepareRequest.enable(); + requests.add(classPrepareRequest); + + CompletableFuture future = new CompletableFuture<>(); + Disposable subscription = eventHub.events() + .filter(debugEvent -> debugEvent.event instanceof ClassPrepareEvent && (classPrepareRequest.equals(debugEvent.event.request()))) + .subscribe(debugEvent -> { + ClassPrepareEvent event = (ClassPrepareEvent) debugEvent.event; + List watchpointRequests = createWatchpointRequests(event.referenceType()); + requests.addAll(watchpointRequests); + if (!watchpointRequests.isEmpty() && !future.isDone()) { + this.putProperty("verified", true); + future.complete(this); + } + }); + subscriptions.add(subscription); + + List watchpointRequests = new ArrayList<>(); + List types = vm.classesByName(className); + for (ReferenceType type : types) { + watchpointRequests.addAll(createWatchpointRequests(type)); + } + + requests.addAll(watchpointRequests); + if (!watchpointRequests.isEmpty() && !future.isDone()) { + this.putProperty("verified", true); + future.complete(this); + } + + return future; + } + + private List createWatchpointRequests(ReferenceType type) { + List watchpointRequests = new ArrayList<>(); + Field field = type.fieldByName(fieldName); + if (field != null) { + if ("read".equals(accessType)) { + watchpointRequests.add(vm.eventRequestManager().createAccessWatchpointRequest(field)); + } else if ("readWrite".equals(accessType)) { + watchpointRequests.add(vm.eventRequestManager().createAccessWatchpointRequest(field)); + watchpointRequests.add(vm.eventRequestManager().createModificationWatchpointRequest(field)); + } else { + watchpointRequests.add(vm.eventRequestManager().createModificationWatchpointRequest(field)); + } + } + + watchpointRequests.forEach(request -> { + request.setSuspendPolicy(WatchpointRequest.SUSPEND_EVENT_THREAD); + if (hitCount > 0) { + request.addCountFilter(hitCount); + } + request.enable(); + }); + return watchpointRequests; + } + + @Override + public String getLogMessage() { + return null; + } + + @Override + public void setLogMessage(String logMessage) { + // do nothing + } + + @Override + public boolean containsEvaluatableExpression() { + return containsConditionalExpression(); + } + + @Override + public boolean containsConditionalExpression() { + return StringUtils.isNotBlank(getCondition()); + } + + @Override + public boolean containsLogpointExpression() { + return false; + } + + public void setCompiledConditionalExpression(Object compiledExpression) { + this.compiledConditionalExpression = compiledExpression; + } + + public Object getCompiledConditionalExpression() { + return compiledConditionalExpression; + } + + @Override + public void setCompiledLogpointExpression(Object compiledExpression) { + // do nothing + } + + @Override + public Object getCompiledLogpointExpression() { + return null; + } +} diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/BreakpointManager.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/BreakpointManager.java index 59cacd1d0..af094501b 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/BreakpointManager.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/BreakpointManager.java @@ -1,5 +1,5 @@ /******************************************************************************* -* Copyright (c) 2017 Microsoft Corporation and others. +* Copyright (c) 2017-2019 Microsoft 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 @@ -14,22 +14,27 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import java.util.logging.Logger; import com.microsoft.java.debug.core.Configuration; import com.microsoft.java.debug.core.IBreakpoint; +import com.microsoft.java.debug.core.IWatchpoint; -public class BreakpointManager { +public class BreakpointManager implements IBreakpointManager { private static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME); /** * A collection of breakpoints registered with this manager. */ private List breakpoints; - private HashMap> sourceToBreakpoints; + private Map> sourceToBreakpoints; + private Map watchpoints; private AtomicInteger nextBreakpointId = new AtomicInteger(1); /** @@ -38,33 +43,15 @@ public class BreakpointManager { public BreakpointManager() { this.breakpoints = Collections.synchronizedList(new ArrayList<>(5)); this.sourceToBreakpoints = new HashMap<>(); + this.watchpoints = new HashMap<>(); } - /** - * Adds breakpoints to breakpoint manager. - * Deletes all breakpoints that are no longer listed. - * @param source - * source path of breakpoints - * @param breakpoints - * full list of breakpoints that locates in this source file - * @return the full breakpoint list that locates in the source file - */ + @Override public IBreakpoint[] setBreakpoints(String source, IBreakpoint[] breakpoints) { return setBreakpoints(source, breakpoints, false); } - /** - * Adds breakpoints to breakpoint manager. - * Deletes all breakpoints that are no longer listed. - * In the case of modified source, delete everything. - * @param source - * source path of breakpoints - * @param breakpoints - * full list of breakpoints that locates in this source file - * @param sourceModified - * the source file are modified or not. - * @return the full breakpoint list that locates in the source file - */ + @Override public IBreakpoint[] setBreakpoints(String source, IBreakpoint[] breakpoints, boolean sourceModified) { List result = new ArrayList<>(); HashMap breakpointMap = this.sourceToBreakpoints.get(source); @@ -151,13 +138,12 @@ private void removeBreakpointsInternally(String source, IBreakpoint[] breakpoint } } + @Override public IBreakpoint[] getBreakpoints() { return this.breakpoints.toArray(new IBreakpoint[0]); } - /** - * Gets the registered breakpoints at the source file. - */ + @Override public IBreakpoint[] getBreakpoints(String source) { HashMap breakpointMap = this.sourceToBreakpoints.get(source); if (breakpointMap == null) { @@ -166,12 +152,60 @@ public IBreakpoint[] getBreakpoints(String source) { return breakpointMap.values().toArray(new IBreakpoint[0]); } - /** - * Cleanup all breakpoints and reset the breakpoint id counter. - */ - public void reset() { - this.sourceToBreakpoints.clear(); - this.breakpoints.clear(); - this.nextBreakpointId.set(1); + @Override + public IWatchpoint[] setWatchpoints(IWatchpoint[] changedWatchpoints) { + List result = new ArrayList<>(); + List toAdds = new ArrayList<>(); + List toRemoves = new ArrayList<>(); + + Set reused = new HashSet<>(); + for (IWatchpoint change : changedWatchpoints) { + if (change == null) { + result.add(change); + continue; + } + + String key = getWatchpointKey(change); + IWatchpoint cache = watchpoints.get(key); + if (cache != null && Objects.equals(cache.accessType(), change.accessType())) { + reused.add(key); + result.add(cache); + } else { + toAdds.add(change); + result.add(change); + } + } + + for (IWatchpoint cache : watchpoints.values()) { + if (!reused.contains(getWatchpointKey(cache))) { + toRemoves.add(cache); + } + } + + for (IWatchpoint toRemove : toRemoves) { + try { + // Destroy the watch point on the debugee VM. + toRemove.close(); + this.watchpoints.remove(getWatchpointKey(toRemove)); + } catch (Exception e) { + logger.log(Level.SEVERE, String.format("Remove the watch point exception: %s", e.toString()), e); + } + } + + for (IWatchpoint toAdd : toAdds) { + toAdd.putProperty("id", this.nextBreakpointId.getAndIncrement()); + this.watchpoints.put(getWatchpointKey(toAdd), toAdd); + } + + return result.toArray(new IWatchpoint[0]); + } + + private String getWatchpointKey(IWatchpoint watchpoint) { + return watchpoint.className() + "#" + watchpoint.fieldName(); + } + + @Override + public IWatchpoint[] getWatchpoints() { + return this.watchpoints.values().stream().filter(wp -> wp != null).toArray(IWatchpoint[]::new); } } diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/DebugAdapter.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/DebugAdapter.java index 78f7d5b63..798200e97 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/DebugAdapter.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/DebugAdapter.java @@ -23,6 +23,7 @@ import com.microsoft.java.debug.core.adapter.handler.AttachRequestHandler; import com.microsoft.java.debug.core.adapter.handler.CompletionsHandler; import com.microsoft.java.debug.core.adapter.handler.ConfigurationDoneRequestHandler; +import com.microsoft.java.debug.core.adapter.handler.DataBreakpointInfoRequestHandler; import com.microsoft.java.debug.core.adapter.handler.DisconnectRequestHandler; import com.microsoft.java.debug.core.adapter.handler.DisconnectRequestWithoutDebuggingHandler; import com.microsoft.java.debug.core.adapter.handler.EvaluateRequestHandler; @@ -33,6 +34,7 @@ import com.microsoft.java.debug.core.adapter.handler.RestartFrameHandler; import com.microsoft.java.debug.core.adapter.handler.ScopesRequestHandler; import com.microsoft.java.debug.core.adapter.handler.SetBreakpointsRequestHandler; +import com.microsoft.java.debug.core.adapter.handler.SetDataBreakpointsRequestHandler; import com.microsoft.java.debug.core.adapter.handler.SetExceptionBreakpointsRequestHandler; import com.microsoft.java.debug.core.adapter.handler.SetVariableRequestHandler; import com.microsoft.java.debug.core.adapter.handler.SourceRequestHandler; @@ -117,6 +119,8 @@ private void initialize() { registerHandlerForDebug(new RestartFrameHandler()); registerHandlerForDebug(new CompletionsHandler()); registerHandlerForDebug(new ExceptionInfoRequestHandler()); + registerHandlerForDebug(new DataBreakpointInfoRequestHandler()); + registerHandlerForDebug(new SetDataBreakpointsRequestHandler()); // NO_DEBUG mode only registerHandlerForNoDebug(new DisconnectRequestWithoutDebuggingHandler()); diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/DebugAdapterContext.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/DebugAdapterContext.java index 646ce01c5..bd29f9946 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/DebugAdapterContext.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/DebugAdapterContext.java @@ -53,6 +53,7 @@ public class DebugAdapterContext implements IDebugAdapterContext { private IStackFrameManager stackFrameManager = new StackFrameManager(); private IExceptionManager exceptionManager = new ExceptionManager(); + private IBreakpointManager breakpointManager = new BreakpointManager(); public DebugAdapterContext(IProtocolServer server, IProviderContext providerContext) { this.providerContext = providerContext; @@ -291,4 +292,9 @@ public Path getArgsfile() { public IExceptionManager getExceptionManager() { return this.exceptionManager; } + + @Override + public IBreakpointManager getBreakpointManager() { + return breakpointManager; + } } diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IBreakpointManager.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IBreakpointManager.java new file mode 100644 index 000000000..9ba609221 --- /dev/null +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IBreakpointManager.java @@ -0,0 +1,72 @@ +/******************************************************************************* +* Copyright (c) 2019 Microsoft 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: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter; + +import com.microsoft.java.debug.core.IBreakpoint; +import com.microsoft.java.debug.core.IWatchpoint; + +public interface IBreakpointManager { + + /** + * Update the breakpoints associated with the source file. + * + * @see #setBreakpoints(String, IBreakpoint[], boolean) + * @param source + * source path of breakpoints + * @param breakpoints + * full list of breakpoints that locates in this source file + * @return the full breakpoint list that locates in the source file + */ + IBreakpoint[] setBreakpoints(String source, IBreakpoint[] breakpoints); + + /** + * Update the breakpoints associated with the source file. If the requested breakpoints already registered in the breakpoint manager, + * reuse the cached one. Otherwise register the requested breakpoint as a new breakpoint. Besides, delete those not existed any more. + * + *

If the source file is modified, delete all cached breakpoints associated the file first and re-register the new breakpoints.

+ * + * @param source + * source path of breakpoints + * @param breakpoints + * full list of breakpoints that locates in this source file + * @param sourceModified + * the source file is modified or not. + * @return the full breakpoint list that locates in the source file + */ + IBreakpoint[] setBreakpoints(String source, IBreakpoint[] breakpoints, boolean sourceModified); + + /** + * Update the watchpoint list. If the requested watchpoint already registered in the breakpoint manager, + * reuse the cached one. Otherwise register the requested watchpoint as a new watchpoint. + * Besides, delete those not existed any more. + * + * @param watchpoints + * the watchpoints requested by client + * @return the full registered watchpoints list + */ + IWatchpoint[] setWatchpoints(IWatchpoint[] watchpoints); + + /** + * Returns all registered breakpoints. + */ + IBreakpoint[] getBreakpoints(); + + /** + * Returns the registered breakpoints at the source file. + */ + IBreakpoint[] getBreakpoints(String source); + + /** + * Returns all registered watchpoints. + */ + IWatchpoint[] getWatchpoints(); +} diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IDebugAdapterContext.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IDebugAdapterContext.java index 27837808b..8f7055eee 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IDebugAdapterContext.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IDebugAdapterContext.java @@ -124,4 +124,6 @@ public interface IDebugAdapterContext { Path getArgsfile(); IExceptionManager getExceptionManager(); + + IBreakpointManager getBreakpointManager(); } diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/DataBreakpointInfoRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/DataBreakpointInfoRequestHandler.java new file mode 100644 index 000000000..2d9b6c30d --- /dev/null +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/DataBreakpointInfoRequestHandler.java @@ -0,0 +1,72 @@ +/******************************************************************************* +* Copyright (c) 2019 Microsoft 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: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.handler; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import org.apache.commons.lang3.StringUtils; + +import com.microsoft.java.debug.core.adapter.IDebugAdapterContext; +import com.microsoft.java.debug.core.adapter.IDebugRequestHandler; +import com.microsoft.java.debug.core.adapter.variables.StackFrameReference; +import com.microsoft.java.debug.core.adapter.variables.VariableProxy; +import com.microsoft.java.debug.core.protocol.Messages.Response; +import com.microsoft.java.debug.core.protocol.Requests.Arguments; +import com.microsoft.java.debug.core.protocol.Requests.Command; +import com.microsoft.java.debug.core.protocol.Requests.DataBreakpointInfoArguments; +import com.microsoft.java.debug.core.protocol.Responses.DataBreakpointInfoResponseBody; +import com.microsoft.java.debug.core.protocol.Types.DataBreakpointAccessType; +import com.sun.jdi.Field; +import com.sun.jdi.ObjectReference; +import com.sun.jdi.ReferenceType; + +public class DataBreakpointInfoRequestHandler implements IDebugRequestHandler { + + @Override + public List getTargetCommands() { + return Arrays.asList(Command.DATABREAKPOINTINFO); + } + + @Override + public CompletableFuture handle(Command command, Arguments arguments, Response response, IDebugAdapterContext context) { + DataBreakpointInfoArguments dataBpArgs = (DataBreakpointInfoArguments) arguments; + if (dataBpArgs.variablesReference > 0) { + Object container = context.getRecyclableIdPool().getObjectById(dataBpArgs.variablesReference); + if (container instanceof VariableProxy) { + if (!(((VariableProxy) container).getProxiedVariable() instanceof StackFrameReference)) { + ObjectReference containerObj = (ObjectReference) ((VariableProxy) container).getProxiedVariable(); + ReferenceType type = containerObj.referenceType(); + Field field = type.fieldByName(dataBpArgs.name); + if (field != null) { + String fullyQualifiedName = type.name(); + String dataId = String.format("%s#%s", fullyQualifiedName, dataBpArgs.name); + String description = String.format("%s.%s : %s", getSimpleName(fullyQualifiedName), dataBpArgs.name, getSimpleName(field.typeName())); + response.body = new DataBreakpointInfoResponseBody(dataId, description, + DataBreakpointAccessType.values(), true); + } + } + } + } + return CompletableFuture.completedFuture(response); + } + + private String getSimpleName(String typeName) { + if (StringUtils.isBlank(typeName)) { + return ""; + } + + String[] names = typeName.split("\\."); + return names[names.length - 1]; + } +} diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/InitializeRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/InitializeRequestHandler.java index f618e9fc2..ff39bb1c1 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/InitializeRequestHandler.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/InitializeRequestHandler.java @@ -61,6 +61,7 @@ public CompletableFuture handle(Requests.Command command, Req }; caps.exceptionBreakpointFilters = exceptionFilters; caps.supportsExceptionInfoRequest = true; + caps.supportsDataBreakpoints = true; response.body = caps; return CompletableFuture.completedFuture(response); } diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetBreakpointsRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetBreakpointsRequestHandler.java index ca2a002c9..23ed69213 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetBreakpointsRequestHandler.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetBreakpointsRequestHandler.java @@ -27,7 +27,6 @@ import com.microsoft.java.debug.core.IDebugSession; import com.microsoft.java.debug.core.IEvaluatableBreakpoint; import com.microsoft.java.debug.core.adapter.AdapterUtils; -import com.microsoft.java.debug.core.adapter.BreakpointManager; import com.microsoft.java.debug.core.adapter.ErrorCode; import com.microsoft.java.debug.core.adapter.HotCodeReplaceEvent.EventType; import com.microsoft.java.debug.core.adapter.IDebugAdapterContext; @@ -56,8 +55,6 @@ public class SetBreakpointsRequestHandler implements IDebugRequestHandler { private static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME); - private BreakpointManager manager = new BreakpointManager(); - private boolean registered = false; @Override @@ -127,7 +124,8 @@ public CompletableFuture handle(Command command, Arguments arguments, IBreakpoint[] toAdds = this.convertClientBreakpointsToDebugger(sourcePath, bpArguments.breakpoints, context); // See the VSCode bug https://github.com/Microsoft/vscode/issues/36471. // The source uri sometimes is encoded by VSCode, the debugger will decode it to keep the uri consistent. - IBreakpoint[] added = manager.setBreakpoints(AdapterUtils.decodeURIComponent(sourcePath), toAdds, bpArguments.sourceModified); + IBreakpoint[] added = context.getBreakpointManager() + .setBreakpoints(AdapterUtils.decodeURIComponent(sourcePath), toAdds, bpArguments.sourceModified); for (int i = 0; i < bpArguments.breakpoints.length; i++) { // For newly added breakpoint, should install it to debuggee first. if (toAdds[i] == added[i] && added[i].className() != null) { @@ -161,8 +159,8 @@ public CompletableFuture handle(Command command, Arguments arguments, } } - private IBreakpoint getAssociatedEvaluatableBreakpoint(BreakpointEvent event) { - return Arrays.asList(manager.getBreakpoints()).stream().filter( + private IBreakpoint getAssociatedEvaluatableBreakpoint(IDebugAdapterContext context, BreakpointEvent event) { + return Arrays.asList(context.getBreakpointManager().getBreakpoints()).stream().filter( bp -> { return bp instanceof IEvaluatableBreakpoint && ((IEvaluatableBreakpoint) bp).containsEvaluatableExpression() @@ -187,17 +185,19 @@ private void registerBreakpointHandler(IDebugAdapterContext context) { } // find the breakpoint related to this breakpoint event - IBreakpoint expressionBP = getAssociatedEvaluatableBreakpoint((BreakpointEvent) event); + IBreakpoint expressionBP = getAssociatedEvaluatableBreakpoint(context, (BreakpointEvent) event); if (expressionBP != null) { CompletableFuture.runAsync(() -> { engine.evaluateForBreakpoint((IEvaluatableBreakpoint) expressionBP, bpThread).whenComplete((value, ex) -> { - boolean resume = handleEvaluationResult(context, bpThread, expressionBP, value, ex); + boolean resume = handleEvaluationResult(context, bpThread, (IEvaluatableBreakpoint) expressionBP, value, ex); // Clear the evaluation environment caused by above evaluation. engine.clearState(bpThread); if (resume) { debugEvent.eventSet.resume(); + } else { + context.getProtocolServer().sendEvent(new Events.StoppedEvent("breakpoint", bpThread.uniqueID())); } }); }); @@ -210,7 +210,11 @@ private void registerBreakpointHandler(IDebugAdapterContext context) { } } - private boolean handleEvaluationResult(IDebugAdapterContext context, ThreadReference bpThread, IBreakpoint breakpoint, Value value, Throwable ex) { + /** + * Check whether the condition expression is satisfied, and return a boolean value to determine to resume the thread or not. + */ + public static boolean handleEvaluationResult(IDebugAdapterContext context, ThreadReference bpThread, IEvaluatableBreakpoint breakpoint, + Value value, Throwable ex) { if (StringUtils.isNotBlank(breakpoint.getLogMessage())) { if (ex != null) { logger.log(Level.SEVERE, String.format("[Logpoint]: %s", ex.getMessage() != null ? ex.getMessage() : ex.toString()), ex); @@ -237,7 +241,6 @@ private boolean handleEvaluationResult(IDebugAdapterContext context, ThreadRefer if (resume) { return true; } else { - context.getProtocolServer().sendEvent(new Events.StoppedEvent("breakpoint", bpThread.uniqueID())); if (ex != null) { logger.log(Level.SEVERE, String.format("[ConditionalBreakpoint]: %s", ex.getMessage() != null ? ex.getMessage() : ex.toString()), ex); context.getProtocolServer().sendEvent(new Events.UserNotificationEvent( @@ -288,7 +291,7 @@ private void reinstallBreakpoints(IDebugAdapterContext context, List typ if (typenames == null || typenames.isEmpty()) { return; } - IBreakpoint[] breakpoints = manager.getBreakpoints(); + IBreakpoint[] breakpoints = context.getBreakpointManager().getBreakpoints(); for (IBreakpoint breakpoint : breakpoints) { if (typenames.contains(breakpoint.className())) { diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetDataBreakpointsRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetDataBreakpointsRequestHandler.java new file mode 100644 index 000000000..be15852e4 --- /dev/null +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetDataBreakpointsRequestHandler.java @@ -0,0 +1,165 @@ +/******************************************************************************* +* Copyright (c) 2019 Microsoft 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: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.handler; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Stream; + +import org.apache.commons.lang3.StringUtils; + +import com.microsoft.java.debug.core.IDebugSession; +import com.microsoft.java.debug.core.IEvaluatableBreakpoint; +import com.microsoft.java.debug.core.IWatchpoint; +import com.microsoft.java.debug.core.Watchpoint; +import com.microsoft.java.debug.core.adapter.AdapterUtils; +import com.microsoft.java.debug.core.adapter.ErrorCode; +import com.microsoft.java.debug.core.adapter.IDebugAdapterContext; +import com.microsoft.java.debug.core.adapter.IDebugRequestHandler; +import com.microsoft.java.debug.core.adapter.IEvaluationProvider; +import com.microsoft.java.debug.core.protocol.Events; +import com.microsoft.java.debug.core.protocol.Events.BreakpointEvent; +import com.microsoft.java.debug.core.protocol.Messages.Response; +import com.microsoft.java.debug.core.protocol.Requests.Arguments; +import com.microsoft.java.debug.core.protocol.Requests.Command; +import com.microsoft.java.debug.core.protocol.Requests.SetDataBreakpointsArguments; +import com.microsoft.java.debug.core.protocol.Responses; +import com.microsoft.java.debug.core.protocol.Types.Breakpoint; +import com.microsoft.java.debug.core.protocol.Types.DataBreakpoint; +import com.sun.jdi.ThreadReference; +import com.sun.jdi.event.Event; +import com.sun.jdi.event.WatchpointEvent; + +public class SetDataBreakpointsRequestHandler implements IDebugRequestHandler { + private boolean registered = false; + + @Override + public List getTargetCommands() { + return Arrays.asList(Command.SETDATABREAKPOINTS); + } + + @Override + public CompletableFuture handle(Command command, Arguments arguments, Response response, IDebugAdapterContext context) { + if (context.getDebugSession() == null) { + return AdapterUtils.createAsyncErrorResponse(response, ErrorCode.EMPTY_DEBUG_SESSION, "Empty debug session."); + } + + if (!registered) { + registered = true; + registerWatchpointHandler(context); + } + + SetDataBreakpointsArguments dataBpArgs = (SetDataBreakpointsArguments) arguments; + IWatchpoint[] requestedWatchpoints = (dataBpArgs.breakpoints == null) ? new Watchpoint[0] : new Watchpoint[dataBpArgs.breakpoints.length]; + for (int i = 0; i < requestedWatchpoints.length; i++) { + DataBreakpoint dataBreakpoint = dataBpArgs.breakpoints[i]; + if (dataBreakpoint.dataId != null) { + String[] segments = dataBreakpoint.dataId.split("#"); + if (segments.length == 2 && StringUtils.isNotBlank(segments[0]) && StringUtils.isNotBlank(segments[1])) { + int hitCount = 0; + try { + hitCount = Integer.parseInt(dataBreakpoint.hitCondition); + } catch (NumberFormatException e) { + hitCount = 0; // If hitCount is an illegal number, ignore hitCount condition. + } + + String accessType = dataBreakpoint.accessType != null ? dataBreakpoint.accessType.label() : null; + requestedWatchpoints[i] = context.getDebugSession().createWatchPoint(segments[0], segments[1], accessType, + dataBreakpoint.condition, hitCount); + } + } + } + + IWatchpoint[] currentWatchpoints = context.getBreakpointManager().setWatchpoints(requestedWatchpoints); + List breakpoints = new ArrayList<>(); + for (int i = 0; i < currentWatchpoints.length; i++) { + if (currentWatchpoints[i] == null) { + breakpoints.add(new Breakpoint(false)); + continue; + } + + // If the requested watchpoint exists in the watchpoint manager, it will reuse the cached watchpoint object. + // Otherwise add the requested watchpoint to the cache. + // So if the returned watchpoint from the manager is same as the requested wantchpoint, this means it's a new watchpoint, need install it. + if (currentWatchpoints[i] == requestedWatchpoints[i]) { + currentWatchpoints[i].install().thenAccept(wp -> { + BreakpointEvent bpEvent = new BreakpointEvent("new", convertDebuggerWatchpointToClient(wp)); + context.getProtocolServer().sendEvent(bpEvent); + }); + } else { + if (currentWatchpoints[i].getHitCount() != requestedWatchpoints[i].getHitCount()) { + currentWatchpoints[i].setHitCount(requestedWatchpoints[i].getHitCount()); + } + + if (!Objects.equals(currentWatchpoints[i].getCondition(), requestedWatchpoints[i].getCondition())) { + currentWatchpoints[i].setCondition(requestedWatchpoints[i].getCondition()); + } + } + + breakpoints.add(convertDebuggerWatchpointToClient(currentWatchpoints[i])); + } + + response.body = new Responses.SetDataBreakpointsResponseBody(breakpoints); + return CompletableFuture.completedFuture(response); + } + + private Breakpoint convertDebuggerWatchpointToClient(IWatchpoint watchpoint) { + return new Breakpoint((int) watchpoint.getProperty("id"), + watchpoint.getProperty("verified") != null && (boolean) watchpoint.getProperty("verified")); + } + + private void registerWatchpointHandler(IDebugAdapterContext context) { + IDebugSession debugSession = context.getDebugSession(); + if (debugSession != null) { + debugSession.getEventHub().events().filter(debugEvent -> debugEvent.event instanceof WatchpointEvent).subscribe(debugEvent -> { + Event event = debugEvent.event; + ThreadReference bpThread = ((WatchpointEvent) event).thread(); + IEvaluationProvider engine = context.getProvider(IEvaluationProvider.class); + if (engine.isInEvaluation(bpThread)) { + return; + } + + // Find the watchpoint related to this watchpoint event + IWatchpoint watchpoint = Stream.of(context.getBreakpointManager().getWatchpoints()) + .filter(wp -> { + return wp instanceof IEvaluatableBreakpoint + && ((IEvaluatableBreakpoint) wp).containsEvaluatableExpression() + && wp.requests().contains(event.request()); + }) + .findFirst().orElse(null); + + if (watchpoint != null) { + CompletableFuture.runAsync(() -> { + engine.evaluateForBreakpoint((IEvaluatableBreakpoint) watchpoint, bpThread).whenComplete((value, ex) -> { + boolean resume = SetBreakpointsRequestHandler.handleEvaluationResult( + context, bpThread, (IEvaluatableBreakpoint) watchpoint, value, ex); + // Clear the evaluation environment caused by above evaluation. + engine.clearState(bpThread); + + if (resume) { + debugEvent.eventSet.resume(); + } else { + context.getProtocolServer().sendEvent(new Events.StoppedEvent("data breakpoint", bpThread.uniqueID())); + } + }); + }); + } else { + context.getProtocolServer().sendEvent(new Events.StoppedEvent("data breakpoint", bpThread.uniqueID())); + } + debugEvent.shouldResume = false; + }); + } + } +} diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Requests.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Requests.java index 9aa1383e6..c0c6621a9 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Requests.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Requests.java @@ -15,6 +15,7 @@ import java.util.Map; import com.google.gson.annotations.SerializedName; +import com.microsoft.java.debug.core.protocol.Types.DataBreakpoint; /** * The request arguments types defined by VSCode Debug Protocol. @@ -288,6 +289,24 @@ public static class CompletionsArguments extends Arguments { public int column; } + public static class DataBreakpointInfoArguments extends Arguments { + /** + * Reference to the Variable container if the data breakpoint is requested for a child of the container. + */ + public int variablesReference; + /** + * The name of the Variable's child to obtain data breakpoint information for. If variableReference isn’t provided, this can be an expression. + */ + public String name; + } + + public static class SetDataBreakpointsArguments extends Arguments { + /** + * The contents of this array replaces all existing data breakpoints. An empty array clears all data breakpoints. + */ + public DataBreakpoint[] breakpoints; + } + public static enum Command { INITIALIZE("initialize", InitializeArguments.class), LAUNCH("launch", LaunchArguments.class), @@ -314,6 +333,8 @@ public static enum Command { RUNINTERMINAL("runInTerminal", RunInTerminalRequestArguments.class), REDEFINECLASSES("redefineClasses", RedefineClassesArguments.class), EXCEPTIONINFO("exceptionInfo", ExceptionInfoArguments.class), + DATABREAKPOINTINFO("dataBreakpointInfo", DataBreakpointInfoArguments.class), + SETDATABREAKPOINTS("setDataBreakpoints", SetDataBreakpointsArguments.class), UNSUPPORTED("", Arguments.class); private String command; diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Responses.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Responses.java index ed9b4767d..b1c8d5ae3 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Responses.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Responses.java @@ -13,6 +13,7 @@ import java.util.List; +import com.microsoft.java.debug.core.protocol.Types.DataBreakpointAccessType; import com.microsoft.java.debug.core.protocol.Types.ExceptionBreakMode; import com.microsoft.java.debug.core.protocol.Types.ExceptionDetails; @@ -207,6 +208,56 @@ public SetBreakpointsResponseBody(List bpts) { } } + public static class SetDataBreakpointsResponseBody extends SetBreakpointsResponseBody { + public SetDataBreakpointsResponseBody(List bpts) { + super(bpts); + } + } + + public static class DataBreakpointInfoResponseBody extends ResponseBody { + /** + * An identifier for the data on which a data breakpoint can be registered with the setDataBreakpoints request + * or null if no data breakpoint is available. + */ + public String dataId; + /** + * UI string that describes on what data the breakpoint is set on or why a data breakpoint is not available. + */ + public String description; + /** + * Optional attribute listing the available access types for a potential data breakpoint. A UI frontend could surface this information. + */ + public DataBreakpointAccessType[] accessTypes; + /** + * Optional attribute indicating that a potential data breakpoint could be persisted across sessions. + */ + public boolean canPersist; + + public DataBreakpointInfoResponseBody(String dataId) { + this(dataId, null); + } + + public DataBreakpointInfoResponseBody(String dataId, String description) { + this(dataId, description, null); + } + + public DataBreakpointInfoResponseBody(String dataId, String description, + DataBreakpointAccessType[] accessTypes) { + this(dataId, description, accessTypes, false); + } + + /** + * Constructor. + */ + public DataBreakpointInfoResponseBody(String dataId, String description, DataBreakpointAccessType[] accessTypes, + boolean canPersist) { + this.dataId = dataId; + this.description = description; + this.accessTypes = accessTypes; + this.canPersist = canPersist; + } + } + public static class ContinueResponseBody extends ResponseBody { public boolean allThreadsContinued; diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Types.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Types.java index 65188273a..0b6fbfa96 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Types.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Types.java @@ -25,6 +25,7 @@ public static class Message { /** * Constructs a message with the given information. + * * @param id * message id * @param format @@ -45,6 +46,7 @@ public static class StackFrame { /** * Constructs a StackFrame with the given information. + * * @param id * the stack frame id * @param name @@ -146,11 +148,32 @@ public Source(String path, int rf) { } public static class Breakpoint { + /** + * An optional identifier for the breakpoint. It is needed if breakpoint events are used to update or remove breakpoints. + */ public int id; + /** + * If true breakpoint could be set (but not necessarily at the desired location). + */ public boolean verified; + /** + * The start line of the actual range covered by the breakpoint. + */ public int line; + /** + * An optional message about the state of the breakpoint. This is shown to the user and can be used to explain why a breakpoint could not be verified. + */ public String message; + public Breakpoint(boolean verified) { + this.verified = verified; + } + + public Breakpoint(int id, boolean verified) { + this.id = id; + this.verified = verified; + } + /** * Constructor. */ @@ -194,6 +217,63 @@ public FunctionBreakpoint(String name) { } } + public static enum DataBreakpointAccessType { + @SerializedName("read") + READ("read"), + @SerializedName("write") + WRITE("write"), + @SerializedName("readWrite") + READWRITE("readWrite"); + + String label; + + DataBreakpointAccessType(String label) { + this.label = label; + } + + public String label() { + return label; + } + } + + public static class DataBreakpoint { + /** + * An id representing the data. This id is returned from the dataBreakpointInfo request. + */ + public String dataId; + /** + * The access type of the data. + */ + public DataBreakpointAccessType accessType; + /** + * An optional expression for conditional breakpoints. + */ + public String condition; + /** + * An optional expression that controls how many hits of the breakpoint are ignored. The backend is expected to interpret the expression as needed. + */ + public String hitCondition; + + public DataBreakpoint(String dataId) { + this.dataId = dataId; + } + + public DataBreakpoint(String dataId, DataBreakpointAccessType accessType) { + this.dataId = dataId; + this.accessType = accessType; + } + + /** + * Constructor. + */ + public DataBreakpoint(String dataId, DataBreakpointAccessType accessType, String condition, String hitCondition) { + this.dataId = dataId; + this.accessType = accessType; + this.condition = condition; + this.hitCondition = hitCondition; + } + } + public static class CompletionItem { public String label; public String text; @@ -265,5 +345,6 @@ public static class Capabilities { public boolean supportsLogPoints; public boolean supportsExceptionInfoRequest; public ExceptionBreakpointFilter[] exceptionBreakpointFilters = new ExceptionBreakpointFilter[0]; + public boolean supportsDataBreakpoints; } } From 7aa2d37fa2aff4a36e1ebe4658fbab304b588002 Mon Sep 17 00:00:00 2001 From: Jinbo Wang Date: Thu, 14 Nov 2019 13:10:22 +0800 Subject: [PATCH 2/3] Address review comments Signed-off-by: Jinbo Wang --- .../com/microsoft/java/debug/core/Watchpoint.java | 13 +++++++++---- .../java/debug/core/adapter/BreakpointManager.java | 6 +++--- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/Watchpoint.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/Watchpoint.java index cc44d070d..5e802aae3 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/Watchpoint.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/Watchpoint.java @@ -14,6 +14,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Objects; import java.util.concurrent.CompletableFuture; import org.apache.commons.lang3.StringUtils; @@ -31,10 +32,10 @@ import io.reactivex.disposables.Disposable; public class Watchpoint implements IWatchpoint, IEvaluatableBreakpoint { - private VirtualMachine vm = null; - private IEventHub eventHub = null; - private String className = null; - private String fieldName = null; + private final VirtualMachine vm; + private final IEventHub eventHub; + private final String className; + private final String fieldName; private String accessType = null; private String condition = null; private int hitCount; @@ -54,6 +55,10 @@ public class Watchpoint implements IWatchpoint, IEvaluatableBreakpoint { } Watchpoint(VirtualMachine vm, IEventHub eventHub, String className, String fieldName, String accessType, String condition, int hitCount) { + Objects.requireNonNull(vm); + Objects.requireNonNull(eventHub); + Objects.requireNonNull(className); + Objects.requireNonNull(fieldName); this.vm = vm; this.eventHub = eventHub; this.className = className; diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/BreakpointManager.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/BreakpointManager.java index af094501b..78898df73 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/BreakpointManager.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/BreakpointManager.java @@ -158,7 +158,7 @@ public IWatchpoint[] setWatchpoints(IWatchpoint[] changedWatchpoints) { List toAdds = new ArrayList<>(); List toRemoves = new ArrayList<>(); - Set reused = new HashSet<>(); + Set visitedKeys = new HashSet<>(); for (IWatchpoint change : changedWatchpoints) { if (change == null) { result.add(change); @@ -168,7 +168,7 @@ public IWatchpoint[] setWatchpoints(IWatchpoint[] changedWatchpoints) { String key = getWatchpointKey(change); IWatchpoint cache = watchpoints.get(key); if (cache != null && Objects.equals(cache.accessType(), change.accessType())) { - reused.add(key); + visitedKeys.add(key); result.add(cache); } else { toAdds.add(change); @@ -177,7 +177,7 @@ public IWatchpoint[] setWatchpoints(IWatchpoint[] changedWatchpoints) { } for (IWatchpoint cache : watchpoints.values()) { - if (!reused.contains(getWatchpointKey(cache))) { + if (!visitedKeys.contains(getWatchpointKey(cache))) { toRemoves.add(cache); } } From 0092288ed905df3cfbcaa925f23d5570c79604b2 Mon Sep 17 00:00:00 2001 From: Jinbo Wang Date: Fri, 15 Nov 2019 10:11:00 +0800 Subject: [PATCH 3/3] Throw unsupported exception when set log message to data breakpoint Signed-off-by: Jinbo Wang --- .../src/main/java/com/microsoft/java/debug/core/Watchpoint.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/Watchpoint.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/Watchpoint.java index 5e802aae3..18b4314cf 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/Watchpoint.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/Watchpoint.java @@ -214,7 +214,7 @@ public String getLogMessage() { @Override public void setLogMessage(String logMessage) { - // do nothing + throw new UnsupportedOperationException("Log message feature is unsupported for watchpoint."); } @Override