Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
Original file line number Diff line number Diff line change
@@ -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<IMethodBreakpoint> install();

Object getProperty(Object key);

void putProperty(Object key, Object value);
}
Original file line number Diff line number Diff line change
@@ -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<Object, Object> propertyMap = new HashMap<>();
private Object compiledConditionalExpression = null;
private Map<Long, Object> compiledExpressions = new ConcurrentHashMap<>();

private List<EventRequest> requests = new ArrayList<>();
private List<Disposable> 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<EventRequest> requests() {
return requests;
}

@Override
public List<Disposable> 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<IMethodBreakpoint> 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<IMethodBreakpoint> 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<MethodEntryRequest> 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<ReferenceType> types = vm.classesByName(className);
for (ReferenceType type : types) {
Optional<MethodEntryRequest> 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<MethodEntryRequest> 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;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -35,6 +36,7 @@ public class BreakpointManager implements IBreakpointManager {
private List<IBreakpoint> breakpoints;
private Map<String, HashMap<String, IBreakpoint>> sourceToBreakpoints;
private Map<String, IWatchpoint> watchpoints;
private Map<String, IMethodBreakpoint> methodBreakpoints;
private AtomicInteger nextBreakpointId = new AtomicInteger(1);

/**
Expand All @@ -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
Expand Down Expand Up @@ -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<IMethodBreakpoint> result = new ArrayList<>();
List<IMethodBreakpoint> toAdds = new ArrayList<>();
List<IMethodBreakpoint> toRemoves = new ArrayList<>();

Set<String> 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();
}
}
Loading