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();
+ }
+}
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IBreakpointManager.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IBreakpointManager.java
new file mode 100644
index 000000000..196714d52
--- /dev/null
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IBreakpointManager.java
@@ -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.
+ *
+ * If the source file is modified, delete all cached breakpoints associated the file first and re-register the new breakpoints.
+ *
+ * @param source
+ * source path of breakpoints
+ * @param breakpoints
+ * full list of breakpoints that locates in this source file
+ * @param sourceModified
+ * the source file is modified or not.
+ * @return the full breakpoint list that locates in the source file
+ */
+ IBreakpoint[] setBreakpoints(String source, IBreakpoint[] breakpoints, boolean sourceModified);
+
+ /**
+ * Update the watchpoint list. If the requested watchpoint already registered in the breakpoint manager,
+ * reuse the cached one. Otherwise register the requested watchpoint as a new watchpoint.
+ * Besides, delete those not existed any more.
+ *
+ * @param watchpoints
+ * the watchpoints requested by client
+ * @return the full registered watchpoints list
+ */
+ IWatchpoint[] setWatchpoints(IWatchpoint[] watchpoints);
+
+ /**
+ * Returns all registered breakpoints.
+ */
+ IBreakpoint[] getBreakpoints();
+
+ /**
+ * Returns the registered breakpoints at the source file.
+ */
+ IBreakpoint[] getBreakpoints(String source);
+
+ /**
+ * Returns all registered watchpoints.
+ */
+ IWatchpoint[] getWatchpoints();
+
+ /**
+ * Returns all the registered method breakpoints.
+ */
+ IMethodBreakpoint[] getMethodBreakpoints();
+
+ /**
+ * Update the method breakpoints list. If the requested method breakpoints
+ * already registered in the breakpoint
+ * manager, reuse the cached one. Otherwise register the requested method
+ * breakpoints as a new method breakpoints.
+ * Besides, delete those not existed any more.
+ *
+ * @param methodBreakpoints
+ * the method breakpoints requested by client
+ * @return the full registered method breakpoints list
+ */
+ IMethodBreakpoint[] setMethodBreakpoints(IMethodBreakpoint[] methodBreakpoints);
+
+}
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IDebugAdapterContext.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IDebugAdapterContext.java
index e1afad81e..e65270eef 100644
--- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IDebugAdapterContext.java
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IDebugAdapterContext.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2017 Microsoft Corporation and others.
+ * 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
@@ -12,6 +12,7 @@
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;
@@ -50,6 +51,12 @@ public interface IDebugAdapterContext {
void setClientLinesStartAt1(boolean clientLinesStartAt1);
+ boolean isClientColumnsStartAt1();
+
+ void setClientColumnsStartAt1(boolean clientColumnsStartAt1);
+
+ boolean isDebuggerColumnsStartAt1();
+
boolean isClientPathsAreUri();
void setClientPathsAreUri(boolean clientPathsAreUri);
@@ -78,7 +85,7 @@ public interface IDebugAdapterContext {
void setVariableFormatter(IVariableFormatter variableFormatter);
- Map getSourceLookupCache();
+ Map getSourceLookupCache();
void setDebuggeeEncoding(Charset encoding);
@@ -101,4 +108,54 @@ public interface IDebugAdapterContext {
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);
}
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IDebugAdapterFactory.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IDebugAdapterFactory.java
new file mode 100644
index 000000000..d392d3b48
--- /dev/null
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IDebugAdapterFactory.java
@@ -0,0 +1,19 @@
+/*******************************************************************************
+ * Copyright (c) 2023 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.protocol.IProtocolServer;
+
+@FunctionalInterface
+public interface IDebugAdapterFactory {
+ public IDebugAdapter create(IProtocolServer server);
+}
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IEvaluationProvider.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IEvaluationProvider.java
index 22ed22b99..8cece2f58 100644
--- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IEvaluationProvider.java
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IEvaluationProvider.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2017 Microsoft Corporation and others.
+ * Copyright (c) 2017-2019 Microsoft Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -14,6 +14,7 @@
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;
@@ -31,16 +32,26 @@ public interface IEvaluationProvider extends IProvider {
boolean isInEvaluation(ThreadReference thread);
/**
- * Evaluate the expression at the given thread and stack frame depth, return the promise which is to be resolved/rejected when
+ * 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 jdi thread to the expression will be executed at
- * @param depth The depth of stackframe of the stopped thread
+ * @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 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 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.
@@ -51,6 +62,20 @@ public interface IEvaluationProvider extends IProvider {
*/
CompletableFuture 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 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.
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IExceptionManager.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IExceptionManager.java
new file mode 100644
index 000000000..eca1c80bb
--- /dev/null
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IExceptionManager.java
@@ -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();
+}
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IProvider.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IProvider.java
index 74b5e17d9..86c30929c 100644
--- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IProvider.java
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IProvider.java
@@ -24,4 +24,10 @@ public interface IProvider {
*/
default void initialize(IDebugAdapterContext debugContext, Map options) {
}
+
+ /**
+ * Close the provider and free all associated resources.
+ */
+ default void close() {
+ }
}
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ISourceLookUpProvider.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ISourceLookUpProvider.java
index 233d539e3..f33742852 100644
--- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ISourceLookUpProvider.java
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ISourceLookUpProvider.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2017 Microsoft Corporation and others.
+ * 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
@@ -8,26 +8,137 @@
* 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 fully qualified class name and source file path, search the associated disk source file.
+ * 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 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.
+ * @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;
+
+ /**
+ * Deprecated, please use {@link #getSource(String, String)} instead.
*/
+ @Deprecated
String getSourceFileURI(String fullyQualifiedName, String sourcePath);
String getSourceContents(String uri);
+
+ /**
+ * Retrieves a {@link Source} object representing the source code associated with the given fully qualified class name and source file path.
+ * The implementation of this interface can determine a source is "local" or "remote".
+ * In case of "remote" a follow up "source" request will be issued by the client
+ *
+ * @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 A {@link Source} object encapsulating the source file URI obtained from
+ * {@link #getSourceFileURI(String, String)} and the source type as {@link SourceType#LOCAL}.
+ */
+ default Source getSource(String fullyQualifiedName, String sourcePath) {
+ return new Source(getSourceFileURI(fullyQualifiedName, sourcePath), SourceType.LOCAL);
+ }
+
+ /**
+ * 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 findMethodInvocations(String uri, int line);
+
+ /**
+ * Return the line mappings from the original line to the decompiled line.
+ *
+ * @param uri The uri
+ * @return the line mappings from the original line to the decompiled line.
+ */
+ default int[] getOriginalLineMappings(String uri) {
+ return null;
+ }
+
+ /**
+ * Return the line mappings from the decompiled line to the original line.
+ *
+ * @param uri The uri
+ * @return the line mappings from the decompiled line to the original line.
+ */
+ default int[] getDecompiledLineMappings(String uri) {
+ return null;
+ }
+
+ 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;
+ }
+ }
}
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IStackFrameManager.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IStackFrameManager.java
index de10319f6..8d6c74486 100644
--- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IStackFrameManager.java
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IStackFrameManager.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2017 Microsoft Corporation and others.
+ * 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
@@ -31,4 +31,35 @@ public interface IStackFrameManager {
* @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();
}
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IStepResultManager.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IStepResultManager.java
new file mode 100644
index 000000000..c19c0ecd6
--- /dev/null
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IStepResultManager.java
@@ -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();
+}
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IVirtualMachineManager.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IVirtualMachineManager.java
new file mode 100644
index 000000000..095c070bf
--- /dev/null
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IVirtualMachineManager.java
@@ -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);
+}
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/LaunchMode.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/LaunchMode.java
new file mode 100644
index 000000000..0c3b4ee48
--- /dev/null
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/LaunchMode.java
@@ -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
+}
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ProcessConsole.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ProcessConsole.java
index de066bcaa..3d823df91 100644
--- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ProcessConsole.java
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ProcessConsole.java
@@ -1,5 +1,5 @@
/*******************************************************************************
-* Copyright (c) 2017 Microsoft Corporation and others.
+* Copyright (c) 2017-2019 Microsoft Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -17,18 +17,18 @@
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
+import java.util.stream.Stream;
-import io.reactivex.functions.Consumer;
+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 Process process;
- private String name;
- private Charset encoding;
- private PublishSubject stdoutSubject = PublishSubject.create();
- private PublishSubject stderrSubject = PublishSubject.create();
- private Thread stdoutThread = null;
- private Thread stderrThread = null;
+ private InputStreamObservable stdoutStream;
+ private InputStreamObservable stderrStream;
+ private Observable observable = null;
public ProcessConsole(Process process) {
this(process, "Process", StandardCharsets.UTF_8);
@@ -44,76 +44,126 @@ public ProcessConsole(Process process) {
* the process encoding format
*/
public ProcessConsole(Process process, String name, Charset encoding) {
- this.process = process;
- this.name = name;
- this.encoding = encoding;
+ this.stdoutStream = new InputStreamObservable(name + " Stdout Handler", process.getInputStream(), encoding);
+ this.stderrStream = new InputStreamObservable(name + " Stderr Handler", process.getErrorStream(), encoding);
+ Observable stdout = this.stdoutStream.messages().map((message) -> new ConsoleMessage(message, Category.stdout));
+ Observable stderr = this.stderrStream.messages().map((message) -> new ConsoleMessage(message, Category.stderr));
+ this.observable = Observable.mergeArrayDelayError(stdout, stderr).observeOn(Schedulers.newThread());
}
/**
- * Start two separate threads to monitor the messages from stdout and stderr streams of the target process.
+ * Start monitoring the stdout/stderr streams of the target process.
*/
public void start() {
- this.stdoutThread = new Thread(this.name + " Stdout Handler") {
- public void run() {
- monitor(process.getInputStream(), stdoutSubject);
- }
- };
- stdoutThread.setDaemon(true);
- stdoutThread.start();
-
- this.stderrThread = new Thread(this.name + " Stderr Handler") {
- public void run() {
- monitor(process.getErrorStream(), stderrSubject);
- }
- };
- stderrThread.setDaemon(true);
- stderrThread.start();
+ stdoutStream.start();
+ stderrStream.start();
}
/**
- * Stop the process console handlers.
+ * Stop monitoring the process console.
*/
public void stop() {
- if (this.stdoutThread != null) {
- this.stdoutThread.interrupt();
- this.stdoutThread = null;
- }
+ stdoutStream.stop();
+ stderrStream.stop();
+ }
- if (this.stderrThread != null) {
- this.stderrThread.interrupt();
- this.stderrThread = null;
- }
+ public Observable messages() {
+ return observable;
}
- public void onStdout(Consumer callback) {
- stdoutSubject.subscribe(callback);
+ public Observable stdoutMessages() {
+ return this.messages().filter((message) -> message.category == Category.stdout);
}
- public void onStderr(Consumer callback) {
- stderrSubject.subscribe(callback);
+ public Observable stderrMessages() {
+ return this.messages().filter((message) -> message.category == Category.stderr);
}
- private void monitor(InputStream input, PublishSubject subject) {
- BufferedReader reader = new BufferedReader(new InputStreamReader(input, encoding));
- final int BUFFERSIZE = 4096;
- char[] buffer = new char[BUFFERSIZE];
- while (true) {
- try {
- if (Thread.interrupted()) {
- subject.onComplete();
- return;
+ /**
+ * Split the stdio message to lines, and return them as a new Observable.
+ */
+ public Observable 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 rxSubject = PublishSubject.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);
}
- int read = reader.read(buffer, 0, BUFFERSIZE);
- if (read == -1) {
- subject.onComplete();
+ };
+ loopingThread.setDaemon(true);
+ loopingThread.start();
+ }
+
+ /**
+ * Stops the stream.
+ */
+ public void stop() {
+ if (loopingThread != null) {
+ loopingThread.interrupt();
+ loopingThread = null;
+ }
+ }
+
+ private void monitor(InputStream input, PublishSubject 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;
}
-
- subject.onNext(new String(buffer, 0, read));
- } catch (IOException e) {
- subject.onError(e);
- return;
}
}
+
+ public Observable 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;
+ }
}
}
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ProtocolServer.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ProtocolServer.java
index 7d5e81585..9e503e0af 100644
--- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ProtocolServer.java
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ProtocolServer.java
@@ -15,6 +15,7 @@
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;
@@ -22,6 +23,8 @@
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;
@@ -31,6 +34,10 @@ public class ProtocolServer extends AbstractProtocolServer {
private IDebugAdapter debugAdapter;
private UsageDataSession usageDataSession = new UsageDataSession();
+ private Object lock = new Object();
+ private boolean isDispatchingRequest = false;
+ private ConcurrentLinkedQueue eventQueue = new ConcurrentLinkedQueue<>();
+
/**
* Constructs a protocol server instance based on the given input stream and output stream.
* @param input
@@ -45,6 +52,20 @@ public ProtocolServer(InputStream input, OutputStream output, IProviderContext c
debugAdapter = new DebugAdapter(this, context);
}
+ /**
+ * 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 debugAdapterFactory
+ * factory to create debug adapter that implements DAP communication
+ */
+ public ProtocolServer(InputStream input, OutputStream output, IDebugAdapterFactory debugAdapterFactory) {
+ super(input, output);
+ debugAdapter = debugAdapterFactory.create(this);
+ }
+
/**
* A while-loop to parse input data and send output data constantly.
*/
@@ -74,40 +95,84 @@ public CompletableFuture sendRequest(Messages.Request 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);
- debugAdapter.dispatchRequest(request).thenCompose((response) -> {
- CompletableFuture future = new CompletableFuture<>();
- if (response != null) {
- sendResponse(response);
- future.complete(null);
- } else {
- logger.log(Level.SEVERE, "The request dispatcher should not return null response.");
- future.completeExceptionally(new DebugException("The request dispatcher should not return null response.",
- ErrorCode.UNKNOWN_FAILURE.getId()));
+ try {
+ synchronized (lock) {
+ this.isDispatchingRequest = true;
}
- return future;
- }).exceptionally((ex) -> {
- Messages.Response response = new Messages.Response(request.seq, request.command);
- if (ex instanceof CompletionException && ex.getCause() != null) {
- ex = ex.getCause();
+
+ debugAdapter.dispatchRequest(request).thenCompose((response) -> {
+ CompletableFuture 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;
}
- if (ex instanceof VMDisconnectedException) {
- // mark it success to avoid reporting error on VSCode.
- response.success = true;
- sendResponse(response);
- } else if (ex instanceof DebugException) {
- sendResponse(AdapterUtils.setErrorResponse(response,
- ErrorCode.parse(((DebugException) ex).getErrorCode()),
- ex.getMessage() != null ? ex.getMessage() : ex.toString()));
- } else {
- sendResponse(AdapterUtils.setErrorResponse(response,
- ErrorCode.UNKNOWN_FAILURE,
- ex.getMessage() != null ? ex.getMessage() : ex.toString()));
+ while (this.eventQueue.peek() != null) {
+ super.sendEvent(this.eventQueue.poll());
}
- return null;
- }).join();
+ }
}
}
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/Source.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/Source.java
new file mode 100644
index 000000000..d00b4cb4c
--- /dev/null
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/Source.java
@@ -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;
+
+public class Source {
+ public final String uri;
+ public final SourceType type;
+
+ public Source(String uri, SourceType type) {
+ this.uri = uri;
+ this.type = type;
+ }
+
+ public String getUri() {
+ return this.uri;
+ }
+
+ public SourceType getType() {
+ return this.type;
+ }
+}
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/SourceType.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/SourceType.java
new file mode 100644
index 000000000..724bf3bda
--- /dev/null
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/SourceType.java
@@ -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 enum SourceType {
+ REMOTE,
+ LOCAL
+}
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/StackFrameManager.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/StackFrameManager.java
index 9e1a86970..518cc7761 100644
--- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/StackFrameManager.java
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/StackFrameManager.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2017 Microsoft Corporation and others.
+ * 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
@@ -11,7 +11,6 @@
package com.microsoft.java.debug.core.adapter;
-import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@@ -21,10 +20,10 @@
import com.sun.jdi.ThreadReference;
public class StackFrameManager implements IStackFrameManager {
- private Map threadStackFrameMap = Collections.synchronizedMap(new HashMap<>());
+ private Map threadStackFrameMap = new HashMap<>();
@Override
- public StackFrame getStackFrame(StackFrameReference ref) {
+ public synchronized StackFrame getStackFrame(StackFrameReference ref) {
ThreadReference thread = ref.getThread();
int depth = ref.getDepth();
StackFrame[] frames = threadStackFrameMap.get(thread.uniqueID());
@@ -32,13 +31,58 @@ public StackFrame getStackFrame(StackFrameReference ref) {
}
@Override
- public StackFrame[] reloadStackFrames(ThreadReference thread) {
+ 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 {
- return thread.frames().toArray(new StackFrame[0]);
+ 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();
+ }
}
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/StepResultManager.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/StepResultManager.java
new file mode 100644
index 000000000..dd3937181
--- /dev/null
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/StepResultManager.java
@@ -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 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();
+ }
+}
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ThreadCache.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ThreadCache.java
new file mode 100644
index 000000000..57d39154e
--- /dev/null
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ThreadCache.java
@@ -0,0 +1,162 @@
+/*******************************************************************************
+* 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.HashMap;
+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 allThreads = new ArrayList<>();
+ private Map threadNameMap = new ConcurrentHashMap<>();
+ private Map deathThreads = Collections.synchronizedMap(new LinkedHashMap<>() {
+ @Override
+ protected boolean removeEldestEntry(java.util.Map.Entry eldest) {
+ return this.size() > 100;
+ }
+ });
+ private Map eventThreads = new ConcurrentHashMap<>();
+ private Map> decompiledClassesByThread = new HashMap<>();
+ private Map threadStoppedReasons = new HashMap<>();
+
+ public synchronized void resetThreads(List threads) {
+ allThreads.clear();
+ allThreads.addAll(threads);
+ }
+
+ public synchronized List 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 addEventThread(ThreadReference thread, String reason) {
+ eventThreads.put(thread.uniqueID(), thread);
+ if (reason != null) {
+ threadStoppedReasons.put(thread.uniqueID(), reason);
+ }
+ }
+
+ 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 visibleThreads(IDebugAdapterContext context) {
+ List visibleThreads = new ArrayList<>(context.getDebugSession().getAllThreads());
+ Set 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;
+ }
+
+ public Set getDecompiledClassesByThread(long threadId) {
+ return decompiledClassesByThread.get(threadId);
+ }
+
+ public void setDecompiledClassesByThread(long threadId, Set decompiledClasses) {
+ if (decompiledClasses == null || decompiledClasses.isEmpty()) {
+ decompiledClassesByThread.remove(threadId);
+ return;
+ }
+
+ decompiledClassesByThread.put(threadId, decompiledClasses);
+ }
+
+ public String getThreadStoppedReason(long threadId) {
+ return threadStoppedReasons.get(threadId);
+ }
+
+ public void setThreadStoppedReason(long threadId, String reason) {
+ if (reason == null) {
+ threadStoppedReasons.remove(threadId);
+ return;
+ }
+
+ threadStoppedReasons.put(threadId, reason);
+ }
+
+ public void clearThreadStoppedState(long threadId) {
+ threadStoppedReasons.remove(threadId);
+ decompiledClassesByThread.remove(threadId);
+ }
+
+ public void clearAllThreadStoppedState() {
+ threadStoppedReasons.clear();
+ decompiledClassesByThread.clear();
+ }
+}
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/formatter/ObjectFormatter.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/formatter/ObjectFormatter.java
index ee974f8fe..25a44d27c 100644
--- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/formatter/ObjectFormatter.java
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/formatter/ObjectFormatter.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2017 Microsoft Corporation and others.
+ * Copyright (c) 2017-2019 Microsoft Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -39,7 +39,7 @@ public ObjectFormatter(BiFunction, String> typeToStrin
@Override
public String toString(Object obj, Map options) {
- return String.format("%s %s", getPrefix((ObjectReference) obj, options),
+ return String.format("%s@%s", getPrefix((ObjectReference) obj, options),
getIdPostfix((ObjectReference) obj, options));
}
@@ -74,6 +74,6 @@ protected String getPrefix(ObjectReference value, Map options) {
}
protected static String getIdPostfix(ObjectReference obj, Map options) {
- return String.format("(id=%s)", NumericFormatter.formatNumber(obj.uniqueID(), options));
+ return NumericFormatter.formatNumber(obj.uniqueID(), options);
}
}
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/formatter/StringObjectFormatter.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/formatter/StringObjectFormatter.java
index d4c1e663d..299f7dd32 100644
--- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/formatter/StringObjectFormatter.java
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/formatter/StringObjectFormatter.java
@@ -1,5 +1,5 @@
/*******************************************************************************
-* Copyright (c) 2017 Microsoft Corporation and others.
+* Copyright (c) 2017-2019 Microsoft Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -19,7 +19,6 @@
import org.apache.commons.lang3.StringUtils;
-import com.sun.jdi.ObjectReference;
import com.sun.jdi.StringReference;
import com.sun.jdi.Type;
import com.sun.jdi.Value;
@@ -43,9 +42,8 @@ public Map getDefaultOptions() {
@Override
public String toString(Object value, Map options) {
int maxLength = getMaxStringLength(options);
- return String.format("\"%s\" %s",
- maxLength > 0 ? StringUtils.abbreviate(((StringReference) value).value(), maxLength) : ((StringReference) value).value(),
- getIdPostfix((ObjectReference) value, options));
+ return String.format("\"%s\"",
+ maxLength > 0 ? StringUtils.abbreviate(((StringReference) value).value(), maxLength) : ((StringReference) value).value());
}
@Override
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/AbstractDisconnectRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/AbstractDisconnectRequestHandler.java
new file mode 100644
index 000000000..fd5c68d6c
--- /dev/null
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/AbstractDisconnectRequestHandler.java
@@ -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 getTargetCommands() {
+ return Arrays.asList(Command.DISCONNECT);
+ }
+
+ @Override
+ public CompletableFuture 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();
+ }
+ }
+}
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/AttachRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/AttachRequestHandler.java
index 727dc24b2..5ab848c8a 100644
--- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/AttachRequestHandler.java
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/AttachRequestHandler.java
@@ -1,5 +1,5 @@
/*******************************************************************************
-* Copyright (c) 2017 Microsoft Corporation and others.
+* 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
@@ -23,6 +23,7 @@
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;
@@ -39,9 +40,13 @@
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 getTargetCommands() {
@@ -55,21 +60,43 @@ public CompletableFuture handle(Command command, Arguments arguments,
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));
- IVirtualMachineManagerProvider vmProvider = context.getProvider(IVirtualMachineManagerProvider.class);
+ Map 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 {
- logger.info(String.format("Trying to attach to remote debuggee VM %s:%d .", attachArguments.hostName, attachArguments.port));
- IDebugSession debugSession = DebugUtility.attach(vmProvider.getVirtualMachineManager(), attachArguments.hostName, attachArguments.port,
- attachArguments.timeout);
- context.setDebugSession(debugSession);
- logger.info("Attaching to debuggee VM succeeded.");
+ 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 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 = System.getProperty("java.version");
- if (!debuggerVersion.equals(debuggeeVersion)) {
+ 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"
@@ -77,26 +104,27 @@ public CompletableFuture handle(Command command, Arguments arguments,
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);
}
- } catch (IOException | IllegalConnectorArgumentsException e) {
- return AdapterUtils.createAsyncErrorResponse(response, ErrorCode.ATTACH_FAILURE,
- String.format("Failed to attach to remote debuggee VM. Reason: %s", e.toString()));
- }
- Map options = new HashMap<>();
- options.put(Constants.DEBUGGEE_ENCODING, context.getDebuggeeEncoding());
- if (attachArguments.projectName != null) {
- options.put(Constants.PROJECT_NAME, attachArguments.projectName);
+ 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);
}
- // 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).
@@ -104,4 +132,13 @@ public CompletableFuture