src/SmaliVM.js
'use strict';
const CLASS = require("./CoreClass.js");
const CONST = require("./CoreConst.js");
const OPCODE = require('./Opcode.js').OPCODE;
const Util = require('./Utils.js');
var Logger = require("./Logger.js")();
const SINGLE_MODE = 0x1;
const PLURAL_MODE = 0x2;
const RET_VOID = 0x100;
const CR = ""; //\n";k
const METH_INVOKE_SIGNATURE = "java.lang.reflect.Method.invoke(<java.lang.Object><java.lang.Object>[])<java.lang.Object>";
const VTYPE = {
METH: 0x1
};
// modif : isImm
const DTYPE = {
IMM_STRING: 0x1,
IMM_NUMERIC: 0x2,
IMM_FLOAT: 0x3,
IMM_BOOLEAN: 0x4,
IMM_BYTE: 0x5,
IMM_CHAR: 0x6,
IMM_SHORT: 0x7,
IMM_DOUBLE: 0x8,
IMM_LONG: 0x9,
OBJECT_REF: 0x20,
CLASS_REF: 0x21,
FIELD_REF: 0x22,
THIS: 0x23,
ARRAY: 0x24,
FIELD: 0x25,
INSTANCE: 0x26,
WRAPPED_HOOK_RESULT: 0x2a,
VOID: 0x30,
UNDEFINED: 0x31,
};
const DTYPE_STRING = {};
DTYPE_STRING[DTYPE.IMM_STRING] = "String";
DTYPE_STRING[DTYPE.IMM_NUMERIC] = "int|long|short";
DTYPE_STRING[DTYPE.IMM_FLOAT] = "float|double";
DTYPE_STRING[DTYPE.IMM_BOOLEAN] = "boolean";
DTYPE_STRING[DTYPE.IMM_BYTE] = "byte";
DTYPE_STRING[DTYPE.IMM_LONG] = "long";
DTYPE_STRING[DTYPE.IMM_DOUBLE] = "double";
DTYPE_STRING[DTYPE.IMM_SHORT] = "short";
DTYPE_STRING[DTYPE.OBJECT_REF] = "ObjectReference";
DTYPE_STRING[DTYPE.CLASS_REF] = "ClassReference";
DTYPE_STRING[DTYPE.FIELD_REF] = "FieldReference";
DTYPE_STRING[DTYPE.VOID] = "void";
DTYPE_STRING[DTYPE.UNDEFINED] = "NotInitialized";
DTYPE_STRING[DTYPE.THIS] = "this";
DTYPE_STRING[DTYPE.IMM_CHAR] = "char";
DTYPE_STRING[DTYPE.ARRAY] = "Array";
DTYPE_STRING[DTYPE.FIELD] = "Field";
DTYPE_STRING[DTYPE.INSTANCE] = "ClassInstance";
DTYPE_STRING[DTYPE.WRAPPED_HOOK_RESULT] = "WrappedHookResult";
const BTYPE_DTYPE = {};
BTYPE_DTYPE[CONST.JAVA.T_BOOL] = DTYPE.IMM_BOOLEAN;
BTYPE_DTYPE[CONST.JAVA.T_CHAR] = DTYPE.IMM_CHAR;
BTYPE_DTYPE[CONST.JAVA.T_INT] = DTYPE.IMM_NUMERIC;
BTYPE_DTYPE[CONST.JAVA.T_LONG] = DTYPE.IMM_LONG;
BTYPE_DTYPE[CONST.JAVA.T_SHORT] = DTYPE.IMM_SHORT;
BTYPE_DTYPE[CONST.JAVA.T_BYTE] = DTYPE.IMM_BYTE;
BTYPE_DTYPE[CONST.JAVA.T_FLOAT] = DTYPE.IMM_FLOAT;
BTYPE_DTYPE[CONST.JAVA.T_DOUBLE] = DTYPE.IMM_DOUBLE;
BTYPE_DTYPE[CONST.JAVA.T_OBJ] = DTYPE.OBJECT_REF;
BTYPE_DTYPE[CONST.JAVA.T_VOID] = DTYPE.VOID;
const ATYPE_DTYPE = {};
ATYPE_DTYPE[OPCODE.AGET.byte] = DTYPE.IMM_NUMERIC;
ATYPE_DTYPE[OPCODE.AGET_BOOLEAN.byte] = DTYPE.IMM_BOOLEAN;
ATYPE_DTYPE[OPCODE.AGET_BYTE.byte] = DTYPE.IMM_BYTE;
ATYPE_DTYPE[OPCODE.AGET_CHAR.byte] = DTYPE.IMM_CHAR;
ATYPE_DTYPE[OPCODE.AGET_OBJECT.byte] = DTYPE.OBJECT_REF;
ATYPE_DTYPE[OPCODE.AGET_SHORT.byte] = DTYPE.IMM_SHORT;
ATYPE_DTYPE[OPCODE.AGET_WIDE.byte] = DTYPE.IMM_LONG;
ATYPE_DTYPE[OPCODE.APUT.byte] = DTYPE.IMM_NUMERIC;
ATYPE_DTYPE[OPCODE.APUT_BOOLEAN.byte] = DTYPE.IMM_BOOLEAN;
ATYPE_DTYPE[OPCODE.APUT_BYTE.byte] = DTYPE.IMM_BYTE;
ATYPE_DTYPE[OPCODE.APUT_CHAR.byte] = DTYPE.IMM_CHAR;
ATYPE_DTYPE[OPCODE.APUT_OBJECT.byte] = DTYPE.OBJECT_REF;
ATYPE_DTYPE[OPCODE.APUT_SHORT.byte] = DTYPE.IMM_SHORT;
ATYPE_DTYPE[OPCODE.APUT_WIDE.byte] = DTYPE.IMM_LONG;
const SYMBOL_OPE = {};
SYMBOL_OPE[CONST.LEX.TOKEN.ADD] = 'add';
SYMBOL_OPE[CONST.LEX.TOKEN.SUB] = 'sub';
SYMBOL_OPE[CONST.LEX.TOKEN.MUL] = 'mul';
SYMBOL_OPE[CONST.LEX.TOKEN.DIV] = 'div';
SYMBOL_OPE[CONST.LEX.TOKEN.REM] = 'rem';
SYMBOL_OPE[CONST.LEX.TOKEN.NOT] = 'not';
SYMBOL_OPE[CONST.LEX.TOKEN.OR] = 'or';
SYMBOL_OPE[CONST.LEX.TOKEN.AND] = 'and';
SYMBOL_OPE[CONST.LEX.TOKEN.XOR] = 'xor';
SYMBOL_OPE[CONST.LEX.TOKEN.SHR] = 'shr';
SYMBOL_OPE[CONST.LEX.TOKEN.SHL] = 'shl';
SYMBOL_OPE[CONST.LEX.TOKEN.USHR] = 'ushr';
function getDataTypeOf(pType){
//Logger.debug("getDataTypeOf: ",pType);
if(pType instanceof CLASS.ObjectType){
if(pType.isArray())
return DTYPE.ARRAY;
else
return DTYPE.OBJECT_REF;
}
else if(pType instanceof VM_ClassInstance){
return DTYPE.OBJECT_REF;
}
else if(pType instanceof CLASS.BasicType){
if(pType.isArray !=undefined && pType.isArray())
return DTYPE.ARRAY;
else
return BTYPE_DTYPE[pType.name];
}
// basic type
else{
return BTYPE_DTYPE[pType.name];
}
}
function castToDataType(pType, pData){
switch(pType){
case DTYPE.IMM_BYTE:
case DTYPE.IMM_LONG:
case DTYPE.IMM_SHORT:
case DTYPE.IMM_NUMERIC:
return parseInt(pData,10);
case DTYPE.IMM_DOUBLE:
case DTYPE.IMM_FLOAT:
return parseFloat(pData);
case DTYPE.IMM_STRING:
case DTYPE.IMM_CHAR:
return pData+"";
default:
return pData;
}
}
/**
*
*/
class VM_Exception extends Error
{
constructor( pCode, pMessage){
super(pMessage);
// Ensure the name of this error is the same as the class name
this.name = this.constructor.name;
this.code = pCode;
Error.captureStackTrace(this, this.constructor);
}
}
class Symbol
{
static SKIPPED = true;
constructor(pVisibility, pType, pValue, pCode=null, pSkipped=false){
this.type = pType;
this.value = pValue;
this.visibility = pVisibility;
this.code = pCode;
this.regs = [];
this.symOffset = false;
this.skipped = pSkipped;
}
print(){
if(this.value instanceof VM_ClassInstance){
return `type:${DTYPE_STRING[this.type]}, value:(ClassInstance)${this.value.parent.name}, code:${this.code}`;
}
else if(this.value instanceof CLASS.Class){
return `type:${DTYPE_STRING[this.type]}, value:${this.value.name}, code:${this.code}`;
}
else if(this.value instanceof VM_VirtualArray){
return `type:${DTYPE_STRING[this.type]}, value:${this.value.print()}, code:${this.code}`;
}
else if(this.value != null){
switch(this.type){
case DTYPE.CLASS_REF:
case DTYPE.FIELD_REF:
return `type:${DTYPE_STRING[this.type]}, value:${this.value.signature()}, code:${this.code}`;
default:
return `type:${DTYPE_STRING[this.type]}, value:${this.value}, code:${this.code}`;
}
}
else{
return `type:${DTYPE_STRING[this.type]}, value:NULL, code:${this.code}`;
}
}
setSkipped(){
this.skipped = true;
}
isSkipped(){
return this.skipped;
}
setCode(pCode){
this.code = pCode;
}
getCode(){
return this.code;
}
hasCode(){
return this.code != null;
}
getReferencedValue(){
return this.value;
}
hasValue(){
return (this.value !== null);
}
getValue(){
return this.value;
}
setValue(pValue){
this.value = pValue;
}
isThis(pMethod){
return (pMethod instanceof CLASS.Method)
&& (pMethod.modifiers.static==false);
}
isConcreteArray(){
}
arrayRead( pOffset){
if(this.value != null){
return this.value[pOffset];
}else{
throw Error("arrayRead : Array is undefined");
}
}
arrayWrite( pOffset, pValue){
if(this.value != null){
this.value.write(pOffset, pValue);
}else{
throw Error("arrayWrite : Array is undefined");
}
}
arrayWriteSymbolic( pSymbolOffset, pSymbolValue){
this.symOffset = true;
if(pSymbolValue.hasValue())
this.symVal.push({ off:pSymbolOffset.getCode(), val:pSymbolValue.getValue() })
else
this.symVal.push({ off:pSymbolOffset.getCode(), val:pSymbolValue.getCode() })
}
arrayReadSymbolic( pSymbolOffset){
// solve offset
if(this.value.length == 1){
return this.value[0];
}
else if(this.symVal.length == 1){
return this.symVal[0];
}
else{
// solve
return null;
}
}
add(pValue, pType, pOption=null){
switch(pType){
case OPCODE.ADD_LONG_2ADDR.byte:
case OPCODE.ADD_LONG.byte:
return ((this.value << 32) | (pOption & 0x00000000FFFFFFFF)) + pValue;
case OPCODE.ADD_INT_2ADDR.byte:
case OPCODE.ADD_INT_LIT8.byte:
case OPCODE.ADD_INT_LIT16.byte:
case OPCODE.ADD_INT.byte:
return this.value + pValue;
case OPCODE.ADD_DOUBLE_2ADDR.byte:
case OPCODE.ADD_FLOAT_2ADDR.byte:
case OPCODE.ADD_DOUBLE.byte:
case OPCODE.ADD_FLOAT.byte:
return parseFloat(this.value) + parseFloat(pValue);
}
}
sub(pValue, pType, pOption=null){
switch(pType){
case OPCODE.SUB_LONG_2ADDR.byte:
case OPCODE.SUB_LONG.byte:
return ((this.value << 32) | (pOption & 0x00000000FFFFFFFF)) - pValue;
case OPCODE.SUB_INT_2ADDR.byte:
case OPCODE.SUB_INT_LIT8.byte:
case OPCODE.SUB_INT_LIT16.byte:
case OPCODE.SUB_INT.byte:
return this.value - pValue;
case OPCODE.SUB_DOUBLE_2ADDR.byte:
case OPCODE.SUB_FLOAT_2ADDR.byte:
case OPCODE.SUB_DOUBLE.byte:
case OPCODE.SUB_FLOAT.byte:
return this.value - pValue;
}
}
mul(pValue, pType, pOption=null){
switch(pType){
case OPCODE.MUL_LONG.byte:
case OPCODE.MUL_LONG_2ADDR.byte:
return ((this.value << 32) | (pOption & 0x00000000FFFFFFFF)) * pValue;
case OPCODE.MUL_INT_2ADDR.byte:
case OPCODE.MUL_INT_LIT8.byte:
case OPCODE.MUL_INT_LIT16.byte:
case OPCODE.MUL_INT.byte:
return this.value * pValue;
case OPCODE.MUL_DOUBLE_2ADDR.byte:
case OPCODE.MUL_FLOAT_2ADDR.byte:
case OPCODE.MUL_DOUBLE.byte:
case OPCODE.MUL_FLOAT.byte:
return this.value * pValue;
}
}
div(pValue, pType, pOption=null){
switch(pType){
case OPCODE.DIV_LONG_2ADDR.byte:
case OPCODE.DIV_LONG.byte:
return ((this.value << 32) | (pOption & 0x00000000FFFFFFFF)) / pValue;
case OPCODE.DIV_INT_2ADDR.byte:
case OPCODE.DIV_INT_LIT8.byte:
case OPCODE.DIV_INT_LIT16.byte:
case OPCODE.DIV_INT.byte:
return this.value / pValue;
case OPCODE.DIV_DOUBLE_2ADDR.byte:
case OPCODE.DIV_FLOAT_2ADDR.byte:
case OPCODE.DIV_DOUBLE.byte:
case OPCODE.DIV_FLOAT.byte:
return parseFloat(this.value) / parseFloat(pValue);
}
}
rem(pValue, pType, pOption=null){
switch(pType){
case OPCODE.REM_LONG_2ADDR.byte:
case OPCODE.REM_LONG.byte:
return ((this.value << 32) | (pOption & 0x00000000FFFFFFFF)) % pValue;
case OPCODE.REM_INT_2ADDR.byte:
case OPCODE.REM_INT_LIT8.byte:
case OPCODE.REM_INT_LIT16.byte:
case OPCODE.REM_INT.byte:
return this.value % pValue;
case OPCODE.REM_DOUBLE_2ADDR.byte:
case OPCODE.REM_FLOAT_2ADDR.byte:
case OPCODE.REM_DOUBLE.byte:
case OPCODE.REM_FLOAT.byte:
return parseFloat(this.value) % parseFloat(pValue);
}
}
and(pValue, pType, pOption=null){
switch(pType){
case OPCODE.AND_LONG_2ADDR.byte:
case OPCODE.AND_LONG.byte:
return ((this.value << 32) | (pOption & 0x00000000FFFFFFFF)) & pValue;
case OPCODE.AND_INT_2ADDR.byte:
case OPCODE.AND_INT_LIT8.byte:
case OPCODE.AND_INT_LIT16.byte:
case OPCODE.AND_INT.byte:
return this.value & pValue;
}
}
or(pValue, pType, pOption=null){
switch(pType){
case OPCODE.OR_LONG_2ADDR.byte:
case OPCODE.OR_LONG.byte:
return ((this.value << 32) | (pOption & 0x00000000FFFFFFFF)) | pValue;
case OPCODE.OR_INT_2ADDR.byte:
case OPCODE.OR_INT_LIT8.byte:
case OPCODE.OR_INT_LIT16.byte:
case OPCODE.OR_INT.byte:
return this.value | pValue;
}
}
xor(pValue, pType, pOption=null){
switch(pType){
case OPCODE.XOR_LONG_2ADDR.byte:
case OPCODE.XOR_LONG.byte:
return ((this.value << 32) | (pOption & 0x00000000FFFFFFFF)) ^ pValue;
case OPCODE.XOR_INT_2ADDR.byte:
case OPCODE.XOR_INT_LIT8.byte:
case OPCODE.XOR_INT_LIT16.byte:
case OPCODE.XOR_INT.byte:
return this.value ^ pValue;
}
}
shl(pValue, pType, pOption=null){
switch(pType){
case OPCODE.SHL_LONG_2ADDR.byte:
case OPCODE.SHL_LONG.byte:
return ((this.value << 32) | (pOption & 0x00000000FFFFFFFF)) << pValue;
case OPCODE.SHL_INT_2ADDR.byte:
case OPCODE.SHL_INT_LIT8.byte:
case OPCODE.SHL_INT_LIT16.byte:
case OPCODE.SHL_INT.byte:
return this.value << pValue;
}
}
shr(pValue, pType, pOption=null){
switch(pType){
case OPCODE.SHR_LONG_2ADDR.byte:
case OPCODE.SHR_LONG.byte:
return ((this.value << 32) | (pOption & 0x00000000FFFFFFFF)) >> pValue;
case OPCODE.SHR_INT_2ADDR.byte:
case OPCODE.SHR_INT_LIT8.byte:
case OPCODE.SHR_INT_LIT16.byte:
case OPCODE.SHR_INT.byte:
return this.value >> pValue;
}
}
ushr(pValue, pType, pOption=null){
switch(pType){
case OPCODE.USHR_LONG_2ADDR.byte:
case OPCODE.USHR_LONG.byte:
return ((this.value << 32) | (pOption & 0x00000000FFFFFFFF)) >>> pValue;
case OPCODE.USHR_INT_2ADDR.byte:
case OPCODE.USHR_INT_LIT8.byte:
case OPCODE.USHR_INT_LIT16.byte:
case OPCODE.USHR_INT.byte:
return this.value >>> pValue;
}
}
}
class SymTable
{
constructor(){
this.table = {};
}
/**
* To print symbol table / stack info to a string
* @returns {String} Symbol table info
*/
print(){
let m= ``, k=0;
for(let i in this.table){
m += ` - ${i}, ${this.table[i].print()}
`;
k++;
}
m = `Table size : ${k}
`+m;
return m;
}
getSymbols(){
return this.table;
}
addEntry(pSymbol, pVisibility, pType, pValue, pCode, pSkipped=false){
this.table[pSymbol] = new Symbol(
pVisibility,
pType,
pValue,
pCode,
pSkipped
);
return this.table[pSymbol];
}
getEntry(pSymbol){
return this.table[pSymbol];
}
hasEntry(pSymbol){
return (this.table[pSymbol]!==undefined);
}
importSymbol(pReg, pSymbol, pExpr){
return this.addEntry(pReg, pSymbol.visibility, pSymbol.type, pSymbol.value, pExpr, pSymbol.skipped);
}
setSymbol(pReg, pType, pValue, pCode=null){
// Logger.debug("setSymbol: (reg=",pReg,", type=",pType,")");
return this.addEntry(pReg, VTYPE.METH, pType, pValue, pCode);
}
getSymbol(pSymbol){
return this.table[pSymbol];
}
clone(){
let tab = new SymTable();
for(let i in this.table){
tab.addEntry(i, this.table[i].visibility, this.table[i].type, this.table[i].value, this.table[i].code);
}
return tab;
}
}
class XNode
{
constructor( pBasicBlock, pPrevious ){
this.block = pBasicBlock;
this.next = [];
this.prev = [pPrevious];
}
}
class State
{
constructor( pEntrypoint){
this.entry = pEntrypoint;
this.current = null;
}
newBranch( pTarget){
let s = this.clone();
return s.append(pTarget);
}
append( pBasicBlock){
let n = new XNode(pBasicBlock, this.current);
this.current.next.push(n);
this.current = n;
return this;
}
clone(){
}
}
class SavedState
{
constructor( pTree, pLocalSymTab, pGlobalSymTab, pCurrentState){
this.state = pTree;
this.localSymTab = pLocalSymTab;
this.globalSymTab = pGlobalSymTab;
this.currentState = pCurrentState;
}
}
class PseudoCodeMaker
{
constructor( pVM, pEnable = true){
this.code = [];
this.enabled = pEnable;
this.vm = pVM;
}
isEnable(){
return this.enabled;
}
turnOn(){
this.enabled = true;
}
turnOff(){
this.enabled = false;
}
pop(){
return this.code.pop();
}
getIndent(){
return " ".repeat(this.vm.depth);
}
renderConcrete( pInstance){
switch(typeof pInstance.getConcrete()){
case 'string':
return `"${pInstance.getConcrete()}"`;
default:
return pInstance.getConcrete();
}
}
writeInvoke( pMethodRef, pParamsReg){
let v = null, rThis=null, vThis=0, rArg=null, vArg=null;
if(pParamsReg.length > 0){
rThis = this.vm.getRegisterName(pParamsReg[0]);
vThis = this.vm.stack.getLocalSymbol(rThis);
}
// add indent
v = this.getIndent();
// Generate 'instance' part of the call
if((pMethodRef instanceof CLASS.Method) && (pMethodRef.name=="<init>"))
v += `${rThis} = new ${pMethodRef.enclosingClass.name}(`;
else if(this.vm.method.modifiers.static==false && regX=="p0"){
v += `this.${pMethodRef.alias!=null? pMethodRef.alias : pMethodRef.name}(`;
}
else if(vThis.type==DTYPE.CLASS_REF && vThis.hasCode()){
v += `${vThis.getCode()}.${pMethodRef.alias!=null? pMethodRef.alias : pMethodRef.name}(`;
}
else if((vThis.getValue() instanceof VM_ClassInstance)
&& (vThis.getValue().hasConcrete())
&& (typeof vThis.getValue().getConcrete() == "string")){
v += `"${vThis.getValue().getConcrete()}".${pMethodRef.alias!=null? pMethodRef.alias : pMethodRef.name}(`;
}
else{
v += `${rThis}.${pMethodRef.alias!=null? pMethodRef.alias : pMethodRef.name}(`;
}
// Generate arguments string
if(pParamsReg.length > 1){
for(let j=1; j<pParamsReg.length; j++){
rArg = this.vm.getRegisterName(pParamsReg[j]);
vArg = this.vm.stack.getLocalSymbol(rArg);
if(this.vm.isImm(vArg))
v += `${this.vm.getImmediateValue(vArg)},`;
else if(vArg.getValue() instanceof VM_VirtualArray){
v+= vArg.getValue().toString()+',';
}
else if(vArg.hasCode() && !vArg.isSkipped())
v+= `${vArg.getCode()},`;
else if(rArg=="p0" && vArg.isThis(this.vm.method)){
v += `this, `;
}
else if((vArg.getValue() instanceof VM_ClassInstance)
&& (vArg.getValue().hasConcrete())
&& (typeof vArg.getValue().getConcrete() == "string")){
v += `"${vArg.getValue().getConcrete()}",`;
}
else{
v += `${rArg},`;
}
}
v = v.substr(0, v.length-1);
}
v += ')';
this.code.push(v);
}
writeIndirectInvoke( pInvokerObjRef, pInvokerArgRef, pInvokedMethod, pObj, pArgs){
let irObj=null, irArg=null, ivObj=null, ivArg=null, v = null, rThis=null, vThis=0, rArg=null, vArg=null;
irObj = this.vm.getRegisterName(pInvokerObjRef);
ivObj = this.vm.stack.getLocalSymbol(irObj);
irArg = this.vm.getRegisterName(pInvokerArgRef);
ivArg = this.vm.stack.getLocalSymbol(irArg);
if((ivArg.getValue() instanceof VM_VirtualArray) == false){
Logger.error("[VM][PCMAKER] PseudoCode generator is not able to simplify Method.invoke() call");
return null;
}
// add indent
v = this.getIndent();
// Generate 'instance' part of the call
if((pInvokedMethod instanceof CLASS.Method) && (pInvokedMethod.name=="<init>")){
// TODO : ??
// v += `${rThis} = new ${pInvokedMethod.enclosingClass.name}(`;
v += `new ${pInvokedMethod.enclosingClass.name}(`;
}
// caller is not static, p0 is 'this'
else if(this.vm.method.modifiers.static==false && irObj=="p0"){
v += `this.${pInvokedMethod.alias!=null? pInvokedMethod.alias : pInvokedMethod.name}(`;
}
// if invoked method is statis
else if(pInvokedMethod.modifiers.static == true){
v += `${pInvokedMethod.enclosingClass.alias!=null? pInvokedMethod.enclosingClass.alias : pInvokedMethod.enclosingClass.name}.${pInvokedMethod.alias!=null? pInvokedMethod.alias : pInvokedMethod.name}(`;
}
// if instance has expr
else if(ivObj.type==DTYPE.CLASS_REF && ivObj.hasCode()){
v += `${ivObj.getCode()}.${pInvokedMethod.alias!=null? pInvokedMethod.alias : pInvokedMethod.name}(`;
}
// if object is a class instance
else if((ivObj.getValue() instanceof VM_ClassInstance)
&& (ivObj.getValue().hasConcrete())
&& (typeof ivObj.getValue().getConcrete() == "string")){
v += `"${ivObj.getValue().getConcrete()}".${pInvokedMethod.alias!=null? pInvokedMethod.alias : pInvokedMethod.name}(`;
}
else{
v += `${irObj}.${pInvokedMethod.alias!=null? pInvokedMethod.alias : pInvokedMethod.name}(`;
}
// read array
ivArg = ivArg.getValue();
// Generate arguments string
if(ivArg.realSize() > 0){
for(let j=0; j<ivArg.realSize(); j++){
rArg = irArg+"["+j+"]";
vArg = ivArg.read(j);
if(vArg instanceof Symbol){
if(this.vm.isImm(vArg))
v += this.vm.getImmediateValue(vArg);
else if(vArg.getValue() instanceof VM_VirtualArray){
v+= vArg.getValue().toString();
}
else if(vArg.hasCode() && !vArg.isSkipped())
v+= vArg.getCode();
else if(rArg=="p0" && vArg.isThis(this.vm.method)){
v += `this`;
}
else if((vArg.getValue() instanceof VM_ClassInstance)
&& (vArg.getValue().hasConcrete())
&& (typeof vArg.getValue().getConcrete() == "string")){
v += `"${vArg.getValue().getConcrete()}"`;
}
else{
v += rArg;
}
}
else if(vArg instanceof VM_ClassInstance){
if(vArg.hasConcrete()){
v+= this.vm.pcmaker.renderConcrete(vArg);
}else{
v+= rArg;
}
}
else{
v+= rArg;
}
v += ',';
}
v = v.substr(0, v.length-1);
}
v += ')';
this.code.push(v);
}
push( pCode){
if(this.enabled) this.code.push(pCode);
}
append( pMessage){
if(this.code.length-1 >= 0)
this.code[this.code.length-1] += pMessage;
else
this.code[0] = pMessage;
}
last(){
if(this.enabled && this.code.length>0)
return this.code[this.code.length-1];
else
return null;
}
getCode(){
return this.code;
}
}
class VM_VirtualArray
{
constructor( pType=null, pSize=null){
this.type = pType;
this.size = pSize;
this.symbolicSize = null;
this.value = [];
}
/**
* To allocate a new array and fill it with given 'stringified' data
*
* Input must be formatted like it :
* NUMBER = 0123456789
* LETTER = abcdefABCDEF
* HEX = NUMBER | LETTER [ HEX ]
* INT = NUMBER [ INT ]
* FLOAT = <INT> '.' [ <INT> ]
* CHAR = "'" <ASCII_CHAR> "'"
* HEX_STR = '0x' <HEX>
* ENTRY = <CHAR> | <HEX_STR> | <INT> | <FLOAT>
* ARRAY = '[' <ENTRY> [, <ENTRY> ] ']'
*
* Example :
* [ 'n', 'u', 'l', 'l' ]
* [ 12, 127, 500, 30 ]
* [ 0x10, 0xFF, 0xbabe ]
* [ 0, 1. ]
*
* @param {String} pArrayStr
*/
static fromString( pType, pArrayStr){
const RE = new RegExp('[\s\t]*\[[\s\t]*(?<ctn>.*)[\s\t]*\][\s\t]*');
let m = RE.exec(pArrayStr);
if(m == null){
throw new VM_Exception('VM002','Unable to parse bytearray parameter: invalid format');
}
let arr = new VM_VirtualArray(pType, 0);
let entries = null, el=null;
if(m.groups.ctn.length > 0){
entries = m.groups.ctn.split(',');
console.log(entries, m);
/*
* write() is used instead of push() because array size is unknown
* and we don't want throw 'array out of bound VM error'.
*/
for(let i=0; i<entries.length; i++){
el = Util.trim(entries[i]);
console.log(el);
// char
if(el[0] == "'" && el.length==3){
arr.write( arr.size, Number.parseInt( el.charCodeAt(1), 10 ) );
}
// hex
else if(el.indexOf('0x')>-1){
if(el[0]=='-')
arr.write( arr.size, - Number.parseInt( el.substr(1), 16 ));
else
arr.write( arr.size, Number.parseInt( el, 16 ));
}
// float
else if(el.indexOf('.')>-1){
arr.write( arr.size, Number.parseFloat(el) );
}
// int
else{
arr.write( arr.size, Number.parseInt( el, 10) );
}
arr.size++;
}
}
console.log(arr);
return arr;
}
/**
* To print array content
*/
print(){
let m='[';
m += `](size: ${this.size}, realsize:${this.value.length})`;
return m;
}
realSize(){
return this.value.length;
}
size(){
return this.size;
}
getValue(){
return this.value;
}
read( pOffset){
return this.value[pOffset];
}
write( pOffset, pObject){
this.value[pOffset] = pObject;
}
push( pObject){
if(this.value.length >= this.size){
throw new VM_Exception('VM003','Array out of bound');
}
this.value.push(pObject);
}
pop(){
return this.value.pop();
}
fillWith( pDataBlock){
for(let i=0; i<pDataBlock.count(); i++){
this.value.push(pDataBlock.read(i));
}
}
setSymbolicSize( pSize){
this.size = null;
this.symbolicSize = pSize;
}
setConcreteSize( pSize){
this.size = pSize;
}
toString(){
let v = '[', e=null;
//console.log(this);
for(let i=0; i<this.value.length; i++){
e = this.value[i];
if(e instanceof Number){
if(e > 0x10 && e<0x7f){
v+=`'${String.fromCharCode(e)}'`;
}else if(e < 0){
v+= e.toString(16).replace('-','-0x');
}else{
v+= '0x'+e.toString(16);
}
}else if(e instanceof VM_ClassInstance){
v += '<'+e.getClass().name+'@>';
}else{
console.log(e);
v += '<other>';
}
v += ',';
}
if(v.length > 1)
return v.substr(0, v.length-1)+']';
else
return '[]';
}
}
/**
* Class performing allocation of component such as
* byte array
*
* @class
* @classdesc Class performing allocation of some components
*/
class VM_Allocator
{
constructor( pVM, pMemorySize=-1){
this.maxMemorySize = pMemorySize;
this.heap = [];
this.top = 0;
this.vm = pVM;
}
newArray( pType, pSize){
this.top = this.heap.length;
this.heap.push( new VM_VirtualArray( pType.name, pSize, this.top ));
return this.heap[this.top];
}
}
/**
* Monitor gather CFG states explored or not.
*/
class Monitor
{
constructor(){
this.queuedStates = [];
this.exploredStates = [];
}
queueState( pState){
this.queuedStates.push(pState);
}
/**
* To move state from queue to exploredState
*
*/
unqueueState(){
this.exploredStates.push( this.queuedStates.pop() );
}
}
class VM_HeapEntry
{
constructor( pName, pType, pValue){
this.name = pName;
this.type = pType;
this.value = pValue;
}
}
class VM_StackEntry
{
constructor( pMethod, pObj, pArgs, pReturn = null){
this.method = pMethod;
this.symTab = new SymTable();
this.ret = pReturn;
this.stats = 0;
this.states = {};
this.initSymTable(pObj, pArgs);
}
saveState(pLabel = "root"){
if(this.states[pLabel]==null) this.states[pLabel] = [];
this.states[pLabel].push(this.symTab.clone());
}
restoreState(pLabel = "root"){
this.symTab = this.states[pLabel];
}
setReturn( pReturn){
this.ret = pReturn;
}
getSymbolTable(){
return this.symTab;
}
getReturn(){
return this.ret;
}
/**
* Set 'this' symbol
* @param {VM_ClassInstance} pInstance
*/
setThis( pInstance){
this.symTab.setSymbol( 'p0', getDataTypeOf(pInstance), pInstance, 'this');
}
/**
* To set concrete values into parameters
*
* @param {Object[]} pArguments
* @param {Boolean} pAutoInstanciate
*/
setArguments( pArguments, pAutoInstanciate=false){
if(this.method.modifiers.static) p=0;
console.log("SetArguments", pArguments);
for(let k in pArguments){
this.symTab.setSymbol( k, pArguments[i].type, pArguments)
}
}
addArgument( pOffset, pType, pValue){
let p=1;
if(this.method.modifiers.static) p=0;
this.symTab.setSymbol( 'p'+(p+pOffset), pType, pValue, null);
}
/**
*
* @param {Symbol} pObj Symbol pointing to an instance of the object of the method
* @param {Symbol[]} pArgs Array of symbols from caller which are arguments of invoked method.
*/
initSymTable(pObj, pArgs){
Logger.debug(`[VM] Init (locals:${this.method.locals}, params:${this.method.args.length})`);
// init parameter register
let paramOffset = 0, arg=null, arr=null;
if(this.method.modifiers.static==false){
this.symTab.setSymbol('p0', DTYPE.OBJECT_REF, pObj);
paramOffset = 1;
}
for(let i=paramOffset; i<this.method.args.length+paramOffset; i++){
arg = this.method.args[i-paramOffset];
Logger.debug(`initRegister: (reg=p${i}, type=${getDataTypeOf(arg)})`);
this.symTab.setSymbol('p'+i, getDataTypeOf(arg), (pArgs[i-paramOffset]!==undefined ? pArgs[i-paramOffset].getValue() : null) ); // arg
}
// init local registers
for(let i=0; i<this.method.locals; i++){
this.symTab.setSymbol('v'+i, DTYPE.UNDEFINED, null);
}
}
}
/**
* Stack Area contains call stack and return value for each call.
* @author FrenchYeti
*/
class VM_StackMemory
{
constructor(){
// keep a trace of called method
this.callstack = [];
this.csLen = 0;
// when a return happens, local symbol returned by the method is push here
// if caller move the result, symbol is popped and imported into caller's symbol table
this.ret = [];
// the local symbol table of the current method is stored here
this.symTab = null;
this.indirect = [];
}
/**
* To get string containing information about current stack memory
*
* @returns {String} A string containing information
*/
print(){
let m=`
Call stack :
`;
for(let i=this.callstack.length-1; i>=0; i--){
m+=` - ${this.callstack[i].method.signature()} (${i})
`;
}
m += `
Current registers/symbols :
`;
m += this.symTab.print();
if(this.ret.length > 0){
m+= `
Latest values returned :
`;
for(let i=0; i<this.ret.length; i++){
if(this.ret[i] != RET_VOID){
m += ` - ${this.ret[i].print()} (${i})
`;
}else{
m += ` - void (${i})
`;
}
}
}
return m;
}
/**
* To track method invoked indirectly through Reflection API
*
* The aim of such tracking is to help future optimization
*
*/
addIndirectInvoke( pMethod, pThis, pArgs){
this.indirect.push({
method: pMethod,
obj: pThis,
args: pArgs
});
}
lastIndirectInvoke(){
return this.indirect.pop();
}
/**
* To get the current call stack depth
* @returns {int} The depth of current call stack. 0 means the top level function is the current function
*/
depth(){
return this.callstack.length-1;
}
/**
* To get current stack entry.
* It returns the stack entry associated to current running method.
*
* @returns {VM_StackEntry} The stack entry
*/
current(){
return this.callstack[this.callstack.length-1];
}
setLocalSymbol( pName, pType, pValue, pCode=null){
Logger.debug(`[VM] [STACK] Set local symbol ${pName}, type=${pType}, value=${pValue}, code=${pCode}`);
return this.symTab.setSymbol( pName, pType, pValue, pCode)
}
getLocalSymbol( pName){
Logger.debug(`[VM] [STACK] Get local symbol ${pName}`);
return this.symTab.getSymbol(pName);
}
importLocalSymbol(pReg, pSymbol, pExpr=null){
Logger.debug(`[VM] [STACK] Import local symbol into ${pReg}`);
return this.symTab.importSymbol(pReg, pSymbol, (pExpr==null&&pSymbol.getCode()!=null ? pSymbol.getCode() : pExpr) );
}
getLocalSymbolTable(){
return this.symTab;
}
clear(){
this.callstack = [];
this.ret = [];
}
add( pMethod, pObj=null, pArgs=[]){
this.callstack.push( new VM_StackEntry( pMethod, pObj, pArgs ));
this.symTab = this.callstack[ this.callstack.length-1 ].symTab;
Logger.debugBgRed('[VM] [STACK] Add entry : '+pMethod.signature());
}
get( pMethodName){
for(let i=0; i<this.callstack.length; i++){
if(this.callstack[i].method.signature() ==pMethodName){
return this.callstack[i];
}
}
return null;
}
last(){
if(this.callstack.length > 0){
return this.callstack[this.callstack.length-1];
}else
return null;
}
// duplicated
lastMethod(){
if(this.callstack.length > 0){
return this.callstack[this.callstack.length-1];
}else
return null;
}
lastReturn(){
if(this.ret.length > 0){
return this.ret[this.ret.length-1];
}else
return null;
}
/**
* To remove a StackEntry (stack frame) from the callstack.
* It is called systematically when a method has been successfully invoked.
*/
pop(){
if(this.callstack.length>1)
this.symTab = this.callstack[this.callstack.length-2].symTab;
return this.callstack.pop();
}
/**
*
* @param {Symbol|int} pSymbol The resturn value
*/
pushReturn( pSymbol){
this.ret.push( pSymbol);
//this.callstack[this.callstack.length-1].setReturn( pSymbol);
}
/**
* To return the latest value push into 'return stack'
*
* @returns {Symbol} The symbol containing concrete data or symbolic expression of the value returned
*/
popReturn(){
return this.ret.pop();
}
getReturn(){
return this.callstack[this.callstack.length-1].getReturn();
}
}
class VM_ClassInstance
{
constructor( pClass){
this.parent = pClass;
this.fields = {};
this.initalized = false;
this.concrete = null;
}
isInitialized(){
return this.initalized;
}
getClass(){
return this.parent;
}
linkConcrete( pData){
this.concrete = pData;
return this;
}
hasConcrete(){
return (this.concrete != null);
}
/**
* To set data into an instance property
*
* @param {require('./CoreClass.js').Field} pField Field description from Analyzer
* @param {*} pData Data to set
*/
setField( pField, pData){
this.fields[pField.name] = pData;
}
/**
* To get data from a specific property of the instance
*
* @param {require('./CoreClass.js').Field} pField Field description from Analyzer
* @returns {*} Data
*/
getField( pField){
if(this.fields[pField.name] === undefined){
return null;
}else
return this.fields[pField.name];
}
setConcrete( pData){
this.concrete = pData;
return this;
}
getConcrete(){
return this.concrete;
}
}
class VM_ClassLoader
{
constructor( pVM){
this.classes={};
this.vm = pVM;
}
/**
* To load a new class, only if it is necessary.
* If the class contains static blocks, clinit() is executed.
* @param {*} pClass
*/
load( pClass, pExecClinit=true){
let clz = null;
if(pClass instanceof CLASS.Class){
if(this.classes[pClass.name] != undefined)
return this.classes[pClass.name];
clz = pClass;
}else if(this.classes[pClass] != undefined)
return this.classes[pClass];
else{
clz = this.vm.context.find.get.class(pClass);
}
let fields = clz.getStaticFields(), clinit=null;
// import static fields into global symbol table
if(fields.length > 0){
for(let i=0; i<fields.length; i++){
this.vm.metharea.setGlobalSymbol(
`${fields[i].enclosingClass.name}.${fields[i].name}`,
getDataTypeOf(fields[i].type), null,
`${fields[i].enclosingClass.name}.${fields[i].name}`);
}
}
// execute clinit()
clinit = clz.getClInit();
if((pExecClinit==true) && (clinit != null)){
Logger.debugPink(`[VM] [CLINIT] START : Execute ${clinit.signature()} ...`);
if(this.vm.stack.depth()==0) this.vm.pcmaker.turnOff();
this.vm.invoke( clinit, null, null);
if(this.vm.stack.depth()==0) this.vm.pcmaker.turnOn();
Logger.debugPink(`[VM] [CLINIT] END : ${clinit.signature()} has been executed`);
}
Logger.debugPink(`[VM] [CLASSLOADER] Class ${clz.name} loaded`);
this.classes[clz.name] = clz;
return clz;
}
}
/**
* This class manages shared memory and overall executed instructions
*/
class VM_MethodArea
{
constructor(){
this.methods = {};
/*
Global symbol table is stored here, this table contains
- static variables (fields)
- loaded classes (ClassPrototype)
- uncatched exception
*/
this.symTab = new SymTable();
}
getGlobalSymbolTable(){
return this.symTab;
}
setGlobalSymbol( pName, pType, pValue, pCode=null){
Logger.debug(`[VM] [METHAREA] Set global symbol ${pName} ${(pValue!=null)? " = "+pValue : ""}`);
return this.symTab.setSymbol( pName, pType, pValue, pCode)
}
getGlobalSymbol( pName){
Logger.debug(`[VM] [METHAREA] Get global symbol ${pName}`);
return this.symTab.getSymbol( pName);
}
importSymbolTable( pTable){
this.symTab = pTable;
}
/**
* To get optimized instructions
* @param {Method} pMethod The method containing requested instructions
* @returns {Object[]} An array of anonymous object containg Instruction object or BasicBlock metadata
* @deprecated
*/
getInstructions( pMethod){
return this.methods[pMethod.signature()];
}
/**
*
* @param {Method} pMethod The method to init
*/
initMethod( pMethod){
let v = pMethod.signature();
if(this.methods[v]==null){
return this.methods[v] = {
instr: this.analyzeBlocks( pMethod)
};
}else
return this.methods[v];
}
/**
* To perform forward and backward analysis. It allows to identify
* IF statement, GOTO, and more
* @param {Method} pMethod
*/
analyzeBlocks(pMethod){
let blocks = pMethod.getBasicBlocks();
if(blocks.length == 0) return null;
let self = null, next=null, instrStack=[];
let entry = blocks[0], instr=null, jump=false;
// forward analysis
self = entry;
for(let i=0; i<blocks.length ; i++){
// avoid basic blocks containing only NOP
if(blocks[i].isNOPblock()) continue;
// avoid basic blocks already visited
if(blocks[i].isVisited()) continue;
instrStack.push({ i:blocks[i], t:'b' });
instr = blocks[i].getInstructions();
jump = false;
for(let k=0; k<instr.length; k++){
instrStack.push({ i:instr[k], t:'i' });
if(instr[k].opcode.type == CONST.INSTR_TYPE.IF){
//console.log("IF-", instr[k]);
next = pMethod.getBasicBlockByLabel(instr[k].right.name, CONST.INSTR_TYPE.IF);
//Logger.debug(`Block ${i} IF => Block ${next.offset}`)
if(blocks[i].hasSuccessor(next)==false){
blocks[i].addSuccessor(next);
}
if(next.hasPredecessor(blocks[i])==false){
next.addPredecessor(blocks[i]);
}
if((blocks[i+1] !== undefined)
&& (blocks[i].hasSuccessor(blocks[i+1])==false)){
//Logger.debug(`Block ${i} ELSE => Block ${blocks[i+1].offset}`)
blocks[i].addSuccessor(blocks[i+1]);
if(blocks[i+1].hasPredecessor(blocks[i])==false){
blocks[i+1].addPredecessor(blocks[i]);
}
}
jump = true;
}
else if(instr[k].opcode.type == CONST.INSTR_TYPE.GOTO){
//Logger.debug("GOTO", instr[k]);
next = pMethod.getBasicBlockByLabel(instr[k].right.name, CONST.INSTR_TYPE.GOTO);
//Logger.debug(`Block ${i} GOTO => Block ${next.offset}`)
if(next == null){
Logger.error("[VM] Basic block 'goto_"+instr[k].right.name+"' targeted by goto is not found.");
}
if(blocks[i].hasSuccessor(next)==false){
blocks[i].addSuccessor(next);
}
if(next.hasPredecessor(blocks[i])==false){
next.addPredecessor(blocks[i]);
}
jump = true;
}
else if(instr[k].opcode.type == CONST.INSTR_TYPE.RET){
//Logger.debug(`Block ${i} RETURN`)
jump = true;
}
}
if((jump == false)
&& (blocks[i+1] !== undefined)
&& (blocks[i].hasSuccessor(blocks[i+1])==false)){
//Logger.debug(`Block ${i} CONTINUE => Block ${blocks[i+1].offset}`)
self.addSuccessor(blocks[i+1]);
blocks[i+1].addPredecessor(blocks[i]);
}
blocks[i].visit();
}
Logger.debug(`[VM] Basic blocks optimization done.`)
return instrStack;
}
}
/**
* Class managing the heap area. This component handle data
* shared by several thread. There is a single heap area per VM instance.
*
* This class handles class instances, class loaders, static field, and more
*
* @class
* @classdesc Class managing the heap area
*/
class VM_HeapArea
{
/**
* To constructr Heap Area
*
* @param {VM} pVM The VM instance
* @param {VM_ClassLoader} pClassLoader The default class loader
*/
constructor( pVM, pClassLoader){
this.heap = [];
this.free = [];
this.vm = pVM;
this.classloader = pClassLoader;
}
/**
* To clear heap area
*/
clear(){
this.heap = [];
this.free = [];
}
/**
* To load a class.
*
* Actually only built-in classloader is supported
*
* @method
* @param {Class} pClass The class to load
*
*/
loadClass( pClass){
return this.classloader.load( pClass);
}
/**
* To instanciante a new object from specified class.
*
* @method
* @param {Class} pClass the class to instanciate
* @param {ObjectType[]|BasicType[]|Symbol[]} pArgs Array of argument to pass to constructor
* @returns {VM_ClassInstance} An instance of the class
*/
newInstance( pClass, pArgs=[], pConstructor=null){
let ref=null, clz=null;
if(pClass instanceof CLASS.Class)
Logger.debugBgRed(`[VM] [HEAP] START : New instance of ${pClass.name}`);
else
Logger.debugBgRed(`[VM] [HEAP] START : New instance of ${pClass}`);
// load class if needded
clz = this.loadClass(pClass);
ref = this.heap.length;
this.heap.push( new VM_ClassInstance(clz));
if(pClass instanceof CLASS.Class)
Logger.debugBgRed(`[VM] [HEAP] END : New instance of ${pClass.name}`);
else
Logger.debugBgRed(`[VM] [HEAP] END : New instance of ${pClass}`);
//return ref;
return this.heap[this.heap.length-1];
}
/**
* To get an element from Heap Area
*
* @method
* @param {ObjectType} pType
* @param {String} pName
*/
get( pType, pName){
for(let i=0; i<this.heap.length; i++){
if((this.heap[i].type ==pType) && (this.heap[i].name == pName)){
return this.heap[i];
}
}
return null;
}
getObject( pRef){
return this.heap[pRef];
}
add( pName, pObject, pType){
if(this.free.length > 0){
this.heap[this.free.pop()] = new VM_HeapEntry( pName, pType, pObject);
}
}
remove( pName, pType){
for(let i=0; i<this.heap.length; i++){
if((this.heap[i].type == pType) && (this.heap[i].name == pName)){
this.heap[i] = null;
this.free.push(i);
}
}
}
}
/**
* Class describing a hook into the VM, it allows to provide custom implementation
* of method.
*
* @class
* @classdesc Class describing a hook into the VM
*/
class VM_Hook
{
/**
*
* @constructor
* @param {String} pMethodName The signature of the method to hook
* @param {Function} pHook
* @param {Boolean} pEnable
*/
constructor( pMethodName, pHook, pEnable=true){
this.method = pMethodName;
this.hook = pHook;
this.enable = pEnable;
}
/**
* To execute the hook code with the given context
*
* @method
* @param {VM} pVM The context of the VM
* @param {VM_ClassInstance} pThis If the method is not static, the instance invoking the method. Else, if the method is static, it is NULL
* @param {Symbol} pArgs The registers containing value of arguments
* @return {*} Value returned by hook function
*/
exec( pVM, pMethod, pThis, pArgs){
return this.hook(pVM, pThis, pArgs);
}
}
/**
* Class logging message from the VM.
*
* It handles internal VM logs and Android logs (thanks to android.Log hooks).
*
* @class
* @classdesc Class logging message from the VM
*/
class VM_Log
{
constructor(){
this.logs = [];
}
reset(){
this.logs = [];
}
write( pMsg){
this.logs.push(pMsg);
}
read(){
return this.logs;
}
}
/**
* Class managing minimalist smali VM and performing partial smali execution.
*
* VM instance is unique (singleton pattern).
* Use getInstance() to get reference.
*
* You can reset different part of the VM quickly by using softReset() or reset().
*
* - softReset() does not reset Heap Area and Method Area. It allows to reuse previously loaded class.
* - reset() does an hard reset. It reset Stack Memory, Method Area, Heap Area, Logger and more.
*
*
* @class
* @classdesc Class managing minimalist smali VM and performing partial smali execution.
*/
class VM
{
/**
* Instance of the VM
* @member
*/
static __instance = null;
constructor(pContext, pMethod = null, pLocalSize = null, pParamSize = null){
this.context = pContext;
this.logs = new VM_Log();
// symTab is the symbol table of the current running method (the last into call stack)
this.symTab = null;
this.globalSymTab = new SymTable();
this.monitor = new Monitor();
// class instance are stored into heap
this.classloader = new VM_ClassLoader(this);
this.heap = new VM_HeapArea(this, this.classloader);
this.stack = new VM_StackMemory();
this.pcmaker = new PseudoCodeMaker(this, true);
this.metharea = new VM_MethodArea();
this.allocator = new VM_Allocator( this, -1);
this.invokes = [];
this.method = pMethod;
this.simplify = 0;
this.countUntreated = 0;
this.depth = 0;
this.savedContexts = {};
this.visited = [];
this.currentContext = "root";
this.config = {
loadClassFirst: false,
mockAndroidInternals: false,
autoInstanceArgs: false,
simplify: 0,
initParent: true
};
this.pseudocode = false;
this.hooks = {};
}
performLongBinaryOp( pOpCode, pDest, pSrc1, pSrc2){
switch(pOpCode){
case OPCODE.ADD_LONG.byte:
break;
}
}
performBinaryOpAddr2( pOpCode, pType, pDest, pSrc1){
}
/**
*
* @param {*} pRegister
*/
prepareLong( pRegister ){
let s = {
mn: this.getRegisterName({ t:pRegister.t, i:pRegister.i }),
ln: this.getRegisterName({ t:pRegister.t, i:pRegister.i+1 }),
v: null
};
s.m = this.stack.getLocalSymbol( s.mn );
s.l = this.stack.getLocalSymbol( s.ln );
if(this.isImm(s.m) && this.isImm(s.l)){
s.v = (s.m << 32) & (s.l & 0x00000000FFFFFFFF);
}
return s;
}
/**
*
* @param {*} pOpCode
* @param {*} pType
* @param {*} pDest
* @param {*} pSrc
*/
performBinaryOp( pOpCode, pType, pDest, pSrc1, pSrc2=null){
let dst = null, src1 = null, src2 = null;
src1 = {
m: this.stack.getLocalSymbol( this.getRegisterName({ t:pSrc1.t, i:pSrc1.i+1 })),
l: this.stack.getLocalSymbol( this.getRegisterName({ t:pSrc1.t, i:pSrc1.i })),
v: null
};
src2 = {
m: this.stack.getLocalSymbol( this.getRegisterName({ t:pSrc2.t, i:pSrc2.i+1 })),
l: this.stack.getLocalSymbol( this.getRegisterName({ t:pSrc2.t, i:pSrc2.i })),
v: null
};
if(this.isImm(src1.m) && this.isImm(src1.l)){
src1.v = (src1.m << 32) & (src1.l & 0x00000000FFFFFFFF);
}
if(this.isImm(src2.m) && this.isImm(src2.l)){
src2.v = (src2.m << 32) & (src2.l & 0x00000000FFFFFFFF);
}
if(src1.v !== null && src2.v !== null){
switch(pOpCode){
case OPCODE.ADD_LONG.byte:
dst = src2.m
case OPCODE.SUB_LONG.byte:
case OPCODE.DIV_LONG.byte:
case OPCODE.MUL_LONG.byte:
case OPCODE.REM_LONG.byte:
case OPCODE.OR_LONG.byte:
case OPCODE.XOR_LONG.byte:
case OPCODE.AND_LONG.byte:
break;
case OPCODE.SHR_LONG.byte:
case OPCODE.SHL_LONG.byte:
case OPCODE.USHR_LONG.byte:
dst = src2.m
break;
}
dst = src1.v
this.stack.setLocalSymbol(
this.getRegisterName({ t:pSrc1.t, i:pSrc1.i+1 }),
DTYPE.IMM_LONG,
);
}
else if(src1.v !== null){
}
else if(src2.v !== null){
}
else{
}
if(pType == DTYPE.IMM_LONG){
return this.performLongBinaryOp( pOpCode, pDest, pSrc1, pSrc2);
}
else if(pType == DTYPE.IMM_DOUBLE){
return this.performLongBinaryOp( pOpCode, pDest, pSrc1, pSrc2);
}
regX = this.getRegisterName(oper.right);
regX = this.stack.getLocalSymbol(regX);
if(this.isImm(regX)){
regV = this.getRegisterName(oper.left[0]);
regV = this.stack.getLocalSymbol(regV);
if(this.isImm(regV)){
// this.setSymbol(regX, regX.add(regV.getValue(), oper.opcode.byte));
this.stack.setLocalSymbol(
regV,
DTYPE.IMM_NUMERIC,
regX[SYMBOL_OPE[oper.opcode.ope]](regV.getValue(), oper.opcode.byte),
`${this.getImmediateValue(regX)}${oper.opcode.ope}${this.getImmediateValue(regV)}`);
//break;
}
else{
if(regV.hasCode()){
this.stack.setLocalSymbol(
regV,
DTYPE.IMM_NUMERIC,
null,
`(${regV.getCode()})${oper.opcode.ope}${this.getImmediateValue(regV)}`);
}else{
this.stack.setLocalSymbol(
regV,
DTYPE.IMM_NUMERIC,
null,
`${this.getRegisterName(oper.left[0])}${oper.opcode.ope}${this.getImmediateValue(regV)}`);
}
}
}
else {
regV = this.getRegisterName(oper.left[0]);
regV = this.stack.getLocalSymbol(regV);
if(this.isImm(regV)){
if(regX.hasCode()){
this.stack.setLocalSymbol(
regV,
DTYPE.IMM_NUMERIC,
null,
`${this.getImmediateValue(regV)}${oper.opcode.ope}(${regX.getCode()})`);
}else{
this.stack.setLocalSymbol(
regV,
DTYPE.IMM_NUMERIC,
null,
`${this.getImmediateValue(regV)}${oper.opcode.ope}${this.getRegisterName(oper.right)}`);
}
}
else{
this.stack.setLocalSymbol(
regV,
DTYPE.IMM_NUMERIC,
null,
`${(regV.hasCode()? '('+regV.getCode()+')':this.getRegisterName(oper.left[0]))}${oper.opcode.ope}${(regX.hasCode()? '('+regV.getCode()+')':this.getRegisterName(oper.right))}`);
}
}
return "";
}
/**
* To write a message into VM logs.
*
* @param {String} pMessage The message to log
*/
writeLog( pMessage){
Logger.info("[VM][LOG] "+pMessage);
this.logs.write(pMessage);
}
/**
* To read all logs from the VM
*
* @returns {String[]} An array containing all log messages
*/
readLog(){
return this.logs.read();
}
/**
* To reset context component related to code inside a method (soft reset)
* Warning: this function not remove static field modified during previous runtime
* or instances created previously.
*
* It can help to improve performane
*/
softReset(){
this.stack = new VM_StackMemory();
this.pcmaker = new PseudoCodeMaker(this, true);
this.allocator = new VM_Allocator( this, -1);
this.logs.reset();
this.savedContexts = {};
this.visited = [];
this.currentContext = "root";
this.depth = 0;
}
/**
* To reset VM components related to context.
*/
reset(){
this.classloader = new VM_ClassLoader(this);
this.heap = new VM_HeapArea(this, this.classloader);
this.stack = new VM_StackMemory();
this.pcmaker = new PseudoCodeMaker(true);
this.metharea = new VM_MethodArea();
this.allocator = new VM_Allocator( this, -1);
this.logs.reset();
this.savedContexts = {};
this.visited = [];
this.currentContext = "root";
}
/**
*
* @param {Class} pClass
* @param {String} pName
* @param {Symbol[]} pArgs
* @returns {Method} The method with the corresponding signature, else NULL
*/
getMethodFromClass( pClass, pName, pArgs){
let ok = null, arg=null;
let meths = pClass.getMethod({ name:pName }, CONST.EXACT_MATCH);
if(meths.length==0) return null;
for(let i=0; i<meths.length; i++){
if(pArgs.length != meths[i].args.length) continue;
ok = true;
for(let j=0; j<pArgs.length; j++){
arg = pArgs[j].getValue();
if(arg instanceof CLASS.ObjectType){
if(arg._name != meths[i].args[j]._name) ok=false;
}
else if(arg instanceof CLASS.Class){
if(arg.name != meths[i].args[j]._name)
ok=false;
}
else{
Logger.error(`[VM] [REFLECT] Method ${pName} : invalid argument type `)
}
}
if(ok) return meths[i];
}
return null;
}
/**
* To check if a hook exist into the VM for given method
*
* @param {Method} pMethod An instance of Method
* @returns {Boolean} Return TRUE if an hook is set, else FALSE
*/
isHooked( pMethod){
return (this.hooks[pMethod.signature()] instanceof VM_Hook);
}
/**
* To execute the hook associate to pMethod
*
* @param {*} pMethod
* @param {*} pThis
* @param {*} pObj
* @returns {Boolean} TRUE if hook have been executed, else FALSE
*/
execHook( pMethod, pThis, pArgs){
if( this.hooks[pMethod.signature()] !== null ){
return this.hooks[pMethod.signature()].exec( this, pMethod, pThis, pArgs);
}else{
return false;
}
}
/**
* To define a function called instead of specified method.
* It allows to hook internal Android method
*
* @param {String} pMethodName The name of the method to hook
* @param {Function} pHook The callback function
*/
defineHook( pMethodName, pHook){
this.hooks[pMethodName] = new VM_Hook( pMethodName, pHook, true);
}
/**
* To configure VM
* @param {Object} pConfig Configuration of the VM
*/
setConfig( pConfig){
for(let i in pConfig){
this.config[i] = pConfig[i];
}
}
/**
* To get an instance of the VM
* @returns {VM} Returns an instance of the VM
*/
static getInstance( pContext){
if(VM.__instance==null){
VM.__instance = new VM(pContext);
}
return VM.__instance;
}
/**
* @deprecated
* @param {*} pSymTab
*/
importGlobalSymbols( pSymTab){
this.metharea.importSymbolTable( pSymTab);
}
/**
*
* @param {String} pLabel
*/
changeContextLabel(pLabel = "root"){
this.currentContext = pLabel;
}
enablePseudocode(){
this.pseudocode = true;
}
disablePseudocode(){
this.pseudocode = false;
}
saveContext(pLabel = "root"){
this.stack.current().saveState(pLabel);
//this.savedContexts[pLabel] = this.stack.current().clone(); // getLocalSymbolTable().clone();
//console.log(this.savedContexts);
Logger.debug("[VM] [SYM] Save : "+pLabel);
}
restoreContext( pNextLabel){
this.stack.current().saveState(this.currentContext);
this.currentContext = pNextLabel;
this.stack.current().restoreState(pNextLabel);
// this.savedContexts[this.currentContext] = this.stack.getLocalSymbolTable();
// this.currentContext = pNextLabel;
// this.symTab = this.savedContexts[pNextLabel];
// this.stack.symTab = this.savedContexts[pNextLabel];
Logger.debug("[VM] [SYM] Archive : "+this.currentContext+", Restore :"+pNextLabel);
}
contextExists( pLabel){
return (this.stack.current().hasState(pLabel) != null);
}
getEntrypoint(){
return this.method;
}
/**
*
* @param {*} pMethod
* @param {*} pLocalSize
* @param {*} pParamSize
* @deprecated
*/
initRegisters(pMethod, pLocalSize, pParamSize){
Logger.debug(`[VM][DEPRECATED] Init (locals:${pLocalSize}, params:${pParamSize})`);
// init parameter register
let paramOffset = 0, arg=null;
if(pMethod.modifiers.static==false){
this.setSymbol('p0', DTYPE.CLASS_REF, pMethod.enclosingClass);
paramOffset = 1;
}
for(let i=paramOffset; i<pParamSize+paramOffset; i++){
arg = pMethod.args[i-paramOffset];
Logger.debug("initRegister: (reg=p",i,", type=", getDataTypeOf(arg),")");
this.setSymbol('p'+i, getDataTypeOf(arg), null); // arg
}
// init local registers
for(let i=0; i<pLocalSize; i++){
this.setSymbol('v'+i, DTYPE.UNDEFINED, null);
}
this.countUntreated = 0;
}
/**
* To remove "visited" flags from basic blocks.
* It should be applied only to analyzed method,
* because it is involved into process making the pseudocode
*/
cleanVisitedBlock(){
let block = this.method.getBasicBlocks();
for(let i=0; i<block.length; i++){
block[i].initVisit();
}
}
/**
*
* @param {*} pLevel
* @deprecated
*/
setSimplifyingLevel(pLevel){
this.simplify = pLevel;
}
prepareArguments( pMethod, pArgumentsValue){
let args={}, p=0;
if(pMethod.modifier.static==false) p=1;
for(let i=0; i<pMethod.args.length; i++){
args[p+i] = new Symbol(
VTYPE.METH,
getDataTypeOf(pMethod.args[i]),
pArgumentsValue["p"+(p+i)].sym==false? pArgumentsValue["p"+(p+i)].val : null,
null
);
}
return args;
}
/**
* To execute a method and to perform static analysis
* @param {*} pMethod
* @param {*} pLevel
*/
start( pMethod, pThis, pArguments=null, pClearHeap=false){
let opt = null, margs=null, arr=null;
// clean StackMemory
this.stack.clear();
// If forced, clean HeapArea
if(pClearHeap == true){
this.heap.clear();
}
// init
this.method = pMethod
opt = this.metharea.initMethod(pMethod);
if(this.config.loadClassFirst){
this.classloader.load(this.method.enclosingClass, (this.config.initParent && pMethod.name!="<clinit>"));
// clear stack : remove data if <clinit> has been executed
this.stack.clear();
}
// init callstack with entrypoint
this.stack.add(pMethod);
// if method is not static 'this' reference should be instanciate
if(this.method.modifiers.static == false){
if(pThis == null){
this.stack.last().setThis( this.heap.newInstance(this.method.enclosingClass) );
//console.log(this.stack.symTab.table.p0);
}else
this.stack.last().setThis( pThis);
}
// if arguments are passed,
//if((typeof pArguments == 'array') && pArguments.length > 0){
// console.log("set args");
// this.stack.last().setArguments(pArguments);
//}
// else if flag 'autoInstanceArgs' is true
// TODO
// else if(this.config.autoInstanceArgs && this.method.hasArgs()){
if(this.config.autoInstanceArgs && this.method.hasArgs()){
margs = this.method.args;
for(let i=0; i<margs.length; i++){
if(margs[i] instanceof CLASS.ObjectType){
if((pArguments!=null) && (pArguments["p"+i] !=null)){
//console.log(margs[i]);
if(pArguments["p"+i].val != null)
this.stack.last().addArgument(i, getDataTypeOf(margs[i]),
this.heap.newInstance(margs[i]._name).setConcrete(pArguments["p"+i].val));
else if(pArguments["p"+i].notset==true)
this.stack.last().addArgument(i, getDataTypeOf(margs[i]), this.heap.newInstance(margs[i]._name));
else
this.stack.last().addArgument(i, getDataTypeOf(margs[i]),
this.heap.newInstance(margs[i]._name));
}else{
this.stack.last().addArgument(i, getDataTypeOf(margs[i]),
this.heap.newInstance(margs[i]._name));
}
}else{
if((pArguments!=null) && (pArguments["p"+i] !=null)){
if(pArguments["p"+i].val != null){
if(getDataTypeOf(margs[i])==DTYPE.ARRAY){
// parse array string
arr = VM_VirtualArray.fromString( margs[i].type, pArguments["p"+i].val);
this.stack.last().addArgument(i, getDataTypeOf(margs[i]), arr);
}else
this.stack.last().addArgument(i, getDataTypeOf(margs[i]), castToDataType(getDataTypeOf(margs[i]), pArguments["p"+i].val));
}else if(pArguments["p"+i].notset==true)
this.stack.last().addArgument(i, DTYPE.UNDEFINED, null);
else
this.stack.last().addArgument(i, getDataTypeOf(margs[i]), null);
}else{
this.stack.last().addArgument(i, getDataTypeOf(margs[i]), null);
}
//this.stack.last().addArgument(i, margs[i], this.allocator.malloc(pArguments[i]));
}
}
}
/*else{
console.log("nothing to do with args");
}*/
// clean visited blocks
this.cleanVisitedBlock();
// execute
this.run(opt.instr);
}
/**
* To convert operand anonymous object from Instruction into register name
*
* @param {Object} pReg
*/
getRegisterName(pReg){
if(pReg.t=='v')
return "v"+pReg.i
else
return "p"+pReg.i;
}
getSymbol(pReg){
// return this.symTab.getEntry(pReg);
return this.stack.getLocalSymbolTable().getSymbol(pReg);
}
getDataTypeOf(pType){
Logger.debug("getDataTypeOf: ",pType);
if(pType instanceof CLASS.ObjectType){
return DTYPE.OBJECT_REF;
}
else if(pType instanceof VM_ClassInstance){
return DTYPE.OBJECT_REF;
}
else{
return BTYPE_DTYPE[pType.name];
}
}
importSymbol(pReg, pSymbol, pExpr){
return this.stack.getLocalSymbolTable().addEntry(pReg, pSymbol.visibility, pSymbol.type, pSymbol.value, pExpr);
}
setSymbol(pReg, pType, pValue, pCode=null){
Logger.debug("setSymbol: (reg=",pReg,", type=",pType,")");
// return this.symTab.addEntry(pReg, VTYPE.METH, pType, pValue, pCode);
return this.stack.getLocalSymbolTable().setSymbol(pReg, pType, pValue, pCode);
}
setGlobalSymbol(pName, pType, pValue, pCode=null){
Logger.debug("setGlobalSymbol: (reg=",pName,", type=",pType,")");
// return this.globalSymTab.addEntry(pName, VTYPE.VM, pType, pValue, pCode);
return this.metharea.setGlobalSymbol(pReg, pType, pValue, pCode);
}
/**
* To check if the Symbol/register has concrete value
*
* @param {Symbol} pSymbol The symbol to check
* @returns {Boolean} TRUE if the symbol has concrete value, else FALSE
*/
isImm(pSymbol){
if(pSymbol.type < DTYPE.OBJECT_REF && (pSymbol.value != null)){
return true;
}
else if(pSymbol.type == DTYPE.OBJECT_REF
&& (pSymbol.value instanceof VM_ClassInstance)
&& (pSymbol.getValue().hasConcrete())){
return true;
}else{
return false;
}
}
getImmediateValue(pSymbol, pSeparator="", pForce=false){
let v="";
switch(pSymbol.type)
{
case DTYPE.IMM_STRING:
v = `"${pSymbol.value}"${pSeparator}`;
break;
case DTYPE.IMM_NUMERIC:
case DTYPE.IMM_SHORT:
case DTYPE.IMM_LONG:
case DTYPE.IMM_DOUBLE:
case DTYPE.IMM_FLOAT:
v = `${pSymbol.value}${pSeparator}`;
break;
case DTYPE.IMM_BYTE:
v = `(byte)0x${pSymbol.value}${pSeparator}`;
break;
case DTYPE.IMM_BOOLEAN:
v = `${pSymbol.value}${pSeparator}`;
break;
case DTYPE.IMM_CHAR:
v = `'${pSymbol.value}'${pSeparator}`;
break;
case DTYPE.OBJECT_REF:
v = pSymbol.getValue().getConcrete();
if(typeof v == 'string'){
v = `"${v}"${pSeparator}`;
break;
}
default:
/*if(pForce)
v+= this.getRegisterName(oper.left[j])+pSeparator;
else*/
v =`<DECOMPILER_ERROR>${pSeparator} // ${pSymbol.type}`;
break;
}
return v;
}
moveRegister(pSrcRegister, pDestRegister){
let src = this.stack.getLocalSymbol(pSrcRegister);
this.stack.setLocalSymbol(pDestRegister, src.type, src.getValue(), src.getCode());
}
findOffsetByLabel( pStack, pLabel, pType){
for(let i=0; i<pStack.length; i++){
if(pStack[i].t=='b'){
//console.log(pStack[i].i.getGotoLabel(), pLabel);
if(pType == CONST.INSTR_TYPE.IF && pStack[i].i.getCondLabel()==pLabel){
return i;
}
else if(pType == CONST.INSTR_TYPE.GOTO && pStack[i].i.getGotoLabel()==pLabel){
return i;
}
}
}
return null;
}
/**
*
* @param {Method} pMethod
* @param {VM_ClassInstance} pObj
* @param {Symbol[]} pArgs Array of symbols containing arguments value or expr
*/
invoke( pMethod, pObj, pArgs){
let opt = null;
// execute hooks
if(this.isHooked(pMethod)){
this.stack.add( pMethod, pObj, pArgs);
console.log(this.stack.print());
this.execHook( pMethod, pObj, pArgs);
this.stack.pop();
return null;
}
// If mockAndroidInternals=TRUE, ignore invoke of Android Internal emthods
if(this.config.mockAndroidInternals && pMethod.hasTag('di')){
Logger.debugPink(`[VM] [INVOKE] Android Internal method ignored by config (mockAndroidInternals) : ${pMethod.signature()}`);
this.stack.pushReturn(new Symbol(
VTYPE.METH,
getDataTypeOf(pMethod.ret),
null,
null
))
return null;
}
// add method to callstack + symtab init
this.stack.add( pMethod, pObj, pArgs);
// set 'this' symbol if not null
if(pObj != null){
this.stack.last().setThis(pObj);
}
// set arguments symbol (import caller symbol into symbol table of called tables)
if((typeof pArgs == 'array') && pArgs.length > 0){
this.stack.last().setArguments(pArgs);
}
// run
opt = this.metharea.initMethod( pMethod);
this.run( opt.instr);
// pop from callstack and move result to return stack
this.stack.pop();
// return, if a value is returned the value is stored
return this.stack.lastReturn();
}
/**
*
* @param {Object[]} pStack
* @param {Integer} pDepth
*/
run( pStack, pDepth=0){
let i=0, f=0, dec=null, msg=null, ctxRST=null, bbs=null, mode=SINGLE_MODE ;
let indent = " ".repeat(this.depth);
// add emulated method to callstack;
do{
// instruction
if(pStack[i].t=='i'){
dec = this.execute(pStack, i);
if(dec.code.length > 0 && !Util.isEmpty(dec.code, Util.FLAG_WS | Util.FLAG_CR | Util.FLAG_TB)){
//console.log(dec.code);
//ssmali = ssmali.concat(dec.code);
this.pcmaker.push(dec.code[0]);
Logger.debug(`[PCMAKER] ${this.pcmaker.last()}`);
}/*else{
ssmali.push(`// empty block : dead code removed or block already simplified (contant propagated)`)
}*/
if(dec.jump != null){
i = this.findOffsetByLabel( pStack, dec.jump.label, dec.jump.type);
// if target block has multiple predecessors,
// add "goto" instruction to pseudo code
if(pStack[i].i.hasMultiplePredecessors()){
this.pcmaker.push(`${indent}goto :goto_${dec.jump.label}`);
}
}
else if(dec.inv != null){
if(this.stack.callstack.length==1) this.pcmaker.turnOff();
if( (this.config.maxdepth==-1) || (this.config.maxdepth - this.stack.depth()) > 0){
this.invoke( dec.inv.meth, dec.inv.obj, dec.inv.args);
if(this.stack.callstack.length==1){
this.pcmaker.turnOn();
if(dec.inv.meth.signature() == METH_INVOKE_SIGNATURE
&& this.config.simplify>0){
dec = this.stack.lastIndirectInvoke();
// replace last pseudo-code line by new one
this.pcmaker.pop();
this.pcmaker.writeIndirectInvoke(
// Method.invoke() context
pStack[i].i.left[1], pStack[i].i.left[2],
// invoked method
dec.method, dec.obj, dec.args);
}
}
// special case : Method.invoke() could be replace by targetde method
}else if(dec.inv.meth.ret != null){
if(this.stack.callstack.length==1) this.pcmaker.turnOn();
this.pcmaker.append(' // skipped, max depth reached');
this.stack.pushReturn( new Symbol(
VTYPE.METH,
getDataTypeOf(dec.inv.meth.ret),
null,
null,
Symbol.SKIPPED
));
}
if(this.stack.callstack.length==1) this.pcmaker.turnOn();
i++;
}
else if(dec.ret != null){
break;
}
else{
i++;
}
}
// enter into basic block
else if(pStack[i].t=='b'){
bbs = pStack[i].i;
f=0;
msg = '';
if(bbs.isConditionalBlock()){
//this.restoreContext(`:cond_${bbs.getCondLabel()}`);
ctxRST = true;
//ssmali.push(`${CR}cond_${bbs.getCondLabel()}:`);
this.pcmaker.push(`${CR}cond_${bbs.getCondLabel()}:`);
msg += `cond_${bbs.getCondLabel()}`;
f++;
}
if(bbs.isCatchBlock()){
if(f>0){
//ssmali.push(`${bbs.getCatchLabel()}:`);
this.pcmaker.push(`:${bbs.getCatchLabel()}`);
}else{
this.pcmaker.push(`${CR}${bbs.getCatchLabel()}`);
f++;
}
msg += `cond_${bbs.getCatchLabel()}`;
this.depth++;
}
if(bbs.isGotoBlock()){
/*if(this.contextExists(`:cond_${bbs.getGotoLabel()}`)){
this.restoreContext(`:cond_${bbs.getGotoLabel()}`);
}*/
if(bbs.hasMultiplePredecessors()){
if(f>0)
this.pcmaker.push(`:goto_${bbs.getGotoLabel()}:`);
else{
this.pcmaker.push(`${CR}goto_${bbs.getGotoLabel()}:`);
f++;
}
}
msg += `goto_${bbs.getGotoLabel()}`;
}
if(bbs.isTryBlock()){
if(f==0)
this.pcmaker.push(`try{`);
else
this.pcmaker.push(`${CR}try{`);
this.depth++;
}
if(msg.length>0)
Logger.debug(`[VM] [RUN] Enter into block : ${msg}`);
i++;
}
// leave basic block
else if(pStack[i].t=='e'){
bbs = pStack[i].i;
if(bbs.isTryEndBlock()){
if(bbs.hasCatchStatement()){
d = bbs.getCatchStatements();
for(let i=0; i<d.length; i++){
if(d[i].getException() != null)
this.pcmaker.push(`${" ".repeat(this.depth-1)}}catch(${d[i].getException().name}) ${d[i].getTarget().getCatchLabel()}`);
else
this.pcmaker.push(`${" ".repeat(this.depth-1)}}catchall ${d[i].getTarget().getCatchLabel()}`);
}
this.depth--;
}else{
this.pcmakersmali.push(`} try END\n`);
this.depth--;
}
}
else if(bbs.isCatchBlock()){
this.depth--;
}
i++;
}
}while(i<pStack.length);
if(dec.ret != null){
if(dec.ret instanceof Symbol){
this.stack.pushReturn(dec.ret);
// this.stack.last().setReturn(dec.ret);
}
}
if(pStack[i].t!='e' && bbs.isTryEndBlock()){
if(bbs.hasCatchStatement()){
d = bbs.getCatchStatements();
for(let i=0; i<d.length; i++){
if(d[i].getException() != null)
this.pcmaker.push(`${" ".repeat(this.depth-1)}}catch(${d[i].getException().name}) ${d[i].getTarget().getCatchLabel()}`);
else
this.pcmaker.push(`${" ".repeat(this.depth-1)}}catchall ${d[i].getTarget().getCatchLabel()}`);
}
}else{
this.pcmaker.push(`} try END\n`);
}
}
return this.pcmaker;
}
/**
* To execute an instruction into current context.
*
* Context contains :
* - Heap Memory
* - Stack Memory
* - Class Loaders
* - Method Area
*
* @param {Instruction[]} pInstrStack
* @param {Integer} pInstrOffset
*/
execute( pInstrStack, pInstrOffset){
let ops = [], dec=[], f={res:false}, v='', regX=null, regV=null, regZ=null, label=null, oper=null, tmp=[];
let state = { code:[], jump:null, ret:null, inv:null};
let regs = {};
let indent = " ".repeat(this.depth);
ops = pInstrStack;
oper = ops[pInstrOffset].i;
switch(oper.opcode.byte)
{
case OPCODE.NEW_INSTANCE.byte:
regX = this.getRegisterName(oper.left);
//regV = this.allocator.newInstance(oper.right)
this.stack.setLocalSymbol(regX, DTYPE.OBJECT_REF, this.heap.newInstance( oper.right));
break;
case OPCODE.CONST.byte:
case OPCODE.CONST_4.byte:
case OPCODE.CONST_16.byte:
case OPCODE.CONST_HIGH16.byte:
case OPCODE.CONST_WIDE.byte:
case OPCODE.CONST_WIDE_16.byte:
case OPCODE.CONST_WIDE_32.byte:
case OPCODE.CONST_WIDE_HIGH16.byte:
regX = this.getRegisterName(oper.left);
regV = this.stack.setLocalSymbol(regX, DTYPE.IMM_NUMERIC, oper.right._value, null);
// assigning concret value are ommited
if(this.simplify<1){
state.code.push(`${indent}${regX} = ${this.getImmediateValue(regV)};`);
}
break;
case OPCODE.CONST_STRING_JUMBO.byte:
case OPCODE.CONST_STRING.byte:
regX = this.getRegisterName(oper.left);
regV = this.stack.setLocalSymbol(regX, DTYPE.IMM_STRING, oper.right._value, null);
if(this.simplify<1)
state.code.push(`${indent}${regX} = (String) ${this.getImmediateValue(regV)};`);
break;
case OPCODE.CONST_CLASS.byte:
regX = this.getRegisterName(oper.left);
this.heap.loadClass(oper.right);
regV = this.stack.setLocalSymbol(regX, DTYPE.CLASS_REF, oper.right, oper.right.name+".class");
break;
case OPCODE.ADD_INT_LIT8.byte:
case OPCODE.ADD_INT_LIT16.byte:
//case OPCODE.SUB .byte:
//case OPCODE.SUB_INT_LIT16.byte:
case OPCODE.MUL_INT_LIT8.byte:
case OPCODE.MUL_INT_LIT16.byte:
case OPCODE.DIV_INT_LIT8.byte:
case OPCODE.DIV_INT_LIT16.byte:
case OPCODE.REM_INT_LIT8.byte:
case OPCODE.REM_INT_LIT16.byte:
case OPCODE.AND_INT_LIT8.byte:
case OPCODE.AND_INT_LIT16.byte:
case OPCODE.OR_INT_LIT8.byte:
case OPCODE.OR_INT_LIT16.byte:
case OPCODE.XOR_INT_LIT8.byte:
case OPCODE.XOR_INT_LIT16.byte:
case OPCODE.SHR_INT_LIT8.byte:
case OPCODE.SHL_INT_LIT8.byte:
case OPCODE.USHR_INT_LIT8.byte:
regX = this.getRegisterName(oper.left[1]);
regV = this.stack.getLocalSymbol(regX);
if(this.isImm(regV)){
regX = this.getRegisterName(oper.left[0]);
v = regV[SYMBOL_OPE[oper.opcode.ope]](oper.right.getValue(), oper.opcode.byte);
this.stack.setLocalSymbol(regX,
DTYPE.IMM_NUMERIC,
v,
'0x'+v.toString(16));
regV = this.stack.getLocalSymbol(regX);
v = `${indent}${this.getRegisterName(oper.left[0])} = ${this.getImmediateValue(regV)};`;
state.code.push(v);
// this.pcmaker.push(v);
}else{
regX = this.getRegisterName(oper.left[0]);
this.stack.setLocalSymbol(regX,
DTYPE.IMM_NUMERIC,
null, // regV[SYMBOL_OPE[oper.opcode.ope]](oper.right.getValue(), oper.opcode.byte)
v = `${(regV.hasCode()? '('+regV.getCode()+')':this.getRegisterName(oper.left[1]))}${oper.opcode.ope}${oper.right.getValue()}`);
// this.getSymbol(regX).setCode(`${this.getRegisterName(oper.left[1])}+${oper.right.getValue()}`).
state.code.push(`${indent}${this.getRegisterName(oper.left[0])} = ${v};`);
}
if(this.simplify<1){
v = `${indent}${this.getRegisterName(oper.left[0])} = ${this.getRegisterName(oper.left[1])}${oper.opcode.ope}${oper.right.getValue()};`;
state.code.push(v);
}
//dec.push(v);
//console.log('add-int/lit8 ',v);
break;
case OPCODE.ADD_INT.byte:
case OPCODE.ADD_FLOAT.byte:
case OPCODE.SUB_INT.byte:
case OPCODE.SUB_FLOAT.byte:
case OPCODE.MUL_INT.byte:
case OPCODE.MUL_FLOAT.byte:
case OPCODE.DIV_INT.byte:
case OPCODE.DIV_FLOAT.byte:
case OPCODE.REM_INT.byte:
case OPCODE.REM_FLOAT.byte:
if(this.simplify<1){
v = `${this.getRegisterName(oper.left[1])}${oper.opcode.ope}${this.getRegisterName(oper.right)}`;
this.stack.setLocalSymbol(regX,
DTYPE.IMM_NUMERIC,
regV.add(oper.right.getValue(), oper.opcode.byte),
v);
state.code.push(`${indent}${this.getRegisterName(oper.left[0])} = ${v};`);
break;
}else{
regX = this.getRegisterName(oper.right);
regV = this.stack.getLocalSymbol(regX);
if(this.isImm(regV)){
regX = this.getRegisterName(oper.left[1]);
if(this.isImm(this.getSymbol(regX)))
this.stack.setLocalSymbol(
this.getRegisterName(oper.left[0]),
DTYPE.IMM_NUMERIC,
regV[SYMBOL_OPE[oper.opcode.ope]](this.getImmediateValue(oper.right), oper.opcode.byte),
v = `${this.getRegisterName(oper.left[1])}${oper.opcode.ope}${this.getImmediateValue(regV)};`);
else{
v = `${this.getRegisterName(oper.left[1])}${oper.opcode.ope}${this.getImmediateValue(regV)};`;
this.stack.setLocalSymbol(
this.getRegisterName(oper.left[0]),
DTYPE.IMM_NUMERIC,
null,
v);
//state.code.push(`${indent}${this.getRegisterName(oper.left[0])} = ${v};`);
}
state.code.push(`${intent}${this.getRegisterName(oper.left[0])} = ${v}`);
break;
}
else if(regV.hasCode()){
v = `${this.getRegisterName(oper.left[1])}${oper.opcode.ope}(${regV.getCode()})`;
this.stack.setLocalSymbol(
this.getRegisterName(oper.left[0]),
DTYPE.IMM_NUMERIC,
null,
v);
state.code.push(`${intent}${this.getRegisterName(oper.left[0])} = ${v}`);
break;
}
regX = this.getRegisterName(oper.left[1]);
regV = this.stack.getLocalSymbol(regX);
if(this.isImm(regV)){
regX = this.stack.getLocalSymbol(this.getRegisterName(oper.left[0]));
// ${indent}${this.getRegisterName(oper.left[0])} =
v = `${this.getImmediateValue(oper.left[1])}${oper.opcode.ope}${this.getRegisterName(oper.right)}`;
this.stack.setLocalSymbol(
this.getRegisterName(oper.left[0]),
DTYPE.IMM_NUMERIC,
null,
v);
//regX.setCode(`${this.getImmediateValue(oper.left[1])}+${this.getRegisterName(oper.right)}`);
// state.code.push(v);
state.code.push(`${intent}${this.getRegisterName(oper.left[0])} = ${v}`);
break;
}
else if(regV.hasCode()){
//v = `${indent}${this.getRegisterName(oper.left[0])} = ${regV.getCode()}+${this.getRegisterName(oper.right)};`;
this.stack.setLocalSymbol(
this.getRegisterName(oper.left[0]),
DTYPE.IMM_NUMERIC,
null,
v = `${regV.getCode()}${oper.opcode.ope}${this.getRegisterName(oper.right)}`);
state.code.push(`${intent}${this.getRegisterName(oper.left[0])} = ${v}`);
//state.code.push(v);
}
else{
//v = `${indent}${this.getRegisterName(oper.left[0])} = ${this.getRegisterName(oper.left[1])}+${this.getRegisterName(oper.right)};`;
this.stack.setLocalSymbol(
this.getRegisterName(oper.left[0]),
DTYPE.IMM_NUMERIC,
null,
v = `${this.getRegisterName(oper.left[1])}${oper.opcode.ope}${this.getRegisterName(oper.right)}`);
state.code.push(`${intent}${this.getRegisterName(oper.left[0])} = ${v}`);
//state.code.push(v);
}
}
break;
case OPCODE.ADD_INT_2ADDR.byte:
case OPCODE.ADD_FLOAT_2ADDR.byte:
case OPCODE.SUB_INT_2ADDR.byte:
case OPCODE.SUB_FLOAT_2ADDR.byte:
case OPCODE.MUL_INT_2ADDR.byte:
case OPCODE.MUL_FLOAT_2ADDR.byte:
case OPCODE.DIV_INT_2ADDR.byte:
case OPCODE.DIV_FLOAT_2ADDR.byte:
case OPCODE.REM_INT_2ADDR.byte:
case OPCODE.REM_FLOAT_2ADDR.byte:
case OPCODE.AND_INT_2ADDR.byte:
case OPCODE.AND_LONG_2ADDR.byte:
case OPCODE.OR_INT_2ADDR.byte:
case OPCODE.OR_LONG_2ADDR.byte:
case OPCODE.XOR_INT_2ADDR.byte:
case OPCODE.XOR_LONG_2ADDR.byte:
case OPCODE.SHL_INT_2ADDR.byte:
case OPCODE.SHL_LONG_2ADDR.byte:
case OPCODE.SHR_INT_2ADDR.byte:
case OPCODE.SHR_LONG_2ADDR.byte:
case OPCODE.USHR_INT_2ADDR.byte:
case OPCODE.USHR_LONG_2ADDR.byte:
if(this.simplify<1){
v = `${this.getRegisterName(oper.left[0])}${oper.opcode.ope}${this.getRegisterName(oper.right)}`;
this.stack.setLocalSymbol(
this.getRegisterName(oper.left[0]),
DTYPE.IMM_NUMERIC,
null,
v);
state.code.push(`${indent}${this.getRegisterName(oper.left[0])} = ${v};`);
}else{
regX = this.getRegisterName(oper.right);
regX = this.stack.getLocalSymbol(regX);
if(this.isImm(regX)){
regV = this.getRegisterName(oper.left[0]);
regV = this.stack.getLocalSymbol(regV);
if(this.isImm(regV)){
// this.setSymbol(regX, regX.add(regV.getValue(), oper.opcode.byte));
this.stack.setLocalSymbol(
regV,
DTYPE.IMM_NUMERIC,
regX[SYMBOL_OPE[oper.opcode.ope]](regV.getValue(), oper.opcode.byte),
`${this.getImmediateValue(regX)}${oper.opcode.ope}${this.getImmediateValue(regV)}`);
break;
}
else{
if(regV.hasCode()){
this.stack.setLocalSymbol(
regV,
DTYPE.IMM_NUMERIC,
null,
`(${regV.getCode()})${oper.opcode.ope}${this.getImmediateValue(regV)}`);
}else{
this.stack.setLocalSymbol(
regV,
DTYPE.IMM_NUMERIC,
null,
`${this.getRegisterName(oper.left[0])}${oper.opcode.ope}${this.getImmediateValue(regV)}`);
}
}
}
else {
regV = this.getRegisterName(oper.left[0]);
regV = this.stack.getLocalSymbol(regV);
if(this.isImm(regV)){
if(regX.hasCode()){
this.stack.setLocalSymbol(
regV,
DTYPE.IMM_NUMERIC,
null,
`${this.getImmediateValue(regV)}${oper.opcode.ope}(${regX.getCode()})`);
}else{
this.stack.setLocalSymbol(
regV,
DTYPE.IMM_NUMERIC,
null,
`${this.getImmediateValue(regV)}${oper.opcode.ope}${this.getRegisterName(oper.right)}`);
}
break;
}
else{
this.stack.setLocalSymbol(
regV,
DTYPE.IMM_NUMERIC,
null,
`${(regV.hasCode()? '('+regV.getCode()+')':this.getRegisterName(oper.left[0]))}${oper.opcode.ope}${(regX.hasCode()? '('+regV.getCode()+')':this.getRegisterName(oper.right))}`);
}
}
}
break;
// long numbers are stored over two 32bits registers
// <op> <
case OPCODE.ADD_LONG.byte:
case OPCODE.SUB_LONG.byte:
case OPCODE.DIV_LONG.byte:
case OPCODE.MUL_LONG.byte:
case OPCODE.REM_LONG.byte:
case OPCODE.OR_LONG.byte:
case OPCODE.XOR_LONG.byte:
case OPCODE.AND_LONG.byte:
regs.src1 = this.prepareLong(oper.left[1]);
regs.src2 = this.prepareLong(oper.right);
regs.val = null;
regs.code = { m:null, l:null };
// if both long are concrete
if(regs.src1.v!==null && regs.src2.v!==null){
regs.val= regs.src1.m[SYMBOL_OPE[oper.opcode.ope]](
regs.src2.v, oper.opcode.byte, regs.src1.l.getValue());
}
else if(regs.src1.v !== null){
regs.code.m = `(${regs.src1.v}${oper.opcode.ope}${regs.src2.mn})`;
regs.code.l = `(${regs.src1.v}${oper.opcode.ope}${regs.src2.ln}) & 0xFFFFFFFF`;
}
else if(regs.src2.v !== null){
regs.code.m = `(${regs.src1.mn}${oper.opcode.ope}${regs.src2.v})`;
regs.code.l = `(${regs.src1.ln}${oper.opcode.ope}${regs.src2.v}) & 0xFFFFFFFF`;
}
else{
regs.code.m = `(${regs.src1.mn}${oper.opcode.ope}${regs.src2.mn})`;
regs.code.l = `(${regs.src1.ln}${oper.opcode.ope}${regs.src2.ln}) & 0xFFFFFFFF`;
}
this.stack.setLocalSymbol(
this.getRegisterName({ t:oper.left[0].t, i:oper.left[0].i }),
DTYPE.IMM_LONG,
regs.val!==null? (regs.val >> 32) & 0xFFFFFFFF : null,
regs.code.m);
this.stack.setLocalSymbol(
this.getRegisterName({ t:oper.left[0].t, i:oper.left[0].i+1 }),
DTYPE.IMM_NUMERIC,
regs.val!==null? regs.val & 0x00000000FFFFFFFF : null,
regs.code.l);
break;
case OPCODE.SHR_LONG.byte:
case OPCODE.SHL_LONG.byte:
case OPCODE.USHR_LONG.byte:
//TODO
break
case OPCODE.ADD_LONG_2ADDR.byte:
case OPCODE.SUB_LONG_2ADDR.byte:
case OPCODE.DIV_LONG_2ADDR.byte:
case OPCODE.MUL_LONG_2ADDR.byte:
case OPCODE.REM_LONG_2ADDR.byte:
case OPCODE.OR_LONG_2ADDR.byte:
case OPCODE.XOR_LONG_2ADDR.byte:
case OPCODE.AND_LONG_2ADDR.byte:
regs.src1 = this.prepareLong(oper.left);
regs.src2 = this.prepareLong(oper.right);
regs.val = null;
regs.code = { m:null, l:null };
// if both long are concrete
if(regs.src1.v!==null && regs.src2.v!==null){
regs.val= regs.src1.m[SYMBOL_OPE[oper.opcode.ope]](
regs.src2.v, oper.opcode.byte, regs.src1.l.getValue());
}
else if(regs.src1.v !== null){
regs.code.m = `(${regs.src1.v}${oper.opcode.ope}${regs.src2.mn})`;
regs.code.l = `(${regs.src1.v}${oper.opcode.ope}${regs.src2.ln}) & 0xFFFFFFFF`;
}
else if(regs.src2.v !== null){
regs.code.m = `(${regs.src1.mn}${oper.opcode.ope}${regs.src2.v})`;
regs.code.l = `(${regs.src1.ln}${oper.opcode.ope}${regs.src2.v}) & 0xFFFFFFFF`;
}
else{
regs.code.m = `(${regs.src1.mn}${oper.opcode.ope}${regs.src2.mn})`;
regs.code.l = `(${regs.src1.ln}${oper.opcode.ope}${regs.src2.ln}) & 0xFFFFFFFF`;
}
this.stack.setLocalSymbol(
this.getRegisterName({ t:oper.left.t, i:oper.left.i }),
DTYPE.IMM_LONG,
regs.val!==null? (regs.val >> 32) & 0xFFFFFFFF : null,
regs.code.m);
this.stack.setLocalSymbol(
this.getRegisterName({ t:oper.left.t, i:oper.left.i+1 }),
DTYPE.IMM_NUMERIC,
regs.val!==null? regs.val & 0x00000000FFFFFFFF : null,
regs.code.l);
break;
case OPCODE.SHR_LONG_2ADDR.byte:
case OPCODE.SHL_LONG_2ADDR.byte:
case OPCODE.USHR_LONG_2ADDR.byte:
// TODO : not supported
break;
case OPCODE.NEG_LONG.byte:
// TODO : not supported
break;
// long numbers are stored over two 32bits registers
// <op> v0,
case OPCODE.ADD_DOUBLE.byte:
case OPCODE.SUB_DOUBLE.byte:
case OPCODE.DIV_DOUBLE.byte:
case OPCODE.MUL_DOUBLE.byte:
case OPCODE.REM_DOUBLE.byte:
// TODO : not supported
break
case OPCODE.ADD_DOUBLE_2ADDR.byte:
case OPCODE.SUB_DOUBLE_2ADDR.byte:
case OPCODE.DIV_DOUBLE_2ADDR.byte:
case OPCODE.MUL_DOUBLE_2ADDR.byte:
case OPCODE.REM_DOUBLE_2ADDR.byte:
// TODO : not supported
break;
case OPCODE.NEG_INT.byte:
case OPCODE.NEG_FLOAT.byte:
case OPCODE.NEG_DOUBLE.byte:
regX = this.getRegisterName(oper.right);
regV = this.stack.getLocalSymbol(regX);
if(regV.getValue() !== null){
this.stack.setLocalSymbol(
this.getRegisterName(oper.left),
regV.type,
-regV.getValue(),
`${this.getImmediateValue(regV)}${oper.opcode.ope}(${regX.getCode()})`);
}
else if(regV.hasCode()){
this.stack.setLocalSymbol(
this.getRegisterName(oper.left),
regV.type,
null,
`-(${regV.getCode()})`);
}
else{
this.stack.setLocalSymbol(
this.getRegisterName(oper.left),
regV.type,
null,
`-${regX}`);
}
break;
// int-to-<op> <dest>, <src>
case OPCODE.INT_TO_BYTE.byte:
regX = this.getRegisterName(oper.right);
regV = this.stack.getLocalSymbol(regX);
v = null;
if(this.isImm(regV))
v = regV.getValue() & 0x000000FF;
this.stack.setLocalSymbol(
this.getRegisterName(oper.left), DTYPE.IMM_BYTE, v, `(byte) ${regX}`
);
break;
case OPCODE.INT_TO_CHAR.byte:
regX = this.getRegisterName(oper.right);
regV = this.stack.getLocalSymbol(regX);
v = null;
if(this.isImm(regV))
v = regV.getValue() & 0x000000FF;
this.stack.setLocalSymbol(
this.getRegisterName(oper.left), DTYPE.IMM_CHAR, v, `(char) ${regX}`
);
break;
case OPCODE.INT_TO_FLOAT.byte:
regX = this.getRegisterName(oper.right);
regV = this.stack.getLocalSymbol(regX);
v = null;
if(this.isImm(regV))
v = regV.getValue();
this.stack.setLocalSymbol(
this.getRegisterName(oper.left), DTYPE.IMM_FLOAT, v, `(float) ${regX}`
);
break;
case OPCODE.INT_TO_SHORT.byte:
regX = this.getRegisterName(oper.right);
regV = this.stack.getLocalSymbol(regX);
v = null;
if(this.isImm(regV))
v = regV.getValue() & 0x0000FFFF;
this.stack.setLocalSymbol(
this.getRegisterName(oper.left), DTYPE.IMM_SHORT, v, `(short) ${regX}`
);
break;
// cast involving multiple registers
case OPCODE.INT_TO_LONG.byte:
regX = this.getRegisterName(oper.right);
regV = this.stack.getLocalSymbol(regX);
v = null;
if(this.isImm(regV))
v = regV.getValue() & 0x0000FFFF;
// <dest> = <src>
this.stack.setLocalSymbol(
this.getRegisterName(oper.left), DTYPE.IMM_LONG, 0x00000000, `(long) ${regX}`
);
// <dest>+1 = 0x00000000
this.stack.setLocalSymbol(
this.getRegisterName({ t:oper.left.t, i:oper.left.i+1 }), DTYPE.IMM_NUMERIC, v, `((long)${regX} >> 32)`
);
break;
// verify :
// 0x cafebabe deadbeef => v0 = cafebabe ; v1 = deadbeef
case OPCODE.INT_TO_DOUBLE.byte:
regX = this.getRegisterName(oper.right);
regV = this.stack.getLocalSymbol(regX);
v = null;
if(this.isImm(regV))
v = regV.getValue() & 0x0000FFFF;
// <dest> = <src>
this.stack.setLocalSymbol(
this.getRegisterName(oper.left), DTYPE.IMM_DOUBLE, v, `(long) ${regX}`
);
// <dest>+1 = 0x00000000
this.stack.setLocalSymbol(
this.getRegisterName({ t:oper.left.t, i:oper.left.i+1 }), DTYPE.IMM_NUMERIC, 0x00000000, `((long)${regX} >> 32)`
);
break;
case OPCODE.MOVE_RESULT.byte:
case OPCODE.MOVE_RESULT_WIDE.byte:
regX = this.getRegisterName(oper.left);
//if(this.stack.ret.length > 0){
regV = this.stack.popReturn();
if(this.pcmaker.isEnable()){
v = this.pcmaker.last();
this.pcmaker.pop();
this.pcmaker.push(`${indent}${regX} = ${v.substr(indent.length,v.length)}`);
this.stack.importLocalSymbol(regX, regV, v);
}else if(regV != null){
this.stack.importLocalSymbol(regX, regV, null);
}
/*}else{
Logger.debug("move-result skipped");
}*/
break;
case OPCODE.MOVE_RESULT_OBJECT.byte:
regX = this.getRegisterName(oper.left);
if(this.stack.ret.length > 0){
regV = this.stack.popReturn();
if(this.pcmaker.isEnable()){
//onsole.log(v);
v = this.pcmaker.pop();
this.pcmaker.push(`${indent}${regX} = ${v.substr(indent.length,v.length)}`);
this.stack.importLocalSymbol(regX, regV, `${v.substr(indent.length,v.length)}`);
}else{
this.stack.importLocalSymbol(regX, regV, null);
}
}else{
Logger.debug("move-result skipped");
}
break;
case OPCODE.MOVE.byte:
case OPCODE.MOVE_16.byte:
case OPCODE.MOVE_FROM16.byte:
case OPCODE.MOVE_OBJECT.byte:
case OPCODE.MOVE_OBJECT_16.byte:
case OPCODE.MOVE_OBJECT_FROM16.byte:
case OPCODE.MOVE_WIDE.byte:
case OPCODE.MOVE_WIDE_16.byte:
case OPCODE.MOVE_WIDE_FROM16.byte:
this.moveRegister(
this.getRegisterName(oper.right),
this.getRegisterName(oper.left)
);
break;
case OPCODE.AGET.byte:
case OPCODE.AGET_BOOLEAN.byte:
case OPCODE.AGET_BYTE.byte:
case OPCODE.AGET_CHAR.byte:
case OPCODE.AGET_OBJECT.byte:
case OPCODE.AGET_SHORT.byte:
case OPCODE.AGET_WIDE.byte:
regX = this.stack.getLocalSymbol( this.getRegisterName(oper.right) ); // offset
regV = this.stack.getLocalSymbol( this.getRegisterName(oper.left[1]) ); // array
// TODO: Index Out-Of-Bound
if(this.isImm(regX)){
console.log(regX);
if(regV.getValue() instanceof VM_VirtualArray){
//console.log("imm - imm", regX, regX.getValue());
this.stack.setLocalSymbol(
this.getRegisterName(oper.left[0]),
ATYPE_DTYPE[oper.opcode.byte],
// regV.arrayRead(regX.getValue()),
regV.getValue().read(regX.getValue()),
v = `${regV.hasCode()?regV.getCode():this.getRegisterName(oper.left[1])}[${regX.getValue()}]`);
}else{
//console.log("imm - sym", regX, regX.getValue());
this.stack.setLocalSymbol(
this.getRegisterName(oper.left[0]),
ATYPE_DTYPE[oper.opcode.byte],
null,
v = `${regV.hasCode()?regV.getCode():this.getRegisterName(oper.left[1])}[${regX.getValue()}]`);
}
}
else{
this.stack.setLocalSymbol(
this.getRegisterName(oper.left[0]),
ATYPE_DTYPE[oper.opcode.byte],
null,
v = `${regV.hasCode()?regV.getCode():this.getRegisterName(oper.left[1])}[${regX.hasCode()?regX.getCode():this.getRegisterName(oper.right)}]`);
}
state.code.push(`${indent}${this.getRegisterName(oper.left[0])} = ${v}`);
break;
case OPCODE.APUT.byte:
case OPCODE.APUT_BOOLEAN.byte:
case OPCODE.APUT_BYTE.byte:
case OPCODE.APUT_CHAR.byte:
case OPCODE.APUT_OBJECT.byte:
case OPCODE.APUT_SHORT.byte:
case OPCODE.APUT_WIDE.byte:
regX = this.stack.getLocalSymbol( this.getRegisterName(oper.right) ); // offset
regV = this.stack.getLocalSymbol( this.getRegisterName(oper.left[1]) ); // array
regZ = this.stack.getLocalSymbol( this.getRegisterName(oper.left[0]) ); // value
// TODO : Index Out-Of-Bound, Out-Of-Memory
if(this.isImm(regX)){ // concrete offset
console.log('put concrete value');
if(regV.getValue() instanceof VM_VirtualArray){ // concrete value
console.log('put array',this.isImm(regZ),regZ.type < DTYPE.OBJECT_REF, (regZ.value != null));
if(this.isImm(regZ))
regV.arrayWrite(regX.getValue(), regZ.getValue());
else
regV.arrayWrite(regX.getValue(), regZ);
}else{
Logger.debug("Non concrete array detected");
console.log("Non concrete array detected");
}
}else{
console.log('put symbolic value')
// offset, // value
regV.arrayWriteSymbolic(regX, regZ);
}
if(regX.getValue()!=null){
label = regX.getValue();
}else{
label = regX.hasCode()? regX.getCode() : this.getRegisterName(oper.right);
}
if(regV.getValue() == null){
v = `${regV.hasCode()?regV.getCode():this.getRegisterName(oper.left[1])}[${label}] = `;
if(this.isImm(regZ)){
v += `${this.isImm(regZ)? this.getImmediateValue(regZ):this.getRegisterName(oper.left[0])};`;
}else if(regZ.hasCode()){
v += `(${regZ.getCode()});`;
}else{
v += `${this.getRegisterName(oper.left[0])};`;
}
state.code.push(v);
}
break;
case OPCODE.MOVE_EXCEPTION.byte:
// nothing todo
break;
case OPCODE.MONITOR_ENTER.byte:
state.code.push(`// monitor-enter`);
// nothing todo
break;
case OPCODE.MONITOR_EXIT.byte:
state.code.push(`// monitor-exit`);
// nothing todo
break;
case OPCODE.THROW.byte:
state.code.push(`${indent}throw ${this.getRegisterName(oper.left)}`);
// nothing todo
break;
case OPCODE.INVOKE_STATIC_RANGE.byte:
// init invoke
state.inv = { meth:oper.right, obj:null, args:[] };
v = `${indent}${oper.right.enclosingClass.alias!=null?oper.right.enclosingClass.alias:oper.right.enclosingClass.name}.${oper.right.alias!=null?oper.right.alias:oper.right.name}( `;
/*
if(oper.left[0].t != oper.left[1].t){
// TODO : Invalid range
}*/
if(oper.left.length > 0){
// parseInt(oper.left[0].i,10
for(let j=parseInt(oper.left[0].i,10); j<parseInt(oper.left[1].i,10)+1; j++){
regX = oper.left[0].t+j;
regV = this.stack.getLocalSymbol(regX);
//console.log(this.stack.print());
// add args
state.inv.args.push(regV);
//console.log(regV, this.isImm(regV), this.getImmediateValue(regV) );
if(this.isImm(regV))
v+= this.getImmediateValue(regV)+', ';
else if(regV.hasCode() && !regV.isSkipped())
v+= `${regV.getCode()}, `;
else
v+= regX+', ';
}
v = v.substr(0, v.length-2);
}
v += ')';
state.code.push(v);
f.res = true;
this.invokes.push(pInstrOffset);
break;
case OPCODE.INVOKE_STATIC.byte:
// init invoke
state.inv = { meth:oper.right, obj:null, args:[] };
// console.log(this.stack.print());
v = `${indent}${oper.right.enclosingClass.alias!=null?oper.right.enclosingClass.alias:oper.right.enclosingClass.name}.${oper.right.alias!=null?oper.right.alias:oper.right.name}( `;
if(oper.left.length > 0){
for(let j=0; j<oper.left.length; j++){
regX = this.getRegisterName(oper.left[j]);
regV = this.stack.getLocalSymbol(regX);
// add args
state.inv.args.push(regV);
if(this.isImm(regV))
v+= this.getImmediateValue(regV)+', ';
else if(regV.hasCode() && !regV.isSkipped())
v+= `${regV.getCode()}, `;
else
v+= regX+', ';
}
v = v.substr(0, v.length-2);
}
v += ')';
state.code.push(v);
f.res = true;
this.invokes.push(pInstrOffset);
break;
case OPCODE.INVOKE_SUPER.byte:
case OPCODE.INVOKE_VIRTUAL.byte:
case OPCODE.INVOKE_DIRECT.byte:
case OPCODE.INVOKE_INTERFACE.byte:
regX = this.getRegisterName(oper.left[0]);
regV = this.stack.getLocalSymbol(regX);
console.log("invoke "+oper.right.signature());
// init invoke
state.inv = { meth:oper.right, obj:regV, args:[] };
if(oper.left.length > 1){
for(let j=1; j<oper.left.length; j++){
tmp = this.getRegisterName(oper.left[j]);
regV = this.stack.getLocalSymbol(tmp);
state.inv.args.push(regV);
}
}
this.pcmaker.writeInvoke( oper.right, oper.left);
break;
case OPCODE.IGET.byte:
case OPCODE.IGET_BYTE.byte:
case OPCODE.IGET_CHAR.byte:
case OPCODE.IGET_OBJECT.byte:
case OPCODE.IGET_SHORT.byte:
case OPCODE.IGET_WIDE.byte:
case OPCODE.IGET_BOOLEAN.byte:
regX = this.getRegisterName(oper.left[0]);
regV = oper.right.type._name;
// read get
regZ = this.stack.getLocalSymbol(this.getRegisterName(oper.left[1]));
if(regZ.type == DTYPE.OBJECT_REF){
label = (this.getRegisterName(oper.left[1])=="p0")? "this": this.getRegisterName(oper.left[1]) ;
if(regZ.getValue() instanceof VM_ClassInstance){
v = `${regV.endsWith(".String")?"":"("+regV+")"} ${label}.${oper.right.alias!=null? oper.right.alias : oper.right.name}`;
this.stack.setLocalSymbol(regX, getDataTypeOf( oper.right.type), regZ.getValue().readField(oper.right), v);
}else{
v = `${regV.endsWith(".String")?"":"("+regV+")"} ${label}.${oper.right.alias!=null? oper.right.alias : oper.right.name}`;
this.stack.setLocalSymbol(regX, getDataTypeOf( oper.right.type), null, v);
}
state.code.push(`${indent}${regX} = ${v};`);
Logger.debug(`${indent}${regX} = ${v};`);
}else{
// error => subject should be an object ref
Logger.error(`[VM][EXEC] Invalid '${oper.opcode.instr}' instruction : ${this.getRegisterName(oper.left[1])} is not an object reference`);
}
/*
this.stack.setLocalSymbol(regX, getDataTypeOf( oper.right.type), null, v);
if(this.getRegisterName(oper.left[1])=="p0" && (this.method.modifiers.static==false)){
if(oper.right.enclosingClass.name == this.method.enclosingClass.name){
v = `${regV.endsWith(".String")?"":"("+regV+")"} this.${oper.right.alias!=null? oper.right.alias : oper.right.name}`;
}else{
v = `${regV.endsWith(".String")?"":"("+regV+")"} p0.${oper.right.alias!=null? oper.right.alias : oper.right.name}`;
}
}else{
v = `${regV.endsWith(".String")?"":"("+regV+")"} ${this.getRegisterName(oper.left[1])}.${oper.right.alias!=null? oper.right.alias : oper.right.name}`;
}
if(this.simplify<1){
this.stack.setLocalSymbol(regX, DTYPE.FIELD_REF, null, v);
state.code.push(`${indent}${regX} = ${v};`);
}else{
this.stack.setLocalSymbol(regX, DTYPE.FIELD_REF, null, v);
Logger.debug(`${indent}${regX} = ${v};`);
}*/
//co
break;
case OPCODE.SGET.byte:
case OPCODE.SGET_BYTE.byte:
case OPCODE.SGET_CHAR.byte:
case OPCODE.SGET_OBJECT.byte:
case OPCODE.SGET_SHORT.byte:
case OPCODE.SGET_WIDE.byte:
case OPCODE.SGET_BOOLEAN.byte:
regX = this.getRegisterName(oper.left);
regZ = this.metharea.getGlobalSymbol(oper.right.enclosingClass.name+'.'+oper.right.name);
regV = oper.right.type._name;
v = `${regV.endsWith(".String")?"":"("+regV+")"} ${oper.right.enclosingClass.alias!=null?oper.right.enclosingClass.alias:oper.right.enclosingClass.name}.${oper.right.alias!=null? oper.right.alias : oper.right.name}`;
if(this.isImm(regZ)){
this.stack.setLocalSymbol(regX, regZ.type, regZ.getValue(), null);
}
else if(regZ.hasCode()){
this.stack.setLocalSymbol(regX, regZ.type, null, regZ.getCode());
}
else{
this.stack.importLocalSymbol(regX, regZ);
}
if(this.config.simplify<1)
state.code.push(`${indent}${regX} = ${v};`);
break;
case OPCODE.IPUT.byte:
case OPCODE.IPUT_BOOLEAN.byte:
case OPCODE.IPUT_BYTE.byte:
case OPCODE.IPUT_CHAR.byte:
case OPCODE.IPUT_OBJECT.byte:
case OPCODE.IPUT_SHORT.byte:
case OPCODE.IPUT_WIDE.byte:
// data
regX = this.getRegisterName(oper.left[0]);
regV = this.stack.getLocalSymbol(regX);
// instance
regZ = this.getRegisterName(oper.left[1]);
regZ = this.stack.getLocalSymbol(regZ);
/*
if(regV.hasCode()){
regX = `(${regV.getCode()})`;
}
if(this.getRegisterName(oper.left[1])=="p0" && (this.method.modifiers.static==false)){
if(oper.right.enclosingClass.name == this.method.enclosingClass.name)
v = `${indent}this.${oper.right.name} = ${regX};`;
else
v = `${indent}p0.${oper.right.name} = ${regX}`;
}else{
regV = this.stack.getLocalSymbol(this.getRegisterName(oper.left[1]));
if(regV.hasCode()){
v = `${indent}(${regV.getCode()}).${oper.right.name} = ${regX};`;
}else{
v = `${indent}${this.getRegisterName(oper.left[1])}.${oper.right.name} = ${regX};`;
}
}
// -------------
if(this.getRegisterName(oper.left[1])=="p0"){
if(oper.right.enclosingClass.name == this.method.enclosingClass.name)
v = `${indent}this.${oper.right.name} = ${regX};`;
else
v = `${indent}p0.${oper.right.name} = ${regX}`;
}else{
regV = this.stack.getLocalSymbol(this.getRegisterName(oper.left[1]));
if(regV.hasCode()){
v = `${indent}(${regV.getCode()}).${oper.right.name} = ${regX};`;
}else{
v = `${indent}${this.getRegisterName(oper.left[1])}.${oper.right.name} = ${regX};`;
}
}*/
if(regZ.type == DTYPE.OBJECT_REF){
label = (this.getRegisterName(oper.left[1])=="p0")? "this": this.getRegisterName(oper.left[1]) ;
// v = `${regV.endsWith(".String")?"":"("+regV+")"} ${label}.${oper.right.alias!=null? oper.right.alias : oper.right.name}`;
v = `${label}.${oper.right.alias!=null? oper.right.alias : oper.right.name}`;
if(regV.getValue() instanceof VM_ClassInstance){
if( !regV.endsWith(".String") ){
v = "("+regV+") "+v;
}
regZ.getValue().setField( oper.right, this.stack.getLocalSymbol(regX));
}
/*else if(regZ.getValue() instanceof VM_VirtualArray){
regZ.getValue().setField( oper.right, this.stack.getLocalSymbol(regX));
}*/
else{
// ClassInstance => not defined
regZ.getValue().setField( oper.right, this.stack.getLocalSymbol(regX));
}
label = this.stack.getLocalSymbol(regX);
if(this.isImm(label)){
state.code.push(`${indent}${v} = ${label.getValue()};`);
Logger.debug(`${indent}${v} = ${label.getValue()};`);
}
else if(label.hasCode()){
state.code.push(`${indent}${v} = ${label.getCode()};`);
Logger.debug(`${indent}${v} = ${label.getCode()};`);
}
else{
state.code.push(`${indent}${v} = ${regX};`);
Logger.debug(`${indent}${v} = ${regX};`);
}
}else{
// error => subject should be an object ref
Logger.error(`[VM][EXEC] Invalid '${oper.opcode.instr}' instruction : ${this.getRegisterName(oper.left[1])} is not an object reference`);
}
//co
//this.setSymbol(`${this.getRegisterName(oper.left[1])}.${oper.right.name}`, DTYPE.FIELD_REF, oper.right.name, v);
//state.code.push(v);
break;
case OPCODE.SPUT.byte:
case OPCODE.SPUT_BOOLEAN.byte:
case OPCODE.SPUT_BYTE.byte:
case OPCODE.SPUT_CHAR.byte:
case OPCODE.SPUT_OBJECT.byte:
case OPCODE.SPUT_SHORT.byte:
case OPCODE.SPUT_WIDE.byte:
regX = this.getRegisterName(oper.left);
regV = this.stack.getLocalSymbol( regX);
if(this.isImm(regV)){
v = [`${indent}${oper.right.enclosingClass.name}.${oper.right.name}`,this.getImmediateValue(regV)];
this.metharea.setGlobalSymbol(
`${oper.right.enclosingClass.name}.${oper.right.name}`,
DTYPE.FIELD,
regV.getValue(),
`${v[0]} = ${v[1]}`);
}
else if(regV.getValue() instanceof VM_VirtualArray){
v = [`${oper.right.enclosingClass.name}.${oper.right.name}`,regV.getValue().toString()];
this.metharea.setGlobalSymbol(
`${oper.right.enclosingClass.name}.${oper.right.name}`,
DTYPE.FIELD,
regV.getValue(),
`${v[0]} = ${v[1]}`);
}
else if(regV.hasCode()){
v = [`${oper.right.enclosingClass.name}.${oper.right.name}`,regV.getCode()];
this.metharea.setGlobalSymbol(
`${oper.right.enclosingClass.name}.${oper.right.name}`,
DTYPE.FIELD,
null,
`${v[0]} = ${v[1]}`);
}
else{
v = [`${oper.right.enclosingClass.name}.${oper.right.name}`,regX];
this.metharea.setGlobalSymbol(
`${oper.right.enclosingClass.name}.${oper.right.name}`,
DTYPE.FIELD,
null,
`${v[0]} = ${v[1]}`);
}
/*
if(this.getRegisterName(oper.left)=="p0" && (this.method.modifiers.static==false)){
if(oper.right.enclosingClass.name == this.method.enclosingClass.name)
v = `${indent}this.${oper.right.name} = ${regX};`;
else
v = `${indent}p0.${oper.right.name} = ${regX}`;
}else{
v = `${indent}${this.getRegisterName(oper.left)}.${oper.right.name} = ${regX};`;
}*/
//co
state.code.push(`${indent}${v[0]} = ${v[1]}`);
break;
case OPCODE.RETURN.byte:
regX = this.getRegisterName(oper.left);
regV = this.stack.getLocalSymbol(regX);
if((regX=="p0") && (this.method.modifiers.static==false)){
if(oper.right.enclosingClass.name == this.method.enclosingClass.name){
v = `${indent}return this;`;
}else if(this.simplify >= 1 && this.isImm(regV)){
v = `${indent}return ${this.getImmediateValue(regV)};`;
}else
v = `${indent}return p0;`;
}else if(this.simplify >= 1 && this.isImm(regV))
v = `${indent}return ${this.getImmediateValue(regV)};`;
else if(regV.getValue()!=null && regV.getValue().hasConcrete()){
v = `${indent}return ${regX}; // ${regV.getValue().getConcrete()} `;
}else{
v = `${indent}return ${regX};`;
}
state.ret = regV;
state.code.push(v);
break;
case OPCODE.RETURN_OBJECT.byte:
regX = this.getRegisterName(oper.left);
regV = this.stack.getLocalSymbol(regX);
if(this.getRegisterName(oper.left)=="p0" && (this.method.modifiers.static==false)){
if(oper.right.enclosingClass.name == this.method.enclosingClass.name){
v = `${indent}return this;`;
//state.ret = regV;
}else if(this.simplify >= 1 && this.isImm(regV)){
v = `${indent}return ${this.getImmediateValue(regV)};`;
//state.ret = regV.getValue();
}else{
v = `${indent}return p0;`;
//state.ret = regV;
}
}
else{
if((regV.getValue() instanceof VM_ClassInstance) && regV.getValue().hasConcrete()){
v = `${indent}return ${regX}; // ${regV.getValue().getConcrete()} `;
}
else if( regV.getValue() != null){
v = `${indent}return ${regX}; // ${regV.getValue()} `;
}else
v = `${indent}return ${regX};`;
}
state.ret = regV;
state.code.push(v);
break;
case OPCODE.RETURN_WIDE.byte:
regX = this.getRegisterName(oper.left);
regV = this.stack.getLocalSymbol(regX);
if(this.isImm(regV)){
state.ret = new Symbol(VTYPE.METH, DTYPE.IMM_NUMERIC, new BigInt(regV.value), null );
}else{
state.ret = regV;
}
state.code.push(`${indent}return <TODO>;`);
break;
case OPCODE.RETURN_VOID.byte:
state.ret = RET_VOID;
state.code.push(`${indent}return ;`);
break;
// IF multi
case OPCODE.IF_EQ.byte:
regX = this.getRegisterName(oper.left[0]);
regV = this.getRegisterName(oper.left[1]);
label = `:cond_${oper.right.name}`;
this.saveContext(label);
if(this.config.simplify>0){
if(this.isImm(this.stack.getLocalSymbol(regX)))
regX = this.getImmediateValue(this.stack.getLocalSymbol(regX));
if(this.isImm(this.stack.getLocalSymbol(regV)))
regV = this.getImmediateValue(this.stack.getLocalSymbol(regV));
}
state.code.push(`${indent}if( ${regX} == ${regV} ) ${label}`);
break;
case OPCODE.IF_NE.byte:
regX = this.getRegisterName(oper.left[0]);
regV = this.getRegisterName(oper.left[1]);
label = `:cond_${oper.right.name}`;
this.saveContext(label);
if(this.config.simplify>0){
if(this.isImm(this.stack.getLocalSymbol(regX)))
regX = this.getImmediateValue(this.stack.getLocalSymbol(regX));
if(this.isImm(this.stack.getLocalSymbol(regV)))
regV = this.getImmediateValue(this.stack.getLocalSymbol(regV));
}
state.code.push(`${indent}if( ${regX} != ${regV} ) ${label}`);
break;
case OPCODE.IF_LT.byte:
regX = this.getRegisterName(oper.left[0]);
regV = this.getRegisterName(oper.left[1]);
label = `:cond_${oper.right.name}`;
this.saveContext(label);
if(this.config.simplify>0){
if(this.isImm(this.stack.getLocalSymbol(regX)))
regX = this.getImmediateValue(this.stack.getLocalSymbol(regX));
if(this.isImm(this.stack.getLocalSymbol(regV)))
regV = this.getImmediateValue(this.stack.getLocalSymbol(regV));
}
state.code.push(`${indent}if( ${regX} < ${regV} ) ${label}`);
break;
case OPCODE.IF_GE.byte:
regX = this.getRegisterName(oper.left[0]);
regV = this.getRegisterName(oper.left[1]);
label = `:cond_${oper.right.name}`;
this.saveContext(label);
if(this.config.simplify>=0){
regX = this.stack.getLocalSymbol(regX);
regV = this.stack.getLocalSymbol(regV);
if(regX==null || regV==null){
// impossible to identify valid path : queueing two path TRUE and FALSE paths
//this.getContext(label).updateIf();
Logger.debug("IF_GE : impossible");
state.code.push(`${indent}if( ${this.getRegisterName(oper.left[0])} <= ${this.getRegisterName(oper.left[1])} ) ${label}`);
state.jump = {type:CONST.INSTR_TYPE.IF, label:oper.right.name};
break;
}
if(this.isImm(regX) && this.isImm(regV)){
if(regX.type !== regV.type){
// need autocast
}
if(regX.getValue() >= regV.getValue()){
state.jump = {type:CONST.INSTR_TYPE.IF, label:oper.right.name};
}
state.code.push(`${indent}if( ${regX.getValue()} <= ${regV.getValue()} ) ${label}`);
Logger.debug("IF_GE : cmp is done");
}else{
// impossible to identify valid path : queueing two path TRUE and FALSE paths
// default is TRUE path, FALSE is queued
if(CONST.VM.DEFAULT_PATH){
state.jump = {type:CONST.INSTR_TYPE.IF, label:oper.right.name};
}
state.code.push(`${indent}if( ${this.getRegisterName(oper.left[0])} <= ${this.getRegisterName(oper.left[1])} ) ${label}`);
// else continue to execute
Logger.debug("IF_GE : default path");
}
}
break;
case OPCODE.IF_GT.byte:
regX = this.getRegisterName(oper.left[0]);
regV = this.getRegisterName(oper.left[1]);
label = `:cond_${oper.right.name}`;
this.saveContext(label);
if(this.config.simplify>0){
if(this.isImm(this.stack.getLocalSymbol(regX)))
regX = this.getImmediateValue(this.stack.getLocalSymbol(regX));
if(this.isImm(this.stack.getLocalSymbol(regV)))
regV = this.getImmediateValue(this.stack.getLocalSymbol(regV));
}
state.code.push(`${indent}if( ${regX} > ${regV} ) ${label}`);
break;
case OPCODE.IF_LE.byte:
regX = this.getRegisterName(oper.left[0]);
regV = this.getRegisterName(oper.left[1]);
label = `:cond_${oper.right.name}`;
this.saveContext(label);
if(this.config.simplify>0){
regX = this.stack.getLocalSymbol(regX);
regV = this.stack.getLocalSymbol(regV);
if(regX==null || regV==null){
// impossible to identify valid path : queueing two path TRUE and FALSE paths
//this.getContext(label).updateIf();
state.code.push(`${indent}if( ${this.getRegisterName(oper.left[0])} <= ${this.getRegisterName(oper.left[1])} ) ${label}`);
break;
}
if(this.isImm(regX) && this.isImm(regV)){
if(regX.type !== regV.type){
// need autocast
}
if(regX.getValue() < regV.getValue()){
state.jump = {type:CONST.INSTR_TYPE.IF, label:oper.right.name};
}
state.code.push(`${indent}if( ${regX.getValue()} <= ${regV.getValue()} ) ${label}`);
}else{
// impossible to identify valid path : queueing two path TRUE and FALSE paths
// default is TRUE path, FALSE is queued
if(CONST.VM.DEFAULT_PATH){
state.jump = {type:CONST.INSTR_TYPE.IF, label:oper.right.name};
}
state.code.push(`${indent}if( ${this.getRegisterName(oper.left[0])} <= ${this.getRegisterName(oper.left[1])} ) ${label}`);
// else continue to execute
}
}
break;
// IF zero
case OPCODE.IF_EQZ.byte:
regX = this.getRegisterName(oper.left);
regV = this.stack.getLocalSymbol(regX);
v = null;
label = `:cond_${oper.right.name}`;
//this.saveContext(label);
if(this.config.simplify>0){
console.log(regV);
if(this.isImm(regV)){
// FALSE case
if(regV.getValue() !== null){
v = `// ${regX}=${this.getImmediateValue(regV)} is not null, so "if(${regX} == 0)" was FALSE. Continue ...`;
}
// TRUE case
else{
v = `// ${regX} is null, so "if(${regX} == 0)" was TRUE. Jump to ${label}`;
state.jump = {type:CONST.INSTR_TYPE.IF, label:oper.right.name};
}
}else if(regV.type == DTYPE.OBJECT_REF){
console.log(regV);
// FALSE case
if(regV.getValue() instanceof VM_ClassInstance){
v = `// ${regX}=(ClassInstance)${regV.getValue().parent.name} is not null, so "if(${regX} == 0)" was FALSE. Continue ...`;
//v = `${indent}if( ${regV.getValue().getConcrete()} != null ) ${label}`;
}
// TRUE
else{
v = `// ${regX}(${regV.hasCode()? regV.getCode():"NULL object"}) is null, so "if(${regX} == 0)" was TRUE. Jump to ${label}`;
state.jump = {type:CONST.INSTR_TYPE.IF, label:oper.right.name};
}
/*
// Unknown case
else if(regV.hasCode()){
v = `${indent}if( ${regV.getCode()} != null ) ${label}`;
}
// TRUE case
else{
v = `// ${regX}(ref) is null, so "if(${regX} == 0)" was TRUE. Jump to ${label}`;
state.jump = {type:CONST.INSTR_TYPE.IF, label:oper.right.name};
}*/
}else{
// Unknown case
v = `${indent}if( ${regX} == 0 ) ${label}`;
this.saveContext(label);
state.jump = {type:CONST.INSTR_TYPE.IF, label:oper.right.name};
}
}else{
v = `if( ${regX} == 0 ) ${label}`;
}
if(v != null)
state.code.push(v);
break;
case OPCODE.IF_NEZ.byte:
/*
if ( x != 0 )
if ( x != null )
*/
regX = this.getRegisterName(oper.left);
regV = this.stack.getLocalSymbol(regX);
label = `:cond_${oper.right.name}`;
if(this.config.simplify>0){
if(this.isImm(regV)){
// TRUE case
if(regV.getValue() !== null){
v = `// ${regX}=${this.getImmediateValue(regV)} is not null, so "if(${regX} != 0)" was TRUE. Continue ...`;
}
// FALSE case
else{
v = `// ${regX} is null, so "if(${regX} != 0)" was FALSE. Jump to ${label}`;
state.jump = {type:CONST.INSTR_TYPE.IF, label:oper.right.name};
}
}else if(regV.type == DTYPE.OBJECT_REF){
// TRUE case
if(regV.getValue() instanceof VM_ClassInstance){
v = `// ${regX}=(ClassInstance)${regV.getValue().parent.name} is not null, so "if(${regX} != 0)" was TRUE. Continue ...`;
state.jump = {type:CONST.INSTR_TYPE.IF, label:oper.right.name};
//v = `${indent}if( ${regV.getValue().getConcrete()} != null ) ${label}`;
}
// Unknown case
else if(regV.hasCode()){
v = `${indent}if( ${regV.getCode()} != null ) ${label}`;
}
// FALSE case
else{
v = `// ${regX}(ref) is null, so "if(${regX} == 0)" was FALSE. Jump to ${label}`;
}
}else{
// Unknown case
v = `${indent}if( ${regX} != 0 ) ${label}`;
this.saveContext(label);
state.jump = {type:CONST.INSTR_TYPE.IF, label:oper.right.name};
}
}else{
v = `if( ${regX} != 0 ) ${label}`;
}
if(v != null)
state.code.push(v);
break;
case OPCODE.IF_LTZ.byte:
regX = this.getRegisterName(oper.left);
regV = this.stack.getLocalSymbol(regX);
label = `:cond_${oper.right.name}`;
this.saveContext(label);
if(this.config.simplify>0 && this.isImm(regV))
state.code.push(`${indent}if( ${this.getImmediateValue(regV)} < 0 )`);
else
state.code.push(`${indent}if( ${regX} < 0 ) ${label}`);
break;
case OPCODE.IF_GEZ.byte:
regX = this.getRegisterName(oper.left);
regV = this.stack.getLocalSymbol(regX);
label = `:cond_${oper.right.name}`;
this.saveContext(label);
if(this.config.simplify>0 && this.isImm(regV))
state.code.push(`${indent}if( ${this.getImmediateValue(regV)} >= 0 ) ${label}`);
else
state.code.push(`${indent}if( ${regX} >= 0 ) ${label}`);
break;
case OPCODE.IF_GTZ.byte:
regX = this.getRegisterName(oper.left);
regV = this.stack.getLocalSymbol(regX);
label = `:cond_${oper.right.name}`;
this.saveContext(label);
if(this.config.simplify>0 && this.isImm(regV))
state.code.push(`${indent}if( ${this.getImmediateValue(regV)} > 0 ) ${label}`);
else
state.code.push(`${indent}if( ${regX} > 0 ) ${label}`);
break;
case OPCODE.IF_LEZ.byte:
regX = this.getRegisterName(oper.left);
regV = this.stack.getLocalSymbol(regX);
label = `:cond_${oper.right.name}`;
this.saveContext(label);
if(this.config.simplify>0 && this.isImm(regV))
state.code.push(`${indent}if( ${this.getImmediateValue(regV)} <= 0 ) ${label}`);
else
state.code.push(`${indent}if( ${regX} <= 0 ) ${label}`);
break;
case OPCODE.ARRAY_LENGTH.byte:
regX = this.getRegisterName(oper.right);
regX = this.stack.getLocalSymbol(regX);
regV = this.getRegisterName(oper.left);
if(regX.getValue() instanceof VM_VirtualArray){
this.stack.setLocalSymbol(
regV,
DTYPE.IMM_NUMERIC,
v = regX.getValue().realSize(),
null
);
if(this.config.simplify<1){
state.code.push(`${indent} ${regV} = ${v}`);
}
}else{
// TODO : track unitiliazed array
}
break;
case OPCODE.FILL_ARRAY_DATA.byte:
regX = this.getRegisterName(oper.left);
regV = this.stack.getLocalSymbol(regX);
if(regV.getValue() instanceof VM_VirtualArray){
//console.log(oper.right.name);
v = this.method.getDataBlockByName(':array_'+oper.right.name);
regV.getValue().fillWith(v);
if(this.config.simplify<1){
state.code.push(`${indent} ${regX} = ${regV.getValue().toString()}`);
}
}else{
throw VM_Exception('VM001','fill-array-data cannot be executed : '+regX+' is not an array');
}
break;
case OPCODE.GOTO.byte:
case OPCODE.GOTO_16.byte:
case OPCODE.GOTO_32.byte:
label = `:goto_${oper.right.name}`;
//console.log("GOTO save state for ",label);
//console.log(this.stack.current());
this.saveContext(label);
//if(this.simplify<1){
// state.code.push(`${indent}goto ${label}`);
//}else{
// get basic block
state.jump = {type:CONST.INSTR_TYPE.GOTO, label:oper.right.name};
//}
break;
case OPCODE.ARRAY_LENGTH.byte:
regX = this.getRegisterName( oper.right );
regV = this.stack.getLocalSymbol( regX);
v = null;
if(regV != undefined){
if(regV.hasValue() && (regV.getReferencedValue() instanceof VM_VirtualArray)){
this.stack.setLocalSymbol(
this.getRegisterName(oper.left),
DTYPE.IMM_NUMERIC,
regV.getReferencedValue().size(),
v = `${regX}.length`)
}
else if(regV.hasCode()){
this.stack.setLocalSymbol(
this.getRegisterName(oper.left),
DTYPE.IMM_NUMERIC,
null,
v = `${regV.getCode()}.length`)
}else{
this.stack.setLocalSymbol(
this.getRegisterName(oper.left),
DTYPE.IMM_NUMERIC,
null,
v = `${regX}.length`)
}
v = `${this.getRegisterName(oper.left)} = ${v}`;
}else{
Logger.debug("[VM] array-length called with undefined array");
this.throwError(regX, regV, oper, "array is undefined");
v = `${oper._raw} // array-length called with undefined array `;
}
if(this.config.simplify<5){
state.code.push(v);
}
break;
case OPCODE.NEW_ARRAY.byte:
regX = this.getRegisterName( oper.left[1] ); // size
regZ = this.getRegisterName( oper.left[0] ); // where the array should be stored
regV = this.stack.getLocalSymbol( regX);
//console.log(oper.right);
if(oper.right instanceof CLASS.BasicType){
v = oper.right._name;
}else{
v = oper.right.name;
}
if(this.isImm(regV))
v = `new ${v}[${this.getImmediateValue(regV)}]`;
else if(regV.hasCode())
v = `new ${v}[${regV.getCode()}]`;
else
v = `new ${v}[${regX}]`;
if(this.config.simplify<1){
state.code.push(`${indent}${regZ} = ${v};`);
//this.pcmaker.optimize(regZ);
}
if( regV.getValue() !== null){
this.stack.setLocalSymbol(
regZ,
DTYPE.ARRAY,
this.allocator.newArray(oper.right, regV.getValue()),
null);
}else{
this.stack.setLocalSymbol(
regZ,
DTYPE.ARRAY,
this.allocator.newArray(oper.right).setSymbolicSize(regV.getCode()),
null);
}
//state.code.push(`${indent}${this.getRegisterName(oper.left[0])} = new ${oper.right._name}[${regX}];`);
break;
case OPCODE.CHECK_CAST.byte:
regV = this.stack.getLocalSymbol( this.getRegisterName(oper.left));
console.log(regV);
//if( regV.getValue().name !== oper.right.name &&
break;
case OPCODE.NOP.byte:
break;
default:
this.countUntreated++;
}
if(state.code[0]=="") state.code.shift();
return state;
}
throwError( pRegister, pSymbol, pInstruction, pMessage){
// TODO
Logger.error(`[VM][ERROR] "${pRegister}" into [${pInstruction.toString()}] : ${pMessage}`);
}
}
module.exports = {
VM: VM,
Symbol: Symbol,
SymbolTable: SymTable,
DTYPE: DTYPE,
VTYPE: VTYPE
};