This commit is contained in:
youys 2023-06-27 10:04:55 +08:00
parent ea7e058591
commit 9cb11cbb57
920 changed files with 186246 additions and 2 deletions

13
.gitignore vendored Normal file
View File

@ -0,0 +1,13 @@
.DS_Store
.idea/
*.iml
target/
2017*
.project
.classpath
.settings/
*.log
bin/
lib/
src/main/java/test/
.vscode/

View File

@ -1,2 +0,0 @@
# java-debug

1
display_runtime.txt Normal file
View File

@ -0,0 +1 @@
1

87
pom.xml Normal file
View File

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>net.educoder</groupId>
<artifactId>java-debug</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.target>17</maven.compiler.target>
<maven.compiler.source>17</maven.compiler.source>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.6.14</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10</version>
</dependency>
<dependency>
<groupId>org.reactivestreams</groupId>
<artifactId>reactive-streams</artifactId>
<version>1.0.4</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
<!-- 引入本地jar -->
<dependency>
<groupId>org</groupId>
<artifactId>minimao-json</artifactId>
<scope>system</scope>
<version>1.0</version>
<systemPath>${project.basedir}/lib/minimal-json.jar</systemPath>
</dependency>
<dependency>
<groupId>org</groupId>
<artifactId>precompiled</artifactId>
<scope>system</scope>
<version>1.0</version>
<systemPath>${project.basedir}/lib/precompiled.jar</systemPath>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.6.14</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
<configuration>
<mainClass>net.educoder.DebuggerApplication</mainClass>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,143 @@
/*******************************************************************************
* 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:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/
package com.microsoft.java.debug.core;
import static java.util.concurrent.CompletableFuture.allOf;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Supplier;
public class AsyncJdwpUtils {
/**
* Create a the thread pool to process JDWP tasks.
* JDWP tasks are IO-bounded, so use a relatively large thread pool for JDWP tasks.
*/
public static ExecutorService jdwpThreadPool = Executors.newWorkStealingPool(100);
// public static ExecutorService jdwpThreadPool = Executors.newCachedThreadPool();
public static CompletableFuture<Void> runAsync(List<Runnable> tasks) {
return runAsync(jdwpThreadPool, tasks.toArray(new Runnable[0]));
}
public static CompletableFuture<Void> runAsync(Runnable... tasks) {
return runAsync(jdwpThreadPool, tasks);
}
public static CompletableFuture<Void> runAsync(Executor executor, List<Runnable> tasks) {
return runAsync(executor, tasks.toArray(new Runnable[0]));
}
public static CompletableFuture<Void> runAsync(Executor executor, Runnable... tasks) {
List<CompletableFuture<Void>> promises = new ArrayList<>();
for (Runnable task : tasks) {
if (task == null) {
continue;
}
promises.add(CompletableFuture.runAsync(task, executor));
}
return CompletableFuture.allOf(promises.toArray(new CompletableFuture[0]));
}
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {
return supplyAsync(jdwpThreadPool, supplier);
}
public static <U> CompletableFuture<U> supplyAsync(Executor executor, Supplier<U> supplier) {
return CompletableFuture.supplyAsync(supplier, executor);
}
public static <U> U await(CompletableFuture<U> future) {
try {
return future.join();
} catch (CompletionException ex) {
if (ex.getCause() instanceof RuntimeException) {
throw (RuntimeException) ex.getCause();
}
throw ex;
}
}
public static <U> List<U> await(CompletableFuture<U>[] futures) {
List<U> results = new ArrayList<>();
try {
allOf(futures).join();
for (CompletableFuture<U> future : futures) {
results.add(await(future));
}
} catch (CompletionException ex) {
if (ex.getCause() instanceof RuntimeException) {
throw (RuntimeException) ex.getCause();
}
throw ex;
}
return results;
}
public static <U> List<U> await(List<CompletableFuture<U>> futures) {
return await((CompletableFuture<U>[]) futures.toArray(new CompletableFuture[0]));
}
public static <U> CompletableFuture<List<U>> all(CompletableFuture<U>... futures) {
return allOf(futures).thenApply((res) -> {
List<U> results = new ArrayList<>();
for (CompletableFuture<U> future : futures) {
results.add(future.join());
}
return results;
});
}
public static <U> CompletableFuture<List<U>> all(List<CompletableFuture<U>> futures) {
return allOf(futures.toArray(new CompletableFuture[0])).thenApply((res) -> {
List<U> results = new ArrayList<>();
for (CompletableFuture<U> future : futures) {
results.add(future.join());
}
return results;
});
}
public static <U> CompletableFuture<List<U>> flatAll(CompletableFuture<List<U>>... futures) {
return allOf(futures).thenApply((res) -> {
List<U> results = new ArrayList<>();
for (CompletableFuture<List<U>> future : futures) {
results.addAll(future.join());
}
return results;
});
}
public static <U> CompletableFuture<List<U>> flatAll(List<CompletableFuture<List<U>>> futures) {
return allOf(futures.toArray(new CompletableFuture[0])).thenApply((res) -> {
List<U> results = new ArrayList<>();
for (CompletableFuture<List<U>> future : futures) {
results.addAll(future.join());
}
return results;
});
}
}

View File

@ -0,0 +1,448 @@
/*******************************************************************************
* Copyright (c) 2017-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:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/
package com.microsoft.java.debug.core;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import com.sun.jdi.AbsentInformationException;
import com.sun.jdi.Location;
import com.sun.jdi.Method;
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.BreakpointRequest;
import com.sun.jdi.request.ClassPrepareRequest;
import com.sun.jdi.request.EventRequest;
import io.reactivex.Observable;
import io.reactivex.disposables.Disposable;
public class Breakpoint implements IBreakpoint {
private VirtualMachine vm = null;
private IEventHub eventHub = null;
private JavaBreakpointLocation sourceLocation = null;
private int hitCount = 0;
private String condition = null;
private String logMessage = null;
private HashMap<Object, Object> propertyMap = new HashMap<>();
private boolean async = false;
Breakpoint(VirtualMachine vm, IEventHub eventHub, String className, int lineNumber) {
this(vm, eventHub, className, lineNumber, 0, null);
}
Breakpoint(VirtualMachine vm, IEventHub eventHub, String className, int lineNumber, int hitCount) {
this(vm, eventHub, className, lineNumber, hitCount, null);
}
Breakpoint(VirtualMachine vm, IEventHub eventHub, String className, int lineNumber, int hitCount, String condition) {
this(vm, eventHub, className, lineNumber, hitCount, condition, null);
}
Breakpoint(VirtualMachine vm, IEventHub eventHub, String className, int lineNumber, int hitCount, String condition, String logMessage) {
this.vm = vm;
this.eventHub = eventHub;
String contextClass = className;
String methodName = null;
String methodSignature = null;
if (className != null && className.contains("#")) {
contextClass = className.substring(0, className.indexOf("#"));
String[] methodInfo = className.substring(className.indexOf("#") + 1).split("#");
methodName = methodInfo[0];
methodSignature = methodInfo[1];
}
this.sourceLocation = new JavaBreakpointLocation(lineNumber, -1);
this.sourceLocation.setClassName(contextClass);
this.sourceLocation.setMethodName(methodName);
this.sourceLocation.setMethodSignature(methodSignature);
this.hitCount = hitCount;
this.condition = condition;
this.logMessage = logMessage;
}
Breakpoint(VirtualMachine vm, IEventHub eventHub, JavaBreakpointLocation sourceLocation, int hitCount, String condition, String logMessage) {
this.vm = vm;
this.eventHub = eventHub;
this.sourceLocation = sourceLocation;
this.hitCount = hitCount;
this.condition = condition;
this.logMessage = logMessage;
}
// IDebugResource
private List<EventRequest> requests = Collections.synchronizedList(new ArrayList<>());
private List<Disposable> subscriptions = new ArrayList<>();
@Override
public List<EventRequest> requests() {
return requests;
}
@Override
public List<Disposable> subscriptions() {
return subscriptions;
}
// AutoCloseable
@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();
}
// IBreakpoint
@Override
public JavaBreakpointLocation sourceLocation() {
return this.sourceLocation;
}
@Override
public String className() {
return this.sourceLocation.className();
}
@Override
public int getLineNumber() {
return this.sourceLocation.lineNumber();
}
@Override
public int getColumnNumber() {
return this.sourceLocation.columnNumber();
}
@Override
public String getCondition() {
return condition;
}
@Override
public int hashCode() {
return Objects.hash(sourceLocation);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Breakpoint)) {
return false;
}
Breakpoint other = (Breakpoint) obj;
return Objects.equals(sourceLocation, other.sourceLocation);
}
@Override
public int getHitCount() {
return hitCount;
}
@Override
public void setHitCount(int hitCount) {
this.hitCount = hitCount;
Observable.fromIterable(this.requests())
.filter(request -> request instanceof BreakpointRequest)
.subscribe(request -> {
request.addCountFilter(hitCount);
request.disable();
request.enable();
});
}
@Override
public void setCondition(String condition) {
this.condition = condition;
}
@Override
public void setLogMessage(String logMessage) {
this.logMessage = logMessage;
}
@Override
public String getLogMessage() {
return this.logMessage;
}
@Override
public boolean async() {
return this.async;
}
@Override
public void setAsync(boolean async) {
this.async = async;
}
@Override
public CompletableFuture<IBreakpoint> 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);
// Local types also needs to be handled
ClassPrepareRequest localClassPrepareRequest = vm.eventRequestManager().createClassPrepareRequest();
localClassPrepareRequest.addClassFilter(className() + "$*");
localClassPrepareRequest.enable();
requests.add(localClassPrepareRequest);
CompletableFuture<IBreakpoint> future = new CompletableFuture<>();
Disposable subscription = eventHub.events()
.filter(debugEvent -> debugEvent.event instanceof ClassPrepareEvent
&& (classPrepareRequest.equals(debugEvent.event.request())
|| localClassPrepareRequest.equals(debugEvent.event.request())))
.subscribe(debugEvent -> {
ClassPrepareEvent event = (ClassPrepareEvent) debugEvent.event;
List<BreakpointRequest> newRequests = AsyncJdwpUtils.await(
createBreakpointRequests(event.referenceType(), getLineNumber(), hitCount, false)
);
requests.addAll(newRequests);
if (!newRequests.isEmpty() && !future.isDone()) {
this.putProperty("verified", true);
future.complete(this);
}
});
subscriptions.add(subscription);
Runnable resolveRequestsFromExistingClasses = () -> {
List<ReferenceType> refTypes = vm.classesByName(className());
createBreakpointRequests(refTypes, getLineNumber(), hitCount, true)
.whenComplete((newRequests, ex) -> {
if (ex != null) {
return;
}
requests.addAll(newRequests);
if (!newRequests.isEmpty() && !future.isDone()) {
this.putProperty("verified", true);
future.complete(this);
}
});
};
if (async()) {
AsyncJdwpUtils.runAsync(resolveRequestsFromExistingClasses);
} else {
resolveRequestsFromExistingClasses.run();
}
return future;
}
private CompletableFuture<List<Location>> collectLocations(ReferenceType refType, int lineNumber) {
List<CompletableFuture<List<Location>>> futures = new ArrayList<>();
Iterator<Method> iter = refType.methods().iterator();
while (iter.hasNext()) {
Method method = iter.next();
if (async()) {
futures.add(AsyncJdwpUtils.supplyAsync(() -> findLocaitonsOfLine(method, lineNumber)));
} else {
futures.add(CompletableFuture.completedFuture(findLocaitonsOfLine(method, lineNumber)));
}
}
return AsyncJdwpUtils.flatAll(futures);
}
private CompletableFuture<List<Location>> collectLocations(List<ReferenceType> refTypes, int lineNumber, boolean includeNestedTypes) {
List<CompletableFuture<List<Location>>> futures = new ArrayList<>();
refTypes.forEach(refType -> {
futures.add(collectLocations(refType, lineNumber, includeNestedTypes));
});
return AsyncJdwpUtils.flatAll(futures);
}
private CompletableFuture<List<Location>> collectLocations(ReferenceType refType, int lineNumber, boolean includeNestedTypes) {
return collectLocations(refType, lineNumber).thenCompose((newLocations) -> {
if (!newLocations.isEmpty()) {
return CompletableFuture.completedFuture(newLocations);
} else if (includeNestedTypes) {
// ReferenceType.nestedTypes() will invoke vm.allClasses() to list all loaded classes,
// should avoid using nestedTypes for performance.
for (ReferenceType nestedType : refType.nestedTypes()) {
CompletableFuture<List<Location>> nestedLocationsFuture = collectLocations(nestedType, lineNumber);
List<Location> nestedLocations = nestedLocationsFuture.join();
if (!nestedLocations.isEmpty()) {
return CompletableFuture.completedFuture(nestedLocations);
}
}
}
return CompletableFuture.completedFuture(Collections.emptyList());
});
}
private CompletableFuture<List<Location>> collectLocations(List<ReferenceType> refTypes, String methodName, String methodSiguature) {
List<CompletableFuture<Location>> futures = new ArrayList<>();
for (ReferenceType refType : refTypes) {
if (async()) {
futures.add(AsyncJdwpUtils.supplyAsync(() -> findMethodLocaiton(refType, methodName, methodSiguature)));
} else {
futures.add(CompletableFuture.completedFuture(findMethodLocaiton(refType, methodName, methodSiguature)));
}
}
return AsyncJdwpUtils.all(futures);
}
private Location findMethodLocaiton(ReferenceType refType, String methodName, String methodSiguature) {
List<Method> methods = refType.methods();
Location location = null;
for (Method method : methods) {
if (!method.isAbstract() && !method.isNative()
&& methodName.equals(method.name())
&& (methodSiguature.equals(method.genericSignature()) || methodSiguature.equals(method.signature()))) {
location = method.location();
break;
}
}
return location;
}
private List<Location> findLocaitonsOfLine(Method method, int lineNumber) {
try {
return method.locationsOfLine(lineNumber);
} catch (AbsentInformationException e) {
// could be AbsentInformationException or ClassNotPreparedException
// but both are expected so no need to further handle
}
return Collections.emptyList();
}
private CompletableFuture<List<BreakpointRequest>> createBreakpointRequests(ReferenceType refType, int lineNumber, int hitCount,
boolean includeNestedTypes) {
return createBreakpointRequests(Arrays.asList(refType), lineNumber, hitCount, includeNestedTypes);
}
private CompletableFuture<List<BreakpointRequest>> createBreakpointRequests(List<ReferenceType> refTypes, int lineNumber,
int hitCount, boolean includeNestedTypes) {
CompletableFuture<List<Location>> locationsFuture;
if (this.sourceLocation.methodName() != null) {
locationsFuture = collectLocations(refTypes, this.sourceLocation.methodName(), this.sourceLocation.methodSignature());
} else {
locationsFuture = collectLocations(refTypes, lineNumber, includeNestedTypes).thenApply((locations) -> {
if (locations.isEmpty()) {
return locations;
}
/**
* For a line breakpoint, we default to breaking at the first location
* of the line. If you want to break at other locations on the same line,
* you can add an inline breakpoint based on the locations returned by
* the BreakpointLocation request.
*/
return Arrays.asList(locations.get(0));
});
}
return locationsFuture.thenCompose((locations) -> {
// find out the existing breakpoint locations
List<Location> existingLocations = new ArrayList<>(requests.size());
Observable.fromIterable(requests).filter(request -> request instanceof BreakpointRequest)
.map(request -> ((BreakpointRequest) request).location()).toList().subscribe(list -> {
existingLocations.addAll(list);
});
// remove duplicated locations
List<Location> newLocations = new ArrayList<>(locations.size());
Observable.fromIterable(locations).filter(location -> !existingLocations.contains(location)).toList().subscribe(list -> {
newLocations.addAll(list);
});
List<BreakpointRequest> newRequests = new ArrayList<>(newLocations.size());
newLocations.forEach(location -> {
BreakpointRequest request = vm.eventRequestManager().createBreakpointRequest(location);
request.setSuspendPolicy(BreakpointRequest.SUSPEND_EVENT_THREAD);
if (hitCount > 0) {
request.addCountFilter(hitCount);
}
request.putProperty(IBreakpoint.REQUEST_TYPE, computeRequestType());
newRequests.add(request);
});
List<CompletableFuture<Void>> futures = new ArrayList<>();
for (BreakpointRequest request : newRequests) {
if (async()) {
futures.add(AsyncJdwpUtils.runAsync(() -> {
try {
request.enable();
} catch (VMDisconnectedException ex) {
// enable breakpoint operation may be executing while JVM is terminating, thus the VMDisconnectedException may be
// possible, in case of VMDisconnectedException, this method will return an empty array which turns out a valid
// response in vscode, causing no error log in trace.
}
}));
} else {
try {
request.enable();
} catch (VMDisconnectedException ex) {
// enable breakpoint operation may be executing while JVM is terminating, thus the VMDisconnectedException may be
// possible, in case of VMDisconnectedException, this method will return an empty array which turns out a valid
// response in vscode, causing no error log in trace.
}
}
}
return AsyncJdwpUtils.all(futures).thenApply((res) -> newRequests);
});
}
private Object computeRequestType() {
if (this.sourceLocation.methodName() == null) {
return IBreakpoint.REQUEST_TYPE_LINE;
}
if (this.sourceLocation.methodName().startsWith("lambda$")) {
return IBreakpoint.REQUEST_TYPE_LAMBDA;
} else {
return IBreakpoint.REQUEST_TYPE_METHOD;
}
}
@Override
public void putProperty(Object key, Object value) {
propertyMap.put(key, value);
}
@Override
public Object getProperty(Object key) {
return propertyMap.get(key);
}
}

View File

@ -0,0 +1,18 @@
/*******************************************************************************
* Copyright (c) 2017 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;
public class Configuration {
public static final String LOGGER_NAME = "java-debug";
public static final String USAGE_DATA_LOGGER_NAME = "java-debug-usage-data";
}

View File

@ -0,0 +1,21 @@
/*******************************************************************************
* Copyright (c) 2017 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 com.sun.jdi.event.Event;
import com.sun.jdi.event.EventSet;
public class DebugEvent {
public Event event = null;
public EventSet eventSet = null;
public boolean shouldResume = true;
}

View File

@ -0,0 +1,74 @@
/*******************************************************************************
* Copyright (c) 2017 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;
public class DebugException extends Exception {
private static final long serialVersionUID = 1L;
private int errorCode;
private boolean userError = false;
public DebugException() {
super();
}
public DebugException(String message) {
super(message);
}
public DebugException(String message, Throwable cause) {
super(message, cause);
}
public DebugException(Throwable cause) {
super(cause);
}
public DebugException(String message, int errorCode) {
super(message);
this.errorCode = errorCode;
}
/**
* Create a debug exception with userError flag.
* @param message the error message
* @param errorCode the error code
* @param userError the boolean value indicating whether this exception is caused by a known user error
*/
public DebugException(String message, int errorCode, boolean userError) {
super(message);
this.errorCode = errorCode;
this.userError = userError;
}
public DebugException(String message, Throwable cause, int errorCode) {
super(message, cause);
this.errorCode = errorCode;
}
public DebugException(Throwable cause, int errorCode) {
super(cause);
this.errorCode = errorCode;
}
public int getErrorCode() {
return this.errorCode;
}
public void setUserError(boolean userError) {
this.userError = userError;
}
public boolean isUserError() {
return this.userError;
}
}

View File

@ -0,0 +1,198 @@
/*******************************************************************************
* Copyright (c) 2017-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:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/
package com.microsoft.java.debug.core;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import com.sun.jdi.ObjectCollectedException;
import com.sun.jdi.ThreadReference;
import com.sun.jdi.VirtualMachine;
import com.sun.jdi.request.EventRequest;
import com.sun.jdi.request.EventRequestManager;
import com.sun.jdi.request.ExceptionRequest;
public class DebugSession implements IDebugSession {
private VirtualMachine vm;
private EventHub eventHub = new EventHub();
public DebugSession(VirtualMachine virtualMachine) {
vm = virtualMachine;
}
@Override
public void start() {
boolean supportsVirtualThreads = mayCreateVirtualThreads();
// request thread events by default
EventRequest threadStartRequest = vm.eventRequestManager().createThreadStartRequest();
threadStartRequest.setSuspendPolicy(EventRequest.SUSPEND_NONE);
if (supportsVirtualThreads) {
addPlatformThreadsOnlyFilter(threadStartRequest);
}
threadStartRequest.enable();
EventRequest threadDeathRequest = vm.eventRequestManager().createThreadDeathRequest();
threadDeathRequest.setSuspendPolicy(EventRequest.SUSPEND_NONE);
if (supportsVirtualThreads) {
addPlatformThreadsOnlyFilter(threadDeathRequest);
}
threadDeathRequest.enable();
eventHub.start(vm);
}
private boolean mayCreateVirtualThreads() {
try {
Method method = vm.getClass().getMethod("mayCreateVirtualThreads");
return (boolean) method.invoke(vm);
} catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
// ignore
}
return false;
}
/**
* For thread start and thread death events, restrict the events so they are only sent for platform threads.
*/
private void addPlatformThreadsOnlyFilter(EventRequest threadLifecycleRequest) {
try {
Method method = threadLifecycleRequest.getClass().getMethod("addPlatformThreadsOnlyFilter");
method.invoke(threadLifecycleRequest);
} catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
// ignore
}
}
@Override
public void suspend() {
vm.suspend();
}
@Override
public void resume() {
/**
* To ensure that all threads are fully resumed when the VM is resumed, make sure the suspend count
* of each thread is no larger than 1.
* Notes: Decrementing the thread' suspend count to 1 is on purpose, because it doesn't break the
* the thread's suspend state, and also make sure the next instruction vm.resume() is able to resume
* all threads fully.
*/
for (ThreadReference tr : DebugUtility.getAllThreadsSafely(this)) {
try {
while (tr.suspendCount() > 1) {
tr.resume();
}
} catch (ObjectCollectedException ex) {
// Skip it if the thread is garbage collected.
}
}
vm.resume();
}
@Override
public void detach() {
vm.dispose();
}
@Override
public void terminate() {
if (vm.process() == null || vm.process().isAlive()) {
vm.exit(0);
}
}
@Override
public IBreakpoint createBreakpoint(JavaBreakpointLocation sourceLocation, int hitCount, String condition, String logMessage) {
return new EvaluatableBreakpoint(vm, this.getEventHub(), sourceLocation, hitCount, condition, logMessage);
}
@Override
public IBreakpoint createBreakpoint(String className, int lineNumber, int hitCount, String condition, String logMessage) {
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) {
setExceptionBreakpoints(notifyCaught, notifyUncaught, null, null);
}
@Override
public void setExceptionBreakpoints(boolean notifyCaught, boolean notifyUncaught, String[] classFilters, String[] classExclusionFilters) {
EventRequestManager manager = vm.eventRequestManager();
ArrayList<ExceptionRequest> legacy = new ArrayList<>(manager.exceptionRequests());
manager.deleteEventRequests(legacy);
// When no exception breakpoints are requested, no need to create an empty exception request.
if (notifyCaught || notifyUncaught) {
// from: https://www.javatips.net/api/REPLmode-master/src/jm/mode/replmode/REPLRunner.java
// Calling this seems to set something internally to make the
// Eclipse JDI wake up. Without it, an ObjectCollectedException
// is thrown on request.enable(). No idea why this works,
// but at least exception handling has returned. (Suspect that it may
// block until all or at least some threads are available, meaning
// that the app has launched and we have legit objects to talk to).
vm.allThreads();
// The bug may not have been noticed because the test suite waits for
// a thread to be available, and queries it by calling allThreads().
// See org.eclipse.debug.jdi.tests.AbstractJDITest for the example.
// get only the uncaught exceptions
ExceptionRequest request = manager.createExceptionRequest(null, notifyCaught, notifyUncaught);
request.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD);
if (classFilters != null) {
for (String classFilter : classFilters) {
request.addClassFilter(classFilter);
}
}
if (classExclusionFilters != null) {
for (String exclusionFilter : classExclusionFilters) {
request.addClassExclusionFilter(exclusionFilter);
}
}
request.enable();
}
}
@Override
public Process process() {
return vm.process();
}
@Override
public List<ThreadReference> getAllThreads() {
return vm.allThreads();
}
@Override
public IEventHub getEventHub() {
return eventHub;
}
@Override
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);
}
}

View File

@ -0,0 +1,103 @@
/*******************************************************************************
* Copyright (c) 2017-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:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/
package com.microsoft.java.debug.core;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;
import com.google.gson.JsonSyntaxException;
import com.google.gson.annotations.SerializedName;
import com.microsoft.java.debug.core.protocol.JsonUtils;
import com.microsoft.java.debug.core.protocol.Requests.ClassFilters;
import com.microsoft.java.debug.core.protocol.Requests.StepFilters;
public final class DebugSettings {
private static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME);
private static Set<IDebugSettingChangeListener> listeners =
Collections.newSetFromMap(new ConcurrentHashMap<IDebugSettingChangeListener, Boolean>());
private static DebugSettings current = new DebugSettings();
public int maxStringLength = 0;
public int numericPrecision = 0;
public boolean showStaticVariables = false;
public boolean showQualifiedNames = false;
public boolean showHex = false;
public boolean showLogicalStructure = true;
public boolean showToString = true;
public String logLevel;
public String javaHome;
public HotCodeReplace hotCodeReplace = HotCodeReplace.MANUAL;
public StepFilters stepFilters = new StepFilters();
public ClassFilters exceptionFilters = new ClassFilters();
public boolean exceptionFiltersUpdated = false;
public int limitOfVariablesPerJdwpRequest = 100;
public int jdwpRequestTimeout = 3000;
public AsyncMode asyncJDWP = AsyncMode.OFF;
public static DebugSettings getCurrent() {
return current;
}
/**
* Update current settings with the values in the parameter.
*
* @param jsonSettings
* the new settings represents in json format.
*/
public void updateSettings(String jsonSettings) {
try {
DebugSettings oldSettings = current;
current = JsonUtils.fromJson(jsonSettings, DebugSettings.class);
for (IDebugSettingChangeListener listener : listeners) {
listener.update(oldSettings, current);
}
} catch (JsonSyntaxException ex) {
logger.severe(String.format("Invalid json for debugSettings: %s, %s", jsonSettings, ex.getMessage()));
}
}
private DebugSettings() {
}
public static boolean addDebugSettingChangeListener(IDebugSettingChangeListener listener) {
return listeners.add(listener);
}
public static boolean removeDebugSettingChangeListener(IDebugSettingChangeListener listener) {
return listeners.remove(listener);
}
public static enum HotCodeReplace {
@SerializedName("manual")
MANUAL,
@SerializedName("auto")
AUTO,
@SerializedName("never")
NEVER
}
public static enum AsyncMode {
@SerializedName("auto")
AUTO,
@SerializedName("on")
ON,
@SerializedName("off")
OFF
}
public static interface IDebugSettingChangeListener {
public void update(DebugSettings oldSettings, DebugSettings newSettings);
}
}

View File

