mirror of
https://github.com/adelphes/android-dev-ext.git
synced 2025-12-22 17:39:19 +00:00
Version 1 (#83)
* replace jq-promises with native Promises * updates to use native promises and async await * Fix variable errors, remove extra parameters and correct export declaratons * refactor launch request to use async/await * fix running debugger on custom ADB port * remove unused files * move socket_ended check to ensure we don't loop reading 0 bytes * refactor logcat code and ensure disconnect status is passed on to webview * Fix warnings * Clean up util and remove unused functions * convert Debugger into a class * update jsconfig target to es2018 and enable checkJS * more updates to use async/await and more readable refactoring. - added type definitions and debugger classes - improved expression evaluation - refactored expressions into parsing, evaluation and variable assignment - fixed invoking methods with parameters - added support for static method invokes - improved exception display reliability - refactored launch into smaller functions - refactored utils into smaller modules - removed redundant code - converted JDWP functions to classes * set version 1.0.0 and update dependencies * add changelog notes
This commit is contained in:
276
src/stack-frame.js
Normal file
276
src/stack-frame.js
Normal file
@@ -0,0 +1,276 @@
|
||||
const { Debugger } = require('./debugger');
|
||||
const { DebuggerFrameInfo, DebuggerValue, JavaType, LiteralValue, VariableValue } = require('./debugger-types');
|
||||
const { assignVariable } = require('./expression/assign');
|
||||
const { NumberBaseConverter } = require('./utils/nbc');
|
||||
const { VariableManager } = require('./variable-manager');
|
||||
|
||||
/**
|
||||
* @param {DebuggerValue[]} variables
|
||||
* @param {boolean} thisFirst
|
||||
* @param {boolean} allCapsLast
|
||||
*/
|
||||
function sortVariables(variables, thisFirst, allCapsLast) {
|
||||
return variables.sort((a,b) => {
|
||||
if (a.name === b.name) return 0;
|
||||
if (thisFirst) {
|
||||
if (a.name === 'this') return -1;
|
||||
if (b.name === 'this') return +1;
|
||||
}
|
||||
if (allCapsLast) {
|
||||
const acaps = !/[a-z]/.test(a.name);
|
||||
const bcaps = !/[a-z]/.test(b.name);
|
||||
if (acaps !== bcaps) {
|
||||
return acaps ? +1 : -1;
|
||||
}
|
||||
}
|
||||
return a.name.localeCompare(b.name, undefined, { sensitivity: 'base' });
|
||||
});
|
||||
}
|
||||
|
||||
class DebuggerStackFrame extends VariableManager {
|
||||
|
||||
/**
|
||||
* @param {Debugger} dbgr
|
||||
* @param {DebuggerFrameInfo} frame
|
||||
* @param {VSCVariableReference} frame_variable_reference
|
||||
*/
|
||||
constructor(dbgr, frame, frame_variable_reference) {
|
||||
super(frame_variable_reference );
|
||||
this.variableReference = frame_variable_reference;
|
||||
this.dbgr = dbgr;
|
||||
this.frame = frame;
|
||||
/** @type {DebuggerValue[]} */
|
||||
this.locals = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of local values for this stack frame
|
||||
* @returns {Promise<DebuggerValue[]>}
|
||||
*/
|
||||
async getLocals() {
|
||||
if (this.locals) {
|
||||
return this.locals;
|
||||
}
|
||||
const fetch_locals = async () => {
|
||||
const values = await this.dbgr.getLocals(this.frame);
|
||||
// display the variables in (case-insensitive) alphabetical order, with 'this' first and all-caps last
|
||||
return this.locals = sortVariables(values, true, false);
|
||||
}
|
||||
// @ts-ignore
|
||||
return this.locals = fetch_locals();
|
||||
}
|
||||
|
||||
async getLocalVariables() {
|
||||
const values = await this.getLocals();
|
||||
return values.map(value => this.makeVariableValue(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {VSCVariableReference} variablesReference
|
||||
* @param {string} name
|
||||
* @param {DebuggerValue} value
|
||||
*/
|
||||
async setVariableValue(variablesReference, name, value) {
|
||||
|
||||
/** @type {DebuggerValue[]} */
|
||||
let variables;
|
||||
if (variablesReference === this.variableReference) {
|
||||
variables = this.locals;
|
||||
} else {
|
||||
const varinfo = this.variableValues.get(variablesReference);
|
||||
if (!varinfo || !varinfo.cached) {
|
||||
throw new Error(`Variable '${name}' not found`);
|
||||
}
|
||||
variables = varinfo.cached;
|
||||
}
|
||||
|
||||
const var_idx = variables.findIndex(v => v.name === name);
|
||||
|
||||
try {
|
||||
const updated_value = await assignVariable(this.dbgr, variables[var_idx], name, value);
|
||||
variables[var_idx] = updated_value;
|
||||
return this.makeVariableValue(updated_value);
|
||||
} catch(e) {
|
||||
throw new Error(`Variable update failed. ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {VSCVariableReference} variablesReference
|
||||
* @returns {Promise<VariableValue[]>}
|
||||
*/
|
||||
async getExpandableValues(variablesReference) {
|
||||
const varinfo = this.variableValues.get(variablesReference);
|
||||
if (!varinfo) {
|
||||
return [];
|
||||
}
|
||||
if (varinfo.cached) {
|
||||
// return the cached version
|
||||
return varinfo.cached.map(v => this.makeVariableValue(v));
|
||||
}
|
||||
if (varinfo.primitive) {
|
||||
// convert the primitive value into alternate formats
|
||||
return this.getPrimitive(varinfo);
|
||||
}
|
||||
|
||||
/** @type {DebuggerValue[]} */
|
||||
let values = [];
|
||||
if (varinfo.objvar) {
|
||||
// object fields request
|
||||
values = sortVariables(await this.getObjectFields(varinfo), false, true);
|
||||
}
|
||||
else if (varinfo.arrvar) {
|
||||
// array elements request
|
||||
const arr = await this.getArrayElements(varinfo);
|
||||
if (arr.isSubrange) {
|
||||
// @ts-ignore
|
||||
return arr.values;
|
||||
}
|
||||
// @ts-ignore
|
||||
values = arr.values;
|
||||
}
|
||||
else if (varinfo.bigstring) {
|
||||
values = [await this.getBigString(varinfo)];
|
||||
}
|
||||
|
||||
return (varinfo.cached = values).map(v => this.makeVariableValue(v));
|
||||
}
|
||||
|
||||
async getObjectFields(varinfo) {
|
||||
const supertype = await this.dbgr.getSuperType(varinfo.objvar);
|
||||
const fields = await this.dbgr.getFieldValues(varinfo.objvar);
|
||||
// add an extra msg field for exceptions
|
||||
if (varinfo.exception) {
|
||||
const call = await this.dbgr.invokeToString(varinfo.objvar.value, varinfo.threadid, varinfo.objvar.type.signature);
|
||||
call.name = ":message";
|
||||
fields.unshift(call);
|
||||
}
|
||||
// add a ":super" member, unless the super is Object
|
||||
if (supertype && supertype.signature !== JavaType.Object.signature) {
|
||||
fields.unshift(new DebuggerValue('super', supertype, varinfo.objvar.value, true, false, ':super', null));
|
||||
}
|
||||
return fields;
|
||||
}
|
||||
|
||||
async getArrayElements(varinfo) {
|
||||
const range = varinfo.range,
|
||||
count = range[1] - range[0];
|
||||
// should always have a +ve count, but just in case...
|
||||
if (count <= 0) {
|
||||
return null;
|
||||
}
|
||||
// counts over 110 are shown as subranges
|
||||
if (count > 110) {
|
||||
return {
|
||||
isSubrange: true,
|
||||
values: this.getArraySubrange(varinfo.arrvar, count, range),
|
||||
};
|
||||
}
|
||||
// get the elements for the specified range
|
||||
const elements = await this.dbgr.getArrayElementValues(varinfo.arrvar, range[0], count);
|
||||
return {
|
||||
isSubrange: false,
|
||||
values: elements,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} arrvar
|
||||
* @param {number} count
|
||||
* @param {[number,number]} range
|
||||
*/
|
||||
getArraySubrange(arrvar, count, range) {
|
||||
// create subranges in the sub-power of 10
|
||||
const subrangelen = Math.max(Math.pow(10, (Math.log10(count)|0)-1),100);
|
||||
/** @type {VariableValue[]} */
|
||||
const variables = [];
|
||||
|
||||
for (let i = range[0]; i < range[1]; i+= subrangelen) {
|
||||
const varinfo = {
|
||||
varref: 0,
|
||||
arrvar,
|
||||
range: [i, Math.min(i+subrangelen, range[1])],
|
||||
};
|
||||
const varref = this._addVariable(varinfo);
|
||||
const variable = new VariableValue(`[${varinfo.range[0]}..${varinfo.range[1]-1}]`, '', null, varref, '');
|
||||
variables.push(variable);
|
||||
}
|
||||
|
||||
return variables;
|
||||
}
|
||||
|
||||
async getBigString(varinfo) {
|
||||
const string = await this.dbgr.getStringText(varinfo.bigstring.value);
|
||||
const res = new LiteralValue(JavaType.String, string);
|
||||
res.name = '<value>';
|
||||
res.string = string;
|
||||
return res;
|
||||
}
|
||||
|
||||
getPrimitive(varinfo) {
|
||||
/** @type {VariableValue[]} */
|
||||
const variables = [];
|
||||
const bits = {
|
||||
J:64,
|
||||
I:32,
|
||||
S:16,
|
||||
B:8,
|
||||
}[varinfo.signature];
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number|hex64} n
|
||||
* @param {number} base
|
||||
* @param {number} len
|
||||
*/
|
||||
function convert(n, base, len) {
|
||||
let converted;
|
||||
if (typeof n === 'string') {
|
||||
converted = {
|
||||
2: () => n.replace(/./g, c => parseInt(c,16).toString(2)),
|
||||
10: () => NumberBaseConverter.hexToDec(n, false),
|
||||
16: () => n,
|
||||
}[base]();
|
||||
} else {
|
||||
converted = n.toString(base);
|
||||
}
|
||||
return converted.padStart(len, '0');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number|hex64} u
|
||||
* @param {8|16|32|64} bits
|
||||
*/
|
||||
function getIntFormats(u, bits) {
|
||||
const bases = [2, 10, 16];
|
||||
const min_lengths = [bits, 1, bits/4];
|
||||
const base_names = ['<binary>', '<decimal>', '<hex>'];
|
||||
return base_names.map((name, i) => new VariableValue(name, convert(u, bases[i], min_lengths[i])));
|
||||
}
|
||||
|
||||
switch(varinfo.signature) {
|
||||
case 'Ljava/lang/String;':
|
||||
variables.push(new VariableValue('<length>', varinfo.value.toString()));
|
||||
break;
|
||||
case 'C':
|
||||
variables.push(new VariableValue('<charCode>', varinfo.value.charCodeAt(0).toString()));
|
||||
break;
|
||||
case 'J':
|
||||
// because JS cannot handle 64bit ints, we need a bit of extra work
|
||||
const v64hex = varinfo.value.replace(/[^0-9a-fA-F]/g,'');
|
||||
variables.push(...getIntFormats(v64hex, 64));
|
||||
break;
|
||||
default:// integer/short/byte value
|
||||
const u = varinfo.value >>> 0;
|
||||
variables.push(...getIntFormats(u, bits));
|
||||
break;
|
||||
}
|
||||
return variables;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
DebuggerStackFrame,
|
||||
}
|
||||
Reference in New Issue
Block a user