mirror of
https://github.com/adelphes/android-dev-ext.git
synced 2025-12-23 01:48:18 +00:00
* initial support for format specifier * add readme notes for format specifiers * add support for showing arrays and objects with format specifiers * create unique object variable references for different display formats * add notes on applying formatting to objects and arrays
243 lines
8.0 KiB
JavaScript
243 lines
8.0 KiB
JavaScript
const { DebuggerValue, JavaType, VariableValue } = require('./debugger-types');
|
|
const { NumberBaseConverter } = require('./utils/nbc');
|
|
|
|
/**
|
|
* Class to manage variable references used by VS code.
|
|
*
|
|
* This class is primarily used to manage references to variables created in stack frames, but is
|
|
* also used in 'standalone' mode for repl expressions evaluated in the global context.
|
|
*/
|
|
class VariableManager {
|
|
/**
|
|
* @param {VSCVariableReference} base_variable_reference The reference value for values stored by this manager
|
|
*/
|
|
constructor(base_variable_reference) {
|
|
|
|
/** @type {VSCVariableReference} */
|
|
this.nextVariableRef = base_variable_reference + 10;
|
|
|
|
/** @type {Map<VSCVariableReference,*>} */
|
|
this.variableValues = new Map();
|
|
|
|
/** @type {Map<JavaObjectID,VSCVariableReference>} */
|
|
this.objIdCache = new Map();
|
|
}
|
|
|
|
_addVariable(varinfo) {
|
|
varinfo.varref = this.nextVariableRef += 1;
|
|
this._setVariable(varinfo.varref, varinfo)
|
|
return varinfo.varref;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {VSCVariableReference} variablesReference
|
|
* @param {*} value
|
|
*/
|
|
_setVariable(variablesReference, value) {
|
|
this.variableValues.set(variablesReference, value);
|
|
}
|
|
|
|
/**
|
|
* Retrieve or create a variable reference for a given object instance
|
|
* @param {JavaType} type
|
|
* @param {JavaObjectID} instance_id
|
|
* @param {string} display_format
|
|
*/
|
|
_getObjectIdReference(type, instance_id, display_format) {
|
|
// we need the type signature because we must have different id's for
|
|
// an instance and it's supertype instance (which obviously have the same instance_id)
|
|
//
|
|
// display_format is also included to give unique variable references for each display type.
|
|
// This is because VSCode caches expanded values, so once evaluated in one format, they can
|
|
// never be changed.
|
|
const key = `${type.signature}:${instance_id}:${display_format || ''}`;
|
|
let value = this.objIdCache.get(key);
|
|
if (!value) {
|
|
this.objIdCache.set(key, value = this.nextVariableRef += 1);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
/**
|
|
* Convert to a VariableValue object used by VSCode
|
|
* @param {DebuggerValue} v
|
|
* @param {string} [display_format]
|
|
*/
|
|
makeVariableValue(v, display_format) {
|
|
let varref = 0;
|
|
let value = '';
|
|
const evaluateName = v.fqname || v.name;
|
|
const full_typename = v.type.fullyQualifiedName();
|
|
switch(true) {
|
|
case v.hasnullvalue && JavaType.isReference(v.type):
|
|
// null object or array type
|
|
value = 'null';
|
|
break;
|
|
case v.vtype === 'class':
|
|
value = full_typename;
|
|
break;
|
|
case v.type.signature === JavaType.Object.signature:
|
|
// Object doesn't really have anything worth seeing, so just treat it as unexpandable
|
|
value = v.type.typename;
|
|
break;
|
|
case v.type.signature === JavaType.String.signature:
|
|
if (v.biglen) {
|
|
// since this is a big string - make it viewable on expand
|
|
varref = this._addVariable({
|
|
bigstring: v,
|
|
});
|
|
value = `String (length:${v.biglen})`;
|
|
} else {
|
|
value = formatString(v.string, display_format);
|
|
}
|
|
break;
|
|
case JavaType.isArray(v.type):
|
|
// non-null array type - if it's not zero-length add another variable reference so the user can expand
|
|
if (v.arraylen) {
|
|
varref = this._getObjectIdReference(v.type, v.value, display_format);
|
|
this._setVariable(varref, {
|
|
varref,
|
|
arrvar: v,
|
|
range:[0, v.arraylen],
|
|
display_format,
|
|
});
|
|
}
|
|
value = v.type.typename.replace(/]/, v.arraylen+']'); // insert len as the first array bound
|
|
break;
|
|
case JavaType.isClass(v.type):
|
|
// non-null object instance - add another variable reference so the user can expand
|
|
varref = this._getObjectIdReference(v.type, v.value, display_format);
|
|
this._setVariable(varref, {
|
|
varref,
|
|
objvar: v,
|
|
display_format,
|
|
});
|
|
value = v.type.typename;
|
|
break;
|
|
case v.type.signature === JavaType.char.signature:
|
|
// character types have a integer value
|
|
value = formatChar(v.value, display_format);
|
|
break;
|
|
case v.type.signature === JavaType.long.signature:
|
|
// because JS cannot handle 64bit ints, we need a bit of extra work
|
|
const v64hex = v.value.replace(/[^0-9a-fA-F]/g,'');
|
|
value = formatLong(v64hex, display_format);
|
|
break;
|
|
case JavaType.isInteger(v.type):
|
|
value = formatInteger(v.value, v.type.signature, display_format);
|
|
break;
|
|
default:
|
|
// other primitives: boolean, etc
|
|
value = v.value.toString();
|
|
break;
|
|
}
|
|
|
|
return new VariableValue(v.name, value, full_typename, varref, evaluateName);
|
|
}
|
|
}
|
|
|
|
const cmap = {
|
|
'\b':'b','\f':'f','\r':'r','\n':'n','\t':'t',
|
|
'\v':'v','\'':'\'','\\':'\\','\0':'0'
|
|
};
|
|
|
|
function makeJavaChar(i) {
|
|
let value;
|
|
const char = String.fromCodePoint(i);
|
|
if (cmap[char]) {
|
|
value = `'\\${cmap[char]}'`;
|
|
} else if (i < 32) {
|
|
value = `'\\u${i.toString(16).padStart(4,'0')}'`;
|
|
} else value = `'${char}'`;
|
|
return value;
|
|
}
|
|
|
|
/**
|
|
* @param {number} c
|
|
* @param {string} df
|
|
*/
|
|
function formatChar(c, df) {
|
|
if (/[xX]b|o|bb|d/.test(df)) {
|
|
return formatInteger(c, 'C', df);
|
|
}
|
|
return makeJavaChar(c);
|
|
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {string} s
|
|
* @param {string} display_format
|
|
*/
|
|
function formatString(s, display_format) {
|
|
if (display_format === '!') {
|
|
return s;
|
|
}
|
|
let value = JSON.stringify(s);
|
|
if (display_format === 'sb') {
|
|
// remove quotes
|
|
value = value.slice(1,-1);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
/**
|
|
* @param {hex64} hex64
|
|
* @param {string} df
|
|
*/
|
|
function formatLong(hex64, df) {
|
|
let minlength;
|
|
if (/[xX]b?/.test(df)) {
|
|
minlength = Math.ceil(64 / 4);
|
|
let s = `${df[1]?'':'0x'}${hex64.padStart(minlength,'0')}`;
|
|
return df[0] === 'x' ? s.toLowerCase() : s.toUpperCase();
|
|
}
|
|
if (/o/.test(df)) {
|
|
minlength = Math.ceil(64 / 3);
|
|
return `${df[1]?'':'0'}${NumberBaseConverter.convertBase(hex64,16,8).padStart(minlength, '0')}`;
|
|
}
|
|
if (/bb?/.test(df)) {
|
|
minlength = 64;
|
|
return `${df[1]?'':'0b'}${NumberBaseConverter.convertBase(hex64,16,2).padStart(minlength, '0')}`;
|
|
}
|
|
if (/c/.test(df)) {
|
|
return makeJavaChar(parseInt(hex64.slice(-4), 16));
|
|
}
|
|
return NumberBaseConverter.convertBase(hex64, 16, 10);
|
|
}
|
|
|
|
/**
|
|
* @param {number} i
|
|
* @param {string} signature
|
|
* @param {string} df
|
|
*/
|
|
function formatInteger(i, signature, df) {
|
|
const bits = { B:8,S:16,I:32,C:16 }[signature];
|
|
let u = (i & (-1 >>> (32 - bits))) >>> 0;
|
|
let minlength;
|
|
if (/[xX]b?/.test(df)) {
|
|
minlength = Math.ceil(bits / 4);
|
|
let s = u.toString(16).padStart(minlength,'0');
|
|
s = df[0] === 'x' ? s.toLowerCase() : s.toUpperCase();
|
|
return `${df[1]?'':'0x'}${s}`;
|
|
}
|
|
if (/o/.test(df)) {
|
|
minlength = Math.ceil(bits / 3);
|
|
return `${df[1]?'':'0'}${u.toString(8).padStart(minlength, '0')}`;
|
|
}
|
|
if (/bb?/.test(df)) {
|
|
minlength = bits;
|
|
return `${df[1]?'':'0b'}${u.toString(2).padStart(minlength, '0')}`;
|
|
}
|
|
if (/c/.test(df)) {
|
|
minlength = bits;
|
|
return makeJavaChar(u & 0xffff);
|
|
}
|
|
return i.toString();
|
|
}
|
|
|
|
module.exports = {
|
|
VariableManager,
|
|
}
|