Java tutorial
/* * dex2jar - Tools to work with android .dex and java .class files * Copyright (c) 2009-2013 Panxiaobo * * 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 com.googlecode.d2j.converter; import java.lang.reflect.Array; import java.util.HashMap; import java.util.Map; import com.googlecode.d2j.asm.LdcOptimizeAdapter; import com.googlecode.dex2jar.ir.stmt.*; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import com.googlecode.dex2jar.ir.IrMethod; import com.googlecode.dex2jar.ir.Trap; import com.googlecode.dex2jar.ir.expr.ArrayExpr; import com.googlecode.dex2jar.ir.expr.CastExpr; import com.googlecode.dex2jar.ir.expr.Constant; import com.googlecode.dex2jar.ir.expr.Exprs; import com.googlecode.dex2jar.ir.expr.FieldExpr; import com.googlecode.dex2jar.ir.expr.FilledArrayExpr; import com.googlecode.dex2jar.ir.expr.InvokeExpr; import com.googlecode.dex2jar.ir.expr.Local; import com.googlecode.dex2jar.ir.expr.NewExpr; import com.googlecode.dex2jar.ir.expr.NewMutiArrayExpr; import com.googlecode.dex2jar.ir.expr.StaticFieldExpr; import com.googlecode.dex2jar.ir.expr.TypeExpr; import com.googlecode.dex2jar.ir.expr.Value; import com.googlecode.dex2jar.ir.expr.Value.E1Expr; import com.googlecode.dex2jar.ir.expr.Value.E2Expr; import com.googlecode.dex2jar.ir.expr.Value.EnExpr; import com.googlecode.dex2jar.ir.expr.Value.VT; import com.googlecode.dex2jar.ir.stmt.Stmt.E2Stmt; import com.googlecode.dex2jar.ir.stmt.Stmt.ST; import com.googlecode.dex2jar.ir.ts.Cfg; import com.googlecode.dex2jar.ir.ts.Cfg.TravelCallBack; @SuppressWarnings("incomplete-switch") public class IR2JConverter implements Opcodes { private boolean optimizeSynchronized = false; public IR2JConverter() { super(); } public IR2JConverter(boolean optimizeSynchronized) { super(); this.optimizeSynchronized = optimizeSynchronized; } public void convert(IrMethod ir, MethodVisitor asm) { mapLabelStmt(ir); reBuildInstructions(ir, asm); reBuildTryCatchBlocks(ir, asm); } private void mapLabelStmt(IrMethod ir) { for (Stmt p : ir.stmts) { if (p.st == ST.LABEL) { LabelStmt labelStmt = (LabelStmt) p; labelStmt.tag = new Label(); } } } /** * an empty try-catch block will cause other crash, we check this by finding non-label stmts between * {@link Trap#start} and {@link Trap#end}. if find we add the try-catch or we drop the try-catch. * * @param ir * @param asm */ private void reBuildTryCatchBlocks(IrMethod ir, MethodVisitor asm) { for (Trap trap : ir.traps) { boolean needAdd = false; for (Stmt p = trap.start.getNext(); p != null && p != trap.end; p = p.getNext()) { if (p.st != ST.LABEL) { needAdd = true; break; } } if (needAdd) { for (int i = 0; i < trap.handlers.length; i++) { String type = trap.types[i]; asm.visitTryCatchBlock((Label) trap.start.tag, (Label) trap.end.tag, (Label) trap.handlers[i].tag, type == null ? null : toInternal(type)); } } } } static String toInternal(String n) { // TODO replace return Type.getType(n).getInternalName(); } private void reBuildInstructions(IrMethod ir, MethodVisitor asm) { asm = new LdcOptimizeAdapter(asm); int maxLocalIndex = 0; for (Local local : ir.locals) { maxLocalIndex = Math.max(maxLocalIndex, local._ls_index); } Map<String, Integer> lockMap = new HashMap<String, Integer>(); for (Stmt st : ir.stmts) { switch (st.st) { case LABEL: LabelStmt labelStmt = (LabelStmt) st; Label label = (Label) labelStmt.tag; asm.visitLabel(label); if (labelStmt.lineNumber >= 0) { asm.visitLineNumber(labelStmt.lineNumber, label); } break; case ASSIGN: { E2Stmt e2 = (E2Stmt) st; Value v1 = e2.op1; Value v2 = e2.op2; switch (v1.vt) { case LOCAL: Local local = ((Local) v1); int i = local._ls_index; if (v2.vt == VT.LOCAL && (i == ((Local) v2)._ls_index)) {// continue; } boolean skipOrg = false; if (v1.valueType.charAt(0) == 'I') {// check for IINC if (v2.vt == VT.ADD) { E2Expr e = (E2Expr) v2; if ((e.op1 == local && e.op2.vt == VT.CONSTANT) || (e.op2 == local && e.op1.vt == VT.CONSTANT)) { int increment = (Integer) ((Constant) (e.op1 == local ? e.op2 : e.op1)).value; if (increment >= Short.MIN_VALUE && increment <= Short.MAX_VALUE) { asm.visitIincInsn(i, increment); skipOrg = true; } } } else if (v2.vt == VT.SUB) { E2Expr e = (E2Expr) v2; if (e.op1 == local && e.op2.vt == VT.CONSTANT) { int increment = -(Integer) ((Constant) e.op2).value; if (increment >= Short.MIN_VALUE && increment <= Short.MAX_VALUE) { asm.visitIincInsn(i, increment); skipOrg = true; } } } } if (!skipOrg) { accept(v2, asm); if (i >= 0) { asm.visitVarInsn(getOpcode(v1, ISTORE), i); } else if (!v1.valueType.equals("V")) { // skip void type locals switch (v1.valueType.charAt(0)) { case 'J': case 'D': asm.visitInsn(POP2); break; default: asm.visitInsn(POP); break; } } } break; case STATIC_FIELD: { StaticFieldExpr fe = (StaticFieldExpr) v1; accept(v2, asm); insertI2x(v2.valueType, fe.type, asm); asm.visitFieldInsn(PUTSTATIC, toInternal(fe.owner), fe.name, fe.type); break; } case FIELD: { FieldExpr fe = (FieldExpr) v1; accept(fe.op, asm); accept(v2, asm); insertI2x(v2.valueType, fe.type, asm); asm.visitFieldInsn(PUTFIELD, toInternal(fe.owner), fe.name, fe.type); break; } case ARRAY: ArrayExpr ae = (ArrayExpr) v1; accept(ae.op1, asm); accept(ae.op2, asm); accept(v2, asm); String tp1 = ae.op1.valueType; String tp2 = ae.valueType; if (tp1.charAt(0) == '[') { String arrayElementType = tp1.substring(1); insertI2x(v2.valueType, arrayElementType, asm); asm.visitInsn(getOpcode(arrayElementType, IASTORE)); } else { asm.visitInsn(getOpcode(tp2, IASTORE)); } break; } } break; case IDENTITY: { E2Stmt e2 = (E2Stmt) st; if (e2.op2.vt == VT.EXCEPTION_REF) { int index = ((Local) e2.op1)._ls_index; if (index >= 0) { asm.visitVarInsn(ASTORE, index); } else { asm.visitInsn(POP); } } } break; case FILL_ARRAY_DATA: { E2Stmt e2 = (E2Stmt) st; Object arrayData = ((Constant) e2.getOp2()).value; int arraySize = Array.getLength(arrayData); String arrayValueType = e2.getOp1().valueType; String elementType; if (arrayValueType.charAt(0) == '[') { elementType = arrayValueType.substring(1); } else { elementType = "I"; } int iastoreOP = getOpcode(elementType, IASTORE); accept(e2.getOp1(), asm); for (int i = 0; i < arraySize; i++) { asm.visitInsn(DUP); asm.visitLdcInsn(i); asm.visitLdcInsn(Array.get(arrayData, i)); asm.visitInsn(iastoreOP); } asm.visitInsn(POP); } break; case GOTO: asm.visitJumpInsn(GOTO, (Label) ((GotoStmt) st).target.tag); break; case IF: reBuildJumpInstructions((IfStmt) st, asm); break; case LOCK: { Value v = ((UnopStmt) st).op; accept(v, asm); if (optimizeSynchronized) { switch (v.vt) { case LOCAL: // FIXME do we have to disable local due to OptSyncTest ? // break; case CONSTANT: { String key; if (v.vt == VT.LOCAL) { key = "L" + ((Local) v)._ls_index; } else { key = "C" + ((Constant) v).value; } Integer integer = lockMap.get(key); int nIndex = integer != null ? integer : ++maxLocalIndex; asm.visitInsn(DUP); asm.visitVarInsn(getOpcode(v, ISTORE), nIndex); lockMap.put(key, nIndex); } break; default: throw new RuntimeException(); } } asm.visitInsn(MONITORENTER); } break; case UNLOCK: { Value v = ((UnopStmt) st).op; if (optimizeSynchronized) { switch (v.vt) { case LOCAL: case CONSTANT: { String key; if (v.vt == VT.LOCAL) { key = "L" + ((Local) v)._ls_index; } else { key = "C" + ((Constant) v).value; } Integer integer = lockMap.get(key); if (integer != null) { asm.visitVarInsn(getOpcode(v, ILOAD), integer); } else { accept(v, asm); } } break; // TODO other default: { accept(v, asm); break; } } } else { accept(v, asm); } asm.visitInsn(MONITOREXIT); } break; case NOP: break; case RETURN: { Value v = ((UnopStmt) st).op; accept(v, asm); insertI2x(v.valueType, ir.ret, asm); asm.visitInsn(getOpcode(v, IRETURN)); } break; case RETURN_VOID: asm.visitInsn(RETURN); break; case LOOKUP_SWITCH: { LookupSwitchStmt lss = (LookupSwitchStmt) st; accept(lss.op, asm); Label targets[] = new Label[lss.targets.length]; for (int i = 0; i < targets.length; i++) { targets[i] = (Label) lss.targets[i].tag; } asm.visitLookupSwitchInsn((Label) lss.defaultTarget.tag, lss.lookupValues, targets); } break; case TABLE_SWITCH: { TableSwitchStmt tss = (TableSwitchStmt) st; accept(tss.op, asm); Label targets[] = new Label[tss.targets.length]; for (int i = 0; i < targets.length; i++) { targets[i] = (Label) tss.targets[i].tag; } asm.visitTableSwitchInsn(tss.lowIndex, tss.lowIndex + targets.length - 1, (Label) tss.defaultTarget.tag, targets); } break; case THROW: accept(((UnopStmt) st).op, asm); asm.visitInsn(ATHROW); break; case VOID_INVOKE: InvokeExpr invokeExpr = (InvokeExpr) st.getOp(); accept(invokeExpr, asm); String ret = invokeExpr.ret; if (invokeExpr.vt == VT.INVOKE_NEW) { asm.visitInsn(POP); } else if (!"V".equals(ret)) { switch (ret.charAt(0)) { case 'J': case 'D': asm.visitInsn(POP2); break; default: asm.visitInsn(POP); break; } } break; default: throw new RuntimeException("not support st: " + st.st); } } } private int[] buildLocalUsage(IrMethod ir) { int maxLocal = 0; for (Local local : ir.locals) { if (local._ls_index > maxLocal) { maxLocal = local._ls_index; } } final int[] localUsages = new int[maxLocal + 1]; // maxIndex+1 to get the size Cfg.travel(ir.stmts, new TravelCallBack() { @Override public Value onAssign(Local v, AssignStmt as) { return v; } @Override public Value onUse(Local v) { localUsages[v._ls_index]++; return v; } }, true); return localUsages; } /** * insert I2x instruction * * @param tos * @param expect * @param mv */ private static void insertI2x(String tos, String expect, MethodVisitor mv) { switch (expect.charAt(0)) { case 'B': switch (tos.charAt(0)) { case 'S': case 'C': case 'I': mv.visitInsn(I2B); } break; case 'S': switch (tos.charAt(0)) { case 'C': case 'I': mv.visitInsn(I2S); } break; case 'C': switch (tos.charAt(0)) { case 'I': mv.visitInsn(I2C); } break; } } static boolean isZeroOrNull(Value v1) { if (v1.vt == VT.CONSTANT) { Object v = ((Constant) v1).value; return Integer.valueOf(0).equals(v) || Constant.Null.equals(v); } return false; } private void reBuildJumpInstructions(IfStmt st, MethodVisitor asm) { Label target = (Label) st.target.tag; Value v = st.op; Value v1 = v.getOp1(); Value v2 = v.getOp2(); String type = v1.valueType; switch (type.charAt(0)) { case '[': case 'L': // IF_ACMPx // IF[non]null if (isZeroOrNull(v1) || isZeroOrNull(v2)) { // IF[non]null if (isZeroOrNull(v2)) {// v2 is null accept(v1, asm); } else { accept(v2, asm); } asm.visitJumpInsn(v.vt == VT.EQ ? IFNULL : IFNONNULL, target); } else { accept(v1, asm); accept(v2, asm); asm.visitJumpInsn(v.vt == VT.EQ ? IF_ACMPEQ : IF_ACMPNE, target); } break; default: // IFx // IF_ICMPx if (isZeroOrNull(v1) || isZeroOrNull(v2)) { // IFx if (isZeroOrNull(v2)) {// v2 is zero accept(v1, asm); } else { accept(v2, asm); } switch (v.vt) { case NE: asm.visitJumpInsn(IFNE, target); break; case EQ: asm.visitJumpInsn(IFEQ, target); break; case GE: asm.visitJumpInsn(IFGE, target); break; case GT: asm.visitJumpInsn(IFGT, target); break; case LE: asm.visitJumpInsn(IFLE, target); break; case LT: asm.visitJumpInsn(IFLT, target); break; } } else { // IF_ICMPx accept(v1, asm); accept(v2, asm); switch (v.vt) { case NE: asm.visitJumpInsn(IF_ICMPNE, target); break; case EQ: asm.visitJumpInsn(IF_ICMPEQ, target); break; case GE: asm.visitJumpInsn(IF_ICMPGE, target); break; case GT: asm.visitJumpInsn(IF_ICMPGT, target); break; case LE: asm.visitJumpInsn(IF_ICMPLE, target); break; case LT: asm.visitJumpInsn(IF_ICMPLT, target); break; } } break; } } /** * * @param v * @param op * DUP * @return */ static int getOpcode(Value v, int op) { return getOpcode(v.valueType, op); } static int getOpcode(String v, int op) { return Type.getType(v).getOpcode(op); } private static void accept(Value value, MethodVisitor asm) { switch (value.et) { case E0: switch (value.vt) { case LOCAL: asm.visitVarInsn(getOpcode(value, ILOAD), ((Local) value)._ls_index); break; case CONSTANT: Constant cst = (Constant) value; if (cst.value.equals(Constant.Null)) { asm.visitInsn(ACONST_NULL); } else if (cst.value instanceof Constant.Type) { asm.visitLdcInsn(Type.getType(((Constant.Type) cst.value).desc)); } else { asm.visitLdcInsn(cst.value); } break; case NEW: asm.visitTypeInsn(NEW, toInternal(((NewExpr) value).type)); break; case STATIC_FIELD: StaticFieldExpr sfe = (StaticFieldExpr) value; asm.visitFieldInsn(GETSTATIC, toInternal(sfe.owner), sfe.name, sfe.type); break; } break; case E1: reBuildE1Expression((E1Expr) value, asm); break; case E2: reBuildE2Expression((E2Expr) value, asm); break; case En: reBuildEnExpression((EnExpr) value, asm); break; } } private static void reBuildEnExpression(EnExpr value, MethodVisitor asm) { if (value.vt == VT.FILLED_ARRAY) { FilledArrayExpr fae = (FilledArrayExpr) value; reBuildE1Expression(Exprs.nNewArray(fae.type, Exprs.nInt(fae.ops.length)), asm); String tp1 = fae.valueType; int xastore = IASTORE; String elementType = null; if (tp1.charAt(0) == '[') { elementType = tp1.substring(1); xastore = getOpcode(elementType, IASTORE); } for (int i = 0; i < fae.ops.length; i++) { if (fae.ops[i] == null) continue; asm.visitInsn(DUP); asm.visitLdcInsn(i); accept(fae.ops[i], asm); String tp2 = fae.ops[i].valueType; if (elementType != null) { insertI2x(tp2, elementType, asm); } asm.visitInsn(xastore); } return; } switch (value.vt) { case NEW_MUTI_ARRAY: for (Value vb : value.ops) { accept(vb, asm); } NewMutiArrayExpr nmae = (NewMutiArrayExpr) value; StringBuilder sb = new StringBuilder(); for (int i = 0; i < nmae.dimension; i++) { sb.append('['); } sb.append(nmae.baseType); asm.visitMultiANewArrayInsn(sb.toString(), nmae.dimension); break; case INVOKE_NEW: asm.visitTypeInsn(NEW, toInternal(((InvokeExpr) value).owner)); asm.visitInsn(DUP); // pass through case INVOKE_INTERFACE: case INVOKE_SPECIAL: case INVOKE_STATIC: case INVOKE_VIRTUAL: InvokeExpr ie = (InvokeExpr) value; int i = 0; if (value.vt != VT.INVOKE_STATIC && value.vt != VT.INVOKE_NEW) { i = 1; accept(value.ops[0], asm); } for (int j = 0; i < value.ops.length; i++, j++) { Value vb = value.ops[i]; accept(vb, asm); insertI2x(vb.valueType, ie.args[j], asm); } int opcode; switch (value.vt) { case INVOKE_VIRTUAL: opcode = INVOKEVIRTUAL; break; case INVOKE_INTERFACE: opcode = INVOKEINTERFACE; break; case INVOKE_NEW: case INVOKE_SPECIAL: opcode = INVOKESPECIAL; break; case INVOKE_STATIC: opcode = INVOKESTATIC; break; default: opcode = -1; } asm.visitMethodInsn(opcode, toInternal(ie.owner), ie.name, buildMethodDesc(ie.vt == VT.INVOKE_NEW ? "V" : ie.ret, ie.args)); break; } } static String buildMethodDesc(String ret, String... ps) { StringBuilder sb = new StringBuilder(); sb.append('('); for (String p : ps) { sb.append(p); } sb.append(')'); sb.append(ret); return sb.toString(); } private static void reBuildE1Expression(E1Expr e1, MethodVisitor asm) { accept(e1.getOp(), asm); switch (e1.vt) { case STATIC_FIELD: { FieldExpr fe = (FieldExpr) e1; asm.visitFieldInsn(GETSTATIC, toInternal(fe.owner), fe.name, fe.type); break; } case FIELD: { FieldExpr fe = (FieldExpr) e1; asm.visitFieldInsn(GETFIELD, toInternal(fe.owner), fe.name, fe.type); break; } case NEW_ARRAY: { TypeExpr te = (TypeExpr) e1; switch (te.type.charAt(0)) { case '[': case 'L': asm.visitTypeInsn(ANEWARRAY, toInternal(te.type)); break; case 'Z': asm.visitIntInsn(NEWARRAY, T_BOOLEAN); break; case 'B': asm.visitIntInsn(NEWARRAY, T_BYTE); break; case 'S': asm.visitIntInsn(NEWARRAY, T_SHORT); break; case 'C': asm.visitIntInsn(NEWARRAY, T_CHAR); break; case 'I': asm.visitIntInsn(NEWARRAY, T_INT); break; case 'F': asm.visitIntInsn(NEWARRAY, T_FLOAT); break; case 'J': asm.visitIntInsn(NEWARRAY, T_LONG); break; case 'D': asm.visitIntInsn(NEWARRAY, T_DOUBLE); break; } } break; case CHECK_CAST: case INSTANCE_OF: { TypeExpr te = (TypeExpr) e1; asm.visitTypeInsn(e1.vt == VT.CHECK_CAST ? CHECKCAST : INSTANCEOF, toInternal(te.type)); } break; case CAST: { CastExpr te = (CastExpr) e1; cast2(e1.op.valueType, te.to, asm); } break; case LENGTH: asm.visitInsn(ARRAYLENGTH); break; case NEG: asm.visitInsn(getOpcode(e1, INEG)); break; } } private static void reBuildE2Expression(E2Expr e2, MethodVisitor asm) { String type = e2.op2.valueType; accept(e2.op1, asm); if ((e2.vt == VT.ADD || e2.vt == VT.SUB) && e2.op2.vt == VT.CONSTANT) { // [x + (-1)] to [x - 1] // [x - (-1)] to [x + 1] Constant constant = (Constant) e2.op2; String t = constant.valueType; switch (t.charAt(0)) { case 'S': case 'B': case 'I': { int s = (Integer) constant.value; if (s < 0) { asm.visitLdcInsn(-s); asm.visitInsn(getOpcode(type, e2.vt == VT.ADD ? ISUB : IADD)); return; } } break; case 'F': { float s = (Float) constant.value; if (s < 0) { asm.visitLdcInsn(-s); asm.visitInsn(getOpcode(type, e2.vt == VT.ADD ? ISUB : IADD)); return; } } break; case 'J': { long s = (Long) constant.value; if (s < 0) { asm.visitLdcInsn(-s); asm.visitInsn(getOpcode(type, e2.vt == VT.ADD ? ISUB : IADD)); return; } } break; case 'D': { double s = (Double) constant.value; if (s < 0) { asm.visitLdcInsn(-s); asm.visitInsn(getOpcode(type, e2.vt == VT.ADD ? ISUB : IADD)); return; } } break; } } accept(e2.op2, asm); String tp1 = e2.op1.valueType; switch (e2.vt) { case ARRAY: String tp2 = e2.valueType; if (tp1.charAt(0) == '[') { asm.visitInsn(getOpcode(tp1.substring(1), IALOAD)); } else { asm.visitInsn(getOpcode(tp2, IALOAD)); } break; case ADD: asm.visitInsn(getOpcode(type, IADD)); break; case SUB: asm.visitInsn(getOpcode(type, ISUB)); break; case DIV: asm.visitInsn(getOpcode(type, IDIV)); break; case MUL: asm.visitInsn(getOpcode(type, IMUL)); break; case REM: asm.visitInsn(getOpcode(type, IREM)); break; case AND: asm.visitInsn(getOpcode(type, IAND)); break; case OR: asm.visitInsn(getOpcode(type, IOR)); break; case XOR: asm.visitInsn(getOpcode(type, IXOR)); break; case SHL: asm.visitInsn(getOpcode(tp1, ISHL)); break; case SHR: asm.visitInsn(getOpcode(tp1, ISHR)); break; case USHR: asm.visitInsn(getOpcode(tp1, IUSHR)); break; case LCMP: asm.visitInsn(LCMP); break; case FCMPG: asm.visitInsn(FCMPG); break; case DCMPG: asm.visitInsn(DCMPG); break; case FCMPL: asm.visitInsn(FCMPL); break; case DCMPL: asm.visitInsn(DCMPL); break; } } private static void cast2(String t1, String t2, MethodVisitor asm) { if (t1.equals(t2)) { return; } switch (t1.charAt(0)) { case 'Z': case 'B': case 'C': case 'S': case 'I': { switch (t2.charAt(0)) { case 'F': asm.visitInsn(I2F); break; case 'J': asm.visitInsn(I2L); break; case 'D': asm.visitInsn(I2D); break; case 'C': asm.visitInsn(I2C); break; case 'B': asm.visitInsn(I2B); break; case 'S': asm.visitInsn(I2S); break; } } break; case 'J': { switch (t2.charAt(0)) { case 'I': asm.visitInsn(L2I); break; case 'F': asm.visitInsn(L2F); break; case 'D': asm.visitInsn(L2D); break; } } break; case 'D': { switch (t2.charAt(0)) { case 'I': asm.visitInsn(D2I); break; case 'F': asm.visitInsn(D2F); break; case 'J': asm.visitInsn(D2L); break; } } break; case 'F': { switch (t2.charAt(0)) { case 'I': asm.visitInsn(F2I); break; case 'J': asm.visitInsn(F2L); break; case 'D': asm.visitInsn(F2D); break; } break; } } } }