/*
* 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.dex2jar.ir.ts;
import java.util.*;
import com.googlecode.dex2jar.ir.IrMethod;
import com.googlecode.dex2jar.ir.expr.Local;
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.StmtList;
public class RemoveLocalFromSSA extends StatedTransformer {
static <T extends Value> void replaceAssign(List<AssignStmt> assignStmtList, Map<Local, T> toReplace) {
for (AssignStmt as : assignStmtList) {
Value right = as.getOp2();
T to = toReplace.get(right);
if (to != null) {
as.setOp2(to);
}
}
}
private boolean simpleAssign(List<LabelStmt> phiLabels, List<AssignStmt> assignStmtList,
Map<Local, Local> toReplace, StmtList stmts) {
Set<Value> usedInPhi = new HashSet<>();
if (phiLabels != null) {
for (LabelStmt labelStmt : phiLabels) {
for (AssignStmt phi : labelStmt.phis) {
usedInPhi.addAll(Arrays.asList(phi.getOp2().getOps()));
}
}
}
boolean changed = false;
for (Iterator<AssignStmt> it = assignStmtList.iterator(); it.hasNext(); ) {
AssignStmt as = it.next();
if (!usedInPhi.contains(as.getOp1())) {
it.remove();
stmts.remove(as);
toReplace.put((Local) as.getOp1(), (Local) as.getOp2());
changed = true;
}
}
return changed;
}
private void replacePhi(List<LabelStmt> phiLabels, Map<Local, Local> toReplace, Set<Value> set) {
if (phiLabels != null) {
for (LabelStmt labelStmt : phiLabels) {
for (AssignStmt phi : labelStmt.phis) {
Value[] ops = phi.getOp2().getOps();
for (Value op : ops) {
Value n = toReplace.get(op);
if (n != null) {
set.add(n);
} else {
set.add(op);
}
}
set.remove(phi.getOp1());
phi.getOp2().setOps(set.toArray(new Value[set.size()]));
set.clear();
}
}
}
}
static class PhiObject {
Set<PhiObject> parent = new HashSet<>();
Set<PhiObject> children = new HashSet<>();
Local local;
boolean isInitByPhi = false;
}
public static PhiObject getOrCreate(Map<Local, PhiObject> map, Local local) {
PhiObject po = map.get(local);
if (po == null) {
po = new PhiObject();
po.local = local;
map.put(local, po);
}
return po;
}
public static void linkPhiObject(PhiObject parent, PhiObject child) {
parent.children.add(child);
child.parent.add(parent);
}
private boolean simplePhi(List<LabelStmt> phiLabels, Map<Local, Local> toReplace, Set<Value> set) {
boolean changed = false;
if (phiLabels != null) {
for (Iterator<LabelStmt> itLabel = phiLabels.iterator(); itLabel.hasNext(); ) {
LabelStmt labelStmt = itLabel.next();
for (Iterator<AssignStmt> it = labelStmt.phis.iterator(); it.hasNext(); ) {
AssignStmt phi = it.next();
set.addAll(Arrays.asList(phi.getOp2().getOps()));
set.remove(phi.getOp1());
if (set.size() == 1) {
it.remove();
changed = true;
toReplace.put((Local) phi.getOp1(), (Local) set.iterator().next());
}
set.clear();
}
if (labelStmt.phis.size() == 0) {
labelStmt.phis = null;
itLabel.remove();
}
}
}
return changed;
}
private boolean removeLoopFromPhi(List<LabelStmt> phiLabels, Map<Local, Local> toReplace) {
boolean changed = false;
if (phiLabels != null) {
Set<Local> toDeletePhiAssign = new HashSet<>();
Map<Local, PhiObject> phis;
// detect loop init in phi
phis = collectPhiObjects(phiLabels);
Queue<PhiObject> q = new UniqueQueue<>();
q.addAll(phis.values());
while (!q.isEmpty()) {
PhiObject po = q.poll();
for (PhiObject child : po.children) {
if (child.isInitByPhi) {
if (child.parent.addAll(po.parent)) {
q.add(child);
}
}
}
}
for (PhiObject po : phis.values()) {
if (po.isInitByPhi) {
Local local = null;
for (PhiObject p : po.parent) {
if (!p.isInitByPhi) {
if (local == null) { // the first non-phi value
local = p.local;
} else {
local = null;
break;
}
}
}
if (local != null) {
toReplace.put(po.local, local);
toDeletePhiAssign.add(po.local);
changed = true;
}
}
}
for (Iterator<LabelStmt> itLabel = phiLabels.iterator(); itLabel.hasNext(); ) {
LabelStmt labelStmt = itLabel.next();
for (Iterator<AssignStmt> it = labelStmt.phis.iterator(); it.hasNext(); ) {
AssignStmt phi = it.next();
if (toDeletePhiAssign.contains(phi.getOp1())) {
it.remove();
}
}
if (labelStmt.phis.size() == 0) {
labelStmt.phis = null;
itLabel.remove();
}
}
}
return changed;
}
private Map<Local, PhiObject> collectPhiObjects(List<LabelStmt> phiLabels) {
Map<Local, PhiObject> phis;
phis = new HashMap<>();
for (LabelStmt labelStmt : phiLabels) {
for (AssignStmt as : labelStmt.phis) {
Local local = (Local) as.getOp1();
PhiObject child = getOrCreate(phis, local);
child.isInitByPhi = true;
for (Value op : as.getOp2().getOps()) {
if (op == local) {
continue;
}
PhiObject parent = getOrCreate(phis, (Local) op);
linkPhiObject(parent, child);
}
}
}
return phis;
}
static <T> void fixReplace(Map<Local, T> toReplace) {
boolean changed = true;
while (changed) {
changed = false;
for (Map.Entry<Local, T> e : toReplace.entrySet()) {
T b = e.getValue();
T n = toReplace.get(b);
if (n != null && b != n) {
changed = true;
e.setValue(n);
}
}
}
}
@Override
public boolean transformReportChanged(IrMethod method) {
boolean irChanged = false;
List<AssignStmt> assignStmtList = new ArrayList<>();
List<LabelStmt> phiLabels = method.phiLabels;
for (Stmt p = method.stmts.getFirst(); p != null; p = p.getNext()) {
if (p.st == Stmt.ST.ASSIGN) {
AssignStmt as = (AssignStmt) p;
if (as.getOp1().vt == Value.VT.LOCAL && as.getOp2().vt == Value.VT.LOCAL) {
assignStmtList.add(as);
}
}
}
final Map<Local, Local> toReplace = new HashMap<>();
Set<Value> set = new HashSet<>();
boolean changed = true;
while (changed) {
changed = false;
if (removeLoopFromPhi(phiLabels, toReplace)) {
fixReplace(toReplace);
replacePhi(phiLabels, toReplace, set);
}
while (simplePhi(phiLabels, toReplace, set)) {// remove a = phi(b)
fixReplace(toReplace);
replacePhi(phiLabels, toReplace, set);
}
while (simpleAssign(phiLabels, assignStmtList, toReplace, method.stmts)) {// remove a=b
fixReplace(toReplace);
replaceAssign(assignStmtList, toReplace);
changed = true;
irChanged = true;
}
replacePhi(phiLabels, toReplace, set);
}
for (Local local : toReplace.keySet()) {
method.locals.remove(local);
irChanged = true;
}
if (toReplace.size() > 0) {
Cfg.travelMod(method.stmts, new Cfg.TravelCallBack() {
@Override
public Value onAssign(Local v, AssignStmt as) {
return v;
}
@Override
public Value onUse(Local v) {
Local n = toReplace.get(v);
return n == null ? v : n;
}
}, true);
}
return irChanged;
}
}