Repository navigation
fix(core): enrich watchpoint/exception events with stacktrace and detailMessage, and validate watchpoint fields - #119
Merged
Conversation
…ailMessage, and validate watchpoint fields
PatilShreyas
enabled auto-merge (squash)
October 26, 2026 12:33
PatilShreyas
deleted the
fix/issue-111-watchpoint-exception-enrichment
branch
October 26, 2026 12:35
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #111
Summary
This PR resolves four issues in
JdiSessionevent handling and watchpoint/exception breakpoint binding:Populate
stacktraceon Watchpoint Hits (WATCHPOINT_ACCESS_HIT&WATCHPOINT_MODIFY_HIT):handleAccessWatchpointEventandhandleModificationWatchpointEventto populatestacktrace = getFramesSafely(event.thread())onDebugEvent, matchingBREAKPOINT_HIT,STEP_COMPLETE, andEXCEPTION_HIT.debroid poll <sessionId> <cursor> --with-stacktracenow includes the full call stack when a watchpoint triggers.Include
detailMessageinEXCEPTION_HITexceptionMessage:formatExceptionMessage(exceptionObj)inJdiSessionto inspect thedetailMessagefield onevent.exception(): ObjectReference.exceptionMessageas"<ExceptionClassName>: <detailMessage>"whendetailMessageis a non-nullStringReference, or falls back to"<ExceptionClassName>"whendetailMessageisnullor inaccessible.Coordinate VM Resume for Filtered
ExceptionEvents viarunEventListener:event.virtualMachine().resume()call insidehandleExceptionEventwhen client-side exception class filtering rejects an event.handleExceptionEventto returnBoolean(trueif the exception matched and suspended,falseif filtered out) and updatedprocessJdiEventaccordingly sorunEventListenercoordinateseventSet.resume()safely across multi-eventEventSets.Validate Watchpoint Field Existence & Bind Across All Matching
ReferenceTypes:ErrorCode.INVALID_ARGUMENTinModels.kt.setWatchpoint, whenrefTypes.isNotEmpty(), validates that at least one loadedReferenceTypecontainsfieldName; otherwise throwsDebugException(ErrorCode.INVALID_ARGUMENT, "Field '$fieldName' not found in class '$className'").setWatchpointandsetExceptionBreakpoint(exceptionRequestschanged toConcurrentHashMap<String, MutableList<ExceptionRequest>>) to bind requests across all loadedReferenceTypes matchingclassNameinstead of onlyrefTypes.first().Tests Performed
1. Automated Unit Tests & Static Analysis (
./gradlew build)JdiSessionTest:handleAccessWatchpointEvent populates stacktrace on WATCHPOINT_ACCESS_HIThandleModificationWatchpointEvent populates stacktrace on WATCHPOINT_MODIFY_HIThandleExceptionEvent formats exceptionMessage with detailMessage when presenthandleExceptionEvent formats exceptionMessage with class name only when detailMessage is nullhandleExceptionEvent filters non-matching exception and returns false without calling vm resume directlysetWatchpoint throws INVALID_ARGUMENT when class is loaded but field does not existsetWatchpoint binds across all loaded ReferenceTypes with matching fieldsetExceptionBreakpoint binds across all loaded ReferenceTypes./gradlew build(detekt,:core:test,:cli:test,:core:jacocoTestReport) passes with 0 issues.2. End-to-End Live Emulator Verification (
sample-app+android-cli)Tested against
com.example.sampledebugapprunning onPixel_10_Pro_Fold(emulator-5554):Invalid Watchpoint Field Validation:
Output:
{ "errorCode": "INVALID_ARGUMENT", "message": "Field 'nonExistentField' not found in class 'com.example.sampledebugapp.data.DefaultDataRepository'", "retryable": false }Watchpoint Access & Modification Stacktraces (
WATCHPOINT_ACCESS_HIT&WATCHPOINT_MODIFY_HIT):Set watchpoint on
DefaultDataRepository.totalOrdersProcessed, tapped"Process Gold Order ($100)", and polled with--with-stacktrace:{ "eventType": "WATCHPOINT_ACCESS_HIT", "sessionId": "sess_100", "threadId": "1", "threadName": "main", "location": "DataRepository.kt:57", "className": "com.example.sampledebugapp.data.DefaultDataRepository", "exceptionMessage": "Field totalOrdersProcessed accessed (current value: 0)", "stacktrace": [ { "frameIndex": 0, "methodName": "processOrder", "declaringClass": "com.example.sampledebugapp.data.DefaultDataRepository", "sourceFile": "DataRepository.kt", "lineNumber": 57 }, ... ] }Resumed and polled
WATCHPOINT_MODIFY_HITwith--with-stacktrace:{ "eventType": "WATCHPOINT_MODIFY_HIT", "sessionId": "sess_100", "threadId": "1", "threadName": "main", "location": "DataRepository.kt:57", "className": "com.example.sampledebugapp.data.DefaultDataRepository", "exceptionMessage": "Field totalOrdersProcessed modified to 1", "stacktrace": [ { "frameIndex": 0, "methodName": "processOrder", "declaringClass": "com.example.sampledebugapp.data.DefaultDataRepository", "sourceFile": "DataRepository.kt", "lineNumber": 57 }, ... ] }Exception Breakpoint
detailMessage& Stacktrace (EXCEPTION_HIT):Set exception breakpoint via
debroid catch-exception sess_100 java.lang.IllegalArgumentException --caught --uncaught, tapped"Trigger Exception Order", and polled with--with-stacktrace:{ "eventType": "EXCEPTION_HIT", "sessionId": "sess_100", "threadId": "1", "threadName": "main", "location": "DataRepository.kt:25", "className": "com.example.sampledebugapp.data.OrderProcessor", "exceptionMessage": "java.lang.IllegalArgumentException: Unknown customer type: UNKNOWN_TYPE", "stacktrace": [ { "frameIndex": 0, "methodName": "calculateTotal", "declaringClass": "com.example.sampledebugapp.data.OrderProcessor", "sourceFile": "DataRepository.kt", "lineNumber": 25 }, ... ] }