init
This commit is contained in:
parent
ea7e058591
commit
9cb11cbb57
|
|
@ -0,0 +1,13 @@
|
|||
.DS_Store
|
||||
.idea/
|
||||
*.iml
|
||||
target/
|
||||
2017*
|
||||
.project
|
||||
.classpath
|
||||
.settings/
|
||||
*.log
|
||||
bin/
|
||||
lib/
|
||||
src/main/java/test/
|
||||
.vscode/
|
||||
|
|
@ -0,0 +1 @@
|
|||
1
|
||||
|
|
@ -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>
|
||||
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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";
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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() {
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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<>();
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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.");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;";
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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
Loading…
Reference in New Issue