package soot.jimple.toolkits.thread.synchronization;
import java.util.*;
import soot.*;
import soot.util.*;
import soot.jimple.*;
import soot.jimple.internal.JNopStmt;
import soot.jimple.toolkits.pointer.*;
import soot.jimple.toolkits.thread.ThreadLocalObjectsAnalysis;
import soot.toolkits.scalar.*;
import soot.toolkits.graph.*;
/**
* @author Richard L. Halpert
* Finds Synchronized Regions and creates a set of CriticalSection objects from them.
*/
public class SynchronizedRegionFinder extends ForwardFlowAnalysis<Unit, FlowSet>
{
FlowSet emptySet = new ArraySparseSet();
Map unitToGenerateSet;
Body body;
Chain units;
SootMethod method;
ExceptionalUnitGraph egraph;
LocalUses slu;
CriticalSectionAwareSideEffectAnalysis tasea;
// SideEffectAnalysis sea;
List<Object> prepUnits;
CriticalSection methodTn;
public boolean optionPrintDebug = false;
public boolean optionOpenNesting = true;
SynchronizedRegionFinder(UnitGraph graph, Body b, boolean optionPrintDebug, boolean optionOpenNesting, ThreadLocalObjectsAnalysis tlo)
{
super(graph);
this.optionPrintDebug = optionPrintDebug;
this.optionOpenNesting = optionOpenNesting;
body = b;
units = b.getUnits();
method = body.getMethod();
if(graph instanceof ExceptionalUnitGraph)
egraph = (ExceptionalUnitGraph) graph;
else
egraph = new ExceptionalUnitGraph(b);
slu = new SimpleLocalUses(egraph, new SmartLocalDefs(egraph, new SimpleLiveLocals(egraph)));
if( G.v().Union_factory == null ) {
G.v().Union_factory = new UnionFactory() {
public Union newUnion() { return FullObjectSet.v(); }
};
}
tasea = new CriticalSectionAwareSideEffectAnalysis(Scene.v().getPointsToAnalysis(),
Scene.v().getCallGraph(), null, tlo);
prepUnits = new ArrayList<Object>();
methodTn = null;
if(method.isSynchronized())
{
// Entire method is transactional
methodTn = new CriticalSection(true, method, 1);
methodTn.beginning = ((JimpleBody) body).getFirstNonIdentityStmt();
}
doAnalysis();
if(method.isSynchronized() && methodTn != null)
{
for(Iterator tailIt = graph.getTails().iterator(); tailIt.hasNext(); )
{
Stmt tail = (Stmt) tailIt.next();
methodTn.earlyEnds.add(new Pair(tail, null)); // has no exitmonitor stmt yet
}
}
}
/**
* All INs are initialized to the empty set.
**/
protected FlowSet newInitialFlow()
{
FlowSet ret = emptySet.clone();
if(method.isSynchronized() && methodTn != null)
{
ret.add(new SynchronizedRegionFlowPair(methodTn, true));
}
return ret;
}
/**
* IN(Start) is the empty set
**/
protected FlowSet entryInitialFlow()
{
FlowSet ret = emptySet.clone();
if(method.isSynchronized() && methodTn != null)
{
ret.add(new SynchronizedRegionFlowPair(methodTn, true));
}
return ret;
}
/**
* OUT is the same as (IN minus killSet) plus the genSet.
**/
protected void flowThrough(FlowSet in, Unit unit, FlowSet out)
{
Stmt stmt = (Stmt) unit;
copy(in, out);
// Determine if this statement is a preparatory statement for an
// upcoming transactional region. Such a statement would be a definition
// which contains no invoke statement, and which corresponds only to
// EnterMonitorStmt and ExitMonitorStmt uses. In this case, the read
// set of this statement should not be considered part of the read set
// of any containing transaction
if(unit instanceof AssignStmt)
{
boolean isPrep = true;
Iterator uses = slu.getUsesOf((Unit) unit).iterator();
if(!uses.hasNext())
isPrep = false;
while(uses.hasNext())
{
UnitValueBoxPair use = (UnitValueBoxPair) uses.next();
Unit useStmt = use.getUnit();
if( !(useStmt instanceof EnterMonitorStmt) && !(useStmt instanceof ExitMonitorStmt) )
{
isPrep = false;
break;
}
}
if(isPrep)
{
prepUnits.add(unit);
if(optionPrintDebug)
{
G.v().out.println("prep: " + unit.toString());
}
return;
}
}
// Determine if this statement is the start of a transaction
boolean addSelf = (unit instanceof EnterMonitorStmt);
// Determine the level of transaction nesting of this statement
int nestLevel = 0;
Iterator outIt0 = out.iterator();
while(outIt0.hasNext())
{
SynchronizedRegionFlowPair srfp = (SynchronizedRegionFlowPair) outIt0.next();
if(srfp.tn.nestLevel > nestLevel && srfp.inside == true)
nestLevel = srfp.tn.nestLevel;
}
// Process this unit's effect on each txn
RWSet stmtRead = null;
RWSet stmtWrite = null;
Iterator outIt = out.iterator();
boolean printed = false;
while(outIt.hasNext())
{
SynchronizedRegionFlowPair srfp = (SynchronizedRegionFlowPair) outIt.next();
CriticalSection tn = srfp.tn;
// Check if we are revisting the start of this existing transaction
if(tn.entermonitor == stmt)
{
srfp.inside = true;
addSelf = false; // this transaction already exists...
}
// if this is the immediately enclosing transaction
if(srfp.inside == true && (tn.nestLevel == nestLevel || optionOpenNesting == false))
{
printed = true; // for debugging purposes, indicated that we'll print a debug output for this statement
// Add this unit to the current transactional region
if(!tn.units.contains(unit))
tn.units.add(unit);
// Check what kind of statement this is
// If it contains an invoke, save it for later processing as part of this transaction
// If it is a monitorexit, mark that it's the end of the transaction
// Otherwise, add it's read/write sets to the transaction's read/write sets
if(stmt.containsInvokeExpr())
{
// Note if this unit is a call to wait() or notify()/notifyAll()
String InvokeSig = stmt.getInvokeExpr().getMethod().getSubSignature();
if((InvokeSig.equals("void notify()") || InvokeSig.equals("void notifyAll()")) && tn.nestLevel == nestLevel) // only applies to outermost txn
{
if(!tn.notifys.contains(unit))
tn.notifys.add(unit);
if(optionPrintDebug)
G.v().out.print("{x,x} ");
}
else if((InvokeSig.equals("void wait()") || InvokeSig.equals("void wait(long)") || InvokeSig.equals("void wait(long,int)")) && tn.nestLevel == nestLevel) // only applies to outermost txn
{
if(!tn.waits.contains(unit))
tn.waits.add(unit);
if(optionPrintDebug)
G.v().out.print("{x,x} ");
}
if(!tn.invokes.contains(unit))
{
// Mark this unit for later read/write set calculation (must be deferred until all tns have been found)
tn.invokes.add(unit);
// Debug Output
if(optionPrintDebug)
{
stmtRead = tasea.readSet(tn.method, stmt, tn, new HashSet());
stmtWrite = tasea.writeSet(tn.method, stmt, tn, new HashSet());
G.v().out.print("{");
if(stmtRead != null)
{
G.v().out.print( ( (stmtRead.getGlobals() != null ? stmtRead.getGlobals().size() : 0) +
(stmtRead.getFields() != null ? stmtRead.getFields().size() : 0) ) );
}
else
G.v().out.print( "0" );
G.v().out.print(",");
if(stmtWrite != null)
{
G.v().out.print( ( (stmtWrite.getGlobals() != null ? stmtWrite.getGlobals().size() : 0) +
(stmtWrite.getFields() != null ? stmtWrite.getFields().size() : 0) ) );
}
else
G.v().out.print( "0" );
G.v().out.print("} ");
}
}
}
else if(unit instanceof ExitMonitorStmt && tn.nestLevel == nestLevel) // only applies to outermost txn
{
// Mark this as end of this tn
srfp.inside = false;
// Check if this is an early end or fallthrough end
Stmt nextUnit = stmt;
do
{
nextUnit = (Stmt) units.getSuccOf(nextUnit);
} while (nextUnit instanceof JNopStmt);
if( nextUnit instanceof ReturnStmt ||
nextUnit instanceof ReturnVoidStmt ||
nextUnit instanceof ExitMonitorStmt )
{
tn.earlyEnds.add(new Pair(nextUnit, stmt)); // <early end stmt, exitmonitor stmt>
}
else if( nextUnit instanceof GotoStmt )
{
tn.end = new Pair(nextUnit, stmt); // <end stmt, exitmonitor stmt>
tn.after = (Stmt) ((GotoStmt) nextUnit).getTarget();
}
else if( nextUnit instanceof ThrowStmt )
{
tn.exceptionalEnd = new Pair(nextUnit, stmt);
}
else
throw new RuntimeException("Unknown bytecode pattern: exitmonitor not followed by return, exitmonitor, goto, or throw");
if(optionPrintDebug)
G.v().out.print("[0,0] ");
}
else
{
// Add this unit's read and write sets to this transactional region
HashSet uses = new HashSet();
stmtRead = tasea.readSet( method, stmt, tn, uses );
stmtWrite = tasea.writeSet( method, stmt, tn, uses );
tn.read.union(stmtRead);
tn.write.union(stmtWrite);
// Debug Output
if(optionPrintDebug)
{
G.v().out.print("[");
if(stmtRead != null)
{
G.v().out.print( ( (stmtRead.getGlobals() != null ? stmtRead.getGlobals().size() : 0) +
(stmtRead.getFields() != null ? stmtRead.getFields().size() : 0) ) );
}
else
G.v().out.print( "0" );
G.v().out.print(",");
if(stmtWrite != null)
{
G.v().out.print( ( (stmtWrite.getGlobals() != null ? stmtWrite.getGlobals().size() : 0) +
(stmtWrite.getFields() != null ? stmtWrite.getFields().size() : 0) ) );
}
else
G.v().out.print( "0" );
G.v().out.print("] ");
}
}
}
}
// DEBUG output
if(optionPrintDebug)
{
if(!printed)
{
G.v().out.print("[0,0] ");
}
G.v().out.println(unit.toString());
// If this unit is an invoke statement calling a library function and the R/W sets are huge, print out the targets
if(stmt.containsInvokeExpr() &&
stmt.getInvokeExpr().getMethod().getDeclaringClass().toString().startsWith("java.") &&
stmtRead != null && stmtWrite != null)
{
if(stmtRead.size() < 25 && stmtWrite.size() < 25)
{
G.v().out.println(" Read/Write Set for LibInvoke:");
G.v().out.println("Read Set:(" + stmtRead.size() + ")" + stmtRead.toString().replaceAll("\n", "\n "));
G.v().out.println("Write Set:(" + stmtWrite.size() + ")" + stmtWrite.toString().replaceAll("\n", "\n "));
}
}
}
// If this statement was a monitorenter, and no transaction object yet exists for it,
// create one.
if(addSelf)
{
CriticalSection newTn = new CriticalSection(false, method, nestLevel + 1);
newTn.entermonitor = stmt;
newTn.beginning = (Stmt) units.getSuccOf(stmt);
if(stmt instanceof EnterMonitorStmt)
newTn.origLock = ((EnterMonitorStmt) stmt).getOp();
if(optionPrintDebug)
G.v().out.println("Transaction found in method: " + newTn.method.toString());
out.add(new SynchronizedRegionFlowPair(newTn, true));
// This is a really stupid way to find out which prep applies to this txn.
Iterator<Object> prepUnitsIt = prepUnits.iterator();
while(prepUnitsIt.hasNext())
{
Unit prepUnit = (Unit) prepUnitsIt.next();
Iterator uses = slu.getUsesOf(prepUnit).iterator();
while(uses.hasNext())
{
UnitValueBoxPair use = (UnitValueBoxPair) uses.next();
if(use.getUnit() == (Unit) unit)
{// if this transaction's monitorenter statement is one of the uses of this preparatory unit
newTn.prepStmt = (Stmt) prepUnit;
}
}
}
}
}
/**
* union
**/
protected void merge(FlowSet inSet1, FlowSet inSet2, FlowSet outSet)
{
inSet1.union(inSet2, outSet);
}
protected void copy(FlowSet sourceSet, FlowSet destSet)
{
destSet.clear();
Iterator it = sourceSet.iterator();
while(it.hasNext())
{
SynchronizedRegionFlowPair tfp = (SynchronizedRegionFlowPair) it.next();
destSet.add(tfp.clone());
}
}
}