src/CoreClass.js
var CONST = require("./CoreConst.js");
const NodeCompare = require("./NodeCompare.js");
const Accessor = require('./AccessFlags');
const EOL = require('os').EOL ;
/**
* Constant values describing a stub type.
*/
var STUB_TYPE={
METHOD: 0x1,
FIELD: 0x2,
ANNOTATION: 0x3,
INSTR: 0x4,
MISSING: 0x5,
CLASS: 0x6,
OBJ_TYPE: 0x7,
BASIC_TYPE: 0x8,
VALUE_CONST: 0x9,
STRING_VALUE: 0xa,
CIRCULAR: 0xb,
VARIABLE: 0xc,
CALL: 0xd,
NATIVE_FUNC: 0xe,
SYSCALL: 0xf
};
var BUILTIN_TAG = {
INTERNAL: "int",
DYNAMIC: "dyn"
};
/**
* Constant values dscribin a class of hookable function
*/
var FUNC_TYPE = {
// A syscall function or SVC
SYSCALL: 0x1,
// Exported fucntion from a native library
NATIVE_EXPORT: 0x2,
// Imported function from a native library
NATIVE_IMPORT: 0x3,
// A Java function from a 3th party library
JAVA_LIB: 0x4
};
function Stub(type,data,exclude=[]){
this.__type__ = type;
for(let i in data){
if(exclude.indexOf(i)==-1)
this[i]=data[i]
};
return this;
}
Savable ={
export: function(exclude){
return new Stub(
this.__stub_type__,
this,
exclude
)
},
import: function(stub){
for(let i in stub) this[i] = stub[i];
return this;
}
};
class RegisterRef
{
constructor( pType, pIdentifier){
this.t = pType;
this.i = pIdentifier;
}
}
/**
* Encapsulate metadata
* @param {Object} cfg
*/
function Metadata(cfg){
this.alias = (cfg.alias!=null? cfg.alias : null);
this.comment = (cfg.comment!=null? cfg.comment : null);
this.tags = [];
return this;
}
Metadata.prototype.setAlias = function(name){
this.alias = name;
}
Metadata.prototype.getAlias = function(){
return this.alias;
}
Metadata.prototype.setComment = function(value){
this.comment = value;
}
Metadata.prototype.getComment = function(){
return this.comment;
}
Metadata.prototype.addTag = function(name){
if(this.tags.indexOf(name)==-1) this.tags.push(name);
}
Metadata.prototype.removeTag = function(){
let i = -1;
if((i=this.tags.indexOf(name))>-1){
this.tags[i] = null;
}
}
Metadata.prototype.getTags = function(){
return this.tags;
}
/**
* TO DO
*/
function ByteArray(){
this.length = null;
this.osize = null;
this.value = [];
}
ByteArray.prototype.toString = function(){
};
class CatchStatement
{
constructor(){
this.d = [];
}
setException(pClass){
this.d[0] = pClass;
}
getException(){
return this.d[0];
}
setTryStart(pLabel){
this.d[1] = pLabel;
}
getTryStart(){
return this.d[1];
}
setTryEnd(pLabel){
this.d[2] = pLabel;
}
getTryEnd(){
return this.d[2];
}
setTarget(pLabel){
this.d[3] = pLabel;
}
getTarget(){
return this.d[3];
}
}
/**
* A cross reference to a subject
* @param {*} obj Subject object
* @param {*} xref Reference to the subject object.
*/
function XRef(obj,xref){
this.subject = obj;
this.xref = xref;
this.noref = (xref.length == 0);
return this;
}
/**
* To represent a primitive type
* @param {string} raw_type - The raw name of the type as it can be found in Smali code
* @param {boolean} isArray - Array flag should be TRUE if the type is an array, else FALSE
* @constructor
*/
function BasicType(raw_type, isArray=false){
this.$ = STUB_TYPE.BASIC_TYPE;
this.name = CONST.TYPES[raw_type];
this.arr = isArray;
this._name = (CONST.WORDS[raw_type]!=undefined)? CONST.WORDS[raw_type] : "???";
this._hashcode = raw_type;
// this.tags = [];
this.hashCode = ()=>{
return this._hashcode;
};
this.sprint = function(){
return "<"+this._name+">"+(this.arr?"[]":"");
};
return this;
}
BasicType.prototype.import = Savable.import;
BasicType.prototype.export = Savable.export;
/**
* To check if the current type is Void
* @returns {boolean} - Returns TRUE if the type is Void, else FALSE
*/
BasicType.prototype.isVoid = function(){
return this.name == CONST.TYPES.V;
}
/**
* To check if the current type is numeric (integer, long or short)
* @returns {boolean} - Returns TRUE if the type is integer or long or short, else FALSE
*/
BasicType.prototype.isNumeric = function(){
return [CONST.TYPES.S, CONST.TYPES.I, CONST.TYPES.J].indexOf(this.name)>-1;
// return [CONST.TYPES.S, CONST.TYPES.I, CONST.TYPES.J].indexOf(this._hashcode)>-1;
}
/**
* To check if the current type is an array
* @returns {boolean} - Returns TRUE if the type is an array, else FALSE
*/
BasicType.prototype.isArray = function(){
return this.arr;
}
/**
* To make the signature of the current type instance
* It has one of these forms :
* - "<I>" if the current type is an Integer
* - "<I>[]" if the current type is an array of Integer
*
* @returns {string} - Returns the signature of the type
*/
BasicType.prototype.signature = function(){
return "<"+this._name+">"+(this.arr?"[]":"");
};
/**
* To make an instance of Object which not contain circular reference
* and which are ready to be serialized.
* @returns {Object} - Returns an Object instance representing the type
*/
BasicType.prototype.toJsonObject = function(){
let obj = new Object();
obj.name = this._name;
obj.arr = this.arr;
obj.primitive = true;
return obj;
};
/**
* Represents a type for a given class.
* @param {string} cls - The class name
* @param {boolean} isArray - The array flag
* @constructor
*/
function ObjectType(cls, isArray=false){
this.$ = STUB_TYPE.OBJ_TYPE;
this.name = cls;
this.arr = isArray;
this._name = cls;
this._hashcode = cls;
this.tags = [];
this.hashCode = ()=>{
return this._hashcode;
};
this.sprint = function(){
return "<"+this._name+">"+(this.arr?"[]":"");
};
return this;
}
ObjectType.prototype.import = Savable.import;
ObjectType.prototype.export = Savable.export;
/**
* To check if the current type is a Java String
* @returns {boolean} - Returns TRUE if the type is a Java String, else FALSE
*/
ObjectType.prototype.isString = function(){
return this.name == "java.lang.String";
}
/**
* To make the signature of the current type instance
* It has one of these forms :
* - "<I>" if the current type is an Integer
* - "<I>[]" if the current type is an array of Integer
*
* @returns {string} - Returns the signature of the type
*/
ObjectType.prototype.signature = function(){
return "<"+this.name+">"+(this.arr?"[]":"");
};
/**
* To make an instance of Object which not contain circular reference
* and which are ready to be serialized.
* @returns {Object} - Returns an Object instance representing the type
*/
ObjectType.prototype.toJsonObject = function(){
let obj = new Object();
obj.name = this.name;
obj.arr = this.arr;
obj.primitive = false;
return obj;
};
ObjectType.prototype.addTag = function(tag){
this.tags.push(tag);
}
ObjectType.prototype.hasTag = function(tagName){
return this.tags.indexOf(tagName)>-1;
}
ObjectType.prototype.getTags = function(){
return this.tags;
}
ObjectType.prototype.isArray = function(){
return this.arr;
}
/**
* Represents a package from the application
* @param {string} name The name of the package
* @constructor
*/
function Package(name){
this.name = name;
this.meta = null;
this.children = [];
this.tags = [];
}
/**
*
* @param {Metadata|Object} obj The metadata
*/
Package.prototype.setMetadata = function(obj){
if(obj instanceof Metadata){
this.meta = obj;
}else if(typeof obj == 'object'){
this.meta = new Metadata(obj);
}else{
console.log("Error : invalid Metadata type");
}
return this;
}
/**
* To append a child - class or package - to the current package
* @param {Class|Package} obj The new child
*/
Package.prototype.childAppend = function(obj){
this.children.push(obj);
return this;
}
/**
* To count children
*/
Package.prototype.getSize = function(){
return this.children.length;
}
Package.prototype.getAbsoluteSize = function(obj){
let absz = 0;
for(let i in this.children){
if(this.children[i] instanceof Class)
absz++;
else if(this.children[i] instanceof Package)
absz += this.children[i].getAbsoluteSize();
}
return absz;
}
Package.prototype.toJsonObject = function(fields){
let o=new Object();
if(fields !== null){
for(let i in fields){
if(typeof this[fields[i]] == "object"){
o[fields[i]] = this[fields[i]].toJsonObject();
}else{
o[fields[i]] = this[fields[i]];
}
}
}else{
o.name = this.name;
o.children = [];
for(let i in this.children){
o.children.push(this.children[i].toJsonObject());
}
o.tags = this.tags;
}
o.absolute_size = this.getAbsoluteSize();
o.size = this.getSize();
return o;
}
Package.prototype.addTag = function(tag){
this.tags.push(tag);
}
Package.prototype.hasTag = function(tagName){
return this.tags.indexOf(tagName)>-1;
}
Package.prototype.getTags = function(){
return this.tags;
}
class SerializedObject
{
static refs = {};
constructor(obj=null){
this.__type = null;
//this.__format = obj.__format;
this.__raw = null;
if(obj!==null){
for(let i in obj){
this[i] = obj[i];
}
}
}
static init(supported_class){
SerializedObject.refs = supported_class;
}
static isSerializable(obj){
return (obj.serialize !=null) && (typeof obj.serialize==='function');
}
static isUnserializable(obj){
return (obj.__type!=null)
&& (obj.__raw!=null)
&& (SerializedObject.refs[obj.__type]!==null);
}
static from(obj,type){
let o = new SerializedObject();
o.__type = type;
o.__raw = obj;
return o;
}
unserialize(){
return SerializedObject
.refs[this.__type]
.unserialize(this.__raw);
}
}
class File
{
constructor(config={}){
this.name = (config.name!=null? config.name : null);
this.path = (config.path!=null? config.path : null);
this.remotePath = (config.remotePath!=null? config.remotePath : null);
this.checksum = (config.checksum!=null? config.checksum : null);
}
getPath(){
return this.path;
}
getRemotePath(){
return this.remotePath;
}
equals(file){
// TODO checksum
}
toJsonObjec(){
let o=new Object();
for(let i in this)
o[i] = this[i];
return o;
}
// ======
isSerializable(){
return true;
}
serialize(){
return SerializedObject.from(this,"File");
}
static unserialize(obj){
return new File(obj);
}
}
/**
* Represent a Class object :
* - Created by the parser and the ClassLoader's hook
* - Updated by the reference solver and the ClassLoader's hook
* @param {object} config Optional, a hashmap with param/value to initiliaze
* @constructor
*/
function Class(config){
// corresponding stub type to use during export
this.__stub_type__ = STUB_TYPE.CLASS;
this.$ = STUB_TYPE.CLASS;
//this.fqcn = null;
// the FQCN of the class
this.name = null;
// An alias
this.alias = null;
this.meta = null;
// the Simple name of the class (the last part of the FQCN)
this.simpleName = null;
// the FQDN of the package
// the package
this.package = null;
// the name of the source file contained into the .source instruction
this.source = null;
// a list of modifiers of the class (public/private/protected/static/final/...)
this.modifiers = null;
// a list of references to the implemented interfaces
this.implements = [];
// a list of references to the extended classes
this.extends = null;
this.supers = null;
// a list of references to the appied annotations
this.annotations = [];
// a list of the declared method
this.methods = {};
this.inherit = {};
// the count of methods inside the class
this._methCount = 0;
// a list of the declared fields
this.fields = {};
// the count of declared fields
this._fieldCount = 0;
// an hashmap of the inner classes, the key is the FQCN of the subject
this.innerClass = {};
this.tags = [];
/*
if the current object is enclosed into another class, a reference to
the enclosing class is stored here
*/
this.enclosingClass = null;
// private : a list of the methods containing instructions which use this class
this._callers = [];
// private : the unique identifier of this object in the graph
this._hashcode = null;
// private : TRUE if this class is binded by the OS or the VM.
this._isBinding = null;
this.__pretty_signature__ = null;
this.__aliasedCallSignature__ = null;
this.hashCode = function(){
return this.name;
};
this.help = function(){
let t="+-------------------- HELP --------------------+";
t += EOL+"[-- Methods : ]";
t += EOL+"\t.dump()\tShow the class information (field, methods, modifiers, parents, etc...)";
t += EOL+"\t.hasField(<string>)\tCheck if the class define the given field";
t += EOL+"\t.hasMethod(<string>)\tCheck if the class define the given method";
t += EOL+"\t.help()\tThis help";
t += EOL+"[-- Properties : ]";
t += EOL+"\n\t.instr:<Instruction>\tGet the instruction";
t += EOL+"\n\t.caller:<Method>\tGet the method performing the call";
t += EOL+"\t.calleed:<*>\tGet the reference to the calleed";
t += EOL;
console.log(t);
}
if(config!==undefined)
for(let i in config)
this[i]=config[i];
return this;
}
/**
* To check if a field is defined whith the given name
* @param {String} name The name of a field
* @returns {Boolean} TRUE if the class contains a definition, else FALSE
*/
Class.prototype.hasField = function(name){
return (this.fields[name]!==undefined);
};
Class.prototype.addField = function(field){
this.fields[field.signature()] = field;
}
Class.prototype.updateField = function(field, override=false){
let diff = this.fields[field.signature()].compare(field);
// if not identic => update, else nothiong to do
if(!diff.isIdentic()){
if(override)
this.fields[field.signature()] = field;
}
}
/**
* To check if a method is defined whith the given hashcode
* @param {String} hash The hashcode of the method
* @returns {Boolean} TRUE if the class contains a definition, else FALSE
*/
Class.prototype.hasMethod = function(hash){
return this.methods[hash]!==undefined;
};
Class.prototype.addMethod = function(meth){
this.methods[meth.signature()] = meth;
}
Class.prototype.updateMethod = function(meth, override=false){
let diff = this.methods[meth.signature()].compare(meth);
// if not identic => update, else nothiong to do
if(!diff.isIdentic()){
if(override)
this.methods[meth.signature()] = meth;
}
}
Class.prototype.hasSuperClass = function(){
return (this.extends != null);
}
Class.prototype.getSuperClass = function(){
return this.extends;
}
Class.prototype.getSuperList = function(){
return this.supers;
}
Class.prototype.setSupersList = function(superList){
this.supers = superList;
}
Class.prototype.getName = function(){
return this.name;
}
Class.prototype.signature = function(){
return this.name;
};
Class.prototype.aliasedSignature = function(){
return this.alias;
};
Class.prototype.prettySignature = function(override=false){
if(!override && this.__pretty_signature__ != null){
return this.__pretty_signature__;
}
this.__pretty_signature__ = this.signatureFactory("__alias_signature__","alias");
return this.__pretty_signature__;
}
// this.signatureFactory("__signature__","name")
// this.signatureFactory("__alias_signature__","alias")
Class.prototype.signatureFactory = function(ppt, seed){
if(this[ppt] !== null) return this[ppt];
this[ppt] = this[seed];
return hash;
};
Class.prototype.getAlias = function(){
return this.alias;
}
Class.prototype.setAlias = function(name){
this.alias = name;
}
Class.prototype.dump = function(){
if(this.extends!=null)
console.log("Class ["+this.name+"] extends ["+this.extends+"]");
else
console.log("Class ["+this.name+"]");
if(this.source!=null)
console.log("Source : "+this.source);
console.log("--------------------------------------\nFields :");
for(let k in this.fields){
console.log( this.fields[k].sprint());
}
console.log("--------------------------------------\nMethods :");
for(let k in this.methods){
console.log( this.methods[k].sprint());
}
}
Class.prototype.raw_import = Savable.import;
Class.prototype.import = function(obj){
// raw impport
this.raw_import(obj);
// construct obj
this.modifiers = new Accessor.AccessFlags(obj.modifiers);
};
Class.prototype.export = Savable.export;
Class.prototype.hasOverrideOf = function(meth){
if(meth == null) return null;
let cs = meth.callSignature();
for(let k in this.methods){
if(this.methods[k].callSignature()==cs){
return this.methods[k];
}
}
return null;
}
/**
* To add inherited method which are not overrided
*/
Class.prototype.addInheritedMethod = function(methodRef,parentMethod){
this.methods[methodRef] = parentMethod;
this.inherit[methodRef] = parentMethod;
return this.methods[methodRef];
}
Class.prototype.addInheritedField = function(localReference, parentField){
this.fields[localReference] = parentField;
this.inherit[localReference] = parentField;
return this.fields[localReference];
}
Class.prototype.toJsonObject = function(filter){
let obj = new Object(), m=null;
for(let i in this){
if(["_","$"].indexOf(i[0])==-1
&& (typeof this[i] != 'array')
&& (typeof this[i] != 'object')){
obj[i] = this[i];
}
else if(i == "supers"){
obj.supers = [];
if(this.supers instanceof Array)
for(let k=0; k<this.supers.length; k++){
if(this.supers[k] instanceof Class)
obj.supers.push({
name:this.supers[k].signature(),
alias: this.supers[k].getAlias()
}); // call signature
}
}
else if(i == "methods"){
obj.methods = [];
for(let k in this.methods){
m = this.methods[k].toJsonObject(["__signature__","__aliasedCallSignature__","__callSignature__","probing","modifiers","alias","name","tags"]);
if(this.inherit[k] != null) m.__inherit = true;
obj.methods.push(m); // call signature
}
}
else if(i == "fields"){
obj.fields = [];
for(let k in this.fields){
m = this.fields[k].toJsonObject(["__signature__","__aliasedSignature__","alias","name","tags","type","modifiers"]);
if(this.inherit[k] != null) m.__inherit = true;
obj.fields.push(m);
}
}
else if(i == "package"){
obj.package = this.package.toJsonObject(["name"]);
}
else if(i == "tags"){
obj.tags = this.tags;
}
else if(i == "extends"){
//obj.extends = (this.extends!=null? this.extends.toJsonObject(["__signature__"]): null);
obj.extends = (this.extends!=null? this.extends.name : null);
//obj.extends = (this.extends!=null? { name: this.extends.name, alias:this.extends.alias } : null);
}
else if(i == "implements"){
if(this.implements.length > 0){
obj.implements = [];
for(let x=0; x<this.implements.length; x++){
obj.implements.push(this.implements[x].name);
}
}
}
}
return obj;
};
/**
* To find a class's method by usins a search pattern
* @param {String} fqcn A raw Full-Qualified Class Name
*/
Class.prototype.initFromFQCN = function(fqcn){
this.name = fqcn;
this.simpleName = fqcn.substr(fqcn.lastIndexOf("$"));
this.fqcn = fqcn;
return this;
}
/**
* To set the class package
* @param {Package} pkg The package containing this class
*/
Class.prototype.setPackage = function(pkg){
this.package = pkg;
return this;
}
/**
* To get the class package
*/
Class.prototype.getPackage = function(){
return this.package;
}
/**
* To find a class's method by usins a search pattern
* @param {String} pattern
*/
Class.prototype.getMethod = function(pattern, pExactMatch=0){
let res0 = [], res1=[], rx={}, match=null;
if(pExactMatch != CONST.EXACT_MATCH){
for(let i in pattern){
rx[i] = new RegExp(pattern[i]);
}
res1 = this.methods;
for(let i in pattern){
res0 = res1;
res1 = [];
for(let meth in res0){
match = rx[i].exec(res0[meth][i]);
if(match !== null) res1.push(res0[meth]);
}
}
}else{
res1 = this.methods;
for(let i in pattern){
res0 = res1;
res1 = [];
for(let meth in res0){
if(pattern[i] === res0[meth][i])
res1.push(res0[meth]);
}
}
}
return res1;
};
Class.prototype.java_getMethod = function(pName, pArgumentsTypes){
for(let i in this.methods){
if(this.methods[i].name == pName){
for(let j=0; j<pArgumentsTypes.length; j++){
}
}
}
};
/**
* To find a class's field by usins a search pattern
* @param {String} pattern
*/
Class.prototype.getField = function(pattern){
let res0 = [], res1=[], rx={}, match=null;
for(let i in pattern){
rx[i] = new RegExp(pattern[i]);
}
res1 = this.fields;
for(let i in pattern){
res0 = res1;
res1 = [];
for(let meth in res0){
match = rx[i].exec(res0[meth][i]);
if(match !== null) res1.push(res0[meth]);
}
}
return res1;
}
/**
* To get all static fields declared or inherited
* @returns {Field[]} An array of fields
*/
Class.prototype.getStaticFields = function(){
let f = [];
for(let i in this.fields){
if(this.fields[i].modifiers.static){
f.push(this.fields[i]);
}
}
return f;
}
/**
* To get <clinit> method
* TODO : do it during analysis
* @returns {Method}
*/
Class.prototype.getClInit = function(){
for(let i in this.methods){
if(this.methods[i].name == "<clinit>"){
return this.methods[i];
}
}
return null;
}
Class.prototype.setupMissingTag = function(){
return (this.tags.push(CONST.TAG.MISSING));
}
Class.prototype.removeMissingTag = function(){
if(this.tags.length==1)
this.tags = [];
else{
let i = this.tags.indexOf(CONST.TAG.MISSING);
let arr = this.tags.slice(0,i);
if(i+1<this.tags.length){
arr = arr.concat(this.tags.slice(i+1,this.tags.length-i-1));
}
this.tags = arr;
}
}
Class.prototype.isMissingClass = function(){
return (this.tags.indexOf(CONST.TAG.MISSING)>-1);
}
Class.prototype.addTag = function(tag){
this.tags.push(tag);
}
Class.prototype.hasTag = function(tagName){
return this.tags.indexOf(tagName)>-1;
}
Class.prototype.getTags = function(){
return this.tags;
}
/**
* To get the implement interface
*/
Class.prototype.getInterfaces = function(){
return this.implements;
}
Class.prototype.removeAllInterfaces = function(){
this.implements = [];
}
Class.prototype.addInterface = function(inf){
this.implements.push(inf);
}
Class.prototype.updateSuper = function(cls){
if(cls.getSuperClass().name != this.getSuperClass().name){
// TODO : create NodeChange
this.extends = cls;
}
}
/**
* Represents a file containing a set of functions (like a shared object)
* @param {Object} cfg Optional, an object wich can be used in order to initialize the instance
* @constructor
*/
function Library(config){
this.name = false;
this.localPath = null;
this.remotePath = null;
//this.package = null;
this.isCiphered = false;
this.isNative = false;
this.functions = [];
for(let i in config) this[i]=config[i];
return this;
}
Library.prototype.addFunc = function(fn){
this.functions.push(fn);
return this;
}
Library.prototype.toJsonObject = function(){
let o =new Object();
o.name = this.name;
o.localPath = this.localPath;
o.remotePath = this.remotePath;
o.isCiphered = this.isCiphered;
//o.package = (this.package!=null)? this.package.toJsonObject() : null;
o.isNative = this.isNative;
o.functions = [];
for(let i in this.functions){
o.functions.push(this.functions[i].toJsonObject());
}
return o;
}
/**
* Represents a Syscall
* @param {Object} config Optional, an object wich can be used in order to initialize the instance
* @constructor
*/
function Syscall(config){
this.sysnum = [];
this.func_name = null;
this.sys_name = null;
this.args = [];
this.ret = null;
for(let i in config) this[i]=config[i];
return this;
}
Syscall.prototype.toJsonObject = function(){
let o = new Object();
for(let i in this) o[i] = this[i];
o.sysnum = this.sysnum.join(",");
o.args = this.args.join(",");
return o;
}
//Object.setPrototypeOf(Class.prototype,Savable);
/**
* Represents any kind of native function :
* - imported/exported functions
* - internal functions
* - syscall functions
* @param {Object} cfg
* @constructor
*/
function NativeFunction(cfg){
this.type = null;
this.sysnum = [];
this.library = null;
this.name = null;
this.args = [];
this.ret = null;
for(let i in cfg) this[i]=cfg[i];
return this;
}
NativeFunction.prototype.toJsonObject = function(){
let o = new Object();
}
// TDB
/**
* Represents a call to a method, a field or a class
* @param {Object} cfg Optional, an object wich can be used in order to initialize the instance
* @constructor
*/
function Call(cfg){
this.$ = STUB_TYPE.CALL;
this.instr = null;
this.caller = null;
this.calleed = null;
this.line = null;
this.type = null;
this.object = null;
this.subject = null;
this.tags = [];
if(cfg !== undefined)
for(let i in cfg) this[i] = cfg[i];
this.print = function(){
console.log("\t"+this.caller.hashCode()+" [:line"+this.instr.line+"] > \n\t\t"
+this.instr.opcode.instr+" "
+this.calleed.hashCode());
};
this.help = function(){
let t="+-------------------- HELP --------------------+";
t += EOL+"[-- Methods : ]";
t += EOL+"\t.print()\tPrint the call data";
t += EOL+"\t.help()\tThis help";
t += EOL+"[-- Properties : ]";
t += EOL+"\t.instr:<Instruction>\tGet the instruction";
t += EOL+"\t.caller:<Method>\tGet the method performing the call";
t += EOL+"\t.calleed:<*>\tGet the reference to the calleed";
t += EOL;
console.log(t);
}
return this;
}
Call.prototype.import = Savable.import;
Call.prototype.export = Savable.export;
Call.prototype.toJsonObject = function(){
let obj = new Object();
for(let i in this){
if(["_","$"].indexOf(i[0])==-1
&& (typeof this[i] != 'array')
&& (typeof this[i] != 'object')){
obj[i] = this[i];
}
else if(i == "tags"){
obj.tags = this.tags;
}
else if(i == "caller"){
obj.caller = this.caller.__signature__;
}
else if(i == "calleed"){
if(this.calleed instanceof Class)
obj.callee = this.calleed.name;
else
obj.callee = this.calleed.__signature__;
}
else if(i == "instr"){
obj.instr = this.instr.exportType(); //toJsonObject(["name"]);
}
}
return obj;
};
Call.prototype.addTag = function(tag){
this.tags.push(tag);
}
Call.prototype.hasTag = function(tagName){
return this.tags.indexOf(tagName)>-1;
}
Call.prototype.getTags = function(){
return this.tags;
}
/**
* Represents an Application's Method.
* It contains several kind of information :
* - the definition with teh instructions
* - locations of the call to this methods (cross references)
* - number of local variables
* - references to classes called, method called, field called, ...
* - eventually, some tags related to the method action
* - eventually, the value of the parameters at runtime
* @param {Object} cfg Optional, an object wich can be used in order to initialize the instance
* @constructor
*/
function Method(config){
// corresponding stub type to use during export
this.__stub_type__ = STUB_TYPE.METHOD;
this.$ = STUB_TYPE.METHOD;
this.alias = null;
this.name = null;
this.modifiers = null;
this.args = [];
this.ret = null;
this.instr = [];
this.datas = [];
this.switches = [];
this.probing = false;
this.locals = 0;
this.registers = 0;
this.params = 0;
this.enclosingClass = null;
this._hashcode = null;
// ========= Signatures ================
//
this.__callSignature__ = null;
this.__aliasedCallSignature__ = null;
this.__signature__ = null;
this.__pretty_signature__ = null;
this._callers = [];
// store arguments values catch at runtime
this.dyn = [];
this._useClass = {};
this._useClassCtr = 0;
this._useMethod = {};
this._useMethodCtr = 0;
this._useField = {};
this._useFieldCtr = 0;
this.tags = [];
this.isDerived = false;
if(config!==undefined)
for(let i in config)
this[i]=config[i];
this.callSignature2 = function(){
if(this.__callSignature__===null){
let xargs = "";
for(let i in this.args) xargs+="<"+this.args[i]._hashcode+">";
this.__callSignature__ = this.name+"("+xargs+")"+this.ret._hashcode;
}
return this.__callSignature__;
};
this.aliasedCallSignature = function(){
if(this.__aliasedCallSignature__===null){
let xargs = "";
// for(let i in this.args) xargs+="<"+this.args[i].signature()+">";
for(let i in this.args) xargs+=this.args[i].signature();
this.__aliasedCallSignature__ = this.alias+"("+xargs+")"+this.ret.signature();
}
return this.__aliasedCallSignature__;
}
this.callSignature = function(){
if(this.__callSignature__===null){
let xargs = "";
// for(let i in this.args) xargs+="<"+this.args[i].signature()+">";
for(let i in this.args) xargs+=this.args[i].signature();
this.__callSignature__ = this.name+"("+xargs+")"+this.ret.signature();
}
return this.__callSignature__;
};
this.hashCode = ()=>{
let xargs = "";
for(let i in this.args) xargs+="<"+this.args[i]._hashcode+">";
return this.enclosingClass.name+"|"+this.name+"|"+xargs+"|"+this.ret._hashcode;
};
this.dump = function(){
console.log("\t"+this._hashcode);
};
/**
* To build a strings containing the method canonical name, with the arguments types and orders,
* and the return type. This signature acts as a primary key into the DB and it is the
* unique identifier of a object, here a method.
*
* Be aware if you modify it you can break the engine !!
*
* @return {String} The method signature
* @function
*/
this.signature = function(){
if(this.__signature__ !== null) return this.__signature__;
let xargs = "", hash="";
for(let i in this.args) xargs+=""+this.args[i].signature()+"";
if(this.fqcn !== undefined)
hash = this.fqcn+"."+this.name+"("+xargs+")"+this.ret.signature();
else{
//console.log(this.ret);
hash = this.enclosingClass.name+"."+this.name+"("+xargs+")"+this.ret.signature();
}
this.__signature__ = hash;
this.callSignature();
return hash;
};
this.prettySignature = function(override=false){
if(!override && this.__pretty_signature__ != null){
return this.__pretty_signature__;
}
this.__pretty_signature__ = this.signatureFactory("__alias_signature__","alias");
return this.__pretty_signature__;
}
// this.signatureFactory("__signature__","name")
// this.signatureFactory("__alias_signature__","alias")
this.signatureFactory = function(ppt, seed){
if(this[ppt] !== null) return this[ppt];
let xargs = "", hash="";
for(let i in this.args) xargs+=""+this.args[i].signatureFactory(ppt, seed)+"";
if(this.fqcn !== undefined)
hash = this.fqcn+"."+this[seed]+"("+xargs+")"+this.ret.signatureFactory(ppt, seed);
else{
//console.log(this.ret);
hash = this.enclosingClass[seed]+"."+this[seed]+"("+xargs+")"+this.ret.signatureFactory(ppt, seed);
}
this[ppt] = hash;
return hash;
};
this.sprint = function(){
let s="\t"+this.modifiers.sprint()+" "+this.ret.sprint()+" "+this.name+"(";
for(let i in this.args){
s+=((i>1)?",":"")+this.args[i].sprint();
}
return s+")";
}
this.help = function(){
let t="+-------------------- HELP --------------------+";
t="\n[-- Methods : ]";
t += "\n\t.callSignature()\tGet the java signature of arguments part";
t += "\n\t.signature()\tGet the java signature of the field";
t += "\n\t.disass()\tShow the method contents in a gdb-style";
t += "\n\t.help()\tThis help";
t="\n[-- Properties : ]";
t += "\n\t.name:<string>\tGet the short name of the method";
t += "\n\t.enclosingClass:<Class>\tGet the enclosing class";
t += "\n\t.modifiers:<AccessFlags>\tGet the modifiers";
t += "\n\t.args:<*Type>[]\tGet the argument types";
t += "\n\t.ret:<*Type>\tGet the type of return value";
t += "\n\t.locals:<int>\tGet the number de locals";
t += "\n\t.regiters:\tGet the number de registers";
t += "\n";
console.log(t)
};
return this;
}
Method.prototype.setupMissingTag = function(){
return (this.tags.push(CONST.TAG.MISSING));
}
Method.prototype.removeMissingTag = function(){
if(this.tags.length==1)
this.tags = [];
else{
let i = this.tags.indexOf(CONST.TAG.MISSING);
let arr = this.tags.slice(0,i);
if(i+1<this.tags.length){
arr = arr.concat(this.tags.slice(i+1,this.tags.length-i-1));
}
this.tags = arr;
}
}
Method.prototype.isMissingClass = function(){
return (this.tags.indexOf(CONST.TAG.MISSING)>-1);
}
Method.prototype.raw_import = Savable.import;
Method.prototype.countUsedMethods = function(){
return this._useMethodCtr;
}
Method.prototype.countUsedClasses = function(){
return this._useClassCtr;
}
Method.prototype.countUsedFields = function(){
return this._useFieldCtr;
}
Method.prototype.getDataBlocks = function(){
return this.datas;
}
Method.prototype.getSwitchBlocks = function(){
return this.switches;
}
Method.prototype.compare = function(meth){
let diff = [];
for(let i in this){
switch(i){
case "_useClass":
case "_useMethod":
case "_useField":
case "_callers":
case "tags":
case "alias":
case "__aliasedCallSignature":
// TODO : Not yet supported
break;
case "__signature__":
case "__callSignature__":
case "name":
case "locals":
case "registers":
case "params":
if(this[i] != meth[i]){
diff.push({ ppt:i, old:this[i], new:meth[i] });
}
break;
case "instr":
if(this.instr.length != meth.instr.length){
diff.push({ ppt:"instr", old:this.instr.length, new:meth.instr.length });
}
break;
case "args":
if(this.args.length != meth.args.length){
diff.push({ ppt:"args", old:this.args.length, new:meth.args.length });
break;
}else{
// TODO
/*for(let j=0; j<this.args.length; j++){
if(this.args[j] )
obj.args.push(this.args[j].toJsonObject());
}*/
}
/*obj.args = [];
if(this.args.length != meth)
for(let j in this.args){
obj.args.push(this.args[j].toJsonObject());
}*/
break;
case "ret":
if(this.ret.signature() != meth.ret.signature()){
diff.push({ ppt:"ret", old:this.ret.signature(), new:meth.ret.signature() });
}
break;
case "enclosingClass":
if(this.enclosingClass.getName() != meth.enclosingClass.getName()){
diff.push({ ppt:"enclosingClass", old:this.enclosingClass.getName(), new:meth.enclosingClass.getName() });
}
// TODO
// obj.enclosingClass = (this.enclosingClass!=null)? this.enclosingClass.name : "";
break;
case "modifiers":
// obj.modifiers = this.modifiers.toJsonObject([
// "public","private","protected","abstract","native","final","constructor","static"]);
break;
}
}
return new NodeCompare(this, meth, ((diff.length>0)? diff : null));
}
Method.prototype.import = function(obj){
// raw impport
this.raw_import(obj);
// estor modifiers
this.modifiers = new Accessor.AccessFlags(obj.modifiers);
// restore return type
if(CONST.WORDS.indexOf(obj.ret.name)>-1){
this.ret = (new BasicType()).import(obj.ret);
}else{
this.ret = (new ObjectType()).import(obj.ret);
}
// restore args type
for(let i=0; i<obj.args.length ; i++){
if(CONST.WORDS.indexOf(obj.args[i].name)>-1){
this.args[i] = (new BasicType()).import(obj.args[i]);
}else{
this.args[i] = (new ObjectType()).import(obj.args[i]);
}
}
};
Method.prototype.export = Savable.export;
Method.prototype.toJsonObject = function(fields=[],exclude=[]){
let obj = new Object();
if(fields.length>0){
for(let i=0; i<fields.length; i++){
if(this[fields[i]] != null && this[fields[i]].toJsonObject != null){
obj[fields[i]] = this[fields[i]].toJsonObject();
}else{
obj[fields[i]] = this[fields[i]];
}
}
}else{
for(let i in this){
if(exclude.indexOf(i)>-1) continue;
// if(fields != null && fields.indexOf(i)==-1) continue;
switch(i){
case "_useClass":
obj._useClass = [];
for(let j in this._useClass){
if(this._useClass[i] != undefined)
obj._useClass.push(this._useClass[i].name);
}
break;
case "_useMethod":
obj._useMethod = [];
for(let j in this._useMethod){
if(this._useMethod[i] != undefined){
obj._useMethod.push(i); //this._useMethod[i].__signature__);
}
}
break;
case "_useField":
obj._useField = [];
for(let j in this._useField){
if(this._useField[i] != undefined)
obj._useField.push(this._useField[i].__signature__);
}
break;
case "_callers":
obj._callers = [];
for(let j=0; j<this._callers.length ; j++){
if(this._callers[j] != undefined)
if(this._callers[j] instanceof Method){
//console.log("Callers -> ",this._callers[i].signature());
obj._callers.push(this._callers[j].signature());
}else{
obj._callers.push(this._callers[j]);
}
}
break;
case "__signature__":
case "__callSignature__":
case "__aliasedCallSignature":
case "name":
case "alias":
case "locals":
case "registers":
case "params":
case "tags":
obj[i] = this[i];
break;
case "instr":
// basic blocks data are not serialized
break;
case "args":
obj.args = [];
for(let j in this.args){
obj.args.push(this.args[j].toJsonObject());
}
break;
case "ret":
obj.ret = this.ret.toJsonObject();
break;
case "enclosingClass":
obj.enclosingClass = (this.enclosingClass!=null)? this.enclosingClass.name : "";
break;
case "modifiers":
if(this.modifiers != null)
obj.modifiers = this.modifiers.toJsonObject();
else
obj.modifiers = null;
break;
}
}
}
return obj;
};
Method.prototype.disass = function(cfg){
let disass = require("./Disassembler.js")
if(cfg != null){
if(cfg.pretty == true)
return disass.methodPretty(this);
else if(cfg.raw == true)
return disass.methodRaw(this);
}else{
return disass.method(this);
}
};
Method.prototype.getTaggedBlock = function(tag){
for(let i in this.instr){
if(this.instr[i].tag==tag) return this.instr[i];
}
return null;
};
Method.prototype.getBasicBlocks = function(){
return this.instr;
};
Method.prototype.getBlock = function(offset){
for(let i in this.instr){
if(i==offset) return this.instr[i];
}
return null;
};
Method.prototype.getModifier = function(){
return this.modifiers;
};
Method.prototype.getInstr = function(offsetBB,offsetInstr){
for(let i in this.instr){
if(i == offsetBB){
for(let j in this.instr[i].stack){
if(j == offsetInstr){
return this.instr[i].stack[j];
}
}
}
}
return null;
};
Method.prototype.getInstrNearTo = function(offsetBB,offsetInstr,windowSize=3){
let min = offsetInstr-windowSize;
let max = offsetInstr+windowSize;
let instr = [];
for(let i in this.instr){
if(i == offsetBB){
for(let j in this.instr[i].stack){
if(j > min && j < max){
instr.push(this.instr[i].stack[j]);
}
}
}
}
return instr;
};
Method.prototype.newImplementationBy = function(cls){
let meth = new Method();
// partial deep copy :
// - primitive value are copied
// - object are passed by reference
for(let i in this){
if(typeof this[i] != "function"){
meth[i]=this[i];
}
}
meth.isDerived = true;
meth.declaringClass = cls;
return meth;
}
Method.prototype.setProbing = function(flag){
this.probing = flag;
}
Method.prototype.addCallValue = function(dyn){
this.dyn.push(dyn);
return this;
}
Method.prototype.getCallValues = function(dyn){
return this.dyn;
}
Method.prototype.getAlias = function(){
return this.alias;
}
Method.prototype.setAlias = function(name){
this.alias = name;
this.__aliasedCallSignature__ = this.aliasedCallSignature();
}
Method.prototype.setEnclosingClass = function(cls){
this.enclosingClass = cls;
}
Method.prototype.getEnclosingClass = function(){
return this.enclosingClass;
}
Method.prototype.setReturnType = function(rettype){
this.ret = rettype;
}
Method.prototype.getReturnType = function(){
return this.ret;
}
Method.prototype.setArgsType = function(argsType){
this.args = argsType;
}
Method.prototype.getArgsType = function(){
return this.args;
}
Method.prototype.hasArgs = function(){
return this.args.length > 0;
}
Method.prototype.addTag = function(tag){
this.tags.push(tag);
}
Method.prototype.hasTag = function(tagName){
return (this.tags.indexOf(tagName) > -1);
}
Method.prototype.getTags = function(){
return this.tags;
}
Method.prototype.getCallers = function(){
return this._callers;
}
Method.prototype.addCaller = function(meth){
if(this._callers.indexOf(meth.signature()) == -1)
this._callers.push(meth.signature());
}
Method.prototype.getMethodUsed = function(){
return this._useMethod;
}
Method.prototype.addMethodUsed = function(method, call){
if(this._useMethod[method.signature()] == null)
this._useMethod[method.signature()] = [];
this._useMethod[method.signature()].push(call);
}
Method.prototype.getClassUsed = function(){
return this._useClass;
}
Method.prototype.getFieldUsed = function(){
return this._useField;
}
Method.prototype.getTryStartBlock = function(pLabel){
let bb = this.getBasicBlocks();
for(let i=0; i<bb.length; i++){
if(bb[i].getTryStartLabel()==pLabel){
return bb[i];
}
}
return null;
}
Method.prototype.getTryEndBlock = function(pLabel){
let bb = this.getBasicBlocks();
for(let i=0; i<bb.length; i++){
if(bb[i].getTryEndLabel()==pLabel){
return bb[i];
}
}
return null;
}
Method.prototype.getCatchBlock = function(pLabel){
let bb = this.getBasicBlocks();
for(let i=0; i<bb.length; i++){
if(bb[i].getCatchLabel()==pLabel){
return bb[i];
}
}
return null;
}
Method.prototype.getBasicBlockByLabel = function(pLabel, pType){
//if(pType == CONST.INSTR_TYPE.IF){
switch(pType)
{
case CONST.INSTR_TYPE.IF:
for(let i=0; i<this.instr.length; i++){
//console.log(this.instr[i]);
if(this.instr[i].isConditionalBlock() && this.instr[i].cond_name==pLabel)
return this.instr[i];
}
break;
case CONST.INSTR_TYPE.GOTO:
for(let i=0; i<this.instr.length; i++){
if(this.instr[i].isGotoBlock() && this.instr[i].goto_name==pLabel)
return this.instr[i];
}
break;
}
return null;
}
Method.prototype.appendBlock = function(block, callback=null){
if(block instanceof BasicBlock){
block.offset = this.instr.length;
this.instr.push(block);
if(callback != null && callback.basicblock != null)
callback.basicblock(this, block);
}
else if(block instanceof DataBlock){
block.setParent(this, this.datas.length);
this.datas.push(block);
if(callback != null && callback.datablock != null)
callback.datablock(this, block);
}
}
Method.prototype.getDataBlockByTag = function( pTag){
let ds = []
for(let i=0; i<this.datas.length; i++){
if(this.datas[i].tags.indexOf(pTag)>-1)
ds.push(this.datas[i]);
}
return ds;
}
Method.prototype.getDataBlockByName = function( pName){
for(let i=0; i<this.datas.length; i++){
if(this.datas[i].name == pName)
return this.datas[i];
}
return null;
}
/*
Method.prototype.getStringUsed = function(){
return this._useMethod;
}
Method.prototype.getArrayUsed = function(){
return this._useMethod;
}
*/
/*
function CondTag(){
this.name = null;
return this;
}
function GotoBlock(){
this.name = null;
return this;
}
function ArrayBlock(){
this.name = null;
return this;
}
function SwitchBlock(){
this.name = null;
return this;
}*/
/*
function Tag(tag){
this.$ = STUB_TYPE.TAG;
this.name = tag;
return this;
}*/
class Tag
{
constructor(pLabel){
this.$ = STUB_TYPE.TAG;
this.name = pLabel;
}
getLabel(){
return this.name.substr(1,this.name.length);
}
isGoto(){
return this.name.startsWith(":goto");
}
isCond(){
return this.name.startsWith(":cond");
}
}
class ArrayData
{
constructor(dataWidth){
this.width = dataWidth;
this.data = [];
}
push(val){
this.data.push(val)
}
length(){
this.data.length();
}
}
/**
* To represent a specific case into a switch statement
*/
class SwitchCase
{
constructor(value, target, type){
this.value = value;
this.target = target;
this.type = type;
}
}
/**
* To represent a packed switch statement
*/
class SparseSwitchStatement
{
constructor(){
this.cases = {};
this.length = 0;
}
appendCase(key,val){
this.cases[key] = new SwitchCase(key, val, CONST.CASE_TYPE.SPARSE);
this.length++;
}
getKeys(){
return Object.keys(this.cases);
}
}
/**
* To represent a packed switch statement
*/
class PackedSwitchStatement
{
constructor(start){
this.start = start;
this.cases = {};
this.offset = start;
this.length = 0;
}
appendCase(tag){
this.cases[this.offset+1] = new SwitchCase(this.offset+1, tag, CONST.CASE_TYPE.PACKED);
this.offset++;
this.length++;
}
getStartValue(radix=16){
return this.start.toString(radix);
}
forEach(fn){
for(let i in this.cases) fn(i, this.cases[i]);
}
}
class DataBlock
{
constructor(dataWidth=null){
this.line = -1;
this.offset = -1;
//this._parent = null;
this.stack = [];
this.tag = null;
this.tags = [];
this.name = null;
//this.values = Buffer.alloc(CONST.MAX.DATABLOCK_SIZE);
this.values = [];
this.width = dataWidth;
this.length = 0;
this.uid = null;
this.virtual64 = false;
if(64==dataWidth && this.values.readBigUInt64LE == null){
this.virtual64 = true;
this.values = [];
}
}
getUID(){
return this.uid;
}
setParent(parent, offset){
if(! parent instanceof Method)
throw Error("The parent of this DataBlock is not a function.");
this.parent = parent;
this.uid = this.parent.signature();
this.uid += ":";
this.uid += (this.name != null)? this.name : 'data_'+offset;
}
pushData(val, isNegative, isShort){
this.values.push(new Number(val));
if(isNegative)
this.values.push( new Number(-this.values.pop() ));
this.length++;
//Logger.debug( (isNegative?'-':'+')+val, this.values[this.values.length-1].toString(16) );
}
read(offset){
return this.values[offset];
}
size(){
return this.length * (this.width >> 3);
}
count(){
return this.length;
}
getByteWidth(){
return this.width>>3;
}
isInt64Array(){
return (this.width == 64);
}
setDataWidth(width){
switch(width){
case 1:
this.width = 8;
break;
case 2:
this.width = 16;
break;
case 4:
this.width = 32;
break;
case 8:
this.width = 64;
if(this.values.readBigUInt64LE == null){
this.virtual64 = true;
this.values = [];
}
break;
}
}
toJsonObject(exclude=[]){
let o = new Object();
for(let i in this){
if(exclude.indexOf(i)>-1) continue;
if(this[i]==null) continue;
switch(i){
case "tags":
if(this[i].length > 0)
o[i] = this[i];
break;
case "line":
case "offset":
if(this[i] > -1)
o[i] = this[i];
break;
case "name":
case "length":
case "width":
case "uid":
o[i] = this[i];
break;
case "parent":
o.parent = this.parent.signature();
break;
case "values":
o.values = this.values;
break;
}
}
console.log(JSON.stringify(this.values));
return o;
}
}
class TagCategory
{
constructor(name, taglist){
this.name = name;
this.taglist = taglist;
}
addTag(tag){
if(this.taglist.indexOf(tag)==-1)
this.taglist.push(tag);
}
getTags(){
return this.taglist;
}
toJsonObject(){
let o = new Object();
o.name = this.name;
o.taglist = this.taglist;
return o;
}
}
/**
* Represents a basic block of dalvik instruction
*/
class BasicBlock
{
/**
* @param {Object} config Optional, an object wich can be used in order to initialize the instance
* @constructor
*/
constructor(config=null){
this.$ = STUB_TYPE.BASIC_BLOCK;
this.line = -1;
this.prologue = false;
this.stack = [];
this.offset = -1;
this._parent = null;
this.tag = null;
this.tags = [];
// special block name
this.cond_name = null;
this.goto_name = null;
this.catch_name = null;
this.try_name = null;
this.try_end_name = null;
//this.catch_cond = null;
this.switch_case = null;
this.switch_statement = null;
// special child
this.linked_try_block = null;
this.linked_catch_block = null;
this.duplicate = null;
this.switch = null;
this.array_data = null;
this.array_data_name = null;
this.succ = [];
this.pred = [];
this.catch = [];
if(config!=null)
for(let i in config)
this[i]=config[i];
}
/**
* To check if the block contains only NOP instruction
* @returns {Boolean} Returns TRUE if thhe block contains only NOP instruction, else FALSE
*/
isNOPblock(){
for(let i=0; i<this.stack.length; i++){
if(this.stack[i].opcode.type != CONST.INSTR_TYPE.NOP){
return false;
}
}
return true;
}
hasCatchStatement(){
return this.catch.length>0;
}
getCatchStatements(){
return this.catch;
}
addCatchStatement(pStmt){
this.catch.push(pStmt);
}
isVisited(){
return (this.visited !== undefined) && (this.visited==true);
}
visit(){
this.visited = true;
return this;
}
initVisit(){
this.visited = false;
return this;
}
getSuccessors(){
return this.succ;
}
addSuccessor(pBasicBlock){
this.succ.push(pBasicBlock);
}
hasSuccessor(pBasicBlock){
return this.succ.indexOf(pBasicBlock)>-1;
}
hasSuccessors(){
return this.succ.length > 0;
}
getPredecessors(){
return this.pred;
}
addPredecessor(pBasicBlock){
this.pred.push(pBasicBlock);
}
hasPredecessor(pBasicBlock){
return this.pred.indexOf(pBasicBlock)>-1;
}
hasMultiplePredecessors(){
return this.pred.length>1;
}
hasPredecessors(){
return this.pred.length > 0;
}
dump(){
console.log("\tBasic Block (line "+this.line+"):\n-------------------------");
for(let i in this.stack){
this.stack[i].dump();
}
console.log("-------------------------");
}
clone(clean=true){
let bb = new BasicBlock();
for(let i in this){
bb[i] = this[i];
}
if(clean){
//bb.cond_name = null;
//bb.goto_name = null;
bb.catch_name = null;
bb.try_name = null;
bb.try_end_name = null;
//bb.catch_name = null;
bb.duplicate = true;
}
return bb;
}
disass(){
let disass = require("./Disassembler.js")
disass.block(this._parent,this,0);
}
hasInstr(type){
for(let i in this.stack){
if(this.stack[i].opcode.type==type) return true;
}
return false;
}
setAsConditionalBlock(name){
this.cond_name = name;
}
isConditionalBlock(){
return this.cond_name != null;
}
getCondLabel(){
return this.cond_name;
}
setAsGotoBlock(name){
this.goto_name = name;
}
isGotoBlock(){
return this.goto_name != null;
}
getGotoLabel(){
return this.goto_name;
}
setAsTryBlock(name){
this.try_name = name;
}
getTryStartLabel(){
return this.try_name;
}
getTryEndLabel(){
return this.try_end_name;
}
setTryEndName(name){
this.try_end_name = name;
}
getTryEndName(){
return this.try_end_name;
}
isTryBlock(){
return this.try_name != null;
}
isTryEndBlock(){
return this.try_end_name != null;
}
setAsCatchBlock(name){
this.catch_name = name;
}
setCatchCond(name){
this.catch_name = name;
}
isCatchBlock(){
return this.catch_name != null;
}
getCatchLabel(){
return this.catch_name;
}
setAsArrayData(name){
this.array_data_name = name;
}
setAsSwitchCase(name){
this.switch_case = name;
}
setAsSwitchStatement(name){
this.switch_statement = name;
}
isSwitchStatement(){
return (this.switch_statement != null) && (this.switch != null);
}
isSwitchCase(){
return this.switch_case != null;
}
setupPackedSwitchStatement(start_value){
this.switch = new PackedSwitchStatement(start_value);
}
setupSparseSwitchStatement(){
this.switch = new SparseSwitchStatement();
}
getSwitchStatement(){
return this.switch;
}
getSwitchCaseName(){
return this.switch_case;
}
getSwitchStatementName(){
return this.switch_statement;
}
getInstructions(){
return this.stack;
}
}
/**
* Represents an Application's Field
* @param {Object} config Optional, an object wich can be used in order to initialize the instance
* @constructor
*/
function Field(config){
// corresponding stub type to use during export
this.__stub_type__ = STUB_TYPE.FIELD;
this.$ = STUB_TYPE.FIELD;
this.alias = null;
this.fqcn = null;
this.name = null;
this.modifiers = null;
this.type = null;
this.instr = null;
this.enclosingClass = null;
this.__signature__ = null;
this.__aliasedSignature__ = null;
this._hashcode = null;
this._isBinding = false;
this._callers = [];
this._getters = [];
this._setters = [];
this.tags = [];
if(config!==undefined)
for(let i in config)
this[i]=config[i];
return this;
}
Field.prototype.setupMissingTag = function(){
return (this.tags.push(CONST.TAG.MISSING));
}
Field.prototype.removeMissingTag = function(){
if(this.tags.length==1)
this.tags = [];
else{
let i = this.tags.indexOf(CONST.TAG.MISSING);
let arr = this.tags.slice(0,i);
if(i+1<this.tags.length){
arr = arr.concat(this.tags.slice(i+1,this.tags.length-i-1));
}
this.tags = arr;
}
}
Field.prototype.isMissingClass = function(){
return (this.tags.indexOf(CONST.TAG.MISSING)>-1);
}
/**
* To generate the aliased signature. This signature is used only by the GUI
* component. Its aim is to improve the user experience by propagating the
* alias value.
*
* @param {Boolean} update If TRUE the cached aliased signature is updated, else it returns the cached signature is returned
* @returns {String} The aliased signature
*/
Field.prototype.aliasedSignature = function(update=false){
if(!update || this.__aliasedSignature__==null){
this.__aliasedSignature__ = this.type.signature()+" "+this.alias;
}
return this.__aliasedSignature__;
}
Field.prototype.getAlias = function(){
return this.alias;
}
Field.prototype.compare = function(field){
let diff = [];
for(let i in this){
switch(i){
case "__signature__":
case "__aliasedSignature__":
case "fqcn":
case "name":
case "_isBinding":
if(this[i] != field[i]){
diff.push({ ppt:i, old:this[i], new:field[i] });
}
break;
case "tags":
// TODO : Not yet supported
break;
case "type":
if(this.type != field.type){
diff.push({ ppt:"type", old:this.type.signature(), new:field.type.signature() });
}
break;
case "modifiers":
// TODO : Not yet supported
break;
case "alias":
case "_getters":
case "_setters":
case "_callers":
case "instr":
case "enclosingClass":
// ignore
break;
}
}
return new NodeCompare(this, field, ((diff.length>0)? diff : null));
}
/**
* To set an alias and update the aliased signature
*
* @param {String} name The alias value
* @function
*/
Field.prototype.setAlias = function(name){
this.alias = name;
this.aliasedSignature(true);
}
Field.prototype.raw_import = Savable.import;
Field.prototype.import = function(obj){
// raw impport
this.raw_import(obj);
// estor modifiers
this.modifiers = new Accessor.AccessFlags(obj.modifiers);
// restore return type
if(CONST.WORDS.indexOf(obj.type.name)>-1){
this.ret = (new BasicType()).import(obj.type);
}else{
this.ret = (new ObjectType()).import(obj.type);
}
};
Field.prototype.addSetter = function(meth){
if(this._setters.indexOf(meth)==-1)
this._setters.push(meth);
}
Field.prototype.getSetters = function(){
return this._setters;
}
Field.prototype.addGetter = function(meth){
if(this._getters.indexOf(meth)==-1)
this._getters.push(meth);
}
Field.prototype.getGetters = function(){
return this._getters;
}
Field.prototype.export = Savable.export;
Field.prototype.toJsonObject = function(fields=null,exclude=null){
let obj = new Object();
/*if(fields.length>0){
for(let i in fields){
if(this[fields[i]] != null && (typeof this[fields[i]] == "object")){
obj[fields[i]] = this[fields[i]].toJsonObject();
}else{
obj[fields[i]] = this[fields[i]];
}
}
}else{*/
for(let i in this){
if((fields instanceof Array) && fields.indexOf(i)==-1) continue;
//if((exclude instanceof Array) && exclude.indexOf(i)>-1) continue;
switch(i){
case "_getters":
case "_setters":
case "_callers":
obj[i] = [];
for(let j=0; j<this[i].length; j++){
if(this[i][j] != undefined)
obj[i].push(this[i][j].__signature__); // getSignature()
}
break;
case "__signature__":
case "__aliasedSignature__":
case "fqcn":
case "name":
case "alias":
case "_isBinding":
obj[i] = this[i];
break;
case "tags":
if(this[i].length > 0)
obj[i] = this[i];
break;
case "instr":
break;
case "type":
if(this.type != null)
obj.type = this.type.toJsonObject();
else
obj.type = null;
break;
case "enclosingClass":
if(this.enclosingClass != null){
obj.enclosingClass = {
name: this.enclosingClass.name
};
if(this.enclosingClass.alias!=null)
obj.enclosingClass.alias = this.enclosingClass.alias;
}
break;
case "modifiers":
if(this.modifiers != null)
obj.modifiers = this.modifiers.toJsonObject();
else
obj.modifiers = null;
break;
}
}
//}
return obj;
}
Field.prototype.help = function(){
let t="+-------------------- HELP --------------------+";
t += "\n\t.getCallers()\tExecute the function <fn> for each row of the result set";
t += "\n\t.signature()\tGet the java signature of the field";
t += "\n\t.sprint()\tPrint object in a string";
t += "\n\t.help()\tThis help";
t += "\n";
console.log(t)
};
Field.prototype.sprint = function(){
let s="\t"+this.modifiers.sprint()+" "+this.type.sprint()+" "+this.name;
if(this.value != null)
s+=" := "+this.value
return s;
};
Field.prototype.getCallers = ()=>{
return this._callers;
};
Field.prototype.hashCode = function(){
if(this.enclosingClass === undefined) console.log(this);
return this.enclosingClass.name+"|"+this.name;//+"|"+this.type._hashcode;
};
Field.prototype.signature = function(){
if(this.__signature__ !== null) return this.__signature__;
if(this.enclosingClass !== null)
this.__signature__ = this.enclosingClass.name+";->"+this.name;
else
this.__signature__ = this.fqcn+";->"+this.name;
return this.__signature__;
};
Field.prototype.addTag = function(tag){
this.tags.push(tag);
}
Field.prototype.hasTag = function(tagName){
return this.tags.indexOf(tagName)>-1;
}
Field.prototype.getTags = function(){
return this.tags;
}
/**
* Represents a reference to a field in the Application bytecode
* @param {Object} config Optional, an object wich can be used in order to initialize the instance
* @constructor
*/
function FieldReference(config){
this.fqcn = null;
this.field = null;
this.name = null;
this.isArray = false;
this._hashcode = "";
this.tags = [];
this.enclosingClass = null;
this.tags = [];
this.toField = function(cls){
let x=new Field();
x.fqcn = this.fqcn;
x.name = this.name;
if(cls !== null && cls !== undefined){
x.enclosingClass = cls;
x._hashcode = x.hashCode();
}
return x;
};
this.signature = function(){
return this.fqcn+";->"+this.name;
};
if(config!==undefined)
for(let i in config)
this[i]=config[i];
return this;
}
/**
* Represents a reference to a class in the Application bytecode
* @param {string} fqcn The Full-Qualified Class Name of the class
* @constructor
*/
function ClassReference(fqcn){
this.fqcn = fqcn;
return this;
}
/**
* Represents a reference to a method in the Application bytecode
* @param {Object} cfg Optional, an object wich can be used in order to initialize the instance
* @constructor
*/
function MethodReference(cfg){
this.fqcn = null;
this.name = null;
this.args = null;
this.ret = null;
this.enclosingClass = null;
this._hashcode = "";
this.__callSignature__ = null;
this.tags = [];
this.hashCode = ()=>{
let xargs = "";
for(let i in this.args) xargs+="<"+this.args[i]._hashcode+">";
this._hashcode = this.fqcn+"|"+this.name+"|"+xargs+"|"+this.ret._hashcode;
return this._hashcode;
};
this.toMethod = function(cls){
let x=new Method();
x.fqcn = this.fqcn;
x.name = this.name;
x.args = this.args;
x.ret = this.ret;
x.__callSignature__ = this.__callSignature__;
if(cls !== null && cls !== undefined){
x.enclosingClass = cls;
x._hashcode = x.hashCode();
}
return x;
};
/**
* To generate the method signature from the reference. The aim of this value
* is to help to resolve the symbols.
*/
this.signature = function(){
let xargs = "";
/*
for(let i in this.args){
if(this.args[i]._hashcode[0]=="L"){
xargs+="<"+this.args[i]._hashcode.substr(1,this.args[i]._hashcode.length-2)+">";
}else{
xargs+="<"+this.args[i]._hashcode+">";
}
}*/
for(let i in this.args){
//console.log(this.args[i]);
xargs += this.args[i].signature();
/*
if(this.args[i] instanceof ObjectType){
xargs+="<"+this.args[i]._hashcode.substr(1,this.args[i]._hashcode.length-2)+">";
}else{
xargs+="<"+this.args[i]._hashcode+">";
}*/
}
let ret = ""; //this.ret._hashcode;
/*if(ret[0]=="L"){
ret = ret.substr(1, ret.length-2);
}*/
ret = this.ret.signature();
// ret = "<"+ret+">";
//if(this.enclosingClass === undefined) console.log(this._hashcode);
if(this.fqcn !== undefined)
return this.fqcn+"."+this.name+"("+xargs+")"+ret;//this.ret._hashcode;
else
return this.enclosingClass.name+"."+this.name+"("+xargs+")"+ret;//this.ret._hashcode;
};
/**
* Idem as signature(), but the signature returned is not canonical
* (class FQCN has been remove). The aim of this signature is to resolve extended or overloaded
* methods.
*/
this.callSignature = function(){
//if(this.__callSignature__===null){
let xargs = "";
let ret = this.ret.signature(); // ._hashcode;
/*if(ret[0]=="L"){
ret = ret.substr(1, ret.length-2);
}*/
for(let i in this.args){
xargs += this.args[i].signature();
/*
if(this.args[i]._hashcode[0]=="L"){
xargs+="<"+this.args[i]._hashcode.substr(1,this.args[i]._hashcode.length-2)+">";
}else{
xargs+="<"+this.args[i]._hashcode+">";
}*/
}
//for(let i in this.args) xargs+="<"+this.args[i]._hashcode+">";
this.__callSignature__ = this.name+"("+xargs+")"+ret;
//}
return this.__callSignature__;
};
/*
this.equalCallSignatureOf = function(method){
if(this._callSignature===null) this._callSignature = this.callSignature();
if(method._callSignature===null) this._callSignature = this.callSignature();
};
*/
if(cfg !== null){
for(let i in cfg) this[i] = cfg[i];
/* if(this.fqcn !== null && this.name !== null
&& this.args !== null && this.ret !== null){
this.hashCode();
} */
}
return this;
}
/**
* Represents an instruction from the Application bytecode
* @param {Object} config Optional, an object wich can be used in order to initialize the instance
* @constructor
*/
function Instruction(config){
this.$ = STUB_TYPE.INSTR;
this._raw = null;
this._call = null;
this._parent = null;
this.iline = null;
// operands
this.opcode = null;
this.left = null;
this.right = null;
// experimental
this.read = false;
// info
this.offset = 0;
// VM
this.value = null;
// TODO : should be inherit
this.tags = [];
if(config!==undefined)
for(let i in config)
this[i]=config[i];
return this;
}
Instruction.prototype.export = Savable.export;
Instruction.prototype.import= Savable.import;
//Instruction.prototype.
Instruction.prototype.eval = function(vm){
vm.restore(this.operands);
this.opcode.eval(vm,this.operands);
};
Instruction.prototype.setCalledObj = function(obj){
this._call = obj;
};
Instruction.prototype.isUsingString = function(){
return (this.right !== undefined) && (this.opcode.reftype==CONST.OPCODE_REFTYPE.STRING);
};
Instruction.prototype.isCallingField = function(){
return (this.right !== undefined) && (this.opcode.reftype==CONST.OPCODE_REFTYPE.FIELD);
};
Instruction.prototype.isStaticCall = function(){
return (this.opcode.flag & CONST.OPCODE_TYPE.STATIC_CALL);
};
Instruction.prototype.isSetter = function(){
return (this.opcode.type==CONST.INSTR_TYPE.SETTER);
};
Instruction.prototype.isGetter = function(){
return (this.opcode.type==CONST.INSTR_TYPE.GETTER);
};
Instruction.prototype.isNOP = function(){
return (this.opcode.type==CONST.INSTR_TYPE.NOP);
};
Instruction.prototype.isDoingCall = function(){
return (this.right !== undefined) && (this.opcode.reftype==CONST.OPCODE_REFTYPE.METHOD);
};
Instruction.prototype.isReferencingType = function(){
return (this.right !== undefined) && (this.opcode.reftype==CONST.OPCODE_REFTYPE.TYPE);
};
Instruction.prototype.dump = function(){
console.log(("\t".repeat(tab))+this.opcode);
};
Instruction.prototype.printRaw = function(tab=2){
console.log(("\t".repeat(tab))+this._raw);
};
Instruction.prototype.exportType = function(){
return CONST.INSTR_TYPE_LABEL[this.opcode.type];
}
Instruction.prototype.toJsonObject = function(parent){
let o = new Object();
o.raw = this._raw;
o.offset = this.offset;
o.location = { offset:this.offset, bb:null };
o.method = "";
if(this._parent instanceof BasicBlock){
o.offset.bb = this._parent.offset;
if(this._parent._parent instanceof Method){
o.method = this._parent._parent.signature();
}
}
//o.parent = this.parent.toJsonObject();
return o;
};
Instruction.prototype.toString = function(){
return this._raw;
}
Instruction.prototype.addTag = function(tag){
this.tags.push(tag);
}
Instruction.prototype.hasTag = function(tagName){
return this.tags.indexOf(tagName)>-1;
}
Instruction.prototype.getTags = function(){
return this.tags;
}
/**
* Represents a reference to a missing Class/Method/Field.
* When a reference to a type cannot be resolved during tree building,
* the original reference (Class/Method/Field) is encapsulate into a MissingReference object
*
* @param {int} type The type of reference according to the OPCODE_REFTYPE list
* @param {Object} data The data to encapsulate
* @param {Class} type The enclosing class if it's a reference to a Method or a Field
* @constructor
*/
class MissingReference
{
constructor(type, data, enclosingClass){
this._log_tag = "";
this._type = type;
this._callers = [];
this.enclosingClass = enclosingClass;
this.tags = [];
for(let i in data)
if(this[i] ===undefined) this[i] = data[i];
switch(this._type){
case CONST.OPCODE_REFTYPE.TYPE:
this._log_tag = "[TYPE]";
break;
case CONST.OPCODE_REFTYPE.FIELD:
this._log_tag = "[FIELD]";
break;
case CONST.OPCODE_REFTYPE.METHOD:
this._log_tag = "[METHOD]";
break;
case CONST.OPCODE_REFTYPE.STRING:
this._log_tag = "[STRING]";
break;
}
//this._val = data;
this.dump = ()=>{
console.log(this._log_tag+" "+this._val);
};
return this;
}
toJsonObject(include=null){
let o = new Object();
for(let i in this){
if(include instanceof Array && include.indexOf(i)==-1) continue;
switch(i){
case "_log_tag":
case "_type":
case "tags":
o[i] = this[i];
break;
case "_callers":
if(this._callers.length > 0){
o._callers = [];
for(let i=0; i<this._callers.length; i++){
o._callers.push(this._callers[i].toJsonObject(["__signature__","alias"]));
}
}
break;
case "enclosingClass":
if(o[i] != null){
o[i] = this[i].toJsonObject(["name","alias"]);
}
break;
}
}
return o;
}
addTag(tag){
this.tags.push(tag);
}
hasTag(tagName){
return this.tags.indexOf(tagName)>-1;
}
getTags(){
return this.tags;
}
}
/**
* Represents a reference to a variable in the Application bytecode
* @param {char} type The type of variable reference (local, register, params)
* @param {int} id The register ID / stack offset of the variable
* @constructor
*/
function Variable(type,id){
this.$ = STUB_TYPE.VARIABLE;
this.id = id;
this.type = type;
this.tags = [];
return this;
}
Variable.prototype.import = Savable.import;
Variable.prototype.export = Savable.export;
Variable.prototype.addTag = function(tag){
this.tags.push(tag);
}
Variable.prototype.hasTag = function(tagName){
return this.tags.indexOf(tagName)>-1;
}
Variable.prototype.getTags = function(){
return this.tags;
}
/**
* Represents a constant value into the Applciation bytecode
* @param {*} value the value
* @param {*} tags some additional tags
* @constructor
*/
class ValueConst{
constructor(pValue, pTags){
this.$ = STUB_TYPE.VALUE_CONST;
this._value = pValue;
this.tags = pTags;
}
getValue(){
return this._value;
}
}
ValueConst.prototype.import = Savable.import;
ValueConst.prototype.export = Savable.export;
/*
{
src:method._hashcode,
instr:instruct,
value:instruct.right._value }
*/
function StringValue(cfg){
this.src = null;
this.instr = null;
this.value = null;
this.tags = [];
for(let i in cfg)
this[i] = cfg[i];
return this;
}
StringValue.prototype.import = Savable.import;
StringValue.prototype.export = Savable.export;
StringValue.prototype.toJsonObject = function(){
let o = new Object();
o.value = this.value;
o.instr = this.instr.toJsonObject();
o.tags = this.tags;
//console.log(this.instr);
return o;
};
StringValue.prototype.addTag = function(tag){
this.tags.push(tag);
}
StringValue.prototype.hasTag = function(tagName){
return this.tags.indexOf(tagName)>-1;
}
StringValue.prototype.getTags = function(){
return this.tags;
}
var all_exports = {
Package: Package,
Class: Class,
Method: Method,
Field: Field,
DataBlock: DataBlock,
BasicBlock: BasicBlock,
ObjectType: ObjectType,
BasicType: BasicType,
Call: Call,
FieldReference: FieldReference,
MethodReference: MethodReference,
ClassReference: ClassReference,
Instruction: Instruction,
MissingReference: MissingReference,
ValueConst: ValueConst,
Variable: Variable,
StringValue: StringValue,
XRef: XRef,
Tag: Tag,
Stub: Stub,
STUB_TYPE: STUB_TYPE,
ByteArray: ByteArray,
NativeFunction: NativeFunction,
Library: Library,
Syscall: Syscall,
FUNC_TYPE: FUNC_TYPE,
BUILTIN_TAG: BUILTIN_TAG,
SwitchCase: SwitchCase,
PackedSwitchStatement: PackedSwitchStatement,
TagCategory: TagCategory,
File: File,
SerializedObject: SerializedObject,
CatchStatement: CatchStatement,
RegisterRef: RegisterRef
};
SerializedObject.init(all_exports);
module.exports = all_exports;