FrenchYeti/dexcalibur

View on GitHub
src/Decompiler.js

Summary

Maintainability
F
2 mos
Test Coverage
'use strict';

const CLASS = require("./CoreClass.js");
const CONST = require("./CoreConst.js");
const OPCODE = require('./Opcode.js').OPCODE;
var Logger = require("./Logger.js")();

const VTYPE = {
    METH: 0x1
};


// modif : isImm
const DTYPE = {
    IMM_STRING: 0x1,
    IMM_NUMERIC: 0x2,
    IMM_FLOAT: 0x3,
    IMM_BOOLEAN: 0x4,
    IMM_BYTE: 0x5,
    OBJECT_REF: 0x6,
    CLASS_REF: 0x7,
    FIELD_REF: 0x8,
    VOID: 0x9,
    UNDEFINED: 0xa,
    THIS: 0xb
};

const BTYPE_DTYPE = {};
BTYPE_DTYPE[CONST.JAVA.T_BOOL] = DTYPE.IMM_BOOLEAN;
BTYPE_DTYPE[CONST.JAVA.T_CHAR] = DTYPE.IMM_NUMERIC;
BTYPE_DTYPE[CONST.JAVA.T_INT] = DTYPE.IMM_NUMERIC;
BTYPE_DTYPE[CONST.JAVA.T_LONG] = DTYPE.IMM_NUMERIC;
BTYPE_DTYPE[CONST.JAVA.T_SHORT] = DTYPE.IMM_FLOAT;
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_FLOAT;
BTYPE_DTYPE[CONST.JAVA.T_OBJ] = DTYPE.OBJECT_REF;
BTYPE_DTYPE[CONST.JAVA.T_VOID] = DTYPE.VOID;

class Symbol
{
    constructor(pVisibility, pType, pValue, pCode=null){
        this.type = pType;
        this.value = pValue;
        this.visibility = pVisibility;
        this.code = pCode;
        this.regs = []; 
    }

    setCode(pCode){
        this.code = pCode;
    }

    getCode(){
        return this.code;
    }

    hasCode(){
        return this.code != null;
    }

    getValue(){
        return this.value;
    }

    isThis(pMethod){
        return (pMethod instanceof CLASS.Method) && (pMethod.modifiers.static==false);
    }

