All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.googlecode.dex2jar.ir.ts.array.FillArrayTransformer Maven / Gradle / Ivy

package com.googlecode.dex2jar.ir.ts.array;

import com.googlecode.dex2jar.ir.IrMethod;
import com.googlecode.dex2jar.ir.expr.ArrayExpr;
import com.googlecode.dex2jar.ir.expr.Constant;
import com.googlecode.dex2jar.ir.expr.Exprs;
import com.googlecode.dex2jar.ir.expr.FilledArrayExpr;
import com.googlecode.dex2jar.ir.expr.Local;
import com.googlecode.dex2jar.ir.expr.TypeExpr;
import com.googlecode.dex2jar.ir.expr.Value;
import com.googlecode.dex2jar.ir.stmt.AssignStmt;
import com.googlecode.dex2jar.ir.stmt.LabelStmt;
import com.googlecode.dex2jar.ir.stmt.Stmt;
import com.googlecode.dex2jar.ir.stmt.Stmts;
import com.googlecode.dex2jar.ir.ts.Cfg;
import com.googlecode.dex2jar.ir.ts.StatedTransformer;
import com.googlecode.dex2jar.ir.ts.UniqueQueue;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;

/**
 * require SSA, usually run after ConstTransformer 1. array is fixed size. 2. array object init and use once, (exclude
 * the element assignment) 3. all elements are init with fixed index before use. 4. the array is not in PhiExpr 5. and
 * for array object init at A, use at B; A and B must in same loop/path, so G(A->B), G(A->C->B), G(A->C,
 * A->D,C->B,D->B),
 * G(A->C,C->D,D->C,C->B) and G(A->C,C->A,C->B) is ok to transform, but for G(A->C,C->B,
 * B->D,D->C), B is in a loop
 * (B->D->C->B), should not transformed.
 * 

* transform * *

 *     a=new String[3]
 *     a[0]="123"
 *     a[2]="1234"
 *     a[1]="12345"
 *     return a
 * 
*

* to * *

 *     return new String[3] { "123", "12345", "1234" }
 * 
*

* 1. This Transformer is useful when cleanup the tool-injected reflection code * *

 *     // before transform
 *     ...
 *     Class a[]=new Class[2]
 *     a[0]=String.class
 *     a[1]=int.class
 *     Method m=x.getMethod("methodA",a)
 *     Object b[]=new Object[2]
 *     b[0]="123";
 *     b[1]=Integer.valueOf(1);
 *     m.invoke(c,b)
 *     // after transform
 *     Method m=x.getMethod("methodA", new Class[2] { String.class ,int.class });
 *     m.invoke(b,new Object[]{"123",Integer.valueOf(1)})
 * 
*

* 2. Suggest decompilers generate better code * *

 *     // for following code, before transform, the decompiler generate same source
 *     Object[]a=new Object[2];
 *     a[0]=b;
 *     a[1]=c
 *     String.format("b is %s, c is %s",a)
 *     // after transform, then decompile generate the following source
 *     String.format("b is %s, c is %s",b,c)
 * 
*

* FIXME also handle not full filled array * *

 * int a[] = new int[5];
 * // a[0]=0;
 * a[1] = 1;
 * a[2] = 3;
 * a[3] = 4;
 * a[4] = 7;
 * 
*/ public class FillArrayTransformer extends StatedTransformer { private static final class ArrayObject { int size; String type; AssignStmt init; List putItem = new ArrayList<>(); List used = new ArrayList<>(); private ArrayObject(int size, String type, AssignStmt init) { this.size = size; this.type = type; this.init = init; } } public static void main(String... args) { IrMethod m = new IrMethod(); m.isStatic = true; m.name = "a"; m.args = new String[0]; m.ret = "[Ljava/lang/String;"; m.owner = "La;"; Local array = Exprs.nLocal(1); m.locals.add(array); m.stmts.add(Stmts.nAssign(array, Exprs.nNewArray("Ljava/lang/String;", Exprs.nInt(2)))); m.stmts.add(Stmts.nAssign(Exprs.nArray(array, Exprs.nInt(1), "Ljava/lang/String;"), Exprs.nString("123"))); m.stmts.add(Stmts.nAssign(Exprs.nArray(array, Exprs.nInt(0), "Ljava/lang/String;"), Exprs.nString("456"))); m.stmts.add(Stmts.nReturn(array)); new FillArrayTransformer().transform(m); System.out.println(m); } @Override public boolean transformReportChanged(IrMethod method) { // find array match fixed size,fixed index, not in phi final Map arraySizes = searchForArrayObject(method); if (arraySizes.isEmpty()) { return false; } makeSureAllElementAreAssigned(arraySizes); if (arraySizes.isEmpty()) { return false; } makeSureArrayUsedAfterAllElementAssigned(method, arraySizes); if (arraySizes.isEmpty()) { return false; } replace(method, arraySizes); return true; } private void replace(IrMethod method, Map arraySizes) { final List filledArrayExprs = new ArrayList<>(); for (Map.Entry e : arraySizes.entrySet()) { final Local local0 = e.getKey(); final ArrayObject ao = e.getValue(); final Value[] t = new Value[ao.size]; for (Stmt p : ao.putItem) { if (p.st == Stmt.ST.FILL_ARRAY_DATA) { Local local = (Local) p.getOp1(); if (local == local0) { Object vs = ((Constant) p.getOp2()).value; int endPos = Array.getLength(vs); for (int j = 0; j < endPos; j++) { t[j] = Exprs.nConstant(Array.get(vs, j)); } } } else { // ASSIGN ArrayExpr ae = (ArrayExpr) p.getOp1(); Local local = (Local) ae.getOp1(); if (local == local0) { int idx = ((Number) ((Constant) ae.getOp2()).value).intValue(); Value op2 = p.getOp2(); if (op2.vt != Value.VT.LOCAL && op2.vt != Value.VT.CONSTANT) { Local n = new Local(-1); method.locals.add(n); method.stmts.insertBefore(p, Stmts.nAssign(n, op2)); op2 = n; } t[idx] = op2; } } } // for code // b=new Object[1] // b[0]=null // a =new Object[1] // a =b; // use(a) // if a is replace before b, the code // b=new Object[1] // b[0]=null // use(new Object[]{b}) // the used stmt of b is outdated, so we have to search pre replaced arrays method.locals.remove(local0); method.stmts.remove(ao.init); for (Stmt p : ao.putItem) { method.stmts.remove(p); } Cfg.TravelCallBack tcb = new Cfg.TravelCallBack() { @Override public Value onAssign(Local v, AssignStmt as) { return v; } @Override public Value onUse(Local v) { if (local0 == v) { FilledArrayExpr fae = Exprs.nFilledArray(ao.type, t); filledArrayExprs.add(fae); return fae; } return v; } }; if (ao.used.size() == 1) { Stmt stmt = ao.used.get(0); if (method.stmts.contains(stmt)) { // the stmt is not removed by pre array replacement Cfg.travelMod(stmt, tcb, false); } else { int size = filledArrayExprs.size(); for (int i = 0; i < size; i++) { Cfg.travelMod(filledArrayExprs.get(i), tcb); } } } else if (!ao.used.isEmpty()) { throw new RuntimeException("array is used multiple times"); } } } // FIXME poor performance private void makeSureArrayUsedAfterAllElementAssigned(IrMethod method, final Map arraySizes) { for (Local local : method.locals) { local.lsIndex = -1; } final int max = 50; if (arraySizes.size() < max) { makeSureArrayUsedAfterAllElementAssigned0(method, arraySizes); } else { // this method consumes too many memory, case 'java.lang.OutOfMemoryError: Java heap space', we have to cut // it Map keptInAll = new HashMap<>(); Map keptInPart = new HashMap<>(); List arrays = new ArrayList<>(max); Iterator> it = arraySizes.entrySet().iterator(); while (it.hasNext()) { for (int i = 0; i < max && it.hasNext(); i++) { Map.Entry e = it.next(); keptInPart.put(e.getKey(), e.getValue()); it.remove(); arrays.add(e.getKey()); } makeSureArrayUsedAfterAllElementAssigned0(method, keptInPart); for (Local local : arrays) { local.lsIndex = -1; } arrays.clear(); keptInAll.putAll(keptInPart); keptInPart.clear(); } arraySizes.putAll(keptInAll); } Cfg.reIndexLocal(method); } private void makeSureArrayUsedAfterAllElementAssigned0(IrMethod method, final Map arraySizes) { int i = 0; for (Local local : arraySizes.keySet()) { local.lsIndex = i++; } final int size = i; final List values = new ArrayList<>(); Cfg.dfs(method.stmts, new Cfg.FrameVisitor() { @Override public ArrayObjectValue[] merge(ArrayObjectValue[] srcFrame, ArrayObjectValue[] distFrame, Stmt src, Stmt dist) { if (distFrame == null) { distFrame = new ArrayObjectValue[size]; for (int i = 0; i < size; i++) { ArrayObjectValue arc = srcFrame[i]; if (arc != null) { ArrayObjectValue aov = new ArrayObjectValue(arc.local); values.add(aov); aov.array = arc.array; aov.parent = arc; aov.pos = (BitSet) arc.pos.clone(); distFrame[i] = aov; } } } else { for (int i = 0; i < size; i++) { ArrayObjectValue arc = srcFrame[i]; ArrayObjectValue aov = distFrame[i]; if (arc != null && aov != null) { if (aov.otherParent == null) { aov.otherParent = new HashSet<>(); } aov.otherParent.add(arc); } } } return distFrame; } @Override public ArrayObjectValue[] initFirstFrame(Stmt first) { return new ArrayObjectValue[size]; } ArrayObjectValue[] tmp = initFirstFrame(null); Stmt currentStmt; @Override public ArrayObjectValue[] exec(ArrayObjectValue[] frame, Stmt stmt) { currentStmt = stmt; System.arraycopy(frame, 0, tmp, 0, size); if (stmt.st == Stmt.ST.FILL_ARRAY_DATA) { if (stmt.getOp1().vt == Value.VT.LOCAL) { Local local = (Local) stmt.getOp1(); if (local.lsIndex >= 0) { ArrayObjectValue av = tmp[local.lsIndex]; Constant cst = (Constant) stmt.getOp2(); int endPos = Array.getLength(cst.value); av.pos.set(0, endPos); } } else { use(stmt.getOp1()); } } else if (stmt.st == Stmt.ST.ASSIGN && stmt.getOp1().vt == Value.VT.ARRAY) { use(stmt.getOp2()); ArrayExpr ae = (ArrayExpr) stmt.getOp1(); if (ae.getOp1().vt == Value.VT.LOCAL) { Local local = (Local) ae.getOp1(); if (local.lsIndex >= 0) { int index = ((Number) ((Constant) ae.getOp2()).value).intValue(); ArrayObjectValue av = tmp[local.lsIndex]; av.pos.set(index); } else { use(ae); } } else { use(ae); } } else if (stmt.st == Stmt.ST.ASSIGN && stmt.getOp1().vt == Value.VT.LOCAL) { Local local = (Local) stmt.getOp1(); use(stmt.getOp2()); if (local.lsIndex >= 0) { ArrayObjectValue aov = new ArrayObjectValue(local); aov.array = arraySizes.get(local); aov.pos = new BitSet(); values.add(aov); tmp[local.lsIndex] = aov; } } else { switch (stmt.et) { case E1: use(stmt.getOp()); break; case E2: use(stmt.getOp1()); use(stmt.getOp2()); break; case En: throw new RuntimeException(); default: break; } } return tmp; } private void use(Value v) { switch (v.et) { case E0: if (v.vt == Value.VT.LOCAL) { Local local = (Local) v; if (local.lsIndex >= 0) { ArrayObjectValue aov = tmp[local.lsIndex]; aov.array.used.add(currentStmt); aov.used = true; } } break; case E1: use(v.getOp()); break; case E2: use(v.getOp1()); use(v.getOp2()); break; case En: for (Value op : v.getOps()) { use(op); } break; default: break; } } }); Set used = markUsed(values); // check if ArrayObjectValue have different parent assignment for (ArrayObjectValue avo : used) { if (avo.array.used.size() > 1) { arraySizes.remove(avo.local); } else { if (avo.parent != null && avo.otherParent != null) { // BitSet bs = avo.pos; BitSet p = avo.parent.pos; for (ArrayObjectValue ps : avo.otherParent) { if (!p.equals(ps.pos)) { arraySizes.remove(avo.local); break; } } } } } // check for un full init array Iterator> it = arraySizes.entrySet().iterator(); while (it.hasNext()) { Map.Entry e = it.next(); Local local = e.getKey(); ArrayObject arrayObject = e.getValue(); for (Stmt use : arrayObject.used) { ArrayObjectValue[] frame = (ArrayObjectValue[]) use.frame; ArrayObjectValue aov = frame[local.lsIndex]; BitSet pos = aov.pos; if (pos.nextClearBit(0) < arrayObject.size || pos.nextSetBit(arrayObject.size) >= 0) { it.remove(); break; } } } // clean up for (Stmt stmt : method.stmts) { stmt.frame = null; } } protected Set markUsed(Collection values) { Set used = new HashSet<>(values.size() / 2); Queue q = new UniqueQueue<>(); q.addAll(values); values.clear(); while (!q.isEmpty()) { ArrayObjectValue v = q.poll(); if (v.used) { if (used.contains(v)) { continue; } used.add(v); { ArrayObjectValue p = v.parent; if (p != null) { if (!p.used) { p.used = true; q.add(p); } } } if (v.otherParent != null) { for (ArrayObjectValue p : v.otherParent) { if (!p.used) { p.used = true; q.add(p); } } } } } return used; } private void makeSureAllElementAreAssigned(Map arraySizes) { BitSet pos = new BitSet(); Iterator> it = arraySizes.entrySet().iterator(); while (it.hasNext()) { Map.Entry e = it.next(); ArrayObject arrayObject = e.getValue(); boolean needRemove = false; for (Stmt p : arrayObject.putItem) { if (p.st == Stmt.ST.FILL_ARRAY_DATA) { int endPos = Array.getLength(((Constant) p.getOp2()).value); int next = pos.nextSetBit(0); if (next < 0 || next >= endPos) { // not set in range pos.set(0, endPos); } else { // setted in range needRemove = true; break; } } else { // ASSIGN ArrayExpr ae = (ArrayExpr) p.getOp1(); int idx = ((Number) ((Constant) ae.getOp2()).value).intValue(); if (!pos.get(idx)) { pos.set(idx); } else { needRemove = true; break; } } } if (needRemove || pos.nextClearBit(0) < arrayObject.size || pos.nextSetBit(arrayObject.size) >= 0) { it.remove(); } pos.clear(); } } private Map searchForArrayObject(IrMethod method) { final Map arraySizes = new HashMap<>(); if (method.locals.isEmpty()) { return arraySizes; } Cfg.createCFG(method); Cfg.dfsVisit(method, p -> { if (p.st == Stmt.ST.ASSIGN) { if (p.getOp2().vt == Value.VT.NEW_ARRAY && p.getOp1().vt == Value.VT.LOCAL) { TypeExpr ae = (TypeExpr) p.getOp2(); if (ae.getOp().vt == Value.VT.CONSTANT) { int size = ((Number) ((Constant) ae.getOp()).value).intValue(); // https://bitbucket.org/pxb1988/dex2jar/issues/2/decompiler-error // the following code may used in a java // try{ // new int[-1]; // } catch(Exception e) { // ... // } if (size >= 0) { arraySizes.put((Local) p.getOp1(), new ArrayObject(size, ae.type, (AssignStmt) p)); } } } else if (p.getOp1().vt == Value.VT.ARRAY) { ArrayExpr ae = (ArrayExpr) p.getOp1(); if (ae.getOp1().vt == Value.VT.LOCAL) { Local local = (Local) ae.getOp1(); ArrayObject arrayObject = arraySizes.get(local); if (arrayObject != null) { if (ae.getOp2().vt == Value.VT.CONSTANT) { arrayObject.putItem.add(p); } else { arraySizes.remove(local); } } } } } else if (p.st == Stmt.ST.FILL_ARRAY_DATA) { if (p.getOp1().vt == Value.VT.LOCAL) { Local local = (Local) p.getOp1(); ArrayObject arrayObject = arraySizes.get(local); if (arrayObject != null) { arrayObject.putItem.add(p); } } } }); if (!arraySizes.isEmpty()) { Set set = new HashSet<>(); if (method.phiLabels != null) { for (LabelStmt labelStmt : method.phiLabels) { if (labelStmt.phis != null) { for (AssignStmt as : labelStmt.phis) { set.add((Local) as.getOp1()); for (Value v : as.getOp2().getOps()) { set.add((Local) v); } } } } } if (!set.isEmpty()) { for (Local local : set) { arraySizes.remove(local); } } } return arraySizes; } static class ArrayObjectValue { BitSet pos; Local local; ArrayObject array; ArrayObjectValue parent; Set otherParent; boolean used; ArrayObjectValue(Local local) { this.local = local; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy