add basic support for Exception UI

This commit is contained in:
adelphes
2017-06-20 13:40:57 +01:00
parent 92bd003122
commit 8baf894fc9
3 changed files with 87 additions and 10 deletions

View File

@@ -127,8 +127,8 @@
"test": "node ./node_modules/vscode/bin/test"
},
"dependencies": {
"vscode-debugprotocol": "^1.15.0",
"vscode-debugadapter": "^1.15.0",
"vscode-debugprotocol": "^1.20.0",
"vscode-debugadapter": "^1.20.0",
"long": "^3.2.0",
"ws": "^1.1.1",
"xmldom": "^0.1.27",

View File

@@ -22,7 +22,7 @@ const { D, isEmptyObject } = require('./util');
const { AndroidVariables } = require('./variables');
const { evaluate } = require('./expressions');
const ws_proxy = require('./wsproxy').proxy.Server(6037, 5037);
const { ensure_path_end_slash,is_subpath_of,variableRefToThreadId } = require('./globals');
const { exmsg_var_name, signatureToFullyQualifiedType, ensure_path_end_slash,is_subpath_of,variableRefToThreadId } = require('./globals');
class AndroidDebugSession extends DebugSession {
@@ -100,6 +100,9 @@ class AndroidDebugSession extends DebugSession {
// we support hit-count conditional breakpoints
response.body.supportsHitConditionalBreakpoints = true;
// we support the new ExceptionInfoRequest
response.body.supportsExceptionInfoRequest = true;
this.sendResponse(response);
}
@@ -781,7 +784,10 @@ class AndroidDebugSession extends DebugSession {
: null;
const src = new Source(sourcefile, srcpath, srcpath ? 0 : srcRefId);
pkginfo && (highest_known_source=i);
stack.push(new StackFrame(stack_frame_id, name, src, linenum, 0));
// we don't support column number when reporting source locations (because JDWP only supports line-granularity)
// but in order to get the Exception UI to show, we must have a non-zero column
const colnum = (!i && x.thread.paused.last_exception && x.thread.paused.reasons[0]==='exception') ? 1 : 0;
stack.push(new StackFrame(stack_frame_id, name, src, linenum, colnum));
}
// trim the stack to exclude calls above the known sources
if (this.callStackDisplaySize > 0) {
@@ -815,13 +821,22 @@ class AndroidDebugSession extends DebugSession {
if (last_exception && !last_exception.objvar) {
// retrieve the exception object
thread.allocateExceptionScopeReference(args.frameId);
this.dbgr.getExceptionLocal(last_exception.exception, {response,scopes,last_exception})
this.dbgr.getExceptionLocal(last_exception.exception, {thread,response,scopes,last_exception})
.then((ex_local,x) => {
x.last_exception.objvar = ex_local;
return $.when(x, x.thread.getVariables(x.last_exception.scopeRef));
})
.then((x, vars) => {
var {response,scopes,last_exception} = x;
last_exception.objvar = ex_local;
// put the exception first - otherwise it can get lost if there's a lot of locals
scopes.unshift(new Scope("Exception: "+ex_local.type.typename, last_exception.scopeRef, false));
scopes.unshift(new Scope("Exception: " + last_exception.objvar.type.typename, last_exception.scopeRef, false));
this.sendResponse(response);
// notify the exceptionInfo who may be waiting on us
if (last_exception.waitForExObject) {
var def = last_exception.waitForExObject;
last_exception.waitForExObject = null;
def.resolveWith(this, []);
}
})
.fail((/*e*/) => { this.sendResponse(response); });
return;
@@ -891,7 +906,7 @@ class AndroidDebugSession extends DebugSession {
return;
}
}
var event = new StoppedEvent(thread.paused.reasons[0], thread.vscode_threadid);
var event = new StoppedEvent(thread.paused.reasons[0], thread.vscode_threadid, thread.paused.last_exception && "Exception thrown");
thread.paused.stoppedEvent = event;
this.sendEvent(event);
}
@@ -1104,6 +1119,52 @@ class AndroidDebugSession extends DebugSession {
})
}
exceptionInfoRequest(response /*DebugProtocol.ExceptionInfoResponse*/, args /**/) {
var thread = this.getThread(args.threadId);
if (!thread) return this.failRequestNoThread('Exception info', args.threadId, response);
if (!thread.paused) return this.cancelRequestThreadNotSuspended('Exception info', args.threadId, response);
if (!thread.paused.last_exception) return this.failRequest('No exception available', response);
if (!thread.paused.last_exception.objvar) {
// we must wait for the exception object to be retreived as a local (along with the message field)
if (!thread.paused.last_exception.waitForExObject) {
thread.paused.last_exception.waitForExObject = $.Deferred().then(() => {
// redo the request
this.exceptionInfoRequest(response, args);
});
}
return;
}
var exobj = thread.paused.last_exception.objvar;
var exmsg = thread.paused.last_exception.cached.find(v => v.name === exmsg_var_name);
exmsg = (exmsg && exmsg.string) || '';
response.body = {
/** ID of the exception that was thrown. */
exceptionId: exobj.type.typename,
/** Descriptive text for the exception provided by the debug adapter. */
description: exmsg,
/** Mode that caused the exception notification to be raised. */
//'never' | 'always' | 'unhandled' | 'userUnhandled';
breakMode: 'always',
/** Detailed information about the exception. */
details: {
/** Message contained in the exception. */
message: exmsg,
/** Short type name of the exception object. */
typeName: exobj.type.typename,
/** Fully-qualified type name of the exception object. */
fullTypeName: signatureToFullyQualifiedType(exobj.type.signature),
/** Optional expression that can be evaluated in the current scope to obtain the exception object. */
//evaluateName: "evaluateName",
/** Stack trace at the time the exception was thrown. */
//stackTrace: "stackTrace",
/** Details of the exception contained by this exception, if any. */
//innerException: [],
}
}
this.sendResponse(response);
}
}

View File

@@ -27,6 +27,22 @@ const JTYPES = {
fromPrimSig(sig) { return JTYPES['byte,short,int,long,float,double,char,boolean'.split(',')['BSIJFDCZ'.indexOf(sig)]] },
}
function signatureToFullyQualifiedType(sig) {
var arr = sig.match(/^\[+/) || '';
if (arr) {
arr = '[]'.repeat(arr[0].length);
sig = sig.slice(0, arr.length/2);
}
var m = sig.match(/^((L([^<;]+).)|T([^;]+).|.)/);
if (!m) return '';
if (m[3]) {
return m[3].replace(/[/$]/g,'.') + arr;
} else if (m[4]) {
return m[4].replace(/[/$]/g, '.') + arr;
}
return JTYPES.fromPrimSig(sig[0]) + arr;
}
// the special name given to exception message fields
const exmsg_var_name = ':msg';
@@ -67,5 +83,5 @@ function variableRefToThreadId(variablesReference) {
Object.assign(exports, {
JTYPES,exmsg_var_name,ensure_path_end_slash,is_subpath_of,decode_char,variableRefToThreadId,createJavaString
JTYPES, exmsg_var_name, ensure_path_end_slash, is_subpath_of, decode_char, variableRefToThreadId, createJavaString, signatureToFullyQualifiedType
});