/*
* 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 com.googlecode.dex2jar.ir.IrMethod;
import com.googlecode.dex2jar.ir.StmtTraveler;
import com.googlecode.dex2jar.ir.expr.*;
import com.googlecode.dex2jar.ir.stmt.AssignStmt;
import com.googlecode.dex2jar.ir.stmt.Stmt;
import com.googlecode.dex2jar.ir.stmt.Stmts;
import java.util.*;
import static com.googlecode.dex2jar.ir.stmt.Stmt.ST.*;
import static com.googlecode.dex2jar.ir.expr.Value.VT.*;
/**
* simply merge
*
* <pre>
* a=NEW Labc;
* a.<init>();
* </pre>
*
* to
*
* <pre>
* a = new abc();
* </pre>
*
* Run after [SSATransformer, RemoveLocalFromSSA]
*/
public class NewTransformer implements Transformer {
@Override
public void transform(IrMethod method) {
final Map<Local, NewExpr> nAssign = new HashMap<>();
final Map<Local, AssignStmt> init = new HashMap<>();
for (Iterator<Stmt> it = method.stmts.iterator(); it.hasNext();) {
Stmt p = it.next();
if (p.st == ASSIGN && p.getOp1().vt == LOCAL && p.getOp2().vt == NEW) {
// the stmt is a new assign stmt
Local local = (Local) p.getOp1();
nAssign.put(local, (NewExpr) p.getOp2());
init.put(local, (AssignStmt) p);
}
}
if (nAssign.size() == 0) {
return;
}
int[] reads = Cfg.countLocalReads(method);
final Set<Local> oneOrLess = new HashSet<>();
final boolean changed[] = { true };
while (changed[0]) {
changed[0] = false;
for (Local local : nAssign.keySet()) {
if (reads[local._ls_index] < 2) {
oneOrLess.add(local);
method.stmts.remove(init.remove(local));
method.locals.remove(local);
}
}
if (oneOrLess.size() > 0) {
new StmtTraveler() {
@Override
public Stmt travel(Stmt stmt) {
Stmt p = super.travel(stmt);
if (p.st == ASSIGN && p.getOp1().vt == LOCAL && p.getOp2().vt == NEW) {
// the stmt is a new assign stmt
Local local = (Local) p.getOp1();
if (!nAssign.containsKey(local)) {
nAssign.put(local, (NewExpr) p.getOp2());
init.put(local, (AssignStmt) p);
changed[0] = true;
}
}
return p;
}
@Override
public Value travel(Value op) {
if (op.vt == LOCAL) {
Local local = (Local) op;
if (oneOrLess.contains(local)) {
return nAssign.get(local);
}
}
return super.travel(op);
}
}.travel(method.stmts);
for (Local local : oneOrLess) {
nAssign.remove(local);
}
oneOrLess.clear();
}
}
Set<Local> replaced = new HashSet<>();
for (Iterator<Stmt> it = method.stmts.iterator(); it.hasNext();) {
Stmt p = it.next();
InvokeExpr ie = null;
if (p.st == ASSIGN) {
if (p.getOp2().vt == INVOKE_SPECIAL) {
ie = (InvokeExpr) p.getOp2();
}
} else if (p.st == VOID_INVOKE) {
ie = (InvokeExpr) p.getOp();
}
if (ie != null) {
if ("<init>".equals(ie.name) && "V".equals(ie.ret)) {
Value[] orgOps = ie.getOps();
if (orgOps[0].vt == LOCAL) {
Local objToInit = (Local) ie.getOps()[0];
NewExpr newExpr = nAssign.get(objToInit);
if (newExpr != null) {
if (!ie.owner.equals(newExpr.type)) {
throw new RuntimeException("");
}
Value[] nOps = new Value[orgOps.length - 1];
System.arraycopy(orgOps, 1, nOps, 0, nOps.length);
InvokeExpr invokeNew = Exprs.nInvokeNew(nOps, ie.args, ie.owner);
method.stmts.insertBefore(p, Stmts.nAssign(objToInit, invokeNew));
it.remove();
replaced.add(objToInit);
}
} else if (orgOps[0].vt == NEW) {
NewExpr newExpr = (NewExpr) ie.getOps()[0];
if (newExpr != null) {
Value[] nOps = new Value[orgOps.length - 1];
System.arraycopy(orgOps, 1, nOps, 0, nOps.length);
InvokeExpr invokeNew = Exprs.nInvokeNew(nOps, ie.args, ie.owner);
method.stmts.insertBefore(p, Stmts.nVoidInvoke(invokeNew));
it.remove();
}
}
}
}
}
nAssign.clear();
for (Local x : replaced) {
method.stmts.remove(init.remove(x));
}
}
}