@ -0,0 +1,796 @@
/*******************************************************************************
* Copyright (c) 2017 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.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import org.apache.commons.lang3.StringUtils;
import com.microsoft.java.debug.core.adapter.AdapterUtils;
import com.sun.jdi.Method;
import com.sun.jdi.ObjectCollectedException;
import com.sun.jdi.ThreadReference;
import com.sun.jdi.VMDisconnectedException;
import com.sun.jdi.VirtualMachine;
import com.sun.jdi.VirtualMachineManager;
import com.sun.jdi.connect.AttachingConnector;
import com.sun.jdi.connect.Connector.Argument;
import com.sun.jdi.connect.IllegalConnectorArgumentsException;
import com.sun.jdi.connect.LaunchingConnector;
import com.sun.jdi.connect.VMStartException;
import com.sun.jdi.event.MethodEntryEvent;
import com.sun.jdi.request.EventRequest;
import com.sun.jdi.request.EventRequestManager;
import com.sun.jdi.request.MethodEntryRequest;
import com.sun.jdi.request.StepRequest;
public class DebugUtility {
public static final String HOME = "home";
public static final String OPTIONS = "options";
public static final String MAIN = "main";
public static final String SUSPEND = "suspend";
public static final String QUOTE = "quote";
public static final String EXEC = "vmexec";
public static final String CWD = "cwd";
public static final String ENV = "env";
public static final String HOSTNAME = "hostname";
public static final String PORT = "port";
public static final String TIMEOUT = "timeout";
/**
* Launch a debuggee in suspend mode.
* @see #launch(VirtualMachineManager, String, String, String, String, String, String, String[])
*/
public static IDebugSession launch(VirtualMachineManager vmManager,
String mainClass,
String programArguments,
String vmArguments,
List<String> modulePaths,
List<String> classPaths,
String cwd,
String[] envVars)
throws IOException, IllegalConnectorArgumentsException, VMStartException {
return DebugUtility.launch(vmManager,
mainClass,
programArguments,
vmArguments,
String.join(File.pathSeparator, modulePaths),
String.join(File.pathSeparator, classPaths),
cwd,
envVars);
}
/**
* Launch a debuggee in suspend mode.
* @see #launch(VirtualMachineManager, String, String, String, String, String, String, String[], String)
*/
public static IDebugSession launch(VirtualMachineManager vmManager,
String mainClass,
String programArguments,
String vmArguments,
List<String> modulePaths,
List<String> classPaths,
String cwd,
String[] envVars,
String javaExec)
throws IOException, IllegalConnectorArgumentsException, VMStartException {
return DebugUtility.launch(vmManager,
mainClass,
programArguments,
vmArguments,
String.join(File.pathSeparator, modulePaths),
String.join(File.pathSeparator, classPaths),
cwd,
envVars,
javaExec);
}
/**
* Launches a debuggee in suspend mode.
*
* @param vmManager
* the virtual machine manager.
* @param mainClass
* the main class.
* @param programArguments
* the program arguments.
* @param vmArguments
* the vm arguments.
* @param modulePaths
* the module paths.
* @param classPaths
* the class paths.
* @param cwd
* the working directory of the program.
* @param envVars
* array of strings, each element of which has environment variable settings in the format name=value.
* or null if the subprocess should inherit the environment of the current process.
* @return an instance of IDebugSession.
* @throws IOException
* when unable to launch.
* @throws IllegalConnectorArgumentsException
* when one of the arguments is invalid.
* @throws VMStartException
* when the debuggee was successfully launched, but terminated
* with an error before a connection could be established.
*/
public static IDebugSession launch(VirtualMachineManager vmManager,
String mainClass,
String programArguments,
String vmArguments,
String modulePaths,
String classPaths,
String cwd,
String[] envVars)
throws IOException, IllegalConnectorArgumentsException, VMStartException {
return launch(vmManager, mainClass, programArguments, vmArguments, modulePaths, classPaths, cwd, envVars, null);
}
/**
* Launches a debuggee in suspend mode.
*
* @param vmManager
* the virtual machine manager.
* @param mainClass
* the main class.
* @param programArguments
* the program arguments.
* @param vmArguments
* the vm arguments.
* @param modulePaths
* the module paths.
* @param classPaths
* the class paths.
* @param cwd
* the working directory of the program.
* @param envVars
* array of strings, each element of which has environment variable settings in the format name=value.
* or null if the subprocess should inherit the environment of the current process.
* @param javaExec
* the java executable path. If not defined, then resolve from java home.
* @return an instance of IDebugSession.
* @throws IOException
* when unable to launch.
* @throws IllegalConnectorArgumentsException
* when one of the arguments is invalid.
* @throws VMStartException
* when the debuggee was successfully launched, but terminated
* with an error before a connection could be established.
*/
public static IDebugSession launch(VirtualMachineManager vmManager,
String mainClass,
String programArguments,
String vmArguments,
String modulePaths,
String classPaths,
String cwd,
String[] envVars,
String javaExec)
throws IOException, IllegalConnectorArgumentsException, VMStartException {
List<LaunchingConnector> connectors = vmManager.launchingConnectors();
LaunchingConnector connector = connectors.get(0);
/** In the sun JDK 10, the first launching connector is com.sun.tools.jdi.RawCommandLineLauncher, which is not the one we want to use.
* Add the logic to filter the right one from LaunchingConnector list.
* This fix is only for the JDI implementation by JDK. Other JDI implementations (such as JDT) doesn't have the impact.
*/
final String SUN_LAUNCHING_CONNECTOR = "com.sun.tools.jdi.SunCommandLineLauncher";
for (LaunchingConnector con : connectors) {
if (con.getClass().getName().equals(SUN_LAUNCHING_CONNECTOR)) {
connector = con;
break;
}
}
Map<String, Argument> arguments = connector.defaultArguments();
arguments.get(SUSPEND).setValue("true");
String options = "";
if (StringUtils.isNotBlank(vmArguments)) {
options = vmArguments;
}
if (StringUtils.isNotBlank(modulePaths)) {
options += " --module-path \"" + modulePaths + "\"";
}
if (StringUtils.isNotBlank(classPaths)) {
options += " -cp \"" + classPaths + "\"";
}
arguments.get(OPTIONS).setValue(options);
// For java 9 project, should specify "-m $MainClass".
String[] mainClasses = mainClass.split("/");
if (mainClasses.length == 2) {
mainClass = "-m " + mainClass;
}
if (StringUtils.isNotBlank(programArguments)) {
mainClass += " " + programArguments;
}
arguments.get(MAIN).setValue(mainClass);
if (arguments.get(CWD) != null) {
arguments.get(CWD).setValue(cwd);
}
if (arguments.get(ENV) != null) {
arguments.get(ENV).setValue(encodeArrayArgument(envVars));
}
if (isValidJavaExec(javaExec)) {
String vmExec = new File(javaExec).getName();
String javaHome = new File(javaExec).getParentFile().getParentFile().getAbsolutePath();
arguments.get(HOME).setValue(javaHome);
arguments.get(EXEC).setValue(vmExec);
} else if (StringUtils.isNotEmpty(DebugSettings.getCurrent().javaHome)) {
arguments.get(HOME).setValue(DebugSettings.getCurrent().javaHome);
}
VirtualMachine vm = connector.launch(arguments);
// workaround for JDT bug.
// vm.version() calls org.eclipse.jdi.internal.MirrorImpl#requestVM
// It calls vm.getIDSizes() to read related sizes including ReferenceTypeIdSize,
// which is required to construct requests with null ReferenceType (such as ExceptionRequest)
// Without this line, it throws ObjectCollectedException in ExceptionRequest.enable().
// See https://github.com/Microsoft/java-debug/issues/23
vm.version();
return new DebugSession(vm);
}
private static boolean isValidJavaExec(String javaExec) {
if (StringUtils.isBlank(javaExec)) {
return false;
}
File file = new File(javaExec);
if (!file.exists() || !file.isFile()) {
return false;
}
return Files.isExecutable(file.toPath())
&& Objects.equals(file.getParentFile().getName(), "bin");
}
/**
* Attach to an existing debuggee VM.
* @param vmManager
* the virtual machine manager
* @param hostName
* the machine where the debuggee VM is launched on
* @param port
* the debug port that the debuggee VM exposed
* @param attachTimeout
* the timeout when attaching to the debuggee VM
* @return an instance of IDebugSession
* @throws IOException
* when unable to attach.
* @throws IllegalConnectorArgumentsException
* when one of the connector arguments is invalid.
*/
public static IDebugSession attach(VirtualMachineManager vmManager, String hostName, int port, int attachTimeout)
throws IOException, IllegalConnectorArgumentsException {
List<AttachingConnector> connectors = vmManager.attachingConnectors();
AttachingConnector connector = connectors.get(0);
// in JDK 10, the first AttachingConnector is not the one we want
final String SUN_ATTACH_CONNECTOR = "com.sun.tools.jdi.SocketAttachingConnector";
for (AttachingConnector con : connectors) {
if (con.getClass().getName().equals(SUN_ATTACH_CONNECTOR)) {
connector = con;
break;
}
}
Map<String, Argument> arguments = connector.defaultArguments();
arguments.get(HOSTNAME).setValue(hostName);
arguments.get(PORT).setValue(String.valueOf(port));
arguments.get(TIMEOUT).setValue(String.valueOf(attachTimeout));
return new DebugSession(connector.attach(arguments));
}
/**
* Create a step over request on the specified thread.
* @param thread
* the target thread.
* @param stepFilters
* the step filters when stepping.
* @return the new step request.
*/
public static StepRequest createStepOverRequest(ThreadReference thread, String[] stepFilters) {
return createStepOverRequest(thread, null, stepFilters);
}
/**
* Create a step over request on the specified thread.
* @param thread
* the target thread.
* @param classFilters
* restricts the step event to those matching the given class patterns when stepping.
* @param classExclusionFilters
* restricts the step event to those not matching the given class patterns when stepping.
* @return the new step request.
*/
public static StepRequest createStepOverRequest(ThreadReference thread, String[] classFilters, String[] classExclusionFilters) {
return createStepRequest(thread, StepRequest.STEP_LINE, StepRequest.STEP_OVER, classFilters, classExclusionFilters);
}
/**
* Create a step into request on the specified thread.
* @param thread
* the target thread.
* @param stepFilters
* the step filters when stepping.
* @return the new step request.
*/
public static StepRequest createStepIntoRequest(ThreadReference thread, String[] stepFilters) {
return createStepIntoRequest(thread, null, stepFilters);
}
/**
* Create a step into request on the specified thread.
* @param thread
* the target thread.
* @param classFilters
* restricts the step event to those matching the given class patterns when stepping.
* @param classExclusionFilters
* restricts the step event to those not matching the given class patterns when stepping.
* @return the new step request.
*/
public static StepRequest createStepIntoRequest(ThreadReference thread, String[] classFilters, String[] classExclusionFilters) {
return createStepRequest(thread, StepRequest.STEP_LINE, StepRequest.STEP_INTO, classFilters, classExclusionFilters);
}
/**
* Create a step out request on the specified thread.
* @param thread
* the target thread.
* @param stepFilters
* the step filters when stepping.
* @return the new step request.
*/
public static StepRequest createStepOutRequest(ThreadReference thread, String[] stepFilters) {
return createStepOutRequest(thread, null, stepFilters);
}
/**
* Create a step out request on the specified thread.
* @param thread
* the target thread.
* @param classFilters
* restricts the step event to those matching the given class patterns when stepping.
* @param classExclusionFilters
* restricts the step event to those not matching the given class patterns when stepping.
* @return the new step request.
*/
public static StepRequest createStepOutRequest(ThreadReference thread, String[] classFilters, String[] classExclusionFilters) {
return createStepRequest(thread, StepRequest.STEP_LINE, StepRequest.STEP_OUT, classFilters, classExclusionFilters);
}
private static StepRequest createStepRequest(ThreadReference thread, int stepSize, int stepDepth, String[] classFilters, String[] classExclusionFilters) {
StepRequest request = thread.virtualMachine().eventRequestManager().createStepRequest(thread, stepSize, stepDepth);
if (classFilters != null) {
for (String classFilter : classFilters) {
request.addClassFilter(classFilter);
}
}
if (classExclusionFilters != null) {
for (String exclusionFilter : classExclusionFilters) {
request.addClassExclusionFilter(exclusionFilter);
}
}
request.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD);
request.addCountFilter(1);
return request;
}
/**
* Suspend the main thread when the program enters the main method of the specified main class.
* @param debugSession
* the debug session.
* @param mainClass
* the fully qualified name of the main class.
* @return
* a {@link CompletableFuture} that contains the suspended main thread id.
*/
public static CompletableFuture<Long> stopOnEntry(IDebugSession debugSession, String mainClass) {
CompletableFuture<Long> future = new CompletableFuture<>();
EventRequestManager manager = debugSession.getVM().eventRequestManager();
MethodEntryRequest request = manager.createMethodEntryRequest();
request.addClassFilter(mainClass);
request.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD);
debugSession.getEventHub().events().filter(debugEvent -> {
return debugEvent.event instanceof MethodEntryEvent && request.equals(debugEvent.event.request());
}).subscribe(debugEvent -> {
Method method = ((MethodEntryEvent) debugEvent.event).method();
if (method.isPublic() && method.isStatic() && method.name().equals("main")
&& method.signature().equals("([Ljava/lang/String;)V")) {
deleteEventRequestSafely(debugSession.getVM().eventRequestManager(), request);
debugEvent.shouldResume = false;
ThreadReference bpThread = ((MethodEntryEvent) debugEvent.event).thread();
future.complete(bpThread.uniqueID());
}
});
request.enable();
return future;
}
/**
* Get the ThreadReference instance by the thread id.
* @param debugSession
* the debug session
* @param threadId
* the thread id
* @return the ThreadReference instance
*/
public static ThreadReference getThread(IDebugSession debugSession, long threadId) {
for (ThreadReference thread : getAllThreadsSafely(debugSession)) {
if (thread.uniqueID() == threadId) {
return thread;
}
}
return null;
}
/**
* Get the available ThreadReference list in the debug session.
* If the debug session has terminated, return an empty list instead of VMDisconnectedException.
* @param debugSession
* the debug session
* @return the available ThreadReference list
*/
public static List<ThreadReference> getAllThreadsSafely(IDebugSession debugSession) {
if (debugSession != null) {
try {
return debugSession.getAllThreads();
} catch (VMDisconnectedException ex) {
// do nothing.
}
}
return new ArrayList<>();
}
/**
* Resume the thread the times as it has been suspended.
*
* @param thread
* the thread reference
*/
public static void resumeThread(ThreadReference thread) {
// if thread is not found or is garbage collected, do nothing
if (thread == null) {
return;
}
try {
int suspends = thread.suspendCount();
for (int i = 0; i < suspends; i++) {
/**
* Invoking this method will decrement the count of pending suspends on this thread.
* If it is decremented to 0, the thread will continue to execute.
*/
thread.resume();
}
} catch (ObjectCollectedException ex) {
// ObjectCollectionException can be thrown if the thread has already completed (exited) in the VM when calling suspendCount,
// the resume operation to this thread is meanness.
}
}
public static void resumeThread(ThreadReference thread, int resumeCount) {
if (thread == null) {
return;
}
try {
for (int i = 0; i < resumeCount; i++) {
/**
* Invoking this method will decrement the count of pending suspends on this thread.
* If it is decremented to 0, the thread will continue to execute.
*/
thread.resume();
}
} catch (ObjectCollectedException ex) {
// ObjectCollectionException can be thrown if the thread has already completed (exited) in the VM when calling suspendCount,
// the resume operation to this thread is meanness.
}
}
/**
* Remove the event request from the vm. If the vm has terminated, do nothing.
* @param eventManager
* The event request manager.
* @param request
* The target event request.
*/
public static void deleteEventRequestSafely(EventRequestManager eventManager, EventRequest request) {
try {
eventManager.deleteEventRequest(request);
} catch (VMDisconnectedException ex) {
// ignore.
}
}
/**
* Remove the event request list from the vm. If the vm has terminated, do nothing.
* @param eventManager
* The event request manager.
* @param requests
* The target event request list.
*/
public static void deleteEventRequestSafely(EventRequestManager eventManager, List<EventRequest> requests) {
try {
eventManager.deleteEventRequests(requests);
} catch (VMDisconnectedException ex) {
// ignore.
}
}
/**
* Encode an string array to a string as the follows.
*
* <p>source argument:
* <pre>["path=C:\\ProgramFiles\\java\\bin", "JAVA_HOME=C:\\ProgramFiles\\java"]</pre>
*
* <p>after encoded:
* <pre>"path%3DC%3A%5CProgramFiles%5Cjava%5Cbin\nJAVA_HOME%3DC%3A%5CProgramFiles%5Cjava"</pre>
*
* @param argument the string array arguments
* @return the encoded string
*/
public static String encodeArrayArgument(String[] argument) {
if (argument == null) {
return null;
}
List<String> encodedArgs = new ArrayList<>();
for (String arg : argument) {
try {
encodedArgs.add(URLEncoder.encode(arg, StandardCharsets.UTF_8.name()));
} catch (UnsupportedEncodingException e) {
// do nothing.
}
}
return String.join("\n", encodedArgs);
}
/**
* Decode the encoded string to the original string array by the rules defined in encodeArrayArgument.
*
* @param argument the encoded string
* @return the original string array argument
*/
public static String[] decodeArrayArgument(String argument) {
if (argument == null) {
return null;
}
List<String> result = new ArrayList<>();
String[] splits = argument.split("\n");
for (String split : splits) {
try {
result.add(URLDecoder.decode(split, StandardCharsets.UTF_8.name()));
} catch (UnsupportedEncodingException e) {
// do nothing.
}
}
return result.toArray(new String[0]);
}
/**
* Parses the given command line into separate arguments that can be passed
* to <code>Runtime.getRuntime().exec(cmdArray)</code>.
*
* @param cmdStr command line as a single string.
* @return the individual arguments.
*/
public static List<String> parseArguments(String cmdStr) {
if (cmdStr == null) {
return new ArrayList<>();
}
return AdapterUtils.isWindows() ? parseArgumentsWindows(cmdStr) : parseArgumentsNonWindows(cmdStr);
}
/**
* Parses the given command line into separate arguments for mac/linux platform.
* This piece of code is mainly copied from
* https://github.com/eclipse/eclipse.platform.debug/blob/master/org.eclipse.debug.core/core/org/eclipse/debug/core/DebugPlugin.java#L1374
*
* @param args
* the command line arguments as a single string.
* @return the individual arguments
*/
private static List<String> parseArgumentsNonWindows(String args) {
// man sh, see topic QUOTING
List<String> result = new ArrayList<>();
final int DEFAULT = 0;
final int ARG = 1;
final int IN_DOUBLE_QUOTE = 2;
final int IN_SINGLE_QUOTE = 3;
int state = DEFAULT;
StringBuilder buf = new StringBuilder();
int len = args.length();
for (int i = 0; i < len; i++) {
char ch = args.charAt(i);
if (Character.isWhitespace(ch)) {
if (state == DEFAULT) {
// skip
continue;
} else if (state == ARG) {
state = DEFAULT;
result.add(buf.toString());
buf.setLength(0);
continue;
}
}
switch (state) {
case DEFAULT:
case ARG:
if (ch == '"') {
state = IN_DOUBLE_QUOTE;
} else if (ch == '\'') {
state = IN_SINGLE_QUOTE;
} else if (ch == '\\' && i + 1 < len) {
state = ARG;
ch = args.charAt(++i);
buf.append(ch);
} else {
state = ARG;
buf.append(ch);
}
break;
case IN_DOUBLE_QUOTE:
if (ch == '"') {
state = ARG;
} else if (ch == '\\' && i + 1 < len && (args.charAt(i + 1) == '\\' || args.charAt(i + 1) == '"')) {
ch = args.charAt(++i);
buf.append(ch);
} else {
buf.append(ch);
}
break;
case IN_SINGLE_QUOTE:
if (ch == '\'') {
state = ARG;
} else {
buf.append(ch);
}
break;
default:
throw new IllegalStateException();
}
}
if (buf.length() > 0 || state != DEFAULT) {
result.add(buf.toString());
}
return result;
}
/**
* Parses the given command line into separate arguments for windows platform.
* This piece of code is mainly copied from
* https://github.com/eclipse/eclipse.platform.debug/blob/master/org.eclipse.debug.core/core/org/eclipse/debug/core/DebugPlugin.java#L1264
*
* @param args
* the command line arguments as a single string.
* @return the individual arguments
*/
private static List<String> parseArgumentsWindows(String args) {
// see http://msdn.microsoft.com/en-us/library/a1y7w461.aspx
List<String> result = new ArrayList<>();
final int DEFAULT = 0;
final int ARG = 1;
final int IN_DOUBLE_QUOTE = 2;
int state = DEFAULT;
int backslashes = 0;
StringBuilder buf = new StringBuilder();
int len = args.length();
for (int i = 0; i < len; i++) {
char ch = args.charAt(i);
if (ch == '\\') {
backslashes++;
continue;
} else if (backslashes != 0) {
if (ch == '"') {
for (; backslashes >= 2; backslashes -= 2) {
buf.append('\\');
}
if (backslashes == 1) {
if (state == DEFAULT) {
state = ARG;
}
buf.append('"');
backslashes = 0;
continue;
} // else fall through to switch
} else {
// false alarm, treat passed backslashes literally...
if (state == DEFAULT) {
state = ARG;
}
for (; backslashes > 0; backslashes--) {
buf.append('\\');
}
// fall through to switch
}
}
if (Character.isWhitespace(ch)) {
if (state == DEFAULT) {
// skip
continue;
} else if (state == ARG) {
state = DEFAULT;
result.add(buf.toString());
buf.setLength(0);
continue;
}
}
switch (state) {
case DEFAULT:
case ARG:
if (ch == '"') {
state = IN_DOUBLE_QUOTE;
} else {
state = ARG;
buf.append(ch);
}
break;
case IN_DOUBLE_QUOTE:
if (ch == '"') {
if (i + 1 < len && args.charAt(i + 1) == '"') {
/* Undocumented feature in Windows:
* Two consecutive double quotes inside a double-quoted argument are interpreted as
* a single double quote.
*/
buf.append('"');
i++;
} else if (buf.length() == 0) {
// empty string on Windows platform. Account for bug in constructor of JDK's java.lang.ProcessImpl.
result.add("\"\""); //$NON-NLS-1$
state = DEFAULT;
} else {
state = ARG;
}
} else {
buf.append(ch);
}
break;
default:
throw new IllegalStateException();
}
}
if (buf.length() > 0 || state != DEFAULT) {
result.add(buf.toString());
}
return result;
}
}

View File

@ -0,0 +1,128 @@
/*******************************************************************************
* Copyright (c) 2018-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:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/
package com.microsoft.java.debug.core;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.lang3.StringUtils;
import com.sun.jdi.ThreadReference;
import com.sun.jdi.VirtualMachine;
import com.sun.jdi.event.ThreadDeathEvent;
import io.reactivex.disposables.Disposable;
public class EvaluatableBreakpoint extends Breakpoint implements IEvaluatableBreakpoint {
private IEventHub eventHub = null;
private Object compiledConditionalExpression = null;
private Object compiledLogpointExpression = null;
private Map<Long, Object> compiledExpressions = new ConcurrentHashMap<>();
EvaluatableBreakpoint(VirtualMachine vm, IEventHub eventHub, String className, int lineNumber) {
this(vm, eventHub, className, lineNumber, 0, null);
}
EvaluatableBreakpoint(VirtualMachine vm, IEventHub eventHub, String className, int lineNumber, int hitCount) {
this(vm, eventHub, className, lineNumber, hitCount, null);
}
EvaluatableBreakpoint(VirtualMachine vm, IEventHub eventHub, String className, int lineNumber, int hitCount,
String condition) {
this(vm, eventHub, className, lineNumber, hitCount, condition, null);
}
EvaluatableBreakpoint(VirtualMachine vm, IEventHub eventHub, String className, int lineNumber, int hitCount,
String condition, String logMessage) {
super(vm, eventHub, className, lineNumber, hitCount, condition, logMessage);
this.eventHub = eventHub;
}
EvaluatableBreakpoint(VirtualMachine vm, IEventHub eventHub, JavaBreakpointLocation sourceLocation, int hitCount,
String condition, String logMessage) {
super(vm, eventHub, sourceLocation, hitCount, condition, logMessage);
this.eventHub = eventHub;
}
@Override
public boolean containsEvaluatableExpression() {
return containsConditionalExpression() || containsLogpointExpression();
}
@Override
public boolean containsConditionalExpression() {
return StringUtils.isNotBlank(getCondition());
}
@Override
public boolean containsLogpointExpression() {
return StringUtils.isNotBlank(getLogMessage());
}
@Override
public void setCompiledConditionalExpression(Object compiledExpression) {
this.compiledConditionalExpression = compiledExpression;
}
@Override
public Object getCompiledConditionalExpression() {
return compiledConditionalExpression;
}
@Override
public void setCompiledLogpointExpression(Object compiledExpression) {
this.compiledLogpointExpression = compiledExpression;
}
@Override
public Object getCompiledLogpointExpression() {
return compiledLogpointExpression;
}
@Override
public void setCondition(String condition) {
super.setCondition(condition);
setCompiledConditionalExpression(null);
compiledExpressions.clear();
}
@Override
public void setLogMessage(String logMessage) {
super.setLogMessage(logMessage);
setCompiledLogpointExpression(null);
compiledExpressions.clear();
}
@Override
public Object getCompiledExpression(long threadId) {
return compiledExpressions.get(threadId);
}
@Override
public void setCompiledExpression(long threadId, Object compiledExpression) {
compiledExpressions.put(threadId, compiledExpression);
}
@Override
public CompletableFuture<IBreakpoint> install() {
Disposable subscription = eventHub.events()
.filter(debugEvent -> debugEvent.event instanceof ThreadDeathEvent)
.subscribe(debugEvent -> {
ThreadReference deathThread = ((ThreadDeathEvent) debugEvent.event).thread();
compiledExpressions.remove(deathThread.uniqueID());
});
super.subscriptions().add(subscription);
return super.install();
}
}

View File

@ -0,0 +1,158 @@
/*******************************************************************************
* Copyright (c) 2017 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.logging.Logger;
import com.sun.jdi.VMDisconnectedException;
import com.sun.jdi.VirtualMachine;
import com.sun.jdi.event.BreakpointEvent;
import com.sun.jdi.event.Event;
import com.sun.jdi.event.EventQueue;
import com.sun.jdi.event.EventSet;
import com.sun.jdi.event.ExceptionEvent;
import com.sun.jdi.event.StepEvent;
import com.sun.jdi.event.ThreadDeathEvent;
import com.sun.jdi.event.ThreadStartEvent;
import com.sun.jdi.event.VMDeathEvent;
import com.sun.jdi.event.VMDisconnectEvent;
import com.sun.jdi.event.VMStartEvent;
import io.reactivex.Observable;
import io.reactivex.subjects.PublishSubject;
public class EventHub implements IEventHub {
private static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME);
private PublishSubject<DebugEvent> subject = PublishSubject.<DebugEvent>create();
@Override
public Observable<DebugEvent> events() {
return subject;
}
private Thread workingThread = null;
private boolean isClosed = false;
/**
* Starts retrieving events from the event queue of the specified virtual machine.
*
* @param vm
* the target virtual machine.
*/
@Override
public void start(VirtualMachine vm) {
if (isClosed) {
throw new IllegalStateException("This event hub is already closed.");
}
workingThread = new Thread(() -> {
EventQueue queue = vm.eventQueue();
while (true) {
try {
if (Thread.interrupted()) {
subject.onComplete();
return;
}
EventSet set = queue.remove();
boolean shouldResume = true;
for (Event event : set) {
try {
logger.fine("\nJDI Event: " + event + "\n");
} catch (VMDisconnectedException e) {
// do nothing
}
DebugEvent dbgEvent = new DebugEvent();
dbgEvent.event = event;
dbgEvent.eventSet = set;
subject.onNext(dbgEvent);
shouldResume &= dbgEvent.shouldResume;
}
if (shouldResume) {
set.resume();
}
} catch (InterruptedException e) {
isClosed = true;
subject.onComplete();
return;
} catch (VMDisconnectedException e) {
isClosed = true;
subject.onError(e);
return;
}
}
}, "Event Hub");
workingThread.start();
}
@Override
public void close() {
if (isClosed) {
return;
}
workingThread.interrupt();
workingThread = null;
isClosed = true;
}
/**
* Gets the observable object for breakpoint events.
* @return the observable object for breakpoint events
*/
@Override
public Observable<DebugEvent> breakpointEvents() {
return this.events().filter(debugEvent -> debugEvent.event instanceof BreakpointEvent);
}
/**
* Gets the observable object for thread events.
* @return the observable object for thread events
*/
@Override
public Observable<DebugEvent> threadEvents() {
return this.events().filter(debugEvent -> debugEvent.event instanceof ThreadStartEvent
|| debugEvent.event instanceof ThreadDeathEvent);
}
/**
* Gets the observable object for exception events.
* @return the observable object for exception events
*/
@Override
public Observable<DebugEvent> exceptionEvents() {
return this.events().filter(debugEvent -> debugEvent.event instanceof ExceptionEvent);
}
/**
* Gets the observable object for step events.
* @return the observable object for step events
*/
@Override
public Observable<DebugEvent> stepEvents() {
return this.events().filter(debugEvent -> debugEvent.event instanceof StepEvent);
}
/**
* Gets the observable object for vm events.
* @return the observable object for vm events
*/
@Override
public Observable<DebugEvent> vmEvents() {
return this.events().filter(debugEvent -> debugEvent.event instanceof VMStartEvent
|| debugEvent.event instanceof VMDisconnectEvent
|| debugEvent.event instanceof VMDeathEvent);
}
}

View File

@ -0,0 +1,58 @@
/*******************************************************************************
* Copyright (c) 2017-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:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/
package com.microsoft.java.debug.core;
import java.util.concurrent.CompletableFuture;
public interface IBreakpoint extends IDebugResource {
String REQUEST_TYPE = "request_type";
int REQUEST_TYPE_LINE = 0;
int REQUEST_TYPE_METHOD = 1;
int REQUEST_TYPE_LAMBDA = 2;
JavaBreakpointLocation sourceLocation();
String className();
int getLineNumber();
int getColumnNumber();
int getHitCount();
void setHitCount(int hitCount);
CompletableFuture<IBreakpoint> install();
void putProperty(Object key, Object value);
Object getProperty(Object key);
String getCondition();
void setCondition(String condition);
String getLogMessage();
void setLogMessage(String logMessage);
default void setAsync(boolean async) {
}
default boolean async() {
return false;
}
}

View File

@ -0,0 +1,24 @@
/*******************************************************************************
* Copyright (c) 2017 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.List;
import com.sun.jdi.request.EventRequest;
import io.reactivex.disposables.Disposable;
public interface IDebugResource extends AutoCloseable {
List<EventRequest> requests();
List<Disposable> subscriptions();
}

View File

@ -0,0 +1,50 @@
/*******************************************************************************
* Copyright (c) 2017-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:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/
package com.microsoft.java.debug.core;
import java.util.List;
import com.sun.jdi.ThreadReference;
import com.sun.jdi.VirtualMachine;
public interface IDebugSession {
void start();
void suspend();
void resume();
void detach();
void terminate();
// breakpoints
IBreakpoint createBreakpoint(String className, int lineNumber, int hitCount, String condition, String logMessage);
IBreakpoint createBreakpoint(JavaBreakpointLocation sourceLocation, int hitCount, String condition, String logMessage);
IWatchpoint createWatchPoint(String className, String fieldName, String accessType, String condition, int hitCount);
void setExceptionBreakpoints(boolean notifyCaught, boolean notifyUncaught);
void setExceptionBreakpoints(boolean notifyCaught, boolean notifyUncaught, String[] classFilters, String[] classExclusionFilters);
IMethodBreakpoint createFunctionBreakpoint(String className, String functionName, String condition, int hitCount);
Process process();
List<ThreadReference> getAllThreads();
IEventHub getEventHub();
VirtualMachine getVM();
}

View File

@ -0,0 +1,69 @@
/*******************************************************************************
* Copyright (c) 2018 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;
public interface IEvaluatableBreakpoint {
boolean containsEvaluatableExpression();
boolean containsConditionalExpression();
boolean containsLogpointExpression();
String getCondition();
void setCondition(String condition);
String getLogMessage();
void setLogMessage(String logMessage);
/**
* please use {@link #setCompiledExpression(long, Object)} instead.
*/
@Deprecated
void setCompiledConditionalExpression(Object compiledExpression);
/**
* please use {@link #getCompiledExpression(long)} instead.
*/
@Deprecated
Object getCompiledConditionalExpression();
/**
* please use {@link #setCompiledExpression(long, Object)} instead.
*/
@Deprecated
void setCompiledLogpointExpression(Object compiledExpression);
/**
* please use {@link #getCompiledExpression(long)} instead.
*/
@Deprecated
Object getCompiledLogpointExpression();
/**
* Sets the compiled expression for a thread.
*
* @param threadId - thread the breakpoint is hit in
* @param compiledExpression - associated compiled expression
*/
void setCompiledExpression(long threadId, Object compiledExpression);
/**
* Returns existing compiled expression for the given thread or
* <code>null</code>.
*
* @param threadId thread the breakpoint was hit in
* @return compiled expression or <code>null</code>
*/
Object getCompiledExpression(long threadId);
}

View File

@ -0,0 +1,32 @@
/*******************************************************************************
* Copyright (c) 2017 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 com.sun.jdi.VirtualMachine;
import io.reactivex.Observable;
public interface IEventHub extends AutoCloseable {
void start(VirtualMachine vm);
Observable<DebugEvent> events();
Observable<DebugEvent> breakpointEvents();
Observable<DebugEvent> threadEvents();
Observable<DebugEvent> exceptionEvents();
Observable<DebugEvent> stepEvents();
Observable<DebugEvent> vmEvents();
}

View File

@ -0,0 +1,40 @@
/*******************************************************************************
* 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);
default void setAsync(boolean async) {
}
default boolean async() {
return false;
}
}

View File

@ -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<IWatchpoint> install();
void putProperty(Object key, Object value);
Object getProperty(Object key);
int getHitCount();
void setHitCount(int hitCount);
String getCondition();
void setCondition(String condition);
}

View File

@ -0,0 +1,113 @@
/*******************************************************************************
* 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:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/
package com.microsoft.java.debug.core;
import java.util.Objects;
import com.microsoft.java.debug.core.protocol.Types;
public class JavaBreakpointLocation {
/**
* The source line of the breakpoint or logpoint.
*/
private int lineNumber;
/**
* The source column of the breakpoint.
*/
private int columnNumber = -1;
/**
* The declaring class name that encloses the target position.
*/
private String className;
/**
* The method name and signature when the target position
* points to a method declaration.
*/
private String methodName;
private String methodSignature;
/**
* All possible locations for source breakpoints in a given range.
*/
private Types.BreakpointLocation[] availableBreakpointLocations = new Types.BreakpointLocation[0];
public JavaBreakpointLocation(int lineNumber, int columnNumber) {
this.lineNumber = lineNumber;
this.columnNumber = columnNumber;
}
@Override
public int hashCode() {
return Objects.hash(lineNumber, columnNumber, className, methodName, methodSignature);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof JavaBreakpointLocation)) {
return false;
}
JavaBreakpointLocation other = (JavaBreakpointLocation) obj;
return lineNumber == other.lineNumber && columnNumber == other.columnNumber
&& Objects.equals(className, other.className) && Objects.equals(methodName, other.methodName)
&& Objects.equals(methodSignature, other.methodSignature);
}
public int lineNumber() {
return lineNumber;
}
public void setLineNumber(int lineNumber) {
this.lineNumber = lineNumber;
}
public int columnNumber() {
return columnNumber;
}
public void setColumnNumber(int columnNumber) {
this.columnNumber = columnNumber;
}
public String className() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
public String methodName() {
return methodName;
}
public void setMethodName(String methodName) {
this.methodName = methodName;
}
public String methodSignature() {
return methodSignature;
}
public void setMethodSignature(String methodSignature) {
this.methodSignature = methodSignature;
}
public Types.BreakpointLocation[] availableBreakpointLocations() {
return availableBreakpointLocations;
}
public void setAvailableBreakpointLocations(Types.BreakpointLocation[] availableBreakpointLocations) {
this.availableBreakpointLocations = availableBreakpointLocations;
}
}

View File

@ -0,0 +1,24 @@
/*******************************************************************************
* 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 com.sun.jdi.ObjectReference;
public class JdiExceptionReference {
public ObjectReference exception;
public boolean isUncaught;
public JdiExceptionReference(ObjectReference exception, boolean isUncaught) {
this.exception = exception;
this.isUncaught = isUncaught;
}
}

View File

@ -0,0 +1,25 @@
/*******************************************************************************
* Copyright (c) 2020 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 com.sun.jdi.Method;
import com.sun.jdi.Value;
public class JdiMethodResult {
public Method method;
public Value value;
public JdiMethodResult(Method method, Value value) {
this.method = method;
this.value = value;
}
}

View File

@ -0,0 +1,51 @@
/*******************************************************************************
* Copyright (c) 2018-2021 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 com.sun.jdi.connect.VMStartException;
/**
* Extends {@link VMStartException} to provide more detail about the failed process
* from before it is destroyed.
*/
public class LaunchException extends VMStartException {
boolean exited;
int exitStatus;
String stdout;
String stderr;
public LaunchException(String message, Process process, boolean exited, int exitStatus, String stdout, String stderr) {
super(message, process);
this.exited = exited;
this.exitStatus = exitStatus;
this.stdout = stdout;
this.stderr = stderr;
}
public boolean isExited() {
return exited;
}
public int getExitStatus() {
return exitStatus;
}
public String getStdout() {
return stdout;
}
public String getStderr() {
return stderr;
}
}

View File

@ -0,0 +1,294 @@
/*******************************************************************************
* 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.Collections;
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 boolean async = false;
private HashMap<Object, Object> propertyMap = new HashMap<>();
private Object compiledConditionalExpression = null;
private Map<Long, Object> compiledExpressions = new ConcurrentHashMap<>();
private List<EventRequest> requests = Collections.synchronizedList(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 boolean async() {
return this.async;
}
@Override
public void setAsync(boolean async) {
this.async = async;
}
@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 = AsyncJdwpUtils.await(
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);
Runnable createRequestsFromLoadedClasses = () -> {
List<ReferenceType> types = vm.classesByName(className);
for (ReferenceType type : types) {
createMethodEntryRequest(type).whenComplete((createdRequest, ex) -> {
if (ex != null) {
return;
}
if (createdRequest.isPresent()) {
MethodEntryRequest methodEntryRequest = createdRequest.get();
requests.add(methodEntryRequest);
if (!future.isDone()) {
this.putProperty("verified", true);
future.complete(this);
}
}
});
}
};
if (async()) {
AsyncJdwpUtils.runAsync(createRequestsFromLoadedClasses);
} else {
createRequestsFromLoadedClasses.run();
}
return future;
}
private CompletableFuture<Optional<MethodEntryRequest>> createMethodEntryRequest(ReferenceType type) {
if (async()) {
return CompletableFuture.supplyAsync(() -> createMethodEntryRequest0(type));
} else {
return CompletableFuture.completedFuture(createMethodEntryRequest0(type));
}
}
private Optional<MethodEntryRequest> createMethodEntryRequest0(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;
}
}

View File

@ -0,0 +1,78 @@
/*******************************************************************************
* Copyright (c) 2017 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 com.sun.jdi.AbsentInformationException;
import com.sun.jdi.IncompatibleThreadStateException;
import com.sun.jdi.InvalidStackFrameException;
import com.sun.jdi.NativeMethodException;
import com.sun.jdi.ReferenceType;
import com.sun.jdi.StackFrame;
public final class StackFrameUtility {
public static boolean isNative(StackFrame frame) {
return frame.location().method().isNative();
}
/**
* Pop a StackFrame from its thread.
*
* @param frame
* the StackFrame will be popped
*/
public static void pop(StackFrame frame) throws DebugException {
try {
frame.thread().popFrames(frame);
} catch (IncompatibleThreadStateException e) {
throw new DebugException(String.format("%s occurred popping stack frame.", e.getMessage()), e);
} catch (InvalidStackFrameException e) {
throw new DebugException("Cannot pop up the top stack farme.", e);
} catch (NativeMethodException e) {
throw new DebugException("Cannot pop up the stack frame because it is not valid for a native method.", e);
} catch (RuntimeException e) {
throw new DebugException(String.format("Runtime exception happened: %s", e.getMessage()), e);
}
}
public static String getName(StackFrame frame) {
return frame.location().method().name();
}
public static String getSignature(StackFrame frame) {
return frame.location().method().signature();
}
public static boolean isObsolete(StackFrame frame) {
return frame.location().method().isObsolete();
}
/**
* Get the StackFrame associated source file path.
*
* @param frame
* StackFrame for the source path
* @return the source file path
*/
public static String getSourcePath(StackFrame frame) {
try {
return frame.location().sourcePath();
} catch (AbsentInformationException e) {
// Ignore it
}
return null;
}
public static ReferenceType getDeclaringType(StackFrame frame) {
return frame.location().method().declaringType();
}
}

View File

