-
Notifications
You must be signed in to change notification settings - Fork 159
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
Support step filters when stepping #106
Conversation
Signed-off-by: Jinbo Wang <[email protected]>
Signed-off-by: Jinbo Wang <[email protected]>
Signed-off-by: Jinbo Wang <[email protected]>
Signed-off-by: Jinbo Wang <[email protected]>
Signed-off-by: Jinbo Wang <[email protected]>
…to jinbo_stepfilter
Signed-off-by: Jinbo Wang <[email protected]>
@@ -312,6 +316,34 @@ public static void resumeThread(ThreadReference thread) { | |||
} | |||
} | |||
|
|||
/** |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
@@ -118,9 +118,7 @@ private void handleDebugEvent(DebugEvent debugEvent, IDebugSession debugSession, | |||
debugEvent.shouldResume = false; | |||
} | |||
} else if (event instanceof StepEvent) { | |||
ThreadReference stepThread = ((StepEvent) event).thread(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If do nothing, just remove this condition switch
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice.
done.
@@ -51,6 +51,7 @@ | |||
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice. Will add some preProcess and postProcess function to handle the common logic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be another ThreadUtility class if you already put something into the DebugUtility.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
@@ -90,7 +110,10 @@ | |||
private CompletableFuture<Response> stepIn(Requests.StepInArguments arguments, Response response, IDebugAdapterContext context) { | |||
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Naming ShouldFilterMethod for concice
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed.
/** | ||
* Return true if the StepEvent's location is a Method that the user has indicated to filter. | ||
*/ | ||
private boolean locationShouldBeFiltered(ThreadReference thread, IDebugAdapterContext context) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ShouldFilterLocation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed.
*/ | ||
public static CompletableFuture<Location> stepOver(ThreadReference thread, IEventHub eventHub) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need to change the return value?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good suggestion.
context.getDebugSession().getEventHub().stepEvents().subscribe(debugEvent -> { | ||
handleDebugEvent(debugEvent, context.getDebugSession(), context); | ||
}); | ||
} else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can just return here so the "else" branch is not needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why isn't anyone using the returned request object?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Who is responsible for removing thread state objects from the collection?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When would this happen?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ThreadReference thread = DebugUtility.getThread(context.getDebugSession(), threadId); | ||
if (thread != null) { | ||
try { | ||
setPendingStepKind(threadId, command); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not just removeThreadState()?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice.
removeThreadState(threadId); | ||
} else if (event instanceof StepEvent) { | ||
debugEvent.shouldResume = false; | ||
ThreadReference thread = ((StepEvent) event).thread(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The space before "event" is not necessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to dispose the request before setting it to null, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Didn't quite get the logic here. Let's sync more tomorrow.
} | ||
} | ||
|
||
private void setPendingStepKind(long threadId, Command kind) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Kind -> Type
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice.
@@ -38,6 +38,13 @@ | |||
public boolean supportsRunInTerminalRequest; | |||
} | |||
|
|||
public static class DebugFilters { | |||
public String[] stepFilters = new String[0]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
stepFilters -> classNameFilters
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed.
@@ -38,6 +38,13 @@ | |||
public boolean supportsRunInTerminalRequest; | |||
} | |||
|
|||
public static class DebugFilters { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DebugFilters -> StepFilters
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed.
context.getStepFilters().classNameFilters); | ||
} | ||
threadState.pendingStepRequest.enable(); | ||
thread.resume(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use DebugUtility.resumeThread
} | ||
|
||
private boolean shouldFilterMethod(Method method, IDebugAdapterContext context) { | ||
if ((context.getStepFilters().skipStaticInitializers && method.isStaticInitializer()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use simple return
StepRequest pendingStepRequest = null; | ||
int stackDepth = -1; | ||
Location stepLocation = null; | ||
Disposable disposable = null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
disposable -> eventSubscription
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