/*
* 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 analyser.gui;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.swing.Action;
import mereflect.BytecodeVisitor;
import mereflect.CorruptBytecodeException;
import mereflect.MEClass;
import mereflect.MEField;
import mereflect.MEMethod;
import mereflect.Type;
import mereflect.bytecode.Bytecode;
import mereflect.bytecode.Bytecodes;
import mereflect.info.AbstractRefInfo;
import mereflect.info.AiCode;
import mereflect.info.CiClass;
import mereflect.info.CiDouble;
import mereflect.info.CiFieldRef;
import mereflect.info.CiFloat;
import mereflect.info.CiInteger;
import mereflect.info.CiLong;
import mereflect.info.CiNameAndType;
import mereflect.info.CiString;
import mereflect.info.CiUtf8;
import mereflect.info.ClassInfo;
import mereflect.io.DescriptorParser;
import andreflect.DexMethod;
public class LineBuilderFormatter {
public static final int COLOR_KEYWORD = 0x880088;
public static final int COLOR_TEXT = 0x000000;
public static final int COLOR_STATIC = 0x0000bb;
public static final int COLOR_HEX = 0x008800;
public static final int COLOR_PC = 0x888888;
public static final int COLOR_OPCODE = 0x000000;
public static final int COLOR_LABEL = 0x000088;
public static final int COLOR_COMMENT = 0x888800;
public static final int COLOR_ERROR = 0xff0000;
static Map<Integer, String> labels;
static int gotoLabelIx = 0;
static class FormatVisitor implements BytecodeVisitor {
LineBuilder lb;
MEClass clazz;
MEMethod method;
ClassInfo[] cp;
byte[] code;
String prefix;
ProgressReporter r;
public FormatVisitor(LineBuilder lb, MEMethod method, String prefix, ProgressReporter r) {
this.lb = lb;
clazz = method.getMEClass();
this.method = method;
code = method.getByteCodes();
cp = clazz.getConstantPool();
this.prefix = prefix;
}
@Override
public void visit(int pc, int bytecode, int len) throws CorruptBytecodeException {
lb.append(" ", COLOR_TEXT);
appendHex(code, lb, pc, len);
}
@Override
public void visitConstantPool(int pc, int bytecode, int len, int cpIndex) throws CorruptBytecodeException {
StringBuffer resolved = new StringBuffer();
lb.append(' ');
appendHex(code, lb, pc, len);
ClassInfo ci = cp[cpIndex];
int ciTag = ci.getTag();
switch (ciTag) {
case ClassInfo.CONSTANT_Integer:
resolved.append(((CiInteger) ci).getInteger());
break;
case ClassInfo.CONSTANT_Float:
resolved.append(((CiFloat) ci).getFloat());
break;
case ClassInfo.CONSTANT_Long:
resolved.append(((CiLong) ci).getLong());
break;
case ClassInfo.CONSTANT_Double:
resolved.append(((CiDouble) ci).getDouble());
break;
case ClassInfo.CONSTANT_String:
resolved.append('\"');
resolved.append(((CiUtf8) cp[((CiString) ci).getStringIndex()]).getUtf8());
resolved.append('\"');
break;
}
lb.append("\t// " + resolved, COLOR_COMMENT);
}
@Override
public void visitInvokation(int pc, int bytecode, int len, int cpIndex) throws CorruptBytecodeException {
lb.append(' ');
appendHex(code, lb, pc, len);
AbstractRefInfo methodRef = (AbstractRefInfo) cp[cpIndex];
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();
try {
MEClass oClass = clazz.getResource().getContext().getMEClass(classname);
MEMethod[] cands = oClass.getMethods(methname);
MEMethod m = null;
for (int i = 0; i < cands.length; i++) {
if (cands[i].getDescriptor().equals(descriptor)) {
m = cands[i];
break;
}
}
lb.append("\t// " + classname + "." + m.getName() + "("
+ m.getArgumentsString() + ")" + m.getReturnClassString(), COLOR_COMMENT);
} catch (Exception e) {
String args = "?";
String retClass = "?";
try {
args = method.getArgumentsString(method.getArguments(new StringBuffer(descriptor)));
} catch (Exception e2) {
}
try {
retClass = method.getReturnClass(new StringBuffer(descriptor)).toString();
} catch (Exception e2) {
}
lb.append("\t// " + classname + "." + methname + "(" + args + ")"
+ retClass, COLOR_COMMENT);
}
}
@Override
public void visitJump(int pc, int bytecode, int len, short relJump) throws CorruptBytecodeException {
int pcDest = pc + relJump;
String label = getLabel(pcDest);
lb.append(" " + label, COLOR_LABEL);
lb.append(" (", COLOR_HEX);
appendHex(code, lb, pc, len);
lb.append(")", COLOR_HEX);
}
@Override
public void visitLocalFieldName(int pc, int bytecode, int len, int cpIndex) throws CorruptBytecodeException {
lb.append(" ", COLOR_TEXT);
appendHex(code, lb, pc, len);
CiFieldRef ciFieldRef = (CiFieldRef) cp[cpIndex];
CiNameAndType ciNameType = (CiNameAndType) cp[ciFieldRef.getNameAndTypeIndex()];
String name = ((CiUtf8) cp[ciNameType.getNameIndex()]).getUtf8();
String descr = ((CiUtf8) cp[ciNameType.getDescriptorIndex()]).getUtf8();
Type type = null;
try {
type = DescriptorParser.processTypeDescriptor(clazz, new StringBuffer(descr));
} catch (IOException ignore) {
}
lb.append("\t// ", COLOR_COMMENT);
if (type != null) {
lb.append(type.toString(), COLOR_COMMENT);
}
lb.append(" " + name, COLOR_COMMENT);
}
@Override
public void visitLookupSwitch(int pc, int bytecode, int len, Map<Object, Object> zwitch) throws CorruptBytecodeException {
int pairs = ((Integer) zwitch.get(Bytecode.SWITCH_PAIRS)).intValue();
int def = ((Integer) zwitch.get(Bytecode.SWITCH_DEFAULT)).intValue();
for (int i = 0; i < pairs; i++) {
Bytecode.Pair pair = (Bytecode.Pair) zwitch.get(new Integer(i));
int caze = pair.vCase;
int jmp = pair.vJump;
lb.newLine();
lb.append(prefix, COLOR_TEXT);
lb.append(" case ", COLOR_KEYWORD);
lb.append("0x" + Integer.toHexString(caze), COLOR_HEX);
lb.append(" : ", COLOR_TEXT);
String label = getLabel(jmp + pc);
lb.append(label, COLOR_LABEL);
lb.append(" (0x" + Integer.toHexString(jmp + pc) + ")", COLOR_HEX);
}
lb.newLine();
lb.append(prefix, COLOR_TEXT);
lb.append(" default", COLOR_KEYWORD);
lb.append(" : ", COLOR_TEXT);
String label = getLabel(def + pc);
lb.append(label, COLOR_LABEL);
lb.append(" (0x" + Integer.toHexString(def + pc) + ")", COLOR_HEX);
}
@Override
public void visitTableSwitch(int pc, int bytecode, int len, Map<Object, Object> zwitch) throws CorruptBytecodeException {
int def = ((Integer) zwitch.get(Bytecode.SWITCH_DEFAULT)).intValue();
int low = ((Integer) zwitch.get(Bytecode.SWITCH_LOW)).intValue();
int high = ((Integer) zwitch.get(Bytecode.SWITCH_HIGH)).intValue();
for (int i = low; i <= high; i++) {
int jmp = ((Integer) zwitch.get(new Integer(i))).intValue();
lb.newLine();
lb.append(prefix, COLOR_TEXT);
lb.append(" case ", COLOR_KEYWORD);
lb.append("0x" + Integer.toHexString(i), COLOR_HEX);
lb.append(" : ", COLOR_TEXT);
String label = getLabel(jmp + pc);
lb.append(label, COLOR_LABEL);
lb.append(" (0x" + Integer.toHexString(jmp + pc) + ")", COLOR_HEX);
}
lb.newLine();
lb.append(prefix, COLOR_TEXT);
lb.append(" default", COLOR_KEYWORD);
lb.append(" : ", COLOR_TEXT);
String label = getLabel(def + pc);
lb.append(label, COLOR_LABEL);
lb.append(" (0x" + Integer.toHexString(def + pc) + ")", COLOR_HEX);
}
@Override
public void visitNewBytecode(int pc, int bytecode) throws CorruptBytecodeException {
lb.newLine();
if (bytecode >= 172 && bytecode <= 177)
{
// ?return
lb.setReferenceToCurrent(new Return(pc));
}
else
{
lb.setReferenceToCurrent(new BytecodeOffset(pc));
}
lb.append(prefix, COLOR_TEXT);
String pcStr = Integer.toHexString(pc);
lb.append(pcStr, COLOR_PC);
for (int i = 6 - pcStr.length(); i >= 0; i--) {
lb.append(' ');
}
String op = Bytecodes.BC_OPCODES[bytecode];
lb.append(op, COLOR_OPCODE);
if (r != null) {
r.reportWork(pc);
}
}
}
static String getLabel(int pcDest) {
String label = labels.get(new Integer(pcDest));
if (label == null) {
label = "LABEL_" + (gotoLabelIx++);
labels.put(new Integer(pcDest), label);
}
return label;
}
public static LineBuilder getByteCodeAssembler(MEMethod method, String prefix)
throws CorruptBytecodeException {
return getByteCodeAssembler(method, prefix, null);
}
public static LineBuilder getByteCodeAssembler(MEMethod method, String prefix, ProgressReporter r)
throws CorruptBytecodeException {
LineBuilder lb = new LineBuilder();
lb.newLine();
labels = new HashMap<Integer, String>();
gotoLabelIx = 0;
if (r != null) {
r.reportStart(method.countBytecodeBytes());
}
method.traverseBytecodes(new FormatVisitor(lb, method, prefix, r));
if (r != null) {
r.reportEnd();
}
MEClass clazz = method.getMEClass();
// insert try/catch/finally blocks
AiCode aiCode = (AiCode) method.getAttributeInfo(method.getAttributes(), AiCode.class);
if (aiCode != null) {
AiCode.ExceptionSpec[] exceptionSpecs = aiCode.getExceptions();
for (int i = 0; exceptionSpecs != null && i < exceptionSpecs.length; i++) {
AiCode.ExceptionSpec exSpec = exceptionSpecs[i];
int lineNbrStart = lb.getLine(new BytecodeOffset(exSpec.getStartPc()));
int catchType = exSpec.getCatchType();
lb.insertLineBefore(lineNbrStart);
lb.append("TRY_" + i + ":", COLOR_LABEL);
lb.setReference(lineNbrStart, new TryStart());
int lineNbrHandler = lb.getLine(new BytecodeOffset(exSpec.getHandlerPc()));
if (catchType == 0) {
// finally
lb.insertLineBefore(lineNbrHandler);
lb.setReference(lineNbrHandler, new Finally(exSpec.getHandlerPc()));
lb.append("FINALLY_" + i + ":", COLOR_LABEL);
} else {
ClassInfo[] cp = clazz.getConstantPool();
CiClass ciClass = (CiClass) cp[catchType];
String classname = ((CiUtf8) cp[ciClass.getNameIndex()]).getUtf8()
.replace('/', '.');
lb.insertLineBefore(lineNbrHandler);
lb.setReference(lineNbrHandler, new Catch(exSpec.getHandlerPc()));
lb.append("CATCH_" + i + "(" + classname + "):", COLOR_LABEL);
}
int lineNbrEnd = lb.getLine(new BytecodeOffset(exSpec.getEndPc()));
lb.insertLineBefore(lineNbrEnd);
lb.setReference(lineNbrEnd, new TryEnd());
lb.append(":TRY_" + i, COLOR_LABEL);
}
}
// insert labels
Iterator<Integer> pcIter = labels.keySet().iterator();
while (pcIter.hasNext()) {
Integer pcKey = pcIter.next();
String label = labels.get(pcKey);
int lineNbr = lb.getLine(new BytecodeOffset(pcKey.intValue()));
if (lineNbr >= 0) {
lb.insertLineBefore(lineNbr);
lb.append(label + ":", COLOR_LABEL);
lb.setReference(lineNbr, new Label(pcKey.intValue(), label));
}
}
return lb;
}
protected static void appendHex(byte[] code, LineBuilder lb, int pc, int len) {
lb.append("0x", COLOR_HEX);
for (int i = 1; i < len; i++) {
int data = (code[pc + i] & 0xff);
String hex = Integer.toHexString(data);
if (data < 0x10) {
lb.append('0', COLOR_HEX);
}
lb.append(hex, COLOR_HEX);
}
}
protected static void makeOutlineArguments(MEMethod method, LineBuilder lb) {
try {
Type[] args = method.getArguments();
for (int i = 0; i < args.length; i++) {
lb.append(args[i], args[i].isPrimitive() ? COLOR_KEYWORD : COLOR_TEXT);
if (method instanceof DexMethod) {
lb.append(" ", COLOR_TEXT);
lb.append(((DexMethod) method).getParameterName(i, args.length), COLOR_HEX);
}
if (i < args.length - 1) {
lb.append(", ", COLOR_TEXT);
}
}
} catch (Exception e) {
lb.append("?", COLOR_ERROR);
}
}
public static void makeOutline(MEMethod method, LineBuilder lb) {
if (method.isStatic()) {
lb.append("static ", COLOR_KEYWORD);
}
if (method.isSynchronized()) {
lb.append("synchronized ", COLOR_KEYWORD);
}
if (method.isNative()) {
lb.append("native ", COLOR_KEYWORD);
}
if (method.isStrict()) {
lb.append("strict ", COLOR_KEYWORD);
}
if (method.isPrivate()) {
lb.append("private ", COLOR_KEYWORD);
} else if (method.isProtected()) {
lb.append("protected ", COLOR_KEYWORD);
} else if (method.isPublic()) {
lb.append("public ", COLOR_KEYWORD);
}
if (method.isFinal()) {
lb.append("final ", COLOR_KEYWORD);
}
if (method.isAbstract()) {
lb.append("abstract ", COLOR_KEYWORD);
}
if (!method.isConstructor()) {
try {
lb.append(method.getReturnClassString(), method.getReturnClass()
.isPrimitive() ? COLOR_KEYWORD : COLOR_TEXT);
} catch (IOException ioe) {
lb.append('?', COLOR_ERROR);
}
lb.append(' ', COLOR_TEXT);
}
lb.append(method.getFormattedName(), COLOR_TEXT);
lb.append('(', COLOR_TEXT);
makeOutlineArguments(method, lb);
lb.append(')', COLOR_TEXT);
if (method.declaresExceptions()) {
lb.append(" throws ", COLOR_KEYWORD);
lb.append(method.getExceptionsString(), COLOR_TEXT);
}
}
public static void makeOutline(MEField field, LineBuilder lb) {
if (field.isPrivate()) {
lb.append("private ", COLOR_KEYWORD);
} else if (field.isProtected()) {
lb.append("protected ", COLOR_KEYWORD);
} else if (field.isPublic()) {
lb.append("public ", COLOR_KEYWORD);
}
if (field.isStatic()) {
lb.append("static ", COLOR_KEYWORD);
}
if (field.isFinal()) {
lb.append("final ", COLOR_KEYWORD);
}
if (field.isVolatile()) {
lb.append("volatile ", COLOR_KEYWORD);
}
if (field.isTransient()) {
lb.append("transient ", COLOR_KEYWORD);
}
lb.append(field.getType(), field.getType().isPrimitive() ? COLOR_KEYWORD
: COLOR_TEXT);
lb.append(' ');
lb.append(field.getName(), field.isStatic() ? COLOR_STATIC : COLOR_TEXT);
String cval = field.getConstantValueString();
if (cval != null) {
lb.append(" = " + cval, COLOR_TEXT);
}
lb.append(';', COLOR_TEXT);
}
public static LineBuilder makeOutline(MEClass clazz) {
LineBuilder lb = new LineBuilder();
lb.newLine();
String pakkage = clazz.getResource().getPackage();
if (pakkage != null && pakkage.length() > 0) {
lb.append("package ", COLOR_KEYWORD);
lb.append(pakkage + ";", COLOR_TEXT);
lb.newLine();
lb.newLine();
}
if (clazz.isInterface()) {
lb.append("interface", COLOR_KEYWORD);
} else {
lb.append("class", COLOR_KEYWORD);
}
lb.append(' ');
lb.append(clazz.getClassName(), COLOR_TEXT);
if (!clazz.isInterface()) {
MEClass superClazz = clazz.getSuperClass();
if (superClazz != null && !superClazz.getName().equals("java.lang.Object")) {
lb.append(" extends ", COLOR_KEYWORD);
lb.append(superClazz.getName(), COLOR_TEXT);
}
}
Type[] ifcs = clazz.getInterfaces();
if (ifcs.length > 0) {
lb.newLine();
for (int i = 0; i < ifcs.length; i++) {
if (i == 0) {
if (!clazz.isInterface()) {
lb.append(" implements ", COLOR_KEYWORD);
} else {
lb.append(" extends ", COLOR_KEYWORD);
}
} else {
lb.append(" ", COLOR_TEXT);
}
lb.append(ifcs[i].getName(), COLOR_TEXT);
if (i < ifcs.length - 1) {
lb.append(',', COLOR_TEXT);
}
lb.newLine();
}
} else {
lb.newLine();
}
lb.append("{", COLOR_TEXT);
lb.newLine();
MEField[] fields = clazz.getFields();
for (int i = 0; i < fields.length; i++) {
lb.append(" ");
makeOutline(fields[i], lb);
lb.setReferenceToCurrent(fields[i]);
lb.newLine();
}
if (fields.length > 0) {
lb.newLine();
}
MEMethod[] meths = clazz.getMethods();
for (int i = 0; i < meths.length; i++) {
lb.append(" ", COLOR_TEXT);
makeOutline(meths[i], lb);
lb.append(';', COLOR_TEXT);
lb.setReferenceToCurrent(meths[i]);
lb.newLine();
}
lb.append("}", COLOR_TEXT);
return lb;
}
public abstract static class Identifier {
}
public static class BytecodeOffset extends Identifier {
public final int pc;
public BytecodeOffset(int pc) {
this.pc = pc;
}
@Override
public boolean equals(Object o) {
return o instanceof BytecodeOffset ? ((BytecodeOffset) o).pc == pc : false;
}
@Override
public int hashCode() {
return pc & 0x3f;
}
}
public static class Return extends BytecodeOffset {
public Return(int pc) {
super(pc);
}
}
public static class Label extends Identifier {
public final int pc;
public final String label;
public Label(int pc, String label) {
this.pc = pc;
this.label = label;
}
}
public static class Link extends Identifier {
Object[] data;
Action link;
public Link(Action linkedAction, Object[] data) {
link = linkedAction;
this.data = data;
}
public Object[] getData() {
return data;
}
public Action getLinkedAction() {
return link;
}
}
public static class QuickLink extends Link {
public QuickLink(Action linkedAction, Object[] data) {
super(linkedAction, data);
}
}
public static class Try extends Identifier {
}
public static class TryStart extends Try {
}
public static class TryEnd extends Try {
}
public static class Catch extends Identifier {
public final int pc;
public Catch(int pc) {
this.pc = pc;
}
}
public static class Finally extends Catch {
public Finally(int pc) {
super(pc);
}
}
}