@ -0,0 +1,228 @@
/*******************************************************************************
* Copyright (c) 2017-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:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/
package com.microsoft.java.debug.core;
import java.util.ArrayList;
import java.util.Formatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.google.gson.JsonElement;
import com.microsoft.java.debug.core.adapter.AdapterUtils;
import com.microsoft.java.debug.core.adapter.ErrorCode;
import com.microsoft.java.debug.core.protocol.JsonUtils;
import com.microsoft.java.debug.core.protocol.Messages.Request;
import com.microsoft.java.debug.core.protocol.Messages.Response;
import com.sun.jdi.event.Event;
public class UsageDataSession {
private static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME);
private static final Logger usageDataLogger = Logger.getLogger(Configuration.USAGE_DATA_LOGGER_NAME);
private static final long RESPONSE_MAX_DELAY_MS = 1000;
private static final ThreadLocal<UsageDataSession> threadLocal = new InheritableThreadLocal<>();
private static final boolean TRACE_DAP_PERF = Boolean.getBoolean("debug.dap.perf");
private final String sessionGuid = UUID.randomUUID().toString();
private boolean jdiEventSequenceEnabled = false;
private long startAt = -1;
private long stopAt = -1;
private Map<String, Integer> commandCountMap = new HashMap<>();
private Map<String, Integer> breakpointCountMap = new HashMap<>();
private Map<Integer, RequestEvent> requestEventMap = new HashMap<>();
private Map<String, Integer> userErrorCount = new HashMap<>();
private Map<String, Integer> commandPerfCountMap = new HashMap<>();
private List<String> eventList = new ArrayList<>();
private List<String[]> dapPerf = new ArrayList<>();
public static String getSessionGuid() {
return threadLocal.get() == null ? "" : threadLocal.get().sessionGuid;
}
public UsageDataSession() {
threadLocal.set(this);
}
class RequestEvent {
Request request;
long timestamp;
RequestEvent(Request request, long timestamp) {
this.request = request;
this.timestamp = timestamp;
}
}
public void reportStart() {
startAt = System.currentTimeMillis();
}
public void reportStop() {
stopAt = System.currentTimeMillis();
}
/**
* Record usage data from request.
*/
public void recordRequest(Request request) {
try {
requestEventMap.put(request.seq, new RequestEvent(request, System.currentTimeMillis()));
// cmd count
commandCountMap.put(request.command, commandCountMap.getOrDefault(request.command, 0) + 1);
// bp count
if ("setBreakpoints".equals(request.command)) {
String fileIdentifier = "unknown file";
JsonElement pathElement = request.arguments.get("source").getAsJsonObject().get("path");
JsonElement nameElement = request.arguments.get("source").getAsJsonObject().get("name");
if (pathElement != null) {
fileIdentifier = pathElement.getAsString();
} else if (nameElement != null) {
fileIdentifier = nameElement.getAsString();
}
String filenameHash = AdapterUtils.getSHA256HexDigest(fileIdentifier);
int bpCount = request.arguments.get("breakpoints").getAsJsonArray().size();
breakpointCountMap.put(filenameHash, breakpointCountMap.getOrDefault(filenameHash, 0) + bpCount);
}
} catch (Throwable e) {
// ignore it
}
}
/**
* Record usage data from response.
*/
public void recordResponse(Response response) {
try {
long responseMillis = System.currentTimeMillis();
long requestMillis = responseMillis;
String command = null;
RequestEvent requestEvent = requestEventMap.getOrDefault(response.request_seq, null);
if (requestEvent != null) {
command = requestEvent.request.command;
requestMillis = requestEvent.timestamp;
requestEventMap.remove(response.request_seq);
}
long duration = responseMillis - requestMillis;
commandPerfCountMap.compute(command, (k, v) -> (v == null ? 0 : v.intValue()) + (int) duration);
if (TRACE_DAP_PERF) {
synchronized (dapPerf) {
dapPerf.add(new String[]{command, String.valueOf(duration)});
}
}
if (!response.success || duration > RESPONSE_MAX_DELAY_MS) {
Map<String, Object> props = new HashMap<>();
props.put("duration", duration);
props.put("command", command);
props.put("success", response.success);
// directly report abnormal response.
usageDataLogger.log(Level.WARNING, "abnormal response", props);
jdiEventSequenceEnabled = true;
}
} catch (Throwable e) {
// ignore it
}
}
/**
* Submit summary of usage data in current session.
*/
public void submitUsageData() {
Map<String, String> props = new HashMap<>();
props.put("sessionStartAt", String.valueOf(startAt));
props.put("sessionStopAt", String.valueOf(stopAt));
props.put("commandCount", JsonUtils.toJson(commandCountMap));
props.put("breakpointCount", JsonUtils.toJson(breakpointCountMap));
props.put("userErrorCount", JsonUtils.toJson(userErrorCount));
props.put("commandPerfCount", JsonUtils.toJson(commandPerfCountMap));
if (jdiEventSequenceEnabled) {
synchronized (eventList) {
props.put("jdiEventSequence", JsonUtils.toJson(eventList));
}
}
usageDataLogger.log(Level.INFO, "session usage data summary", props);
if (TRACE_DAP_PERF) {
Formatter fmt = new Formatter();
fmt.format("\nDAP Performance Metrics:\n");
fmt.format("%30s %10s(ms)\n", "Request", "Duration");
synchronized (dapPerf) {
dapPerf.forEach((event) -> {
fmt.format("%30s %14s\n", event[0], event[1]);
});
}
logger.info(String.valueOf(fmt));
}
}
/**
* Record JDI event.
*/
public static void recordEvent(Event event) {
try {
UsageDataSession currentSession = threadLocal.get();
if (currentSession != null) {
Map<String, String> eventEntry = new HashMap<>();
eventEntry.put("timestamp", String.valueOf(System.currentTimeMillis()));
eventEntry.put("event", event.toString());
synchronized (currentSession.eventList) {
currentSession.eventList.add(JsonUtils.toJson(eventEntry));
}
}
} catch (Exception e) {
logger.log(Level.SEVERE, String.format("Exception on recording event: %s.", e.toString()), e);
}
}
public static void recordInfo(String key, Object value) {
Map<String, Object> map = new HashMap<>();
map.put(key, value);
usageDataLogger.log(Level.INFO, "session info", map);
}
public static void recordInfo(String description, Map<String, Object> data) {
usageDataLogger.log(Level.INFO, description, data);
}
/**
* Record counts for each user errors encountered.
*/
public void recordUserError(ErrorCode errorCode) {
try {
String errorCodeStr = errorCode.name();
userErrorCount.put(errorCodeStr, userErrorCount.getOrDefault(errorCodeStr, 0) + 1);
} catch (Exception e) {
logger.log(Level.SEVERE, String.format("Exception on recording user error: %s.", e.toString()), e);
}
}
/**
* Enable JDI event sequence track in current session.
*/
public static void enableJdiEventSequence() {
try {
UsageDataSession currentSession = threadLocal.get();
if (currentSession != null) {
currentSession.jdiEventSequenceEnabled = true;
}
} catch (Exception e) {
// ignore it
}
}
}

View File

@ -0,0 +1,104 @@
/*******************************************************************************
* Copyright (c) 2017 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.time.Instant;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentLinkedQueue;
import com.microsoft.java.debug.core.protocol.JsonUtils;
public class UsageDataStore {
private ConcurrentLinkedQueue<Object> queue;
private static final int QUEUE_MAX_SIZE = 10000;
private static final String DEBUG_SESSION_ID_NAME = "debugSessionId";
private static final String DESCRIPTION_NAME = "description";
private static final String ERROR_MESSAGE_NAME = "message";
private static final String STACKTRACE_NAME = "stackTrace";
private static final String SCOPE_NAME = "scope";
private static final String TIMESTAMP_NAME = "timestamp";
/**
* Constructor.
*/
private UsageDataStore() {
queue = new ConcurrentLinkedQueue<>();
}
private static final class SingletonHolder {
private static final UsageDataStore INSTANCE = new UsageDataStore();
}
/**
* Fetch all pending user data records.
* @return List of user data Object.
*/
public synchronized Object[] fetchAll() {
Object[] ret = queue.toArray();
queue.clear();
return ret;
}
public static UsageDataStore getInstance() {
return SingletonHolder.INSTANCE;
}
/**
* Log user data Object into the queue.
*/
public void logSessionData(String desc, Map<String, String> props) {
if (queue == null) {
return;
}
Map<String, String> sessionEntry = new HashMap<>();
sessionEntry.put(SCOPE_NAME, "session");
sessionEntry.put(DEBUG_SESSION_ID_NAME, UsageDataSession.getSessionGuid());
if (desc != null) {
sessionEntry.put(DESCRIPTION_NAME, desc);
}
if (props != null) {
sessionEntry.putAll(props);
}
enqueue(sessionEntry);
}
/**
* Log Exception details into queue.
*/
public void logErrorData(String desc, Throwable th) {
if (queue == null) {
return;
}
Map<String, String> errorEntry = new HashMap<>();
errorEntry.put(SCOPE_NAME, "exception");
errorEntry.put(DEBUG_SESSION_ID_NAME, UsageDataSession.getSessionGuid());
if (desc != null) {
errorEntry.put(DESCRIPTION_NAME, desc);
}
if (th != null) {
errorEntry.put(ERROR_MESSAGE_NAME, th.getMessage());
errorEntry.put(STACKTRACE_NAME, JsonUtils.toJson(th.getStackTrace()));
}
enqueue(errorEntry);
}
private synchronized void enqueue(Map<String, String> entry) {
if (queue.size() > QUEUE_MAX_SIZE) {
queue.poll();
}
if (entry != null) {
entry.put(TIMESTAMP_NAME, Instant.now().toString());
queue.add(entry);
}
}
}

View File

@ -0,0 +1,276 @@
/*******************************************************************************
* 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.Map;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.lang3.StringUtils;
import com.sun.jdi.Field;
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.WatchpointRequest;
import io.reactivex.Observable;
import io.reactivex.disposables.Disposable;
public class Watchpoint implements IWatchpoint, IEvaluatableBreakpoint {
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;
private HashMap<Object, Object> propertyMap = new HashMap<>();
private Object compiledConditionalExpression = null;
private Map<Long, Object> compiledExpressions = new ConcurrentHashMap<>();
// IDebugResource
private List<EventRequest> requests = new ArrayList<>();
private List<Disposable> 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) {
Objects.requireNonNull(vm);
Objects.requireNonNull(eventHub);
Objects.requireNonNull(className);
Objects.requireNonNull(fieldName);
this.vm = vm;
this.eventHub = eventHub;
this.className = className;
this.fieldName = fieldName;
this.accessType = accessType;
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(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);
compiledExpressions.clear();
}
@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<IWatchpoint> 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<IWatchpoint> future = new CompletableFuture<>();
subscription = eventHub.events()
.filter(debugEvent -> debugEvent.event instanceof ClassPrepareEvent && (classPrepareRequest.equals(debugEvent.event.request())))
.subscribe(debugEvent -> {
ClassPrepareEvent event = (ClassPrepareEvent) debugEvent.event;
List<WatchpointRequest> watchpointRequests = createWatchpointRequests(event.referenceType());
requests.addAll(watchpointRequests);
if (!watchpointRequests.isEmpty() && !future.isDone()) {
this.putProperty("verified", true);
future.complete(this);
}
});
subscriptions.add(subscription);
List<EventRequest> watchpointRequests = new ArrayList<>();
List<ReferenceType> 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<WatchpointRequest> createWatchpointRequests(ReferenceType type) {
List<WatchpointRequest> 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) {
throw new UnsupportedOperationException("Log message feature is unsupported for watchpoint.");
}
@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;
}
@Override
public Object getCompiledExpression(long threadId) {
return compiledExpressions.get(threadId);
}
@Override
public void setCompiledExpression(long threadId, Object compiledExpression) {
compiledExpressions.put(threadId, compiledExpression);
}
}

View File

@ -0,0 +1,313 @@
/*******************************************************************************
* Copyright (c) 2017-2021 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 java.io.File;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystemNotFoundException;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
import com.microsoft.java.debug.core.DebugException;
import com.microsoft.java.debug.core.protocol.Messages.Response;
import com.microsoft.java.debug.core.protocol.Responses;
import com.microsoft.java.debug.core.protocol.Types;
public class AdapterUtils {
private static final String OS_NAME = System.getProperty("os.name", "").toLowerCase();
private static final Pattern ENCLOSING_CLASS_REGEX = Pattern.compile("^([^\\$]*)");
public static final boolean isWin = isWindows();
public static final boolean isMac = OS_NAME.contains("mac") || OS_NAME.contains("darwin");
/**
* Check if the OS is windows or not.
*/
public static boolean isWindows() {
return OS_NAME.contains("win");
}
/**
* Search the absolute path of the java file under the specified source path directory.
* @param sourcePaths
* the project source directories
* @param sourceName
* the java file path
* @return the absolute file path
*/
public static String sourceLookup(String[] sourcePaths, String sourceName) {
if (sourcePaths != null) {
for (String path : sourcePaths) {
Path fullpath = Paths.get(path, sourceName);
if (Files.isRegularFile(fullpath)) {
return fullpath.toString();
}
}
}
return null;
}
/**
* Get the enclosing type name of the given fully qualified name.
* <pre>
* a.b.c -> a.b.c
* a.b.c$1 -> a.b.c
* a.b.c$1$2 -> a.b.c
* </pre>
* @param fullyQualifiedName
* fully qualified name
* @return the enclosing type name
*/
public static String parseEnclosingType(String fullyQualifiedName) {
if (fullyQualifiedName == null) {
return null;
}
Matcher matcher = ENCLOSING_CLASS_REGEX.matcher(fullyQualifiedName);
if (matcher.find()) {
return matcher.group();
}
return null;
}
/**
* Convert the source platform's line number to the target platform's line number.
*
* @param line
* the line number from the source platform
* @param sourceLinesStartAt1
* the source platform's line starts at 1 or not
* @param targetLinesStartAt1
* the target platform's line starts at 1 or not
* @return the new line number
*/
public static int convertLineNumber(int line, boolean sourceLinesStartAt1, boolean targetLinesStartAt1) {
if (sourceLinesStartAt1) {
return targetLinesStartAt1 ? line : line - 1;
} else {
return targetLinesStartAt1 ? line + 1 : line;
}
}
/**
* Convert the source platform's column number to the target platform's column
* number.
*
* @param column
* the column number from the source platform
* @param sourceColumnsStartAt1
* the source platform's column starts at 1 or not
* @param targetColumnStartAt1
* the target platform's column starts at 1 or not
* @return the new column number
*/
public static int convertColumnNumber(int column, boolean sourceColumnsStartAt1, boolean targetColumnStartAt1) {
if (sourceColumnsStartAt1) {
return targetColumnStartAt1 ? column : column - 1;
} else {
return targetColumnStartAt1 ? column + 1 : column;
}
}
/**
* Convert the source platform's path format to the target platform's path format.
*
* @param path
* the path value from the source platform
* @param sourceIsUri
* the path format of the source platform is uri or not
* @param targetIsUri
* the path format of the target platform is uri or not
* @return the new path value
*/
public static String convertPath(String path, boolean sourceIsUri, boolean targetIsUri) {
if (path == null) {
return null;
}
if (sourceIsUri == targetIsUri) {
return path;
} else if (sourceIsUri && !targetIsUri) {
return toPath(path);
} else {
return toUri(path);
}
}
/**
* Convert a file uri to a file path, or null if this uri does not represent a file in the local file system.
* @param uri
* the uri string
* @return the file path
*/
public static String toPath(String uri) {
try {
return Paths.get(new URI(uri)).toString();
} catch (URISyntaxException | IllegalArgumentException | FileSystemNotFoundException
| SecurityException e) {
return null;
}
}
/**
* Convert a file path to an uri string.
* @param path
* the file path
* @return the uri string
*/
public static String toUri(String path) {
try {
return Paths.get(path).toUri().toString();
} catch (InvalidPathException e) {
return null;
}
}
/**
* Convert a file path to an url string.
* @param path
* the file path
* @return the url string
* @throws MalformedURLException
* if the file path cannot be constructed to an url because of some errors.
*/
public static String toUrl(String path) throws MalformedURLException {
File file = new File(path);
return file.toURI().toURL().toString();
}
/**
* Check a string variable is an uri or not.
*/
public static boolean isUri(String uriString) {
try {
URI uri = new URI(uriString);
return StringUtils.isNotBlank(uri.getScheme());
} catch (URISyntaxException | IllegalArgumentException | FileSystemNotFoundException
| SecurityException e) {
return false;
}
}
/**
* Populate the response body with the given error message, and mark the success flag to false. At last return the response object back.
*
* @param response
* the response object
* @param errorCode
* the error code
* @param errorMessage
* the error message
* @return the modified response object.
*/
public static Response setErrorResponse(Response response, ErrorCode errorCode, String errorMessage) {
response.body = new Responses.ErrorResponseBody(new Types.Message(errorCode.getId(), errorMessage));
response.message = errorMessage;
response.success = false;
return response;
}
/**
* Populate the response body with the given exception, and mark the success flag to false. At last return the response object back.
*
* @param response
* the response object
* @param errorCode
* the error code
* @param e
* the exception
* @return the modified response object.
*/
public static Response setErrorResponse(Response response, ErrorCode errorCode, Exception e) {
String errorMessage = e.toString();
response.body = new Responses.ErrorResponseBody(new Types.Message(errorCode.getId(), errorMessage));
response.message = errorMessage;
response.success = false;
return response;
}
/**
* Generate a CompletableFuture response with the given error message.
*/
public static CompletableFuture<Response> createAsyncErrorResponse(Response response, ErrorCode errorCode, String errorMessage) {
return CompletableFuture.completedFuture(setErrorResponse(response, errorCode, errorMessage));
}
/**
* Generate a CompletableFuture response with the given exception.
*/
public static CompletableFuture<Response> createAsyncErrorResponse(Response response, ErrorCode errorCode, Exception e) {
return CompletableFuture.completedFuture(setErrorResponse(response, errorCode, e));
}
public static CompletionException createCompletionException(String message, ErrorCode errorCode, Throwable cause) {
return new CompletionException(new DebugException(message, cause, errorCode.getId()));
}
public static CompletionException createCompletionException(String message, ErrorCode errorCode) {
return new CompletionException(new DebugException(message, errorCode.getId()));
}
public static DebugException createUserErrorDebugException(String message, ErrorCode errorCode) {
return new DebugException(message, errorCode.getId(), true);
}
/**
* Calculate SHA-256 Digest of given string.
* @param content
*
* string to digest
* @return string of Hex digest
*/
public static String getSHA256HexDigest(String content) {
byte[] hashBytes = null;
try {
hashBytes = MessageDigest.getInstance("SHA-256").digest(content.getBytes(StandardCharsets.UTF_8));
} catch (NoSuchAlgorithmException e) {
// ignore it.
}
StringBuffer buf = new StringBuffer();
if (hashBytes != null) {
for (byte b : hashBytes) {
buf.append(Integer.toHexString((b & 0xFF) + 0x100).substring(1));
}
}
return buf.toString();
}
/**
* Decode the uri string.
* @param uri
* the uri string
* @return the decoded uri
*/
public static String decodeURIComponent(String uri) {
try {
return URLDecoder.decode(uri, StandardCharsets.UTF_8.name());
} catch (UnsupportedEncodingException e) {
return uri;
}
}
}

View File

@ -0,0 +1,271 @@
/*******************************************************************************
* 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
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/
package com.microsoft.java.debug.core.adapter;
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.IMethodBreakpoint;
import com.microsoft.java.debug.core.IWatchpoint;
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<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);
/**
* Constructor.
*/
public BreakpointManager() {
this.breakpoints = Collections.synchronizedList(new ArrayList<>(5));
this.sourceToBreakpoints = new HashMap<>();
this.watchpoints = new HashMap<>();
this.methodBreakpoints = new HashMap<>();
}
@Override
public IBreakpoint[] setBreakpoints(String source, IBreakpoint[] breakpoints) {
return setBreakpoints(source, breakpoints, false);
}
@Override
public IBreakpoint[] setBreakpoints(String source, IBreakpoint[] breakpoints, boolean sourceModified) {
List<IBreakpoint> result = new ArrayList<>();
HashMap<String, IBreakpoint> breakpointMap = this.sourceToBreakpoints.get(source);
// When source file is modified, delete all previously added breakpoints.
if (sourceModified && breakpointMap != null) {
for (IBreakpoint bp : breakpointMap.values()) {
try {
// Destroy the breakpoint on the debugee VM.
bp.close();
} catch (Exception e) {
logger.log(Level.SEVERE, String.format("Remove breakpoint exception: %s", e.toString()), e);
}
this.breakpoints.remove(bp);
}
this.sourceToBreakpoints.put(source, null);
breakpointMap = null;
}
if (breakpointMap == null) {
breakpointMap = new HashMap<>();
this.sourceToBreakpoints.put(source, breakpointMap);
}
// Compute the breakpoints that are newly added.
List<IBreakpoint> toAdd = new ArrayList<>();
List<Integer> visitedBreakpoints = new ArrayList<>();
for (IBreakpoint breakpoint : breakpoints) {
IBreakpoint existed = breakpointMap.get(String.valueOf(breakpoint.hashCode()));
if (existed != null) {
result.add(existed);
visitedBreakpoints.add(existed.hashCode());
continue;
} else {
result.add(breakpoint);
}
toAdd.add(breakpoint);
}
// Compute the breakpoints that are no longer listed.
List<IBreakpoint> toRemove = new ArrayList<>();
for (IBreakpoint breakpoint : breakpointMap.values()) {
if (!visitedBreakpoints.contains(breakpoint.hashCode())) {
toRemove.add(breakpoint);
}
}
removeBreakpointsInternally(source, toRemove.toArray(new IBreakpoint[0]));
addBreakpointsInternally(source, toAdd.toArray(new IBreakpoint[0]));
return result.toArray(new IBreakpoint[0]);
}
private void addBreakpointsInternally(String source, IBreakpoint[] breakpoints) {
Map<String, IBreakpoint> breakpointMap = this.sourceToBreakpoints.computeIfAbsent(source, k -> new HashMap<>());
if (breakpoints != null && breakpoints.length > 0) {
for (IBreakpoint breakpoint : breakpoints) {
breakpoint.putProperty("id", this.nextBreakpointId.getAndIncrement());
this.breakpoints.add(breakpoint);
breakpointMap.put(String.valueOf(breakpoint.hashCode()), breakpoint);
}
}
}
/**
* Removes the specified breakpoints from breakpoint manager.
*/
private void removeBreakpointsInternally(String source, IBreakpoint[] breakpoints) {
Map<String, IBreakpoint> breakpointMap = this.sourceToBreakpoints.get(source);
if (breakpointMap == null || breakpointMap.isEmpty() || breakpoints.length == 0) {
return;
}
for (IBreakpoint breakpoint : breakpoints) {
if (this.breakpoints.contains(breakpoint)) {
try {
// Destroy the breakpoint on the debugee VM.
breakpoint.close();
this.breakpoints.remove(breakpoint);
breakpointMap.remove(String.valueOf(breakpoint.hashCode()));
} catch (Exception e) {
logger.log(Level.SEVERE, String.format("Remove breakpoint exception: %s", e.toString()), e);
}
}
}
}
@Override
public IBreakpoint[] getBreakpoints() {
return this.breakpoints.toArray(new IBreakpoint[0]);
}
@Override
public IBreakpoint[] getBreakpoints(String source) {
HashMap<String, IBreakpoint> breakpointMap = this.sourceToBreakpoints.get(source);
if (breakpointMap == null) {
return new IBreakpoint[0];
}
return breakpointMap.values().toArray(new IBreakpoint[0]);
}
@Override
public IWatchpoint[] setWatchpoints(IWatchpoint[] changedWatchpoints) {
List<IWatchpoint> result = new ArrayList<>();
List<IWatchpoint> toAdds = new ArrayList<>();
List<IWatchpoint> toRemoves = new ArrayList<>();
Set<String> visitedKeys = 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())) {
visitedKeys.add(key);
result.add(cache);
} else {
toAdds.add(change);
result.add(change);
}
}
for (IWatchpoint cache : watchpoints.values()) {
if (!visitedKeys.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);
}
@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();
}
}

View File

@ -0,0 +1,18 @@
/*******************************************************************************
* Copyright (c) 2017 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;
public final class Constants {
public static final String PROJECT_NAME = "projectName";
public static final String DEBUGGEE_ENCODING = "debuggeeEncoding";
public static final String MAIN_CLASS = "mainClass";
}

View File

@ -0,0 +1,166 @@
/*******************************************************************************
* Copyright (c) 2017 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 java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.microsoft.java.debug.core.Configuration;
import com.microsoft.java.debug.core.adapter.handler.AttachRequestHandler;
import com.microsoft.java.debug.core.adapter.handler.BreakpointLocationsRequestHander;
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;
import com.microsoft.java.debug.core.adapter.handler.ExceptionInfoRequestHandler;
import com.microsoft.java.debug.core.adapter.handler.HotCodeReplaceHandler;
import com.microsoft.java.debug.core.adapter.handler.InitializeRequestHandler;
import com.microsoft.java.debug.core.adapter.handler.InlineValuesRequestHandler;
import com.microsoft.java.debug.core.adapter.handler.LaunchRequestHandler;
import com.microsoft.java.debug.core.adapter.handler.ProcessIdHandler;
import com.microsoft.java.debug.core.adapter.handler.RefreshVariablesHandler;
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.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;
import com.microsoft.java.debug.core.adapter.handler.StepInTargetsRequestHandler;
import com.microsoft.java.debug.core.adapter.handler.StepRequestHandler;
import com.microsoft.java.debug.core.adapter.handler.ThreadsRequestHandler;
import com.microsoft.java.debug.core.adapter.handler.VariablesRequestHandler;
import com.microsoft.java.debug.core.protocol.IProtocolServer;
import com.microsoft.java.debug.core.protocol.JsonUtils;
import com.microsoft.java.debug.core.protocol.Messages;
import com.microsoft.java.debug.core.protocol.Requests.Arguments;
import com.microsoft.java.debug.core.protocol.Requests.Command;
public class DebugAdapter implements IDebugAdapter {
private static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME);
private IDebugAdapterContext debugContext = null;
private Map<Command, List<IDebugRequestHandler>> requestHandlersForDebug = null;
private Map<Command, List<IDebugRequestHandler>> requestHandlersForNoDebug = null;
/**
* Constructor.
*/
public DebugAdapter(IProtocolServer server, IProviderContext providerContext) {
this.debugContext = new DebugAdapterContext(server, providerContext);
requestHandlersForDebug = new HashMap<>();
requestHandlersForNoDebug = new HashMap<>();
initialize();
}
@Override
public CompletableFuture<Messages.Response> dispatchRequest(Messages.Request request) {
Messages.Response response = new Messages.Response();
response.request_seq = request.seq;
response.command = request.command;
response.success = true;
Command command = Command.parse(request.command);
Arguments cmdArgs = JsonUtils.fromJson(request.arguments, command.getArgumentType());
if (debugContext.isVmTerminated() && command != Command.DISCONNECT) {
return CompletableFuture.completedFuture(response);
}
List<IDebugRequestHandler> handlers = this.debugContext.getLaunchMode() == LaunchMode.DEBUG
? requestHandlersForDebug.get(command) : requestHandlersForNoDebug.get(command);
if (handlers != null && !handlers.isEmpty()) {
CompletableFuture<Messages.Response> future = CompletableFuture.completedFuture(response);
for (IDebugRequestHandler handler : handlers) {
future = future.thenCompose((res) -> {
return handler.handle(command, cmdArgs, res, debugContext);
});
}
return future;
} else {
final String errorMessage = String.format("Unrecognized request: { _request: %s }", request.command);
logger.log(Level.SEVERE, errorMessage);
return AdapterUtils.createAsyncErrorResponse(response, ErrorCode.UNRECOGNIZED_REQUEST_FAILURE, errorMessage);
}
}
private void initialize() {
// Register request handlers.
// When there are multiple handlers registered for the same request, follow the rule "first register, first execute".
registerHandler(new InitializeRequestHandler());
registerHandler(new LaunchRequestHandler());
// DEBUG mode only
registerHandlerForDebug(new AttachRequestHandler());
registerHandlerForDebug(new ConfigurationDoneRequestHandler());
registerHandlerForDebug(new DisconnectRequestHandler());
registerHandlerForDebug(new SetBreakpointsRequestHandler());
registerHandlerForDebug(new SetExceptionBreakpointsRequestHandler());
registerHandlerForDebug(new SourceRequestHandler());
registerHandlerForDebug(new ThreadsRequestHandler());
registerHandlerForDebug(new StepRequestHandler());
registerHandlerForDebug(new StackTraceRequestHandler());
registerHandlerForDebug(new ScopesRequestHandler());
registerHandlerForDebug(new VariablesRequestHandler());
registerHandlerForDebug(new SetVariableRequestHandler());
registerHandlerForDebug(new EvaluateRequestHandler());
registerHandlerForDebug(new HotCodeReplaceHandler());
registerHandlerForDebug(new RestartFrameHandler());
registerHandlerForDebug(new CompletionsHandler());
registerHandlerForDebug(new ExceptionInfoRequestHandler());
registerHandlerForDebug(new DataBreakpointInfoRequestHandler());
registerHandlerForDebug(new SetDataBreakpointsRequestHandler());
registerHandlerForDebug(new InlineValuesRequestHandler());
registerHandlerForDebug(new RefreshVariablesHandler());
registerHandlerForDebug(new ProcessIdHandler());
registerHandlerForDebug(new SetFunctionBreakpointsRequestHandler());
registerHandlerForDebug(new BreakpointLocationsRequestHander());
registerHandlerForDebug(new StepInTargetsRequestHandler());
// NO_DEBUG mode only
registerHandlerForNoDebug(new DisconnectRequestWithoutDebuggingHandler());
registerHandlerForNoDebug(new ProcessIdHandler());
}
private void registerHandlerForDebug(IDebugRequestHandler handler) {
registerHandler(requestHandlersForDebug, handler);
}
private void registerHandlerForNoDebug(IDebugRequestHandler handler) {
registerHandler(requestHandlersForNoDebug, handler);
}
private void registerHandler(IDebugRequestHandler handler) {
registerHandler(requestHandlersForDebug, handler);
registerHandler(requestHandlersForNoDebug, handler);
}
private void registerHandler(Map<Command, List<IDebugRequestHandler>> requestHandlers, IDebugRequestHandler handler) {
for (Command command : handler.getTargetCommands()) {
List<IDebugRequestHandler> handlerList = requestHandlers.get(command);
if (handlerList == null) {
handlerList = new ArrayList<>();
requestHandlers.put(command, handlerList);
}
handler.initialize(debugContext);
handlerList.add(handler);
}
}
}

View File

@ -0,0 +1,421 @@
/*******************************************************************************
* Copyright (c) 2017-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:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/
package com.microsoft.java.debug.core.adapter;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import com.microsoft.java.debug.core.DebugSettings;
import com.microsoft.java.debug.core.IDebugSession;
import com.microsoft.java.debug.core.DebugSettings.AsyncMode;
import com.microsoft.java.debug.core.adapter.variables.IVariableFormatter;
import com.microsoft.java.debug.core.adapter.variables.VariableFormatterFactory;
import com.microsoft.java.debug.core.protocol.IProtocolServer;
import com.microsoft.java.debug.core.protocol.Requests.StepFilters;
import org.apache.commons.lang3.ArrayUtils;
public class DebugAdapterContext implements IDebugAdapterContext {
private static final int MAX_CACHE_ITEMS = 10000;
private final StepFilters defaultFilters = new StepFilters();
private Map<String, String> sourceMappingCache = Collections.synchronizedMap(new LRUCache<>(MAX_CACHE_ITEMS));
private IProviderContext providerContext;
private IProtocolServer server;
private IDebugSession debugSession;
private boolean debuggerLinesStartAt1 = true;
// The Java model on debugger uses 0-based column number.
private boolean debuggerColumnStartAt1 = false;
private boolean debuggerPathsAreUri = true;
private boolean clientLinesStartAt1 = true;
private boolean clientColumnsStartAt1 = true;
private boolean clientPathsAreUri = false;
private boolean supportsRunInTerminalRequest;
private boolean isAttached = false;
private String[] sourcePaths;
private Charset debuggeeEncoding;
private transient boolean vmTerminated;
private boolean isVmStopOnEntry = false;
private LaunchMode launchMode = LaunchMode.DEBUG;
private Process debuggeeProcess;
private String mainClass;
private StepFilters stepFilters;
private Path classpathJar = null;
private Path argsfile = null;
private boolean isInitialized = false;
private long shellProcessId = -1;
private long processId = -1;
private boolean localDebugging = true;
private long jdwpLatency = 0;
private IdCollection<String> sourceReferences = new IdCollection<>();
private RecyclableObjectPool<Long, Object> recyclableIdPool = new RecyclableObjectPool<>();
private IVariableFormatter variableFormatter = VariableFormatterFactory.createVariableFormatter();
private IStackFrameManager stackFrameManager = new StackFrameManager();
private IExceptionManager exceptionManager = new ExceptionManager();
private IBreakpointManager breakpointManager = new BreakpointManager();
private IStepResultManager stepResultManager = new StepResultManager();
private ThreadCache threadCache = new ThreadCache();
public DebugAdapterContext(IProtocolServer server, IProviderContext providerContext) {
this.providerContext = providerContext;
this.server = server;
}
@Override
public IProtocolServer getProtocolServer() {
return server;
}
@Override
public <T extends IProvider> T getProvider(Class<T> clazz) {
return providerContext.getProvider(clazz);
}
@Override
public void setDebugSession(IDebugSession session) {
debugSession = session;
}
@Override
public IDebugSession getDebugSession() {
return debugSession;
}
@Override
public boolean isDebuggerLinesStartAt1() {
return debuggerLinesStartAt1;
}
@Override
public void setDebuggerLinesStartAt1(boolean debuggerLinesStartAt1) {
this.debuggerLinesStartAt1 = debuggerLinesStartAt1;
}
public boolean isDebuggerColumnsStartAt1() {
return debuggerColumnStartAt1;
}
@Override
public boolean isDebuggerPathsAreUri() {
return debuggerPathsAreUri;
}
@Override
public void setDebuggerPathsAreUri(boolean debuggerPathsAreUri) {
this.debuggerPathsAreUri = debuggerPathsAreUri;
}
@Override
public boolean isClientLinesStartAt1() {
return clientLinesStartAt1;
}
@Override
public void setClientLinesStartAt1(boolean clientLinesStartAt1) {
this.clientLinesStartAt1 = clientLinesStartAt1;
}
public boolean isClientColumnsStartAt1() {
return clientColumnsStartAt1;
}
public void setClientColumnsStartAt1(boolean clientColumnsStartAt1) {
this.clientColumnsStartAt1 = clientColumnsStartAt1;
}
@Override
public boolean isClientPathsAreUri() {
return clientPathsAreUri;
}
@Override
public void setClientPathsAreUri(boolean clientPathsAreUri) {
this.clientPathsAreUri = clientPathsAreUri;
}
@Override
public void setSupportsRunInTerminalRequest(boolean supportsRunInTerminalRequest) {
this.supportsRunInTerminalRequest = supportsRunInTerminalRequest;
}
@Override
public boolean supportsRunInTerminalRequest() {
return supportsRunInTerminalRequest;
}
@Override
public boolean isAttached() {
return isAttached;
}
@Override
public void setAttached(boolean attached) {
isAttached = attached;
}
@Override
public String[] getSourcePaths() {
return sourcePaths;
}
@Override
public void setSourcePaths(String[] sourcePaths) {
this.sourcePaths = sourcePaths;
}
@Override
public String getSourceUri(int sourceReference) {
return sourceReferences.get(sourceReference);
}
@Override
public int createSourceReference(String uri) {
return sourceReferences.create(uri);
}
@Override
public RecyclableObjectPool<Long, Object> getRecyclableIdPool() {
return recyclableIdPool;
}
@Override
public void setRecyclableIdPool(RecyclableObjectPool<Long, Object> idPool) {
recyclableIdPool = idPool;
}
@Override
public IVariableFormatter getVariableFormatter() {
return variableFormatter;
}
@Override
public void setVariableFormatter(IVariableFormatter variableFormatter) {
this.variableFormatter = variableFormatter;
}
@Override
public Map<String, String> getSourceLookupCache() {
return sourceMappingCache;
}
@Override
public void setDebuggeeEncoding(Charset encoding) {
debuggeeEncoding = encoding;
}
@Override
public Charset getDebuggeeEncoding() {
return debuggeeEncoding;
}
@Override
public void setVmTerminated() {
vmTerminated = true;
}
@Override
public boolean isVmTerminated() {
return vmTerminated;
}
@Override
public void setVmStopOnEntry(boolean stopOnEntry) {
isVmStopOnEntry = stopOnEntry;
}
@Override
public boolean isVmStopOnEntry() {
return isVmStopOnEntry;
}
@Override
public void setMainClass(String mainClass) {
this.mainClass = mainClass;
}
@Override
public String getMainClass() {
return this.mainClass;
}
@Override
public void setStepFilters(StepFilters stepFilters) {
// For backward compatibility, merge the classNameFilters to skipClasses.
if (stepFilters != null && ArrayUtils.isNotEmpty(stepFilters.classNameFilters)) {
Set<String> patterns = new LinkedHashSet<>();
if (ArrayUtils.isNotEmpty(stepFilters.skipClasses)) {
patterns.addAll(Arrays.asList(stepFilters.skipClasses));
}
patterns.addAll(Arrays.asList(stepFilters.classNameFilters));
stepFilters.skipClasses = patterns.toArray(new String[0]);
}
this.stepFilters = stepFilters;
}
@Override
public StepFilters getStepFilters() {
if (stepFilters != null) {
return stepFilters;
} else if (DebugSettings.getCurrent().stepFilters != null) {
return DebugSettings.getCurrent().stepFilters;
}
return defaultFilters;
}
@Override
public IStackFrameManager getStackFrameManager() {
return stackFrameManager;
}
@Override
public LaunchMode getLaunchMode() {
return launchMode;
}
@Override
public void setLaunchMode(LaunchMode launchMode) {
this.launchMode = launchMode;
}
@Override
public Process getDebuggeeProcess() {
return this.debuggeeProcess;
}
@Override
public void setDebuggeeProcess(Process debuggeeProcess) {
this.debuggeeProcess = debuggeeProcess;
}
@Override
public void setClasspathJar(Path classpathJar) {
this.classpathJar = classpathJar;
}
@Override
public Path getClasspathJar() {
return this.classpathJar;
}
@Override
public void setArgsfile(Path argsfile) {
this.argsfile = argsfile;
}
@Override
public Path getArgsfile() {
return this.argsfile;
}
@Override
public IExceptionManager getExceptionManager() {
return this.exceptionManager;
}
@Override
public IBreakpointManager getBreakpointManager() {
return breakpointManager;
}
@Override
public IStepResultManager getStepResultManager() {
return stepResultManager;
}
@Override
public long getProcessId() {
return this.processId;
}
@Override
public long getShellProcessId() {
return this.shellProcessId;
}
@Override
public void setProcessId(long processId) {
this.processId = processId;
}
@Override
public void setShellProcessId(long shellProcessId) {
this.shellProcessId = shellProcessId;
}
@Override
public void setThreadCache(ThreadCache cache) {
this.threadCache = cache;
}
@Override
public ThreadCache getThreadCache() {
return this.threadCache;
}
@Override
public boolean asyncJDWP() {
/**
* If we take 1 second as the acceptable latency for DAP requests,
* With a single-threaded strategy for handling JDWP requests,
* a latency of about 15ms per JDWP request can ensure the responsiveness
* for most DAPs. It allows sending 66 JDWP requests within 1 seconds,
* which can cover most DAP operations such as breakpoint, threads,
* call stack, step and continue.
*/
return asyncJDWP(15);
}
@Override
public boolean asyncJDWP(long usableLatency) {
return DebugSettings.getCurrent().asyncJDWP == AsyncMode.ON
|| (DebugSettings.getCurrent().asyncJDWP == AsyncMode.AUTO && this.jdwpLatency > usableLatency);
}
public boolean isLocalDebugging() {
return localDebugging;
}
public void setLocalDebugging(boolean local) {
this.localDebugging = local;
}
@Override
public long getJDWPLatency() {
return this.jdwpLatency;
}
@Override
public void setJDWPLatency(long baseLatency) {
this.jdwpLatency = baseLatency;
}
@Override
public boolean isInitialized() {
return isInitialized;
}
@Override
public void setInitialized(boolean isInitialized) {
this.isInitialized = isInitialized;
}
}

