Skip to content

Add support for StepInTarget request #434

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Oct 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
import com.microsoft.java.debug.core.adapter.handler.SetVariableRequestHandler;
import com.microsoft.java.debug.core.adapter.handler.SourceRequestHandler;
import com.microsoft.java.debug.core.adapter.handler.StackTraceRequestHandler;
import com.microsoft.java.debug.core.adapter.handler.StepInTargetsRequestHandler;
import com.microsoft.java.debug.core.adapter.handler.StepRequestHandler;
import com.microsoft.java.debug.core.adapter.handler.ThreadsRequestHandler;
import com.microsoft.java.debug.core.adapter.handler.VariablesRequestHandler;
Expand Down Expand Up @@ -131,6 +132,8 @@ private void initialize() {
registerHandlerForDebug(new ProcessIdHandler());
registerHandlerForDebug(new SetFunctionBreakpointsRequestHandler());
registerHandlerForDebug(new BreakpointLocationsRequestHander());
registerHandlerForDebug(new StepInTargetsRequestHandler());

// NO_DEBUG mode only
registerHandlerForNoDebug(new DisconnectRequestWithoutDebuggingHandler());
registerHandlerForNoDebug(new ProcessIdHandler());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@

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;
Expand Down Expand Up @@ -60,4 +63,50 @@ public interface ISourceLookUpProvider extends IProvider {
default String getJavaRuntimeVersion(String projectName) {
return null;
}

/**
* Return method invocation found in the statement as the given line number of
* the source file.
*
* @param uri The source file where the invocation must be searched.
* @param line The line number where the invocation must be searched.
*
* @return List of found method invocation or empty if not method invocations
* can be found.
*/
List<MethodInvocation> findMethodInvocations(String uri, int line);

public static class MethodInvocation {
public String expression;
public String methodName;
public String methodSignature;
public String methodGenericSignature;
public String declaringTypeName;
public int lineStart;
public int lineEnd;
public int columnStart;
public int columnEnd;

@Override
public int hashCode() {
return Objects.hash(expression, methodName, methodSignature, methodGenericSignature, declaringTypeName,
lineStart, lineEnd, columnStart, columnEnd);
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof MethodInvocation)) {
return false;
}
MethodInvocation other = (MethodInvocation) obj;
return Objects.equals(expression, other.expression) && Objects.equals(methodName, other.methodName)
&& Objects.equals(methodSignature, other.methodSignature)
&& Objects.equals(methodGenericSignature, other.methodGenericSignature)
&& Objects.equals(declaringTypeName, other.declaringTypeName) && lineStart == other.lineStart
&& lineEnd == other.lineEnd && columnStart == other.columnStart && columnEnd == other.columnEnd;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ public CompletableFuture<Messages.Response> handle(Requests.Command command, Req
caps.supportsFunctionBreakpoints = true;
caps.supportsClipboardContext = true;
caps.supportsBreakpointLocationsRequest = true;
caps.supportsStepInTargetsRequest = true;
response.body = caps;
return CompletableFuture.completedFuture(response);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,12 @@ public CompletableFuture<Response> handle(Command command, Arguments arguments,
StackFrame[] frames = context.getStackFrameManager().reloadStackFrames(thread, stacktraceArgs.startFrame, count);
List<StackFrameInfo> jdiFrames = resolveStackFrameInfos(frames, context.asyncJDWP());
for (int i = 0; i < count; i++) {
StackFrameReference stackframe = new StackFrameReference(thread, stacktraceArgs.startFrame + i);
int frameId = context.getRecyclableIdPool().addObject(stacktraceArgs.threadId, stackframe);
StackFrameReference frameReference = new StackFrameReference(thread, stacktraceArgs.startFrame + i);
int frameId = context.getRecyclableIdPool().addObject(stacktraceArgs.threadId, frameReference);
StackFrameInfo jdiFrame = jdiFrames.get(i);
result.add(convertDebuggerStackFrameToClient(jdiFrame, frameId, i == 0, context));
Types.StackFrame lspFrame = convertDebuggerStackFrameToClient(jdiFrame, frameId, i == 0, context);
result.add(lspFrame);
frameReference.setSource(lspFrame.source);
}
} catch (IncompatibleThreadStateException | IndexOutOfBoundsException | URISyntaxException
| AbsentInformationException | ObjectCollectedException
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/*******************************************************************************
* Copyright (c) 2022 Microsoft Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Gayan Perera - initial API and implementation
*******************************************************************************/
package com.microsoft.java.debug.core.adapter.handler;

import java.io.File;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.microsoft.java.debug.core.Configuration;
import com.microsoft.java.debug.core.adapter.AdapterUtils;
import com.microsoft.java.debug.core.adapter.IDebugAdapterContext;
import com.microsoft.java.debug.core.adapter.IDebugRequestHandler;
import com.microsoft.java.debug.core.adapter.ISourceLookUpProvider;
import com.microsoft.java.debug.core.adapter.ISourceLookUpProvider.MethodInvocation;
import com.microsoft.java.debug.core.adapter.variables.StackFrameReference;
import com.microsoft.java.debug.core.protocol.Messages.Response;
import com.microsoft.java.debug.core.protocol.Requests.Arguments;
import com.microsoft.java.debug.core.protocol.Requests.Command;
import com.microsoft.java.debug.core.protocol.Requests.StepInTargetsArguments;
import com.microsoft.java.debug.core.protocol.Responses.StepInTargetsResponse;
import com.microsoft.java.debug.core.protocol.Types.Source;
import com.microsoft.java.debug.core.protocol.Types.StepInTarget;
import com.sun.jdi.AbsentInformationException;
import com.sun.jdi.ReferenceType;
import com.sun.jdi.StackFrame;

public class StepInTargetsRequestHandler implements IDebugRequestHandler {
private static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME);

@Override
public List<Command> getTargetCommands() {
return Arrays.asList(Command.STEPIN_TARGETS);
}

@Override
public CompletableFuture<Response> handle(Command command, Arguments arguments, Response response,
IDebugAdapterContext context) {
final StepInTargetsArguments stepInTargetsArguments = (StepInTargetsArguments) arguments;

final int frameId = stepInTargetsArguments.frameId;
return CompletableFuture.supplyAsync(() -> {
response.body = new StepInTargetsResponse(
findFrame(frameId, context).map(f -> findTargets(f, context))
.orElse(Collections.emptyList()).toArray(StepInTarget[]::new));
return response;
});
}

private Optional<StackFrameReference> findFrame(int frameId, IDebugAdapterContext context) {
Object object = context.getRecyclableIdPool().getObjectById(frameId);
if (object instanceof StackFrameReference) {
return Optional.of((StackFrameReference) object);
}
return Optional.empty();
}

private List<StepInTarget> findTargets(StackFrameReference frameReference, IDebugAdapterContext context) {
StackFrame stackframe = context.getStackFrameManager().getStackFrame(frameReference);
if (stackframe == null) {
return Collections.emptyList();
}

Source source = frameReference.getSource() == null ? findSource(stackframe, context) : frameReference.getSource();
if (source == null) {
return Collections.emptyList();
}

String sourceUri = AdapterUtils.convertPath(source.path, AdapterUtils.isUri(source.path), true);
if (sourceUri == null) {
return Collections.emptyList();
}

ISourceLookUpProvider sourceLookUpProvider = context.getProvider(ISourceLookUpProvider.class);
List<MethodInvocation> invocations = sourceLookUpProvider.findMethodInvocations(sourceUri, stackframe.location().lineNumber());
if (invocations.isEmpty()) {
return Collections.emptyList();
}

long threadId = stackframe.thread().uniqueID();
List<StepInTarget> targets = new ArrayList<>(invocations.size());
for (MethodInvocation methodInvocation : invocations) {
int id = context.getRecyclableIdPool().addObject(threadId, methodInvocation);
StepInTarget target = new StepInTarget(id, methodInvocation.expression);
target.column = AdapterUtils.convertColumnNumber(methodInvocation.columnStart,
context.isDebuggerColumnsStartAt1(), context.isClientColumnsStartAt1());
target.endColumn = AdapterUtils.convertColumnNumber(methodInvocation.columnEnd,
context.isDebuggerColumnsStartAt1(), context.isClientColumnsStartAt1());
target.line = AdapterUtils.convertLineNumber(methodInvocation.lineStart,
context.isDebuggerLinesStartAt1(), context.isClientLinesStartAt1());
target.endLine = AdapterUtils.convertLineNumber(methodInvocation.lineEnd,
context.isDebuggerLinesStartAt1(), context.isClientLinesStartAt1());
targets.add(target);
}

// TODO remove the executed method calls.
return targets;
}

private Source findSource(StackFrame frame, IDebugAdapterContext context) {
ReferenceType declaringType = frame.location().declaringType();
String typeName = declaringType.name();
String sourceName = null;
String sourcePath = null;
try {
// When the .class file doesn't contain source information in meta data,
// invoking ReferenceType#sourceName() would throw AbsentInformationException.
sourceName = declaringType.sourceName();
sourcePath = declaringType.sourcePaths(null).get(0);
} catch (AbsentInformationException e) {
String enclosingType = AdapterUtils.parseEnclosingType(typeName);
sourceName = enclosingType.substring(enclosingType.lastIndexOf('.') + 1) + ".java";
sourcePath = enclosingType.replace('.', File.separatorChar) + ".java";
}

try {
return StackTraceRequestHandler.convertDebuggerSourceToClient(typeName, sourceName, sourcePath, context);
} catch (URISyntaxException e) {
logger.log(Level.SEVERE, "Failed to resolve the source info of the stack frame.", e);
}

return null;
}
}
Loading