/**
* This file is licensed under the University of Illinois/NCSA Open Source License. See LICENSE.TXT for details.
*/
package edu.illinois.keshmesh.detector;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.eclipse.jdt.core.IJavaProject;
import com.ibm.wala.classLoader.IClass;
import com.ibm.wala.classLoader.IField;
import com.ibm.wala.classLoader.IMethod;
import com.ibm.wala.classLoader.SyntheticMethod;
import com.ibm.wala.dataflow.graph.BitVectorFramework;
import com.ibm.wala.dataflow.graph.BitVectorSolver;
import com.ibm.wala.fixpoint.BitVectorVariable;
import com.ibm.wala.ipa.callgraph.CGNode;
import com.ibm.wala.ipa.callgraph.propagation.InstanceFieldPointerKey;
import com.ibm.wala.ipa.callgraph.propagation.InstanceKey;
import com.ibm.wala.ipa.callgraph.propagation.LocalPointerKey;
import com.ibm.wala.ipa.callgraph.propagation.PointerKey;
import com.ibm.wala.ssa.IR;
import com.ibm.wala.ssa.SSAAbstractInvokeInstruction;
import com.ibm.wala.ssa.SSAFieldAccessInstruction;
import com.ibm.wala.ssa.SSAInstruction;
import com.ibm.wala.ssa.SSAMonitorInstruction;
import com.ibm.wala.util.CancelException;
import com.ibm.wala.util.graph.Graph;
import com.ibm.wala.util.graph.impl.GraphInverter;
import com.ibm.wala.util.graph.traverse.DFS;
import com.ibm.wala.util.intset.BitVector;
import com.ibm.wala.util.intset.IntIterator;
import com.ibm.wala.util.intset.IntSet;
import com.ibm.wala.util.intset.MutableMapping;
import com.ibm.wala.util.intset.OrdinalSetMapping;
import edu.illinois.keshmesh.detector.bugs.BugInstance;
import edu.illinois.keshmesh.detector.bugs.BugInstances;
import edu.illinois.keshmesh.detector.bugs.BugPatterns;
import edu.illinois.keshmesh.detector.bugs.VNA00JFixInformation;
import edu.illinois.keshmesh.detector.util.AnalysisUtils;
import edu.illinois.keshmesh.util.Logger;
import edu.illinois.keshmesh.util.Modes;
/**
*
* @author Mohsen Vakilian
* @author Stas Negara
*
*/
public class VNA00JBugDetector extends BugPatternDetector {
VNA00JIntermediateResults intermediateResults = new VNA00JIntermediateResults();
private final OrdinalSetMapping<InstructionInfo> globalValues = MutableMapping.make();
private final Map<CGNode, CGNodeInfo> cgNodeInfoMap = new HashMap<CGNode, CGNodeInfo>();
private Collection<IClass> threadSafeClasses;
@Override
public IntermediateResults getIntermediateResults() {
return intermediateResults;
}
private boolean isThreadSafe(IClass klass) {
populateThreadSafeClassesLazily();
return threadSafeClasses.contains(klass);
}
private void populateThreadSafeClassesLazily() {
if (threadSafeClasses == null) {
populateThreadSafeClasses();
intermediateResults.setThreadSafeClasses(threadSafeClasses);
}
}
@Override
public BugInstances doPerformAnalysis(IJavaProject javaProject, BasicAnalysisData basicAnalysisData) {
populateThreadSafeClassesLazily();
collectUnprotectedInstructionsThatMayAccessUnsafelySharedFields();
BitVectorSolver<CGNode> bitVectorSolver = propagateUnprotectedInstructionThatMayAccessUnsafelySharedFields();
Collection<InstructionInfo> instructionInfosToReport = getInstructionsToReport(bitVectorSolver);
return createBugInstances(instructionInfosToReport);
}
//TODO: All derived classes of a thread safe class should be considered thread safe as well.
private void populateThreadSafeClasses() {
threadSafeClasses = new HashSet<IClass>();
Iterator<CGNode> cgNodesIter = basicAnalysisData.callGraph.iterator();
while (cgNodesIter.hasNext()) {
CGNode cgNode = cgNodesIter.next();
if (!isIgnoredClass(cgNode.getMethod().getDeclaringClass()) && belongsToThreadSafeClass(cgNode)) {
threadSafeClasses.add(cgNode.getMethod().getDeclaringClass());
}
}
}
private boolean belongsToThreadSafeClass(CGNode cgNode) {
IClass declaringClass = cgNode.getMethod().getDeclaringClass();
if (implementsRunnableInterface(declaringClass) || extendsThreadClass(declaringClass)) {
return true;
} else {
return AnalysisUtils.contains(javaProject, cgNode, new InstructionFilter() {
@Override
public boolean accept(InstructionInfo instructionInfo) {
return instructionInfo.getInstruction() instanceof SSAMonitorInstruction;
}
});
}
}
private boolean extendsThreadClass(IClass klass) {
IClass superclass = klass.getSuperclass();
if (superclass == null) {
return false;
}
if (isThreadClass(superclass)) {
return true;
}
return extendsThreadClass(superclass);
}
private boolean implementsRunnableInterface(IClass klass) {
for (IClass implementedInterface : klass.getAllImplementedInterfaces()) {
if (isRunnableInterface(implementedInterface)) {
return true;
}
}
return false;
}
private boolean isRunnableInterface(IClass interfaceClass) {
return AnalysisUtils.getEnclosingNonanonymousClassName(interfaceClass.getName()).equals("java.lang.Runnable");
}
private boolean isThreadClass(IClass klass) {
return AnalysisUtils.getEnclosingNonanonymousClassName(klass.getName()).equals("java.lang.Thread");
}
/**
*
* This method is based on
* LCK06JBugDetector#populateSynchronizedBlocksForNode.
*
* @param synchronizedBlocks
* @param cgNode
* @param synchronizedBlockKind
*/
private void populateSynchronizedBlocksForNode(Collection<InstructionInfo> synchronizedBlocks, final CGNode cgNode) {
AnalysisUtils.collect(javaProject, synchronizedBlocks, cgNode, new InstructionFilter() {
@Override
public boolean accept(InstructionInfo instructionInfo) {
SSAInstruction instruction = instructionInfo.getInstruction();
if (AnalysisUtils.isMonitorEnter(instruction)) {
return true;
}
return false;
}
});
}
/**
* This method is based on
* LCK06JBugDetector#populateUnsafeModifyingStaticFieldsInstructionsMap.
*
* For every method of the input program, this method computes the set of
* instructions that are not protected by a synchronized block but may
* access a field that is shared unsafely. A field that is neither volatile
* nor final is shared unsafely.
*
* The resulting instructions will be used as initial values of the data
* flow problem corresponding to the interprocedural part of the detector.
*
* FIXME: This method is doing too much work and needs to be split into
* smaller methods.
*/
private void collectUnprotectedInstructionsThatMayAccessUnsafelySharedFields() {
Map<CGNode, Collection<InstructionInfo>> intermediateMapOfUnprotectedInstructions = null;
if (!Modes.isInProductionMode()) {
intermediateMapOfUnprotectedInstructions = new HashMap<CGNode, Collection<InstructionInfo>>();
}
Iterator<CGNode> cgNodesIterator = basicAnalysisData.callGraph.iterator();
while (cgNodesIterator.hasNext()) {
CGNode cgNode = cgNodesIterator.next();
BitVector bitVector = new BitVector();
Collection<InstructionInfo> synchronizedBlocks = new HashSet<InstructionInfo>();
Collection<InstructionInfo> unprotectedInstructionsThatMayAccessUnsafelySharedFields = new HashSet<InstructionInfo>();
if (canContainUnprotectedInstructions(cgNode.getMethod())) {
Collection<InstructionInfo> instructionsThatMayAccessUnsafelySharedFields = getInstructionsThatMayAccessUnsafelySharedFields(cgNode);
populateSynchronizedBlocksForNode(synchronizedBlocks, cgNode);
for (InstructionInfo instructionThatMayAccessesUnsafelySharedFields : instructionsThatMayAccessUnsafelySharedFields) {
if (!AnalysisUtils.isProtectedByAnySynchronizedBlock(synchronizedBlocks, instructionThatMayAccessesUnsafelySharedFields)) {
unprotectedInstructionsThatMayAccessUnsafelySharedFields.add(instructionThatMayAccessesUnsafelySharedFields);
}
}
for (InstructionInfo instructionThatMayAccessesUnsafelySharedFields : instructionsThatMayAccessUnsafelySharedFields) {
Logger.log("UNSAFE ACCESS: " + instructionThatMayAccessesUnsafelySharedFields);
}
for (InstructionInfo unprotectedInstruction : unprotectedInstructionsThatMayAccessUnsafelySharedFields) {
bitVector.set(globalValues.add(unprotectedInstruction));
Logger.log("UNPROTECTED INSTRUCTION: " + unprotectedInstruction);
}
}
cgNodeInfoMap.put(cgNode, new CGNodeInfo(synchronizedBlocks, bitVector));
if (!Modes.isInProductionMode() && intermediateMapOfUnprotectedInstructions != null) {
intermediateMapOfUnprotectedInstructions.put(cgNode, unprotectedInstructionsThatMayAccessUnsafelySharedFields);
}
}
if (!Modes.isInProductionMode()) {
intermediateResults.setUnprotectedInstructionsThatMayAccessUnsafelySharedFields(intermediateMapOfUnprotectedInstructions);
}
}
private boolean canContainUnprotectedInstructions(IMethod method) {
return !method.isSynchronized() && !isIgnoredClass(method.getDeclaringClass()) && !isInitializationMethod(method);
}
private boolean isInitializationMethod(IMethod method) {
String methodName = method.getName().toString();
return methodName.equals("<init>") || methodName.equals("<clinit>");
}
/**
*
* See LCK06JBugDetector#isIgnoredClass
*
* @param klass
* @return
*/
private boolean isIgnoredClass(IClass klass) {
return AnalysisUtils.isJDKClass(klass);
}
/**
*
* See LCK06JBugDetector#getModifyingStaticFieldsInstructions
*
* @param cgNode
* @return
*/
private Collection<InstructionInfo> getInstructionsThatMayAccessUnsafelySharedFields(CGNode cgNode) {
Collection<InstructionInfo> instructionsThatMayAccessUnsafelySharedFields = new HashSet<InstructionInfo>();
AnalysisUtils.collect(javaProject, instructionsThatMayAccessUnsafelySharedFields, cgNode, new InstructionFilter() {
@Override
public boolean accept(InstructionInfo instructionInfo) {
return mayAccessUnsafelySharedFields(instructionInfo);
}
});
return instructionsThatMayAccessUnsafelySharedFields;
}
private Collection<IClass> getConcreteTypes(Collection<InstanceKey> instanceKeys) {
Collection<IClass> concreteTypes = new HashSet<IClass>();
for (InstanceKey instanceKey : instanceKeys) {
concreteTypes.add(instanceKey.getConcreteType());
}
return concreteTypes;
}
private boolean isAnyThreadSafe(Collection<IClass> classes) {
for (IClass klass : classes) {
if (isThreadSafe(klass)) {
return true;
}
}
return false;
}
private Collection<InstanceKey> getSuccNodes(CGNode cgNode, int valueNumber) {
PointerKey pointerForValueNumber = getPointerForValueNumber(cgNode, valueNumber);
Collection<InstanceKey> pointedInstances = new HashSet<InstanceKey>();
Iterator<Object> iterator = basicAnalysisData.basicHeapGraph.getSuccNodes(pointerForValueNumber);
while (iterator.hasNext()) {
InstanceKey nextInstanceKey = (InstanceKey) iterator.next();
pointedInstances.add(nextInstanceKey);
}
return pointedInstances;
}
/**
*
* See LCK06JBugDetector#canModifyStaticField.
*
* @param instructionInfo
* @return
*/
private boolean mayAccessUnsafelySharedFields(InstructionInfo instructionInfo) {
SSAInstruction ssaInstruction = instructionInfo.getInstruction();
if (ssaInstruction instanceof SSAFieldAccessInstruction) {
SSAFieldAccessInstruction fieldAccessInstruction = (SSAFieldAccessInstruction) ssaInstruction;
IField accessedField = basicAnalysisData.classHierarchy.resolveField(fieldAccessInstruction.getDeclaredField());
if (accessedField.isVolatile() || accessedField.isFinal()) {
return false;
}
if (fieldAccessInstruction.isStatic()) {
IClass declaringClass = accessedField.getDeclaringClass();
if (isThreadSafe(declaringClass)) {
return true;
}
} else {
Collection<InstanceKey> pointedInstances = getSuccNodes(instructionInfo.getCGNode(), fieldAccessInstruction.getRef());
Graph<Object> invertedHeapGraph = GraphInverter.invert(basicAnalysisData.basicHeapGraph);
Set<Object> reachingHeapGraphNodes = DFS.getReachableNodes(invertedHeapGraph, pointedInstances);
return doesContainExternalPointer(reachingHeapGraphNodes, instructionInfo.getCGNode())
&& (isAnyThreadSafe(getConcreteTypes(pointedInstances)) || doesContainThreadSafeFieldPointer(reachingHeapGraphNodes));
}
}
return false;
}
private boolean doesContainExternalPointer(Set<Object> reachingHeapGraphNodes, CGNode localCGNode) {
for (Object node : reachingHeapGraphNodes) {
if (node instanceof LocalPointerKey && isExternalPointer((LocalPointerKey) node, localCGNode)) {
return true;
}
}
return false;
}
private boolean doesContainThreadSafeFieldPointer(Set<Object> reachingNodes) {
for (Object node : reachingNodes) {
if (node instanceof InstanceFieldPointerKey && isThreadSafeFieldPointer((InstanceFieldPointerKey) node)) {
return true;
}
}
return false;
}
private boolean isExternalPointer(LocalPointerKey pointerKey, CGNode cgNode) {
CGNode pointerNode = pointerKey.getNode();
return !isInitializationMethod(pointerNode.getMethod()) && pointerNode != cgNode;
}
private boolean isThreadSafeFieldPointer(InstanceFieldPointerKey pointerKey) {
return isThreadSafe(pointerKey.getInstanceKey().getConcreteType());
}
/**
* This method is based on
* LCK06JBugDetector#propagateUnsafeModifyingStaticFieldsInstructions. The
* only difference is in the first line when the actual transfer function is
* instantiated.
*
* @return
*/
private BitVectorSolver<CGNode> propagateUnprotectedInstructionThatMayAccessUnsafelySharedFields() {
VNA00JTransferFunctionProvider transferFunctions = new VNA00JTransferFunctionProvider(javaProject, basicAnalysisData.callGraph, cgNodeInfoMap, basicAnalysisData.classHierarchy);
BitVectorFramework<CGNode, InstructionInfo> bitVectorFramework = new BitVectorFramework<CGNode, InstructionInfo>(GraphInverter.invert(basicAnalysisData.callGraph), transferFunctions,
globalValues);
BitVectorSolver<CGNode> bitVectorSolver = new BitVectorSolver<CGNode>(bitVectorFramework) {
@Override
protected BitVectorVariable makeNodeVariable(CGNode cgNode, boolean IN) {
BitVectorVariable nodeBitVectorVariable = new BitVectorVariable();
nodeBitVectorVariable.addAll(cgNodeInfoMap.get(cgNode).getBitVector());
return nodeBitVectorVariable;
}
};
try {
bitVectorSolver.solve(null);
} catch (CancelException ex) {
throw new RuntimeException("Bitvector solver was stopped", ex);
}
return bitVectorSolver;
}
/**
* This method is based on LCK06JBugDetector#getActuallyUnsafeInstructions.
*
* This methods returns a collection of instructions from the callees that
* we'll report to the user. After we propagate the unprotected instructions
* along the call graph, we look into the list of instructions propagated to
* each method. Some of these instructions are immediate instructions of the
* method itself, while others have originated from other methods. Let's say
* we'd like to report the problematic instructions in method m. These
* instructions will be the unprotected instructions that we have computed
* for method m plus some of the method invocations in m. The method
* invocations that we include in our collection have the following
* criteria: the callee has propagated some unprotected instructions and the
* method invocation is not inside a synchronized block of the caller.
*
* TODO: Update the comment with how we handle local variables passed
* through method invocations.
*
* @param bitVectorSolver
* @param unsafeSynchronizedBlock
* @return
*/
private Collection<InstructionInfo> getInstructionsToReport(final BitVectorSolver<CGNode> bitVectorSolver, final CGNode cgNode) {
final Collection<InstructionInfo> unprotectedInstructionsThatMayAccessUnsafelySharedFields = new HashSet<InstructionInfo>();
final Collection<InstructionInfo> synchronizedBlocks = new HashSet<InstructionInfo>();
populateSynchronizedBlocksForNode(synchronizedBlocks, cgNode);
IR ir = cgNode.getIR();
if (ir == null) {
return unprotectedInstructionsThatMayAccessUnsafelySharedFields; //should not really be null here
}
//Add the initial set of the given CGNode.
CGNodeInfo cgNodeInfo = cgNodeInfoMap.get(cgNode);
cgNodeInfo.getBitVectorContents(unprotectedInstructionsThatMayAccessUnsafelySharedFields, globalValues);
//Add the instructions propagated from the callees.
AnalysisUtils.collect(javaProject, new HashSet<InstructionInfo>(), cgNode, new InstructionFilter() {
@Override
public boolean accept(InstructionInfo instructionInfo) {
SSAInstruction instruction = instructionInfo.getInstruction();
if (instruction instanceof SSAAbstractInvokeInstruction) {
//FIXME: The following condition is similar to the one in edu.illinois.keshmesh.detector.VNA00JTransferFunctionProvider.getEdgeTransferFunction(CGNode, CGNode). We should consider removing this duplication.
if (!AnalysisUtils.isProtectedByAnySynchronizedBlock(synchronizedBlocks, instructionInfo) && AnalysisUtils.doesAllowPropagation(instructionInfo, basicAnalysisData.classHierarchy)) {
SSAAbstractInvokeInstruction invokeInstruction = (SSAAbstractInvokeInstruction) instruction;
// Add the unprotected instructions of the methods that are the targets of the invocation instruction.
Set<CGNode> possibleTargets = basicAnalysisData.callGraph.getPossibleTargets(cgNode, invokeInstruction.getCallSite());
for (CGNode possibleTarget : possibleTargets) {
// Add unprotected instructions coming from callees.
if (hasPropagatedUnprotectedInstructions(bitVectorSolver, possibleTarget)) {
unprotectedInstructionsThatMayAccessUnsafelySharedFields.add(instructionInfo);
break;
}
}
}
}
return false;
}
});
return unprotectedInstructionsThatMayAccessUnsafelySharedFields;
}
/**
* This method is based on LCK06JBugDetector#addSolverResults.
*
* @param results
* @param bitVectorSolver
* @param cgNode
*/
private boolean hasPropagatedUnprotectedInstructions(BitVectorSolver<CGNode> bitVectorSolver, CGNode cgNode) {
IntSet value = bitVectorSolver.getIn(cgNode).getValue();
if (value != null) {
IntIterator intIterator = value.intIterator();
if (intIterator.hasNext()) {
return true;
}
}
return false;
}
private Collection<InstructionInfo> getInstructionsToReport(BitVectorSolver<CGNode> bitVectorSolver) {
Iterator<CGNode> cgNodesIter = basicAnalysisData.callGraph.iterator();
final Collection<InstructionInfo> unprotectedInstructionsThatMayAccessUnsafelySharedFields = new HashSet<InstructionInfo>();
while (cgNodesIter.hasNext()) {
CGNode cgNode = cgNodesIter.next();
if (!isIgnoredClass(cgNode.getMethod().getDeclaringClass())) {
unprotectedInstructionsThatMayAccessUnsafelySharedFields.addAll(getInstructionsToReport(bitVectorSolver, cgNode));
}
}
return unprotectedInstructionsThatMayAccessUnsafelySharedFields;
}
private BugInstances createBugInstances(Collection<InstructionInfo> instructionInfosToReport) {
BugInstances bugInstances = new BugInstances();
for (InstructionInfo instructionInfo : instructionInfosToReport) {
if (!isInFakeRootMethod(instructionInfo)) {
//FIXME: The following try-catch block is a workaround for issue #41.
try {
bugInstances.add(new BugInstance(BugPatterns.VNA00J, instructionInfo.getPosition(), new VNA00JFixInformation()));
} catch (RuntimeException e) {
if (e.getMessage() == null || !e.getMessage().startsWith("Position not found.")) {
throw e;
}
}
}
}
return bugInstances;
}
private boolean isInFakeRootMethod(InstructionInfo instructionInfo) {
return instructionInfo.getCGNode().getMethod() instanceof SyntheticMethod;
}
}