    add(pValue, pType){
        switch(pType){
            case OPCODE.ADD_INT_2ADDR.byte:
            case OPCODE.ADD_LONG_2ADDR.byte:
            case OPCODE.ADD_INT_LIT8.byte:
            case OPCODE.ADD_INT_LIT16.byte:
            case OPCODE.ADD_INT.byte:
            case OPCODE.ADD_LONG.byte:
                return parseInt(this.value) + parseInt(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){
        switch(pType){
            case OPCODE.SUB_INT_2ADDR.byte:
            case OPCODE.SUB_LONG_2ADDR.byte:
            case OPCODE.SUB_INT_LIT8.byte:
            case OPCODE.SUB_INT_LIT16.byte:
            case OPCODE.SUB_INT.byte:
            case OPCODE.SUB_LONG.byte:
                return parseInt(this.value) - parseInt(pValue);
            case OPCODE.SUB_DOUBLE_2ADDR.byte:
            case OPCODE.SUB_FLOAT_2ADDR.byte:
            case OPCODE.SUB_DOUBLE.byte:
            case OPCODE.SUB_FLOAT.byte:
                return parseFloat(this.value) - parseFloat(pValue);
        }
    }

    mul(pValue, pType){
        switch(pType){
            case OPCODE.MUL_INT_2ADDR.byte:
            case OPCODE.MUL_LONG_2ADDR.byte:
            case OPCODE.MUL_INT_LIT8.byte:
            case OPCODE.MUL_INT_LIT16.byte:
            case OPCODE.MUL_INT.byte:
            case OPCODE.MUL_LONG.byte:
                return parseInt(this.value) * parseInt(pValue);
            case OPCODE.MUL_DOUBLE_2ADDR.byte:
            case OPCODE.MUL_FLOAT_2ADDR.byte:
            case OPCODE.MUL_DOUBLE.byte:
            case OPCODE.MUL_FLOAT.byte:
                return parseFloat(this.value) * parseFloat(pValue);
        }
    }


    div(pValue, pType){
        switch(pType){
            case OPCODE.DIV_INT_2ADDR.byte:
            case OPCODE.DIV_LONG_2ADDR.byte:
            case OPCODE.DIV_INT_LIT8.byte:
            case OPCODE.DIV_INT_LIT16.byte:
            case OPCODE.DIV_INT.byte:
            case OPCODE.DIV_LONG.byte:
                return parseInt(this.value) / parseInt(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){
        switch(pType){
            case OPCODE.REM_INT_2ADDR.byte:
            case OPCODE.REM_LONG_2ADDR.byte:
            case OPCODE.REM_INT_LIT8.byte:
            case OPCODE.REM_INT_LIT8.byte:
            case OPCODE.REM_INT_LIT16.byte:
            case OPCODE.REM_INT.byte:
            case OPCODE.REM_LONG.byte:
                return parseInt(this.value) % parseInt(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){
        switch(pType){
            case OPCODE.AND_INT_2ADDR.byte:
            case OPCODE.AND_LONG_2ADDR.byte:
            case OPCODE.AND_INT_LIT8.byte:
            case OPCODE.AND_INT_LIT16.byte:
            case OPCODE.AND_INT.byte:
            case OPCODE.AND_LONG.byte:
                return parseInt(this.value) & parseInt(pValue);
        }
    }


    or(pValue, pType){
        switch(pType){
            case OPCODE.OR_INT_2ADDR.byte:
            case OPCODE.OR_LONG_2ADDR.byte:
            case OPCODE.OR_INT_LIT8.byte:
            case OPCODE.OR_INT_LIT16.byte:
            case OPCODE.OR_INT.byte:
            case OPCODE.OR_LONG.byte:
                return parseInt(this.value) | parseInt(pValue);
        }
    }


    xor(pValue, pType){
        switch(pType){
            case OPCODE.XOR_INT_2ADDR.byte:
            case OPCODE.XOR_LONG_2ADDR.byte:
            case OPCODE.XOR_INT_LIT8.byte:
            case OPCODE.XOR_INT_LIT16.byte:
            case OPCODE.XOR_INT.byte:
            case OPCODE.XOR_LONG.byte:
                return parseInt(this.value) ^ parseInt(pValue);
        }
    }


    shl(pValue, pType){
        switch(pType){
            case OPCODE.SHL_INT_2ADDR.byte:
            case OPCODE.SHL_LONG_2ADDR.byte:
            case OPCODE.SHL_INT_LIT8.byte:
            case OPCODE.SHL_INT_LIT16.byte:
            case OPCODE.SHL_INT.byte:
            case OPCODE.SHL_LONG.byte:
                return parseInt(this.value) << parseInt(pValue);
        }
    }


    shr(pValue, pType){
        switch(pType){
            case OPCODE.SHR_INT_2ADDR.byte:
            case OPCODE.SHR_LONG_2ADDR.byte:
            case OPCODE.SHR_INT_LIT8.byte:
            case OPCODE.SHR_INT_LIT16.byte:
            case OPCODE.SHR_INT.byte:
            case OPCODE.SHR_LONG.byte:
                return parseInt(this.value) >> parseInt(pValue);
        }
    }


    ushr(pValue, pType){
        switch(pType){
            case OPCODE.USHR_INT_2ADDR.byte:
            case OPCODE.USHR_LONG_2ADDR.byte:
            case OPCODE.USHR_INT_LIT8.byte:
            case OPCODE.USHR_INT_LIT16.byte:
            case OPCODE.USHR_INT.byte:
            case OPCODE.USHR_LONG.byte:
                return parseInt(this.value) >>> parseInt(pValue);
        }
    }    
}

class SymTable
{
    constructor(){
        this.table = [];
    }

    addEntry(pSymbol, pVisibility, pType, pValue){
        this.table[pSymbol] =  new Symbol(
            pVisibility,
            pType,
            pValue
        );

        return this.table[pSymbol];
    }

    getEntry(pSymbol){
        return this.table[pSymbol];
    }

    hasEntry(pSymbol){
        return (this.table[pSymbol]!==undefined);
    }
}

class VM
{
    constructor(pMethod, pLocalSize, pParamSize){
        this.code = [];
        this.registers = {};
        this.symTab = new SymTable();
        this.invokes = [];
        this.method = pMethod;
        this.simplify = 0;
        this.countUntreated = 0;
        this.branch = null;
        this.depth = 0;

        this.initRegisters(pMethod, pLocalSize, pParamSize);
    }

    initRegisters(pMethod, pLocalSize, pParamSize){

        Logger.debug(`[VM] 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=",this.getDataTypeOf(arg),")");
            this.setSymbol('p'+i, this.getDataTypeOf(arg), null); // arg   
        }


        // init local registers
        for(let i=0; i<pLocalSize; i++){
            this.setSymbol('v'+i, DTYPE.UNDEFINED, null);
        }

        this.countUntreated = 0;
    }

    cleanVisitedBlock(){
        let block = this.method.getBasicBlocks();
        for(let i=0; i<block.length; i++){
            block[i].initVisit();
        }
    }

    setSimplifyingLevel(pLevel){
        this.simplify = pLevel;
    }

    setContext(pCode){
        this.code = pCode;
    }

    run(pInstruction){
        pInstruction.opcode.eval(pVm, pInstruction);
    }

    getRegisterName(pReg){
        if(pReg.t=='v')
            return "v"+pReg.i
        else
            return "p"+pReg.i;
    }

    getSymbol(pReg){
        return this.symTab.getEntry(pReg); //this.registers[pReg];
    }

    getDataTypeOf(pType){
        Logger.debug("getDataTypeOf: ",pType);
        if(pType instanceof CLASS.ObjectType){
            return DTYPE.OBJECT_REF;
        }else{
            return BTYPE_DTYPE[pType.name];
        }
    }


    setSymbol(pReg, pType, pValue){
        Logger.debug("setSymbol: (reg=",pReg,", type=",pType,")");
        return this.symTab.addEntry(pReg, VTYPE.METH, pType, pValue);
    }

    isImm(pSymbol){
        return pSymbol.type < DTYPE.OBJECT_REF && (pSymbol.value != null);
    }

    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_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.FIELD_REF:
                console.log(tmp);
                v+= `${tmp.code},`;
                break;*/
            default:
                /*if(pForce)
                    v+= this.getRegisterName(oper.left[j])+pSeparator;
                else*/
                v =`<DECOMPILER_ERROR>${pSeparator}`;
                break;
        }
        return v;
    }


    detectIfElse(pBlocks){
        let f=false;
        for(let i=0; i<pBlocks.length; i++){
            console.log(pBlocks[i].offset);
            if(pBlocks[i].isConditionalBlock()) 
                f=true;
        }
        return f;
    }

    dump(pBlock){
        let p=pBlock.getSuccessors(), v="";
        v+=`${pBlock.offset} => `;
        for(let i=0; i<p.length; i++) v+= " :: "+p[i].offset+" ";
        return "[ "+v+" ]";
    }

    runDecompile(pTree, pSource=[], pDepth=0, pStop=false, pClose=false){
        let cont = true, source=pSource, res={ depth:0 }, succ=null, pred=[], d=null, tmp=null;
        let next = [], ifelse=false;
        
        console.log(this.dump(pTree));

        Logger.debug(pTree.offset+" => "+pTree.isConditionalBlock());

        if(pTree.isGotoBlock()){
            source.push(`goto_${pTree.getGotoLabel()}:`);
        }
        if(pTree.isTryBlock()){
            source.push(`${"    ".repeat(pDepth)}try{`);
            pDepth++;
        }
        
        if(pTree.isVisited()==false){
            res = this.decompileBasicBlock(pTree, pDepth);
            //console.log(pTree);
            pTree.visit();

            source = source.concat(res.code);
        }

        if(pClose)
            source.push(`${"    ".repeat(pDepth)}}`);

        Logger.debug(pTree.offset+" (END)=> "+pTree.isConditionalBlock());


        pred = pTree.getPredecessors();
        succ = pTree.getSuccessors();

        /*if(pred.length>1 && pWidth<pred.length){

            source.push(`${"    ".repeat(pDepth)}}else{`);
            for(let i=pWidth; i<pred.length; i++){
                if(pred[i].isVisited()) continue;
                source = this.runDecompile(pred[i], source, pDepth, pWidth+1);
            }
            source.push(`${"    ".repeat(pDepth)}}`);

        }else*/ 
        if(pTree.hasSuccessors()){
            if(succ.length == 1 && !succ[0].isVisited()){

                if(!succ[0].hasMultiplePredecessors())
                    source = this.runDecompile(succ[0], source, pDepth, true);
                else{
                    source = this.runDecompile(succ[0], source, pDepth-1, true);
                }
            }else{
                // explore blocks with single predecessor
                ifelse = this.detectIfElse(succ);
                for(let i=0; i<succ.length; i++){
                    if(succ[i].isVisited()) continue;
                    if(succ[i].hasMultiplePredecessors()){
                        next.push(succ[i]);
                        //this.branch.push(succ[i]);
                        continue;
                    } 
                    if(ifelse){
                        if(succ[i].isConditionalBlock()){
                            cont = source.pop();
                            source.push(cont+'{');
                        }else{
                            source.push(`${"    ".repeat(pDepth)}else{`);
                        }
                    }
                    // pDepth+(succ[i].isConditionalBlock()?1:0)
                    source = this.runDecompile(succ[i], source, pDepth+1, true, true);
                    source.push(`${"    ".repeat(pDepth)}}`);
                }
            }
        }
        
        if(pTree.isTryEndBlock()){
            if(pTree.hasCatchStatement()){
                d = pTree.getCatchStatements();
                for(let i=0; i<d.length; i++){
                    source.push(`${"    ".repeat(pDepth-1)}}catch(${d[i].getException().name}){`);
                    source = this.runDecompile(d[i].getTarget(), source, pDepth+1, false);
                    source.push(`${"    ".repeat(pDepth-1)}}`);
                }
            }
        }   

        // treat blocks with multiple predecessors
        //this.branch
        for(let i=0; i<next.length; i++){
            if(next[i].isVisited()==false){
                source = this.runDecompile(next[i], source, pDepth, false);
            }
        }


        return source;
    }


    runSimplify(){
        let ssmali=[], dec=null, f=0, pDepth=0, d=null;
        let bbs = this.method.getBasicBlocks();

        for(let i=0; i<bbs.length; i++){
            f = 0;
            if(bbs[i].isConditionalBlock()){
                ssmali.push(`\ncond_${bbs[i].getCondLabel()}:`);
                f++;
            }
            if(bbs[i].isGotoBlock()){
                if(f>0) 
                    ssmali.push(`goto_${bbs[i].getGotoLabel()}:`);
                else{
                    ssmali.push(`\ngoto_${bbs[i].getGotoLabel()}:`);
                    f++;
                }
            }
            if(bbs[i].isTryBlock()){
                if(f==0) 
                    ssmali.push(`try{`);
                else
                    ssmali.push(`\ntry{`);

                this.depth++;
            }


            dec = this.simplifyBasicBlock(bbs[i], this.depth);  
            ssmali = ssmali.concat(dec.code);      

            if(bbs[i].isTryEndBlock()){
                if(bbs[i].hasCatchStatement()){
                    d = bbs[i].getCatchStatements();
                    for(let i=0; i<d.length; i++){
                        if(d[i].getException() != null)
                            ssmali.push(`${"    ".repeat(this.depth-1)}}catch(${d[i].getException().name}) :${d[i].getTarget().getCatchLabel()}`);
                        else
                            ssmali.push(`${"    ".repeat(this.depth-1)}}catchall :${d[i].getTarget().getCatchLabel()}`);
                    }
                }else{
                    ssmali.push(`} try END\n`);
                    this.depth--;
                }
            }
        }

        Logger.debug('[SIMPLIFIER] '+this.countUntreated+' instructions not treated');
        return ssmali;
    }

    decompileBasicBlock(pBlock, pDepth=0){
        let ops = [], dec=[],  f={res:false}, v='', regX=null,  regV=null, oper=null, tmp=[];
        let indent = "    ".repeat(pDepth);

        ops = pBlock.getInstructions();  ;  
        dec.push("");
        for(let k=0; k<ops.length; k++){
            v = '';
            oper = ops[k];
            switch(oper.opcode.byte)
            {
                case OPCODE.NEW_INSTANCE.byte:

                    regX = this.getRegisterName(oper.left);
                    this.setSymbol(regX, DTYPE.CLASS_REF, oper.right.name);
                    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.setSymbol(regX, DTYPE.IMM_NUMERIC, oper.right._value);
                    if(this.simplify<1)
                        dec.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.setSymbol(regX, DTYPE.IMM_STRING, oper.right._value);

                    if(this.simplify<1)
                        dec.push(`${indent}String ${regX} = ${this.getImmediateValue(regV)};`);
                    break;

                case OPCODE.MOVE_RESULT.byte:
                case OPCODE.MOVE_RESULT_WIDE.byte:
//                        console.log(oper);

                    regX = this.getRegisterName(oper.left);
                    if(this.invokes.length > 0){
                        regV = this.invokes.pop();
                        if(ops[regV]==undefined){
                            this.setSymbol(regX, this.getDataTypeOf(ops[k-1].right.ret), null);
                        }else
                            this.setSymbol(regX, this.getDataTypeOf(ops[regV].right.ret), null);
                        /*
                        if(ops[regV]==undefined){
                            this.setSymbol(regX, DTYPE.IMM_NUMERIC, ops[k-1].right.ret);
                        }else
                            this.setSymbol(regX, DTYPE.IMM_NUMERIC, ops[regV].right.ret);*/
                    }else{
                        Logger.debug("move-result skipped");
                        //this.setSymbol(regX, DTYPE.IMM_NUMERIC, null);
                        break;
                    }

                    v = dec.pop();
                    if(f.res == true){
                        dec.push(`${indent}${regX} = ${v.substr(indent.length,v.length)}`);
                        f.res = false;
                    }


                    break;

                case OPCODE.MOVE_RESULT_OBJECT.byte:

                    regX = this.getRegisterName(oper.left);
                    if(this.invokes.length > 0){
                        regV = this.invokes.pop();
                        if(ops[regV]==undefined){
                            this.setSymbol(regX, DTYPE.OBJECT_REF, ops[k-1].right.ret);
                        }else
                            this.setSymbol(regX, DTYPE.OBJECT_REF, ops[regV].right.ret);
                    }else{
                        Logger.debug("move-result-object skipped");
                        //this.setSymbol(regX, DTYPE.IMM_NUMERIC, null);
                        break;
                    }

                    v = dec.pop();
                    if(f.res == true){
                        dec.push(`${indent}${regX} = ${v.substr(indent.length,v.length)}`);
                        f.res = false;
                    }

                    break;

                case OPCODE.INVOKE_STATIC.byte:
                    v = `${indent}${oper.right.enclosingClass.name}.${ops[k].right.name}(`;
                    if(oper.left.length > 0){
                        for(let j=0; j<oper.left.length; j++){
                            regX = this.getRegisterName(oper.left[j]);
                            regV = this.getSymbol(regX);

                            if(this.isImm(regV))
                                v+= this.getImmediateValue(regV)+',';
                            else
                                v+= regX+',';

                        } 
                        v = v.substr(0, v.length-1);
                    }
                    v += ');';
                    dec.push(v);
                    f.res = true;
                    break;

                case OPCODE.INVOKE_VIRTUAL.byte:
                case OPCODE.INVOKE_DIRECT.byte:
                case OPCODE.INVOKE_INTERFACE.byte:
                    regX = this.getRegisterName(oper.left[0]);
                    regV = this.getSymbol(regX);

                    if((oper.right instanceof CLASS.Method) && (oper.right.name=="<init>"))
                        v = `${regX} = new ${oper.right.enclosingClass.name}(`;
                    else if(method.modifiers.static==false && regX=="p0"){
                        v = `this.${oper.right.name}(`;
                        f.res = true;
                        this.invokes.push(k);
                    }
                    else{
                        v = `${regX}.${oper.right.name}(`;
                        f.res = true;
                        this.invokes.push(k);
                    }

                    if(oper.left.length > 1){
                        for(let j=1; j<oper.left.length; j++){

                            tmp = this.getRegisterName(oper.left[j]);
                            regV = this.getSymbol(tmp);

                            if(this.simplify >= 1 && this.isImm(regV))
                                v += `${this.getImmediateValue(regV)},`;
                            else{
                                v += `${tmp},`;
                            }
                        } 
                        v = v.substr(0, v.length-1);
                    }
                    v += ');';
                    dec.push(`${indent}${v}`);
                    break;


                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;
                    
                    if(this.getRegisterName(oper.left[1])=="p0" && (this.method.modifiers.static==false)){
                        if(oper.right.enclosingClass.name == this.method.enclosingClass.name)
                            v = `${indent}${regV} ${regX} = this.${oper.right.name};`;
                        else
                            v = `${indent}${regV} ${regX} = p0.${oper.right.name};`;

                    }else{
                        v = `${indent}${regV} ${regX} = ${this.getRegisterName(oper.left[1])}.${oper.right.name};`;
                    }


                    //co
                    this.setSymbol(regX, DTYPE.FIELD_REF, oper.right.name, v);
                    dec.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[0]);
                        
                    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{
                        v = `${indent}${this.getRegisterName(oper.left[1])}.${oper.right.name} = ${regX};`;
                    }

                    //co
                    //this.setSymbol(`${this.getRegisterName(oper.left[1])}.${oper.right.name}`, DTYPE.FIELD_REF, oper.right.name, v);
                    dec.push(v);
                    break;

                case OPCODE.ADD_INT_LIT8.byte:
                case OPCODE.ADD_INT_LIT8.byte:
                    if(this.simplify<1){
                        v = `${this.getRegisterName(oper.left[0])} = ${this.getRegisterName(oper.left[1])}+${this.getImmediateValue(oper.right)};`;

                    }else{
                        regX = this.getRegisterName(oper.left[1]);
                        regV = this.getSymbol(regX);

                        if(this.isImm(regV)){
                            regX = this.getRegisterName(oper.left[0]);
                            this.setSymbol(regX, regV.addInt8(this.getImmediateValue(oper.right)));
                        }else{
                           v = `${this.getRegisterName(oper.left[0])} = ${this.getRegisterName(oper.left[1])}+${this.getImmediateValue(oper.right)};`;
                        }
                    }
                    break;

                /*case OPCODE.ADD_DOUBLE.byte:
                case OPCODE.ADD_FLOAT.byte:

                    
                    break;*/

                case OPCODE.RETURN.byte:
                    regX = this.getRegisterName(oper.left);
                    regV = this.getSymbol(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{

                        v = `${indent}return ${regX};`;
                    }

                    dec.push(v);
                    break;
                case OPCODE.RETURN_OBJECT.byte:
                    regX = this.getRegisterName(oper.left);
                    regV = this.getSymbol(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;`;
                        else if(this.simplify >= 1 && this.isImm(regV))
                            v = `${indent}return ${this.getImmediateValue(regV)};`;
                        else 
                            v = `${indent}return p0;`;

                    }
                    else{
                        v = `${indent}return ${regX};`;
                    }

                    dec.push(v);
                    break;
                case OPCODE.RETURN_WIDE.byte:
                    dec.push(`${indent}return <TODO>;`);
                    break;
                case OPCODE.RETURN_VOID.byte:
                    dec.push(`${indent}return ;`);
                    break;
                
                // IF multi
                case OPCODE.IF_EQ.byte:                
                    regX = this.getRegisterName(oper.left[0]);
                    regV = this.getRegisterName(oper.left[1]);

                    if(this.simplify>0){
                        if(this.isImm(this.getSymbol(regX)))
                            regX = this.getImmediateValue(this.getSymbol(regX));
                        if(this.isImm(this.getSymbol(regV)))
                            regV = this.getImmediateValue(this.getSymbol(regV));
                    }

                    dec.push(`${indent}if( ${regX} == ${regV} )`);
                    break;
                case OPCODE.IF_NE.byte:                
                    regX = this.getRegisterName(oper.left[0]);
                    regV = this.getRegisterName(oper.left[1]);

                    if(this.simplify>0){
                        if(this.isImm(this.getSymbol(regX)))
                            regX = this.getImmediateValue(this.getSymbol(regX));
                        if(this.isImm(this.getSymbol(regV)))
                            regV = this.getImmediateValue(this.getSymbol(regV));
                    }

                    dec.push(`${indent}if( ${regX} != ${regV} )`);
                    break;
                case OPCODE.IF_LT.byte:                
                    regX = this.getRegisterName(oper.left[0]);
                    regV = this.getRegisterName(oper.left[1]);

                    if(this.simplify>0){
                        if(this.isImm(this.getSymbol(regX)))
                            regX = this.getImmediateValue(this.getSymbol(regX));
                        if(this.isImm(this.getSymbol(regV)))
                            regV = this.getImmediateValue(this.getSymbol(regV));
                    }

                    dec.push(`${indent}if( ${regX} < ${regV} )`);
                    break;
                case OPCODE.IF_GE.byte:                
                    regX = this.getRegisterName(oper.left[0]);
                    regV = this.getRegisterName(oper.left[1]);

                    if(this.simplify>0){
                        if(this.isImm(this.getSymbol(regX)))
                            regX = this.getImmediateValue(this.getSymbol(regX));
                        if(this.isImm(this.getSymbol(regV)))
                            regV = this.getImmediateValue(this.getSymbol(regV));
                    }

                    dec.push(`${indent}if( ${regX} >= ${regV} )`);
                    break;
                case OPCODE.IF_GT.byte:                
                    regX = this.getRegisterName(oper.left[0]);
                    regV = this.getRegisterName(oper.left[1]);

                    if(this.simplify>0){
                        if(this.isImm(this.getSymbol(regX)))
                            regX = this.getImmediateValue(this.getSymbol(regX));
                        if(this.isImm(this.getSymbol(regV)))
                            regV = this.getImmediateValue(this.getSymbol(regV));
                    }

                    dec.push(`${indent}if( ${regX} > ${regV} )`);
                    break;
                case OPCODE.IF_LE.byte:                
                    regX = this.getRegisterName(oper.left[0]);
                    regV = this.getRegisterName(oper.left[1]);
                    
                    if(this.simplify>0){
                        if(this.isImm(this.getSymbol(regX)))
                            regX = this.getImmediateValue(this.getSymbol(regX));
                        if(this.isImm(this.getSymbol(regV)))
                            regV = this.getImmediateValue(this.getSymbol(regV));
                    }
                        
                    dec.push(`${indent}if( ${regX} <= ${regV} )`);
                    break;

                // IF zero
                case OPCODE.IF_EQZ.byte:                
                    regX = this.getRegisterName(oper.left);
                    regV = this.getSymbol(regX);
                    if(this.simplify>0){
                        if(this.isImm(regV))
                            dec.push(`${indent}if( ${this.getImmediateValue(regV)} == 0 )`);
                        else if(regV.type == DTYPE.OBJECT_REF){
                            dec.push(`${indent}if( ${regX} != null )`);
                        }else{
                            dec.push(`${indent}if( ${regX} == 0 )`);
                        }
                    }else
                        dec.push(`if( ${regX} == 0 )`);
                    break;
                case OPCODE.IF_NEZ.byte:                
                    regX = this.getRegisterName(oper.left);
                    regV = this.getSymbol(regX);
                    if(this.simplify>0 && this.isImm(regV))
                        dec.push(`${indent}if( ${this.getImmediateValue(regV)} != 0 )`);
                    else
                        dec.push(`${indent}if( ${regX} != 0 )`);
                    break;
                case OPCODE.IF_LTZ.byte:                
                    regX = this.getRegisterName(oper.left);
                    regV = this.getSymbol(regX);
                    if(this.simplify>0 && this.isImm(regV))
                        dec.push(`${indent}if( ${this.getImmediateValue(regV)} < 0 )`);
                    else
                        dec.push(`${indent}if( ${regX} < 0 )`);
                    break;
                case OPCODE.IF_GEZ.byte:                
                    regX = this.getRegisterName(oper.left);
                    regV = this.getSymbol(regX);
                    if(this.simplify>0 && this.isImm(regV))
                        dec.push(`${indent}if( ${this.getImmediateValue(regV)} >= 0 )`);
                    else
                        dec.push(`${indent}if( ${regX} >= 0 )`);
                    break;
                case OPCODE.IF_GTZ.byte:                
                    regX = this.getRegisterName(oper.left);
                    regV = this.getSymbol(regX);
                    if(this.simplify>0 && this.isImm(regV))
                        dec.push(`${indent}if( ${this.getImmediateValue(regV)} > 0 )`);
                    else
                        dec.push(`${indent}if( ${regX} > 0 )`);
                    break;
                case OPCODE.IF_LEZ.byte:                
                    regX = this.getRegisterName(oper.left);
                    regV = this.getSymbol(regX);
                    if(this.simplify>0 && this.isImm(regV))
                        dec.push(`${indent}if( ${this.getImmediateValue(regV)} <= 0 )`);
                    else
                        dec.push(`${indent}if( ${regX} <= 0 )`);
                    break;
                /*case OPCODE.MONITOR_ENTER.byte:
                    dec.push("try{");
                    break;
                case OPCODE.MONITOR_ENTER.byte:
                    dec.push("try{");
                    break;*/
            }
        }

        if(dec[0]=="") dec.shift();

        return { code:dec, depth:pDepth };
    }



    simplifyBasicBlock(pBlock, pDepth=0){
        let ops = [], dec=[],  f={res:false}, v='', regX=null,  regV=null, oper=null, tmp=[];
        let indent = "    ".repeat(pDepth);

        ops = pBlock.getInstructions();  ;  
        dec.push("");
        for(let k=0; k<ops.length; k++){
            v = '';
            oper = ops[k];
            switch(oper.opcode.byte)
            {
                case OPCODE.NEW_INSTANCE.byte:

                    regX = this.getRegisterName(oper.left);
                    this.setSymbol(regX, DTYPE.CLASS_REF, oper.right.name);
                    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.setSymbol(regX, DTYPE.IMM_NUMERIC, oper.right._value);
                    if(this.simplify<1)
                        dec.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.setSymbol(regX, DTYPE.IMM_STRING, oper.right._value);

                    if(this.simplify<1)
                        dec.push(`${indent}${regX} = (String) ${this.getImmediateValue(regV)};`);
                    break;

                
                case OPCODE.ADD_INT_LIT8.byte:
                case OPCODE.ADD_INT_LIT16.byte:
                    if(this.simplify<1){
                        v = `${this.getRegisterName(oper.left[0])} = ${this.getRegisterName(oper.left[1])}+${this.getImmediateValue(oper.right)};`;

                    }else{
                        regX = this.getRegisterName(oper.left[1]);
                        regV = this.getSymbol(regX);

                        if(this.isImm(regV)){
                            regX = this.getRegisterName(oper.left[0]);
                            this.setSymbol(regX, regV.add(this.getImmediateValue(oper.right), oper.opcode.byte));
                        }else{
                            v = `${this.getRegisterName(oper.left[0])} = ${this.getRegisterName(oper.left[1])}+${this.getImmediateValue(oper.right)};`;
                        }
                    }
                    break;
                case OPCODE.ADD_INT.byte:
                case OPCODE.ADD_LONG.byte:
                case OPCODE.ADD_DOUBLE.byte:
                case OPCODE.ADD_FLOAT.byte:
                    if(this.simplify<1){
                        v = `${this.getRegisterName(oper.left[0])} = ${this.getRegisterName(oper.left[1])}+${this.getRegisterName(oper.right)};`;
                        
                    }else{
                        regX = this.getRegisterName(oper.right);
                        regV = this.getSymbol(regX);

                        if(this.isImm(regV)){
                            regX = this.getRegisterName(oper.left[1]);

                            if(this.isImm(this.getSymbol(regX)))
                                this.setSymbol(this.getRegisterName(oper.left[0]), regV.add(this.getImmediateValue(oper.right), oper.opcode.byte));
                            else{
                                v = `${this.getRegisterName(oper.left[0])} = ${this.getRegisterName(oper.left[1])}+${this.getImmediateValue(regV)};`;                    
                                dec.push(v);
                            }
                            break;
                        }
                        

                        regX = this.getRegisterName(oper.left[1]);
                        regV = this.getSymbol(regX);
                        if(this.isImm(regV)){
                            regX = this.getSymbol(this.getRegisterName(oper.left[0]));

                            v = `${this.getRegisterName(oper.left[0])} = ${this.getImmediateValue(oper.left[1])}+${this.getRegisterName(oper.right)};`;
                            regX.setCode(v);

                            break;
                        }
                        else{
                            v = `${this.getRegisterName(oper.left[0])} = ${this.getRegisterName(oper.left[1])}+${this.getRegisterName(oper.right)};`;
                            dec.push(v);
                        }
                    }
                    break;

                case OPCODE.ADD_INT_2ADDR.byte:
                case OPCODE.ADD_LONG_2ADDR.byte:
                case OPCODE.ADD_DOUBLE_2ADDR.byte:
                case OPCODE.ADD_FLOAT_2ADDR.byte:
                    if(this.simplify<1){
                        v = `${this.getRegisterName(oper.left[0])} = ${this.getRegisterName(oper.left[0])}+${this.getRegisterName(oper.right)};`;

                        dec.push(v);
                    }else{
                        regX = this.getRegisterName(oper.right);
                        regV = this.getSymbol(regX);

                        if(this.isImm(regV)){

                            regX = this.getRegisterName(oper.left[0]);
                            regX = this.getSymbol(regX);

                            if(this.isImm(regX)){
                                this.setSymbol(regX, regX.add(regV.getValue(), oper.opcode.byte));
                            }

                        }else{
                            v = `${this.getRegisterName(oper.left[0])} = ${this.getRegisterName(oper.left[1])}+${this.getImmediateValue(oper.right)};`;
                             dec.push(v);
                        }
                    }
                    
                    break;
                case OPCODE.MOVE_RESULT.byte:
                case OPCODE.MOVE_RESULT_WIDE.byte:
//                        console.log(oper);

                    regX = this.getRegisterName(oper.left);
                    if(this.invokes.length > 0){
                        regV = this.invokes.pop();

                        if(ops[regV]==undefined){
                            this.setSymbol(regX, this.getDataTypeOf(ops[k-1].right.ret), null);
                        }else
                            this.setSymbol(regX, this.getDataTypeOf(ops[regV].right.ret), null);

                        /*    
                        if(ops[regV]==undefined){
                            this.setSymbol(regX, DTYPE.IMM_NUMERIC, ops[k-1].right.ret);
                        }else
                            this.setSymbol(regX, DTYPE.IMM_NUMERIC, ops[regV].right.ret);*/
                    }else{
                        Logger.debug("move-result skipped");
                        //this.setSymbol(regX, DTYPE.IMM_NUMERIC, null);
                        break;
                    }

                    v = dec.pop();
                    if(f.res == true){
                        dec.push(`${indent}${regX} = ${v.substr(indent.length,v.length)}`);
                        f.res = false;
                    }


                    break;

                case OPCODE.MOVE_RESULT_OBJECT.byte:

                    regX = this.getRegisterName(oper.left);
                    if(this.invokes.length > 0){
                        regV = this.invokes.pop();
                        if(ops[regV]==undefined){
                            this.setSymbol(regX, DTYPE.OBJECT_REF, ops[k-1].right.ret);
                        }else
                            this.setSymbol(regX, DTYPE.OBJECT_REF, ops[regV].right.ret);
                    }else{
                        Logger.debug("move-result-object skipped");
                        //this.setSymbol(regX, DTYPE.IMM_NUMERIC, null);
                        break;
                    }

                    v = dec.pop();
                    if(f.res == true){
                        dec.push(`${indent}${regX} = ${v.substr(indent.length,v.length)}`);
                        f.res = false;
                    }

                    break;

                
                case OPCODE.MOVE_EXCEPTION.byte:
                    // nothing todo
                    break;

                case OPCODE.MONITOR_ENTER.byte:
                    dec.push(`// monitor-enter`);
                    // nothing todo
                    break;
                case OPCODE.MONITOR_EXIT.byte:
                    dec.push(`// monitor-exit`);
                    // nothing todo
                    break;
                case OPCODE.THROW.byte:
                    dec.push(`${indent}throw ${this.getRegisterName(oper.left)}`);
                    // nothing todo
                    break;
                case OPCODE.INVOKE_STATIC.byte:
                    v = `${indent}${oper.right.enclosingClass.name}.${ops[k].right.name}(`;
                    if(oper.left.length > 0){
                        for(let j=0; j<oper.left.length; j++){
                            regX = this.getRegisterName(oper.left[j]);
                            regV = this.getSymbol(regX);

                            if(this.isImm(regV))
                                v+= this.getImmediateValue(regV)+',';
                            else
                                v+= regX+',';

                        } 
                        v = v.substr(0, v.length-1);
                    }
                    v += ');';
                    dec.push(v);
                    f.res = true;
                    break;

                case OPCODE.INVOKE_VIRTUAL.byte:
                case OPCODE.INVOKE_DIRECT.byte:
                case OPCODE.INVOKE_INTERFACE.byte:
                    regX = this.getRegisterName(oper.left[0]);
                    regV = this.getSymbol(regX);

                    if((oper.right instanceof CLASS.Method) && (oper.right.name=="<init>"))
                        v = `${indent}${regX} = new ${oper.right.enclosingClass.name}(`;
                    else if(method.modifiers.static==false && regX=="p0"){
                        v = `${indent}this.${oper.right.name}(`;
                        f.res = true;
                        this.invokes.push(k);
                    }
                    else{
                        v = `${indent}${regX}.${oper.right.name}(`;
                        f.res = true;
                        this.invokes.push(k);
                    }

                    if(oper.left.length > 1){
                        for(let j=1; j<oper.left.length; j++){

                            tmp = this.getRegisterName(oper.left[j]);
                            regV = this.getSymbol(tmp);

                            if(this.simplify >= 1 && this.isImm(regV))
                                v += `${this.getImmediateValue(regV)},`;
                            else{
                                v += `${tmp},`;
                            }
                        } 
                        v = v.substr(0, v.length-1);
                    }
                    v += ');';
                    dec.push(`${indent}${v}`);
                    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;
                    
                    if(this.getRegisterName(oper.left[1])=="p0" && (this.method.modifiers.static==false)){
                        if(oper.right.enclosingClass.name == this.method.enclosingClass.name)
                            v = `${indent}${regX} = (${regV}) this.${oper.right.name};`;
                        else
                            v = `${indent}${regX} = (${regV})  p0.${oper.right.name};`;

                    }else{
                        v = `${indent}${regX} = (${regV}) ${this.getRegisterName(oper.left[1])}.${oper.right.name};`;
                    }


                    //co
                    this.setSymbol(regX, DTYPE.FIELD_REF, oper.right.name, v);
                    dec.push(v);
                    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[0]);
                    regV = oper.right.type.name;
                    
                    if(this.getRegisterName(oper.left[1])=="p0" && (this.method.modifiers.static==false)){
                        if(oper.right.enclosingClass.name == this.method.enclosingClass.name)
                            v = `${indent}${regX} = (${regV}) this.${oper.right.name};`;
                        else
                            v = `${indent}${regX} = (${regV})  p0.${oper.right.name};`;

                    }else{
                        v = `${indent}${regX} = (${regV}) ${this.getRegisterName(oper.left[1])}.${oper.right.name};`;
                    }


                    //co
                    this.setSymbol(regX, DTYPE.FIELD_REF, oper.right.name, v);
                    dec.push(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:
                    
                        regX = this.getRegisterName(oper.left[0]);
                        regV = this.getSymbol(regX);    
                        
                        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{
                            v = `${indent}${this.getRegisterName(oper.left[1])}.${oper.right.name} = ${regX};`;
                        }
    
                        //co
                        //this.setSymbol(`${this.getRegisterName(oper.left[1])}.${oper.right.name}`, DTYPE.FIELD_REF, oper.right.name, v);
                        dec.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[0]);
                        
                    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{
                        v = `${indent}${this.getRegisterName(oper.left[1])}.${oper.right.name} = ${regX};`;
                    }

                    //co
                    //this.setSymbol(`${this.getRegisterName(oper.left[1])}.${oper.right.name}`, DTYPE.FIELD_REF, oper.right.name, v);
                    dec.push(v);
                    break;

                case OPCODE.RETURN.byte:
                    regX = this.getRegisterName(oper.left);
                    regV = this.getSymbol(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{

                        v = `${indent}return ${regX};`;
                    }

                    dec.push(v);
                    break;
                case OPCODE.RETURN_OBJECT.byte:
                    regX = this.getRegisterName(oper.left);
                    regV = this.getSymbol(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;`;
                        else if(this.simplify >= 1 && this.isImm(regV))
                            v = `${indent}return ${this.getImmediateValue(regV)};`;
                        else 
                            v = `${indent}return p0;`;

                    }
                    else{
                        v = `${indent}return ${regX};`;
                    }

                    dec.push(v);
                    break;
                case OPCODE.RETURN_WIDE.byte:
                    dec.push(`${indent}return <TODO>;`);
                    break;
                case OPCODE.RETURN_VOID.byte:
                    dec.push(`${indent}return ;`);
                    break;
                
                // IF multi
                case OPCODE.IF_EQ.byte:                
                    regX = this.getRegisterName(oper.left[0]);
                    regV = this.getRegisterName(oper.left[1]);

                    if(this.simplify>0){
                        if(this.isImm(this.getSymbol(regX)))
                            regX = this.getImmediateValue(this.getSymbol(regX));
                        if(this.isImm(this.getSymbol(regV)))
                            regV = this.getImmediateValue(this.getSymbol(regV));
                    }

                    dec.push(`${indent}if( ${regX} == ${regV} ) :cond_${oper.right.name}`);
                    break;
                case OPCODE.IF_NE.byte:                
                    regX = this.getRegisterName(oper.left[0]);
                    regV = this.getRegisterName(oper.left[1]);

                    if(this.simplify>0){
                        if(this.isImm(this.getSymbol(regX)))
                            regX = this.getImmediateValue(this.getSymbol(regX));
                        if(this.isImm(this.getSymbol(regV)))
                            regV = this.getImmediateValue(this.getSymbol(regV));
                    }

                    dec.push(`${indent}if( ${regX} != ${regV} ) :cond_${oper.right.name}`);
                    break;
                case OPCODE.IF_LT.byte:                
                    regX = this.getRegisterName(oper.left[0]);
                    regV = this.getRegisterName(oper.left[1]);

                    if(this.simplify>0){
                        if(this.isImm(this.getSymbol(regX)))
                            regX = this.getImmediateValue(this.getSymbol(regX));
                        if(this.isImm(this.getSymbol(regV)))
                            regV = this.getImmediateValue(this.getSymbol(regV));
                    }

                    dec.push(`${indent}if( ${regX} < ${regV} ) :cond_${oper.right.name}`);
                    break;
                case OPCODE.IF_GE.byte:                
                    regX = this.getRegisterName(oper.left[0]);
                    regV = this.getRegisterName(oper.left[1]);

                    if(this.simplify>0){
                        if(this.isImm(this.getSymbol(regX)))
                            regX = this.getImmediateValue(this.getSymbol(regX));
                        if(this.isImm(this.getSymbol(regV)))
                            regV = this.getImmediateValue(this.getSymbol(regV));
                    }

                    dec.push(`${indent}if( ${regX} >= ${regV} ) :cond_${oper.right.name}`);
                    break;
                case OPCODE.IF_GT.byte:                
                    regX = this.getRegisterName(oper.left[0]);
                    regV = this.getRegisterName(oper.left[1]);

                    if(this.simplify>0){
                        if(this.isImm(this.getSymbol(regX)))
                            regX = this.getImmediateValue(this.getSymbol(regX));
                        if(this.isImm(this.getSymbol(regV)))
                            regV = this.getImmediateValue(this.getSymbol(regV));
                    }

                    dec.push(`${indent}if( ${regX} > ${regV} ) :cond_${oper.right.name}`);
                    break;
                case OPCODE.IF_LE.byte:                
                    regX = this.getRegisterName(oper.left[0]);
                    regV = this.getRegisterName(oper.left[1]);
                    
                    if(this.simplify>0){
                        if(this.isImm(this.getSymbol(regX)))
                            regX = this.getImmediateValue(this.getSymbol(regX));
                        if(this.isImm(this.getSymbol(regV)))
                            regV = this.getImmediateValue(this.getSymbol(regV));
                    }
                        
                    dec.push(`${indent}if( ${regX} <= ${regV} ) :cond_${oper.right.name}`);
                    break;

                // IF zero
                case OPCODE.IF_EQZ.byte:                
                    regX = this.getRegisterName(oper.left);
                    regV = this.getSymbol(regX);
                    if(this.simplify>0){
                        if(this.isImm(regV))
                            dec.push(`${indent}if( ${this.getImmediateValue(regV)} == 0 ) :cond_${oper.right.name}`);
                        else if(regV.type == DTYPE.OBJECT_REF){
                            dec.push(`${indent}if( ${regX} != null ) :cond_${oper.right.name}`);
                        }else{
                            dec.push(`${indent}if( ${regX} == 0 ) :cond_${oper.right.name}`);
                        }
                    }else
                        dec.push(`if( ${regX} == 0 ) :cond_${oper.right.name}`);
                    break;
                case OPCODE.IF_NEZ.byte:                
                    regX = this.getRegisterName(oper.left);
                    regV = this.getSymbol(regX);
                    if(this.simplify>0 && this.isImm(regV))
                        dec.push(`${indent}if( ${this.getImmediateValue(regV)} != 0 ) :cond_${oper.right.name}`);
                    else{
                        console.log(oper.right, oper);
                        dec.push(`${indent}if( ${regX} != 0 ) :cond_${oper.right.name}`);
                    }break;
                case OPCODE.IF_LTZ.byte:                
                    regX = this.getRegisterName(oper.left);
                    regV = this.getSymbol(regX);
                    if(this.simplify>0 && this.isImm(regV))
                        dec.push(`${indent}if( ${this.getImmediateValue(regV)} < 0 )`);
                    else
                        dec.push(`${indent}if( ${regX} < 0 ) :cond_${oper.right.name}`);
                    break;
                case OPCODE.IF_GEZ.byte:                
                    regX = this.getRegisterName(oper.left);
                    regV = this.getSymbol(regX);
                    if(this.simplify>0 && this.isImm(regV))
                        dec.push(`${indent}if( ${this.getImmediateValue(regV)} >= 0 ) :cond_${oper.right.name}`);
                    else
                        dec.push(`${indent}if( ${regX} >= 0 ) :cond_${oper.right.name}`);
                    break;
                case OPCODE.IF_GTZ.byte:                
                    regX = this.getRegisterName(oper.left);
                    regV = this.getSymbol(regX);
                    if(this.simplify>0 && this.isImm(regV))
                        dec.push(`${indent}if( ${this.getImmediateValue(regV)} > 0 ) :cond_${oper.right.name}`);
                    else
                        dec.push(`${indent}if( ${regX} > 0 ) :cond_${oper.right.name}`);
                    break;
                case OPCODE.IF_LEZ.byte:                
                    regX = this.getRegisterName(oper.left);
                    regV = this.getSymbol(regX);
                    if(this.simplify>0 && this.isImm(regV))
                        dec.push(`${indent}if( ${this.getImmediateValue(regV)} <= 0 ) :cond_${oper.right.name}`);
                    else
                        dec.push(`${indent}if( ${regX} <= 0 ) :cond_${oper.right.name}`);
                    break;
                /*case OPCODE.MONITOR_ENTER.byte:
                    dec.push("try{");
                    break;
                case OPCODE.MONITOR_ENTER.byte:
                    dec.push("try{");
                    break;*/
                default:
                    this.countUntreated++;
            }
        }

        if(dec[0]=="") dec.shift();


        return { code:dec, depth:pDepth };
    }
}



class Decompiler {

    static instance = null;

    constructor(pContext){
        this.context = pContext;
        this.vm = null;
        this.decompiled = {};
    }


    /**
     * To get an instance of the decompiler.
     * Important : only one instance of this.decompiled should exist to
     * prevent method already analyzed, to be reprocessed.
     *  
     * @param {Project} pContext 
     */
    static getInstance(pContext){
        if(Decompiler.instance == null){
            Decompiler.instance = new Decompiler(pContext);
        }

        return Decompiler.instance;
    }
    
    /**
     * 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;
        let entry = blocks[0], instr=null, jump=false;

        // forward analysis
        self = entry;
        for(let i=0; i<blocks.length ; i++){

            if(blocks[i].isVisited()) continue;

            instr = blocks[i].getInstructions();  
            jump = false;
            for(let k=0; k<instr.length; k++){

                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}`)
                    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}`)
                    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();   
        }

        // backward analysis
        /*for(let i=0; i<blocks.length ; i++){

            if(blocks[i].hasSuccessors()){
                instr = blocks[i].getSuccessors();
                for(let k=0; k<instr.length; k++){
                    if(instr[k].hasPredecessor(blocks[i])==false){
                        Logger.debug(i," predecessor of ",k);
                        instr[k].addPredecessor(blocks[i]);
                    }
                }
            } 
        }*/

        return entry;
    }

    /*compute(pTree, pList){
        let succ=null, l=[], first=null, next=null;
        if(pTree.hasSuccessors()){
            succ = pTree.getSuccessors();
            for(let i=0; i<succ.leength; i++){
                if(succ[i].isConditionalBlock()){
                    first = succ[i];
                }else if(!succ[i].hasMultiplePredecessors()){
                    l.push(succ[i]);
                }else{
                    next = succ[i];
                }
            }
        }
        pList.push(first);
        pList.concat(l);
        pList.push(next);
    }*/

    simplify(pMethod, pLevel=0){
        let blocks = [], cs = {
            tag: null,
            intr: []
        };

        // init
        blocks =  pMethod.getBasicBlocks();
        if(blocks.length == 0) return cs;

        this.vm = new VM( pMethod, pMethod.locals, pMethod.args.length);

        // explore blocks
        this.vm.cleanVisitedBlock();
        this.vm.setSimplifyingLevel(pLevel);

        cs.instr = this.vm.runSimplify();

        Logger.debug(cs.instr.join("\n"));

        return cs;
    }

    decompile(pMethod, pLevel=0){
        let blocks = [], tree=null, instr = [], vm = [], cs = {
            tag: null,
            intr: []
        };

        // init
        blocks =  pMethod.getBasicBlocks();
        if(blocks.length == 0) return cs;

        this.vm = new VM( pMethod, pMethod.locals, pMethod.args.length);

        // explore blocks
        this.vm.cleanVisitedBlock();

        if(this.decompiled[pMethod.signature()]!=true){
            tree = this.analyzeBlocks(pMethod);
            this.decompiled[pMethod.signature()] = true;
            Logger.debug("[DECOMPILER] Forward/Backward analysis done !"); 
        }else{
            tree = blocks[0];
            Logger.debug("[DECOMPILER] Forward/Backward previously done. Read from cache");
        }

        // decompile
        this.vm.cleanVisitedBlock();
        this.vm.setSimplifyingLevel(pLevel);

        cs.instr = this.vm.runDecompile(tree);
        //console.log(cs.instr.join("\n"));

        Logger.debug(cs.instr.join("\n"));

        /*
        blocks =  pMethod.getBasicBlocks();

        if(blocks.length == 0) return cs;
        this.vm = new VM( pMethod, pMethod.locals, pMethod.args.length);
        this.vm.setContext(blocks);
        cs.intr = this.vm.runAll();*/

        return cs;
    }
}

module.exports = Decompiler;