View File

@ -0,0 +1,68 @@
/*******************************************************************************
* Copyright (c) 2017-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:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/
package com.microsoft.java.debug.core.adapter;
import java.util.Arrays;
public enum ErrorCode {
UNKNOWN_FAILURE(1000),
UNRECOGNIZED_REQUEST_FAILURE(1001),
LAUNCH_FAILURE(1002),
ATTACH_FAILURE(1003),
ARGUMENT_MISSING(1004),
SET_BREAKPOINT_FAILURE(1005),
SET_EXCEPTIONBREAKPOINT_FAILURE(1006),
GET_STACKTRACE_FAILURE(1007),
GET_VARIABLE_FAILURE(1008),
SET_VARIABLE_FAILURE(1009),
EVALUATE_FAILURE(1010),
EMPTY_DEBUG_SESSION(1011),
INVALID_ENCODING(1012),
VM_TERMINATED(1013),
LAUNCH_IN_TERMINAL_FAILURE(1014),
STEP_FAILURE(1015),
RESTARTFRAME_FAILURE(1016),
COMPLETIONS_FAILURE(1017),
EXCEPTION_INFO_FAILURE(1018),
EVALUATION_COMPILE_ERROR(2001),
EVALUATE_NOT_SUSPENDED_THREAD(2002),
HCR_FAILURE(3001),
INVALID_DAP_HEADER(3002);
private int id;
ErrorCode(int id) {
this.id = id;
}
public int getId() {
return id;
}
/**
* Get the corresponding ErrorCode type by the error code id.
* If the error code is not defined in the enum type, return ErrorCode.UNKNOWN_FAILURE.
* @param id
* the error code id.
* @return the ErrorCode type.
*/
public static ErrorCode parse(int id) {
ErrorCode[] found = Arrays.stream(ErrorCode.values()).filter(code -> {
return code.getId() == id;
}).toArray(ErrorCode[]::new);
if (found.length > 0) {
return found[0];
}
return ErrorCode.UNKNOWN_FAILURE;
}
}

View File

@ -0,0 +1,42 @@
/*******************************************************************************
* 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 java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import com.microsoft.java.debug.core.JdiExceptionReference;
public class ExceptionManager implements IExceptionManager {
private Map<Long, JdiExceptionReference> exceptions = Collections.synchronizedMap(new HashMap<>());
@Override
public JdiExceptionReference getException(long threadId) {
return exceptions.get(threadId);
}
@Override
public JdiExceptionReference removeException(long threadId) {
return exceptions.remove(threadId);
}
@Override
public JdiExceptionReference setException(long threadId, JdiExceptionReference exception) {
return exceptions.put(threadId, exception);
}
@Override
public void removeAllExceptions() {
exceptions.clear();
}
}

View File

@ -0,0 +1,65 @@
/*******************************************************************************
* Copyright (c) 2017 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;
public class HotCodeReplaceEvent {
public enum EventType {
ERROR(-1),
WARNING(-2),
STARTING(1),
END(2),
BUILD_COMPLETE(3);
private int value;
private EventType(int value) {
this.value = value;
}
public int getValue() {
return this.value;
}
}
private EventType eventType;
private String message;
private Object data;
public HotCodeReplaceEvent(EventType eventType, String message) {
this.eventType = eventType;
this.message = message;
}
public HotCodeReplaceEvent(EventType eventType, String message, Object data) {
this(eventType, message);
this.data = data;
}
public EventType getEventType() {
return eventType;
}
public String getMessage() {
return message;
}
public Object getData() {
return data;
}
}

View File

@ -0,0 +1,92 @@
/*******************************************************************************
* 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.IMethodBreakpoint;
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.
*
* <p>If the source file is modified, delete all cached breakpoints associated the file first and re-register the new breakpoints.</p>
*
* @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();
/**
* 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);
}

View File

@ -0,0 +1,35 @@
/*******************************************************************************
* Copyright (c) 2018 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 java.util.List;
import com.microsoft.java.debug.core.protocol.Types.CompletionItem;
import com.sun.jdi.StackFrame;
public interface ICompletionsProvider extends IProvider {
/**
* Complete the code snippet on the target frame.
*
* @param frame
* the target frame that the completions on
* @param snippet
* the code snippet text
* @param line
* the line number of the operation happens inside the snippet
* @param column
* the column number of the operation happens inside the snippet
* @return a list of {@link CompletionItem}
*/
List<CompletionItem> codeComplete(StackFrame frame, String snippet, int line, int column);
}

View File

@ -0,0 +1,20 @@
/*******************************************************************************
* Copyright (c) 2017 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 java.util.concurrent.CompletableFuture;
import com.microsoft.java.debug.core.protocol.Messages;
public interface IDebugAdapter {
CompletableFuture<Messages.Response> dispatchRequest(Messages.Request request);
}

View File

@ -0,0 +1,161 @@
/*******************************************************************************
* Copyright (c) 2017-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:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/
package com.microsoft.java.debug.core.adapter;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.util.Map;
import com.microsoft.java.debug.core.IDebugSession;
import com.microsoft.java.debug.core.adapter.variables.IVariableFormatter;
import com.microsoft.java.debug.core.protocol.IProtocolServer;
import com.microsoft.java.debug.core.protocol.Requests.StepFilters;
public interface IDebugAdapterContext {
IProtocolServer getProtocolServer();
<T extends IProvider> T getProvider(Class<T> clazz);
/**
* Set the debug session.
* @param session
* the new debug session
*/
void setDebugSession(IDebugSession session);
/**
* Get the debug session.
*
* @return the debug session.
*/
IDebugSession getDebugSession();
boolean isDebuggerLinesStartAt1();
void setDebuggerLinesStartAt1(boolean debuggerLinesStartAt1);
boolean isDebuggerPathsAreUri();
void setDebuggerPathsAreUri(boolean debuggerPathsAreUri);
boolean isClientLinesStartAt1();
void setClientLinesStartAt1(boolean clientLinesStartAt1);
boolean isClientColumnsStartAt1();
void setClientColumnsStartAt1(boolean clientColumnsStartAt1);
boolean isDebuggerColumnsStartAt1();
boolean isClientPathsAreUri();
void setClientPathsAreUri(boolean clientPathsAreUri);
void setSupportsRunInTerminalRequest(boolean supportsRunInTerminalRequest);
boolean supportsRunInTerminalRequest();
boolean isAttached();
void setAttached(boolean attached);
String[] getSourcePaths();
void setSourcePaths(String[] sourcePaths);
String getSourceUri(int sourceReference);
int createSourceReference(String uri);
RecyclableObjectPool<Long, Object> getRecyclableIdPool();
void setRecyclableIdPool(RecyclableObjectPool<Long, Object> idPool);
IVariableFormatter getVariableFormatter();
void setVariableFormatter(IVariableFormatter variableFormatter);
Map<String, String> getSourceLookupCache();
void setDebuggeeEncoding(Charset encoding);
Charset getDebuggeeEncoding();
void setVmTerminated();
boolean isVmTerminated();
void setVmStopOnEntry(boolean stopOnEntry);
boolean isVmStopOnEntry();
void setMainClass(String mainClass);
String getMainClass();
void setStepFilters(StepFilters stepFilters);
StepFilters getStepFilters();
IStackFrameManager getStackFrameManager();
LaunchMode getLaunchMode();
void setLaunchMode(LaunchMode launchMode);
Process getDebuggeeProcess();
void setDebuggeeProcess(Process debuggeeProcess);
void setClasspathJar(Path classpathJar);
Path getClasspathJar();
void setArgsfile(Path argsfile);
Path getArgsfile();
IExceptionManager getExceptionManager();
IBreakpointManager getBreakpointManager();
IStepResultManager getStepResultManager();
void setShellProcessId(long shellProcessId);
long getShellProcessId();
void setProcessId(long processId);
long getProcessId();
void setThreadCache(ThreadCache cache);
ThreadCache getThreadCache();
boolean asyncJDWP();
boolean asyncJDWP(long usableLatency/**ms*/);
boolean isLocalDebugging();
void setLocalDebugging(boolean local);
long getJDWPLatency();
void setJDWPLatency(long baseLatency);
boolean isInitialized();
void setInitialized(boolean isInitialized);
}

View File

@ -0,0 +1,30 @@
/*******************************************************************************
* Copyright (c) 2017 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 java.util.List;
import java.util.concurrent.CompletableFuture;
import com.microsoft.java.debug.core.protocol.Messages.Response;
import com.microsoft.java.debug.core.protocol.Requests;
import com.microsoft.java.debug.core.protocol.Requests.Arguments;
import com.microsoft.java.debug.core.protocol.Requests.Command;
public interface IDebugRequestHandler {
List<Command> getTargetCommands();
default void initialize(IDebugAdapterContext context) {
}
CompletableFuture<Response> handle(Command command, Arguments arguments, Response response, IDebugAdapterContext context);
}

View File

@ -0,0 +1,86 @@
/*******************************************************************************
* 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
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/
package com.microsoft.java.debug.core.adapter;
import java.util.concurrent.CompletableFuture;
import com.microsoft.java.debug.core.IEvaluatableBreakpoint;
import com.sun.jdi.ObjectReference;
import com.sun.jdi.ThreadReference;
import com.sun.jdi.Value;
/**
* An evaluation engine performs an evaluation of a code snippet or expression
* in a specified thread of a debug target. An evaluation engine is associated
* with a specific debug target and Java project on creation.
*/
public interface IEvaluationProvider extends IProvider {
/**
* This method provides the event hub the ability to exclude the breakpoint event raised during evaluation.
* @param thread the thread to be checked against evaluation work.
* @return whether or not the thread is performing evaluation
*/
boolean isInEvaluation(ThreadReference thread);
/**
* Evaluate the expression in the context of the specified stack frame, return the promise which is to be resolved/rejected when
* the evaluation finishes.
*
* @param expression The expression to be evaluated
* @param thread The suspended thread the evaluation will be executed at
* @param depth The stack frame depth in the suspended thread
* @return the evaluation result future
*/
CompletableFuture<Value> evaluate(String expression, ThreadReference thread, int depth);
/**
* Evaluate the expression in the context of the specified 'this' object, return the promise which is to be resolved/rejected when
* the evaluation finishes.
* @param expression The expression to be evaluated
* @param thisContext The 'this' context for the evaluation
* @param thread The suspended thread which the evaluation will be executed at
* @return the evaluation result future
*/
CompletableFuture<Value> evaluate(String expression, ObjectReference thisContext, ThreadReference thread);
/**
* Evaluate the conditional breakpoint or logpoint at the given thread and return the promise which is to be resolved/rejected when
* the evaluation finishes.
*
* @param breakpoint The evaluatable breakpoint
* @param thread The jdi thread to the expression will be executed at
* @return the evaluation result future
*/
CompletableFuture<Value> evaluateForBreakpoint(IEvaluatableBreakpoint breakpoint, ThreadReference thread);
/**
* Invoke the specified method with the given arguments at this object and the given thread, and return the result.
* The given thread is resumed to perform the method invocation. The thread will suspend in its originallocation when the method invocation is complete.
* @param thisContext The 'this' context for the invocation
* @param methodName The method to be invoked
* @param methodSignature The JNI style signature of the method to be invoked
* @param args The arguments of the method, which can be null or empty if there are none
* @param thread The thread in which to invoke the method
* @param invokeSuper true if the method lookup should begin in thisobject's superclass
* @return The result of invoking the method
*/
CompletableFuture<Value> invokeMethod(ObjectReference thisContext, String methodName, String methodSignature,
Value[] args, ThreadReference thread, boolean invokeSuper);
/**
* Call this method when the thread is to be resumed by user, it will first cancel ongoing evaluation tasks on specified thread and
* ensure the inner states is cleaned.
*
* @param thread the JDI thread reference where the evaluation task is executing at
*/
void clearState(ThreadReference thread);
}

View File

@ -0,0 +1,38 @@
/*******************************************************************************
* 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.JdiExceptionReference;
public interface IExceptionManager {
/**
* Returns the Exception associated with the thread.
*/
JdiExceptionReference getException(long threadId);
/**
* Removes the Exception associated with the thread. Returns the previous Exception mapping to the thread,
* null if no mapping exists.
*/
JdiExceptionReference removeException(long threadId);
/**
* Associates an Exception with the thread. Returns the previous Exception mapping to the thread,
* null if no mapping exists before.
*/
JdiExceptionReference setException(long threadId, JdiExceptionReference exception);
/**
* Clear all Exceptions.
*/
void removeAllExceptions();
}

View File

@ -0,0 +1,26 @@
/*******************************************************************************
* Copyright (c) 2017 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 java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import io.reactivex.Observable;
public interface IHotCodeReplaceProvider extends IProvider {
void onClassRedefined(Consumer<List<String>> consumer);
CompletableFuture<List<String>> redefineClasses();
Observable<HotCodeReplaceEvent> getEventHub();
}

View File

@ -0,0 +1,33 @@
/*******************************************************************************
* Copyright (c) 2017 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 java.util.Map;
public interface IProvider {
/**
* Initialize this provider.
*
* @param debugContext
* The associated debug context
* @param options
* the options
*/
default void initialize(IDebugAdapterContext debugContext, Map<String, Object> options) {
}
/**
* Close the provider and free all associated resources.
*/
default void close() {
}
}

View File

@ -0,0 +1,19 @@
/*******************************************************************************
* Copyright (c) 2017 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;
public interface IProviderContext {
<T extends IProvider> T getProvider(Class<T> clazz);
void registerProvider(Class<? extends IProvider> clazz, IProvider provider);
}

View File

@ -0,0 +1,112 @@
/*******************************************************************************
* Copyright (c) 2017-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:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/
package com.microsoft.java.debug.core.adapter;
import java.util.List;
import java.util.Objects;
import com.microsoft.java.debug.core.DebugException;
import com.microsoft.java.debug.core.JavaBreakpointLocation;
import com.microsoft.java.debug.core.protocol.Types.SourceBreakpoint;
public interface ISourceLookUpProvider extends IProvider {
boolean supportsRealtimeBreakpointVerification();
/**
* Deprecated, please use {@link #getBreakpointLocations(String, SourceBreakpoint[])} instead.
*/
@Deprecated
String[] getFullyQualifiedName(String uri, int[] lines, int[] columns) throws DebugException;
/**
* Given a set of source breakpoint locations with line and column numbers,
* verify if they are valid breakpoint locations. If it's a valid location,
* resolve its enclosing class name, method name and signature (for method
* breakpoint) and all possible inline breakpoint locations in that line.
*
* @param sourceUri
* the source file uri
* @param sourceBreakpoints
* the source breakpoints with line and column numbers
* @return Locations of Breakpoints containing context class and method information.
*/
JavaBreakpointLocation[] getBreakpointLocations(String sourceUri, SourceBreakpoint[] sourceBreakpoints) throws DebugException;
/**
* Given a fully qualified class name and source file path, search the associated disk source file.
*
* @param fullyQualifiedName
* the fully qualified class name (e.g. com.microsoft.java.debug.core.adapter.ISourceLookUpProvider).
* @param sourcePath
* the qualified source file path (e.g. com\microsoft\java\debug\core\adapter\ISourceLookupProvider.java).
* @return the associated source file uri.
*/
String getSourceFileURI(String fullyQualifiedName, String sourcePath);
String getSourceContents(String uri);
/**
* Returns the Java runtime that the specified project's build path used.
* @param projectName
* the specified project name
* @return the Java runtime version the specified project used. null if projectName is empty or doesn't exist.
*/
default String getJavaRuntimeVersion(String projectName) {
return null;
}
/**
* Return method invocation found in the statement as the given line number of
* the source file.
*
* @param uri The source file where the invocation must be searched.
* @param line The line number where the invocation must be searched.
*
* @return List of found method invocation or empty if not method invocations
* can be found.
*/
List<MethodInvocation> findMethodInvocations(String uri, int line);
public static class MethodInvocation {
public String expression;
public String methodName;
public String methodSignature;
public String methodGenericSignature;
public String declaringTypeName;
public int lineStart;
public int lineEnd;
public int columnStart;
public int columnEnd;
@Override
public int hashCode() {
return Objects.hash(expression, methodName, methodSignature, methodGenericSignature, declaringTypeName,
lineStart, lineEnd, columnStart, columnEnd);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof MethodInvocation)) {
return false;
}
MethodInvocation other = (MethodInvocation) obj;
return Objects.equals(expression, other.expression) && Objects.equals(methodName, other.methodName)
&& Objects.equals(methodSignature, other.methodSignature)
&& Objects.equals(methodGenericSignature, other.methodGenericSignature)
&& Objects.equals(declaringTypeName, other.declaringTypeName) && lineStart == other.lineStart
&& lineEnd == other.lineEnd && columnStart == other.columnStart && columnEnd == other.columnEnd;
}
}
}

View File

@ -0,0 +1,65 @@
/*******************************************************************************
* Copyright (c) 2017-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:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/
package com.microsoft.java.debug.core.adapter;
import com.microsoft.java.debug.core.adapter.variables.StackFrameReference;
import com.sun.jdi.StackFrame;
import com.sun.jdi.ThreadReference;
public interface IStackFrameManager {
/**
* Get a jdi stack frame from stack frame reference.
*
* @param ref the stackframe reference
* @return the jdi stackframe
*/
StackFrame getStackFrame(StackFrameReference ref);
/**
* Refresh all stackframes from jdi thread.
*
* @param thread the jdi thread
* @return all the stackframes in the specified thread
*/
StackFrame[] reloadStackFrames(ThreadReference thread);
/**
* Refresh all stackframes from jdi thread.
*
* @param thread the jdi thread
* @param force Whether to load the whole frames if the thread's stackframes haven't been cached.
* @return all the stackframes in the specified thread
*/
StackFrame[] reloadStackFrames(ThreadReference thread, boolean force);
/**
* Refersh the stackframes starting from the specified depth and length.
*
* @param thread the jdi thread
* @param start the index of the first frame to refresh. Index 0 represents the current frame.
* @param length the number of frames to refersh
* @return the refreshed stackframes
*/
StackFrame[] reloadStackFrames(ThreadReference thread, int start, int length);
/**
* Clear the stackframes cache from the specified thread.
*
* @param thread the jdi thread
*/
void clearStackFrames(ThreadReference thread);
/**
* Clear the whole stackframes cache.
*/
void clearStackFrames();
}

View File

@ -0,0 +1,24 @@
/*******************************************************************************
* Copyright (c) 2020 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.JdiMethodResult;
public interface IStepResultManager {
JdiMethodResult setMethodResult(long threadId, JdiMethodResult methodResult);
JdiMethodResult getMethodResult(long threadId);
JdiMethodResult removeMethodResult(long threadId);
void removeAllMethodResults();
}

View File

@ -0,0 +1,18 @@
/*******************************************************************************
* Copyright (c) 2020 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;
public interface IVirtualMachineManager extends com.sun.jdi.VirtualMachineManager {
boolean connectVirtualMachine(com.sun.jdi.VirtualMachine vm);
boolean disconnectVirtualMachine(com.sun.jdi.VirtualMachine vm);
}

View File

@ -0,0 +1,16 @@
/*******************************************************************************
* Copyright (c) 2017 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;
public interface IVirtualMachineManagerProvider extends IProvider {
com.sun.jdi.VirtualMachineManager getVirtualMachineManager();
}

View File

@ -0,0 +1,79 @@
/*******************************************************************************
* Copyright (c) 2017 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 java.util.HashMap;
import java.util.concurrent.atomic.AtomicInteger;
public class IdCollection<T> {
private int startId;
private AtomicInteger nextId;
private HashMap<Integer, T> idMap;
private HashMap<T, Integer> reverseMap;
public IdCollection() {
this(1);
}
/**
* Constructs a new id generator with the given startId as the start id number.
* @param startId
* the start id number
*/
public IdCollection(int startId) {
this.startId = startId;
this.nextId = new AtomicInteger(startId);
this.idMap = new HashMap<>();
this.reverseMap = new HashMap<>();
}
/**
* Reset the id to the initial start number.
*/
public void reset() {
this.nextId.set(this.startId);
this.idMap.clear();
this.reverseMap.clear();
}
/**
* Create a new id if the id doesn't exist for the given value.
* Otherwise return the existing id.
*/
public int create(T value) {
if (this.reverseMap.containsKey(value)) {
return this.reverseMap.get(value);
}
int id = this.nextId.getAndIncrement();
this.idMap.put(id, value);
this.reverseMap.put(value, id);
return id;
}
/**
* Get the original value by the id.
*/
public T get(int id) {
return this.idMap.get(id);
}
/**
* Remove the id from the id collection.
*/
public T remove(int id) {
T target = this.idMap.remove(id);
if (target != null) {
this.reverseMap.remove(target);
}
return target;
}
}

View File

@ -0,0 +1,38 @@
/*******************************************************************************
* Copyright (c) 2017 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 java.util.LinkedHashMap;
import java.util.Map;
public class LRUCache<K, V> extends LinkedHashMap<K, V> {
private static final long serialVersionUID = -7068164191168103891L;
private static final float DEFAULT_LOAD_FACTOR = 0.75f;
private int cacheSize;
/**
* Create a LUR cache with the max capacity.
* @param cacheSize the max size of elements in this cache.
*/
public LRUCache(int cacheSize) {
super((int) Math.ceil(cacheSize / DEFAULT_LOAD_FACTOR) + 1, DEFAULT_LOAD_FACTOR, true);
if (cacheSize < 0) {
throw new IllegalArgumentException("cacheSize is negative.");
}
this.cacheSize = cacheSize;
}
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > cacheSize;
}
}

View File

@ -0,0 +1,17 @@
/*******************************************************************************
* Copyright (c) 2018 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;
public enum LaunchMode {
DEBUG,
NO_DEBUG
}

View File

@ -0,0 +1,169 @@
/*******************************************************************************
* 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
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/
package com.microsoft.java.debug.core.adapter;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.stream.Stream;
import com.microsoft.java.debug.core.protocol.Events.OutputEvent.Category;
import io.reactivex.Observable;
import io.reactivex.schedulers.Schedulers;
import io.reactivex.subjects.PublishSubject;
public class ProcessConsole {
private InputStreamObservable stdoutStream;
private InputStreamObservable stderrStream;
private Observable<ConsoleMessage> observable = null;
public ProcessConsole(Process process) {
this(process, "Process", StandardCharsets.UTF_8);
}
/**
* Constructor.
* @param process
* the process
* @param name
* the process name
* @param encoding
* the process encoding format
*/
public ProcessConsole(Process process, String name, Charset encoding) {
this.stdoutStream = new InputStreamObservable(name + " Stdout Handler", process.getInputStream(), encoding);
this.stderrStream = new InputStreamObservable(name + " Stderr Handler", process.getErrorStream(), encoding);
Observable<ConsoleMessage> stdout = this.stdoutStream.messages().map((message) -> new ConsoleMessage(message, Category.stdout));
Observable<ConsoleMessage> stderr = this.stderrStream.messages().map((message) -> new ConsoleMessage(message, Category.stderr));
this.observable = Observable.mergeArrayDelayError(stdout, stderr).observeOn(Schedulers.newThread());
}
/**
* Start monitoring the stdout/stderr streams of the target process.
*/
public void start() {
stdoutStream.start();
stderrStream.start();
}
/**
* Stop monitoring the process console.
*/
public void stop() {
stdoutStream.stop();
stderrStream.stop();
}
public Observable<ConsoleMessage> messages() {
return observable;
}
public Observable<ConsoleMessage> stdoutMessages() {
return this.messages().filter((message) -> message.category == Category.stdout);
}
public Observable<ConsoleMessage> stderrMessages() {
return this.messages().filter((message) -> message.category == Category.stderr);
}
/**
* Split the stdio message to lines, and return them as a new Observable.
*/
public Observable<ConsoleMessage> lineMessages() {
return this.messages().map((message) -> {
String[] lines = message.output.split("(?<=\n)");
return Stream.of(lines).map((line) -> new ConsoleMessage(line, message.category)).toArray(ConsoleMessage[]::new);
}).concatMap((lines) -> Observable.fromArray(lines));
}
public static class InputStreamObservable {
private PublishSubject<String> rxSubject = PublishSubject.<String>create();
private String name;
private InputStream inputStream;
private Charset encoding;
private Thread loopingThread;
/**
* Constructor.
*/
public InputStreamObservable(String name, InputStream inputStream, Charset encoding) {
this.name = name;
this.inputStream = inputStream;
this.encoding = encoding;
}
/**
* Starts the stream.
*/
public void start() {
loopingThread = new Thread(name) {
public void run() {
monitor(inputStream, rxSubject);
}
};
loopingThread.setDaemon(true);
loopingThread.start();
}
/**
* Stops the stream.
*/
public void stop() {
if (loopingThread != null) {
loopingThread.interrupt();
loopingThread = null;
}
}
private void monitor(InputStream input, PublishSubject<String> subject) {
BufferedReader reader = new BufferedReader(encoding == null ? new InputStreamReader(input) : new InputStreamReader(input, encoding));
final int BUFFERSIZE = 4096;
char[] buffer = new char[BUFFERSIZE];
while (true) {
try {
if (Thread.interrupted()) {
subject.onComplete();
return;
}
int read = reader.read(buffer, 0, BUFFERSIZE);
if (read == -1) {
subject.onComplete();
return;
}
subject.onNext(new String(buffer, 0, read));
} catch (IOException e) {
subject.onError(e);
return;
}
}
}
public Observable<String> messages() {
return rxSubject;
}
}
public static class ConsoleMessage {
public String output;
public Category category;
public ConsoleMessage(String message, Category category) {
this.output = message;
this.category = category;
}
}
}

View File

@ -0,0 +1,164 @@
/*******************************************************************************
* Copyright (c) 2017 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 java.io.InputStream;
import java.io.OutputStream;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.microsoft.java.debug.core.Configuration;
import com.microsoft.java.debug.core.DebugException;
import com.microsoft.java.debug.core.UsageDataSession;
import com.microsoft.java.debug.core.protocol.AbstractProtocolServer;
import com.microsoft.java.debug.core.protocol.Events.DebugEvent;
import com.microsoft.java.debug.core.protocol.Events.StoppedEvent;
import com.microsoft.java.debug.core.protocol.Messages;
import com.sun.jdi.VMDisconnectedException;
public class ProtocolServer extends AbstractProtocolServer {
private static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME);
private IDebugAdapter debugAdapter;
private UsageDataSession usageDataSession = new UsageDataSession();
private Object lock = new Object();
private boolean isDispatchingRequest = false;
private ConcurrentLinkedQueue<DebugEvent> eventQueue = new ConcurrentLinkedQueue<>();
/**
* Constructs a protocol server instance based on the given input stream and output stream.
* @param input
* the input stream
* @param output
* the output stream
* @param context
* provider context for a series of provider implementation
*/
public ProtocolServer(InputStream input, OutputStream output, IProviderContext context) {
super(input, output);
debugAdapter = new DebugAdapter(this, context);
}
/**
* A while-loop to parse input data and send output data constantly.
*/
@Override
public void run() {
usageDataSession.reportStart();
super.run();
usageDataSession.reportStop();
usageDataSession.submitUsageData();
}
@Override
public void sendResponse(Messages.Response response) {
usageDataSession.recordResponse(response);
super.sendResponse(response);
}
@Override
public CompletableFuture<Messages.Response> sendRequest(Messages.Request request) {
usageDataSession.recordRequest(request);
return super.sendRequest(request);
}
@Override
public CompletableFuture<Messages.Response> sendRequest(Messages.Request request, long timeout) {
usageDataSession.recordRequest(request);
return super.sendRequest(request, timeout);
}
@Override
public void sendEvent(DebugEvent event) {
// See the two bugs https://github.com/Microsoft/java-debug/issues/134 and https://github.com/Microsoft/vscode/issues/58327,
// it requires the java-debug to send the StoppedEvent after ContinueResponse/StepResponse is received by DA.
if (event instanceof StoppedEvent) {
sendEventLater(event);
} else {
super.sendEvent(event);
}
}
/**
* If the the dispatcher is idle, then send the event to the DA immediately.
* Else add the new event to an eventQueue first and send them when dispatcher becomes idle again.
*/
private void sendEventLater(DebugEvent event) {
synchronized (lock) {
if (this.isDispatchingRequest) {
this.eventQueue.offer(event);
} else {
super.sendEvent(event);
}
}
}
@Override
protected void dispatchRequest(Messages.Request request) {
usageDataSession.recordRequest(request);
try {
synchronized (lock) {
this.isDispatchingRequest = true;
}
debugAdapter.dispatchRequest(request).thenCompose((response) -> {
CompletableFuture<Void> future = new CompletableFuture<>();
if (response != null) {
sendResponse(response);
future.complete(null);
} else {
future.completeExceptionally(new DebugException("The request dispatcher should not return null response.",
ErrorCode.UNKNOWN_FAILURE.getId()));
}
return future;
}).exceptionally((ex) -> {
Messages.Response response = new Messages.Response(request.seq, request.command);
if (ex instanceof CompletionException && ex.getCause() != null) {
ex = ex.getCause();
}
if (ex instanceof VMDisconnectedException) {
// mark it success to avoid reporting error on VSCode.
response.success = true;
sendResponse(response);
} else {
String exceptionMessage = ex.getMessage() != null ? ex.getMessage() : ex.toString();
ErrorCode errorCode = ex instanceof DebugException ? ErrorCode.parse(((DebugException) ex).getErrorCode()) : ErrorCode.UNKNOWN_FAILURE;
boolean isUserError = ex instanceof DebugException && ((DebugException) ex).isUserError();
if (isUserError) {
usageDataSession.recordUserError(errorCode);
} else {
logger.log(Level.SEVERE, String.format("[error response][%s]: %s", request.command, exceptionMessage), ex);
}
sendResponse(AdapterUtils.setErrorResponse(response,
errorCode,
exceptionMessage));
}
return null;
}).join();
} finally {
synchronized (lock) {
this.isDispatchingRequest = false;
}
while (this.eventQueue.peek() != null) {
super.sendEvent(this.eventQueue.poll());
}
}
}
}

View File

@ -0,0 +1,65 @@
/*******************************************************************************
* Copyright (c) 2017 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 java.util.HashMap;
import java.util.Map;
public class ProviderContext implements IProviderContext {
private Map<Class<? extends IProvider>, IProvider> providerMap;
public ProviderContext() {
providerMap = new HashMap<>();
}
/**
* Get the registered provider with the interface type,
* <code>IllegalArgumentException</code> will raise if the provider is absent.
* The returned object is type-safe to be assigned to T since registerProvider
* will check the compatibility, so suppress unchecked rule.
*/
@SuppressWarnings("unchecked")
@Override
public <T extends IProvider> T getProvider(Class<T> clazz) {
if (!providerMap.containsKey(clazz)) {
throw new IllegalArgumentException(String.format("%s has not been registered.", clazz.getName()));
}
return (T) providerMap.get(clazz);
}
@Override
public void registerProvider(Class<? extends IProvider> clazz, IProvider provider) {
if (clazz == null) {
throw new IllegalArgumentException("Null provider class is illegal.");
}
if (provider == null) {
throw new IllegalArgumentException("Null provider is illegal.");
}
if (providerMap.containsKey(clazz)) {
throw new IllegalArgumentException(String.format("%s has already been registered.", clazz.getName()));
}
if (!clazz.isInstance(provider)) {
throw new IllegalArgumentException(String.format("The provider doesn't implement interface %s.", clazz.getName()));
}
if (!clazz.isInterface()) {
throw new IllegalArgumentException("The provider class should be an interface");
}
providerMap.put(clazz, provider);
}
}

View File

