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 a56eaf695..ab4341f51 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 @@ -146,4 +146,10 @@ public IEventHub getEventHub() { public VirtualMachine getVM() { return vm; } + + @Override + public IMethodBreakpoint createFunctionBreakpoint(String className, String functionName, String condition, + int hitCount) { + return new MethodBreakpoint(vm, this.getEventHub(), className, functionName, condition, hitCount); + } } 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 24138fef1..03780c2d9 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 @@ -36,7 +36,7 @@ public interface IDebugSession { void setExceptionBreakpoints(boolean notifyCaught, boolean notifyUncaught, String[] classFilters, String[] classExclusionFilters); - // TODO: createFunctionBreakpoint + IMethodBreakpoint createFunctionBreakpoint(String className, String functionName, String condition, int hitCount); Process process(); diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/IMethodBreakpoint.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/IMethodBreakpoint.java new file mode 100644 index 000000000..668884567 --- /dev/null +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/IMethodBreakpoint.java @@ -0,0 +1,33 @@ +/******************************************************************************* +* Copyright (c) 2022 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: +* Gayan Perera - initial API and implementation +*******************************************************************************/ +package com.microsoft.java.debug.core; + +import java.util.concurrent.CompletableFuture; + +public interface IMethodBreakpoint extends IDebugResource { + String methodName(); + + String className(); + + int getHitCount(); + + String getCondition(); + + void setHitCount(int hitCount); + + void setCondition(String condition); + + CompletableFuture install(); + + Object getProperty(Object key); + + void putProperty(Object key, Object value); +} diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/MethodBreakpoint.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/MethodBreakpoint.java new file mode 100644 index 000000000..82c5b75e2 --- /dev/null +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/MethodBreakpoint.java @@ -0,0 +1,258 @@ +/******************************************************************************* +* Copyright (c) 2022 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: +* Gayan Perera - 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.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.commons.lang3.StringUtils; + +import com.sun.jdi.ReferenceType; +import com.sun.jdi.ThreadReference; +import com.sun.jdi.VMDisconnectedException; +import com.sun.jdi.VirtualMachine; +import com.sun.jdi.event.ClassPrepareEvent; +import com.sun.jdi.event.ThreadDeathEvent; +import com.sun.jdi.request.ClassPrepareRequest; +import com.sun.jdi.request.EventRequest; +import com.sun.jdi.request.MethodEntryRequest; + +import io.reactivex.Observable; +import io.reactivex.disposables.Disposable; + +public class MethodBreakpoint implements IMethodBreakpoint, IEvaluatableBreakpoint { + + private VirtualMachine vm; + private IEventHub eventHub; + private String className; + private String functionName; + private String condition; + private int hitCount; + + private HashMap propertyMap = new HashMap<>(); + private Object compiledConditionalExpression = null; + private Map compiledExpressions = new ConcurrentHashMap<>(); + + private List requests = new ArrayList<>(); + private List subscriptions = new ArrayList<>(); + + public MethodBreakpoint(VirtualMachine vm, IEventHub eventHub, String className, String functionName, + String condition, int hitCount) { + Objects.requireNonNull(vm); + Objects.requireNonNull(eventHub); + Objects.requireNonNull(className); + Objects.requireNonNull(functionName); + this.vm = vm; + this.eventHub = eventHub; + this.className = className; + this.functionName = functionName; + 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(Disposable::dispose); + requests.clear(); + subscriptions.clear(); + } + + @Override + public boolean containsEvaluatableExpression() { + return containsConditionalExpression() || containsLogpointExpression(); + } + + @Override + public boolean containsConditionalExpression() { + return StringUtils.isNotBlank(getCondition()); + } + + @Override + public boolean containsLogpointExpression() { + return false; + } + + @Override + public String getCondition() { + return condition; + } + + @Override + public void setCondition(String condition) { + this.condition = condition; + setCompiledConditionalExpression(null); + compiledExpressions.clear(); + } + + @Override + public String getLogMessage() { + return null; + } + + @Override + public void setLogMessage(String logMessage) { + // for future implementation + } + + @Override + public void setCompiledConditionalExpression(Object compiledExpression) { + this.compiledConditionalExpression = compiledExpression; + } + + @Override + public Object getCompiledConditionalExpression() { + return compiledConditionalExpression; + } + + @Override + public void setCompiledLogpointExpression(Object compiledExpression) { + // for future implementation + } + + @Override + public Object getCompiledLogpointExpression() { + return null; + } + + @Override + public void setCompiledExpression(long threadId, Object compiledExpression) { + compiledExpressions.put(threadId, compiledExpression); + } + + @Override + public Object getCompiledExpression(long threadId) { + return compiledExpressions.get(threadId); + } + + @Override + public int getHitCount() { + return hitCount; + } + + @Override + public void setHitCount(int hitCount) { + this.hitCount = hitCount; + Observable.fromIterable(this.requests()) + .filter(request -> request instanceof MethodEntryRequest) + .subscribe(request -> { + request.addCountFilter(hitCount); + request.enable(); + }); + } + + @Override + public CompletableFuture install() { + Disposable subscription = eventHub.events() + .filter(debugEvent -> debugEvent.event instanceof ThreadDeathEvent) + .subscribe(debugEvent -> { + ThreadReference deathThread = ((ThreadDeathEvent) debugEvent.event).thread(); + compiledExpressions.remove(deathThread.uniqueID()); + }); + + subscriptions.add(subscription); + + // 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<>(); + subscription = eventHub.events() + .filter(debugEvent -> debugEvent.event instanceof ClassPrepareEvent + && (classPrepareRequest.equals(debugEvent.event.request()))) + .subscribe(debugEvent -> { + ClassPrepareEvent event = (ClassPrepareEvent) debugEvent.event; + Optional createdRequest = createMethodEntryRequest(event.referenceType()); + if (createdRequest.isPresent()) { + MethodEntryRequest methodEntryRequest = createdRequest.get(); + requests.add(methodEntryRequest); + if (!future.isDone()) { + this.putProperty("verified", true); + future.complete(this); + } + } + }); + subscriptions.add(subscription); + + List types = vm.classesByName(className); + for (ReferenceType type : types) { + Optional createdRequest = createMethodEntryRequest(type); + if (createdRequest.isPresent()) { + MethodEntryRequest methodEntryRequest = createdRequest.get(); + requests.add(methodEntryRequest); + if (!future.isDone()) { + this.putProperty("verified", true); + future.complete(this); + } + } + } + return future; + } + + private Optional createMethodEntryRequest(ReferenceType type) { + return type.methodsByName(functionName).stream().findFirst().map(method -> { + MethodEntryRequest request = vm.eventRequestManager().createMethodEntryRequest(); + + request.addClassFilter(type); + request.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD); + if (hitCount > 0) { + request.addCountFilter(hitCount); + } + request.enable(); + return request; + }); + } + + @Override + public Object getProperty(Object key) { + return propertyMap.get(key); + } + + @Override + public void putProperty(Object key, Object value) { + propertyMap.put(key, value); + } + + @Override + public String methodName() { + return functionName; + } + + @Override + public String className() { + return 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 78898df73..4b0a7ae89 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 @@ -25,6 +25,7 @@ import com.microsoft.java.debug.core.Configuration; import com.microsoft.java.debug.core.IBreakpoint; +import com.microsoft.java.debug.core.IMethodBreakpoint; import com.microsoft.java.debug.core.IWatchpoint; public class BreakpointManager implements IBreakpointManager { @@ -35,6 +36,7 @@ public class BreakpointManager implements IBreakpointManager { private List breakpoints; private Map> sourceToBreakpoints; private Map watchpoints; + private Map methodBreakpoints; private AtomicInteger nextBreakpointId = new AtomicInteger(1); /** @@ -44,6 +46,7 @@ public BreakpointManager() { this.breakpoints = Collections.synchronizedList(new ArrayList<>(5)); this.sourceToBreakpoints = new HashMap<>(); this.watchpoints = new HashMap<>(); + this.methodBreakpoints = new HashMap<>(); } @Override @@ -208,4 +211,61 @@ private String getWatchpointKey(IWatchpoint watchpoint) { public IWatchpoint[] getWatchpoints() { return this.watchpoints.values().stream().filter(wp -> wp != null).toArray(IWatchpoint[]::new); } + + @Override + public IMethodBreakpoint[] getMethodBreakpoints() { + return this.methodBreakpoints.values().stream().filter(Objects::nonNull).toArray(IMethodBreakpoint[]::new); + } + + @Override + public IMethodBreakpoint[] setMethodBreakpoints(IMethodBreakpoint[] breakpoints) { + List result = new ArrayList<>(); + List toAdds = new ArrayList<>(); + List toRemoves = new ArrayList<>(); + + Set visitedKeys = new HashSet<>(); + for (IMethodBreakpoint change : breakpoints) { + if (change == null) { + result.add(change); + continue; + } + + String key = getMethodBreakpointKey(change); + IMethodBreakpoint cache = methodBreakpoints.get(key); + if (cache != null) { + visitedKeys.add(key); + result.add(cache); + } else { + toAdds.add(change); + result.add(change); + } + } + + for (IMethodBreakpoint cache : methodBreakpoints.values()) { + if (!visitedKeys.contains(getMethodBreakpointKey(cache))) { + toRemoves.add(cache); + } + } + + for (IMethodBreakpoint toRemove : toRemoves) { + try { + // Destroy the method breakpoint on the debugee VM. + toRemove.close(); + this.methodBreakpoints.remove(getMethodBreakpointKey(toRemove)); + } catch (Exception e) { + logger.log(Level.SEVERE, String.format("Remove the method breakpoint exception: %s", e.toString()), e); + } + } + + for (IMethodBreakpoint toAdd : toAdds) { + toAdd.putProperty("id", this.nextBreakpointId.getAndIncrement()); + this.methodBreakpoints.put(getMethodBreakpointKey(toAdd), toAdd); + } + + return result.toArray(new IMethodBreakpoint[0]); + } + + private String getMethodBreakpointKey(IMethodBreakpoint breakpoint) { + return breakpoint.className() + "#" + breakpoint.methodName(); + } } 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 afdb5759c..0bf5d2683 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 @@ -39,6 +39,7 @@ 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.SetFunctionBreakpointsRequestHandler; import com.microsoft.java.debug.core.adapter.handler.SetVariableRequestHandler; import com.microsoft.java.debug.core.adapter.handler.SourceRequestHandler; import com.microsoft.java.debug.core.adapter.handler.StackTraceRequestHandler; @@ -127,7 +128,7 @@ private void initialize() { registerHandlerForDebug(new InlineValuesRequestHandler()); registerHandlerForDebug(new RefreshVariablesHandler()); registerHandlerForDebug(new ProcessIdHandler()); - + registerHandlerForDebug(new SetFunctionBreakpointsRequestHandler()); // NO_DEBUG mode only registerHandlerForNoDebug(new DisconnectRequestWithoutDebuggingHandler()); registerHandlerForNoDebug(new ProcessIdHandler()); 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 index 9ba609221..196714d52 100644 --- 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 @@ -12,6 +12,7 @@ package com.microsoft.java.debug.core.adapter; import com.microsoft.java.debug.core.IBreakpoint; +import com.microsoft.java.debug.core.IMethodBreakpoint; import com.microsoft.java.debug.core.IWatchpoint; public interface IBreakpointManager { @@ -69,4 +70,23 @@ public interface IBreakpointManager { * Returns all registered watchpoints. */ IWatchpoint[] getWatchpoints(); + + /** + * Returns all the registered method breakpoints. + */ + IMethodBreakpoint[] getMethodBreakpoints(); + + /** + * Update the method breakpoints list. If the requested method breakpoints + * already registered in the breakpoint + * manager, reuse the cached one. Otherwise register the requested method + * breakpoints as a new method breakpoints. + * Besides, delete those not existed any more. + * + * @param methodBreakpoints + * the method breakpoints requested by client + * @return the full registered method breakpoints list + */ + IMethodBreakpoint[] setMethodBreakpoints(IMethodBreakpoint[] methodBreakpoints); + } 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 4733170eb..ae92f356f 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 @@ -62,6 +62,7 @@ public CompletableFuture handle(Requests.Command command, Req caps.exceptionBreakpointFilters = exceptionFilters; caps.supportsExceptionInfoRequest = true; caps.supportsDataBreakpoints = true; + caps.supportsFunctionBreakpoints = true; caps.supportsClipboardContext = 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/SetFunctionBreakpointsRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetFunctionBreakpointsRequestHandler.java new file mode 100644 index 000000000..fa4d23388 --- /dev/null +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetFunctionBreakpointsRequestHandler.java @@ -0,0 +1,189 @@ +/******************************************************************************* +* Copyright (c) 2022 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: +* Gayan Perera - 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.IMethodBreakpoint; +import com.microsoft.java.debug.core.MethodBreakpoint; +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.SetFunctionBreakpointsArguments; +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.FunctionBreakpoint; +import com.sun.jdi.ThreadReference; +import com.sun.jdi.event.MethodEntryEvent; + +public class SetFunctionBreakpointsRequestHandler implements IDebugRequestHandler { + private boolean registered = false; + + @Override + public List getTargetCommands() { + return Arrays.asList(Command.SETFUNCTIONBREAKPOINTS); + } + + @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; + registerMethodBreakpointHandler(context); + } + + SetFunctionBreakpointsArguments funcBpArgs = (SetFunctionBreakpointsArguments) arguments; + IMethodBreakpoint[] requestedMethodBreakpoints = (funcBpArgs.breakpoints == null) ? new IMethodBreakpoint[0] + : new MethodBreakpoint[funcBpArgs.breakpoints.length]; + for (int i = 0; i < requestedMethodBreakpoints.length; i++) { + FunctionBreakpoint funcBreakpoint = funcBpArgs.breakpoints[i]; + if (funcBreakpoint.name != null) { + String[] segments = funcBreakpoint.name.split("#"); + if (segments.length == 2 && StringUtils.isNotBlank(segments[0]) + && StringUtils.isNotBlank(segments[1])) { + int hitCount = 0; + try { + hitCount = Integer.parseInt(funcBreakpoint.hitCondition); + } catch (NumberFormatException e) { + hitCount = 0; // If hitCount is an illegal number, ignore hitCount condition. + } + requestedMethodBreakpoints[i] = context.getDebugSession().createFunctionBreakpoint(segments[0], + segments[1], + funcBreakpoint.condition, hitCount); + } + } + } + + IMethodBreakpoint[] currentMethodBreakpoints = context.getBreakpointManager() + .setMethodBreakpoints(requestedMethodBreakpoints); + List breakpoints = new ArrayList<>(); + for (int i = 0; i < currentMethodBreakpoints.length; i++) { + if (currentMethodBreakpoints[i] == null) { + breakpoints.add(new Breakpoint(false)); + continue; + } + + // If the requested method breakpoint exists in the manager, it will reuse + // the cached breakpoint exists object. + // Otherwise add the requested method breakpoint to the cache. + // So if the returned method breakpoint from the manager is same as the + // requested method breakpoint, this means it's a new method breakpoint, need + // install it. + if (currentMethodBreakpoints[i] == requestedMethodBreakpoints[i]) { + currentMethodBreakpoints[i].install().thenAccept(wp -> { + BreakpointEvent bpEvent = new BreakpointEvent("changed", convertDebuggerMethodToClient(wp)); + context.getProtocolServer().sendEvent(bpEvent); + }); + } else { + if (currentMethodBreakpoints[i].getHitCount() != requestedMethodBreakpoints[i].getHitCount()) { + currentMethodBreakpoints[i].setHitCount(requestedMethodBreakpoints[i].getHitCount()); + } + + if (!Objects.equals(currentMethodBreakpoints[i].getCondition(), + requestedMethodBreakpoints[i].getCondition())) { + currentMethodBreakpoints[i].setCondition(requestedMethodBreakpoints[i].getCondition()); + } + } + + breakpoints.add(convertDebuggerMethodToClient(currentMethodBreakpoints[i])); + } + + response.body = new Responses.SetDataBreakpointsResponseBody(breakpoints); + return CompletableFuture.completedFuture(response); + } + + private Breakpoint convertDebuggerMethodToClient(IMethodBreakpoint methodBreakpoint) { + return new Breakpoint((int) methodBreakpoint.getProperty("id"), + methodBreakpoint.getProperty("verified") != null && (boolean) methodBreakpoint.getProperty("verified")); + } + + private void registerMethodBreakpointHandler(IDebugAdapterContext context) { + IDebugSession debugSession = context.getDebugSession(); + if (debugSession != null) { + debugSession.getEventHub().events().filter(debugEvent -> debugEvent.event instanceof MethodEntryEvent) + .subscribe(debugEvent -> { + MethodEntryEvent methodEntryEvent = (MethodEntryEvent) debugEvent.event; + ThreadReference bpThread = methodEntryEvent.thread(); + IEvaluationProvider engine = context.getProvider(IEvaluationProvider.class); + + // Find the method breakpoint related to this method entry event + IMethodBreakpoint methodBreakpoint = Stream + .of(context.getBreakpointManager().getMethodBreakpoints()) + .filter(mp -> { + return mp.requests().contains(methodEntryEvent.request()) + && matches(methodEntryEvent, mp); + }) + .findFirst().orElse(null); + + if (methodBreakpoint != null) { + if (methodBreakpoint instanceof IEvaluatableBreakpoint + && ((IEvaluatableBreakpoint) methodBreakpoint).containsConditionalExpression()) { + if (engine.isInEvaluation(bpThread)) { + return; + } + CompletableFuture.runAsync(() -> { + engine.evaluateForBreakpoint((IEvaluatableBreakpoint) methodBreakpoint, bpThread) + .whenComplete((value, ex) -> { + boolean resume = SetBreakpointsRequestHandler.handleEvaluationResult( + context, bpThread, (IEvaluatableBreakpoint) methodBreakpoint, + 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( + "function breakpoint", bpThread.uniqueID())); + } + }); + }); + + } else { + context.getProtocolServer() + .sendEvent(new Events.StoppedEvent("function breakpoint", bpThread.uniqueID())); + } + + debugEvent.shouldResume = false; + } + }); + } + } + + private boolean matches(MethodEntryEvent methodEntryEvent, IMethodBreakpoint breakpoint) { + return breakpoint.className().equals(methodEntryEvent.location().declaringType().name()) + && breakpoint.methodName().equals(methodEntryEvent.method().name()); + } + +} 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 e59f65f74..cd20faf4f 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 @@ -374,5 +374,6 @@ public static class Capabilities { public ExceptionBreakpointFilter[] exceptionBreakpointFilters = new ExceptionBreakpointFilter[0]; public boolean supportsDataBreakpoints; public boolean supportsClipboardContext; + public boolean supportsFunctionBreakpoints; } }