BackupManager.js

var fs = require('fs');
var Chalk = require("chalk");
var Process = require("child_process");
const Path = require("path");

const CONST = require('./CoreConst.js');
const Core = require("./CoreClass.js");
const INSTR = require("./Opcode.js");

var JSONutil = require("util");

var cyclicDebug = []
var cycleMax = 8;

let STUB=Core.STUB_TYPE;


function findCyclicRef(obj,fullPath=[],endPath=[],cycle=0){
    let path = fullPath, end=endPath, k=0;
    if(cycle>cycleMax){
        //console.log("[E] Max cycle : "+path.join('.'));
        return false;
    }

    for(let i in obj){

        if(path.length == 0 && (k%200 == 0)){
            console.log("\tStatus : "+k+" nodes scanned");
            k++;
        }
        
        if(typeof obj[i] === 'object'){
            path.push(i);
            if(obj[i] instanceof Core.Class){
                cyclicDebug.push("Class "+i+" > "+path.join('/'));
                console.log(fullPath);
                console.log("Cycling : "+path.join('.'));
                path.pop();
            }
            else if(obj[i] instanceof Core.Method){
                cyclicDebug.push("Method :"+path.join('/'));
                console.log("Cycling : "+path.join('.'));
                path.pop();
            }
            else if(obj[i] instanceof Core.Field){
                cyclicDebug.push("Field :"+path.join('/'));
                console.log("Cycling : "+path.join('.'));
                path.pop();
            }
            else if(obj[i] instanceof Core.MissingReference){
                cyclicDebug.push("MissingRef :"+path.join('/'));
                console.log("Cycling : "+path.join('.'));
                path.pop();
            }
            else{
                findCyclicRef(obj[i],path,[],cycle+1);
                path.pop();
            }
        }
        else if(typeof obj[i] === 'array'){
            path.push(i);
            console.log("Cycling : "+path.join('/'));
            findCyclicRef(obj[i],path,[],cycle+1);
            path.pop();
        }   

    }

    return true;
}


function CircularReference(type,id){
    this.$ = 0x4;
    this.type = type;
    this.id = id;

    return this;
}

function MultipleReference(type,id){
    this.type = type;
    this.id = id;

    return this;
}

/*
function Stub(type,data,exclude){
    this.__type__ = type;

    for(let i in data){
        if(exclude.indexOf(i)==-1)
            this[i]=data[i]
    };

    return this;
}*/