@ -0,0 +1,138 @@
/*******************************************************************************
* Copyright (c) 2017 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 java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* An utility object pool class with the following ability:
* 1. store an object to get an object id.
* 2. remove an object.
* 3. remove objects which has specified owner.
* 4. remove all objects.
*
* <p>It is thread-safe, the duplicate object will not be stored, an object can be referenced by multiple owners, it is
* removed only when user explicitly calls removeObjectById or all the owners has been removed.</p>
*
* @param <O> the owner class type
* @param <V> the object type
*/
public class RecyclableObjectPool<O, V> {
private final IdCollection<V> objectCollection = new IdCollection<>();
private final Map<V, Set<O>> referenceMap = new HashMap<>();
private final Map<V, Integer> objectIdMap = new HashMap<>();
/**
* Add an object into this pool, if the object is already added, the original id will be used, it will also create a
* reference link from the object to its owner.
*
* @param owner the owner of this object
* @param object the object
* @return the inner id of this object
*/
public int addObject(O owner, V object) {
if (owner == null) {
throw new IllegalArgumentException("Owner cannot be null.");
}
if (object == null) {
throw new IllegalArgumentException("Null object cannot be added.");
}
synchronized (this) {
if (!referenceMap.containsKey(object)) {
// the object is new
Set<O> owners = new HashSet<>(1);
owners.add(owner);
referenceMap.put(object, owners);
int id = objectCollection.create(object);
objectIdMap.put(object, id);
return id;
} else {
// the object is already in this pool
referenceMap.get(object).add(owner);
return objectIdMap.get(object);
}
}
}
/**
* Get the object by object id.
*
* @param id the object id.
* @return the object, null if the object cannot be found.
*/
public V getObjectById(int id) {
synchronized (this) {
return objectCollection.get(id);
}
}
/**
* Remove the object by object id.
*
* @param id the object id.
* @return true if the object is removed successfully, false if the object cannot be found.
*/
public boolean removeObjectById(int id) {
synchronized (this) {
V object = this.objectCollection.remove(id);
if (object == null) {
return false;
}
referenceMap.remove(object);
objectIdMap.remove(object);
return true;
}
}
/**
* Remove a group of objects with the owner, the objects which only refers this owner will be removed.
*
* @param owner the owner.
* @return true if any object is removed.
*/
public boolean removeObjectsByOwner(O owner) {
if (owner == null) {
throw new IllegalArgumentException("owner cannot be null.");
}
synchronized (this) {
List<V> recycling = new ArrayList<>();
referenceMap.forEach((key, value) -> {
if (value.remove(owner)) {
if (value.isEmpty()) {
recycling.add(key);
}
}
});
for (V recycled : recycling) {
this.objectCollection.remove(objectIdMap.remove(recycled));
referenceMap.remove(recycled);
}
return !recycling.isEmpty();
}
}
/**
* Removes all the objects.
*/
public void removeAllObjects() {
synchronized (this) {
this.objectCollection.reset();
this.referenceMap.clear();
this.objectIdMap.clear();
}
}
}

View File

@ -0,0 +1,88 @@
/*******************************************************************************
* Copyright (c) 2017-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:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/
package com.microsoft.java.debug.core.adapter;
import java.util.HashMap;
import java.util.Map;
import com.microsoft.java.debug.core.adapter.variables.StackFrameReference;
import com.sun.jdi.IncompatibleThreadStateException;
import com.sun.jdi.StackFrame;
import com.sun.jdi.ThreadReference;
public class StackFrameManager implements IStackFrameManager {
private Map<Long, StackFrame[]> threadStackFrameMap = new HashMap<>();
@Override
public synchronized StackFrame getStackFrame(StackFrameReference ref) {
ThreadReference thread = ref.getThread();
int depth = ref.getDepth();
StackFrame[] frames = threadStackFrameMap.get(thread.uniqueID());
return frames == null || frames.length < depth ? null : frames[depth];
}
@Override
public synchronized StackFrame[] reloadStackFrames(ThreadReference thread) {
return reloadStackFrames(thread, true);
}
@Override
public synchronized StackFrame[] reloadStackFrames(ThreadReference thread, boolean force) {
return threadStackFrameMap.compute(thread.uniqueID(), (key, old) -> {
try {
if (old == null || old.length == 0) {
if (force) {
return thread.frames().toArray(new StackFrame[0]);
} else {
return new StackFrame[0];
}
} else {
return thread.frames(0, old.length).toArray(new StackFrame[0]);
}
} catch (IncompatibleThreadStateException e) {
return new StackFrame[0];
}
});
}
@Override
public synchronized StackFrame[] reloadStackFrames(ThreadReference thread, int start, int length) {
long threadId = thread.uniqueID();
StackFrame[] old = threadStackFrameMap.get(threadId);
try {
StackFrame[] newFrames = thread.frames(start, length).toArray(new StackFrame[0]);
if (old == null || (start == 0 && length == old.length)) {
threadStackFrameMap.put(threadId, newFrames);
} else {
int maxLength = Math.max(old.length, start + length);
StackFrame[] totalFrames = new StackFrame[maxLength];
System.arraycopy(old, 0, totalFrames, 0, old.length);
System.arraycopy(newFrames, 0, totalFrames, start, length);
threadStackFrameMap.put(threadId, totalFrames);
}
return newFrames;
} catch (IncompatibleThreadStateException | IndexOutOfBoundsException e) {
return new StackFrame[0];
}
}
@Override
public synchronized void clearStackFrames(ThreadReference thread) {
threadStackFrameMap.remove(thread.uniqueID());
}
@Override
public synchronized void clearStackFrames() {
threadStackFrameMap.clear();
}
}

View File

@ -0,0 +1,42 @@
/*******************************************************************************
* Copyright (c) 2020 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 java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import com.microsoft.java.debug.core.JdiMethodResult;
public class StepResultManager implements IStepResultManager {
private Map<Long, JdiMethodResult> methodResults = Collections.synchronizedMap(new HashMap<>());
@Override
public JdiMethodResult setMethodResult(long threadId, JdiMethodResult methodResult) {
return this.methodResults.put(threadId, methodResult);
}
@Override
public JdiMethodResult getMethodResult(long threadId) {
return this.methodResults.get(threadId);
}
@Override
public JdiMethodResult removeMethodResult(long threadId) {
return this.methodResults.remove(threadId);
}
@Override
public void removeAllMethodResults() {
this.methodResults.clear();
}
}

View File

@ -0,0 +1,116 @@
/*******************************************************************************
* 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:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/
package com.microsoft.java.debug.core.adapter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import com.sun.jdi.ThreadReference;
public class ThreadCache {
private List<ThreadReference> allThreads = new ArrayList<>();
private Map<Long, String> threadNameMap = new ConcurrentHashMap<>();
private Map<Long, Boolean> deathThreads = Collections.synchronizedMap(new LinkedHashMap<>() {
@Override
protected boolean removeEldestEntry(Map.Entry<Long, Boolean> eldest) {
return this.size() > 100;
}
});
private Map<Long, ThreadReference> eventThreads = new ConcurrentHashMap<>();
public synchronized void resetThreads(List<ThreadReference> threads) {
allThreads.clear();
allThreads.addAll(threads);
}
public synchronized List<ThreadReference> getThreads() {
return allThreads;
}
public synchronized ThreadReference getThread(long threadId) {
for (ThreadReference thread : allThreads) {
if (threadId == thread.uniqueID()) {
return thread;
}
}
for (ThreadReference thread : eventThreads.values()) {
if (threadId == thread.uniqueID()) {
return thread;
}
}
return null;
}
public void setThreadName(long threadId, String name) {
threadNameMap.put(threadId, name);
}
public String getThreadName(long threadId) {
return threadNameMap.get(threadId);
}
public void addDeathThread(long threadId) {
threadNameMap.remove(threadId);
eventThreads.remove(threadId);
deathThreads.put(threadId, true);
}
public boolean isDeathThread(long threadId) {
return deathThreads.containsKey(threadId);
}
public void addEventThread(ThreadReference thread) {
eventThreads.put(thread.uniqueID(), thread);
}
public void removeEventThread(long threadId) {
eventThreads.remove(threadId);
}
public void clearEventThread() {
eventThreads.clear();
}
/**
* The visible threads includes:
* 1. The currently running threads returned by the JDI API
* VirtualMachine.allThreads().
* 2. The threads suspended by events such as Breakpoint, Step, Exception etc.
*
* The part 2 is mainly for virtual threads, since VirtualMachine.allThreads()
* does not include virtual threads by default. For those virtual threads
* that are suspended, we need to show their call stacks in CALL STACK view.
*/
public List<ThreadReference> visibleThreads(IDebugAdapterContext context) {
List<ThreadReference> visibleThreads = new ArrayList<>(context.getDebugSession().getAllThreads());
Set<Long> idSet = new HashSet<>();
visibleThreads.forEach(thread -> idSet.add(thread.uniqueID()));
for (ThreadReference thread : eventThreads.values()) {
if (idSet.contains(thread.uniqueID())) {
continue;
}
idSet.add(thread.uniqueID());
visibleThreads.add(thread);
}
return visibleThreads;
}
}

View File

@ -0,0 +1,44 @@
/*******************************************************************************
* Copyright (c) 2017 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.formatter;
import static com.microsoft.java.debug.core.adapter.formatter.TypeIdentifiers.ARRAY;
import java.util.Map;
import java.util.function.BiFunction;
import com.sun.jdi.ArrayReference;
import com.sun.jdi.ObjectReference;
import com.sun.jdi.Type;
import com.sun.jdi.Value;
public class ArrayObjectFormatter extends ObjectFormatter {
public ArrayObjectFormatter(BiFunction<Type, Map<String, Object>, String> typeStringFunction) {
super(typeStringFunction);
}
@Override
protected String getPrefix(ObjectReference value, Map<String, Object> options) {
String arrayTypeWithLength = String.format("[%s]",
NumericFormatter.formatNumber(arrayLength(value), options));
return super.getPrefix(value, options).replaceFirst("\\[]", arrayTypeWithLength);
}
@Override
public boolean acceptType(Type type, Map<String, Object> options) {
return type != null && type.signature().charAt(0) == ARRAY;
}
private static int arrayLength(Value value) {
return ((ArrayReference) value).length();
}
}

View File

@ -0,0 +1,43 @@
/*******************************************************************************
* Copyright (c) 2017 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.formatter;
import static com.microsoft.java.debug.core.adapter.formatter.TypeIdentifiers.BOOLEAN;
import java.util.Map;
import com.sun.jdi.Type;
import com.sun.jdi.Value;
import com.sun.jdi.VirtualMachine;
public class BooleanFormatter implements IValueFormatter {
@Override
public String toString(Object value, Map<String, Object> options) {
return value == null ? NullObjectFormatter.NULL_STRING : value.toString();
}
@Override
public boolean acceptType(Type type, Map<String, Object> options) {
if (type == null) {
return false;
}
char signature0 = type.signature().charAt(0);
return signature0 == BOOLEAN;
}
@Override
public Value valueOf(String value, Type type, Map<String, Object> options) {
VirtualMachine vm = type.virtualMachine();
return vm.mirrorOf(Boolean.parseBoolean(value));
}
}

View File

@ -0,0 +1,51 @@
/*******************************************************************************
* Copyright (c) 2017 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.formatter;
import static com.microsoft.java.debug.core.adapter.formatter.TypeIdentifiers.CHAR;
import java.util.Map;
import com.sun.jdi.Type;
import com.sun.jdi.Value;
import com.sun.jdi.VirtualMachine;
public class CharacterFormatter implements IValueFormatter {
@Override
public String toString(Object value, Map<String, Object> options) {
return value == null ? NullObjectFormatter.NULL_STRING : value.toString();
}
@Override
public boolean acceptType(Type type, Map<String, Object> options) {
if (type == null) {
return false;
}
char signature0 = type.signature().charAt(0);
return signature0 == CHAR;
}
@Override
public Value valueOf(String value, Type type, Map<String, Object> options) {
VirtualMachine vm = type.virtualMachine();
if (value == null) {
return null;
}
if (value.length() == 3
&& value.startsWith("'")
&& value.endsWith("'")) {
return type.virtualMachine().mirrorOf(value.charAt(1));
}
return vm.mirrorOf(value.charAt(0));
}
}

View File

@ -0,0 +1,41 @@
/*******************************************************************************
* Copyright (c) 2017 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.formatter;
import static com.microsoft.java.debug.core.adapter.formatter.TypeIdentifiers.CLASS_OBJECT;
import static com.microsoft.java.debug.core.adapter.formatter.TypeIdentifiers.CLASS_SIGNATURE;
import java.util.Map;
import java.util.function.BiFunction;
import com.sun.jdi.ClassObjectReference;
import com.sun.jdi.ObjectReference;
import com.sun.jdi.Type;
public class ClassObjectFormatter extends ObjectFormatter {
public ClassObjectFormatter(BiFunction<Type, Map<String, Object>, String> typeStringFunction) {
super(typeStringFunction);
}
@Override
protected String getPrefix(ObjectReference value, Map<String, Object> options) {
Type classType = ((ClassObjectReference) value).reflectedType();
return String.format("%s (%s)", super.getPrefix(value, options),
typeToStringFunction.apply(classType, options));
}
@Override
public boolean acceptType(Type type, Map<String, Object> options) {
return super.acceptType(type, options) && (type.signature().charAt(0) == CLASS_OBJECT
|| type.signature().equals(CLASS_SIGNATURE));
}
}

View File

@ -0,0 +1,46 @@
/*******************************************************************************
* Copyright (c) 2017 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.formatter;
import java.util.HashMap;
import java.util.Map;
import com.sun.jdi.Type;
public interface IFormatter {
/**
* Get the string representations for an object.
*
* @param value the value
* @param options additional information about expected format.
* @return the string representations.
*/
String toString(Object value, Map<String, Object> options);
/**
* The conditional function for this formatter.
*
* @param type the JDI type
* @param options additional information about expected format
* @return whether or not this formatter is expected to work on this type.
*/
boolean acceptType(Type type, Map<String, Object> options);
/**
* Get the default options for this formatter.
* @return the default options
*/
default Map<String, Object> getDefaultOptions() {
return new HashMap<>();
}
}

View File

@ -0,0 +1,15 @@
/*******************************************************************************
* Copyright (c) 2017 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.formatter;
public interface ITypeFormatter extends IFormatter {
}

View File

@ -0,0 +1,30 @@
/*******************************************************************************
* Copyright (c) 2017 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.formatter;
import java.util.Map;
import com.sun.jdi.Type;
import com.sun.jdi.Value;
public interface IValueFormatter extends IFormatter {
/**
* Create the value from string, this method is used in setValue feature
* where converts user-input string to JDI value.
*
* @param value the string text.
* @param type the expected value type.
* @param options additional information about expected format
* @return the JDI value.
*/
Value valueOf(String value, Type type, Map<String, Object> options);
}

View File

@ -0,0 +1,40 @@
/*******************************************************************************
* Copyright (c) 2017 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.formatter;
import java.util.Map;
import com.sun.jdi.Type;
import com.sun.jdi.Value;
public class NullObjectFormatter implements IValueFormatter {
public static final String NULL_STRING = "null";
@Override
public String toString(Object value, Map<String, Object> options) {
return NULL_STRING;
}
@Override
public boolean acceptType(Type type, Map<String, Object> options) {
return type == null;
}
@Override
public Value valueOf(String value, Type type, Map<String, Object> options) {
if (value == null || NULL_STRING.equals(value)) {
return null;
}
throw new UnsupportedOperationException("Set value is not supported by NullObjectFormatter.");
}
}

View File

@ -0,0 +1,18 @@
/*******************************************************************************
* Copyright (c) 2017 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.formatter;
public enum NumericFormatEnum {
HEX,
OCT,
DEC
}

View File

@ -0,0 +1,155 @@
/*******************************************************************************
* Copyright (c) 2017 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.formatter;
import static com.microsoft.java.debug.core.adapter.formatter.TypeIdentifiers.BYTE;
import static com.microsoft.java.debug.core.adapter.formatter.TypeIdentifiers.DOUBLE;
import static com.microsoft.java.debug.core.adapter.formatter.TypeIdentifiers.FLOAT;
import static com.microsoft.java.debug.core.adapter.formatter.TypeIdentifiers.INT;
import static com.microsoft.java.debug.core.adapter.formatter.TypeIdentifiers.LONG;
import static com.microsoft.java.debug.core.adapter.formatter.TypeIdentifiers.SHORT;
import java.util.HashMap;
import java.util.Map;
import com.sun.jdi.Type;
import com.sun.jdi.Value;
import com.sun.jdi.VirtualMachine;
public class NumericFormatter implements IValueFormatter {
public static final String NUMERIC_FORMAT_OPTION = "numeric_format";
public static final String NUMERIC_PRECISION_OPTION = "numeric_precision";
private static final NumericFormatEnum DEFAULT_NUMERIC_FORMAT = NumericFormatEnum.DEC;
private static final int DEFAULT_NUMERIC_PRECISION = 0;
private static final Map<NumericFormatEnum, String> enumFormatMap = new HashMap<>();
static {
enumFormatMap.put(NumericFormatEnum.DEC, "%d");
enumFormatMap.put(NumericFormatEnum.HEX, "%#x");
enumFormatMap.put(NumericFormatEnum.OCT, "%#o");
}
/**
* Get the string representations for an object.
*
* @param obj the value object
* @param options extra information for printing
* @return the string representations.
*/
@Override
public String toString(Object obj, Map<String, Object> options) {
Value value = (Value) obj;
char signature0 = value.type().signature().charAt(0);
if (signature0 == LONG
|| signature0 == INT
|| signature0 == SHORT
|| signature0 == BYTE) {
return formatNumber(Long.parseLong(value.toString()), options);
} else if (hasFraction(signature0)) {
return formatFloatDouble(Double.parseDouble(value.toString()), options);
}
throw new UnsupportedOperationException(String.format("%s is not a numeric type.", value.type().name()));
}
@Override
public Value valueOf(String value, Type type, Map<String, Object> options) {
VirtualMachine vm = type.virtualMachine();
char signature0 = type.signature().charAt(0);
if (signature0 == LONG
|| signature0 == INT
|| signature0 == SHORT
|| signature0 == BYTE) {
long number = parseNumber(value);
if (signature0 == LONG) {
return vm.mirrorOf(number);
} else if (signature0 == INT) {
return vm.mirrorOf((int) number);
} else if (signature0 == SHORT) {
return vm.mirrorOf((short) number);
} else if (signature0 == BYTE) {
return vm.mirrorOf((byte) number);
}
} else if (hasFraction(signature0)) {
double doubleNumber = parseFloatDouble(value);
if (signature0 == DOUBLE) {
return vm.mirrorOf(doubleNumber);
} else {
return vm.mirrorOf((float) doubleNumber);
}
}
throw new UnsupportedOperationException(String.format("%s is not a numeric type.", type.name()));
}
/**
* The conditional function for this formatter.
*
* @param type the JDI type
* @return whether or not this formatter is expected to work on this value.
*/
@Override
public boolean acceptType(Type type, Map<String, Object> options) {
if (type == null) {
return false;
}
char signature0 = type.signature().charAt(0);
return signature0 == LONG
|| signature0 == INT
|| signature0 == SHORT
|| signature0 == BYTE
|| signature0 == FLOAT
|| signature0 == DOUBLE;
}
@Override
public Map<String, Object> getDefaultOptions() {
Map<String, Object> options = new HashMap<>();
options.put(NUMERIC_FORMAT_OPTION, DEFAULT_NUMERIC_FORMAT);
options.put(NUMERIC_PRECISION_OPTION, DEFAULT_NUMERIC_PRECISION);
return options;
}
static String formatNumber(long value, Map<String, Object> options) {
NumericFormatEnum formatEnum = getNumericFormatOption(options);
return String.format(enumFormatMap.get(formatEnum), value);
}
private static long parseNumber(String number) {
return Long.decode(number);
}
private static double parseFloatDouble(String number) {
return Double.parseDouble(number);
}
private static String formatFloatDouble(double value, Map<String, Object> options) {
int precision = getFractionPrecision(options);
return String.format(precision > 0 ? String.format("%%.%df", precision) : "%f", value);
}
private static NumericFormatEnum getNumericFormatOption(Map<String, Object> options) {
return options.containsKey(NUMERIC_FORMAT_OPTION)
? (NumericFormatEnum) options.get(NUMERIC_FORMAT_OPTION) : DEFAULT_NUMERIC_FORMAT;
}
private static boolean hasFraction(char signature0) {
return signature0 == FLOAT
|| signature0 == DOUBLE;
}
private static int getFractionPrecision(Map<String, Object> options) {
return options.containsKey(NUMERIC_PRECISION_OPTION)
? (int) options.get(NUMERIC_PRECISION_OPTION) : DEFAULT_NUMERIC_PRECISION;
}
}

View File

@ -0,0 +1,79 @@
/*******************************************************************************
* 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
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/
package com.microsoft.java.debug.core.adapter.formatter;
import static com.microsoft.java.debug.core.adapter.formatter.TypeIdentifiers.ARRAY;
import static com.microsoft.java.debug.core.adapter.formatter.TypeIdentifiers.CLASS_LOADER;
import static com.microsoft.java.debug.core.adapter.formatter.TypeIdentifiers.CLASS_OBJECT;
import static com.microsoft.java.debug.core.adapter.formatter.TypeIdentifiers.OBJECT;
import static com.microsoft.java.debug.core.adapter.formatter.TypeIdentifiers.STRING;
import static com.microsoft.java.debug.core.adapter.formatter.TypeIdentifiers.THREAD;
import static com.microsoft.java.debug.core.adapter.formatter.TypeIdentifiers.THREAD_GROUP;
import java.util.Map;
import java.util.function.BiFunction;
import com.sun.jdi.ObjectReference;
import com.sun.jdi.Type;
import com.sun.jdi.Value;
public class ObjectFormatter implements IValueFormatter {
/**
* The format type function for this object.
*/
protected final BiFunction<Type, Map<String, Object>, String> typeToStringFunction;
public ObjectFormatter(BiFunction<Type, Map<String, Object>, String> typeToStringFunction) {
this.typeToStringFunction = typeToStringFunction;
}
@Override
public String toString(Object obj, Map<String, Object> options) {
return String.format("%s@%s", getPrefix((ObjectReference) obj, options),
getIdPostfix((ObjectReference) obj, options));
}
@Override
public boolean acceptType(Type type, Map<String, Object> options) {
if (type == null) {
return false;
}
char tag = type.signature().charAt(0);
return (tag == OBJECT) || (tag == ARRAY) || (tag == STRING)
|| (tag == THREAD) || (tag == THREAD_GROUP)
|| (tag == CLASS_LOADER)
|| (tag == CLASS_OBJECT);
}
@Override
public Value valueOf(String value, Type type, Map<String, Object> options) {
if (value == null || NullObjectFormatter.NULL_STRING.equals(value)) {
return null;
}
throw new UnsupportedOperationException(String.format("Set value is not supported yet for type %s.", type.name()));
}
/**
* The type with additional prefix before id=${id} of this object.(eg: class, array length)
* @param value The object value.
* @param options additional information about expected format
* @return the type name with additional text
*/
protected String getPrefix(ObjectReference value, Map<String, Object> options) {
return typeToStringFunction.apply(value.type(), options);
}
protected static String getIdPostfix(ObjectReference obj, Map<String, Object> options) {
return NumericFormatter.formatNumber(obj.uniqueID(), options);
}
}

View File

@ -0,0 +1,70 @@
/*******************************************************************************
* Copyright (c) 2017 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.formatter;
import java.util.HashMap;
import java.util.Map;
import com.sun.jdi.Type;
public class SimpleTypeFormatter implements ITypeFormatter {
public static final String QUALIFIED_CLASS_NAME_OPTION = "qualified_class_name";
private static final boolean DEFAULT_QUALIFIED_CLASS_NAME_OPTION = false;
/**
* Format a JDI type, using the <code>SimpleTypeFormatter.QUALIFIED_FORMAT_OPTION</code> to control whether or not
* to use the fully qualified name. Set QUALIFIED_FORMAT_OPTION to true(<code>java.lang.Boolean</code>) to enable
* fully qualified name, the default option for QUALIFIED_FORMAT_OPTION is false.
*
* @param type the Jdi type
* @param options the format options
* @return the type name
*/
@Override
public String toString(Object type, Map<String, Object> options) {
if (type == null) {
return NullObjectFormatter.NULL_STRING;
}
String typeName = ((Type) type).name();
return showQualifiedClassName(options) ? typeName : trimTypeName(typeName);
}
@Override
public boolean acceptType(Type type, Map<String, Object> options) {
return true;
}
@Override
public Map<String, Object> getDefaultOptions() {
Map<String, Object> options = new HashMap<>();
options.put(QUALIFIED_CLASS_NAME_OPTION, DEFAULT_QUALIFIED_CLASS_NAME_OPTION);
return options;
}
/**
* An utility method for convert fully qualified class name to the simplified class name.
* @param type the fully qualified class name
* @return the simplified class name
*/
public static String trimTypeName(String type) {
if (type.indexOf('.') >= 0) {
type = type.substring(type.lastIndexOf('.') + 1);
}
return type;
}
private static boolean showQualifiedClassName(Map<String, Object> options) {
return options.containsKey(QUALIFIED_CLASS_NAME_OPTION)
? (Boolean) options.get(QUALIFIED_CLASS_NAME_OPTION) : DEFAULT_QUALIFIED_CLASS_NAME_OPTION;
}
}

View File

@ -0,0 +1,72 @@
/*******************************************************************************
* 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
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/
package com.microsoft.java.debug.core.adapter.formatter;
import static com.microsoft.java.debug.core.adapter.formatter.TypeIdentifiers.STRING;
import static com.microsoft.java.debug.core.adapter.formatter.TypeIdentifiers.STRING_SIGNATURE;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import com.sun.jdi.StringReference;
import com.sun.jdi.Type;
import com.sun.jdi.Value;
public class StringObjectFormatter extends ObjectFormatter implements IValueFormatter {
public static final String MAX_STRING_LENGTH_OPTION = "max_string_length";
private static final int DEFAULT_MAX_STRING_LENGTH = 0;
private static final String QUOTE_STRING = "\"";
public StringObjectFormatter() {
super(null);
}
@Override
public Map<String, Object> getDefaultOptions() {
Map<String, Object> options = new HashMap<>();
options.put(MAX_STRING_LENGTH_OPTION, DEFAULT_MAX_STRING_LENGTH);
return options;
}
@Override
public String toString(Object value, Map<String, Object> options) {
int maxLength = getMaxStringLength(options);
return String.format("\"%s\"",
maxLength > 0 ? StringUtils.abbreviate(((StringReference) value).value(), maxLength) : ((StringReference) value).value());
}
@Override
public boolean acceptType(Type type, Map<String, Object> options) {
return type != null && (type.signature().charAt(0) == STRING
|| type.signature().equals(STRING_SIGNATURE));
}
@Override
public Value valueOf(String value, Type type, Map<String, Object> options) {
if (value == null || NullObjectFormatter.NULL_STRING.equals(value)) {
return null;
}
if (value.length() >= 2
&& value.startsWith(QUOTE_STRING)
&& value.endsWith(QUOTE_STRING)) {
return type.virtualMachine().mirrorOf(StringUtils.substring(value, 1, -1));
}
return type.virtualMachine().mirrorOf(value);
}
private static int getMaxStringLength(Map<String, Object> options) {
return options.containsKey(MAX_STRING_LENGTH_OPTION)
? (int) options.get(MAX_STRING_LENGTH_OPTION) : DEFAULT_MAX_STRING_LENGTH;
}
}

View File

@ -0,0 +1,34 @@
/*******************************************************************************
* Copyright (c) 2017 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.formatter;
public final class TypeIdentifiers {
public static final char ARRAY = '[';
public static final char BYTE = 'B';
public static final char CHAR = 'C';
public static final char OBJECT = 'L';
public static final char FLOAT = 'F';
public static final char DOUBLE = 'D';
public static final char INT = 'I';
public static final char LONG = 'J';
public static final char SHORT = 'S';
public static final char BOOLEAN = 'Z';
public static final char STRING = 's';
public static final char THREAD = 't';
public static final char THREAD_GROUP = 'g';
public static final char CLASS_LOADER = 'l';
public static final char CLASS_OBJECT = 'c';
public static final String STRING_SIGNATURE = "Ljava/lang/String;";
public static final String CLASS_SIGNATURE = "Ljava/lang/Class;";
}

View File

@ -0,0 +1,110 @@
/*******************************************************************************
* 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.io.IOException;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.microsoft.java.debug.core.Configuration;
import com.microsoft.java.debug.core.adapter.IDebugAdapterContext;
import com.microsoft.java.debug.core.adapter.IDebugRequestHandler;
import com.microsoft.java.debug.core.adapter.IHotCodeReplaceProvider;
import com.microsoft.java.debug.core.adapter.LaunchMode;
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;
public abstract class AbstractDisconnectRequestHandler implements IDebugRequestHandler {
private static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME);
@Override
public List<Command> getTargetCommands() {
return Arrays.asList(Command.DISCONNECT);
}
@Override
public CompletableFuture<Response> handle(Command command, Arguments arguments, Response response,
IDebugAdapterContext context) {
context.setVmTerminated();
destroyDebugSession(command, arguments, response, context);
destroyResource(context);
return CompletableFuture.completedFuture(response);
}
/**
* Destroy the resources generated by the debug session.
*
* @param context the debug context
*/
private void destroyResource(IDebugAdapterContext context) {
destroyProviders(context);
if (shouldDestroyLaunchFiles(context)) {
destroyLaunchFiles(context);
}
}
private boolean shouldDestroyLaunchFiles(IDebugAdapterContext context) {
// Delete the temporary launch files must happen after the debuggee process is fully exited,
// otherwise it throws error saying the file is being used by other process.
// In Debug mode, the debugger is able to receive VM terminate event. It's sensible to do cleanup.
// In noDebug mode, if the debuggee is launched internally by the debugger, the debugger knows
// when the debuggee process exited. Should do cleanup. But if the debuggee is launched in the
// integrated/external terminal, the debugger lost the contact with the debuggee after it's launched.
// Have no idea when the debuggee is exited. So ignore the cleanup.
return context.getLaunchMode() == LaunchMode.DEBUG || context.getDebuggeeProcess() != null;
}
private void destroyLaunchFiles(IDebugAdapterContext context) {
// Sometimes when the debug session is terminated, the debuggee process is not exited immediately.
// Add retry to delete the temporary launch files.
int retry = 5;
while (retry-- > 0) {
try {
if (context.getClasspathJar() != null) {
Files.deleteIfExists(context.getClasspathJar());
context.setClasspathJar(null);
}
if (context.getArgsfile() != null) {
Files.deleteIfExists(context.getArgsfile());
context.setArgsfile(null);
}
break;
} catch (IOException e) {
// do nothing.
logger.log(Level.WARNING, "Failed to destory launch files, will retry again.");
}
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
// do nothing.
}
}
}
protected abstract void destroyDebugSession(Command command, Arguments arguments, Response response, IDebugAdapterContext context);
protected void destroyProviders(IDebugAdapterContext context) {
IHotCodeReplaceProvider hcrProvider = context.getProvider(IHotCodeReplaceProvider.class);
if (hcrProvider != null) {
hcrProvider.close();
}
}
}

View File

@ -0,0 +1,144 @@
/*******************************************************************************
* Copyright (c) 2017-2020 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.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.logging.Logger;
import com.microsoft.java.debug.core.Configuration;
import com.microsoft.java.debug.core.DebugUtility;
import com.microsoft.java.debug.core.IDebugSession;
import com.microsoft.java.debug.core.UsageDataSession;
import com.microsoft.java.debug.core.adapter.AdapterUtils;
import com.microsoft.java.debug.core.adapter.Constants;
import com.microsoft.java.debug.core.adapter.ErrorCode;
import com.microsoft.java.debug.core.adapter.ICompletionsProvider;
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.adapter.IHotCodeReplaceProvider;
import com.microsoft.java.debug.core.adapter.ISourceLookUpProvider;
import com.microsoft.java.debug.core.adapter.IVirtualMachineManagerProvider;
import com.microsoft.java.debug.core.protocol.Events;
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.AttachArguments;
import com.microsoft.java.debug.core.protocol.Requests.Command;
import com.sun.jdi.connect.IllegalConnectorArgumentsException;
import com.sun.jdi.request.EventRequest;
import org.apache.commons.lang3.StringUtils;
public class AttachRequestHandler implements IDebugRequestHandler {
private static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME);
private VMHandler vmHandler = new VMHandler();
@Override
public List<Command> getTargetCommands() {
return Arrays.asList(Command.ATTACH);
}
@Override
public CompletableFuture<Response> handle(Command command, Arguments arguments, Response response, IDebugAdapterContext context) {
AttachArguments attachArguments = (AttachArguments) arguments;
context.setAttached(true);
context.setSourcePaths(attachArguments.sourcePaths);
context.setDebuggeeEncoding(StandardCharsets.UTF_8); // Use UTF-8 as debuggee's default encoding format.
context.setStepFilters(attachArguments.stepFilters);
context.setLocalDebugging(isLocalHost(attachArguments.hostName));
Map<String, Object> traceInfo = new HashMap<>();
traceInfo.put("localAttach", context.isLocalDebugging());
traceInfo.put("asyncJDWP", context.asyncJDWP());
IVirtualMachineManagerProvider vmProvider = context.getProvider(IVirtualMachineManagerProvider.class);
vmHandler.setVmProvider(vmProvider);
IDebugSession debugSession = null;
try {
try {
logger.info(String.format("Trying to attach to remote debuggee VM %s:%d .", attachArguments.hostName, attachArguments.port));
debugSession = DebugUtility.attach(vmProvider.getVirtualMachineManager(), attachArguments.hostName, attachArguments.port,
attachArguments.timeout);
context.setDebugSession(debugSession);
vmHandler.connectVirtualMachine(debugSession.getVM());
logger.info("Attaching to debuggee VM succeeded.");
} catch (IOException | IllegalConnectorArgumentsException e) {
throw AdapterUtils.createCompletionException(
String.format("Failed to attach to remote debuggee VM. Reason: %s", e.toString()),
ErrorCode.ATTACH_FAILURE,
e);
}
Map<String, Object> options = new HashMap<>();
options.put(Constants.DEBUGGEE_ENCODING, context.getDebuggeeEncoding());
if (attachArguments.projectName != null) {
options.put(Constants.PROJECT_NAME, attachArguments.projectName);
}
// TODO: Clean up the initialize mechanism
ISourceLookUpProvider sourceProvider = context.getProvider(ISourceLookUpProvider.class);
sourceProvider.initialize(context, options);
// If the debugger and debuggee run at the different JVM platforms, show a warning message.
if (debugSession != null) {
String debuggeeVersion = debugSession.getVM().version();
String debuggerVersion = sourceProvider.getJavaRuntimeVersion(attachArguments.projectName);
if (StringUtils.isNotBlank(debuggerVersion) && !debuggerVersion.equals(debuggeeVersion)) {
String warnMessage = String.format("[Warn] The debugger and the debuggee are running in different versions of JVMs. "
+ "You could see wrong source mapping results.\n"
+ "Debugger JVM version: %s\n"
+ "Debuggee JVM version: %s", debuggerVersion, debuggeeVersion);
logger.warning(warnMessage);
context.getProtocolServer().sendEvent(Events.OutputEvent.createConsoleOutput(warnMessage));
}
EventRequest request = debugSession.getVM().eventRequestManager().createVMDeathRequest();
request.setSuspendPolicy(EventRequest.SUSPEND_NONE);
long sent = System.currentTimeMillis();
request.enable();
long received = System.currentTimeMillis();
long latency = received - sent;
context.setJDWPLatency(latency);
logger.info("Network latency for JDWP command: " + latency + "ms");
traceInfo.put("networkLatency", latency);
}
IEvaluationProvider evaluationProvider = context.getProvider(IEvaluationProvider.class);
evaluationProvider.initialize(context, options);
IHotCodeReplaceProvider hcrProvider = context.getProvider(IHotCodeReplaceProvider.class);
hcrProvider.initialize(context, options);
ICompletionsProvider completionsProvider = context.getProvider(ICompletionsProvider.class);
completionsProvider.initialize(context, options);
} finally {
UsageDataSession.recordInfo("attach debug info", traceInfo);
}
// Send an InitializedEvent to indicate that the debugger is ready to accept configuration requests
// (e.g. SetBreakpointsRequest, SetExceptionBreakpointsRequest).
context.getProtocolServer().sendEvent(new Events.InitializedEvent());
return CompletableFuture.completedFuture(response);
}
private boolean isLocalHost(String hostName) {
if (hostName == null || "localhost".equals(hostName) || "127.0.0.1".equals(hostName)) {
return true;
}
// TODO: Check the host name of current computer as well.
return false;
}
}

View File

@ -0,0 +1,83 @@
/*******************************************************************************
* 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:
* 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 java.util.stream.Stream;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import com.microsoft.java.debug.core.IBreakpoint;
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.protocol.Requests;
import com.microsoft.java.debug.core.protocol.Responses;
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.BreakpointLocationsArguments;
import com.microsoft.java.debug.core.protocol.Requests.Command;
import com.microsoft.java.debug.core.protocol.Types.BreakpointLocation;
/**
* The breakpointLocations request returns all possible locations for source breakpoints in a given range.
* Clients should only call this request if the corresponding capability supportsBreakpointLocationsRequest is true.
*/
public class BreakpointLocationsRequestHander implements IDebugRequestHandler {
@Override
public List<Command> getTargetCommands() {
return Arrays.asList(Command.BREAKPOINTLOCATIONS);
}
@Override
public CompletableFuture<Response> handle(Command command, Arguments arguments, Response response,
IDebugAdapterContext context) {
BreakpointLocationsArguments bpArgs = (BreakpointLocationsArguments) arguments;
String sourceUri = SetBreakpointsRequestHandler.normalizeSourcePath(bpArgs.source, context);
// When breakpoint source path is null or an invalid file path, send an ErrorResponse back.
if (StringUtils.isBlank(sourceUri)) {
throw AdapterUtils.createCompletionException(
String.format("Failed to get BreakpointLocations. Reason: '%s' is an invalid path.", bpArgs.source.path),
ErrorCode.SET_BREAKPOINT_FAILURE);
}
int debuggerLine = AdapterUtils.convertLineNumber(bpArgs.line, context.isClientLinesStartAt1(), context.isDebuggerLinesStartAt1());
IBreakpoint[] breakpoints = context.getBreakpointManager().getBreakpoints(sourceUri);
BreakpointLocation[] locations = new BreakpointLocation[0];
for (int i = 0; i < breakpoints.length; i++) {
if (breakpoints[i].getLineNumber() == debuggerLine && ArrayUtils.isNotEmpty(
breakpoints[i].sourceLocation().availableBreakpointLocations())) {
locations = Stream.of(breakpoints[i].sourceLocation().availableBreakpointLocations()).map(location -> {
BreakpointLocation newLocaiton = new BreakpointLocation();
newLocaiton.line = AdapterUtils.convertLineNumber(location.line,
context.isDebuggerLinesStartAt1(), context.isClientLinesStartAt1());
newLocaiton.column = AdapterUtils.convertColumnNumber(location.column,
context.isDebuggerColumnsStartAt1(), context.isClientColumnsStartAt1());
newLocaiton.endLine = AdapterUtils.convertLineNumber(location.endLine,
context.isDebuggerLinesStartAt1(), context.isClientLinesStartAt1());
newLocaiton.endColumn = AdapterUtils.convertColumnNumber(location.endColumn,
context.isDebuggerColumnsStartAt1(), context.isClientColumnsStartAt1());
return newLocaiton;
}).toArray(BreakpointLocation[]::new);
break;
}
}
response.body = new Responses.BreakpointLocationsResponseBody(locations);
return CompletableFuture.completedFuture(response);
}
}

