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() {
- return stepFilters;
+ if (stepFilters != null) {
+ return stepFilters;
+ } else if (DebugSettings.getCurrent().stepFilters != null) {
+ return DebugSettings.getCurrent().stepFilters;
+ }
+
+ return defaultFilters;
}
@Override
@@ -291,4 +331,91 @@ public Path getArgsfile() {
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;
+ }
}
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ErrorCode.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ErrorCode.java
index f39796909..6cfe523cf 100644
--- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ErrorCode.java
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ErrorCode.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
@@ -34,7 +34,9 @@ public enum ErrorCode {
COMPLETIONS_FAILURE(1017),
EXCEPTION_INFO_FAILURE(1018),
EVALUATION_COMPILE_ERROR(2001),
- EVALUATE_NOT_SUSPENDED_THREAD(2002);
+ EVALUATE_NOT_SUSPENDED_THREAD(2002),
+ HCR_FAILURE(3001),
+ INVALID_DAP_HEADER(3002);
private int id;
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 27837808b..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
@@ -55,6 +55,8 @@ public interface IDebugAdapterContext {
void setClientColumnsStartAt1(boolean clientColumnsStartAt1);
+ boolean isDebuggerColumnsStartAt1();
+
boolean isClientPathsAreUri();
void setClientPathsAreUri(boolean clientPathsAreUri);
@@ -83,7 +85,7 @@ public interface IDebugAdapterContext {
void setVariableFormatter(IVariableFormatter variableFormatter);
- Map getSourceLookupCache();
+ Map getSourceLookupCache();
void setDebuggeeEncoding(Charset encoding);
@@ -124,4 +126,36 @@ public interface IDebugAdapterContext {
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/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/ProcessConsole.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ProcessConsole.java
index 6b384c628..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
@@ -129,7 +129,7 @@ public void stop() {
}
private void monitor(InputStream input, PublishSubject subject) {
- BufferedReader reader = new BufferedReader(new InputStreamReader(input, encoding));
+ BufferedReader reader = new BufferedReader(encoding == null ? new InputStreamReader(input) : new InputStreamReader(input, encoding));
final int BUFFERSIZE = 4096;
char[] buffer = new char[BUFFERSIZE];
while (true) {
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 0526293b9..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
@@ -52,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.
*/
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/handler/AbstractDisconnectRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/AbstractDisconnectRequestHandler.java
index d01fa06f8..fd5c68d6c 100644
--- 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
@@ -23,6 +23,7 @@
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;
@@ -39,6 +40,7 @@ public List getTargetCommands() {
@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);
@@ -50,6 +52,7 @@ public CompletableFuture handle(Command command, Arguments arguments,
* @param context the debug context
*/
private void destroyResource(IDebugAdapterContext context) {
+ destroyProviders(context);
if (shouldDestroyLaunchFiles(context)) {
destroyLaunchFiles(context);
}
@@ -81,13 +84,15 @@ private void destroyLaunchFiles(IDebugAdapterContext context) {
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.SECONDS.sleep(1);
+ TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
// do nothing.
}
@@ -95,4 +100,11 @@ private void destroyLaunchFiles(IDebugAdapterContext context) {
}
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 d9968c3f5..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,28 +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) {
- 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);
+ 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).
@@ -106,4 +132,13 @@ public CompletableFuture handle(Command command, Arguments arguments,
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;
+ }
+
}
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/BreakpointLocationsRequestHander.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/BreakpointLocationsRequestHander.java
new file mode 100644
index 000000000..57c7ea15e
--- /dev/null
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/BreakpointLocationsRequestHander.java
@@ -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 getTargetCommands() {
+ return Arrays.asList(Requests.Command.BREAKPOINTLOCATIONS);
+ }
+
+ @Override
+ public CompletableFuture 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);
+ }
+}
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/CompletionsHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/CompletionsHandler.java
index 9f660644a..eba7d5153 100644
--- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/CompletionsHandler.java
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/CompletionsHandler.java
@@ -11,8 +11,8 @@
package com.microsoft.java.debug.core.adapter.handler;
-import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@@ -46,7 +46,7 @@ public CompletableFuture handle(Command command, Arguments 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 ArrayList<>();
+ response.body = new Responses.CompletionsResponseBody(Collections.emptyList());
return CompletableFuture.completedFuture(response);
}
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/ConfigurationDoneRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/ConfigurationDoneRequestHandler.java
index 5e77eff92..308adddb6 100644
--- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/ConfigurationDoneRequestHandler.java
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/ConfigurationDoneRequestHandler.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
@@ -27,6 +27,7 @@
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;
@@ -39,10 +40,12 @@
import com.sun.jdi.event.ThreadStartEvent;
import com.sun.jdi.event.VMDeathEvent;
import com.sun.jdi.event.VMDisconnectEvent;
+import com.sun.jdi.request.EventRequest;
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 getTargetCommands() {
@@ -52,6 +55,7 @@ public List getTargetCommands() {
@Override
public CompletableFuture 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 -> {
@@ -73,12 +77,15 @@ private void handleDebugEvent(DebugEvent debugEvent, IDebugSession debugSession,
if (context.isVmStopOnEntry()) {
DebugUtility.stopOnEntry(debugSession, context.getMainClass()).thenAccept(threadId -> {
context.getProtocolServer().sendEvent(new Events.StoppedEvent("entry", threadId));
+ context.getThreadCache().setThreadStoppedReason(threadId, "entry");
});
}
} 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());
@@ -99,20 +106,23 @@ private void handleDebugEvent(DebugEvent debugEvent, IDebugSession debugSession,
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();
- ThreadReference bpThread = ((ExceptionEvent) event).thread();
IEvaluationProvider engine = context.getProvider(IEvaluationProvider.class);
- if (engine.isInEvaluation(bpThread)) {
+ if (engine.isInEvaluation(thread)) {
return;
}
JdiExceptionReference jdiException = new JdiExceptionReference(((ExceptionEvent) event).exception(),
((ExceptionEvent) event).catchLocation() == null);
context.getExceptionManager().setException(thread.uniqueID(), jdiException);
- context.getProtocolServer().sendEvent(new Events.StoppedEvent("exception", thread.uniqueID()));
+ context.getThreadCache().addEventThread(thread, "exception");
+ boolean allThreadsStopped = event.request() != null
+ && event.request().suspendPolicy() == EventRequest.SUSPEND_ALL;
+ context.getProtocolServer().sendEvent(new Events.StoppedEvent("exception", thread.uniqueID(), allThreadsStopped));
debugEvent.shouldResume = false;
} else {
isImportantEvent = false;
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/DataBreakpointInfoRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/DataBreakpointInfoRequestHandler.java
new file mode 100644
index 000000000..2d9b6c30d
--- /dev/null
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/DataBreakpointInfoRequestHandler.java
@@ -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 getTargetCommands() {
+ return Arrays.asList(Command.DATABREAKPOINTINFO);
+ }
+
+ @Override
+ public CompletableFuture 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];
+ }
+}
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/DisconnectRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/DisconnectRequestHandler.java
index 7170e1549..a615e8963 100644
--- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/DisconnectRequestHandler.java
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/DisconnectRequestHandler.java
@@ -32,5 +32,4 @@ public void destroyDebugSession(Command command, Arguments arguments, Response r
}
}
}
-
}
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/DisconnectRequestWithoutDebuggingHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/DisconnectRequestWithoutDebuggingHandler.java
index bb56d4052..59d750bcd 100644
--- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/DisconnectRequestWithoutDebuggingHandler.java
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/DisconnectRequestWithoutDebuggingHandler.java
@@ -1,5 +1,5 @@
/*******************************************************************************
-* Copyright (c) 2018 Microsoft Corporation and others.
+* 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
@@ -11,6 +11,8 @@
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;
@@ -25,6 +27,11 @@ public void destroyDebugSession(Command command, Arguments arguments, Response r
Process debuggeeProcess = context.getDebuggeeProcess();
if (debuggeeProcess != null && disconnectArguments.terminateDebuggee) {
debuggeeProcess.destroy();
+ } else if (context.getProcessId() > 0 && disconnectArguments.terminateDebuggee) {
+ Optional debuggeeHandle = ProcessHandle.of(context.getProcessId());
+ if (debuggeeHandle.isPresent()) {
+ debuggeeHandle.get().destroy();
+ }
}
}
}
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/EvaluateRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/EvaluateRequestHandler.java
index 30f98d7a8..d135ee5b2 100644
--- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/EvaluateRequestHandler.java
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/EvaluateRequestHandler.java
@@ -1,5 +1,5 @@
/*******************************************************************************
-* Copyright (c) 2017-2019 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
@@ -14,7 +14,6 @@
import java.util.Arrays;
import java.util.List;
import java.util.Map;
-import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutionException;
@@ -32,6 +31,7 @@
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;
@@ -64,6 +64,13 @@ public CompletableFuture handle(Command command, Arguments arguments,
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.",
@@ -88,41 +95,73 @@ public CompletableFuture handle(Command command, Arguments arguments,
}
long threadId = stackFrameReference.getThread().uniqueID();
if (value instanceof ObjectReference) {
- VariableProxy varProxy = new VariableProxy(stackFrameReference.getThread(), "eval", value);
+ 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 && DebugSettings.getCurrent().showLogicalStructure
- && engine != null
- && JavaLogicalStructureManager.isIndexedVariable((ObjectReference) value)) {
+ } else if (value instanceof ObjectReference && supportsLogicStructureView(context, evalArguments.context) && engine != null) {
try {
- sizeValue = JavaLogicalStructureManager.getLogicalSize((ObjectReference) value, stackFrameReference.getThread(), engine);
- if (sizeValue != null && sizeValue instanceof IntegerValue) {
- indexedVariables = ((IntegerValue) sizeValue).value();
+ 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 (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);
+ } 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 && VariableUtils.hasChildren(value, showStaticVariables))) {
+ if (indexedVariables > 0 || (indexedVariables < 0 && value instanceof ObjectReference)) {
referenceId = context.getRecyclableIdPool().addObject(threadId, varProxy);
}
- String valueString = variableFormatter.valueToString(value, options);
+ 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 = "";
+ } catch (Exception e) {
+ hasErrors = true;
+ logger.log(Level.SEVERE, "Failed to resolve the variable value", e);
+ valueString = "";
+ }
+
String detailsString = null;
- if (sizeValue != 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 (DebugSettings.getCurrent().showToString) {
- detailsString = VariableDetailUtils.formatDetailsValue(value, stackFrameReference.getThread(), variableFormatter, options, engine);
+ } 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 = "";
+ } catch (Exception e) {
+ logger.log(Level.SEVERE, "Failed to compute the toString() value", e);
+ detailsString = "";
+ }
}
- response.body = new Responses.EvaluateResponseBody((detailsString == null) ? valueString : valueString + " " + detailsString,
- referenceId, variableFormatter.typeToString(value == null ? null : value.type(), options),
- Math.max(indexedVariables, 0));
+ 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
@@ -145,4 +184,24 @@ public CompletableFuture handle(Command command, Arguments arguments,
}
});
}
+
+ 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;
+ }
}
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/ExceptionInfoRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/ExceptionInfoRequestHandler.java
index 8052ff745..5e065edd0 100644
--- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/ExceptionInfoRequestHandler.java
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/ExceptionInfoRequestHandler.java
@@ -1,5 +1,5 @@
/*******************************************************************************
-* Copyright (c) 2019 Microsoft Corporation and others.
+* 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
@@ -53,7 +53,11 @@ public List getTargetCommands() {
public CompletableFuture handle(Command command, Arguments arguments, Response response,
IDebugAdapterContext context) {
ExceptionInfoArguments exceptionInfoArgs = (ExceptionInfoArguments) arguments;
- ThreadReference thread = DebugUtility.getThread(context.getDebugSession(), exceptionInfoArgs.threadId);
+ 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);
}
@@ -80,6 +84,15 @@ public CompletableFuture handle(Command command, Arguments arguments,
} 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.
+ }
}
}
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/HotCodeReplaceHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/HotCodeReplaceHandler.java
index f32d4503e..fa65e978b 100644
--- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/HotCodeReplaceHandler.java
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/HotCodeReplaceHandler.java
@@ -59,8 +59,12 @@ public CompletableFuture handle(Command command, Arguments arguments,
IHotCodeReplaceProvider provider = context.getProvider(IHotCodeReplaceProvider.class);
- return provider.redefineClasses().thenApply(classNames -> {
+ 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;
});
}
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/InitializeRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/InitializeRequestHandler.java
index f618e9fc2..6b9245166 100644
--- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/InitializeRequestHandler.java
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/InitializeRequestHandler.java
@@ -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
@@ -61,7 +61,13 @@ public CompletableFuture handle(Requests.Command command, Req
};
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);
}
}
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/InlineValuesRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/InlineValuesRequestHandler.java
new file mode 100644
index 000000000..21f77ee5a
--- /dev/null
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/InlineValuesRequestHandler.java
@@ -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 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 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 formatterOptions = variableFormatter.getDefaultOptions();
+ Map 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
+ }
+ }
+}
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchRequestHandler.java
index 31a0a0878..e5662f936 100644
--- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchRequestHandler.java
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchRequestHandler.java
@@ -1,5 +1,5 @@
/*******************************************************************************
-* Copyright (c) 2018 Microsoft Corporation and others.
+* 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
@@ -16,8 +16,9 @@
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
+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;
@@ -41,6 +42,8 @@
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;
@@ -74,7 +77,19 @@ public List getTargetCommands() {
@Override
public CompletableFuture 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 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);
@@ -89,23 +104,21 @@ protected CompletableFuture handleLaunchCommand(Arguments arguments, R
"Failed to launch debuggee VM. Missing mainClass or modulePaths/classPaths options in launch configuration.",
ErrorCode.ARGUMENT_MISSING);
}
- if (StringUtils.isBlank(launchArguments.encoding)) {
- context.setDebuggeeEncoding(StandardCharsets.UTF_8);
- } else {
+ 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());
+ }
}
- 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);
@@ -114,7 +127,7 @@ protected CompletableFuture handleLaunchCommand(Arguments arguments, R
if (launchArguments.shortenCommandLine == ShortenApproach.JARMANIFEST) {
if (ArrayUtils.isNotEmpty(launchArguments.classPaths)) {
try {
- Path tempfile = AdapterUtils.generateClasspathJar(launchArguments.classPaths);
+ Path tempfile = LaunchUtils.generateClasspathJar(launchArguments.classPaths);
launchArguments.vmArgs += " -cp \"" + tempfile.toAbsolutePath().toString() + "\"";
launchArguments.classPaths = new String[0];
context.setClasspathJar(tempfile);
@@ -128,17 +141,69 @@ protected CompletableFuture handleLaunchCommand(Arguments arguments, R
}
} else if (launchArguments.shortenCommandLine == ShortenApproach.ARGFILE) {
try {
- Path tempfile = AdapterUtils.generateArgfile(launchArguments.classPaths, launchArguments.modulePaths);
- launchArguments.vmArgs += " @" + tempfile.toAbsolutePath().toString();
- launchArguments.classPaths = new String[0];
- launchArguments.modulePaths = new String[0];
- context.setArgsfile(tempfile);
+ /**
+ * 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);
}
@@ -183,12 +248,18 @@ protected void handleTerminatedEvent(IDebugAdapterContext context) {
* @return the command arrays
*/
public static String[] constructLaunchCommands(LaunchArguments launchArguments, boolean serverMode, String address) {
- String slash = System.getProperty("file.separator");
-
List launchCmds = new ArrayList<>();
- final String javaHome = StringUtils.isNotEmpty(DebugSettings.getCurrent().javaHome) ? DebugSettings.getCurrent().javaHome
- : System.getProperty("java.home");
- launchCmds.add(javaHome + slash + "bin" + slash + "java");
+ 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));
}
@@ -205,7 +276,7 @@ public static String[] constructLaunchCommands(LaunchArguments launchArguments,
}
// For java 9 project, should specify "-m $MainClass".
String[] mainClasses = launchArguments.mainClass.split("/");
- if (ArrayUtils.isNotEmpty(launchArguments.modulePaths) || mainClasses.length == 2) {
+ if (mainClasses.length == 2) {
launchCmds.add("-m");
}
launchCmds.add(launchArguments.mainClass);
@@ -239,6 +310,22 @@ protected CompletableFuture launch(LaunchArguments launchArguments, Re
.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(
@@ -251,12 +338,12 @@ protected CompletableFuture launch(LaunchArguments launchArguments, Re
return resultFuture;
}
- private static final Pattern STACKTRACE_PATTERN = Pattern.compile("\\s+at\\s+(([\\w$]+\\.)*[\\w$]+)\\(([\\w-$]+\\.java:\\d+)\\)");
+ 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(1);
+ 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(".")) : "";
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchUtils.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchUtils.java
new file mode 100644
index 000000000..7370328b2
--- /dev/null
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchUtils.java
@@ -0,0 +1,392 @@
+/*******************************************************************************
+* 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 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 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_" + getSha256(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_" + getSha256(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 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 psProcs = new ArrayList<>();
+ List 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 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 getSha256(String input) {
+ try {
+ MessageDigest md = MessageDigest.getInstance("SHA-256");
+ byte[] messageDigest = md.digest(input.getBytes());
+ // Use only first 16 bytes to keep filename shorter
+ byte[] truncated = new byte[16];
+ System.arraycopy(messageDigest, 0, truncated, 0, 16);
+ BigInteger hash = new BigInteger(1, truncated);
+ return hash.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;
+ }
+ }
+}
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchWithDebuggingDelegate.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchWithDebuggingDelegate.java
index 0f690773a..2962294a0 100644
--- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchWithDebuggingDelegate.java
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchWithDebuggingDelegate.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
@@ -24,6 +24,7 @@
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;
@@ -45,6 +46,7 @@
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;
@@ -56,14 +58,15 @@ public class LaunchWithDebuggingDelegate implements ILaunchDelegate {
protected static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME);
private static final int ATTACH_TERMINAL_TIMEOUT = 20 * 1000;
- private static final String TERMINAL_TITLE = "Java Debug Console";
protected static final long RUNINTERMINAL_TIMEOUT = 10 * 1000;
+ private VMHandler vmHandler = new VMHandler();
@Override
public CompletableFuture launchInTerminal(LaunchArguments launchArguments, Response response, IDebugAdapterContext context) {
CompletableFuture resultFuture = new CompletableFuture<>();
IVirtualMachineManagerProvider vmProvider = context.getProvider(IVirtualMachineManagerProvider.class);
+ vmHandler.setVmProvider(vmProvider);
final String launchInTerminalErrorFormat = "Failed to launch debuggee in terminal. Reason: %s";
try {
@@ -73,6 +76,8 @@ public CompletableFuture launchInTerminal(LaunchArguments launchArgume
((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) {
@@ -80,13 +85,13 @@ public CompletableFuture launchInTerminal(LaunchArguments launchArgume
cmds,
launchArguments.cwd,
launchArguments.env,
- TERMINAL_TITLE);
+ terminalName);
} else {
requestArgs = RunInTerminalRequestArguments.createExternalTerminal(
cmds,
launchArguments.cwd,
launchArguments.env,
- TERMINAL_TITLE);
+ terminalName);
}
Request request = new Request(Command.RUNINTERMINAL.getName(),
(JsonObject) JsonUtils.toJsonTree(requestArgs, RunInTerminalRequestArguments.class));
@@ -100,9 +105,24 @@ public CompletableFuture