mirror of
https://github.com/adelphes/android-dev-ext.git
synced 2025-12-22 17:39:19 +00:00
add initial support for method call expressions
This commit is contained in:
@@ -9,7 +9,6 @@ const crypto = require('crypto');
|
|||||||
const dom = require('xmldom').DOMParser;
|
const dom = require('xmldom').DOMParser;
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const os = require('os');
|
const os = require('os');
|
||||||
const Long = require('long');
|
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const xpath = require('xpath');
|
const xpath = require('xpath');
|
||||||
|
|
||||||
@@ -1103,7 +1102,7 @@ class AndroidDebugSession extends DebugSession {
|
|||||||
|
|
||||||
// wait for any locals in the given context to be retrieved
|
// wait for any locals in the given context to be retrieved
|
||||||
getvars.then((thread, locals, vars) => {
|
getvars.then((thread, locals, vars) => {
|
||||||
return evaluate(args.expression, thread, locals, vars);
|
return evaluate(args.expression, thread, locals, vars, this.dbgr);
|
||||||
})
|
})
|
||||||
.then((value,variablesReference) => {
|
.then((value,variablesReference) => {
|
||||||
response.body = { result:value, variablesReference:variablesReference|0 };
|
response.body = { result:value, variablesReference:variablesReference|0 };
|
||||||
@@ -1138,7 +1137,7 @@ class AndroidDebugSession extends DebugSession {
|
|||||||
var exobj = thread.paused.last_exception.objvar;
|
var exobj = thread.paused.last_exception.objvar;
|
||||||
var exmsg = thread.paused.last_exception.cached.find(v => v.name === exmsg_var_name);
|
var exmsg = thread.paused.last_exception.cached.find(v => v.name === exmsg_var_name);
|
||||||
exmsg = (exmsg && exmsg.string) || '';
|
exmsg = (exmsg && exmsg.string) || '';
|
||||||
|
|
||||||
response.body = {
|
response.body = {
|
||||||
/** ID of the exception that was thrown. */
|
/** ID of the exception that was thrown. */
|
||||||
exceptionId: exobj.type.typename,
|
exceptionId: exobj.type.typename,
|
||||||
|
|||||||
@@ -1054,6 +1054,50 @@ Debugger.prototype = {
|
|||||||
return this.invokeMethod(objectid, threadid, type_signature || 'Ljava/lang/Object;', 'toString', '()Ljava/lang/String;', [], extra);
|
return this.invokeMethod(objectid, threadid, type_signature || 'Ljava/lang/Object;', 'toString', '()Ljava/lang/String;', [], extra);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
findNamedMethods(type_signature, name, method_signature) {
|
||||||
|
var x = { type_signature, name, method_signature }
|
||||||
|
const ismatch = function(x, y) {
|
||||||
|
if (!x || (x === y)) return true;
|
||||||
|
return (x instanceof RegExp) && x.test(y);
|
||||||
|
}
|
||||||
|
return this.gettypedebuginfo(x.type_signature)
|
||||||
|
.then(dbgtype => this._ensuremethods(dbgtype[x.type_signature]))
|
||||||
|
.then(typeinfo => ({
|
||||||
|
// resolving the methods only resolves the non-inherited methods
|
||||||
|
// if we can't find a matching method, we need to search the super types
|
||||||
|
dbgr: this,
|
||||||
|
def: $.Deferred(),
|
||||||
|
matches:[],
|
||||||
|
find_methods(typeinfo) {
|
||||||
|
for (var mid in typeinfo.methods) {
|
||||||
|
var m = typeinfo.methods[mid];
|
||||||
|
// does the name match
|
||||||
|
if (!ismatch(x.name, m.name)) continue;
|
||||||
|
// does the signature match
|
||||||
|
if (!ismatch(x.method_signature, m.genericsig || m.sig)) continue;
|
||||||
|
// add it to the results
|
||||||
|
this.matches.push(m);
|
||||||
|
}
|
||||||
|
// search the supertype
|
||||||
|
if (typeinfo.type.signature === 'Ljava/lang/Object;') {
|
||||||
|
this.def.resolveWith(this.dbgr, [this.matches]);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
this.dbgr._ensuresuper(typeinfo)
|
||||||
|
.then(typeinfo => {
|
||||||
|
return this.dbgr.gettypedebuginfo(typeinfo.super.signature, typeinfo.super.signature)
|
||||||
|
})
|
||||||
|
.then((dbgtype, sig) => {
|
||||||
|
return this.dbgr._ensuremethods(dbgtype[sig])
|
||||||
|
})
|
||||||
|
.then(typeinfo => {
|
||||||
|
this.find_methods(typeinfo)
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}).find_methods(typeinfo).def)
|
||||||
|
},
|
||||||
|
|
||||||
getstringchars: function (stringref, extra) {
|
getstringchars: function (stringref, extra) {
|
||||||
return this.session.adbclient.jdwp_command({
|
return this.session.adbclient.jdwp_command({
|
||||||
ths: this,
|
ths: this,
|
||||||
@@ -1269,8 +1313,10 @@ Debugger.prototype = {
|
|||||||
return $.Deferred().resolveWith(this, [typeinfo]);
|
return $.Deferred().resolveWith(this, [typeinfo]);
|
||||||
}
|
}
|
||||||
if (typeinfo.info.reftype.string !== 'class' || typeinfo.type.signature[0] !== 'L' || typeinfo.type.signature === 'Ljava/lang/Object;') {
|
if (typeinfo.info.reftype.string !== 'class' || typeinfo.type.signature[0] !== 'L' || typeinfo.type.signature === 'Ljava/lang/Object;') {
|
||||||
typeinfo.super = null;
|
if (typeinfo.info.reftype.string !== 'array') {
|
||||||
return $.Deferred().resolveWith(this, [typeinfo]);
|
typeinfo.super = null;
|
||||||
|
return $.Deferred().resolveWith(this, [typeinfo]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
typeinfo.super = $.Deferred();
|
typeinfo.super = $.Deferred();
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
'use strict'
|
'use strict'
|
||||||
|
const Long = require('long');
|
||||||
const $ = require('./jq-promise');
|
const $ = require('./jq-promise');
|
||||||
const { D } = require('./util');
|
const { D } = require('./util');
|
||||||
const { JTYPES, exmsg_var_name, decode_char, createJavaString } = require('./globals');
|
const { JTYPES, exmsg_var_name, decode_char, createJavaString } = require('./globals');
|
||||||
@@ -6,7 +7,7 @@ const { JTYPES, exmsg_var_name, decode_char, createJavaString } = require('./glo
|
|||||||
/*
|
/*
|
||||||
Asynchronously evaluate an expression
|
Asynchronously evaluate an expression
|
||||||
*/
|
*/
|
||||||
exports.evaluate = function(expression, thread, locals, vars) {
|
exports.evaluate = function(expression, thread, locals, vars, dbgr) {
|
||||||
D('evaluate: ' + expression);
|
D('evaluate: ' + expression);
|
||||||
|
|
||||||
const reject_evaluation = (msg) => $.Deferred().rejectWith(this, [new Error(msg)]);
|
const reject_evaluation = (msg) => $.Deferred().rejectWith(this, [new Error(msg)]);
|
||||||
@@ -202,7 +203,7 @@ exports.evaluate = function(expression, thread, locals, vars) {
|
|||||||
else if (local.hasnullvalue) s = '(null)';
|
else if (local.hasnullvalue) s = '(null)';
|
||||||
if (typeof s === 'string')
|
if (typeof s === 'string')
|
||||||
return $.Deferred().resolveWith(this, [s]);
|
return $.Deferred().resolveWith(this, [s]);
|
||||||
return this.dbgr.invokeToString(local.value, local.info.frame.threadid, local.type.signature)
|
return dbgr.invokeToString(local.value, local.info.frame.threadid, local.type.signature)
|
||||||
.then(s => s.string);
|
.then(s => s.string);
|
||||||
}
|
}
|
||||||
const evaluate_expression = (expr) => {
|
const evaluate_expression = (expr) => {
|
||||||
@@ -334,7 +335,7 @@ exports.evaluate = function(expression, thread, locals, vars) {
|
|||||||
return { vtype: 'literal', name: '', hasnullvalue: false, type: JTYPES.boolean, value: a, valid: true };
|
return { vtype: 'literal', name: '', hasnullvalue: false, type: JTYPES.boolean, value: a, valid: true };
|
||||||
}
|
}
|
||||||
else if (expr.operator === '+' && JTYPES.isString(lhs_local.type)) {
|
else if (expr.operator === '+' && JTYPES.isString(lhs_local.type)) {
|
||||||
return stringify(rhs_local).then(rhs_str => createJavaString(this.dbgr, lhs_local.string + rhs_str, { israw: true }));
|
return stringify(rhs_local).then(rhs_str => createJavaString(dbgr, lhs_local.string + rhs_str, { israw: true }));
|
||||||
}
|
}
|
||||||
return invalid_operator();
|
return invalid_operator();
|
||||||
});
|
});
|
||||||
@@ -363,7 +364,7 @@ exports.evaluate = function(expression, thread, locals, vars) {
|
|||||||
break;
|
break;
|
||||||
case 'string':
|
case 'string':
|
||||||
// we must get the runtime to create string instances
|
// we must get the runtime to create string instances
|
||||||
q = createJavaString(this.dbgr, expr.root_term);
|
q = createJavaString(dbgr, expr.root_term);
|
||||||
local = { valid: true }; // make sure we don't fail the evaluation
|
local = { valid: true }; // make sure we don't fail the evaluation
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -392,12 +393,67 @@ exports.evaluate = function(expression, thread, locals, vars) {
|
|||||||
if (!JTYPES.isInteger(idx_local.type)) return reject_evaluation('TypeError: array index is not an integer value');
|
if (!JTYPES.isInteger(idx_local.type)) return reject_evaluation('TypeError: array index is not an integer value');
|
||||||
var idx = numberify(idx_local);
|
var idx = numberify(idx_local);
|
||||||
if (idx < 0 || idx >= arr_local.arraylen) return reject_evaluation(`BoundsError: array index (${idx}) out of bounds. Array length = ${arr_local.arraylen}`);
|
if (idx < 0 || idx >= arr_local.arraylen) return reject_evaluation(`BoundsError: array index (${idx}) out of bounds. Array length = ${arr_local.arraylen}`);
|
||||||
return this.dbgr.getarrayvalues(arr_local, idx, 1)
|
return dbgr.getarrayvalues(arr_local, idx, 1)
|
||||||
}.bind(this, arr_local))
|
}.bind(this, arr_local))
|
||||||
.then(els => els[0])
|
.then(els => els[0])
|
||||||
}
|
}
|
||||||
const evaluate_methodcall = (/*m, obj_local*/) => {
|
const evaluate_methodcall = (m, obj_local) => {
|
||||||
return reject_evaluation('Error: method calls are not supported');
|
// until we can figure out why method invokes with parameters crash the debugger, disallow parameterised calls
|
||||||
|
if (m.array_or_fncall.call.length)
|
||||||
|
return reject_evaluation('Error: method calls with parameter values are not supported');
|
||||||
|
|
||||||
|
// find any methods matching the member name with any parameters in the signature
|
||||||
|
return dbgr.findNamedMethods(obj_local.type.signature, m.member, /^/)
|
||||||
|
.then(methods => {
|
||||||
|
if (!methods[0])
|
||||||
|
return reject_evaluation(`Error: method '${m.member}()' not found`);
|
||||||
|
// evaluate any parameters (and wait for the results)
|
||||||
|
return $.when({methods},...m.array_or_fncall.call.map(evaluate_expression));
|
||||||
|
})
|
||||||
|
.then((x,...paramValues) => {
|
||||||
|
// filter the method based upon the types of parameters - note that null types and integer literals can match multiple types
|
||||||
|
paramValues = paramValues = paramValues.map(p => p[0]);
|
||||||
|
var matchers = paramValues.map(p => {
|
||||||
|
switch(true) {
|
||||||
|
case p.type.signature === 'I':
|
||||||
|
// match bytes/shorts/ints/longs/floats/doubles within range
|
||||||
|
if (p.value >= -128 && p.value <= 127) return /^[BSIJFD]$/
|
||||||
|
if (p.value >= -32768 && p.value <= 32767) return /^[SIJFD]$/
|
||||||
|
return /^[IJFD]$/;
|
||||||
|
case p.type.signature === 'F':
|
||||||
|
return /^[FD]$/;
|
||||||
|
case p.type.signature === 'Lnull;':
|
||||||
|
return /^[LT\[]/; // any reference type
|
||||||
|
default:
|
||||||
|
// anything else must be an exact signature match (for now - in reality we should allow subclassed type)
|
||||||
|
return new RegExp(`^${p.type.signature.replace(/[$]/g,x=>'\\'+x)}$`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var methods = x.methods.filter(m => {
|
||||||
|
// extract a list of parameter types
|
||||||
|
var paramtypere = /\[*([BSIJFDCZ]|([LT][^;]+;))/g;
|
||||||
|
for (var x, ptypes=[]; x = paramtypere.exec(m.sig); ) {
|
||||||
|
ptypes.push(x[0]);
|
||||||
|
}
|
||||||
|
// the last paramter type is the return value
|
||||||
|
ptypes.pop();
|
||||||
|
// check if they match
|
||||||
|
if (ptypes.length !== paramValues.length)
|
||||||
|
return;
|
||||||
|
return matchers.filter(m => {
|
||||||
|
return !m.test(ptypes.shift())
|
||||||
|
}).length === 0;
|
||||||
|
});
|
||||||
|
if (!methods[0])
|
||||||
|
return reject_evaluation(`Error: incompatible parameters for method '${m.member}'`);
|
||||||
|
// convert the parameters to exact debugger-compatible values
|
||||||
|
paramValues = paramValues.map(p => {
|
||||||
|
if (p.type.signature.length === 1)
|
||||||
|
return { type: p.type.typename, value: p.value};
|
||||||
|
return { type: 'oref', value: p.value };
|
||||||
|
})
|
||||||
|
return dbgr.invokeMethod(obj_local.value, thread.threadid, obj_local.type.signature, m.member, methods[0].genericsig || methods[0].sig, paramValues, {});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
const evaluate_member = (m, obj_local) => {
|
const evaluate_member = (m, obj_local) => {
|
||||||
if (!JTYPES.isReference(obj_local.type)) return reject_evaluation('TypeError: value is not a reference type');
|
if (!JTYPES.isReference(obj_local.type)) return reject_evaluation('TypeError: value is not a reference type');
|
||||||
@@ -408,15 +464,15 @@ exports.evaluate = function(expression, thread, locals, vars) {
|
|||||||
}
|
}
|
||||||
// length is a 'fake' field of arrays, so special-case it
|
// length is a 'fake' field of arrays, so special-case it
|
||||||
else if (JTYPES.isArray(obj_local.type) && m.member === 'length') {
|
else if (JTYPES.isArray(obj_local.type) && m.member === 'length') {
|
||||||
chain = evaluate_number(obj_local.arraylen);
|
chain = $.Deferred().resolve(evaluate_number(obj_local.arraylen));
|
||||||
}
|
}
|
||||||
// we also special-case :super (for object instances)
|
// we also special-case :super (for object instances)
|
||||||
else if (JTYPES.isObject(obj_local.type) && m.member === ':super') {
|
else if (JTYPES.isObject(obj_local.type) && m.member === ':super') {
|
||||||
chain = this.dbgr.getsuperinstance(obj_local);
|
chain = dbgr.getsuperinstance(obj_local);
|
||||||
}
|
}
|
||||||
// anything else must be a real field
|
// anything else must be a real field
|
||||||
else {
|
else {
|
||||||
chain = this.dbgr.getFieldValue(obj_local, m.member, true)
|
chain = dbgr.getFieldValue(obj_local, m.member, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
return chain.then(local => {
|
return chain.then(local => {
|
||||||
|
|||||||
Reference in New Issue
Block a user