View File

@ -0,0 +1,82 @@
/*******************************************************************************
* Copyright (c) 2018 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.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import com.microsoft.java.debug.core.adapter.AdapterUtils;
import com.microsoft.java.debug.core.adapter.ErrorCode;
import com.microsoft.java.debug.core.adapter.ICompletionsProvider;
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.protocol.Messages.Response;
import com.microsoft.java.debug.core.protocol.Requests;
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.CompletionsArguments;
import com.microsoft.java.debug.core.protocol.Responses;
import com.microsoft.java.debug.core.protocol.Types.CompletionItem;
import com.sun.jdi.IncompatibleThreadStateException;
import com.sun.jdi.ThreadReference;
public class CompletionsHandler implements IDebugRequestHandler {
@Override
public List<Command> getTargetCommands() {
return Arrays.asList(Command.COMPLETIONS);
}
@Override
public CompletableFuture<Response> handle(Command command, Arguments arguments, Response response, IDebugAdapterContext context) {
CompletionsArguments completionsArgs = (CompletionsArguments) arguments;
// completions should be illegal when frameId is zero, it is sent when the program is running, while during running we cannot resolve
// the completion candidates
if (completionsArgs.frameId == 0) {
response.body = new Responses.CompletionsResponseBody(Collections.emptyList());
return CompletableFuture.completedFuture(response);
}
StackFrameReference stackFrameReference = (StackFrameReference) context.getRecyclableIdPool().getObjectById(completionsArgs.frameId);
if (stackFrameReference == null) {
throw AdapterUtils.createCompletionException(
String.format("Completions: cannot find the stack frame with frameID %s", completionsArgs.frameId),
ErrorCode.COMPLETIONS_FAILURE
);
}
return CompletableFuture.supplyAsync(() -> {
try {
ICompletionsProvider completionsProvider = context.getProvider(ICompletionsProvider.class);
if (completionsProvider != null) {
ThreadReference thread = stackFrameReference.getThread();
List<CompletionItem> res = completionsProvider.codeComplete(thread.frame(stackFrameReference.getDepth()), completionsArgs.text,
completionsArgs.line, completionsArgs.column);
response.body = new Responses.CompletionsResponseBody(res);
}
return response;
} catch (IncompatibleThreadStateException e) {
throw AdapterUtils.createCompletionException(
String.format("Cannot provide code completions because of %s.", e.toString()),
ErrorCode.COMPLETIONS_FAILURE,
e
);
}
});
}
}

View File

@ -0,0 +1,132 @@
/*******************************************************************************
* Copyright (c) 2017-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:
* 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 java.util.logging.Logger;
import com.microsoft.java.debug.core.Configuration;
import com.microsoft.java.debug.core.DebugEvent;
import com.microsoft.java.debug.core.DebugUtility;
import com.microsoft.java.debug.core.IDebugSession;
import com.microsoft.java.debug.core.JdiExceptionReference;
import com.microsoft.java.debug.core.UsageDataSession;
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.adapter.IVirtualMachineManagerProvider;
import com.microsoft.java.debug.core.protocol.Events;
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.sun.jdi.ThreadReference;
import com.sun.jdi.event.BreakpointEvent;
import com.sun.jdi.event.Event;
import com.sun.jdi.event.ExceptionEvent;
import com.sun.jdi.event.ThreadDeathEvent;
import com.sun.jdi.event.ThreadStartEvent;
import com.sun.jdi.event.VMDeathEvent;
import com.sun.jdi.event.VMDisconnectEvent;
import com.sun.jdi.event.VMStartEvent;
public class ConfigurationDoneRequestHandler implements IDebugRequestHandler {
protected static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME);
private VMHandler vmHandler = new VMHandler();
@Override
public List<Command> getTargetCommands() {
return Arrays.asList(Command.CONFIGURATIONDONE);
}
@Override
public CompletableFuture<Response> handle(Command command, Arguments arguments, Response response, IDebugAdapterContext context) {
IDebugSession debugSession = context.getDebugSession();
vmHandler.setVmProvider(context.getProvider(IVirtualMachineManagerProvider.class));
if (debugSession != null) {
// This is a global event handler to handle the JDI Event from Virtual Machine.
debugSession.getEventHub().events().subscribe(debugEvent -> {
handleDebugEvent(debugEvent, debugSession, context);
});
// configuration is done, and start debug session.
debugSession.start();
return CompletableFuture.completedFuture(response);
} else {
context.getProtocolServer().sendEvent(new Events.TerminatedEvent());
return AdapterUtils.createAsyncErrorResponse(response, ErrorCode.EMPTY_DEBUG_SESSION, "Failed to launch debug session, the debugger will exit.");
}
}
private void handleDebugEvent(DebugEvent debugEvent, IDebugSession debugSession, IDebugAdapterContext context) {
Event event = debugEvent.event;
boolean isImportantEvent = true;
if (event instanceof VMStartEvent) {
if (context.isVmStopOnEntry()) {
DebugUtility.stopOnEntry(debugSession, context.getMainClass()).thenAccept(threadId -> {
context.getProtocolServer().sendEvent(new Events.StoppedEvent("entry", threadId));
});
}
} else if (event instanceof VMDeathEvent) {
vmHandler.disconnectVirtualMachine(event.virtualMachine());
context.setVmTerminated();
context.getProtocolServer().sendEvent(new Events.ExitedEvent(0));
} else if (event instanceof VMDisconnectEvent) {
vmHandler.disconnectVirtualMachine(event.virtualMachine());
if (context.isAttached()) {
context.setVmTerminated();
context.getProtocolServer().sendEvent(new Events.TerminatedEvent());
// Terminate eventHub thread.
try {
debugSession.getEventHub().close();
} catch (Exception e) {
// do nothing.
}
} else {
// Skip it when the debugger is in launch mode, because LaunchRequestHandler will handle the event there.
}
} else if (event instanceof ThreadStartEvent) {
ThreadReference startThread = ((ThreadStartEvent) event).thread();
Events.ThreadEvent threadEvent = new Events.ThreadEvent("started", startThread.uniqueID());
context.getProtocolServer().sendEvent(threadEvent);
} else if (event instanceof ThreadDeathEvent) {
ThreadReference deathThread = ((ThreadDeathEvent) event).thread();
Events.ThreadEvent threadDeathEvent = new Events.ThreadEvent("exited", deathThread.uniqueID());
context.getProtocolServer().sendEvent(threadDeathEvent);
context.getThreadCache().addDeathThread(deathThread.uniqueID());
} else if (event instanceof BreakpointEvent) {
// ignore since SetBreakpointsRequestHandler has already handled
} else if (event instanceof ExceptionEvent) {
ThreadReference thread = ((ExceptionEvent) event).thread();
IEvaluationProvider engine = context.getProvider(IEvaluationProvider.class);
if (engine.isInEvaluation(thread)) {
return;
}
JdiExceptionReference jdiException = new JdiExceptionReference(((ExceptionEvent) event).exception(),
((ExceptionEvent) event).catchLocation() == null);
context.getExceptionManager().setException(thread.uniqueID(), jdiException);
context.getThreadCache().addEventThread(thread);
context.getProtocolServer().sendEvent(new Events.StoppedEvent("exception", thread.uniqueID()));
debugEvent.shouldResume = false;
} else {
isImportantEvent = false;
}
// record events of important types only, to get rid of noises.
if (isImportantEvent) {
UsageDataSession.recordEvent(event);
}
}
}

View File

@ -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<Command> getTargetCommands() {
return Arrays.asList(Command.DATABREAKPOINTINFO);
}
@Override
public CompletableFuture<Response> 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];
}
}

View File

@ -0,0 +1,35 @@
/*******************************************************************************
* Copyright (c) 2017 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 com.microsoft.java.debug.core.IDebugSession;
import com.microsoft.java.debug.core.adapter.IDebugAdapterContext;
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.DisconnectArguments;
public class DisconnectRequestHandler extends AbstractDisconnectRequestHandler {
@Override
public void destroyDebugSession(Command command, Arguments arguments, Response response, IDebugAdapterContext context) {
DisconnectArguments disconnectArguments = (DisconnectArguments) arguments;
IDebugSession debugSession = context.getDebugSession();
if (debugSession != null) {
if (disconnectArguments.terminateDebuggee && !context.isAttached()) {
debugSession.terminate();
} else {
debugSession.detach();
}
}
}
}

View File

@ -0,0 +1,37 @@
/*******************************************************************************
* Copyright (c) 2018-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:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/
package com.microsoft.java.debug.core.adapter.handler;
import java.util.Optional;
import com.microsoft.java.debug.core.adapter.IDebugAdapterContext;
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.DisconnectArguments;
public class DisconnectRequestWithoutDebuggingHandler extends AbstractDisconnectRequestHandler {
@Override
public void destroyDebugSession(Command command, Arguments arguments, Response response, IDebugAdapterContext context) {
DisconnectArguments disconnectArguments = (DisconnectArguments) arguments;
Process debuggeeProcess = context.getDebuggeeProcess();
if (debuggeeProcess != null && disconnectArguments.terminateDebuggee) {
debuggeeProcess.destroy();
} else if (context.getProcessId() > 0 && disconnectArguments.terminateDebuggee) {
Optional<ProcessHandle> debuggeeHandle = ProcessHandle.of(context.getProcessId());
if (debuggeeHandle.isPresent()) {
debuggeeHandle.get().destroy();
}
}
}
}

View File

@ -0,0 +1,207 @@
/*******************************************************************************
* Copyright (c) 2017-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:
* 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.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.lang3.StringUtils;
import com.microsoft.java.debug.core.Configuration;
import com.microsoft.java.debug.core.DebugException;
import com.microsoft.java.debug.core.DebugSettings;
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.adapter.variables.IVariableFormatter;
import com.microsoft.java.debug.core.adapter.variables.JavaLogicalStructure;
import com.microsoft.java.debug.core.adapter.variables.JavaLogicalStructureManager;
import com.microsoft.java.debug.core.adapter.variables.StackFrameReference;
import com.microsoft.java.debug.core.adapter.variables.VariableDetailUtils;
import com.microsoft.java.debug.core.adapter.variables.VariableProxy;
import com.microsoft.java.debug.core.adapter.variables.VariableUtils;
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.EvaluateArguments;
import com.microsoft.java.debug.core.protocol.Responses;
import com.sun.jdi.ArrayReference;
import com.sun.jdi.IntegerValue;
import com.sun.jdi.ObjectReference;
import com.sun.jdi.Value;
import com.sun.jdi.VoidValue;
public class EvaluateRequestHandler implements IDebugRequestHandler {
protected static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME);
@Override
public List<Command> getTargetCommands() {
return Arrays.asList(Command.EVALUATE);
}
@Override
public CompletableFuture<Response> handle(Command command, Arguments arguments, Response response, IDebugAdapterContext context) {
EvaluateArguments evalArguments = (EvaluateArguments) arguments;
final boolean showStaticVariables = DebugSettings.getCurrent().showStaticVariables;
Map<String, Object> options = context.getVariableFormatter().getDefaultOptions();
VariableUtils.applyFormatterOptions(options, evalArguments.format != null && evalArguments.format.hex);
String expression = evalArguments.expression;
// Async mode is supposed to be performant, then disable the advanced features like hover evaluation.
if (context.asyncJDWP(VariablesRequestHandler.USABLE_JDWP_LATENCY)
&& context.getJDWPLatency() > VariablesRequestHandler.USABLE_JDWP_LATENCY
&& "hover".equals(evalArguments.context)) {
return CompletableFuture.completedFuture(response);
}
if (StringUtils.isBlank(expression)) {
throw new CompletionException(AdapterUtils.createUserErrorDebugException(
"Failed to evaluate. Reason: Empty expression cannot be evaluated.",
ErrorCode.EVALUATION_COMPILE_ERROR));
}
StackFrameReference stackFrameReference = (StackFrameReference) context.getRecyclableIdPool().getObjectById(evalArguments.frameId);
if (stackFrameReference == null) {
// stackFrameReference is null means the given thread is running
throw new CompletionException(AdapterUtils.createUserErrorDebugException(
"Evaluation failed because the thread is not suspended.",
ErrorCode.EVALUATE_NOT_SUSPENDED_THREAD));
}
return CompletableFuture.supplyAsync(() -> {
IEvaluationProvider engine = context.getProvider(IEvaluationProvider.class);
try {
Value value = engine.evaluate(expression, stackFrameReference.getThread(), stackFrameReference.getDepth()).get();
IVariableFormatter variableFormatter = context.getVariableFormatter();
if (value instanceof VoidValue) {
response.body = new Responses.EvaluateResponseBody(value.toString(), 0, "<void>", 0);
return response;
}
long threadId = stackFrameReference.getThread().uniqueID();
if (value instanceof ObjectReference) {
VariableProxy varProxy = new VariableProxy(stackFrameReference.getThread(), "eval", value, null, expression);
int indexedVariables = -1;
Value sizeValue = null;
if (value instanceof ArrayReference) {
indexedVariables = ((ArrayReference) value).length();
} else if (value instanceof ObjectReference && supportsLogicStructureView(context, evalArguments.context) && engine != null) {
try {
JavaLogicalStructure structure = JavaLogicalStructureManager.getLogicalStructure((ObjectReference) value);
if (structure != null && structure.getSizeExpression() != null) {
sizeValue = structure.getSize((ObjectReference) value, stackFrameReference.getThread(), engine);
if (sizeValue != null && sizeValue instanceof IntegerValue) {
indexedVariables = ((IntegerValue) sizeValue).value();
}
}
} catch (Exception e) {
logger.log(Level.INFO, "Failed to get the logical size of the variable", e);
}
}
int referenceId = 0;
if (indexedVariables > 0 || (indexedVariables < 0 && value instanceof ObjectReference)) {
referenceId = context.getRecyclableIdPool().addObject(threadId, varProxy);
}
boolean hasErrors = false;
String valueString = null;
try {
valueString = variableFormatter.valueToString(value, options);
} catch (OutOfMemoryError e) {
hasErrors = true;
logger.log(Level.SEVERE, "Failed to convert the value of a large object to a string", e);
valueString = "<Unable to display the value of a large object>";
} catch (Exception e) {
hasErrors = true;
logger.log(Level.SEVERE, "Failed to resolve the variable value", e);
valueString = "<Failed to resolve the variable value due to \"" + e.getMessage() + "\">";
}
String detailsString = null;
if (hasErrors) {
// If failed to resolve the variable value, skip the details info as well.
} else if (sizeValue != null) {
detailsString = "size=" + variableFormatter.valueToString(sizeValue, options);
} else if (supportsToStringView(context, evalArguments.context)) {
try {
detailsString = VariableDetailUtils.formatDetailsValue(value, stackFrameReference.getThread(), variableFormatter, options, engine);
} catch (OutOfMemoryError e) {
logger.log(Level.SEVERE, "Failed to compute the toString() value of a large object", e);
detailsString = "<Unable to display the details of a large object>";
} catch (Exception e) {
logger.log(Level.SEVERE, "Failed to compute the toString() value", e);
detailsString = "<Failed to resolve the variable details due to \"" + e.getMessage() + "\">";
}
}
if ("clipboard".equals(evalArguments.context) && detailsString != null) {
response.body = new Responses.EvaluateResponseBody(detailsString, -1, "String", 0);
} else {
String typeString = "";
try {
typeString = variableFormatter.typeToString(value == null ? null : value.type(), options);
} catch (Exception e) {
logger.log(Level.SEVERE, "Failed to resolve the variable type", e);
typeString = "";
}
response.body = new Responses.EvaluateResponseBody((detailsString == null) ? valueString : valueString + " " + detailsString,
referenceId, typeString, Math.max(indexedVariables, 0));
}
return response;
}
// for primitive value
response.body = new Responses.EvaluateResponseBody(variableFormatter.valueToString(value, options), 0,
variableFormatter.typeToString(value == null ? null : value.type(), options), 0);
return response;
} catch (InterruptedException | ExecutionException e) {
Throwable cause = e;
if (e instanceof ExecutionException && e.getCause() != null) {
cause = e.getCause();
}
if (cause instanceof DebugException) {
throw new CompletionException(cause);
}
throw AdapterUtils.createCompletionException(
String.format("Cannot evaluate because of %s.", cause.toString()),
ErrorCode.EVALUATE_FAILURE,
cause);
}
});
}
private boolean supportsLogicStructureView(IDebugAdapterContext context, String evalContext) {
if (!"watch".equals(evalContext)) {
return true;
}
return (!context.asyncJDWP(VariablesRequestHandler.USABLE_JDWP_LATENCY)
|| context.getJDWPLatency() <= VariablesRequestHandler.USABLE_JDWP_LATENCY)
&& DebugSettings.getCurrent().showLogicalStructure;
}
private boolean supportsToStringView(IDebugAdapterContext context, String evalContext) {
if (!"watch".equals(evalContext)) {
return true;
}
return (!context.asyncJDWP(VariablesRequestHandler.USABLE_JDWP_LATENCY)
|| context.getJDWPLatency() <= VariablesRequestHandler.USABLE_JDWP_LATENCY)
&& DebugSettings.getCurrent().showToString;
}
}

View File

@ -0,0 +1,103 @@
/*******************************************************************************
* Copyright (c) 2019-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:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/
package com.microsoft.java.debug.core.adapter.handler;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.microsoft.java.debug.core.Configuration;
import com.microsoft.java.debug.core.DebugUtility;
import com.microsoft.java.debug.core.JdiExceptionReference;
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.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.ExceptionInfoArguments;
import com.microsoft.java.debug.core.protocol.Responses;
import com.microsoft.java.debug.core.protocol.Types.ExceptionBreakMode;
import com.sun.jdi.ClassNotLoadedException;
import com.sun.jdi.IncompatibleThreadStateException;
import com.sun.jdi.InvalidTypeException;
import com.sun.jdi.InvocationException;
import com.sun.jdi.Method;
import com.sun.jdi.ObjectReference;
import com.sun.jdi.ThreadReference;
import com.sun.jdi.Value;
public class ExceptionInfoRequestHandler implements IDebugRequestHandler {
protected static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME);
@Override
public List<Command> getTargetCommands() {
return Arrays.asList(Command.EXCEPTIONINFO);
}
@Override
public CompletableFuture<Response> handle(Command command, Arguments arguments, Response response,
IDebugAdapterContext context) {
ExceptionInfoArguments exceptionInfoArgs = (ExceptionInfoArguments) arguments;
ThreadReference thread = context.getThreadCache().getThread(exceptionInfoArgs.threadId);
if (thread == null) {
thread = DebugUtility.getThread(context.getDebugSession(), exceptionInfoArgs.threadId);
}
if (thread == null) {
throw AdapterUtils.createCompletionException("Thread " + exceptionInfoArgs.threadId + " doesn't exist.", ErrorCode.EXCEPTION_INFO_FAILURE);
}
JdiExceptionReference jdiException = context.getExceptionManager().getException(exceptionInfoArgs.threadId);
if (jdiException == null) {
throw AdapterUtils.createCompletionException("No exception exists in thread " + exceptionInfoArgs.threadId, ErrorCode.EXCEPTION_INFO_FAILURE);
}
Method toStringMethod = null;
for (Method method : jdiException.exception.referenceType().allMethods()) {
if (Objects.equals("toString", method.name()) && Objects.equals("()Ljava/lang/String;", method.signature())) {
toStringMethod = method;
break;
}
}
String typeName = jdiException.exception.type().name();
String exceptionToString = typeName;
if (toStringMethod != null) {
try {
Value returnValue = jdiException.exception.invokeMethod(thread, toStringMethod, Collections.EMPTY_LIST, ObjectReference.INVOKE_SINGLE_THREADED);
exceptionToString = returnValue.toString();
} catch (InvalidTypeException | ClassNotLoadedException | IncompatibleThreadStateException
| InvocationException e) {
logger.log(Level.SEVERE, String.format("Failed to get the return value of the method Exception.toString(): %s", e.toString(), e));
} finally {
try {
// See bug https://github.com/microsoft/vscode-java-debug/issues/767:
// The operation exception.invokeMethod above will resume the thread, that will cause
// the previously cached stack frames for this thread to be invalid.
context.getStackFrameManager().reloadStackFrames(thread);
} catch (Exception e) {
// do nothing.
}
}
}
response.body = new Responses.ExceptionInfoResponse(typeName, exceptionToString,
jdiException.isUncaught ? ExceptionBreakMode.USERUNHANDLED : ExceptionBreakMode.ALWAYS);
return CompletableFuture.completedFuture(response);
}
}

View File

@ -0,0 +1,72 @@
/*******************************************************************************
* 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
* 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 com.microsoft.java.debug.core.adapter.HotCodeReplaceEvent;
import com.microsoft.java.debug.core.adapter.IDebugAdapterContext;
import com.microsoft.java.debug.core.adapter.IDebugRequestHandler;
import com.microsoft.java.debug.core.adapter.IHotCodeReplaceProvider;
import com.microsoft.java.debug.core.protocol.Events;
import com.microsoft.java.debug.core.protocol.Events.HotCodeReplaceEvent.ChangeType;
import com.microsoft.java.debug.core.protocol.Messages.Response;
import com.microsoft.java.debug.core.protocol.Requests;
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.Responses;
public class HotCodeReplaceHandler implements IDebugRequestHandler {
@Override
public List<Command> getTargetCommands() {
return Arrays.asList(Command.REDEFINECLASSES);
}
@Override
public void initialize(IDebugAdapterContext context) {
IDebugRequestHandler.super.initialize(context);
IHotCodeReplaceProvider provider = context.getProvider(IHotCodeReplaceProvider.class);
provider.getEventHub()
.subscribe(event -> {
if (event.getEventType() == HotCodeReplaceEvent.EventType.BUILD_COMPLETE) {
context.getProtocolServer().sendEvent(new Events.HotCodeReplaceEvent(ChangeType.BUILD_COMPLETE, event.getMessage()));
} else if (event.getEventType() == HotCodeReplaceEvent.EventType.STARTING) {
context.getProtocolServer().sendEvent(new Events.HotCodeReplaceEvent(ChangeType.STARTING, event.getMessage()));
} else if (event.getEventType() == HotCodeReplaceEvent.EventType.END) {
context.getProtocolServer().sendEvent(new Events.HotCodeReplaceEvent(ChangeType.END, event.getMessage()));
} else if (event.getEventType() == HotCodeReplaceEvent.EventType.ERROR) {
context.getProtocolServer().sendEvent(new Events.HotCodeReplaceEvent(ChangeType.ERROR, event.getMessage()));
} else if (event.getEventType() == HotCodeReplaceEvent.EventType.WARNING) {
context.getProtocolServer().sendEvent(new Events.HotCodeReplaceEvent(ChangeType.WARNING, event.getMessage()));
}
});
}
@Override
public CompletableFuture<Response> handle(Command command, Arguments arguments, Response response,
IDebugAdapterContext context) {
IHotCodeReplaceProvider provider = context.getProvider(IHotCodeReplaceProvider.class);
return provider.redefineClasses().thenCompose(classNames -> {
response.body = new Responses.RedefineClassesResponse(classNames.toArray(new String[0]));
return CompletableFuture.completedFuture(response);
}).exceptionally(ex -> {
String errorMessage = ex.getCause() != null ? ex.getCause().getMessage() : ex.getMessage();
response.body = new Responses.RedefineClassesResponse(new String[0], errorMessage);
return response;
});
}
}

View File

@ -0,0 +1,33 @@
/*******************************************************************************
* Copyright (c) 2018 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.io.IOException;
import java.util.concurrent.CompletableFuture;
import com.microsoft.java.debug.core.adapter.IDebugAdapterContext;
import com.microsoft.java.debug.core.protocol.Messages.Response;
import com.microsoft.java.debug.core.protocol.Requests.LaunchArguments;
import com.sun.jdi.connect.IllegalConnectorArgumentsException;
import com.sun.jdi.connect.VMStartException;
public interface ILaunchDelegate {
void postLaunch(LaunchArguments launchArguments, IDebugAdapterContext context);
void preLaunch(LaunchArguments launchArguments, IDebugAdapterContext context);
CompletableFuture<Response> launchInTerminal(LaunchArguments launchArguments, Response response, IDebugAdapterContext context);
Process launch(LaunchArguments launchArguments, IDebugAdapterContext context)
throws IOException, IllegalConnectorArgumentsException, VMStartException;
}

View File

@ -0,0 +1,73 @@
/*******************************************************************************
* Copyright (c) 2017-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:
* 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 com.microsoft.java.debug.core.adapter.IDebugAdapterContext;
import com.microsoft.java.debug.core.adapter.IDebugRequestHandler;
import com.microsoft.java.debug.core.protocol.Messages;
import com.microsoft.java.debug.core.protocol.Requests;
import com.microsoft.java.debug.core.protocol.Types;
public class InitializeRequestHandler implements IDebugRequestHandler {
@Override
public List<Requests.Command> getTargetCommands() {
return Arrays.asList(Requests.Command.INITIALIZE);
}
@Override
public CompletableFuture<Messages.Response> handle(Requests.Command command, Requests.Arguments argument, Messages.Response response,
IDebugAdapterContext context) {
Requests.InitializeArguments initializeArguments = (Requests.InitializeArguments) argument;
context.setClientLinesStartAt1(initializeArguments.linesStartAt1);
context.setClientColumnsStartAt1(initializeArguments.columnsStartAt1);
String pathFormat = initializeArguments.pathFormat;
if (pathFormat != null) {
switch (pathFormat) {
case "uri":
context.setClientPathsAreUri(true);
break;
default:
context.setClientPathsAreUri(false);
}
}
context.setSupportsRunInTerminalRequest(initializeArguments.supportsRunInTerminalRequest);
Types.Capabilities caps = new Types.Capabilities();
caps.supportsConfigurationDoneRequest = true;
caps.supportsHitConditionalBreakpoints = true;
caps.supportsConditionalBreakpoints = true;
caps.supportsSetVariable = true;
caps.supportTerminateDebuggee = true;
caps.supportsCompletionsRequest = true;
caps.supportsRestartFrame = true;
caps.supportsLogPoints = true;
caps.supportsEvaluateForHovers = true;
Types.ExceptionBreakpointFilter[] exceptionFilters = {
Types.ExceptionBreakpointFilter.UNCAUGHT_EXCEPTION_FILTER,
Types.ExceptionBreakpointFilter.CAUGHT_EXCEPTION_FILTER,
};
caps.exceptionBreakpointFilters = exceptionFilters;
caps.supportsExceptionInfoRequest = true;
caps.supportsDataBreakpoints = true;
caps.supportsFunctionBreakpoints = true;
caps.supportsClipboardContext = true;
caps.supportsBreakpointLocationsRequest = true;
caps.supportsStepInTargetsRequest = true;
response.body = caps;
context.setInitialized(true);
return CompletableFuture.completedFuture(response);
}
}

View File

@ -0,0 +1,257 @@
/*******************************************************************************
* Copyright (c) 2021 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.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.microsoft.java.debug.core.Configuration;
import com.microsoft.java.debug.core.DebugSettings;
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.adapter.IStackFrameManager;
import com.microsoft.java.debug.core.adapter.variables.IVariableFormatter;
import com.microsoft.java.debug.core.adapter.variables.JavaLogicalStructure;
import com.microsoft.java.debug.core.adapter.variables.JavaLogicalStructureManager;
import com.microsoft.java.debug.core.adapter.variables.StackFrameReference;
import com.microsoft.java.debug.core.adapter.variables.Variable;
import com.microsoft.java.debug.core.adapter.variables.VariableDetailUtils;
import com.microsoft.java.debug.core.protocol.Responses;
import com.microsoft.java.debug.core.protocol.Types;
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.InlineVariable;
import com.microsoft.java.debug.core.protocol.Requests.InlineValuesArguments;
import com.sun.jdi.ArrayReference;
import com.sun.jdi.Field;
import com.sun.jdi.IntegerValue;
import com.sun.jdi.Method;
import com.sun.jdi.ObjectReference;
import com.sun.jdi.ReferenceType;
import com.sun.jdi.StackFrame;
import com.sun.jdi.Value;
import org.apache.commons.lang3.math.NumberUtils;
public class InlineValuesRequestHandler implements IDebugRequestHandler {
protected static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME);
@Override
public List<Command> getTargetCommands() {
return Arrays.asList(Command.INLINEVALUES);
}
/**
* This request only resolves the values for those non-local variables, such as
* field variables and captured variables from outer scope. Because the values
* of local variables in current stackframe are usually expanded by Variables View
* by default, inline values can reuse these values directly. However, for field
* variables and variables captured from external scopes, they are hidden as properties
* of 'this' variable and require additional evaluation to get their values.
*/
@Override
public CompletableFuture<Response> handle(Command command, Arguments arguments, Response response,
IDebugAdapterContext context) {
InlineValuesArguments inlineValuesArgs = (InlineValuesArguments) arguments;
final int variableCount = inlineValuesArgs == null || inlineValuesArgs.variables == null ? 0 : inlineValuesArgs.variables.length;
InlineVariable[] inlineVariables = inlineValuesArgs.variables;
StackFrameReference stackFrameReference = (StackFrameReference) context.getRecyclableIdPool().getObjectById(inlineValuesArgs.frameId);
if (stackFrameReference == null) {
logger.log(Level.SEVERE, String.format("InlineValues failed: invalid stackframe id %d.", inlineValuesArgs.frameId));
response.body = new Responses.InlineValuesResponse(null);
return CompletableFuture.completedFuture(response);
}
// Async mode is supposed to be performant, then disable the advanced features like inline values.
if (context.getJDWPLatency() > VariablesRequestHandler.USABLE_JDWP_LATENCY
&& context.asyncJDWP(VariablesRequestHandler.USABLE_JDWP_LATENCY)) {
response.body = new Responses.InlineValuesResponse(null);
return CompletableFuture.completedFuture(response);
}
IStackFrameManager stackFrameManager = context.getStackFrameManager();
StackFrame frame = stackFrameManager.getStackFrame(stackFrameReference);
if (frame == null) {
logger.log(Level.SEVERE, String.format("InlineValues failed: stale stackframe id %d.", inlineValuesArgs.frameId));
response.body = new Responses.InlineValuesResponse(null);
return CompletableFuture.completedFuture(response);
}
Variable[] values = new Variable[variableCount];
try {
if (isLambdaFrame(frame)) {
// Lambda expression stores the captured variables from 'outer' scope in a synthetic stackframe below the lambda frame.
StackFrame syntheticLambdaFrame = stackFrameReference.getThread().frame(stackFrameReference.getDepth() + 1);
resolveValuesFromThisVariable(syntheticLambdaFrame.thisObject(), inlineVariables, values, true);
}
resolveValuesFromThisVariable(frame.thisObject(), inlineVariables, values, false);
} catch (Exception ex) {
// do nothig
}
Types.Variable[] result = new Types.Variable[variableCount];
IVariableFormatter variableFormatter = context.getVariableFormatter();
Map<String, Object> formatterOptions = variableFormatter.getDefaultOptions();
Map<InlineVariable, Types.Variable> calculatedValues = new HashMap<>();
IEvaluationProvider evaluationEngine = context.getProvider(IEvaluationProvider.class);
for (int i = 0; i < variableCount; i++) {
if (values[i] == null) {
continue;
}
if (calculatedValues.containsKey(inlineVariables[i])) {
result[i] = calculatedValues.get(inlineVariables[i]);
continue;
}
Value value = values[i].value;
String name = values[i].name;
int indexedVariables = -1;
Value sizeValue = null;
if (value instanceof ArrayReference) {
indexedVariables = ((ArrayReference) value).length();
} else if (value instanceof ObjectReference && DebugSettings.getCurrent().showLogicalStructure && evaluationEngine != null) {
try {
JavaLogicalStructure structure = JavaLogicalStructureManager.getLogicalStructure((ObjectReference) value);
if (structure != null && structure.getSizeExpression() != null) {
sizeValue = structure.getSize((ObjectReference) value, frame.thread(), evaluationEngine);
if (sizeValue != null && sizeValue instanceof IntegerValue) {
indexedVariables = ((IntegerValue) sizeValue).value();
}
}
} catch (CancellationException | IllegalArgumentException | InterruptedException | ExecutionException | UnsupportedOperationException e) {
logger.log(Level.INFO,
String.format("Failed to get the logical size for the type %s.", value.type().name()), e);
}
}
Types.Variable formattedVariable = new Types.Variable(name, variableFormatter.valueToString(value, formatterOptions));
formattedVariable.indexedVariables = Math.max(indexedVariables, 0);
String detailsValue = null;
if (sizeValue != null) {
detailsValue = "size=" + variableFormatter.valueToString(sizeValue, formatterOptions);
} else if (DebugSettings.getCurrent().showToString) {
detailsValue = VariableDetailUtils.formatDetailsValue(value, frame.thread(), variableFormatter, formatterOptions, evaluationEngine);
}
if (detailsValue != null) {
formattedVariable.value = formattedVariable.value + " " + detailsValue;
}
result[i] = formattedVariable;
calculatedValues.put(inlineVariables[i], formattedVariable);
}
response.body = new Responses.InlineValuesResponse(result);
return CompletableFuture.completedFuture(response);
}
private static boolean isCapturedLocalVariable(String fieldName, String variableName) {
String capturedVariableName = "val$" + variableName;
return Objects.equals(fieldName, capturedVariableName)
|| (fieldName.startsWith(capturedVariableName + "$") && NumberUtils.isDigits(fieldName.substring(capturedVariableName.length() + 1)));
}
private static boolean isCapturedThisVariable(String fieldName) {
if (fieldName.startsWith("this$")) {
String suffix = fieldName.substring(5).replaceAll("\\$+$", "");
return NumberUtils.isDigits(suffix);
}
return false;
}
private static boolean isLambdaFrame(StackFrame frame) {
Method method = frame.location().method();
return method.isSynthetic() && method.name().startsWith("lambda$");
}
private void resolveValuesFromThisVariable(ObjectReference thisObj, InlineVariable[] unresolvedVariables, Variable[] result,
boolean isSyntheticLambdaFrame) {
if (thisObj == null) {
return;
}
int unresolved = 0;
for (Variable item : result) {
if (item == null) {
unresolved++;
}
}
try {
ReferenceType type = thisObj.referenceType();
String typeName = type.name();
ObjectReference enclosingInstance = null;
for (Field field : type.allFields()) {
String fieldName = field.name();
boolean isSyntheticField = field.isSynthetic();
Value fieldValue = null;
for (int i = 0; i < unresolvedVariables.length; i++) {
if (result[i] != null) {
continue;
}
InlineVariable inlineVariable = unresolvedVariables[i];
boolean isInlineFieldVariable = (inlineVariable.declaringClass != null);
boolean isMatch = false;
if (isSyntheticLambdaFrame) {
isMatch = !isInlineFieldVariable && Objects.equals(fieldName, inlineVariable.expression);
} else {
boolean isMatchedField = isInlineFieldVariable
&& Objects.equals(fieldName, inlineVariable.expression)
&& Objects.equals(typeName, inlineVariable.declaringClass);
boolean isMatchedCapturedVariable = !isInlineFieldVariable
&& isSyntheticField
&& isCapturedLocalVariable(fieldName, inlineVariable.expression);
isMatch = isMatchedField || isMatchedCapturedVariable;
if (!isMatch && isSyntheticField && enclosingInstance == null && isCapturedThisVariable(fieldName)) {
Value value = thisObj.getValue(field);
if (value instanceof ObjectReference) {
enclosingInstance = (ObjectReference) value;
break;
}
}
}
if (isMatch) {
fieldValue = fieldValue == null ? thisObj.getValue(field) : fieldValue;
result[i] = new Variable(inlineVariable.expression, fieldValue);
unresolved--;
}
}
if (unresolved <= 0) {
break;
}
}
if (unresolved > 0 && enclosingInstance != null) {
resolveValuesFromThisVariable(enclosingInstance, unresolvedVariables, result, isSyntheticLambdaFrame);
}
} catch (Exception ex) {
// do nothing
}
}
}

