/* Soot - a J*va Optimization Framework
* Copyright (C) 1997-1999 Raja Vallee-Rai
*
* 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;
import java.io.ByteArrayOutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import soot.jimple.CaughtExceptionRef;
import soot.jimple.DefinitionStmt;
import soot.jimple.IdentityStmt;
import soot.jimple.InstanceInvokeExpr;
import soot.jimple.InvokeExpr;
import soot.jimple.InvokeStmt;
import soot.jimple.ParameterRef;
import soot.jimple.ThisRef;
import soot.options.Options;
import soot.tagkit.AbstractHost;
import soot.tagkit.Tag;
import soot.toolkits.exceptions.PedanticThrowAnalysis;
import soot.toolkits.graph.ExceptionalUnitGraph;
import soot.toolkits.graph.UnitGraph;
import soot.toolkits.scalar.FlowSet;
import soot.toolkits.scalar.InitAnalysis;
import soot.toolkits.scalar.LocalDefs;
import soot.toolkits.scalar.SimpleLiveLocals;
import soot.toolkits.scalar.SmartLocalDefs;
import soot.util.Chain;
import soot.util.EscapedWriter;
import soot.util.HashChain;
/**
* Abstract base class that models the body (code attribute) of a Java method.
* Classes that implement an Intermediate Representation for a method body should subclass it.
* In particular the classes GrimpBody, JimpleBody and BafBody all extend this
* class. This class provides methods that are common to any IR, such as methods
* to get the body's units (statements), traps, and locals.
*
* @see soot.grimp.GrimpBody
* @see soot.jimple.JimpleBody
* @see soot.baf.BafBody
*/
public abstract class Body extends AbstractHost implements Serializable
{
/** The method associated with this Body. */
protected transient SootMethod method = null;
/** The chain of locals for this Body. */
protected Chain<Local> localChain = new HashChain<Local>();
/** The chain of traps for this Body. */
protected Chain<Trap> trapChain = new HashChain<Trap>();
// RoboVM note: Added
protected List<LocalVariable> localVariables = new ArrayList<>();
/** The chain of units for this Body. */
protected PatchingChain<Unit> unitChain = new PatchingChain<Unit>(new HashChain<Unit>());
/** Creates a deep copy of this Body. */
abstract public Object clone();
/** Creates a Body associated to the given method. Used by subclasses during initialization.
* Creation of a Body is triggered by e.g. Jimple.v().newBody(options).
*/
protected Body(SootMethod m)
{
this.method = m;
}
/** Creates an extremely empty Body. The Body is not associated to any method. */
protected Body()
{
}
/**
* Returns the method associated with this Body.
* @return the method that owns this body.
*/
public SootMethod getMethod()
{
if(method == null)
throw new RuntimeException("no method associated w/ body");
return method;
}
/**
* Sets the method associated with this Body.
* @param method the method that owns this body.
*
*/
public void setMethod(SootMethod method)
{
this.method = method;
}
/** Returns the number of locals declared in this body. */
public int getLocalCount()
{
return localChain.size();
}
/** Copies the contents of the given Body into this one. */
public Map<Object, Object> importBodyContentsFrom(Body b)
{
HashMap<Object, Object> bindings = new HashMap<Object, Object>();
{
Iterator<Unit> it = b.getUnits().iterator();
// Clone units in body's statement list
while(it.hasNext()) {
Unit original = it.next();
Unit copy = (Unit) original.clone();
copy.addAllTagsOf(original);
// Add cloned unit to our unitChain.
unitChain.addLast(copy);
// Build old <-> new map to be able to patch up references to other units
// within the cloned units. (these are still refering to the original
// unit objects).
bindings.put(original, copy);
}
}
{
// Clone trap units.
Iterator<Trap> it = b.getTraps().iterator();
while(it.hasNext()) {
Trap original = it.next();
Trap copy = (Trap) original.clone();
// Add cloned unit to our trap list.
trapChain.addLast(copy);
// Store old <-> new mapping.
bindings.put(original, copy);
}
}
{
// Clone local units.
Iterator<Local> it = b.getLocals().iterator();
while(it.hasNext()) {
Local original = it.next();
Local copy = (Local) original.clone();
// Add cloned unit to our trap list.
localChain.addLast(copy);
// Build old <-> new mapping.
bindings.put(original, copy);
}
}
{
// Patch up references within units using our (old <-> new) map.
Iterator<UnitBox> it = getAllUnitBoxes().iterator();
while(it.hasNext()) {
UnitBox box = it.next();
Unit newObject, oldObject = box.getUnit();
// if we have a reference to an old object, replace it
// it's clone.
if( (newObject = (Unit) bindings.get(oldObject)) != null )
box.setUnit(newObject);
}
}
{
// backpatching all local variables.
Iterator<ValueBox> it = getUseBoxes().iterator();
while(it.hasNext()) {
ValueBox vb = it.next();
if(vb.getValue() instanceof Local)
vb.setValue((Value) bindings.get(vb.getValue()));
}
it = getDefBoxes().iterator();
while(it.hasNext()) {
ValueBox vb = it.next();
if(vb.getValue() instanceof Local)
vb.setValue((Value) bindings.get(vb.getValue()));
}
}
return bindings;
}
/** Verifies a few sanity conditions on the contents on this body. */
public void validate()
{
//System.out.println("body: "+this.getUnits());
validateLocals();
validateTraps();
validateUnitBoxes();
validateLocalVariables(); // RoboVM note: Added
if (Options.v().debug() || Options.v().validate()) {
validateUses();
validateValueBoxes();
checkInit();
checkTypes();
checkLocals();
}
}
/** Verifies that a ValueBox is not used in more than one place. */
public void validateValueBoxes()
{
List<ValueBox> l = getUseAndDefBoxes();
for( int i = 0; i < l.size(); i++ ) {
for( int j = 0; j < l.size(); j++ ) {
if( i == j ) continue;
if( l.get(i) == l.get(j) ) {
System.err.println("Aliased value box : "+l.get(i)+" in "+getMethod());
for( Iterator<Unit> uIt = getUnits().iterator(); uIt.hasNext(); ) {
final Unit u = uIt.next();
System.err.println(""+u);
}
throw new RuntimeException("Aliased value box : "+l.get(i)+" in "+getMethod());
}
}
}
}
/** Verifies that each Local of getUseAndDefBoxes() is in this body's locals Chain. */
public void validateLocals()
{
Iterator<ValueBox> it = getUseBoxes().iterator();
while(it.hasNext()){
validateLocal( it.next() );
}
it = getDefBoxes().iterator();
while(it.hasNext()){
validateLocal( it.next() );
}
}
private void validateLocal( ValueBox vb ) {
Value value;
if( (value = vb.getValue()) instanceof Local) {
//System.out.println("localChain: "+localChain);
if(!localChain.contains(value))
throw new RuntimeException("Local not in chain : "+value+" in "+getMethod());
}
}
/** Verifies that the begin, end and handler units of each trap are in this body. */
public void validateTraps()
{
Iterator<Trap> it = getTraps().iterator();
while (it.hasNext())
{
Trap t = it.next();
if (!unitChain.contains(t.getBeginUnit()))
throw new RuntimeException("begin not in chain"+" in "+getMethod());
if (!unitChain.contains(t.getEndUnit()))
throw new RuntimeException("end not in chain"+" in "+getMethod());
if (!unitChain.contains(t.getHandlerUnit()))
throw new RuntimeException("handler not in chain"+" in "+getMethod());
}
}
// RoboVM note: Added
public void validateLocalVariables()
{
Iterator<LocalVariable> it = getLocalVariables().iterator();
while (it.hasNext())
{
LocalVariable lv = it.next();
if (!unitChain.contains(lv.getStartUnit()))
throw new RuntimeException("start not in chain"+" in "+getMethod());
if (lv.getEndUnit() != null && !unitChain.contains(lv.getEndUnit()))
throw new RuntimeException("end not in chain"+" in "+getMethod());
}
}
/** Verifies that the UnitBoxes of this Body all point to a Unit contained within this body. */
public void validateUnitBoxes()
{
Iterator<UnitBox> it = getAllUnitBoxes().iterator();
while (it.hasNext())
{
UnitBox ub = it.next();
if (!unitChain.contains(ub.getUnit()))
throw new RuntimeException
("Unitbox points outside unitChain! to unit : "+ub.getUnit()+" in "+getMethod());
}
}
/** Verifies that each use in this Body has a def. */
public void validateUses()
{
UnitGraph g = new ExceptionalUnitGraph(this);
LocalDefs ld = new SmartLocalDefs(g, new SimpleLiveLocals(g));
Iterator<Unit> unitsIt = getUnits().iterator();
while (unitsIt.hasNext())
{
Unit u = unitsIt.next();
Iterator<ValueBox> useBoxIt = u.getUseBoxes().iterator();
while (useBoxIt.hasNext())
{
Value v = (useBoxIt.next()).getValue();
if (v instanceof Local)
{
// This throws an exception if there is
// no def already; we check anyhow.
List<Unit> l = ld.getDefsOfAt((Local)v, u);
if (l.size() == 0){
for( Iterator<Unit> uuIt = getUnits().iterator(); uuIt.hasNext(); ) {
final Unit uu = uuIt.next();
System.err.println(""+uu);
}
throw new RuntimeException("no defs for value: "+v+"!"+" in "+getMethod());
}
}
}
}
}
/** Returns a backed chain of the locals declared in this Body. */
public Chain<Local> getLocals() {return localChain;}
/** Returns a backed view of the traps found in this Body. */
public Chain<Trap> getTraps() {return trapChain;}
// RoboVM note: Added
public List<LocalVariable> getLocalVariables() {
return localVariables;
}
/** Return LHS of the first identity stmt assigning from \@this. **/
public Local getThisLocal()
{
Iterator<Unit> unitsIt = getUnits().iterator();
while (unitsIt.hasNext())
{
Unit s = unitsIt.next();
if (s instanceof IdentityStmt &&
((IdentityStmt)s).getRightOp() instanceof ThisRef)
return (Local)(((IdentityStmt)s).getLeftOp());
}
throw new RuntimeException("couldn't find identityref!"+" in "+getMethod());
}
/** Return LHS of the first identity stmt assigning from \@parameter i. **/
public Local getParameterLocal(int i)
{
Iterator<Unit> unitsIt = getUnits().iterator();
while (unitsIt.hasNext())
{
Unit s = unitsIt.next();
if (s instanceof IdentityStmt &&
((IdentityStmt)s).getRightOp() instanceof ParameterRef)
{
IdentityStmt is = (IdentityStmt)s;
ParameterRef pr = (ParameterRef)is.getRightOp();
if (pr.getIndex() == i)
return (Local)is.getLeftOp();
}
}
throw new RuntimeException("couldn't find parameterref!"+" in "+getMethod());
}
/**
* Returns the Chain of Units that make up this body. The units are
* returned as a PatchingChain. The client can then manipulate the chain,
* adding and removing units, and the changes will be reflected in the body.
* Since a PatchingChain is returned the client need <i>not</i> worry about removing exception
* boundary units or otherwise corrupting the chain.
*
* @return the units in this Body
*
* @see PatchingChain
* @see Unit
*/
public PatchingChain<Unit> getUnits()
{
return unitChain;
}
/**
* Returns the result of iterating through all Units in this body
* and querying them for their UnitBoxes. All UnitBoxes thus
* found are returned. Branching Units and statements which use
* PhiExpr will have UnitBoxes; a UnitBox contains a Unit that is
* either a target of a branch or is being used as a pointer to
* the end of a CFG block.
*
* <p> This method is typically used for pointer patching, eg when
* the unit chain is cloned.
*
* @return A list of all the UnitBoxes held by this body's units.
* @see UnitBox
* @see #getUnitBoxes(boolean)
* @see Unit#getUnitBoxes()
* @see soot.shimple.PhiExpr#getUnitBoxes()
**/
public List<UnitBox> getAllUnitBoxes()
{
ArrayList<UnitBox> unitBoxList = new ArrayList<UnitBox>();
{
Iterator<Unit> it = unitChain.iterator();
while(it.hasNext()) {
Unit item = it.next();
unitBoxList.addAll(item.getUnitBoxes());
}
}
{
Iterator<Trap> it = trapChain.iterator();
while(it.hasNext()) {
Trap item = it.next();
unitBoxList.addAll(item.getUnitBoxes());
}
}
// RoboVM note: Added
{
Iterator<LocalVariable> it = localVariables.iterator();
while(it.hasNext()) {
LocalVariable item = it.next();
unitBoxList.addAll(item.getUnitBoxes());
}
}
return unitBoxList;
}
/**
* If branchTarget is true, returns the result of iterating
* through all branching Units in this body and querying them for
* their UnitBoxes. These UnitBoxes contain Units that are the
* target of a branch. This is useful for, say, labeling blocks
* or updating the targets of branching statements.
*
* <p> If branchTarget is false, returns the result of iterating
* through the non-branching Units in this body and querying them
* for their UnitBoxes. Any such UnitBoxes (typically from
* PhiExpr) contain a Unit that indicates the end of a CFG block.
*
* @return a list of all the UnitBoxes held by this body's
* branching units.
*
* @see UnitBox
* @see #getAllUnitBoxes()
* @see Unit#getUnitBoxes()
* @see soot.shimple.PhiExpr#getUnitBoxes()
**/
public List<UnitBox> getUnitBoxes(boolean branchTarget)
{
ArrayList<UnitBox> unitBoxList = new ArrayList<UnitBox>();
{
Iterator<Unit> it = unitChain.iterator();
while(it.hasNext()) {
Unit item = it.next();
if(branchTarget){
if(item.branches())
unitBoxList.addAll(item.getUnitBoxes());
}
else{
if(!item.branches())
unitBoxList.addAll(item.getUnitBoxes());
}
}
}
{
Iterator<Trap> it = trapChain.iterator();
while(it.hasNext()) {
Trap item = it.next();
unitBoxList.addAll(item.getUnitBoxes());
}
}
return unitBoxList;
}
/**
* Returns the result of iterating through all Units in this
* body and querying them for ValueBoxes used.
* All of the ValueBoxes found are then returned as a List.
*
* @return a list of all the ValueBoxes for the Values used this body's units.
*
* @see Value
* @see Unit#getUseBoxes
* @see ValueBox
* @see Value
*
*/
public List<ValueBox> getUseBoxes()
{
ArrayList<ValueBox> useBoxList = new ArrayList<ValueBox>();
Iterator<Unit> it = unitChain.iterator();
while(it.hasNext()) {
Unit item = it.next();
useBoxList.addAll(item.getUseBoxes());
}
return useBoxList;
}
/**
* Returns the result of iterating through all Units in this
* body and querying them for ValueBoxes defined.
* All of the ValueBoxes found are then returned as a List.
*
* @return a list of all the ValueBoxes for Values defined by this body's units.
*
* @see Value
* @see Unit#getDefBoxes
* @see ValueBox
* @see Value
*/
public List<ValueBox> getDefBoxes()
{
ArrayList<ValueBox> defBoxList = new ArrayList<ValueBox>();
Iterator<Unit> it = unitChain.iterator();
while(it.hasNext()) {
Unit item = it.next();
defBoxList.addAll(item.getDefBoxes());
}
return defBoxList;
}
/**
* Returns a list of boxes corresponding to Values
* either used or defined in any unit of this Body.
*
* @return a list of ValueBoxes for held by the body's Units.
*
* @see Value
* @see Unit#getUseAndDefBoxes
* @see ValueBox
* @see Value
*/
public List<ValueBox> getUseAndDefBoxes()
{
ArrayList<ValueBox> useAndDefBoxList = new ArrayList<ValueBox>();
Iterator<Unit> it = unitChain.iterator();
while(it.hasNext()) {
Unit item = it.next();
useAndDefBoxList.addAll(item.getUseBoxes());
useAndDefBoxList.addAll(item.getDefBoxes());
}
return useAndDefBoxList;
}
private void checkLocals() {
Chain<Local> locals=getLocals();
Iterator<Local> it=locals.iterator();
while(it.hasNext()) {
Local l=it.next();
if(l.getType() instanceof VoidType)
throw new RuntimeException("Local "+l+" in "+method+" defined with void type");
}
}
private void checkTypes() {
Chain<Unit> units=getUnits();
Iterator<Unit> it=units.iterator();
while(it.hasNext()) {
Unit stmt=(it.next());
InvokeExpr iexpr=null;
String errorSuffix=" at "+stmt+" in "+getMethod();
if(stmt instanceof DefinitionStmt) {
DefinitionStmt astmt=(DefinitionStmt) stmt;
if( !(astmt.getRightOp() instanceof CaughtExceptionRef ) ) {
Type leftType=Type.toMachineType(astmt.getLeftOp().getType());
Type rightType=Type.toMachineType(astmt.getRightOp().getType());
checkCopy(leftType,rightType,errorSuffix);
if(astmt.getRightOp() instanceof InvokeExpr)
iexpr=(InvokeExpr) (astmt.getRightOp());
}
}
if(stmt instanceof InvokeStmt) iexpr=((InvokeStmt) stmt).getInvokeExpr();
if(iexpr!=null) {
SootMethodRef called=iexpr.getMethodRef();
if(iexpr instanceof InstanceInvokeExpr) {
InstanceInvokeExpr iiexpr=(InstanceInvokeExpr) iexpr;
checkCopy(called.declaringClass().getType(),
iiexpr.getBase().getType(),
" in receiver of call"+errorSuffix);
}
if(called.parameterTypes().size() != iexpr.getArgCount())
throw new RuntimeException("Warning: Argument count doesn't match up with signature in call"+errorSuffix+" in "+getMethod());
else
for(int i=0;i<iexpr.getArgCount();i++)
checkCopy(Type.toMachineType(called.parameterType(i)),
Type.toMachineType(iexpr.getArg(i).getType()),
" in argument "+i+" of call"+errorSuffix);
}
}
}
private void checkCopy(Type leftType,Type rightType,String errorSuffix) {
if(leftType instanceof PrimType || rightType instanceof PrimType) {
if(leftType instanceof IntType && rightType instanceof IntType) return;
if(leftType instanceof LongType && rightType instanceof LongType) return;
if(leftType instanceof FloatType && rightType instanceof FloatType) return;
if(leftType instanceof DoubleType && rightType instanceof DoubleType) return;
throw new RuntimeException("Warning: Bad use of primitive type"+errorSuffix+" in "+getMethod());
}
if(rightType instanceof NullType) return;
if(leftType instanceof RefType &&
((RefType) leftType).getClassName().equals("java.lang.Object")) return;
if(leftType instanceof ArrayType || rightType instanceof ArrayType) {
if(leftType instanceof ArrayType && rightType instanceof ArrayType) return;
//it is legal to assign arrays to variables of type Serializable, Cloneable or Object
if(rightType instanceof ArrayType) {
if(leftType.equals(RefType.v("java.io.Serializable")) ||
leftType.equals(RefType.v("java.lang.Cloneable")) ||
leftType.equals(RefType.v("java.lang.Object")))
return;
}
throw new RuntimeException("Warning: Bad use of array type"+errorSuffix+" in "+getMethod());
}
if(leftType instanceof RefType && rightType instanceof RefType) {
SootClass leftClass=((RefType) leftType).getSootClass();
SootClass rightClass=((RefType) rightType).getSootClass();
if(leftClass.isPhantom() || rightClass.isPhantom()) {
return;
}
if(leftClass.isInterface()) {
if(rightClass.isInterface()) {
if(!(leftClass.getName().equals(rightClass.getName()) ||
Scene.v().getActiveHierarchy().isInterfaceSubinterfaceOf(rightClass,leftClass)))
throw new RuntimeException("Warning: Bad use of interface type"+errorSuffix+" in "+getMethod());
} else {
// No quick way to check this for now.
}
} else {
if(rightClass.isInterface()) {
throw new RuntimeException("Warning: trying to use interface type where non-Object class expected"
+errorSuffix+" in "+getMethod());
} else {
if(!Scene.v().getActiveHierarchy().isClassSubclassOfIncluding(rightClass,leftClass))
throw new RuntimeException("Warning: Bad use of class type"+errorSuffix+" in "+getMethod());
}
}
return;
}
throw new RuntimeException("Warning: Bad types"+errorSuffix+" in "+getMethod());
}
@SuppressWarnings("unchecked")
public void checkInit() {
Chain<Unit> units=getUnits();
ExceptionalUnitGraph g = new ExceptionalUnitGraph
(this, PedanticThrowAnalysis.v(), false);
// FIXME: Work around for bug in soot
Scene.v().releaseActiveHierarchy();
InitAnalysis analysis=new InitAnalysis(g);
Iterator<Unit> it=units.iterator();
while(it.hasNext()) {
Unit s=(it.next());
FlowSet init=(FlowSet) analysis.getFlowBefore(s);
List<ValueBox> uses=s.getUseBoxes();
Iterator<ValueBox> usesIt=uses.iterator();
while(usesIt.hasNext()) {
Value v=((usesIt.next())).getValue();
if(v instanceof Local) {
Local l=(Local) v;
if(!init.contains(l))
throw new RuntimeException("Warning: Local variable "+l
+" not definitely defined at "+s
+" in "+method);
}
}
}
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
ByteArrayOutputStream streamOut = new ByteArrayOutputStream();
PrintWriter writerOut = new PrintWriter(new EscapedWriter(new OutputStreamWriter(streamOut)));
try {
Printer.v().printTo(this, writerOut);
} catch (RuntimeException e) {
e.printStackTrace(writerOut);
}
writerOut.flush();
writerOut.close();
return streamOut.toString();
}
}