/*
* Copyright (C) 2012 Sony Mobile Communications AB
*
* This file is part of ApkAnalyser.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package mereflect;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import mereflect.bytecode.Bytecode;
import mereflect.bytecode.Bytecodes;
import mereflect.info.AbstractRefInfo;
import mereflect.info.AiCode;
import mereflect.info.AiExceptions;
import mereflect.info.AiLineNumberTable;
import mereflect.info.AttributeInfo;
import mereflect.info.CiClass;
import mereflect.info.CiNameAndType;
import mereflect.info.CiUtf8;
import mereflect.info.ClassInfo;
import mereflect.io.DescriptorParser;
import org.jf.dexlib.Code.Instruction;
import andreflect.Util;
public class MEMethod {
public static final int ACC_PUBLIC = 0x0001;
public static final int ACC_PRIVATE = 0x0002;
public static final int ACC_PROTECTED = 0x0004;
public static final int ACC_STATIC = 0x0008;
public static final int ACC_FINAL = 0x0010;
public static final int ACC_SYNCHRONIZED = 0x0020;
public static final int ACC_NATIVE = 0x0100;
public static final int ACC_ABSTRACT = 0x0400;
public static final int ACC_STRICT = 0x0800;
public static final String CTOR_METHOD_NAME = "<init>";
protected MEClass m_class;
protected int m_accFlags;
protected int m_nameIndex;
protected int m_descIndex;
protected AttributeInfo[] m_attributes;
protected Type m_returnType = null;
protected Type[] m_arguments = null;
protected MEClass[] m_exceptions = null;
protected AiLineNumberTable m_lineNumberTable = null;
public MEMethod(MEClass clazz) {
m_class = clazz;
}
// Logic
public boolean isPublic() {
return (m_accFlags & ACC_PUBLIC) > 0;
}
public boolean isPrivate() {
return (m_accFlags & ACC_PRIVATE) > 0;
}
public boolean isProtected() {
return (m_accFlags & ACC_PROTECTED) > 0;
}
public boolean isStatic() {
return (m_accFlags & ACC_STATIC) > 0;
}
public boolean isFinal() {
return (m_accFlags & ACC_FINAL) > 0;
}
public boolean isSynchronized() {
return (m_accFlags & ACC_SYNCHRONIZED) > 0;
}
public boolean isNative() {
return (m_accFlags & ACC_NATIVE) > 0;
}
public boolean isAbstract() {
return (m_accFlags & ACC_ABSTRACT) > 0;
}
public boolean isStrict() {
return (m_accFlags & ACC_STRICT) > 0;
}
public int getFlags() {
return m_accFlags;
}
/**
* Returns method name from constant pool.
*
* @return name from constant pool.
*/
public String getName() {
return ((CiUtf8) m_class.getConstantPool()[getNameIndex()]).getUtf8();
}
public boolean isConstructor() {
return getName().equals(CTOR_METHOD_NAME);
}
/**
* Returns a formatted name for this method, e.g. classname for constructor
* etc.
*
* @return a formatted name for this method.
*/
public String getFormattedName() {
String methodStr = getName();
if (isConstructor()) {
methodStr = getMEClass().getClassName();
}
return methodStr;
}
/**
* Returns method descriptor from constant pool.
*
* @return method descriptor from constant pool.
*/
public String getDescriptor() {
return ((CiUtf8) m_class.getConstantPool()[getDescriptorIndex()]).getUtf8();
}
/**
* Returns type of return value for this method.
*
* @return type of return value.
* @throws IOException
*/
public Type getReturnClass() throws IOException {
if (m_returnType == null) {
m_returnType = getReturnClass(new StringBuffer(getDescriptor()));
}
return m_returnType;
}
public Type getReturnClass(StringBuffer descriptor) throws IOException {
descriptor.delete(0, descriptor.indexOf(")") + 1);
return DescriptorParser.processTypeDescriptor(m_class, descriptor);
}
/**
* Returns a type array of this method's arguments.
*
* @return a type array of arguments.
* @throws IOException
*/
public Type[] getArguments() throws IOException {
if (m_arguments == null) {
m_arguments = getArguments(new StringBuffer(getDescriptor()));
}
return m_arguments;
}
public Type[] getArguments(StringBuffer sb) throws IOException {
List<Type> res = new ArrayList<Type>();
sb.deleteCharAt(0);
sb.delete(sb.indexOf(")"), sb.length());
Type t = null;
do {
t = DescriptorParser.processTypeDescriptor(m_class, sb);
if (t != null) {
res.add(t);
}
} while (t != null);
return res.toArray(new Type[res.size()]);
}
/**
* Returns an exception array of declared exceptions of this method.
*
* @return an exception array of declared exceptions.
* @throws IOException
*/
public MEClass[] getExceptions() throws IOException {
if (m_exceptions == null) {
AiExceptions excs = null;
for (int i = 0; i < m_attributes.length; i++) {
if (m_attributes[i] instanceof AiExceptions) {
excs = (AiExceptions) m_attributes[i];
break;
}
}
if (excs != null) {
int[] exIndices = excs.getExceptionIndices();
m_exceptions = new MEClass[exIndices.length];
for (int i = 0; i < exIndices.length; i++) {
CiClass classInfo = (CiClass) m_class.getConstantPool()[exIndices[i]];
String classname = ((CiUtf8) m_class.getConstantPool()[classInfo
.getNameIndex()]).getUtf8();
classname = classname.replace('/', '.');
try {
m_exceptions[i] = m_class.getResource().getContext().getMEClass(
classname);
} catch (ClassNotFoundException cnfe) {
m_exceptions[i] = new UnknownClass(classname, m_class.getResource());
}
}
} else {
m_exceptions = new MEClass[0];
}
}
return m_exceptions;
}
public byte[] getByteCodes() {
AiCode code = (AiCode) getAttributeInfo(m_attributes, AiCode.class);
if (code == null) {
return null;
} else {
return code.getCode();
}
}
public AiLineNumberTable getLineNumberTable() {
if (m_lineNumberTable == null) {
AiCode code = (AiCode) getAttributeInfo(m_attributes, AiCode.class);
if (code != null) {
AiLineNumberTable table = (AiLineNumberTable) getAttributeInfo(code
.getAttributes(), AiLineNumberTable.class);
if (table != null) {
m_lineNumberTable = table;
}
}
}
return m_lineNumberTable;
}
/**
* Returns a List of the invokations called from within this method. The list contains
* a formatted representation of the invokation MEMethod.Invokation.
*
* @return a list of the invokations called from within this method.
* @throws CorruptBytecodeException
*/
public List<MEMethod.Invokation> getInvokations() throws CorruptBytecodeException {
final List<MEMethod.Invokation> res = new ArrayList<MEMethod.Invokation>();
final ClassInfo[] cp = m_class.getConstantPool();
traverseBytecodes(new BytecodeVisitor() {
int index = 0;
@Override
public void visitInvokation(int pc, int bytecode, int len, int cpIndex) throws CorruptBytecodeException {
if (cpIndex > cp.length) {
throw new CorruptBytecodeException(pc, len, bytecode);
}
if (cp[cpIndex] instanceof AbstractRefInfo) // safeguard for evil obfuscators
{
AbstractRefInfo methodRef = (AbstractRefInfo) cp[cpIndex];
if (methodRef == null) {
throw new CorruptBytecodeException(pc, len, bytecode);
}
CiClass ciClass = (CiClass) cp[methodRef.getClassIndex()];
String classname = ((CiUtf8) cp[ciClass.getNameIndex()]).getUtf8().replace('/', '.');
CiNameAndType ciNameType = (CiNameAndType) cp[methodRef.getNameAndTypeIndex()];
String methname = ((CiUtf8) cp[ciNameType.getNameIndex()]).getUtf8();
String descriptor = ((CiUtf8) cp[ciNameType.getDescriptorIndex()]).getUtf8();
boolean isVirtual = bytecode == 182; //invokevirtual
boolean isInterface = bytecode == 185; //invokeinterface
MEMethod.Invokation inv = new MEMethod.Invokation(classname, methname, descriptor, pc, index, isVirtual, isInterface, null);
//System.out.println("[MEMethod] invoke:" + classname + "->" +methname +"->" +descriptor);
res.add(inv);
}
index++;
}
@Override
public void visitNewBytecode(int pc, int bytecode) {
}
@Override
public void visit(int pc, int bytecode, int len) {
index++;
}
@Override
public void visitConstantPool(int pc, int bytecode, int len, int cpIndex) {
index++;
}
@Override
public void visitJump(int pc, int bytecode, int len, short relJump) {
index++;
}
@Override
public void visitLocalFieldName(int pc, int bytecode, int len, int cpIndex) {
index++;
}
@Override
public void visitLookupSwitch(int pc, int bytecode, int len, Map<Object, Object> switch1) {
index++;
}
@Override
public void visitTableSwitch(int pc, int bytecode, int len, Map<Object, Object> switch1) {
index++;
}
});
return res;
}
/**
* Returns a list integers of pc-offsets where specified bytecode is defined.
*
* @return a list of pc's where specified bytecode is defined.
* @throws CorruptBytecodeException
*/
public List<Integer> getBytecode(final int bytecodeSearch) throws CorruptBytecodeException {
final List<Integer> res = new ArrayList<Integer>();
traverseBytecodes(new SimpleVisitor() {
@Override
public void visitNewBytecode(int pc, int bytecode) {
if (bytecode == bytecodeSearch) {
res.add(new Integer(pc));
}
}
});
return res;
}
public int countBytecodeBytes() {
return getByteCodes() != null ? getByteCodes().length : 0;
}
public void traverseBytecodes(BytecodeVisitor bv) throws CorruptBytecodeException {
byte[] code = getByteCodes();
int pc = 0;
if (code != null) {
while (pc < code.length) {
int bytecode = (code[pc] & 0xff);
bv.visitNewBytecode(pc, bytecode);
int len = Bytecodes.BC_LENGTHS[bytecode];
if (bytecode == 170) // tableswitch
{
try {
Map<Object, Object> tSwitch = Bytecode.getTableSwitch(code, pc);
len = ((Integer) tSwitch.get(Bytecode.SWITCH_OP_LENGTH)).intValue();
bv.visitTableSwitch(pc, bytecode, len, tSwitch);
} catch (Exception e) {
throw new CorruptBytecodeException();
}
} else if (bytecode == 171) // lookupswitch
{
try {
Map<Object, Object> lSwitch = Bytecode.getLookupSwitch(code, pc);
len = ((Integer) lSwitch.get(Bytecode.SWITCH_OP_LENGTH)).intValue();
bv.visitLookupSwitch(pc, bytecode, len, lSwitch);
} catch (Exception e) {
throw new CorruptBytecodeException();
}
} else if (len > 1) {
// invokation name lookup
if (bytecode == 182 || // invokevirtual
bytecode == 183 || // invokespecial
bytecode == 184 || // invokestatic
bytecode == 185) // invokeinterface
{
int cpIndex = ((code[pc + 1] & 0xff) << 8)
| ((code[pc + 2]) & 0xff);
bv.visitInvokation(pc, bytecode, len, cpIndex);
}
// local field name lookup
else if (bytecode == 178 // getstatic
|| bytecode == 179 // putstatic
/*|| (pc > 0 && code[pc - 1] == 42 // aload_0 (this)
&&*/|| (bytecode == 180 // getfield
|| bytecode == 181)) // putfield
{
int cpIndex = ((code[pc + 1] & 0xff) << 8)
| ((code[pc + 2]) & 0xff);
bv.visitLocalFieldName(pc, bytecode, len, cpIndex);
}
// constant pool lookup
else if (bytecode == 18 || bytecode == 19 || bytecode == 20) // ldc, ldc_w (integer, float or string), ldc2_w
{
int cpIndex =
(bytecode == 19 || bytecode == 20) ? ((code[pc + 1] & 0xff) << 8) | ((code[pc + 2]) & 0xff) : (code[pc + 1] & 0xff);
bv.visitConstantPool(pc, bytecode, len, cpIndex);
}
// label registration
else if (bytecode == 167 || (bytecode >= 153 && bytecode <= 166) || bytecode == 198 || bytecode == 199) // goto, if*
{
short relJump = (short) (((code[pc + 1] & 0xff) << 8) | ((code[pc + 2]) & 0xff));
bv.visitJump(pc, bytecode, len, relJump);
} else
// unparsed data
{
bv.visit(pc, bytecode, len);
}
}
if (len < 0 || pc + len > code.length) {
throw new CorruptBytecodeException(pc, len, bytecode);
}
pc += len;
}
}
}
// Getters
public int getAccessFlags() {
return m_accFlags;
}
public int getNameIndex() {
return m_nameIndex;
}
public int getDescriptorIndex() {
return m_descIndex;
}
public AttributeInfo[] getAttributes() {
return m_attributes;
}
public MEClass getMEClass() {
return m_class;
}
// Setters
public void setAccessFlags(int accFlags) {
m_accFlags = accFlags;
}
public void setAttributes(AttributeInfo[] attributes) {
m_attributes = attributes;
}
public void setDescriptorIndex(int descIndex) {
m_descIndex = descIndex;
}
public void setNameIndex(int nameIndex) {
m_nameIndex = nameIndex;
}
@Override
public int hashCode() {
//return getName().hashCode();
int hashCode = m_class.getName().hashCode();
hashCode = 31 * hashCode + getName().hashCode();
hashCode = 31 * hashCode + getDescriptor().hashCode();
return hashCode;
}
@Override
public boolean equals(Object o) {
if (o != null && o instanceof MEMethod) {
MEMethod m = (MEMethod) o;
boolean match = m.getMEClass().equals(getMEClass())
&& m.getName().equals(getName());
if (match) {
try {
Type[] ma = m.getArguments();
Type[] a = getArguments();
match = ma.length == a.length;
for (int i = 0; match && i < ma.length; i++) {
match = ma[i].equals(a[i]);
}
if (match) {
match = getReturnClass().equals(m.getReturnClass());
}
} catch (IOException e) {
return false;
}
}
return match;
} else {
return false;
}
}
public String getArgumentsStringUml() {
try {
Type[] args = getArguments();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < args.length; i++) {
sb.append(Util.shortenClassName(args[i].toString()));
if (i < args.length - 1) {
sb.append(", ");
}
}
return sb.toString();
} catch (Exception e) {
return "?";
}
}
public String getArgumentsString() {
try {
return getArgumentsString(getArguments());
} catch (Exception e) {
return "?";
}
}
public String getArgumentsString(Type[] args) {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < args.length; i++) {
sb.append(args[i]);
if (i < args.length - 1) {
sb.append(", ");
}
}
return sb.toString();
}
public String getExceptionsString() {
StringBuffer sb = new StringBuffer();
try {
Type[] ex = getExceptions();
for (int i = 0; i < ex.length; i++) {
sb.append(ex[i]);
if (i < ex.length - 1) {
sb.append(", ");
}
}
} catch (Exception e) {
sb.append("?");
}
return sb.toString();
}
public boolean declaresExceptions() {
try {
return getExceptions().length > 0;
} catch (IOException e) {
return false;
}
}
public String getReturnClassString() {
try {
return getReturnClass().toString();
} catch (Exception e) {
return "?";
}
}
@Override
public String toString() {
StringBuffer sb = new StringBuffer();
if (isStatic()) {
sb.append("static ");
}
if (isSynchronized()) {
sb.append("synchronized ");
}
if (isNative()) {
sb.append("native ");
}
if (isStrict()) {
sb.append("strict ");
}
if (isPrivate()) {
sb.append("private ");
} else if (isProtected()) {
sb.append("protected ");
} else if (isPublic()) {
sb.append("public ");
}
if (isFinal()) {
sb.append("final ");
}
if (isAbstract()) {
sb.append("abstract ");
}
if (!isConstructor()) {
sb.append(getReturnClassString());
sb.append(' ');
}
sb.append(getFormattedName());
sb.append('(');
sb.append(getArgumentsString());
sb.append(")");
if (declaresExceptions()) {
sb.append(" throws ");
sb.append(getExceptionsString());
}
return sb.toString();
}
public AttributeInfo getAttributeInfo(AttributeInfo[] infos, Class<?> infoType) {
AttributeInfo res = null;
for (int i = 0; i < infos.length; i++) {
if (infos[i].getClass().equals(infoType)) {
res = infos[i];
break;
}
}
return res;
}
public static class Invokation {
public String invClassname;
public String invMethodname;
public String invDescriptor;
public int bytecodePc;
public int bytecodeIndex;
public boolean isVirtual;
public boolean isInterface;
public Instruction offsetIns;
public MEMethod method;
public MEClass clazz;
public Invokation(String classname, String methodName, String descriptor, int pc, int index,
boolean isVirtual, boolean isInterface, Instruction offsetIns) {
invClassname = classname;
invMethodname = methodName;
invDescriptor = descriptor;
bytecodePc = pc;
bytecodeIndex = index;
this.isVirtual = isVirtual;
this.isInterface = isInterface;
this.offsetIns = offsetIns;
method = null;
clazz = null;
}
public void setMethod(MEMethod method, MEClass clazz) {
this.method = method;
this.clazz = clazz;
}
@Override
public String toString() {
return invClassname + "." + invMethodname + ":" + invDescriptor;
}
@Override
public int hashCode() {
return (invClassname.hashCode() & 0x0000ffff) +
((invMethodname.hashCode() + invDescriptor.hashCode()) & 0xffff0000);
}
@Override
public boolean equals(Object o) {
if (o instanceof MEMethod.Invokation) {
MEMethod.Invokation inv = (MEMethod.Invokation) o;
return inv.invClassname.equals(invClassname) &&
inv.invMethodname.equals(invMethodname) &&
inv.invDescriptor.equals(invDescriptor) &&
inv.bytecodePc == bytecodePc;
}
return false;
}
}
}