View File

@ -0,0 +1,398 @@
/*******************************************************************************
* Copyright (c) 2018-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:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/
package com.microsoft.java.debug.core.adapter.handler;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import com.microsoft.java.debug.core.Configuration;
import com.microsoft.java.debug.core.DebugException;
import com.microsoft.java.debug.core.DebugSettings;
import com.microsoft.java.debug.core.DebugUtility;
import com.microsoft.java.debug.core.IDebugSession;
import com.microsoft.java.debug.core.LaunchException;
import com.microsoft.java.debug.core.UsageDataSession;
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.LaunchMode;
import com.microsoft.java.debug.core.adapter.ProcessConsole;
import com.microsoft.java.debug.core.protocol.Events;
import com.microsoft.java.debug.core.protocol.Events.OutputEvent;
import com.microsoft.java.debug.core.protocol.Events.OutputEvent.Category;
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.CONSOLE;
import com.microsoft.java.debug.core.protocol.Requests.Command;
import com.microsoft.java.debug.core.protocol.Requests.LaunchArguments;
import com.microsoft.java.debug.core.protocol.Requests.ShortenApproach;
import com.microsoft.java.debug.core.protocol.Types;
import com.sun.jdi.connect.IllegalConnectorArgumentsException;
import com.sun.jdi.connect.VMStartException;
import com.sun.jdi.event.VMDisconnectEvent;
public class LaunchRequestHandler implements IDebugRequestHandler {
protected static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME);
protected static final long RUNINTERMINAL_TIMEOUT = 10 * 1000;
protected ILaunchDelegate activeLaunchHandler;
private CompletableFuture<Boolean> waitForDebuggeeConsole = new CompletableFuture<>();
@Override
public List<Command> getTargetCommands() {
return Arrays.asList(Command.LAUNCH);
}
@Override
public CompletableFuture<Response> handle(Command command, Arguments arguments, Response response, IDebugAdapterContext context) {
if (!context.isInitialized()) {
final String errorMessage = "'launch' request is rejected since the debug session has not been initialized yet.";
logger.log(Level.SEVERE, errorMessage);
return CompletableFuture.completedFuture(
AdapterUtils.setErrorResponse(response, ErrorCode.LAUNCH_FAILURE, errorMessage));
}
LaunchArguments launchArguments = (LaunchArguments) arguments;
Map<String, Object> traceInfo = new HashMap<>();
traceInfo.put("asyncJDWP", context.asyncJDWP());
traceInfo.put("noDebug", launchArguments.noDebug);
traceInfo.put("console", launchArguments.console);
UsageDataSession.recordInfo("launch debug info", traceInfo);
activeLaunchHandler = launchArguments.noDebug ? new LaunchWithoutDebuggingDelegate((daContext) -> handleTerminatedEvent(daContext))
: new LaunchWithDebuggingDelegate();
return handleLaunchCommand(arguments, response, context);
}
protected CompletableFuture<Response> handleLaunchCommand(Arguments arguments, Response response, IDebugAdapterContext context) {
LaunchArguments launchArguments = (LaunchArguments) arguments;
// validation
if (StringUtils.isBlank(launchArguments.mainClass)
|| ArrayUtils.isEmpty(launchArguments.modulePaths) && ArrayUtils.isEmpty(launchArguments.classPaths)) {
throw AdapterUtils.createCompletionException(
"Failed to launch debuggee VM. Missing mainClass or modulePaths/classPaths options in launch configuration.",
ErrorCode.ARGUMENT_MISSING);
}
if (StringUtils.isNotBlank(launchArguments.encoding)) {
if (!Charset.isSupported(launchArguments.encoding)) {
throw AdapterUtils.createCompletionException(
"Failed to launch debuggee VM. 'encoding' options in the launch configuration is not recognized.",
ErrorCode.INVALID_ENCODING);
}
context.setDebuggeeEncoding(Charset.forName(launchArguments.encoding));
if (StringUtils.isBlank(launchArguments.vmArgs)) {
launchArguments.vmArgs = String.format("-Dfile.encoding=%s", context.getDebuggeeEncoding().name());
} else {
// if vmArgs already has the file.encoding settings, duplicate options for jvm will not cause an error, the right most value wins
launchArguments.vmArgs = String.format("%s -Dfile.encoding=%s", launchArguments.vmArgs, context.getDebuggeeEncoding().name());
}
}
context.setLaunchMode(launchArguments.noDebug ? LaunchMode.NO_DEBUG : LaunchMode.DEBUG);
activeLaunchHandler.preLaunch(launchArguments, context);
// Use the specified cli style to launch the program.
if (launchArguments.shortenCommandLine == ShortenApproach.JARMANIFEST) {
if (ArrayUtils.isNotEmpty(launchArguments.classPaths)) {
try {
Path tempfile = LaunchUtils.generateClasspathJar(launchArguments.classPaths);
launchArguments.vmArgs += " -cp \"" + tempfile.toAbsolutePath().toString() + "\"";
launchArguments.classPaths = new String[0];
context.setClasspathJar(tempfile);
} catch (IllegalArgumentException | MalformedURLException ex) {
logger.log(Level.SEVERE, String.format("Failed to launch the program with jarmanifest style: %s", ex.toString(), ex));
throw AdapterUtils.createCompletionException("Failed to launch the program with jarmanifest style: " + ex.toString(),
ErrorCode.LAUNCH_FAILURE, ex);
} catch (IOException e) {
logger.log(Level.SEVERE, String.format("Failed to create a temp classpath.jar: %s", e.toString()), e);
}
}
} else if (launchArguments.shortenCommandLine == ShortenApproach.ARGFILE) {
try {
/**
* See the JDK spec https://docs.oracle.com/en/java/javase/18/docs/specs/man/java.html#java-command-line-argument-files.
* The argument file must contain only ASCII characters or characters in system default encoding that's ASCII friendly.
*/
Charset systemCharset = LaunchUtils.getSystemCharset();
CharsetEncoder encoder = systemCharset.newEncoder();
String vmArgsForShorten = null;
String[] classPathsForShorten = null;
String[] modulePathsForShorten = null;
if (StringUtils.isNotBlank(launchArguments.vmArgs)) {
if (!encoder.canEncode(launchArguments.vmArgs)) {
logger.warning(String.format("Cannot generate the 'vmArgs' argument into the argfile because it contains characters "
+ "that cannot be encoded in the system charset '%s'.", systemCharset.displayName()));
} else {
vmArgsForShorten = launchArguments.vmArgs;
}
}
if (ArrayUtils.isNotEmpty(launchArguments.classPaths)) {
if (!encoder.canEncode(String.join(File.pathSeparator, launchArguments.classPaths))) {
logger.warning(String.format("Cannot generate the '-cp' argument into the argfile because it contains characters "
+ "that cannot be encoded in the system charset '%s'.", systemCharset.displayName()));
} else {
classPathsForShorten = launchArguments.classPaths;
}
}
if (ArrayUtils.isNotEmpty(launchArguments.modulePaths)) {
if (!encoder.canEncode(String.join(File.pathSeparator, launchArguments.modulePaths))) {
logger.warning(String.format("Cannot generate the '--module-path' argument into the argfile because it contains characters "
+ "that cannot be encoded in the system charset '%s'.", systemCharset.displayName()));
} else {
modulePathsForShorten = launchArguments.modulePaths;
}
}
if (vmArgsForShorten != null || classPathsForShorten != null || modulePathsForShorten != null) {
Path tempfile = LaunchUtils.generateArgfile(vmArgsForShorten, classPathsForShorten, modulePathsForShorten, systemCharset);
launchArguments.vmArgs = (vmArgsForShorten == null ? launchArguments.vmArgs : "")
+ " \"@" + tempfile.toAbsolutePath().toString() + "\"";
launchArguments.classPaths = (classPathsForShorten == null ? launchArguments.classPaths : new String[0]);
launchArguments.modulePaths = (modulePathsForShorten == null ? launchArguments.modulePaths : new String[0]);
context.setArgsfile(tempfile);
}
} catch (IOException e) {
logger.log(Level.SEVERE, String.format("Failed to create a temp argfile: %s", e.toString()), e);
}
}
return launch(launchArguments, response, context).thenCompose(res -> {
long processId = context.getProcessId();
long shellProcessId = context.getShellProcessId();
if (context.getDebuggeeProcess() != null) {
processId = context.getDebuggeeProcess().pid();
}
// If processId or shellProcessId exist, send a notification to client.
if (processId > 0 || shellProcessId > 0) {
context.getProtocolServer().sendEvent(new Events.ProcessIdNotification(processId, shellProcessId));
}
LaunchUtils.releaseTempLaunchFile(context.getClasspathJar());
LaunchUtils.releaseTempLaunchFile(context.getArgsfile());
if (res.success) {
activeLaunchHandler.postLaunch(launchArguments, context);
}
IDebugSession debugSession = context.getDebugSession();
if (debugSession != null) {
debugSession.getEventHub().events()
.filter((debugEvent) -> debugEvent.event instanceof VMDisconnectEvent)
.subscribe((debugEvent) -> {
context.setVmTerminated();
// Terminate eventHub thread.
try {
debugSession.getEventHub().close();
} catch (Exception e) {
// do nothing.
}
handleTerminatedEvent(context);
});
}
return CompletableFuture.completedFuture(res);
});
}
protected void handleTerminatedEvent(IDebugAdapterContext context) {
CompletableFuture.runAsync(() -> {
try {
waitForDebuggeeConsole.get(5, TimeUnit.SECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
// do nothing.
}
context.getProtocolServer().sendEvent(new Events.TerminatedEvent());
});
}
/**
* Construct the Java command lines based on the given launch arguments.
* @param launchArguments - The launch arguments
* @param serverMode - whether to enable the debug port with server mode
* @param address - the debug port
* @return the command arrays
*/
public static String[] constructLaunchCommands(LaunchArguments launchArguments, boolean serverMode, String address) {
List<String> launchCmds = new ArrayList<>();
if (launchArguments.launcherScript != null) {
launchCmds.add(launchArguments.launcherScript);
}
if (StringUtils.isNotBlank(launchArguments.javaExec)) {
launchCmds.add(launchArguments.javaExec);
} else {
final String javaHome = StringUtils.isNotEmpty(DebugSettings.getCurrent().javaHome) ? DebugSettings.getCurrent().javaHome
: System.getProperty("java.home");
launchCmds.add(Paths.get(javaHome, "bin", "java").toString());
}
if (StringUtils.isNotEmpty(address)) {
launchCmds.add(String.format("-agentlib:jdwp=transport=dt_socket,server=%s,suspend=y,address=%s", serverMode ? "y" : "n", address));
}
if (StringUtils.isNotBlank(launchArguments.vmArgs)) {
launchCmds.addAll(DebugUtility.parseArguments(launchArguments.vmArgs));
}
if (ArrayUtils.isNotEmpty(launchArguments.modulePaths)) {
launchCmds.add("--module-path");
launchCmds.add(String.join(File.pathSeparator, launchArguments.modulePaths));
}
if (ArrayUtils.isNotEmpty(launchArguments.classPaths)) {
launchCmds.add("-cp");
launchCmds.add(String.join(File.pathSeparator, launchArguments.classPaths));
}
// For java 9 project, should specify "-m $MainClass".
String[] mainClasses = launchArguments.mainClass.split("/");
if (mainClasses.length == 2) {
launchCmds.add("-m");
}
launchCmds.add(launchArguments.mainClass);
if (StringUtils.isNotBlank(launchArguments.args)) {
launchCmds.addAll(DebugUtility.parseArguments(launchArguments.args));
}
return launchCmds.toArray(new String[0]);
}
protected CompletableFuture<Response> launch(LaunchArguments launchArguments, Response response, IDebugAdapterContext context) {
logger.info("Trying to launch Java Program with options:\n" + String.format("main-class: %s\n", launchArguments.mainClass)
+ String.format("args: %s\n", launchArguments.args)
+ String.format("module-path: %s\n", StringUtils.join(launchArguments.modulePaths, File.pathSeparator))
+ String.format("class-path: %s\n", StringUtils.join(launchArguments.classPaths, File.pathSeparator))
+ String.format("vmArgs: %s", launchArguments.vmArgs));
if (context.supportsRunInTerminalRequest()
&& (launchArguments.console == CONSOLE.integratedTerminal || launchArguments.console == CONSOLE.externalTerminal)) {
waitForDebuggeeConsole.complete(true);
return activeLaunchHandler.launchInTerminal(launchArguments, response, context);
}
CompletableFuture<Response> resultFuture = new CompletableFuture<>();
try {
Process debuggeeProcess = activeLaunchHandler.launch(launchArguments, context);
context.setDebuggeeProcess(debuggeeProcess);
ProcessConsole debuggeeConsole = new ProcessConsole(debuggeeProcess, "Debuggee", context.getDebuggeeEncoding());
debuggeeConsole.lineMessages()
.map((message) -> convertToOutputEvent(message.output, message.category, context))
.doFinally(() -> waitForDebuggeeConsole.complete(true))
.subscribe((event) -> context.getProtocolServer().sendEvent(event));
debuggeeConsole.start();
resultFuture.complete(response);
} catch (LaunchException e) {
if (StringUtils.isNotBlank(e.getStdout())) {
OutputEvent event = convertToOutputEvent(e.getStdout(), Category.stdout, context);
context.getProtocolServer().sendEvent(event);
}
if (StringUtils.isNotBlank(e.getStderr())) {
OutputEvent event = convertToOutputEvent(e.getStderr(), Category.stderr, context);
context.getProtocolServer().sendEvent(event);
}
resultFuture.completeExceptionally(
new DebugException(
String.format("Failed to launch debuggee VM. Reason: %s", e.getMessage()),
ErrorCode.LAUNCH_FAILURE.getId()
)
);
} catch (IOException | IllegalConnectorArgumentsException | VMStartException e) {
resultFuture.completeExceptionally(
new DebugException(
String.format("Failed to launch debuggee VM. Reason: %s", e.toString()),
ErrorCode.LAUNCH_FAILURE.getId()
)
);
}
return resultFuture;
}
private static final Pattern STACKTRACE_PATTERN = Pattern.compile("\\s+at\\s+([\\w$\\.]+\\/)?(([\\w$]+\\.)+[<\\w$>]+)\\(([\\w-$]+\\.java:\\d+)\\)");
private static OutputEvent convertToOutputEvent(String message, Category category, IDebugAdapterContext context) {
Matcher matcher = STACKTRACE_PATTERN.matcher(message);
if (matcher.find()) {
String methodField = matcher.group(2);
String locationField = matcher.group(matcher.groupCount());
String fullyQualifiedName = methodField.substring(0, methodField.lastIndexOf("."));
String packageName = fullyQualifiedName.lastIndexOf(".") > -1 ? fullyQualifiedName.substring(0, fullyQualifiedName.lastIndexOf(".")) : "";
String[] locations = locationField.split(":");
String sourceName = locations[0];
int lineNumber = Integer.parseInt(locations[1]);
String sourcePath = StringUtils.isBlank(packageName) ? sourceName
: packageName.replace('.', File.separatorChar) + File.separatorChar + sourceName;
Types.Source source = null;
try {
source = StackTraceRequestHandler.convertDebuggerSourceToClient(fullyQualifiedName, sourceName, sourcePath, context);
} catch (URISyntaxException e) {
// do nothing.
}
return new OutputEvent(category, message, source, lineNumber);
}
return new OutputEvent(category, message);
}
protected static String[] constructEnvironmentVariables(LaunchArguments launchArguments) {
String[] envVars = null;
if (launchArguments.env != null && !launchArguments.env.isEmpty()) {
Map<String, String> environment = new HashMap<>(System.getenv());
List<String> duplicated = new ArrayList<>();
for (Entry<String, String> entry : launchArguments.env.entrySet()) {
if (environment.containsKey(entry.getKey())) {
duplicated.add(entry.getKey());
}
environment.put(entry.getKey(), entry.getValue());
}
// For duplicated variables, show a warning message.
if (!duplicated.isEmpty()) {
logger.warning(String.format("There are duplicated environment variables. The values specified in launch.json will be used. "
+ "Here are the duplicated entries: %s.", String.join(",", duplicated)));
}
envVars = new String[environment.size()];
int i = 0;
for (Entry<String, String> entry : environment.entrySet()) {
envVars[i++] = entry.getKey() + "=" + entry.getValue();
}
}
return envVars;
}
public static String parseMainClassWithoutModuleName(String mainClass) {
int index = mainClass.indexOf('/');
return mainClass.substring(index + 1);
}
}

View File

@ -0,0 +1,389 @@
/*******************************************************************************
* Copyright (c) 2021-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:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/
package com.microsoft.java.debug.core.adapter.handler;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.jar.Attributes;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;
import com.microsoft.java.debug.core.Configuration;
import com.microsoft.java.debug.core.adapter.AdapterUtils;
public class LaunchUtils {
private static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME);
private static Set<Path> tempFilesInUse = new HashSet<>();
private static final Charset SYSTEM_CHARSET;
static {
Charset result = null;
try {
// JEP 400: Java 17+ populates this system property.
String encoding = System.getProperty("native.encoding"); //$NON-NLS-1$
if (encoding != null && !encoding.isBlank()) {
result = Charset.forName(encoding);
} else {
// JVM internal property, works on older JVM's too
encoding = System.getProperty("sun.jnu.encoding"); //$NON-NLS-1$
if (encoding != null && !encoding.isBlank()) {
result = Charset.forName(encoding);
}
}
} catch (Exception e) {
logger.log(Level.SEVERE, "Error occurs during resolving system encoding", e);
}
if (result == null) {
// This is always UTF-8 on Java >= 18.
result = Charset.defaultCharset();
}
SYSTEM_CHARSET = result;
}
public static Charset getSystemCharset() {
return SYSTEM_CHARSET;
}
/**
* Generate the classpath parameters to a temporary classpath.jar.
* @param classPaths - the classpath parameters
* @return the file path of the generate classpath.jar
* @throws IOException Some errors occur during generating the classpath.jar
*/
public static synchronized Path generateClasspathJar(String[] classPaths) throws IOException {
List<String> classpathUrls = new ArrayList<>();
for (String classpath : classPaths) {
classpathUrls.add(AdapterUtils.toUrl(classpath));
}
Manifest manifest = new Manifest();
Attributes attributes = manifest.getMainAttributes();
attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0");
// In jar manifest, the absolute path C:\a.jar should be converted to the url style file:///C:/a.jar
String classpathValue = String.join(" ", classpathUrls);
attributes.put(Attributes.Name.CLASS_PATH, classpathValue);
String baseName = "cp_" + getMd5(classpathValue);
cleanupTempFiles(baseName, ".jar");
Path tempfile = createTempFile(baseName, ".jar");
JarOutputStream jar = new JarOutputStream(new FileOutputStream(tempfile.toFile()), manifest);
jar.close();
lockTempLaunchFile(tempfile);
return tempfile;
}
/**
* Generate the classpath parameters to a temporary argfile file.
* @param classPaths - the classpath parameters
* @param modulePaths - the modulepath parameters
* @return the file path of the generated argfile
* @throws IOException Some errors occur during generating the argfile
*/
public static synchronized Path generateArgfile(String vmArgs, String[] classPaths, String[] modulePaths, Charset encoding) throws IOException {
String argfile = "";
if (StringUtils.isNotBlank(vmArgs)) {
argfile += vmArgs;
}
if (ArrayUtils.isNotEmpty(classPaths)) {
argfile += " -cp \"" + String.join(File.pathSeparator, classPaths) + "\"";
}
if (ArrayUtils.isNotEmpty(modulePaths)) {
argfile += " --module-path \"" + String.join(File.pathSeparator, modulePaths) + "\"";
}
argfile = argfile.replace("\\", "\\\\");
String baseName = "cp_" + getMd5(argfile);
cleanupTempFiles(baseName, ".argfile");
Path tempfile = createTempFile(baseName, ".argfile");
Files.writeString(tempfile, argfile, encoding);
lockTempLaunchFile(tempfile);
return tempfile;
}
public static void lockTempLaunchFile(Path tempFile) {
if (tempFile != null) {
tempFilesInUse.add(tempFile);
}
}
public static void releaseTempLaunchFile(Path tempFile) {
if (tempFile != null) {
tempFilesInUse.remove(tempFile);
}
}
public static ProcessHandle findJavaProcessInTerminalShell(long shellPid, String javaCommand, int timeout/*ms*/) {
ProcessHandle shellProcess = ProcessHandle.of(shellPid).orElse(null);
if (shellProcess != null) {
int retry = 0;
final int INTERVAL = 20/*ms*/;
final int maxRetries = timeout / INTERVAL;
final boolean isCygwinShell = isCygwinShell(shellProcess.info().command().orElse(null));
while (retry <= maxRetries) {
Optional<ProcessHandle> subProcessHandle = shellProcess.descendants().filter(proc -> {
String command = proc.info().command().orElse("");
return Objects.equals(command, javaCommand) || command.endsWith("\\java.exe") || command.endsWith("/java");
}).findFirst();
if (subProcessHandle.isPresent()) {
if (retry > 0) {
logger.info("Retried " + retry + " times to find Java subProcess.");
}
logger.info("shellPid: " + shellPid + ", javaPid: " + subProcessHandle.get().pid());
return subProcessHandle.get();
} else if (isCygwinShell) {
long javaPid = findJavaProcessByCygwinPsCommand(shellProcess, javaCommand);
if (javaPid > 0) {
if (retry > 0) {
logger.info("Retried " + retry + " times to find Java subProcess.");
}
logger.info("[Cygwin Shell] shellPid: " + shellPid + ", javaPid: " + javaPid);
return ProcessHandle.of(javaPid).orElse(null);
}
}
retry++;
if (retry > maxRetries) {
break;
}
try {
Thread.sleep(INTERVAL);
} catch (InterruptedException e) {
// do nothing
}
}
logger.info("Retried " + retry + " times but failed to find Java subProcess of shell pid " + shellPid);
}
return null;
}
private static long findJavaProcessByCygwinPsCommand(ProcessHandle shellProcess, String javaCommand) {
String psCommand = detectPsCommandPath(shellProcess.info().command().orElse(null));
if (psCommand == null) {
return -1;
}
BufferedReader psReader = null;
List<PsProcess> psProcs = new ArrayList<>();
List<PsProcess> javaCandidates = new ArrayList<>();
try {
String[] headers = null;
int pidIndex = -1;
int ppidIndex = -1;
int winpidIndex = -1;
String line;
String javaExeName = Paths.get(javaCommand).toFile().getName().replaceFirst("\\.exe$", "");
Process p = Runtime.getRuntime().exec(new String[] {psCommand, "-l"});
psReader = new BufferedReader(new InputStreamReader(p.getInputStream()));
/**
* Here is a sample output when running ps command in Cygwin/MINGW64 shell.
* PID PPID PGID WINPID TTY UID STIME COMMAND
* 1869 1 1869 7852 cons2 4096 15:29:27 /usr/bin/bash
* 2271 1 2271 30820 cons4 4096 19:38:30 /usr/bin/bash
* 1812 1 1812 21540 cons1 4096 15:05:03 /usr/bin/bash
* 2216 1 2216 11328 cons3 4096 19:38:18 /usr/bin/bash
* 1720 1 1720 5404 cons0 4096 13:46:42 /usr/bin/bash
* 2269 2216 2269 6676 cons3 4096 19:38:21 /c/Program Files/Microsoft/jdk-11.0.14.9-hotspot/bin/java
* 1911 1869 1869 29708 cons2 4096 15:29:31 /c/Program Files/nodejs/node
* 2315 2271 2315 18064 cons4 4096 19:38:34 /usr/bin/ps
*/
while ((line = psReader.readLine()) != null) {
String[] cols = line.strip().split("\\s+");
if (headers == null) {
headers = cols;
pidIndex = ArrayUtils.indexOf(headers, "PID");
ppidIndex = ArrayUtils.indexOf(headers, "PPID");
winpidIndex = ArrayUtils.indexOf(headers, "WINPID");
if (pidIndex < 0 || ppidIndex < 0 || winpidIndex < 0) {
logger.warning("Failed to find Java process because ps command is not the standard Cygwin ps command.");
return -1;
}
} else if (cols.length >= headers.length) {
long pid = Long.parseLong(cols[pidIndex]);
long ppid = Long.parseLong(cols[ppidIndex]);
long winpid = Long.parseLong(cols[winpidIndex]);
PsProcess process = new PsProcess(pid, ppid, winpid);
psProcs.add(process);
if (cols[cols.length - 1].endsWith("/" + javaExeName) || cols[cols.length - 1].endsWith("/java")) {
javaCandidates.add(process);
}
}
}
} catch (Exception err) {
logger.log(Level.WARNING, "Failed to find Java process by Cygwin ps command.", err);
} finally {
if (psReader != null) {
try {
psReader.close();
} catch (IOException e) {
// ignore
}
}
}
if (!javaCandidates.isEmpty()) {
Set<Long> descendantWinpids = shellProcess.descendants().map(proc -> proc.pid()).collect(Collectors.toSet());
long shellWinpid = shellProcess.pid();
for (PsProcess javaCandidate: javaCandidates) {
if (descendantWinpids.contains(javaCandidate.winpid)) {
return javaCandidate.winpid;
}
for (PsProcess psProc : psProcs) {
if (javaCandidate.ppid != psProc.pid) {
continue;
}
if (descendantWinpids.contains(psProc.winpid) || psProc.winpid == shellWinpid) {
return javaCandidate.winpid;
}
break;
}
}
}
return -1;
}
private static boolean isCygwinShell(String shellPath) {
if (!SystemUtils.IS_OS_WINDOWS || shellPath == null) {
return false;
}
String lowerShellPath = shellPath.toLowerCase();
return lowerShellPath.endsWith("git\\bin\\bash.exe")
|| lowerShellPath.endsWith("git\\usr\\bin\\bash.exe")
|| lowerShellPath.endsWith("mintty.exe")
|| lowerShellPath.endsWith("cygwin64\\bin\\bash.exe")
|| (lowerShellPath.endsWith("bash.exe") && detectPsCommandPath(shellPath) != null)
|| (lowerShellPath.endsWith("sh.exe") && detectPsCommandPath(shellPath) != null);
}
private static String detectPsCommandPath(String shellPath) {
if (shellPath == null) {
return null;
}
Path psPath = Paths.get(shellPath, "..\\ps.exe");
if (!Files.exists(psPath)) {
psPath = Paths.get(shellPath, "..\\..\\usr\\bin\\ps.exe");
if (!Files.exists(psPath)) {
psPath = null;
}
}
if (psPath == null) {
return null;
}
return psPath.normalize().toString();
}
private static Path tmpdir = null;
private static synchronized Path getTmpDir() throws IOException {
if (tmpdir == null) {
Path tmpfile = Files.createTempFile("", UUID.randomUUID().toString());
tmpdir = tmpfile.getParent();
try {
Files.deleteIfExists(tmpfile);
} catch (Exception ex) {
// do nothing
}
}
return tmpdir;
}
private static void cleanupTempFiles(String baseName, String suffix) throws IOException {
for (int i = 0; ; i++) {
Path tempFile = getTmpDir().resolve(baseName + (i == 0 ? "" : i) + suffix);
if (tempFilesInUse.contains(tempFile)) {
continue;
} else if (!Files.exists(tempFile)) {
break;
} else {
try {
// delete the old temp file
Files.deleteIfExists(tempFile);
} catch (Exception e) {
// do nothing
}
}
}
}
private static Path createTempFile(String baseName, String suffix) throws IOException {
// loop until the temp file can be created
for (int i = 0; ; i++) {
Path tempFile = getTmpDir().resolve(baseName + (i == 0 ? "" : i) + suffix);
if (!Files.exists(tempFile)) {
return Files.createFile(tempFile);
}
}
}
private static String getMd5(String input) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] messageDigest = md.digest(input.getBytes());
BigInteger md5 = new BigInteger(1, messageDigest);
return md5.toString(Character.MAX_RADIX);
} catch (NoSuchAlgorithmException e) {
return Integer.toString(input.hashCode(), Character.MAX_RADIX);
}
}
private static class PsProcess {
long pid;
long ppid;
long winpid;
public PsProcess(long pid, long ppid, long winpid) {
this.pid = pid;
this.ppid = ppid;
this.winpid = winpid;
}
}
}

View File

