Support step filters when stepping#106
Conversation
Signed-off-by: Jinbo Wang <jinbwan@microsoft.com>
Signed-off-by: Jinbo Wang <jinbwan@microsoft.com>
Signed-off-by: Jinbo Wang <jinbwan@microsoft.com>
Signed-off-by: Jinbo Wang <jinbwan@microsoft.com>
Signed-off-by: Jinbo Wang <jinbwan@microsoft.com>
…to jinbo_stepfilter
Signed-off-by: Jinbo Wang <jinbwan@microsoft.com>
| } | ||
| } | ||
|
|
||
| /** |
There was a problem hiding this comment.
Better to put another class ThreadUtility. There will be StackFrameUtility definitely. If all utility in DebugUtility, this file will be bloated.
There was a problem hiding this comment.
There are many places to reference to these methods. It would cause many changed files in this PR. Prefer to use a separate PR to do this.
| debugEvent.shouldResume = false; | ||
| } | ||
| } else if (event instanceof StepEvent) { | ||
| ThreadReference stepThread = ((StepEvent) event).thread(); |
There was a problem hiding this comment.
If do nothing, just remove this condition switch
| context.setAttached(true); | ||
| context.setSourcePaths(attachArguments.sourcePaths); | ||
| context.setDebuggeeEncoding(StandardCharsets.UTF_8); // Use UTF-8 as debuggee's default encoding format. | ||
| context.setDebugFilters(attachArguments.debugFilters); |
There was a problem hiding this comment.
A thought about AttachRequesthandler & LanuchRequestHandler. There are lots of duplicate code from these two classes. We need refactor out a common base in this case.
There was a problem hiding this comment.
nice. Will add some preProcess and postProcess function to handle the common logic.
There was a problem hiding this comment.
When implementing launchInTerminal, i have changed the launch logic, would prefer to refactor common base of the attach/launch handler there.
Will send out new PR later.
| return threadStates.get(threadId); | ||
| } | ||
|
|
||
| private void setPendingStepKind(ThreadReference thread, int kind) { |
There was a problem hiding this comment.
Should be another ThreadUtility class if you already put something into the DebugUtility.
There was a problem hiding this comment.
At the ThreadsRequestHandler constructor, i create a thread state map private Map<Long, ThreadState> threadStates = new HashMap<>();. This method is used to update the state map.
| ThreadReference thread = DebugUtility.getThread(context.getDebugSession(), arguments.threadId); | ||
| if (thread != null) { | ||
| DebugUtility.stepInto(thread, context.getDebugSession().getEventHub()); | ||
| setPendingStepKind(thread, StepRequest.STEP_INTO); |
There was a problem hiding this comment.
There is already use StepRequest.STEP_INOT/STEP_OVER in the DebugUtility. I would suggest put the same logic either on inside DebugUtility.stepxxx or all on here. But not on different method call stacks.
There was a problem hiding this comment.
This logic is to record the thread's stepping state, and putting the code in ThreadRequestHandler is reasonable because ThreadRequestHandler life cycle is debug session dependent.
| return !methodShouldBeFiltered(originalLocation.method(), context) && methodShouldBeFiltered(currentLocation.method(), context); | ||
| } | ||
|
|
||
| private boolean methodShouldBeFiltered(Method method, IDebugAdapterContext context) { |
There was a problem hiding this comment.
Naming ShouldFilterMethod for concice
| /** | ||
| * Return true if the StepEvent's location is a Method that the user has indicated to filter. | ||
| */ | ||
| private boolean locationShouldBeFiltered(ThreadReference thread, IDebugAdapterContext context) { |
| * the step filters when stepping. | ||
| * @return the created {@link StepRequest}. | ||
| */ | ||
| public static CompletableFuture<Location> stepOver(ThreadReference thread, IEventHub eventHub) { |
There was a problem hiding this comment.
Why do we need to change the return value?
There was a problem hiding this comment.
One common practice of changing interface is to overload the method by adding parameters. So the old method can call the new method. In this way, the change happens internally without affecting other parts.
There was a problem hiding this comment.
In this case, a new method can be added:
public static CompletableFuture<Location> stepOver(ThreadReference thread, IEventHub eventHub, String[] stepFilters)
And in the old method, we change the implementation to call the new one with null or empty string array.
There was a problem hiding this comment.
good suggestion.
| context.getDebugSession().getEventHub().stepEvents().subscribe(debugEvent -> { | ||
| handleDebugEvent(debugEvent, context.getDebugSession(), context); | ||
| }); | ||
| } else { |
There was a problem hiding this comment.
We can just return here so the "else" branch is not needed.
There was a problem hiding this comment.
nice. fixed.
| setOriginalStackDepth(thread); | ||
| setOriginalStepLocation(thread); | ||
| if (command == Command.STEPIN) { | ||
| DebugUtility.stepInto(thread, context.getDebugSession().getEventHub(), context.getDebugFilters().stepFilters); |
There was a problem hiding this comment.
Why isn't anyone using the returned request object?
There was a problem hiding this comment.
fixed. the latest change has used the stepRequest info.
| import com.sun.jdi.request.StepRequest; | ||
|
|
||
| public class StepRequestHandler implements IDebugRequestHandler { | ||
| private Map<Long, ThreadState> threadStates; |
There was a problem hiding this comment.
Who is responsible for removing thread state objects from the collection?
There was a problem hiding this comment.
Add logic to remove thread state when ThreadDeathEvent occurs.
| logger.log(Level.SEVERE, failureMessage); | ||
| return AdapterUtils.createAsyncErrorResponse(response, ErrorCode.STEP_FAILURE, failureMessage); | ||
| } catch (IndexOutOfBoundsException ex) { | ||
| final String failureMessage = String.format("Failed to step because the thread %d doesn't contain any stack frame", threadId); |
| ThreadReference thread = DebugUtility.getThread(context.getDebugSession(), threadId); | ||
| if (thread != null) { | ||
| try { | ||
| setPendingStepKind(threadId, command); |
There was a problem hiding this comment.
Do we need to check the thread status before setting it? What if the thread state is already set by other requests?
| if (pendingStepRequest != null) { | ||
| DebugUtility.deleteEventRequestSafely(debugSession.getVM().eventRequestManager(), pendingStepRequest); | ||
| } | ||
| setPendingStepRequest(threadId, null); |
There was a problem hiding this comment.
Why not just removeThreadState()?
| removeThreadState(threadId); | ||
| } else if (event instanceof StepEvent) { | ||
| debugEvent.shouldResume = false; | ||
| ThreadReference thread = ((StepEvent) event).thread(); |
There was a problem hiding this comment.
The space before "event" is not necessary.
There was a problem hiding this comment.
Do you mean the whitespace between (StepEvent) and event? checkstyle will need add a space there.
| debugEvent.shouldResume = false; | ||
| ThreadReference thread = ((StepEvent) event).thread(); | ||
| long threadId = thread.uniqueID(); | ||
| setPendingStepRequest(threadId, null); // clean up the pending status. |
There was a problem hiding this comment.
We need to dispose the request before setting it to null, right?
There was a problem hiding this comment.
No need to dispose it because DebugUtility.stepXXX method also subscribes StepEvent and it will dispose the StepRequest via JDI.
| try { | ||
| if (getPendingStepKind(threadId) == Command.STEPIN) { | ||
| // Check if the step into operation stepped through the filtered code and stopped at an un-filtered location. | ||
| if (getOriginalStackDepth(threadId) + 1 < thread.frameCount()) { |
There was a problem hiding this comment.
Didn't quite get the logic here. Let's sync more tomorrow.
| } | ||
| // If the ending step location is filtered, or same as the original location where the step into operation is originated, | ||
| // do another step of the same kind. | ||
| if (shouldFilterLocation(thread, context) || shouldDoExtraStepInto(thread)) { |
There was a problem hiding this comment.
Didn't quite get the logic here. Let's sync more tomorrow.
| } | ||
| } | ||
|
|
||
| private void setPendingStepKind(long threadId, Command kind) { |
| } | ||
|
|
||
| public static class DebugFilters { | ||
| public String[] stepFilters = new String[0]; |
There was a problem hiding this comment.
stepFilters -> classNameFilters
| public boolean supportsRunInTerminalRequest; | ||
| } | ||
|
|
||
| public static class DebugFilters { |
| context.getStepFilters().classNameFilters); | ||
| } | ||
| threadState.pendingStepRequest.enable(); | ||
| thread.resume(); |
There was a problem hiding this comment.
use DebugUtility.resumeThread
| } | ||
|
|
||
| private boolean shouldFilterMethod(Method method, IDebugAdapterContext context) { | ||
| if ((context.getStepFilters().skipStaticInitializers && method.isStaticInitializer()) |
| StepRequest pendingStepRequest = null; | ||
| int stackDepth = -1; | ||
| Location stepLocation = null; | ||
| Disposable disposable = null; |

Expose debugFilters options in launch.json to allow user to customize the filters to debug "Just My Code".
The peer PR in vscode side is microsoft/vscode-java-debug#155