/* Soot - a J*va Optimization Framework
* Copyright (C) 2000 Feng Qian
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
/*
* Modified by the Sable Research Group and others 1997-1999.
* See the 'credits' file distributed with Soot for the complete list of
* contributors. (Soot is distributed at http://www.sable.mcgill.ca/soot)
*/
package soot.jimple.toolkits.annotation.arraycheck;
import soot.options.*;
import soot.*;
import soot.util.*;
import soot.jimple.*;
import soot.jimple.internal.*;
import soot.jimple.toolkits.callgraph.*;
import java.util.*;
/** Interprocedural analysis to identify rectangular multi-dimension array
* locals. It is based on the call graph.
*/
public class RectangularArrayFinder extends SceneTransformer
{
public RectangularArrayFinder( Singletons.Global g ) {}
public static RectangularArrayFinder v() { return G.v().soot_jimple_toolkits_annotation_arraycheck_RectangularArrayFinder(); }
private final ExtendedHashMutableDirectedGraph agraph =
new ExtendedHashMutableDirectedGraph();
private final Set falseSet = new HashSet();
private final Set<Object> trueSet = new HashSet<Object>();
private CallGraph cg;
protected void internalTransform(String phaseName, Map opts)
{
Scene sc = Scene.v();
cg = sc.getCallGraph();
Date start = new Date();
if (Options.v().verbose())
G.v().out.println("[ra] Finding rectangular arrays, start on "+start);
Chain appClasses = sc.getApplicationClasses();
Iterator classIt = appClasses.iterator();
while (classIt.hasNext())
{
SootClass c = (SootClass)classIt.next();
Iterator methodIt = c.methodIterator();
while (methodIt.hasNext())
{
SootMethod method = (SootMethod)methodIt.next();
if (!method.isConcrete())
continue;
if (!sc.getReachableMethods().contains(method))
continue;
recoverRectArray(method);
addInfoFromMethod(method);
}
}
/*
MutableDirectedGraph methodGraph = ig.newMethodGraph();
HashSet visitedMethods = new HashSet();
LinkedList tovisitMethods = new LinkedList();
List heads = methodGraph.getHeads();
Iterator headIt = heads.iterator();
while (headIt.hasNext())
{
SootMethod entry = (SootMethod)headIt.next();
String sig = entry.getSubSignature();
if (sig.equals(mainSignature))
tovisitMethods.add(entry);
}
while (!tovisitMethods.isEmpty())
{
SootMethod visiting = (SootMethod)tovisitMethods.removeFirst();
visitedMethods.add(visiting);
recoverRectArray(visiting);
addInfoFromMethod(visiting);
List succs = methodGraph.getSuccsOf(visiting);
Iterator succIt = succs.iterator();
while (succIt.hasNext())
{
Object succ = succIt.next();
if (!visitedMethods.contains(succ))
tovisitMethods.add(succ);
}
}
*/
/* propagate the graph info from FALSE node. */
if (agraph.containsNode(BoolValue.v(false)))
{
List changedNodeList = new ArrayList();
List startNodes = agraph.getSuccsOf(BoolValue.v(false));
falseSet.addAll(startNodes);
changedNodeList.addAll(startNodes);
while (!changedNodeList.isEmpty())
{
Object node = changedNodeList.remove(0);
List succs = agraph.getSuccsOf(node);
Iterator succsIt = succs.iterator();
while (succsIt.hasNext())
{
Object succ = succsIt.next();
if (!falseSet.contains(succ))
{
falseSet.add(succ);
changedNodeList.add(succ);
}
}
}
}
/* propagate graph info from TRUE node then. */
if (agraph.containsNode(BoolValue.v(true)))
{
List<Object> changedNodeList = new ArrayList<Object>();
List startNodes = agraph.getSuccsOf(BoolValue.v(true));
Iterator nodesIt = startNodes.iterator();
while (nodesIt.hasNext())
{
Object node = nodesIt.next();
if (falseSet.contains(node))
continue;
changedNodeList.add(node);
trueSet.add(node);
}
while (!changedNodeList.isEmpty())
{
Object node = changedNodeList.remove(0);
List succs = agraph.getSuccsOf(node);
Iterator succsIt = succs.iterator();
while (succsIt.hasNext())
{
Object succ = succsIt.next();
if (falseSet.contains(succ))
continue;
if (trueSet.contains(succ))
continue;
trueSet.add(succ);
changedNodeList.add(succ);
}
}
}
/* For verification, print out true set and false set. */
if (Options.v().debug())
{
G.v().out.println("Rectangular Array :");
{
Iterator<Object> nodeIt = trueSet.iterator();
while (nodeIt.hasNext())
{
Object node = nodeIt.next();
G.v().out.println(node);
}
}
G.v().out.println("\nNon-rectangular Array :");
{
Iterator nodeIt = falseSet.iterator();
while (nodeIt.hasNext())
{
Object node = nodeIt.next();
G.v().out.println(node);
}
}
}
Date finish = new Date();
if (Options.v().verbose())
{
long runtime = finish.getTime() - start.getTime();
long mins = runtime/60000;
long secs = (runtime%60000)/1000;
G.v().out.println("[ra] Rectangular array finder finishes."
+" It took "+mins+" mins and "+secs+" secs.");
}
}
private void addInfoFromMethod(SootMethod method)
{
if (Options.v().verbose())
G.v().out.println("[ra] Operating "+method.getSignature());
boolean needTransfer = true;
Body body = method.getActiveBody();
Set<Object> tmpNode = new HashSet<Object>();
/* check the return type of method, if it is multi-array. */
boolean trackReturn = false;
Type rtnType = method.getReturnType();
if (rtnType instanceof ArrayType)
{
if (((ArrayType)rtnType).numDimensions > 1)
{
trackReturn = true;
needTransfer = true;
}
}
Set<Local> arrayLocal = new HashSet<Local>();
/* Collect the multi-array locals */
Chain locals = body.getLocals();
Iterator localIt = locals.iterator();
while (localIt.hasNext())
{
Local local = (Local)localIt.next();
Type type = local.getType();
if (type instanceof ArrayType)
{
if ( ((ArrayType)type).numDimensions > 1)
{
arrayLocal.add(local);
}
else
tmpNode.add(new MethodLocal(method, local));
}
}
/* The method has a local graph. It will be merged to the whole graph after simplification. */
ExtendedHashMutableDirectedGraph ehmdg = new ExtendedHashMutableDirectedGraph();
Iterator unitIt = body.getUnits().snapshotIterator();
while (unitIt.hasNext())
{
Stmt s = (Stmt)unitIt.next();
/* for each invoke site, add edges from local parameter to the target methods' parameter node. */
if (s.containsInvokeExpr())
{
InvokeExpr iexpr = s.getInvokeExpr();
int argnum = iexpr.getArgCount();
for (int i=0; i<argnum; i++)
{
Value arg = iexpr.getArg(i);
if (! arrayLocal.contains(arg))
continue;
needTransfer = true;
/* from node, it is a local */
MethodLocal ml = new MethodLocal(method, (Local)arg);
Iterator targetIt = new Targets( cg.edgesOutOf(s) );
while (targetIt.hasNext())
{
SootMethod target = (SootMethod)targetIt.next();
MethodParameter mp = new MethodParameter(target, i);
/* add edge to the graph. */
ehmdg.addMutualEdge(ml, mp);
}
}
}
/* if the return type is multiarray, add an mutual edge from local to return node. */
if (trackReturn && (s instanceof ReturnStmt))
{
Value op = ((ReturnStmt)s).getOp();
if (op instanceof Local)
{
ehmdg.addMutualEdge(new MethodLocal(method, (Local)op), new MethodReturn(method));
}
}
/* examine each assign statement. build edge relationship between them. */
if (s instanceof DefinitionStmt)
{
Value leftOp = ((DefinitionStmt)s).getLeftOp();
Value rightOp = ((DefinitionStmt)s).getRightOp();
if (! (leftOp.getType() instanceof ArrayType)
&& ! (rightOp.getType() instanceof ArrayType))
continue;
Object from = null;
Object to = null;
/* kick out the possible cast. */
if ((leftOp instanceof Local) && (rightOp instanceof Local))
{
if (arrayLocal.contains(leftOp) && arrayLocal.contains(rightOp))
{
int leftDims = ((ArrayType)((Local)leftOp).getType()).numDimensions;
int rightDims = ((ArrayType)((Local)rightOp).getType()).numDimensions;
to = new MethodLocal(method, (Local)leftOp);
from = new MethodLocal(method, (Local)rightOp);
ehmdg.addMutualEdge(from, to);
if (leftDims != rightDims)
ehmdg.addEdge(BoolValue.v(false), from);
}
else
if (! arrayLocal.contains(leftOp))
{ /* implicitly cast from right side to left side, and the left side declare type is Object ... */
ehmdg.addEdge(BoolValue.v(false), new MethodLocal(method, (Local)rightOp));
}
}
else
if ((leftOp instanceof Local) && (rightOp instanceof ParameterRef))
{
if (arrayLocal.contains(leftOp))
{
to = new MethodLocal(method, (Local)leftOp);
int index = ((ParameterRef)rightOp).getIndex();
from = new MethodParameter(method, index);
ehmdg.addMutualEdge(from, to);
needTransfer = true;
}
}
else
if ((leftOp instanceof Local) && (rightOp instanceof ArrayRef))
{
Local base = (Local)((ArrayRef)rightOp).getBase();
/* it may include one-dimension array into the graph, */
if (arrayLocal.contains(base))
{
/* add 'a' to 'a[' first */
to = new ArrayReferenceNode(method, base);
from = new MethodLocal(method, base);
ehmdg.addMutualEdge(from, to);
/* put 'a[' into temporary object pool. */
tmpNode.add(to);
/* add 'a[' to 'p' then */
from = to;
to = new MethodLocal(method, (Local)leftOp);
ehmdg.addMutualEdge(from, to);
}
}
else
if ((leftOp instanceof ArrayRef) && (rightOp instanceof Local))
{
Local base = (Local)((ArrayRef)leftOp).getBase();
if (arrayLocal.contains(base))
{
/* to recover the SWAP of array dimensions. */
Object suspect = new MethodLocal(method, (Local)rightOp);
Object arrRef = new ArrayReferenceNode(method, base);
boolean doNothing = false;
blocklabel:
{
if (!ehmdg.containsNode(suspect))
break blocklabel;
List succs = ehmdg.getSuccsOf(suspect);
List preds = ehmdg.getSuccsOf(suspect);
Set neighbor = new HashSet();
neighbor.addAll(succs);
neighbor.addAll(preds);
if (neighbor.size() != 1)
break blocklabel;
Object neighborOne = (neighbor.toArray())[0];
if (arrRef.equals(neighborOne))
doNothing = true;
}
if (!doNothing)
ehmdg.addEdge(BoolValue.v(false), new MethodLocal(method, base));
}
}
else
if ((leftOp instanceof Local) && (rightOp instanceof InvokeExpr))
{
if (arrayLocal.contains(leftOp))
{
to = new MethodLocal(method, (Local)leftOp);
Iterator targetIt = new Targets( cg.edgesOutOf(s) );
while (targetIt.hasNext())
{
SootMethod target = (SootMethod)targetIt.next();
ehmdg.addMutualEdge(new MethodReturn(target), to);
}
}
}
else
/* For field reference, we can make conservative assumption that all instance fieldRef use the same node.*/
if ((leftOp instanceof FieldRef) && (rightOp instanceof Local))
{
if (arrayLocal.contains(rightOp))
{
Type ftype = ((FieldRef)leftOp).getType();
Type ltype = ((Local)rightOp).getType();
to = ((FieldRef)leftOp).getField();
from = new MethodLocal(method, (Local)rightOp);
ehmdg.addMutualEdge(from, to);
if (!ftype.equals(ltype))
{
ehmdg.addEdge(BoolValue.v(false), to);
}
needTransfer = true;
}
}
else
if ((leftOp instanceof Local) && (rightOp instanceof FieldRef))
{
if (arrayLocal.contains(leftOp))
{
Type ftype = ((FieldRef)rightOp).getType();
Type ltype = ((Local)leftOp).getType();
to = new MethodLocal(method, (Local)leftOp);
from = ((FieldRef)rightOp).getField();
ehmdg.addMutualEdge(from, to);
if (!ftype.equals(ltype))
{
ehmdg.addEdge(BoolValue.v(false), to);
}
needTransfer = true;
}
}
else
if ((leftOp instanceof Local) && ((rightOp instanceof NewArrayExpr)||(rightOp instanceof NewMultiArrayExpr)))
{
if (arrayLocal.contains(leftOp))
{
ehmdg.addEdge(BoolValue.v(true), new MethodLocal(method, (Local)leftOp));
}
}
else
if ((leftOp instanceof Local) && (rightOp instanceof CastExpr))
/* Cast express, we will use conservative solution. */
{
Local rOp = (Local)((CastExpr)rightOp).getOp();
to = new MethodLocal(method, (Local)leftOp);
from = new MethodLocal(method, rOp);
if (arrayLocal.contains(leftOp) && arrayLocal.contains(rOp))
{
ArrayType lat = (ArrayType)leftOp.getType();
ArrayType rat = (ArrayType)rOp.getType();
if (lat.numDimensions == rat.numDimensions)
{
ehmdg.addMutualEdge(from, to);
}
else
{
ehmdg.addEdge(BoolValue.v(false), from);
ehmdg.addEdge(BoolValue.v(false), to);
}
}
else
if (arrayLocal.contains(leftOp))
{
ehmdg.addEdge(BoolValue.v(false), to);
}
else
if (arrayLocal.contains(rOp))
{
ehmdg.addEdge(BoolValue.v(false), from);
}
}
}
}
/* Compute the graph locally, it will skip all locals */
if (needTransfer)
{
Iterator<Object> tmpNodeIt = tmpNode.iterator();
while (tmpNodeIt.hasNext())
{
ehmdg.skipNode(tmpNodeIt.next());
}
/* Add local graph to whole graph */
agraph.mergeWith(ehmdg);
}
}
private void recoverRectArray(SootMethod method)
{
Body body = method.getActiveBody();
HashSet<Local> malocal = new HashSet<Local>();
Chain locals = body.getLocals();
Iterator localsIt = locals.iterator();
while (localsIt.hasNext())
{
Local local = (Local)localsIt.next();
Type type = local.getType();
if (!(type instanceof ArrayType))
continue;
if (((ArrayType)type).numDimensions == 2)
malocal.add(local);
}
if (malocal.size() == 0)
return;
Chain units = body.getUnits();
Stmt stmt = (Stmt)units.getFirst();
while (true)
{
if (stmt == null)
break;
/* only deal with the first block */
if (!stmt.fallsThrough())
break;
searchblock:
{
/* possible candidates */
if (!(stmt instanceof AssignStmt))
break searchblock;
Value leftOp = ((AssignStmt)stmt).getLeftOp();
Value rightOp = ((AssignStmt)stmt).getRightOp();
if (!malocal.contains(leftOp) || !(rightOp instanceof NewArrayExpr))
break searchblock;
Local local = (Local)leftOp;
NewArrayExpr naexpr = (NewArrayExpr)rightOp;
Value size = naexpr.getSize();
if (!(size instanceof IntConstant))
break searchblock;
int firstdim = ((IntConstant)size).value;
if (firstdim > 100)
break searchblock;
ArrayType localtype = (ArrayType)local.getType();
Type basetype = localtype.baseType;
Local[] tmplocals = new Local[firstdim];
int seconddim = lookforPattern(units, stmt,
firstdim, local,
basetype, tmplocals);
if (seconddim >= 0)
transferPattern(units, stmt,
firstdim, seconddim,
local, basetype, tmplocals);
}
stmt = (Stmt)units.getSuccOf(stmt);
}
}
/* if the local is assigned a rect array, return back the second dimension length,
else return -1
*/
private int lookforPattern(Chain units, Stmt startpoint,
int firstdim, Local local,
Type basetype, Local[] tmplocals)
{
/* It is a state machine to match the pattern */
/* state input goto
start r1 = new(A[])[c] 1
1 r2 = newA[d] 2
2 r2[?] = ... 2
r1[e] = r2 (e = c-1) 3
r1[e] = r2 (e = e'+1) 2
3 end
*/
int seconddim = -1;
int curdim = 0;
Object curtmp = local; // Local, I have to initialize it. It should not be this value.
Stmt curstmt = startpoint;
int fault = 99;
int state = 1;
while (true)
{
curstmt = (Stmt)units.getSuccOf(curstmt);
if (curstmt == null)
return -1;
if (!(curstmt instanceof AssignStmt))
return -1;
Value leftOp = ((AssignStmt)curstmt).getLeftOp();
Value rightOp = ((AssignStmt)curstmt).getRightOp();
switch (state)
{
/* we already did state 0 outside */
case 0:
break;
case 1:
/* make sure it is a new array expr */
{
state = fault;
if (!(rightOp instanceof NewArrayExpr))
break;
NewArrayExpr naexpr = (NewArrayExpr)rightOp;
Type type = naexpr.getBaseType();
Value size = naexpr.getSize();
if (!type.equals(basetype))
break;
if (!(size instanceof IntConstant))
break;
if (curdim == 0)
seconddim = ((IntConstant)size).value;
else
{
if (((IntConstant)size).value != seconddim)
break;
}
curtmp = leftOp;
state = 2;
}
break;
case 2:
{
state = fault;
if (!(leftOp instanceof ArrayRef))
break;
Value base = ((ArrayRef)leftOp).getBase();
Value idx = ((ArrayRef)leftOp).getIndex();
/* curtmp[?] = ? */
if (base.equals(curtmp))
state = 2;
else
/* local[?] = curtmp? */
if (base.equals(local))
{
if (!(idx instanceof IntConstant))
break;
if (curdim != ((IntConstant)idx).value)
break;
if (!rightOp.equals(curtmp))
break;
tmplocals[curdim] = (Local)curtmp;
curdim++;
if (curdim >= firstdim)
state = 3;
else
state = 1;
}
}
break;
case 3:
return seconddim;
default:
return -1;
}
}
}
private void transferPattern(Chain units, Stmt startpoint,
int firstdim, int seconddim,
Local local, Type basetype,
Local[] tmplocals)
{
/* sequentially search and replace the sub dimension assignment */
{
/* change the first one, reset the right op */
ArrayType atype = (ArrayType)local.getType();
List sizes = new ArrayList(2);
sizes.add(IntConstant.v(firstdim));
sizes.add(IntConstant.v(seconddim));
Value nmexpr = new JNewMultiArrayExpr(atype, sizes);
((AssignStmt)startpoint).setRightOp(nmexpr);
}
{
int curdim = 0;
Local tmpcur = local;
Stmt curstmt = (Stmt)units.getSuccOf(startpoint);
while (curdim < firstdim)
{
Value leftOp = ((AssignStmt)curstmt).getLeftOp();
Value rightOp = ((AssignStmt)curstmt).getRightOp();
if (tmplocals[curdim].equals(leftOp) && (rightOp instanceof NewArrayExpr))
{
ArrayRef arexpr = new JArrayRef(local, IntConstant.v(curdim));
((AssignStmt)curstmt).setRightOp(arexpr);
tmpcur = (Local)leftOp;
}
else
if ((leftOp instanceof ArrayRef) && (rightOp.equals(tmpcur)))
{
/* delete current stmt */
Stmt tmpstmt = curstmt;
curstmt = (Stmt)units.getSuccOf(curstmt);
units.remove(tmpstmt);
curdim++;
}
else
curstmt = (Stmt)units.getSuccOf(curstmt);
}
}
}
public boolean isRectangular(Object obj)
{
if (trueSet.contains(obj))
return true;
else
return false;
}
}