@ -0,0 +1,249 @@
/*******************************************************************************
* Copyright (c) 2017-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:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/
package com.microsoft.java.debug.core.adapter.handler;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.logging.Logger;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;
import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;
import com.microsoft.java.debug.core.Configuration;
import com.microsoft.java.debug.core.DebugException;
import com.microsoft.java.debug.core.DebugSession;
import com.microsoft.java.debug.core.DebugUtility;
import com.microsoft.java.debug.core.IDebugSession;
import com.microsoft.java.debug.core.adapter.Constants;
import com.microsoft.java.debug.core.adapter.ErrorCode;
import com.microsoft.java.debug.core.adapter.ICompletionsProvider;
import com.microsoft.java.debug.core.adapter.IDebugAdapterContext;
import com.microsoft.java.debug.core.adapter.IEvaluationProvider;
import com.microsoft.java.debug.core.adapter.IHotCodeReplaceProvider;
import com.microsoft.java.debug.core.adapter.ISourceLookUpProvider;
import com.microsoft.java.debug.core.adapter.IVirtualMachineManagerProvider;
import com.microsoft.java.debug.core.protocol.Events;
import com.microsoft.java.debug.core.protocol.JsonUtils;
import com.microsoft.java.debug.core.protocol.Messages.Request;
import com.microsoft.java.debug.core.protocol.Messages.Response;
import com.microsoft.java.debug.core.protocol.Requests.CONSOLE;
import com.microsoft.java.debug.core.protocol.Requests.Command;
import com.microsoft.java.debug.core.protocol.Requests.LaunchArguments;
import com.microsoft.java.debug.core.protocol.Requests.RunInTerminalRequestArguments;
import com.microsoft.java.debug.core.protocol.Responses.RunInTerminalResponseBody;
import com.sun.jdi.VirtualMachine;
import com.sun.jdi.connect.Connector;
import com.sun.jdi.connect.IllegalConnectorArgumentsException;
import com.sun.jdi.connect.ListeningConnector;
import com.sun.jdi.connect.TransportTimeoutException;
import com.sun.jdi.connect.VMStartException;
public class LaunchWithDebuggingDelegate implements ILaunchDelegate {
protected static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME);
private static final int ATTACH_TERMINAL_TIMEOUT = 20 * 1000;
protected static final long RUNINTERMINAL_TIMEOUT = 10 * 1000;
private VMHandler vmHandler = new VMHandler();
@Override
public CompletableFuture<Response> launchInTerminal(LaunchArguments launchArguments, Response response, IDebugAdapterContext context) {
CompletableFuture<Response> resultFuture = new CompletableFuture<>();
IVirtualMachineManagerProvider vmProvider = context.getProvider(IVirtualMachineManagerProvider.class);
vmHandler.setVmProvider(vmProvider);
final String launchInTerminalErrorFormat = "Failed to launch debuggee in terminal. Reason: %s";
try {
List<ListeningConnector> connectors = vmProvider.getVirtualMachineManager().listeningConnectors();
ListeningConnector listenConnector = connectors.get(0);
Map<String, Connector.Argument> args = listenConnector.defaultArguments();
((Connector.IntegerArgument) args.get("timeout")).setValue(ATTACH_TERMINAL_TIMEOUT);
String address = listenConnector.startListening(args);
final String[] names = launchArguments.mainClass.split("[/\\.]");
final String terminalName = "Debug: " + names[names.length - 1];
String[] cmds = LaunchRequestHandler.constructLaunchCommands(launchArguments, false, address);
RunInTerminalRequestArguments requestArgs = null;
if (launchArguments.console == CONSOLE.integratedTerminal) {
requestArgs = RunInTerminalRequestArguments.createIntegratedTerminal(
cmds,
launchArguments.cwd,
launchArguments.env,
terminalName);
} else {
requestArgs = RunInTerminalRequestArguments.createExternalTerminal(
cmds,
launchArguments.cwd,
launchArguments.env,
terminalName);
}
Request request = new Request(Command.RUNINTERMINAL.getName(),
(JsonObject) JsonUtils.toJsonTree(requestArgs, RunInTerminalRequestArguments.class));
// Notes: In windows (reference to https://support.microsoft.com/en-us/help/830473/command-prompt-cmd--exe-command-line-string-limitation),
// when launching the program in cmd.exe, if the command line length exceed the threshold value (8191 characters),
// it will be automatically truncated so that launching in terminal failed. Especially, for maven project, the class path contains
// the local .m2 repository path, it may exceed the limit.
context.getProtocolServer().sendRequest(request, RUNINTERMINAL_TIMEOUT)
.whenComplete((runResponse, ex) -> {
if (runResponse != null) {
if (runResponse.success) {
try {
try {
RunInTerminalResponseBody terminalResponse = JsonUtils.fromJson(
JsonUtils.toJson(runResponse.body), RunInTerminalResponseBody.class);
context.setProcessId(terminalResponse.processId);
context.setShellProcessId(terminalResponse.shellProcessId);
} catch (JsonSyntaxException e) {
logger.severe("Failed to resolve runInTerminal response: " + e.toString());
}
VirtualMachine vm = listenConnector.accept(args);
vmHandler.connectVirtualMachine(vm);
context.setDebugSession(new DebugSession(vm));
logger.info("Launching debuggee in terminal console succeeded.");
if (context.getShellProcessId() > 0) {
ProcessHandle debuggeeProcess = LaunchUtils.findJavaProcessInTerminalShell(context.getShellProcessId(), cmds[0], 0);
if (debuggeeProcess != null) {
context.setProcessId(debuggeeProcess.pid());
}
}
resultFuture.complete(response);
} catch (TransportTimeoutException e) {
int commandLength = StringUtils.length(launchArguments.cwd) + 1;
for (String cmd : cmds) {
commandLength += StringUtils.length(cmd) + 1;
}
final int threshold = SystemUtils.IS_OS_WINDOWS ? 8092 : 32 * 1024;
String errorMessage = String.format(launchInTerminalErrorFormat, e.toString());
if (commandLength >= threshold) {
errorMessage = "Failed to launch debuggee in terminal. The possible reason is the command line too long. "
+ "More details: " + e.toString();
logger.severe(errorMessage
+ "\r\n"
+ "The estimated command line length is " + commandLength + ". "
+ "Try to enable shortenCommandLine option in the debug launch configuration.");
}
resultFuture.completeExceptionally(
new DebugException(
errorMessage,
ErrorCode.LAUNCH_IN_TERMINAL_FAILURE.getId()
)
);
} catch (IOException | IllegalConnectorArgumentsException e) {
resultFuture.completeExceptionally(
new DebugException(
String.format(launchInTerminalErrorFormat, e.toString()),
ErrorCode.LAUNCH_IN_TERMINAL_FAILURE.getId()
)
);
}
} else {
resultFuture.completeExceptionally(
new DebugException(
String.format(launchInTerminalErrorFormat, runResponse.message),
ErrorCode.LAUNCH_IN_TERMINAL_FAILURE.getId()
)
);
}
} else {
if (ex instanceof CompletionException && ex.getCause() != null) {
ex = ex.getCause();
}
String errorMessage = String.format(launchInTerminalErrorFormat, ex != null ? ex.toString() : "Null response");
resultFuture.completeExceptionally(
new DebugException(
String.format(launchInTerminalErrorFormat, errorMessage),
ErrorCode.LAUNCH_IN_TERMINAL_FAILURE.getId()
)
);
}
});
} catch (IOException | IllegalConnectorArgumentsException e) {
resultFuture.completeExceptionally(
new DebugException(
String.format(launchInTerminalErrorFormat, e.toString()),
ErrorCode.LAUNCH_IN_TERMINAL_FAILURE.getId()
)
);
}
return resultFuture;
}
@Override
public Process launch(LaunchArguments launchArguments, IDebugAdapterContext context)
throws IOException, IllegalConnectorArgumentsException, VMStartException {
IVirtualMachineManagerProvider vmProvider = context.getProvider(IVirtualMachineManagerProvider.class);
vmHandler.setVmProvider(vmProvider);
IDebugSession debugSession = DebugUtility.launch(
vmProvider.getVirtualMachineManager(),
launchArguments.mainClass,
launchArguments.args,
launchArguments.vmArgs,
Arrays.asList(launchArguments.modulePaths),
Arrays.asList(launchArguments.classPaths),
launchArguments.cwd,
LaunchRequestHandler.constructEnvironmentVariables(launchArguments),
launchArguments.javaExec);
context.setDebugSession(debugSession);
vmHandler.connectVirtualMachine(debugSession.getVM());
logger.info("Launching debuggee VM succeeded.");
return debugSession.process();
}
@Override
public void postLaunch(LaunchArguments launchArguments, IDebugAdapterContext context) {
Map<String, Object> options = new HashMap<>();
options.put(Constants.DEBUGGEE_ENCODING, context.getDebuggeeEncoding());
if (launchArguments.projectName != null) {
options.put(Constants.PROJECT_NAME, launchArguments.projectName);
}
if (launchArguments.mainClass != null) {
options.put(Constants.MAIN_CLASS, launchArguments.mainClass);
}
// TODO: Clean up the initialize mechanism
ISourceLookUpProvider sourceProvider = context.getProvider(ISourceLookUpProvider.class);
sourceProvider.initialize(context, options);
IEvaluationProvider evaluationProvider = context.getProvider(IEvaluationProvider.class);
evaluationProvider.initialize(context, options);
IHotCodeReplaceProvider hcrProvider = context.getProvider(IHotCodeReplaceProvider.class);
hcrProvider.initialize(context, options);
ICompletionsProvider completionsProvider = context.getProvider(ICompletionsProvider.class);
completionsProvider.initialize(context, options);
// send an InitializedEvent to indicate that the debugger is ready to accept
// configuration requests (e.g. SetBreakpointsRequest, SetExceptionBreakpointsRequest).
context.getProtocolServer().sendEvent(new Events.InitializedEvent());
}
@Override
public void preLaunch(LaunchArguments launchArguments, IDebugAdapterContext context) {
// debug only
context.setAttached(false);
context.setSourcePaths(launchArguments.sourcePaths);
context.setVmStopOnEntry(launchArguments.stopOnEntry);
context.setMainClass(LaunchRequestHandler.parseMainClassWithoutModuleName(launchArguments.mainClass));
context.setStepFilters(launchArguments.stepFilters);
}
}

View File

@ -0,0 +1,168 @@
/*******************************************************************************
* Copyright (c) 2018-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:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/
package com.microsoft.java.debug.core.adapter.handler;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.function.Consumer;
import java.util.logging.Logger;
import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;
import com.microsoft.java.debug.core.Configuration;
import com.microsoft.java.debug.core.DebugException;
import com.microsoft.java.debug.core.adapter.ErrorCode;
import com.microsoft.java.debug.core.adapter.IDebugAdapterContext;
import com.microsoft.java.debug.core.protocol.Events;
import com.microsoft.java.debug.core.protocol.JsonUtils;
import com.microsoft.java.debug.core.protocol.Messages.Request;
import com.microsoft.java.debug.core.protocol.Messages.Response;
import com.microsoft.java.debug.core.protocol.Requests.CONSOLE;
import com.microsoft.java.debug.core.protocol.Requests.Command;
import com.microsoft.java.debug.core.protocol.Requests.LaunchArguments;
import com.microsoft.java.debug.core.protocol.Requests.RunInTerminalRequestArguments;
import com.microsoft.java.debug.core.protocol.Responses.RunInTerminalResponseBody;
import com.sun.jdi.connect.IllegalConnectorArgumentsException;
import com.sun.jdi.connect.VMStartException;
public class LaunchWithoutDebuggingDelegate implements ILaunchDelegate {
protected static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME);
protected static final long RUNINTERMINAL_TIMEOUT = 10 * 1000;
private Consumer<IDebugAdapterContext> terminateHandler;
public LaunchWithoutDebuggingDelegate(Consumer<IDebugAdapterContext> terminateHandler) {
this.terminateHandler = terminateHandler;
}
@Override
public Process launch(LaunchArguments launchArguments, IDebugAdapterContext context)
throws IOException, IllegalConnectorArgumentsException, VMStartException {
String[] cmds = LaunchRequestHandler.constructLaunchCommands(launchArguments, false, null);
File workingDir = null;
if (launchArguments.cwd != null && Files.isDirectory(Paths.get(launchArguments.cwd))) {
workingDir = new File(launchArguments.cwd);
}
Process debuggeeProcess = Runtime.getRuntime().exec(cmds, LaunchRequestHandler.constructEnvironmentVariables(launchArguments),
workingDir);
new Thread() {
public void run() {
try {
debuggeeProcess.waitFor();
} catch (InterruptedException ignore) {
logger.warning(String.format("Current thread is interrupted. Reason: %s", ignore.toString()));
debuggeeProcess.destroy();
} finally {
terminateHandler.accept(context);
}
}
}.start();
logger.info("Launching debuggee proccess succeeded.");
return debuggeeProcess;
}
@Override
public void postLaunch(LaunchArguments launchArguments, IDebugAdapterContext context) {
// For NO_DEBUG launch mode, the debugger does not respond to requests like
// SetBreakpointsRequest,
// but the front end keeps sending them according to the Debug Adapter Protocol.
// To avoid receiving them, a workaround is not to send InitializedEvent back to
// the front end.
// See https://github.com/Microsoft/vscode/issues/55850#issuecomment-412819676
return;
}
@Override
public CompletableFuture<Response> launchInTerminal(LaunchArguments launchArguments, Response response,
IDebugAdapterContext context) {
CompletableFuture<Response> resultFuture = new CompletableFuture<>();
final String launchInTerminalErrorFormat = "Failed to launch debuggee in terminal. Reason: %s";
final String[] names = launchArguments.mainClass.split("[/\\.]");
final String terminalName = "Run: " + names[names.length - 1];
String[] cmds = LaunchRequestHandler.constructLaunchCommands(launchArguments, false, null);
RunInTerminalRequestArguments requestArgs = null;
if (launchArguments.console == CONSOLE.integratedTerminal) {
requestArgs = RunInTerminalRequestArguments.createIntegratedTerminal(cmds, launchArguments.cwd,
launchArguments.env, terminalName);
} else {
requestArgs = RunInTerminalRequestArguments.createExternalTerminal(cmds, launchArguments.cwd,
launchArguments.env, terminalName);
}
Request request = new Request(Command.RUNINTERMINAL.getName(),
(JsonObject) JsonUtils.toJsonTree(requestArgs, RunInTerminalRequestArguments.class));
// Notes: In windows (reference to
// https://support.microsoft.com/en-us/help/830473/command-prompt-cmd--exe-command-line-string-limitation),
// when launching the program in cmd.exe, if the command line length exceed the
// threshold value (8191 characters),
// it will be automatically truncated so that launching in terminal failed.
// Especially, for maven project, the class path contains
// the local .m2 repository path, it may exceed the limit.
context.getProtocolServer().sendRequest(request, RUNINTERMINAL_TIMEOUT).whenComplete((runResponse, ex) -> {
if (runResponse != null) {
if (runResponse.success) {
ProcessHandle debuggeeProcess = null;
try {
RunInTerminalResponseBody terminalResponse = JsonUtils.fromJson(
JsonUtils.toJson(runResponse.body), RunInTerminalResponseBody.class);
context.setProcessId(terminalResponse.processId);
context.setShellProcessId(terminalResponse.shellProcessId);
if (terminalResponse.processId > 0) {
debuggeeProcess = ProcessHandle.of(terminalResponse.processId).orElse(null);
} else if (terminalResponse.shellProcessId > 0) {
debuggeeProcess = LaunchUtils.findJavaProcessInTerminalShell(terminalResponse.shellProcessId, cmds[0], 3000);
}
if (debuggeeProcess != null) {
context.setProcessId(debuggeeProcess.pid());
debuggeeProcess.onExit().thenAcceptAsync(proc -> {
context.getProtocolServer().sendEvent(new Events.TerminatedEvent());
});
}
} catch (JsonSyntaxException e) {
logger.severe("Failed to resolve runInTerminal response: " + e.toString());
}
if (debuggeeProcess == null || !debuggeeProcess.isAlive()) {
context.getProtocolServer().sendEvent(new Events.TerminatedEvent());
}
resultFuture.complete(response);
} else {
resultFuture.completeExceptionally(
new DebugException(String.format(launchInTerminalErrorFormat, runResponse.message),
ErrorCode.LAUNCH_IN_TERMINAL_FAILURE.getId()));
}
} else {
if (ex instanceof CompletionException && ex.getCause() != null) {
ex = ex.getCause();
}
String errorMessage = String.format(launchInTerminalErrorFormat,
ex != null ? ex.toString() : "Null response");
resultFuture.completeExceptionally(
new DebugException(String.format(launchInTerminalErrorFormat, errorMessage),
ErrorCode.LAUNCH_IN_TERMINAL_FAILURE.getId()));
}
});
return resultFuture;
}
@Override
public void preLaunch(LaunchArguments launchArguments, IDebugAdapterContext context) {
context.setSourcePaths(launchArguments.sourcePaths);
}
}

View File

@ -0,0 +1,43 @@
/*******************************************************************************
* 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:
* 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 com.microsoft.java.debug.core.adapter.IDebugAdapterContext;
import com.microsoft.java.debug.core.adapter.IDebugRequestHandler;
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.Responses.ProcessIdResponseBody;
public class ProcessIdHandler implements IDebugRequestHandler {
@Override
public List<Command> getTargetCommands() {
return Arrays.asList(Command.PROCESSID);
}
@Override
public CompletableFuture<Response> handle(Command command, Arguments arguments, Response response,
IDebugAdapterContext context) {
long processId = context.getProcessId();
long shellProcessId = context.getShellProcessId();
if (context.getDebuggeeProcess() != null) {
processId = context.getDebuggeeProcess().pid();
}
response.body = new ProcessIdResponseBody(processId, shellProcessId);
return CompletableFuture.completedFuture(response);
}
}

View File

@ -0,0 +1,50 @@
/*******************************************************************************
* Copyright (c) 2021 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 com.microsoft.java.debug.core.DebugSettings;
import com.microsoft.java.debug.core.adapter.IDebugAdapterContext;
import com.microsoft.java.debug.core.adapter.IDebugRequestHandler;
import com.microsoft.java.debug.core.protocol.Events.InvalidatedAreas;
import com.microsoft.java.debug.core.protocol.Events.InvalidatedEvent;
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.RefreshVariablesArguments;
public class RefreshVariablesHandler implements IDebugRequestHandler {
@Override
public List<Command> getTargetCommands() {
return Arrays.asList(Command.REFRESHVARIABLES);
}
@Override
public CompletableFuture<Response> handle(Command command, Arguments arguments, Response response,
IDebugAdapterContext context) {
RefreshVariablesArguments refreshArgs = (RefreshVariablesArguments) arguments;
if (refreshArgs != null) {
DebugSettings.getCurrent().showHex = refreshArgs.showHex;
DebugSettings.getCurrent().showQualifiedNames = refreshArgs.showQualifiedNames;
DebugSettings.getCurrent().showStaticVariables = refreshArgs.showStaticVariables;
DebugSettings.getCurrent().showLogicalStructure = refreshArgs.showLogicalStructure;
DebugSettings.getCurrent().showToString = refreshArgs.showToString;
}
context.getProtocolServer().sendEvent(new InvalidatedEvent(InvalidatedAreas.VARIABLES));
return CompletableFuture.completedFuture(response);
}
}

View File

@ -0,0 +1,129 @@
/*******************************************************************************
* Copyright (c) 2017 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 com.microsoft.java.debug.core.DebugException;
import com.microsoft.java.debug.core.DebugUtility;
import com.microsoft.java.debug.core.StackFrameUtility;
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.variables.StackFrameReference;
import com.microsoft.java.debug.core.protocol.Events;
import com.microsoft.java.debug.core.protocol.Events.UserNotificationEvent.NotificationType;
import com.microsoft.java.debug.core.protocol.Messages.Response;
import com.microsoft.java.debug.core.protocol.Requests;
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.RestartFrameArguments;
import com.sun.jdi.IncompatibleThreadStateException;
import com.sun.jdi.StackFrame;
import com.sun.jdi.ThreadReference;
import com.sun.jdi.request.StepRequest;
/**
* Support Eclipse's `Drop To Frame` action, which is restartFrame in VSCode's
* debug.
*/
public class RestartFrameHandler implements IDebugRequestHandler {
@Override
public List<Command> getTargetCommands() {
return Arrays.asList(Command.RESTARTFRAME);
}
@Override
public CompletableFuture<Response> handle(Command command, Arguments arguments, Response response, IDebugAdapterContext context) {
RestartFrameArguments restartFrameArgs = (RestartFrameArguments) arguments;
StackFrameReference stackFrameReference = (StackFrameReference) context.getRecyclableIdPool().getObjectById(restartFrameArgs.frameId);
if (stackFrameReference == null) {
throw AdapterUtils.createCompletionException(
String.format("RestartFrame: cannot find the stack frame with frameID %s", restartFrameArgs.frameId),
ErrorCode.RESTARTFRAME_FAILURE);
}
if (canRestartFrame(context, stackFrameReference)) {
try {
ThreadReference reference = stackFrameReference.getThread();
popStackFrames(context, stackFrameReference);
stepInto(context, reference);
} catch (DebugException de) {
context.getProtocolServer().sendEvent(new Events.UserNotificationEvent(NotificationType.ERROR, de.getMessage()));
throw AdapterUtils.createCompletionException(
String.format("Failed to restart stack frame. Reason: %s", de.getMessage()),
ErrorCode.RESTARTFRAME_FAILURE,
de);
}
return CompletableFuture.completedFuture(response);
} else {
context.getProtocolServer().sendEvent(new Events.UserNotificationEvent(NotificationType.ERROR, "Current stack frame doesn't support restart."));
throw AdapterUtils.createCompletionException("Current stack frame doesn't support restart.", ErrorCode.RESTARTFRAME_FAILURE);
}
}
private boolean canRestartFrame(IDebugAdapterContext context, StackFrameReference frameReference) {
if (!context.getDebugSession().getVM().canPopFrames()) {
return false;
}
ThreadReference reference = frameReference.getThread();
int totalFrames;
try {
totalFrames = reference.frameCount();
} catch (IncompatibleThreadStateException e) {
return false;
}
// The frame cannot be the bottom one of the call stack:
if (totalFrames <= frameReference.getDepth() + 1) {
return false;
}
StackFrame[] frames = context.getStackFrameManager().reloadStackFrames(reference, 0, frameReference.getDepth() + 2);
if (frames.length == 0) {
return false;
}
// Cannot restart frame involved with native call stacks:
for (int i = 0; i <= frameReference.getDepth() + 1; i++) {
if (StackFrameUtility.isNative(frames[i])) {
return false;
}
}
return true;
}
private void popStackFrames(IDebugAdapterContext context, StackFrameReference stackFrameRef) throws DebugException {
StackFrame frame = context.getStackFrameManager().getStackFrame(stackFrameRef);
if (frame == null) {
return;
}
StackFrameUtility.pop(frame);
}
private void stepInto(IDebugAdapterContext context, ThreadReference thread) {
StepRequest request = DebugUtility.createStepIntoRequest(thread, context.getStepFilters().allowClasses, context.getStepFilters().skipClasses);
context.getDebugSession().getEventHub().stepEvents().filter(debugEvent -> request.equals(debugEvent.event.request())).take(1).subscribe(debugEvent -> {
debugEvent.shouldResume = false;
// Have to send two events to keep the UI sync with the step in operations:
context.getProtocolServer().sendEvent(new Events.ContinuedEvent(thread.uniqueID()));
context.getProtocolServer().sendEvent(new Events.StoppedEvent("restartframe", thread.uniqueID()));
});
request.enable();
thread.resume();
}
}

View File

@ -0,0 +1,55 @@
/*******************************************************************************
* Copyright (c) 2017 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.concurrent.CompletableFuture;
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.ScopesArguments;
import com.microsoft.java.debug.core.protocol.Responses;
import com.microsoft.java.debug.core.protocol.Types;
import com.sun.jdi.ThreadReference;
public class ScopesRequestHandler implements IDebugRequestHandler {
@Override
public List<Command> getTargetCommands() {
return Arrays.asList(Command.SCOPES);
}
@Override
public CompletableFuture<Response> handle(Command command, Arguments arguments, Response response, IDebugAdapterContext context) {
ScopesArguments scopesArgs = (ScopesArguments) arguments;
List<Types.Scope> scopes = new ArrayList<>();
StackFrameReference stackFrameReference = (StackFrameReference) context.getRecyclableIdPool().getObjectById(scopesArgs.frameId);
if (stackFrameReference == null) {
response.body = new Responses.ScopesResponseBody(scopes);
return CompletableFuture.completedFuture(response);
}
ThreadReference thread = stackFrameReference.getThread();
VariableProxy localScope = new VariableProxy(thread, "Local", stackFrameReference, null, null);
int localScopeId = context.getRecyclableIdPool().addObject(thread.uniqueID(), localScope);
scopes.add(new Types.Scope(localScope.getScope(), localScopeId, false));
response.body = new Responses.ScopesResponseBody(scopes);
return CompletableFuture.completedFuture(response);
}
}

View File

@ -0,0 +1,350 @@
/*******************************************************************************
* Copyright (c) 2017-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:
* 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.concurrent.CompletableFuture;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Stream;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import com.microsoft.java.debug.core.Configuration;
import com.microsoft.java.debug.core.DebugException;
import com.microsoft.java.debug.core.IBreakpoint;
import com.microsoft.java.debug.core.IDebugSession;
import com.microsoft.java.debug.core.IEvaluatableBreakpoint;
import com.microsoft.java.debug.core.JavaBreakpointLocation;
import com.microsoft.java.debug.core.adapter.AdapterUtils;
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;
import com.microsoft.java.debug.core.adapter.IDebugRequestHandler;
import com.microsoft.java.debug.core.adapter.IEvaluationProvider;
import com.microsoft.java.debug.core.adapter.IHotCodeReplaceProvider;
import com.microsoft.java.debug.core.adapter.ISourceLookUpProvider;
import com.microsoft.java.debug.core.protocol.Events;
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.SetBreakpointArguments;
import com.microsoft.java.debug.core.protocol.Responses;
import com.microsoft.java.debug.core.protocol.Types;
import com.sun.jdi.BooleanValue;
import com.sun.jdi.Field;
import com.sun.jdi.ObjectReference;
import com.sun.jdi.ReferenceType;
import com.sun.jdi.StringReference;
import com.sun.jdi.ThreadReference;
import com.sun.jdi.VMDisconnectedException;
import com.sun.jdi.Value;
import com.sun.jdi.event.BreakpointEvent;
import com.sun.jdi.event.Event;
import com.sun.jdi.event.StepEvent;
import com.sun.jdi.request.EventRequest;
public class SetBreakpointsRequestHandler implements IDebugRequestHandler {
private static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME);
private boolean registered = false;
@Override
public List<Command> getTargetCommands() {
return Arrays.asList(Command.SETBREAKPOINTS);
}
@Override
public void initialize(IDebugAdapterContext context) {
IDebugRequestHandler.super.initialize(context);
IHotCodeReplaceProvider provider = context.getProvider(IHotCodeReplaceProvider.class);
provider.getEventHub()
.filter(event -> event.getEventType() == EventType.END)
.subscribe(event -> {
try {
List<String> classNames = (List<String>) event.getData();
reinstallBreakpoints(context, classNames);
} catch (Exception e) {
logger.severe(e.toString());
}
});
}
@Override
public CompletableFuture<Response> 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;
registerBreakpointHandler(context);
}
SetBreakpointArguments bpArguments = (SetBreakpointArguments) arguments;
String sourcePath = normalizeSourcePath(bpArguments.source, context);
// When breakpoint source path is null or an invalid file path, send an ErrorResponse back.
if (StringUtils.isBlank(sourcePath)) {
throw AdapterUtils.createCompletionException(
String.format("Failed to setBreakpoint. Reason: '%s' is an invalid path.", bpArguments.source.path),
ErrorCode.SET_BREAKPOINT_FAILURE);
}
try {
List<Types.Breakpoint> res = new ArrayList<>();
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 = context.getBreakpointManager()
.setBreakpoints(AdapterUtils.decodeURIComponent(sourcePath), toAdds, bpArguments.sourceModified);
for (int i = 0; i < bpArguments.breakpoints.length; i++) {
added[i].setAsync(context.asyncJDWP());
// For newly added breakpoint, should install it to debuggee first.
if (toAdds[i] == added[i] && added[i].className() != null) {
added[i].install().thenAccept(bp -> {
Events.BreakpointEvent bpEvent = new Events.BreakpointEvent("changed", this.convertDebuggerBreakpointToClient(bp, context));
context.getProtocolServer().sendEvent(bpEvent);
});
} else if (added[i].className() != null) {
if (toAdds[i].getHitCount() != added[i].getHitCount()) {
// Update hitCount condition.
added[i].setHitCount(toAdds[i].getHitCount());
}
if (!StringUtils.equals(toAdds[i].getLogMessage(), added[i].getLogMessage())) {
added[i].setLogMessage(toAdds[i].getLogMessage());
}
if (!StringUtils.equals(toAdds[i].getCondition(), added[i].getCondition())) {
added[i].setCondition(toAdds[i].getCondition());
}
}
res.add(this.convertDebuggerBreakpointToClient(added[i], context));
}
response.body = new Responses.SetBreakpointsResponseBody(res);
return CompletableFuture.completedFuture(response);
} catch (DebugException e) {
throw AdapterUtils.createCompletionException(
String.format("Failed to setBreakpoint. Reason: '%s'", e.toString()),
ErrorCode.SET_BREAKPOINT_FAILURE);
}
}
public static String normalizeSourcePath(Types.Source source, IDebugAdapterContext context) {
String clientPath = source.path;
if (AdapterUtils.isWindows()) {
// VSCode may send drive letters with inconsistent casing which will mess up the key
// in the BreakpointManager. See https://github.com/Microsoft/vscode/issues/6268
// Normalize the drive letter casing. Note that drive letters
// are not localized so invariant is safe here.
String drivePrefix = FilenameUtils.getPrefix(clientPath);
if (drivePrefix != null && drivePrefix.length() >= 2
&& Character.isLowerCase(drivePrefix.charAt(0)) && drivePrefix.charAt(1) == ':') {
drivePrefix = drivePrefix.substring(0, 2); // d:\ is an illegal regex string, convert it to d:
clientPath = clientPath.replaceFirst(drivePrefix, drivePrefix.toUpperCase());
}
}
String sourcePath = clientPath;
if (source.sourceReference != 0 && context.getSourceUri(source.sourceReference) != null) {
sourcePath = context.getSourceUri(source.sourceReference);
} else if (StringUtils.isNotBlank(clientPath)) {
// See the bug https://github.com/Microsoft/vscode/issues/30996
// Source.path in the SetBreakpointArguments could be a file system path or uri.
sourcePath = AdapterUtils.convertPath(clientPath, AdapterUtils.isUri(clientPath), context.isDebuggerPathsAreUri());
}
return sourcePath;
}
private IBreakpoint getAssociatedEvaluatableBreakpoint(IDebugAdapterContext context, BreakpointEvent event) {
return Arrays.asList(context.getBreakpointManager().getBreakpoints()).stream().filter(
bp -> {
return bp instanceof IEvaluatableBreakpoint
&& ((IEvaluatableBreakpoint) bp).containsEvaluatableExpression()
&& bp.requests().contains(event.request());
}
).findFirst().orElse(null);
}
private void registerBreakpointHandler(IDebugAdapterContext context) {
IDebugSession debugSession = context.getDebugSession();
if (debugSession != null) {
debugSession.getEventHub().events().filter(debugEvent -> debugEvent.event instanceof BreakpointEvent).subscribe(debugEvent -> {
Event event = debugEvent.event;
if (debugEvent.eventSet.size() > 1 && debugEvent.eventSet.stream().anyMatch(t -> t instanceof StepEvent)) {
// The StepEvent and BreakpointEvent are grouped in the same event set only if they occurs at the same location and in the same thread.
// In order to avoid two duplicated StoppedEvents, the debugger will skip the BreakpointEvent.
} else {
ThreadReference bpThread = ((BreakpointEvent) event).thread();
IEvaluationProvider engine = context.getProvider(IEvaluationProvider.class);
if (engine.isInEvaluation(bpThread)) {
return;
}
// find the breakpoint related to this breakpoint event
IBreakpoint expressionBP = getAssociatedEvaluatableBreakpoint(context, (BreakpointEvent) event);
String breakpointName = computeBreakpointName(event.request());
if (expressionBP != null) {
CompletableFuture.runAsync(() -> {
engine.evaluateForBreakpoint((IEvaluatableBreakpoint) expressionBP, bpThread).whenComplete((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.getThreadCache().addEventThread(bpThread);
context.getProtocolServer().sendEvent(new Events.StoppedEvent(
breakpointName, bpThread.uniqueID()));
}
});
});
} else {
context.getThreadCache().addEventThread(bpThread);
context.getProtocolServer().sendEvent(new Events.StoppedEvent(
breakpointName, bpThread.uniqueID()));
}
debugEvent.shouldResume = false;
}
});
}
}
private String computeBreakpointName(EventRequest request) {
switch ((int) request.getProperty(IBreakpoint.REQUEST_TYPE)) {
case IBreakpoint.REQUEST_TYPE_LAMBDA:
return "lambda breakpoint";
case IBreakpoint.REQUEST_TYPE_METHOD:
return "function breakpoint";
default:
return "breakpoint";
}
}
/**
* 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);
context.getProtocolServer().sendEvent(new Events.UserNotificationEvent(
Events.UserNotificationEvent.NotificationType.ERROR,
String.format("[Logpoint] Log message '%s' error: %s", breakpoint.getLogMessage(), ex.getMessage())));
} else if (value != null) {
if (value instanceof StringReference) {
String message = ((StringReference) value).value();
context.getProtocolServer().sendEvent(Events.OutputEvent.createConsoleOutput(
message + System.lineSeparator()));
}
}
return true;
} else {
boolean resume = false;
boolean resultNotBoolean = false;
if (value != null && ex == null) {
if (value instanceof BooleanValue) {
resume = !((BooleanValue) value).booleanValue();
} else if (value instanceof ObjectReference
&& ((ObjectReference) value).type().name().equals("java.lang.Boolean")) {
// get boolean value from java.lang.Boolean object
Field field = ((ReferenceType) ((ObjectReference) value).type()).fieldByName("value");
resume = !((BooleanValue) ((ObjectReference) value).getValue(field)).booleanValue();
} else {
resultNotBoolean = true;
}
}
if (resume) {
return true;
} else {
if (context.isVmTerminated()) {
// do nothing
} else if (ex != null) {
if (!(ex instanceof VMDisconnectedException || ex.getCause() instanceof VMDisconnectedException)) {
logger.log(Level.SEVERE, String.format("[ConditionalBreakpoint]: %s", ex.getMessage() != null ? ex.getMessage() : ex.toString()), ex);
context.getProtocolServer().sendEvent(new Events.UserNotificationEvent(
Events.UserNotificationEvent.NotificationType.ERROR,
String.format("Breakpoint condition '%s' error: %s", breakpoint.getCondition(), ex.getMessage())));
}
} else if (value == null || resultNotBoolean) {
context.getProtocolServer().sendEvent(new Events.UserNotificationEvent(
Events.UserNotificationEvent.NotificationType.WARNING,
String.format("Result of breakpoint condition '%s' is not a boolean, please correct your expression.", breakpoint.getCondition())));
}
return false;
}
}
}
private Types.Breakpoint convertDebuggerBreakpointToClient(IBreakpoint breakpoint, IDebugAdapterContext context) {
int id = (int) breakpoint.getProperty("id");
boolean verified = breakpoint.getProperty("verified") != null && (boolean) breakpoint.getProperty("verified");
int lineNumber = AdapterUtils.convertLineNumber(breakpoint.getLineNumber(), context.isDebuggerLinesStartAt1(), context.isClientLinesStartAt1());
return new Types.Breakpoint(id, verified, lineNumber, "");
}
private IBreakpoint[] convertClientBreakpointsToDebugger(String sourceFile, Types.SourceBreakpoint[] sourceBreakpoints, IDebugAdapterContext context)
throws DebugException {
Types.SourceBreakpoint[] debugSourceBreakpoints = Stream.of(sourceBreakpoints).map(sourceBreakpoint -> {
int line = AdapterUtils.convertLineNumber(sourceBreakpoint.line, context.isClientLinesStartAt1(), context.isDebuggerLinesStartAt1());
int column = AdapterUtils.convertColumnNumber(sourceBreakpoint.column, context.isClientColumnsStartAt1(), context.isDebuggerColumnsStartAt1());
return new Types.SourceBreakpoint(line, column);
}).toArray(Types.SourceBreakpoint[]::new);
ISourceLookUpProvider sourceProvider = context.getProvider(ISourceLookUpProvider.class);
JavaBreakpointLocation[] locations = sourceProvider.getBreakpointLocations(sourceFile, debugSourceBreakpoints);
IBreakpoint[] breakpoints = new IBreakpoint[locations.length];
for (int i = 0; i < locations.length; i++) {
int hitCount = 0;
try {
hitCount = Integer.parseInt(sourceBreakpoints[i].hitCondition);
} catch (NumberFormatException e) {
hitCount = 0; // If hitCount is an illegal number, ignore hitCount condition.
}
breakpoints[i] = context.getDebugSession().createBreakpoint(locations[i], hitCount, sourceBreakpoints[i].condition,
sourceBreakpoints[i].logMessage);
if (sourceProvider.supportsRealtimeBreakpointVerification() && StringUtils.isNotBlank(locations[i].className())) {
breakpoints[i].putProperty("verified", true);
}
}
return breakpoints;
}
private void reinstallBreakpoints(IDebugAdapterContext context, List<String> typenames) {
if (typenames == null || typenames.isEmpty()) {
return;
}
IBreakpoint[] breakpoints = context.getBreakpointManager().getBreakpoints();
for (IBreakpoint breakpoint : breakpoints) {
if (typenames.contains(breakpoint.className())) {
try {
breakpoint.close();
breakpoint.install().thenAccept(bp -> {
Events.BreakpointEvent bpEvent = new Events.BreakpointEvent("new", this.convertDebuggerBreakpointToClient(bp, context));
context.getProtocolServer().sendEvent(bpEvent);
});
} catch (Exception e) {
logger.log(Level.SEVERE, String.format("Remove breakpoint exception: %s", e.toString()), e);
}
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More