mirror of
https://github.com/adelphes/android-dev-ext.git
synced 2025-12-22 17:39:19 +00:00
added support for setting variable values
This commit is contained in:
376
src/debugMain.js
376
src/debugMain.js
@@ -59,6 +59,11 @@ const NumberBaseConverter = {
|
|||||||
return negdigits.reverse().map(d => d.toString(base)).join('');
|
return negdigits.reverse().map(d => d.toString(base)).join('');
|
||||||
},
|
},
|
||||||
convertBase(str, fromBase, toBase) {
|
convertBase(str, fromBase, toBase) {
|
||||||
|
if (fromBase === 10 && /[eE]/.test(str)) {
|
||||||
|
// convert exponents to a string of zeros
|
||||||
|
var s = str.split(/[eE]/);
|
||||||
|
str = s[0] + '0'.repeat(parseInt(s[1],10)); // works for 0/+ve exponent,-ve throws
|
||||||
|
}
|
||||||
var digits = str.split('').map(d => parseInt(d,fromBase)).reverse();
|
var digits = str.split('').map(d => parseInt(d,fromBase)).reverse();
|
||||||
var outArray = [], power = [1];
|
var outArray = [], power = [1];
|
||||||
for (var i = 0; i < digits.length; i++) {
|
for (var i = 0; i < digits.length; i++) {
|
||||||
@@ -69,6 +74,26 @@ const NumberBaseConverter = {
|
|||||||
}
|
}
|
||||||
return outArray.reverse().map(d => d.toString(toBase)).join('');
|
return outArray.reverse().map(d => d.toString(toBase)).join('');
|
||||||
},
|
},
|
||||||
|
decToHex(decstr, minlen) {
|
||||||
|
var res, isneg = decstr[0] === '-';
|
||||||
|
if (isneg) decstr = decstr.slice(1)
|
||||||
|
decstr = decstr.match(/^0*(.+)$/)[1]; // strip leading zeros
|
||||||
|
if (decstr.length < 16 && !/[eE]/.test(decstr)) { // 16 = Math.pow(2,52).toString().length
|
||||||
|
// less than 52 bits - just use parseInt
|
||||||
|
res = parseInt(decstr, 10).toString(16);
|
||||||
|
} else {
|
||||||
|
res = NumberBaseConverter.convertBase(decstr, 10, 16);
|
||||||
|
}
|
||||||
|
if (isneg) {
|
||||||
|
res = NumberBaseConverter.twosComplement(res, 16);
|
||||||
|
if (/^[0-7]/.test(res)) res = 'f'+res; //msb must be set for -ve numbers
|
||||||
|
} else if (/^[^0-7]/.test(res))
|
||||||
|
res = '0' + res; // msb must not be set for +ve numbers
|
||||||
|
if (minlen && res.length < minlen) {
|
||||||
|
res = (isneg?'f':'0').repeat(minlen - res.length) + res;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
},
|
||||||
hexToDec(hexstr, signed) {
|
hexToDec(hexstr, signed) {
|
||||||
var res, isneg = /^[^0-7]/.test(hexstr);
|
var res, isneg = /^[^0-7]/.test(hexstr);
|
||||||
if (hexstr.match(/^0*(.+)$/)[1].length*4 < 52) {
|
if (hexstr.match(/^0*(.+)$/)[1].length*4 < 52) {
|
||||||
@@ -103,12 +128,28 @@ const JTYPES = {
|
|||||||
isReference(t) { return /^[L[]/.test(t.signature) },
|
isReference(t) { return /^[L[]/.test(t.signature) },
|
||||||
isPrimitive(t) { return !JTYPES.isReference(t.signature) },
|
isPrimitive(t) { return !JTYPES.isReference(t.signature) },
|
||||||
isInteger(t) { return /^[BIJS]$/.test(t.signature) },
|
isInteger(t) { return /^[BIJS]$/.test(t.signature) },
|
||||||
|
fromPrimSig(sig) { return JTYPES['byte,short,int,long,float,double,char,boolean'.split(',')['BSIJFDCZ'.indexOf(sig)]] },
|
||||||
}
|
}
|
||||||
|
|
||||||
function ensure_path_end_slash(p) {
|
function ensure_path_end_slash(p) {
|
||||||
return p + (/[\\/]$/.test(p) ? '' : path.sep);
|
return p + (/[\\/]$/.test(p) ? '' : path.sep);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function decode_char(c) {
|
||||||
|
switch(true) {
|
||||||
|
case /^\\[^u]$/.test(c):
|
||||||
|
// backslash escape
|
||||||
|
var x = {b:'\b',f:'\f',r:'\r',n:'\n',t:'\t',v:'\v','0':String.fromCharCode(0)}[c[1]];
|
||||||
|
return x || c[1];
|
||||||
|
case /^\\u[0-9a-fA-F]{4}$/.test(c):
|
||||||
|
// unicode escape
|
||||||
|
return String.fromCharCode(parseInt(c.slice(2),16));
|
||||||
|
case c.length===1 :
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
throw new Error('Invalid character value');
|
||||||
|
}
|
||||||
|
|
||||||
class AndroidDebugSession extends DebugSession {
|
class AndroidDebugSession extends DebugSession {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -174,6 +215,9 @@ class AndroidDebugSession extends DebugSession {
|
|||||||
{ label:'Uncaught Exceptions', filter:'uncaught', default:true },
|
{ label:'Uncaught Exceptions', filter:'uncaught', default:true },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// we support modifying variable values
|
||||||
|
response.body.supportsSetVariable = true;
|
||||||
|
|
||||||
this.sendResponse(response);
|
this.sendResponse(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -676,84 +720,81 @@ class AndroidDebugSession extends DebugSession {
|
|||||||
/**
|
/**
|
||||||
* Converts locals (or other vars) in debugger format into Variable objects used by VSCode
|
* Converts locals (or other vars) in debugger format into Variable objects used by VSCode
|
||||||
*/
|
*/
|
||||||
_locals_to_variables(vars) {
|
_local_to_variable(v) {
|
||||||
return vars.map(v => {
|
var varref = 0, objvalue, typename = v.type.package ? `${v.type.package}.${v.type.typename}` : v.type.typename;
|
||||||
var varref = 0, objvalue, typename = v.type.package ? `${v.type.package}.${v.type.typename}` : v.type.typename;
|
switch(true) {
|
||||||
switch(true) {
|
case v.hasnullvalue && JTYPES.isReference(v.type):
|
||||||
case v.hasnullvalue && JTYPES.isReference(v.type):
|
// null object or array type
|
||||||
// null object or array type
|
objvalue = 'null';
|
||||||
objvalue = 'null';
|
break;
|
||||||
break;
|
case v.type.signature === JTYPES.Object.signature:
|
||||||
case v.type.signature === JTYPES.Object.signature:
|
// Object doesn't really have anything worth seeing, so just treat it as unexpandable
|
||||||
// Object doesn't really have anything worth seeing, so just treat it as unexpandable
|
objvalue = v.type.typename;
|
||||||
objvalue = v.type.typename;
|
break;
|
||||||
break;
|
case v.type.signature === JTYPES.String.signature:
|
||||||
case v.type.signature === JTYPES.String.signature:
|
objvalue = JSON.stringify(v.string);
|
||||||
objvalue = JSON.stringify(v.string);
|
if (v.biglen) {
|
||||||
if (v.biglen) {
|
// since this is a big string - make it viewable on expand
|
||||||
// since this is a big string - make it viewable on expand
|
|
||||||
varref = ++this._nextObjVarRef;
|
|
||||||
this._variableHandles[varref] = {varref:varref, bigstring:v};
|
|
||||||
objvalue = `String (length:${v.biglen})`;
|
|
||||||
}
|
|
||||||
else if (this._expandable_prims) {
|
|
||||||
// as a courtesy, allow strings to be expanded to see their length
|
|
||||||
varref = ++this._nextObjVarRef;
|
|
||||||
this._variableHandles[varref] = {varref:varref, signature:v.type.signature, primitive:true, value:v.string.length};
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case JTYPES.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._nextObjVarRef;
|
|
||||||
this._variableHandles[varref] = { varref:varref, arrvar:v, range:[0,v.arraylen] };
|
|
||||||
}
|
|
||||||
objvalue = v.type.typename.replace(/]$/, v.arraylen+']'); // insert len as the final array bound
|
|
||||||
break;
|
|
||||||
case JTYPES.isObject(v.type):
|
|
||||||
// non-null object instance - add another variable reference so the user can expand
|
|
||||||
varref = ++this._nextObjVarRef;
|
varref = ++this._nextObjVarRef;
|
||||||
this._variableHandles[varref] = {varref:varref, objvar:v};
|
this._variableHandles[varref] = {varref:varref, bigstring:v};
|
||||||
objvalue = v.type.typename;
|
objvalue = `String (length:${v.biglen})`;
|
||||||
break;
|
}
|
||||||
case v.type.signature === 'C':
|
else if (this._expandable_prims) {
|
||||||
const cmap = {'\f':'f','\r':'r','\n':'n','\t':'t','\v':'v','\'':'\'','\\':'\\'}, cc = v.value.charCodeAt(0);
|
// as a courtesy, allow strings to be expanded to see their length
|
||||||
if (cmap[v.value]) {
|
varref = ++this._nextObjVarRef;
|
||||||
objvalue = `'\\${cmap[v.value]}'`;
|
this._variableHandles[varref] = {varref:varref, signature:v.type.signature, primitive:true, value:v.string.length};
|
||||||
} else if (cc < 32) {
|
}
|
||||||
objvalue = cc ? `'\\u${('000'+cc.toString(16)).slice(-4)}'` : "'\\0'";
|
break;
|
||||||
} else objvalue = `'${v.value}'`;
|
case JTYPES.isArray(v.type):
|
||||||
break;
|
// non-null array type - if it's not zero-length add another variable reference so the user can expand
|
||||||
case v.type.signature === 'J':
|
if (v.arraylen) {
|
||||||
// because JS cannot handle 64bit ints, we need a bit of extra work
|
varref = ++this._nextObjVarRef;
|
||||||
var v64hex = v.value.replace(/[^0-9a-fA-F]/g,'');
|
this._variableHandles[varref] = { varref:varref, arrvar:v, range:[0,v.arraylen] };
|
||||||
objvalue = NumberBaseConverter.hexToDec(v64hex, true);
|
}
|
||||||
break;
|
objvalue = v.type.typename.replace(/]$/, v.arraylen+']'); // insert len as the final array bound
|
||||||
default:
|
break;
|
||||||
// other primitives: int, boolean, etc
|
case JTYPES.isObject(v.type):
|
||||||
objvalue = v.value.toString();
|
// non-null object instance - add another variable reference so the user can expand
|
||||||
break;
|
|
||||||
}
|
|
||||||
// as a courtesy, allow integer and character values to be expanded to show the value in alternate bases
|
|
||||||
if (this._expandable_prims && /^[IJBSC]$/.test(v.type.signature)) {
|
|
||||||
varref = ++this._nextObjVarRef;
|
varref = ++this._nextObjVarRef;
|
||||||
this._variableHandles[varref] = {varref:varref, signature:v.type.signature, primitive:true, value:v.value};
|
this._variableHandles[varref] = {varref:varref, objvar:v};
|
||||||
}
|
objvalue = v.type.typename;
|
||||||
return {
|
break;
|
||||||
name: v.name,
|
case v.type.signature === 'C':
|
||||||
type: typename,
|
const cmap = {'\b':'b','\f':'f','\r':'r','\n':'n','\t':'t','\v':'v','\'':'\'','\\':'\\'}, cc = v.value.charCodeAt(0);
|
||||||
value: objvalue,
|
if (cmap[v.value]) {
|
||||||
variablesReference: varref,
|
objvalue = `'\\${cmap[v.value]}'`;
|
||||||
}
|
} else if (cc < 32) {
|
||||||
});
|
objvalue = cc ? `'\\u${('000'+cc.toString(16)).slice(-4)}'` : "'\\0'";
|
||||||
|
} else objvalue = `'${v.value}'`;
|
||||||
|
break;
|
||||||
|
case v.type.signature === 'J':
|
||||||
|
// because JS cannot handle 64bit ints, we need a bit of extra work
|
||||||
|
var v64hex = v.value.replace(/[^0-9a-fA-F]/g,'');
|
||||||
|
objvalue = NumberBaseConverter.hexToDec(v64hex, true);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// other primitives: int, boolean, etc
|
||||||
|
objvalue = v.value.toString();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// as a courtesy, allow integer and character values to be expanded to show the value in alternate bases
|
||||||
|
if (this._expandable_prims && /^[IJBSC]$/.test(v.type.signature)) {
|
||||||
|
varref = ++this._nextObjVarRef;
|
||||||
|
this._variableHandles[varref] = {varref:varref, signature:v.type.signature, primitive:true, value:v.value};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
name: v.name,
|
||||||
|
type: typename,
|
||||||
|
value: objvalue,
|
||||||
|
variablesReference: varref,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
variablesRequest(response/*: DebugProtocol.VariablesResponse*/, args/*: DebugProtocol.VariablesArguments*/) {
|
variablesRequest(response/*: DebugProtocol.VariablesResponse*/, args/*: DebugProtocol.VariablesArguments*/) {
|
||||||
|
|
||||||
const return_mapped_vars = (vars, response) => {
|
const return_mapped_vars = (vars, response) => {
|
||||||
response.body = {
|
response.body = {
|
||||||
variables: this._locals_to_variables(vars.filter(v => v.valid))
|
variables: vars.filter(v => v.valid).map(v => this._local_to_variable(v))
|
||||||
};
|
};
|
||||||
this.sendResponse(response);
|
this.sendResponse(response);
|
||||||
}
|
}
|
||||||
@@ -957,6 +998,175 @@ class AndroidDebugSession extends DebugSession {
|
|||||||
this.sendEvent(new StoppedEvent("exception", parseInt(e.throwlocation.threadid,16)));
|
this.sendEvent(new StoppedEvent("exception", parseInt(e.throwlocation.threadid,16)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setVariableRequest(response/*: DebugProtocol.SetVariableResponse*/, args/*: DebugProtocol.SetVariableArguments*/) {
|
||||||
|
const failSetVariableRequest = (response, reason) => {
|
||||||
|
response.success = false;
|
||||||
|
response.message = reason;
|
||||||
|
this.sendResponse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
var v = this._variableHandles[args.variablesReference];
|
||||||
|
if (!v || !v.cached) {
|
||||||
|
failSetVariableRequest(response, `Variable '${args.name}' not found`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var destvar = v.cached.find(v => v.name===args.name);
|
||||||
|
|
||||||
|
// be nice and remove any superfluous whitespace
|
||||||
|
var value = args.value.trim();
|
||||||
|
|
||||||
|
if (!args || !args.value) {
|
||||||
|
// just ignore blank requests
|
||||||
|
var vsvar = this._local_to_variable(destvar);
|
||||||
|
response.body = {
|
||||||
|
value: vsvar.value,
|
||||||
|
type: vsvar.type,
|
||||||
|
variablesReference: vsvar.variablesReference,
|
||||||
|
};
|
||||||
|
this.sendResponse(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// non-string reference types can only set to null
|
||||||
|
if (/^L/.test(destvar.type.signature) && destvar.type.signature !== JTYPES.String.signature) {
|
||||||
|
if (value !== 'null') {
|
||||||
|
failSetVariableRequest(response, 'Object references can only be set to null');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert the new value into a debugger-compatible object
|
||||||
|
var m, num, data, datadef;
|
||||||
|
switch(true) {
|
||||||
|
case value === 'null':
|
||||||
|
data = {valuetype:'oref',value:null}; // null object reference
|
||||||
|
break;
|
||||||
|
case /^(true|false)$/.test(value):
|
||||||
|
data = {valuetype:'boolean',value:value!=='false'}; // boolean literal
|
||||||
|
break;
|
||||||
|
case !!(m=value.match(/^[+-]?0x([0-9a-f]+)$/i)):
|
||||||
|
// hex integer- convert to decimal and fall through
|
||||||
|
if (m[1].length < 52/4)
|
||||||
|
value = parseInt(value, 16).toString(10);
|
||||||
|
else
|
||||||
|
value = NumberBaseConverter.hexToDec(value);
|
||||||
|
m=value.match(/^[+-]?[0-9]+([eE][+]?[0-9]+)?$/);
|
||||||
|
// fall-through
|
||||||
|
case !!(m=value.match(/^[+-]?[0-9]+([eE][+]?[0-9]+)?$/)):
|
||||||
|
// decimal integer
|
||||||
|
num = parseFloat(value, 10); // parseInt() can't handle exponents
|
||||||
|
switch(true) {
|
||||||
|
case (num >= -128 && num <= 127): data = {valuetype:'byte',value:num}; break;
|
||||||
|
case (num >= -32768 && num <= 32767): data = {valuetype:'short',value:num}; break;
|
||||||
|
case (num >= -2147483648 && num <= 2147483647): data = {valuetype:'int',value:num}; break;
|
||||||
|
case /inf/i.test(num): failSetVariableRequest(response,`Value '${args.value}' exceeds the maximum number range.`); return;
|
||||||
|
case /^[FD]$/.test(destvar.type.signature): data = {valuetype:'float',value:num}; break;
|
||||||
|
default:
|
||||||
|
// long (or larger) - need to use the arbitrary precision class
|
||||||
|
data = {valuetype:'long',value:NumberBaseConverter.decToHex(value, 16)};
|
||||||
|
switch(true){
|
||||||
|
case data.value.length > 16:
|
||||||
|
case num > 0 && data.value.length===16 && /[^0-7]/.test(data.value[0]):
|
||||||
|
// number exceeds signed 63 bit - make it a float
|
||||||
|
data = {valuetype:'float',value:num};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case !!(m=value.match(/^(Float|Double)\s*\.\s*(POSITIVE_INFINITY|NEGATIVE_INFINITY|NaN)$/)):
|
||||||
|
// Java special float constants
|
||||||
|
data = {valuetype:m[1].toLowerCase(),value:{POSITIVE_INFINITY:Infinity,NEGATIVE_INFINITY:-Infinity,NaN:NaN}[m[2]]};
|
||||||
|
break;
|
||||||
|
case !!(m=value.match(/^([+-])?infinity$/i)):// allow js infinity
|
||||||
|
data = {valuetype:'float',value:m[1]!=='-'?Infinity:-Infinity};
|
||||||
|
break;
|
||||||
|
case !!(m=value.match(/^nan$/i)): // allow js nan
|
||||||
|
data = {valuetype:'float',value:NaN};
|
||||||
|
break;
|
||||||
|
case !!(m=value.match(/^[+-]?[0-9]+[eE][-][0-9]+([dDfF])?$/)):
|
||||||
|
case !!(m=value.match(/^[+-]?[0-9]*\.[0-9]+(?:[eE][+-]?[0-9]+)?([dDfF])?$/)):
|
||||||
|
// decimal float
|
||||||
|
num = parseFloat(value);
|
||||||
|
data = {valuetype:/^[dD]$/.test(m[1]) ? 'double': 'float',value:num};
|
||||||
|
break;
|
||||||
|
case !!(m=value.match(/^'(?:\\u([0-9a-fA-F]{4})|\\([bfrntv0'])|(.))'$/)):
|
||||||
|
// character literal
|
||||||
|
var cvalue = m[1] ? String.fromCharCode(parseInt(m[1],16)) :
|
||||||
|
m[2] ? {b:'\b',f:'\f',r:'\r',n:'\n',t:'\t',v:'\v',0:'\0',"'":"'"}[m[2]]
|
||||||
|
: m[3]
|
||||||
|
data = {valuetype:'char',value:cvalue};
|
||||||
|
break;
|
||||||
|
case !!(m=value.match(/^"[^"\\\n]*(\\.[^"\\\n]*)*"$/)):
|
||||||
|
// string literal - we need to get the runtime to create a new string first
|
||||||
|
datadef = this.createJavaString(value).then(stringlit => ({valuetype:'oref', value:stringlit.value}));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// invalid literal
|
||||||
|
failSetVariableRequest(response, `'${args.value}' is not a valid literal value.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!datadef) {
|
||||||
|
// as a nicety, if the destination is a string, stringify any primitive value
|
||||||
|
if (data.valuetype !== 'oref' && destvar.type.signature === JTYPES.String.signature) {
|
||||||
|
datadef = this.createJavaString(data.value.toString(), {israw:true})
|
||||||
|
.then(stringlit => ({valuetype:'oref', value:stringlit.value}));
|
||||||
|
} else if (destvar.type.signature.length===1) {
|
||||||
|
// if the destination is a primitive, we need to range-check it here
|
||||||
|
// Neither our debugger nor the JDWP endpoint validates primitives, so we end up with
|
||||||
|
// weirdness if we allow primitives to be set with out-of-range values
|
||||||
|
var validmap = {
|
||||||
|
B:'byte,char', // char may not fit - we special-case this later
|
||||||
|
S:'byte,short,char',
|
||||||
|
I:'byte,short,int,char',
|
||||||
|
J:'byte,short,int,long,char',
|
||||||
|
F:'byte,short,int,long,char,float',
|
||||||
|
D:'byte,short,int,long,char,double,float',
|
||||||
|
C:'byte,short,char',Z:'boolean',
|
||||||
|
isCharInRangeForByte: c => c.charCodeAt(0) < 256,
|
||||||
|
};
|
||||||
|
var is_in_range = (validmap[destvar.type.signature]||'').indexOf(data.valuetype) >= 0;
|
||||||
|
if (destvar.type.signature === 'B' && data.valuetype === 'char')
|
||||||
|
is_in_range = validmap.isCharInRangeForByte(data.value);
|
||||||
|
if (!is_in_range) {
|
||||||
|
failSetVariableRequest(response, `Value '${args.value}' is not compatible with variable type: ${destvar.type.typename}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// check complete - make sure the type matches the destination and use a resolved deferred with the value
|
||||||
|
if (destvar.type.signature!=='C' && data.valuetype === 'char')
|
||||||
|
data.value = data.value.charCodeAt(0); // convert char to it's int value
|
||||||
|
if (destvar.type.signature==='J' && typeof data.value === 'number')
|
||||||
|
data.value = NumberBaseConverter.decToHex(''+data.value,16); // convert ints to hex-string longs
|
||||||
|
data.valuetype = destvar.type.typename;
|
||||||
|
|
||||||
|
datadef = $.Deferred().resolveWith(this,[data]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
datadef.then(data => {
|
||||||
|
// setxxxvalue sets the new value and then returns a new local for the variable
|
||||||
|
switch(destvar.vtype) {
|
||||||
|
case 'field': return this.dbgr.setfieldvalue(destvar, data);
|
||||||
|
case 'local': return this.dbgr.setlocalvalue(destvar, data);
|
||||||
|
default: throw new Error('Unsupported variable type');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(newlocalvar => {
|
||||||
|
Object.assign(destvar, newlocalvar);
|
||||||
|
var vsvar = this._local_to_variable(destvar);
|
||||||
|
response.body = {
|
||||||
|
value: vsvar.value,
|
||||||
|
type: vsvar.type,
|
||||||
|
variablesReference: vsvar.variablesReference,
|
||||||
|
};
|
||||||
|
this.sendResponse(response);
|
||||||
|
})
|
||||||
|
.fail(e => {
|
||||||
|
failSetVariableRequest(response, 'Variable update failed.');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called by VSCode to perform watch, console and hover evaluations
|
* Called by VSCode to perform watch, console and hover evaluations
|
||||||
*/
|
*/
|
||||||
@@ -1002,6 +1212,12 @@ class AndroidDebugSession extends DebugSession {
|
|||||||
this.doEvaluateRequest.apply(this, this._evals_queue[0]);
|
this.doEvaluateRequest.apply(this, this._evals_queue[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createJavaString(s, opts) {
|
||||||
|
const raw = (opts && opts.israw) ? s : s.slice(1,-1).replace(/\\u[0-9a-fA-F]{4}|\\./,decode_char);
|
||||||
|
// return a deferred, which resolves to a local variable named 'literal'
|
||||||
|
return this.dbgr.createstring(raw);
|
||||||
|
}
|
||||||
|
|
||||||
doEvaluateRequest(response, args) {
|
doEvaluateRequest(response, args) {
|
||||||
|
|
||||||
// just in case the user starts the app running again, before we've evaluated everything in the queue
|
// just in case the user starts the app running again, before we've evaluated everything in the queue
|
||||||
@@ -1045,7 +1261,7 @@ class AndroidDebugSession extends DebugSession {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
var parse_expression = function(e) {
|
var parse_expression = function(e) {
|
||||||
var root_term = e.expr.match(/^(?:(true(?![\w$]))|(false(?![\w$]))|(null(?![\w$]))|([a-zA-Z_$][a-zA-Z0-9_$]*)|(\d+(?:\.\d+)?)|('[^\\']')|('\\[frntv0]')|('\\u[0-9a-fA-F]{4}')|("[^"]*"))/);
|
var root_term = e.expr.match(/^(?:(true(?![\w$]))|(false(?![\w$]))|(null(?![\w$]))|([a-zA-Z_$][a-zA-Z0-9_$]*)|(\d+(?:\.\d+)?)|('[^\\']')|('\\[bfrntv0]')|('\\u[0-9a-fA-F]{4}')|("[^"]*"))/);
|
||||||
if (!root_term) return null;
|
if (!root_term) return null;
|
||||||
var res = {
|
var res = {
|
||||||
root_term: root_term[0],
|
root_term: root_term[0],
|
||||||
@@ -1068,15 +1284,6 @@ class AndroidDebugSession extends DebugSession {
|
|||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
const descape_char = (c) => {
|
|
||||||
if (c.length===2) {
|
|
||||||
// backslash escape
|
|
||||||
var x = {'f':'\f','r':'\r','n':'\n','t':'\t',v:'\v'}[c[1]];
|
|
||||||
return x || (c[1]==='0'?String.fromCharCode(0):c[1]);
|
|
||||||
}
|
|
||||||
// unicode escape
|
|
||||||
return String.fromCharCode(parseInt(c.slice(2,6),16));
|
|
||||||
}
|
|
||||||
var reject_evaluation = (msg) => $.Deferred().rejectWith(this, [new Error(msg)]);
|
var reject_evaluation = (msg) => $.Deferred().rejectWith(this, [new Error(msg)]);
|
||||||
var evaluate_number = (n) => {
|
var evaluate_number = (n) => {
|
||||||
const numtype = /\./.test(n) ? JTYPES.double : JTYPES.int;
|
const numtype = /\./.test(n) ? JTYPES.double : JTYPES.int;
|
||||||
@@ -1102,16 +1309,13 @@ class AndroidDebugSession extends DebugSession {
|
|||||||
local = evaluate_number(expr.root_term);
|
local = evaluate_number(expr.root_term);
|
||||||
break;
|
break;
|
||||||
case 'char':
|
case 'char':
|
||||||
local = expr.root_term[1]; // fall-through
|
|
||||||
case 'echar':
|
case 'echar':
|
||||||
case 'uchar':
|
case 'uchar':
|
||||||
!local && (local = descape_char(expr.root_term.slice(1,-1))); // fall-through
|
local = { vtype:'literal',name:'',hasnullvalue:false,type:JTYPES.char,value:decode_char(expr.root_term.slice(1,-1)),valid:true };
|
||||||
local = { vtype:'literal',name:'',hasnullvalue:false,type:JTYPES.char,value:local,valid:true };
|
|
||||||
break;
|
break;
|
||||||
case 'string':
|
case 'string':
|
||||||
const raw = expr.root_term.slice(1,-1).replace(/\\u[0-9a-fA-F]{4}|\\./,descape_char);
|
|
||||||
// we must get the runtime to create string instances
|
// we must get the runtime to create string instances
|
||||||
q = this.dbgr.createstring(raw);
|
q = this.createJavaString(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;
|
||||||
}
|
}
|
||||||
@@ -1173,7 +1377,7 @@ class AndroidDebugSession extends DebugSession {
|
|||||||
// the expression is well-formed - start the (asynchronous) evaluation
|
// the expression is well-formed - start the (asynchronous) evaluation
|
||||||
evaluate_expression(parsed_expression)
|
evaluate_expression(parsed_expression)
|
||||||
.then(function(response,local) {
|
.then(function(response,local) {
|
||||||
var v = this._locals_to_variables([local])[0];
|
var v = this._local_to_variable(local);
|
||||||
this.sendResponseAndDoNext(response, v.value, v.variablesReference);
|
this.sendResponseAndDoNext(response, v.value, v.variablesReference);
|
||||||
}.bind(this,response))
|
}.bind(this,response))
|
||||||
.fail(function(response,reason) {
|
.fail(function(response,reason) {
|
||||||
|
|||||||
@@ -396,7 +396,8 @@ function _JDWP() {
|
|||||||
res.push((i)&255);
|
res.push((i)&255);
|
||||||
},
|
},
|
||||||
encodeChar: function(res, c) {
|
encodeChar: function(res, c) {
|
||||||
this.encodeShort(res, c.charCodeAt(0));
|
// c can either be a 1 char string or an integer
|
||||||
|
this.encodeShort(res, typeof c === 'string' ? c.charCodeAt(0) : c);
|
||||||
},
|
},
|
||||||
encodeString : function(res, s) {
|
encodeString : function(res, s) {
|
||||||
var utf8bytes = getutf8bytes(s);
|
var utf8bytes = getutf8bytes(s);
|
||||||
@@ -405,6 +406,7 @@ function _JDWP() {
|
|||||||
res.push(utf8bytes[i]);
|
res.push(utf8bytes[i]);
|
||||||
},
|
},
|
||||||
encodeRef: function(res, ref) {
|
encodeRef: function(res, ref) {
|
||||||
|
if (ref === null) ref = this.nullRefValue();
|
||||||
for(var i=0; i < ref.length; i+=2) {
|
for(var i=0; i < ref.length; i+=2) {
|
||||||
res.push(parseInt(ref.substring(i,i+2), 16));
|
res.push(parseInt(ref.substring(i,i+2), 16));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user