mirror of
https://github.com/adelphes/android-dev-ext.git
synced 2025-12-22 09:29:38 +00:00
* upgrade package-lock.jsons * upgrade debugadapter package * upgrade debugprotocol package * upgrade long package * upgrade unzipper package * upgrade uuid package * upgrade ws package * upgrade xpath package * update dev dependencies * fix eslint config to allow newer language features (async shorthand functions and optional catch parameters) * fix import type declarations * update eslint * remove unsupported stopOnEntry properties * code tidy - fix warnings, separate type imports from value imports, remove unused code * report stack on adb connection error and default host name to 127.0.0.1 * fix imported types in jdwp * lang server tidyups * add a new helper for creating android API library cache file * update the android API cache file to 34 * bump to version 1.4.0
281 lines
9.2 KiB
JavaScript
281 lines
9.2 KiB
JavaScript
const { DebuggerValue, JavaType, LiteralValue, VariableValue } = require('./debugger-types');
|
|
const { assignVariable } = require('./expression/assign');
|
|
const { NumberBaseConverter } = require('./utils/nbc');
|
|
const { VariableManager } = require('./variable-manager');
|
|
|
|
/**
|
|
* @typedef {import('./debugger').Debugger} Debugger
|
|
* @typedef {import('./debugger-types').DebuggerFrameInfo} DebuggerFrameInfo
|
|
*/
|
|
|
|
/**
|
|
* @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
|
|
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, varinfo.display_format));
|
|
}
|
|
|
|
async getObjectFields(varinfo) {
|
|
const supertype = await this.dbgr.getSuperType(varinfo.objvar.type.signature);
|
|
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,
|
|
}
|