package dk.brics.xact.analysis.soot;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import soot.ArrayType;
import soot.Local;
import soot.Unit;
import soot.ValueBox;
import soot.jimple.ArrayRef;
import soot.jimple.DefinitionStmt;
import soot.jimple.IntConstant;
import soot.jimple.NewArrayExpr;
import soot.jimple.Stmt;
import soot.toolkits.graph.DirectedGraph;
import soot.toolkits.scalar.ForwardFlowAnalysis;
/**
* Determines for each program point the arrays whose exact contents are known in terms
* of other local variables.
* <p/>
* The analysis is specifically designed to handle the bytecode produced by inline array initializers
* (see example) and not much of anything else. Example of inline array initializer:
* <pre>
* XML[] array = new XML[] { xml1, xml2, xml3 }; // explicit
* XML y = XML.concat(xml1, xml2, xml3); // implicit
* </pre>
*/
public class ArrayConstantAnalysis extends ForwardFlowAnalysis<Unit,Map<Local,ArrayConstantInfo>> {
// bottom is represented by not being in the keyset
// top is represented by mapping to ArrayConstantInfo with contents=null
public ArrayConstantAnalysis(DirectedGraph<Unit> graph) {
super(graph);
doAnalysis();
}
@Override
protected void flowThrough(
Map<Local, ArrayConstantInfo> src,
Unit unit,
Map<Local, ArrayConstantInfo> dest) {
Stmt stmt = (Stmt)unit;
final Set<Local> dead = new HashSet<Local>();
srcloop:
for (Local local : src.keySet()) {
ArrayConstantInfo info = src.get(local);
if (info.contents == null) {
dead.add(local);
continue;
}
ValueBox allowedBox = null; // the only place the statement is allowed to reference an array without breaking it
boolean affected = false;
if (stmt instanceof DefinitionStmt) {
DefinitionStmt def = (DefinitionStmt)stmt;
if (def.getLeftOp() instanceof ArrayRef) {
ArrayRef array = (ArrayRef)def.getLeftOp();
allowedBox = array.getBaseBox();
if (array.getBase() == local) {
// assignment into interesting array
if (array.getIndex() instanceof IntConstant) {
IntConstant index = (IntConstant)array.getIndex();
if (index.value >= 0 && index.value < info.contents.length) {
ArrayConstantInfo out = info.clone();
out.contents[index.value] = def.getRightOpBox();
dest.put(local, out);
} else {
// definitely ArrayIndexOutOfBoundsException
// let's just map it to BOTTOM by not adding the array
dest.clear();
return;
}
} else {
// unknown index, information is lost
dead.add(local);
}
affected = true;
}
}
else if (def.getLeftOp() instanceof Local) {
for (int i=0; i<info.contents.length; i++) {
if (info.contents[i] == def.getLeftOp()) {
dead.add(local);
continue srcloop;
}
}
}
}
if (!affected) {
dest.put(local, src.get(local));
}
for (ValueBox used : stmt.getUseBoxes()) {
if (used.getValue() == local && used != allowedBox) {
dead.add(local);
break;
}
}
}
// consider new arrays that become live
if (stmt instanceof DefinitionStmt) {
DefinitionStmt def = (DefinitionStmt)stmt;
if (def.getRightOp() instanceof NewArrayExpr) {
Local left = (Local)def.getLeftOp(); // left side is local because right side is complex expression
NewArrayExpr expr = (NewArrayExpr)def.getRightOp();
if (expr.getSize() instanceof IntConstant) {
int size = ((IntConstant)expr.getSize()).value;
dest.put(left, new ArrayConstantInfo(new ValueBox[size]));
} else {
// unknown length
dead.add(left);
}
}
else if (def.getLeftOp().getType() instanceof ArrayType) {
if (def.getLeftOp() instanceof Local) {
Local left = (Local)def.getLeftOp();
// array acquired by some other means (method call, multidimensional array, typecast, aliasing, etc.)
dead.add(left);
}
}
}
// move dead arrays to TOP
for (Local deadLocal : dead) {
dest.put(deadLocal, ArrayConstantInfo.UNKNOWN);
}
}
@Override
protected void copy(
Map<Local, ArrayConstantInfo> src,
Map<Local, ArrayConstantInfo> dest) {
dest.clear();
for (Map.Entry<Local,ArrayConstantInfo> entry : src.entrySet()) {
dest.put(entry.getKey(), entry.getValue().clone());
}
}
@Override
protected Map<Local, ArrayConstantInfo> entryInitialFlow() {
return new LinkedHashMap<Local,ArrayConstantInfo>();
}
@Override
protected void merge(
Map<Local, ArrayConstantInfo> src1,
Map<Local, ArrayConstantInfo> src2,
Map<Local, ArrayConstantInfo> dest) {
for (Map.Entry<Local,ArrayConstantInfo> entry : src1.entrySet()) {
ArrayConstantInfo info2 = src2.get(entry.getKey());
if (info2 == null) {
dest.put(entry.getKey(), entry.getValue());
} else {
ArrayConstantInfo merged = entry.getValue().leastUpperBound(info2);
dest.put(entry.getKey(), merged);
}
}
for (Map.Entry<Local,ArrayConstantInfo> entry : src2.entrySet()) {
ArrayConstantInfo info1 = src1.get(entry.getKey());
if (info1 != null)
continue;
dest.put(entry.getKey(), entry.getValue());
}
}
@Override
protected Map<Local, ArrayConstantInfo> newInitialFlow() {
return new LinkedHashMap<Local,ArrayConstantInfo>();
}
}