function save(data,file){
    let tab={}, src={}, block=null;


    for(let i in data.classes){
        tab[data.classes[i].name] = new Core.Stub(
            STUB.CLASS,
            data.classes[i],
            ['methods','fields','extends','implements','enclosingClass','innerClass','_callers']
        );

        if(data.classes[i]._val !== undefined 
            && (data.classes[i]._val instanceof Core.Class)){

            console.log("Missing class found at top level");

            tab[data.classes[i].name] = new Core.Stub(
                STUB.MISSING,
                data.classes[i],
                ['methods','fields','extends','implements','enclosingClass','innerClass','_callers','_val']
            );
            tab[data.classes[i]].name.__srcType__ = STUB.CLASS;
        }

        
        for(let k in data.classes[i].methods){
            tab[data.classes[i].methods[k].signature()] = new Core.Stub(
                STUB.METHOD,
                data.classes[i].methods[k],
                ['instr','enclosingClass','declaringClass','_useClass','_useMethod','_useField','_callers']                    
            );
        }

        for(let k in data.classes[i].fields){
            tab[data.classes[i].fields[k].signature()] = new Core.Stub(
                STUB.FIELD,
                data.classes[i].fields[k],
                ['enclosingClass','_callers']                    
            );
        }
    }

    
    // create an unique list containing all objects
    // replace circular reference by ID 
    for(let i in data.classes){
        src = data.classes[i];

        if(src.enclosingClass !== null){
            tab[src.name].enclosingClass = new CircularReference(
                STUB.CLASS,
                src.enclosingClass.name
            );
        }

        if(src.extends !== null){
            tab[src.name].extends = new CircularReference(
                STUB.CLASS,
                src.extends.name
            );
        }

        if(src.innerClass.length > 0){
            tab[src.name].innerClass = [];
            for(let j in src.innerClass){
                tab[src.name].innerClass[j] = new CircularReference(
                    STUB.CLASS,
                    j
                );
            }

        }

        if(src.implements.length > 0){
            tab[src.name].implements = [];
            for(let j in src.implements){
                tab[src.name].implements.push(
                    new CircularReference(
                        STUB.CLASS,
                        src.implements[j].name
                    )
                );
            }
        }

        // ._callers[*] references
        if(src._callers.length > 0){
            tab[src.name]._callers = [];
            for(let j in src._callers){
                tab[src.name]._callers.push(
                    new CircularReference(
                        STUB.METHOD,
                        src._callers[j].signature()
                    )
                );
            }
        }

        // .methods[*] references
        if(src.methods !== null){
            for(let k in src.methods){
                id = src.methods[k].signature();
                
                // INSTRUCTIONS
                tab[id].instr = [];
                if(src.methods[k].instr.length > 0){

                    for(let l in src.methods[k].instr){
                        block = src.methods[k].instr[l];
                        bbstub = new Core.Stub(
                            STUB.BASIC_BLOCK,
                            block,
                            ['stack']
                        );
                        
                        bbstub.stack = [];
                        for(let m in block.stack){
                            istub = new Core.Stub(
                                STUB.INSTR,
                                block.stack[m],
                                ['opcode','right']
                            );

                            istub.opcode = new MultipleReference(
                                STUB.OPCODE,
                                block.stack[m].opcode.byte
                            );

                            if(block.stack[m].right instanceof Core.Field){
                                istub.right = new CircularReference(
                                    STUB.FIELD,
                                    block.stack[m].right.signature()
                                );
                            }
                            else if(block.stack[m].right instanceof Core.Method){
                                istub.right = new CircularReference(
                                    STUB.METHOD,
                                    block.stack[m].right.signature()
                                );
                            }
                            else if(block.stack[m].right instanceof Core.Class){
                                istub.right = new CircularReference(
                                    STUB.METHOD,
                                    block.stack[m].right.name
                                );
                            }
                            /*
                            else if(block.stack[m].right instanceof Core.StringValue){
                                istub.right = new CircularReference(
                                    STUB.STRING,
                                    block.stack[m].right.name
                                );
                            }*/
                            else if(block.stack[m].right instanceof Core.MissingReference){
                                
                                if(block.stack[m].right.signature !== undefined){
                                    istub.right = new CircularReference(
                                        STUB.MISSING,
                                        block.stack[m].right.signature()
                                    );
                                }else{
                                    istub.right = new CircularReference(
                                        STUB.MISSING,
                                        block.stack[m].name
                                    );
                                }

                                switch(block.stack[m].right._type){
                                    case CONST.OPCODE_REFTYPE.FIELD:
                                        istub.right.__srcType__ = STUB.FIELD;
                                        break;
                                    case CONST.OPCODE_REFTYPE.METHOD:
                                        istub.right.__srcType__ = STUB.METHOD;
                                        break;
                                    case CONST.OPCODE_REFTYPE.CLASS:
                                        istub.right.__srcType__ = STUB.CLASS;
                                        break;
                                }
                        
                                
                            }else{

                                istub.right = block.stack[m].right;
                            }

                            bbstub.stack.push(istub);
                        }

                        tab[id].instr.push(bbstub);
                    }
                }

                // CALLERS
                if(src.methods[k]._callers.length > 0){
                    tab[id]._callers = [];
                    for(let j in src.methods[k]._callers){
                        tab[id]._callers.push(
                            new CircularReference(
                                STUB.METHOD,
                                src.methods[k]._callers[j].signature()
                            )
                        );
                    }
                }

                tab[id]._useMethod = {};
                for(let l in src.methods[k]._useMethod){
                    tab[id]._useMethod[l] = [];
                    for(let m in src.methods[k]._useMethod[l]){
                        tab[id]._useMethod[l][m] = new CircularReference(
                            STUB.METHOD,
                            l
                        );
                    }
                }

                tab[id]._useField = {};
                for(let l in src.methods[k]._useField){
                    tab[id]._useField[l] = [];
                    for(let m in src.methods[k]._useField[l]){
                        tab[id]._useField[l][m] = new CircularReference(
                            STUB.FIELD,
                            l
                        );
                    }
                }

                tab[id]._useClass = {};
                for(let l in src.methods[k]._useClass){
                    tab[id]._useClass[l] = [];
                    for(let m in src.methods[k]._useClass[l]){
                        tab[id]._useClass[l][m] = new CircularReference(
                            STUB.CLASS,
                            l
                        );
                    }
                }
                
                tab[src.methods[k].signature()].enclosingClass  = new CircularReference(
                    STUB.CLASS,
                    src.name
                );

                if(src.methods[k].declaringClass !== undefined && (src.methods[k].declaringClass instanceof Core.Class)){
                    tab[src.methods[k].signature()].declaringClass = new CircularReference(
                        STUB.CLASS,
                        src.methods[k].declaringClass.name
                    );
                }
            };


        }

        // Class.fields[*] references        
        if(src.fields !== null){
            for(let k in src.fields){
                fieldSign = src.fields[k].signature();

                // ._callers[*] references
                if(src.fields[k]._callers.length > 0){
                    tab[fieldSign]._callers = [];
                    for(let j in src.fields[k]._callers){
                        tab[fieldSign]._callers.push(
                            new CircularReference(
                                STUB.METHOD,
                                src.fields[k]._callers[j].name
                            )
                        );
                    }
                }

                // .enclosingClass references
                if(src.enclosingClass !== null){
                    tab[fieldSign].enclosingClass = new CircularReference(
                        STUB.CLASS,
                        src.enclosingClass.name
                    );
                }
            }
        }
    }

    //return tab;
    
    /*
    console.log("[*] Class/method/field exported : "+tab.length); 
    console.log("[*] Checking for cycling reference :")
    findCyclicRef(tab);
    
    if(cyclicDebug.length > 0){
        console.log(Chalk.bold.red("[E] Error : cycling references detected."));
        for(let i in cyclicDebug){
            console.log("\t"+cyclicDebug[i]);
        }
        return;
    }
        
    let json = JSONutil.inspect(tab);*/
    // into each object replace reference by the corresponding ID
    fs.writeFileSync(file+".tmp", JSON.stringify(tab));
    console.log(Chalk.green("[*] Compressing... "))
    
    Process.exec("/usr/bin/zip "+file+".zip "+file+".tmp");
    console.log(Chalk.green("[*] Saved sucessfully. "))

    //fs.unlinkSync(file+".tmp");

    return ;
}


/**
 * Return map
 * @param {*} file 
 */
function restore(filepath){
    let src=null, db=[];

    // unzip file
    Process.exec("unzip "+filepath);


    // read save file
    src=fs.readFileSync(
        Path.join(filepath.substr(0,filepath.lastIndexOf("/")),filepath.replace(".zip",".tmp")),'ascii');
    src=JSON.parse(src);

    // create corresponding obj for each Stub
    for(let i in src){
        if(src[i].__type__==REF.CLASS){
            db[i] = new Core.Class(src[i]);
            db[i].import(src[i]);

            // search obj from circular ref
            for(let j in src[i].fields){
                db[i].fields[j] = new Core.Field();
                db[i].fields[j].import(src[i].fields[j]);
            }

            // map method
            for(let j in src[i].methods){
                db[i].methods[j] = new Core.Method();
                db[i].methods[j].import(src[i].methods[j]);
            }
        }
        else if(src[i].__type__==REF.MISSING){
            switch(src[i].__srcType__){
                case REF.METHOD: 
                    db[src[i].enclosingClass.id].methods[i] = new Core.Method();
                    db[src[i].enclosingClass.id].methods[i].import(src[i]);
                    break;
                case REF.FIELD:
                    db[src[i].enclosingClass.id].fields[i] = new Core.Field();
                    db[src[i].enclosingClass.id].fields[i].import(src[i]);
                    break;
                case REF.CLASS:
                    db[i] = new Core.Class();
                    db[i].import(src[i]);
                    break;
            }
        }
    }

    // re-map
    for(let i in db){

        // class properties
        if(db[i].enclosingClass !== null){
            db[i].enclosingClass = db[src[i].enclosingClass.id];
        }
        if(db[i].extends !== null){
            db[i].extends = db[src[i].extends.id];
        }
        if(db[i].innerClass !== null){
            db[i].enclosingClass = db[src[i].enclosingClass.id];
        }
        if(typeof db[i].innerClass == 'array'){
            db[i].innerClass = [];
            for(let j in src[i].innerClass){
                db[i].innerClass.push(db[src[i].innerClass[j].id]);
            }
        }
        if(typeof db[i].implements == 'array'){
            db[i].implements = [];
            for(let j in src[i].implements){
                db[i].implements.push(db[src[i].implements[j].id]);
            }
        }
        if(typeof db[i]._callers == 'array'){
            db[i]._callers = [];
            for(let j in src[i]._callers){
                db[i]._callers.push(db[src[i]._callers[j].id]);
            }
        }

        // method 
        // ['instr','enclosingClass','declaringClass','_useClass','_useMethod','_useField','_callers']                  
        for(let j in db[i].methods){

            // ins
            db[i].methods[j].instr = [];
            for(let k=0; k<src[j].instr.length; k++){
                bb = new Core.BasicBlock();
                bb.import(src[j].instr[k]);
                
                for(let l=0; l<bb.stack.length; l++){
                    // restore opcode
                    bb.stack[l].opcode = INSTR.findOpcode(bb.stack[l].opcode.id);
                    // restore right reference
                    if(bb.stack[l].right.$ !== undefined){

                        switch(bb.stack[l].right.__type__){
                            case STUB_TYPE.CLASS:
                                bb.stack[l].right = db[bb.stack[l].right.id];
                                break;
                            case STUB_TYPE.METHOD:
                                meth = db[bb.stack[l].right.id];
                                bb.stack[l].right = src[meth.enclosingClass.id].methods[meth.id];
                                break;
                            case STUB_TYPE.FIELD:
                                fied = db[bb.stack[l].right.id];
                                bb.stack[l].right = src[fied.enclosingClass.id].fields[fied.id];
                                break;
                            case STUB_TYPE.MISSING:
                                switch(bb.stack[l].right.__srcType__){
                                    case STUB_TYPE.CLASS:
                                        bb.stack[l].right = db[bb.stack[l].right.id];
                                        break;
                                    case STUB_TYPE.METHOD:
                                        meth = src[bb.stack[l].right.id];
                                        bb.stack[l].right = db[meth.enclosingClass.id].methods[meth.id];
                                        break;
                                    case STUB_TYPE.FIELD:
                                        field = src[bb.stack[l].right.id];
                                        bb.stack[l].right = db[field.enclosingClass.id].fields[field.id];
                                        break;
                                }
                                break;
                        }
                    }else{
                        
                    }
                }
                db[i].methods[j].instr.push(bb);
            }
        }

        // field
    }
    /*
    db[i].methods[j]._callers = {};
    for(let k in src[i].methods[j]._callers){
        callers = [];
        for(let l in src[i].methods[j]._callers[k]){
            callers.push()
        }
        db[i].methods[j]._callers[k] = callers;
    }
    */
    return db;
}


module.exports = {
    save: save,
    restore: restore
};