/**
* 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.Collections;
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.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.InstanceKey;
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.SSAInstruction;
import com.ibm.wala.ssa.SSAMonitorInstruction;
import com.ibm.wala.ssa.SSAPutInstruction;
import com.ibm.wala.util.CancelException;
import com.ibm.wala.util.graph.impl.GraphInverter;
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.CodePosition;
import edu.illinois.keshmesh.detector.bugs.LCK06JFixInformation;
import edu.illinois.keshmesh.detector.util.AnalysisUtils;
import edu.illinois.keshmesh.util.Logger;
/**
*
* An unsafe instruction of a CGNode is an unsafe modification of a static
* field. This modification occurs directly or indirectly inside the CGNode.
*
*
* @author Mohsen Vakilian
* @author Stas Negara
* @author Samira Tasharofi
*
*/
public class LCK06JBugDetector extends BugPatternDetector {
enum SynchronizedBlockKind {
SAFE, UNSAFE
}
private final Set<InstanceKey> instancesPointedByStaticFields = new HashSet<InstanceKey>();
private final OrdinalSetMapping<InstructionInfo> globalValues = MutableMapping.make();
private final Map<CGNode, CGNodeInfo> cgNodeInfoMap = new HashMap<CGNode, CGNodeInfo>();
private LCK06JIntermediateResults intermediateResults;
public LCK06JBugDetector() {
this.intermediateResults = new LCK06JIntermediateResults();
}
@Override
public LCK06JIntermediateResults getIntermediateResults() {
return intermediateResults;
}
public Map<CGNode, CGNodeInfo> getCGNodeInfoMap() {
return Collections.unmodifiableMap(cgNodeInfoMap);
}
@Override
public BugInstances doPerformAnalysis(IJavaProject javaProject, BasicAnalysisData basicAnalysisData) {
intermediateResults.setStaticFields(getAllStaticFields());
populateAllInstancesPointedByStaticFields();
BugInstances bugInstances = new BugInstances();
Collection<InstructionInfo> unsafeSynchronizedBlocks = new HashSet<InstructionInfo>();
Collection<CGNode> unsafeSynchronizedMethods = new HashSet<CGNode>();
populatedUnsafeSynchronizedStructures(unsafeSynchronizedBlocks, unsafeSynchronizedMethods);
if (unsafeSynchronizedBlocks.isEmpty() && unsafeSynchronizedMethods.isEmpty()) {
return bugInstances;
}
populateUnsafeModifyingStaticFieldsInstructionsMap();
BitVectorSolver<CGNode> bitVectorSolver = propagateUnsafeModifyingStaticFieldsInstructions();
Iterator<CGNode> cgNodesIterator = basicAnalysisData.callGraph.iterator();
while (cgNodesIterator.hasNext()) {
CGNode cgNode = cgNodesIterator.next();
IntSet value = bitVectorSolver.getOut(cgNode).getValue();
if (value != null) {
IntIterator intIterator = value.intIterator();
Logger.log("CGNode: " + cgNode.getMethod().getSignature());
while (intIterator.hasNext()) {
InstructionInfo instructionInfo = globalValues.getMappedObject(intIterator.next());
Logger.log("\tPropagated instruction: " + instructionInfo);
}
}
}
reportActuallyUnsafeInstructions(bugInstances, unsafeSynchronizedBlocks, unsafeSynchronizedMethods, bitVectorSolver);
return bugInstances;
}
/*
* If an unsafe instruction i is inside an unsafe synchronized block b in a
* method m, then, if m is safe, we don't report i. Also, if i is inside
* some safe synchronized block in m, then we don't report it as well. But
* we report i for all other cases, including scenarios, where method m is
* called inside a safe synchronized block in some other method m2 (or m2 is
* a safe synchronized method).
*/
private void reportActuallyUnsafeInstructions(BugInstances bugInstances, Collection<InstructionInfo> unsafeSynchronizedBlocks, Collection<CGNode> unsafeSynchronizedMethods,
BitVectorSolver<CGNode> bitVectorSolver) {
reportActuallyUnsafeInstructionsOfSynchronizedBlocks(bugInstances, unsafeSynchronizedBlocks, bitVectorSolver);
reportActuallyUnsafeInstructionsOfMethods(bugInstances, unsafeSynchronizedMethods, bitVectorSolver);
}
/*
* Report the instructions of synchronized instance methods that have unsafe
* instructions.
*/
private void reportActuallyUnsafeInstructionsOfMethods(BugInstances bugInstances, Collection<CGNode> unsafeSynchronizedMethods, BitVectorSolver<CGNode> bitVectorSolver) {
for (CGNode unsafeSynchronizedMethod : unsafeSynchronizedMethods) {
Collection<InstructionInfo> actuallyUnsafeInstructions = new HashSet<InstructionInfo>();
addSolverResults(actuallyUnsafeInstructions, bitVectorSolver, unsafeSynchronizedMethod);
reportActuallyUnsafeInstructionsOfMethod(unsafeSynchronizedMethod, actuallyUnsafeInstructions, bugInstances);
}
}
/*
* Report the instructions of synchronized blocks that take a nonstatic lock
* but has unsafe instructions.
*/
private void reportActuallyUnsafeInstructionsOfSynchronizedBlocks(BugInstances bugInstances, Collection<InstructionInfo> unsafeSynchronizedBlocks, BitVectorSolver<CGNode> bitVectorSolver) {
for (InstructionInfo unsafeSynchronizedBlock : unsafeSynchronizedBlocks) {
// If the method is safe, i.e. static and synchronized, we report no instances of LCK06J in that method. Therefore, we ignore the unsafe synchronized blocks in it.
if (isSafeSynchronized(unsafeSynchronizedBlock.getCGNode())) {
continue;
}
Collection<InstructionInfo> actuallyUnsafeInstructions = getActuallyUnsafeInstructions(bitVectorSolver, unsafeSynchronizedBlock);
reportActuallyUnsafeInstructionsOfSynchronizedBlock(unsafeSynchronizedBlock, actuallyUnsafeInstructions, bugInstances);
}
}
private void reportActuallyUnsafeInstructionsOfSynchronizedBlock(InstructionInfo unsafeSynchronizedBlock, Collection<InstructionInfo> actuallyUnsafeInstructions, BugInstances bugInstances) {
reportActuallyUnsafeInstructions(unsafeSynchronizedBlock.getCGNode(), unsafeSynchronizedBlock.getPosition(), actuallyUnsafeInstructions, bugInstances);
Logger.log("Unsafe instructions of " + unsafeSynchronizedBlock + " are " + actuallyUnsafeInstructions.toString());
}
private void reportActuallyUnsafeInstructionsOfMethod(CGNode unsafeSynchronizedMethod, Collection<InstructionInfo> actuallyUnsafeInstructions, BugInstances bugInstances) {
reportActuallyUnsafeInstructions(unsafeSynchronizedMethod, getPosition(unsafeSynchronizedMethod), actuallyUnsafeInstructions, bugInstances);
Logger.log("Unsafe instructions of " + unsafeSynchronizedMethod + " are " + actuallyUnsafeInstructions.toString());
}
private void reportActuallyUnsafeInstructions(CGNode cgNode, CodePosition position, Collection<InstructionInfo> actuallyUnsafeInstructions, BugInstances bugInstances) {
if (!actuallyUnsafeInstructions.isEmpty()) {
LCK06JFixInformation fixInfo = new LCK06JFixInformation(getFieldNames(getUnsafeStaticFields(actuallyUnsafeInstructions)));
bugInstances.add(new BugInstance(BugPatterns.LCK06J, position, fixInfo));
}
}
private Set<String> getFieldNames(Collection<IField> fields) {
Set<String> fieldNames = new HashSet<String>();
for (IField field : fields) {
fieldNames.add(AnalysisUtils.walaTypeNameToJavaName(field.getDeclaringClass().getName()) + "." + field.getName().toString());
}
return fieldNames;
}
//TODO: is modifiedStaticFields is enough to report or it is not?
private Set<IField> getUnsafeStaticFields(Collection<InstructionInfo> actuallyUnsafeInstructions) {
Set<IField> modifiedStaticFields = new HashSet<IField>();
for (InstructionInfo unsafeInstructionInfo : actuallyUnsafeInstructions) {
modifiedStaticFields.addAll(getModifiedStaticFields(unsafeInstructionInfo));
}
return modifiedStaticFields;
}
private Collection<IField> getModifiedStaticFields(final InstructionInfo unsafeInstructionInfo) {
class ScanVisitor extends SSAInstruction.Visitor {
public Collection<IField> result = new HashSet<IField>();
@Override
public void visitPut(SSAPutInstruction instruction) {
IField accessedField = AnalysisUtils.getAccessedField(basicAnalysisData, instruction);
if (accessedField.isStatic()) {
if (!accessedField.isFinal()) {
result.add(accessedField);
}
} else {
result.addAll(getStaticFieldsPointingTo(instruction.getRef(), unsafeInstructionInfo.getCGNode()));
}
}
}
ScanVisitor visitor = new ScanVisitor();
unsafeInstructionInfo.getInstruction().visit(visitor);
return visitor.result;
}
private Collection<IField> getStaticFieldsPointingTo(int valueNumber, CGNode cgNode) {
Collection<IField> staticFieldsPointingToValueNumber = new HashSet<IField>();
for (IField staticField : getAllStaticFields()) {
if (isPointedByStaticField(staticField, valueNumber, cgNode)) {
staticFieldsPointingToValueNumber.add(staticField);
}
}
return staticFieldsPointingToValueNumber;
}
private boolean isPointedByStaticField(IField staticField, int valueNumber, CGNode cgNode) {
PointerKey staticFieldPointer = basicAnalysisData.heapModel.getPointerKeyForStaticField(staticField);
Collection<InstanceKey> instancesPointedByStaticField = getPointedInstances(staticFieldPointer);
Collection<InstanceKey> pointedInstances = getPointedInstances(getPointerForValueNumber(cgNode, valueNumber));
if (containsAny(pointedInstances, instancesPointedByStaticField)) {
return true;
}
return false;
}
private boolean isPointedByAnyStaticField(int valueNumber, CGNode cgNode) {
for (IField staticField : getAllStaticFields()) {
if (isPointedByStaticField(staticField, valueNumber, cgNode)) {
return true;
}
}
return false;
}
/**
* Has a side-effect: container collection may change.
*
* @param container
* @param containee
* @return
*/
private boolean containsAny(Collection<InstanceKey> container, Collection<InstanceKey> containee) {
return containee.removeAll(container);
}
// private IField getStaticNonFinalField(SSAFieldAccessInstruction fieldAccessInstruction) {
// IField accessedField = basicAnalysisData.classHierarchy.resolveField(fieldAccessInstruction.getDeclaredField());
// if (!(fieldAccessInstruction.isStatic() && !accessedField.isFinal())) {
// throw new AssertionError("Expected an instruction accessing a nonfinal static field.");
// }
// return accessedField;
// }
/**
* @param results
* The unsafe instructions of the CGNode will get added to this
* collection.
* @param bitVectorSolver
* It maps every CGNode to a collection of its unsafe
* instructions.
* @param cgNode
*/
public void addSolverResults(Collection<InstructionInfo> results, BitVectorSolver<CGNode> bitVectorSolver, CGNode cgNode) {
IntSet value = bitVectorSolver.getOut(cgNode).getValue();
if (value != null) {
IntIterator intIterator = value.intIterator();
while (intIterator.hasNext()) {
InstructionInfo instructionInfo = globalValues.getMappedObject(intIterator.next());
results.add(instructionInfo);
}
}
}
/**
* After we propagate statements that modify static fields through the call
* graph, we have to filter the result just to keep the modifications that
* occur inside an unsafe synchronized block. Direct modifications to static
* fields or indirect ones through method calls inside an unsafe
* synchronized block are called actual unsafe modifications.
*
* @return a collection of instructions that modify static fields and are
* directly or indirectly inside the given unsafeSynchronizedBlock
* but are not directly or indirectly inside a safe synchronized
* block.
*/
private Collection<InstructionInfo> getActuallyUnsafeInstructions(final BitVectorSolver<CGNode> bitVectorSolver, final InstructionInfo unsafeSynchronizedBlock) {
final Collection<InstructionInfo> unsafeInstructions = new HashSet<InstructionInfo>();
final CGNode cgNode = unsafeSynchronizedBlock.getCGNode();
final Collection<InstructionInfo> safeSynchronizedBlocks = new HashSet<InstructionInfo>();
populateSynchronizedBlocksForNode(safeSynchronizedBlocks, cgNode, SynchronizedBlockKind.SAFE);
IR ir = cgNode.getIR();
if (ir == null) {
return unsafeInstructions; //should not really be null here
}
AnalysisUtils.collect(javaProject, new HashSet<InstructionInfo>(), cgNode, new InstructionFilter() {
@Override
public boolean accept(InstructionInfo instructionInfo) {
SSAInstruction instruction = instructionInfo.getInstruction();
if (instructionInfo.isInside(unsafeSynchronizedBlock) && !AnalysisUtils.isProtectedByAnySynchronizedBlock(safeSynchronizedBlocks, instructionInfo)) {
if (canModifyStaticField(cgNode, instruction)) {
unsafeInstructions.add(instructionInfo);
}
if (instruction instanceof SSAAbstractInvokeInstruction) {
SSAAbstractInvokeInstruction invokeInstruction = (SSAAbstractInvokeInstruction) instruction;
// Add the unsafe modifications 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 unsafe operations coming from callees.
addSolverResults(unsafeInstructions, bitVectorSolver, possibleTarget);
}
}
}
return false;
}
});
return unsafeInstructions;
}
private BitVectorSolver<CGNode> propagateUnsafeModifyingStaticFieldsInstructions() {
LCK06JTransferFunctionProvider transferFunctions = new LCK06JTransferFunctionProvider(this);
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;
}
/**
* Populates the initial sets for modifying static fields of all CGNodes.
*
* @param cgNodeInfoMap
* @param globalValues
*/
private void populateUnsafeModifyingStaticFieldsInstructionsMap() {
Iterator<CGNode> cgNodesIterator = basicAnalysisData.callGraph.iterator();
while (cgNodesIterator.hasNext()) {
CGNode cgNode = cgNodesIterator.next();
BitVector bitVector = new BitVector();
Collection<InstructionInfo> safeSynchronizedBlocks = new HashSet<InstructionInfo>();
if (!isSafeSynchronized(cgNode) && !isIgnoredClass(cgNode.getMethod().getDeclaringClass())) {
Collection<InstructionInfo> modifyingStaticFieldsInstructions = getModifyingStaticFieldsInstructions(cgNode);
populateSynchronizedBlocksForNode(safeSynchronizedBlocks, cgNode, SynchronizedBlockKind.SAFE);
Collection<InstructionInfo> unsafeModifyingStaticFieldsInstructions = new HashSet<InstructionInfo>();
for (InstructionInfo modifyingStaticFieldInstruction : modifyingStaticFieldsInstructions) {
if (!AnalysisUtils.isProtectedByAnySynchronizedBlock(safeSynchronizedBlocks, modifyingStaticFieldInstruction)) {
unsafeModifyingStaticFieldsInstructions.add(modifyingStaticFieldInstruction);
}
}
for (InstructionInfo modifyInstruction : modifyingStaticFieldsInstructions) {
Logger.log("MODIFY: " + modifyInstruction);
}
for (InstructionInfo unsafeModifyInstruction : unsafeModifyingStaticFieldsInstructions) {
bitVector.set(globalValues.add(unsafeModifyInstruction));
Logger.log("UNSAFE MODIFY: " + unsafeModifyInstruction);
}
}
cgNodeInfoMap.put(cgNode, new CGNodeInfo(safeSynchronizedBlocks, bitVector));
}
}
private Collection<InstructionInfo> getModifyingStaticFieldsInstructions(final CGNode cgNode) {
Collection<InstructionInfo> modifyingStaticFieldsInstructions = new HashSet<InstructionInfo>();
IR ir = cgNode.getIR();
if (ir == null) {
return modifyingStaticFieldsInstructions;
}
AnalysisUtils.collect(javaProject, modifyingStaticFieldsInstructions, cgNode, new InstructionFilter() {
@Override
public boolean accept(InstructionInfo instructionInfo) {
return canModifyStaticField(cgNode, instructionInfo.getInstruction());
}
});
return modifyingStaticFieldsInstructions;
}
private boolean canModifyStaticField(final CGNode cgNode, final SSAInstruction ssaInstruction) {
class ScanVisitor extends SSAInstruction.Visitor {
public boolean result = false;
@Override
public void visitPut(SSAPutInstruction instruction) {
IField accessedField = AnalysisUtils.getAccessedField(basicAnalysisData, instruction);
if (accessedField.isStatic()) {
result = !accessedField.isFinal();
} else {
result = isPointedByAnyStaticField(instruction.getRef(), cgNode);
}
}
}
ScanVisitor visitor = new ScanVisitor();
ssaInstruction.visit(visitor);
return visitor.result;
}
// private boolean isStaticNonFinal(SSAFieldAccessInstruction fieldAccessInstruction) {
// IField accessedField = basicAnalysisData.classHierarchy.resolveField(fieldAccessInstruction.getDeclaredField());
// return fieldAccessInstruction.isStatic() && !accessedField.isFinal();
// }
/*
* FIXME: A synchronized block that is nested inside a safe one is
* considered safe (See edu.illinois.keshmesh.detector.LCK06JBugDetector.
* getActuallyUnsafeInstructions(BitVectorSolver<CGNode>, InstructionInfo)).
* So, this method should not return such synchronized blocks.
*/
private void populatedUnsafeSynchronizedStructures(Collection<InstructionInfo> unsafeSynchronizedBlocks, Collection<CGNode> unsafeSynchronizedMethods) {
Iterator<CGNode> cgNodesIterator = basicAnalysisData.callGraph.iterator();
while (cgNodesIterator.hasNext()) {
final CGNode cgNode = cgNodesIterator.next();
Logger.log("IR is:" + cgNode.getIR());
IMethod method = cgNode.getMethod();
if (!isIgnoredClass(method.getDeclaringClass())) {
populateSynchronizedBlocksForNode(unsafeSynchronizedBlocks, cgNode, SynchronizedBlockKind.UNSAFE);
if (isUnsafeSynchronized(cgNode)) {
unsafeSynchronizedMethods.add(cgNode);
}
}
}
}
private void populateSynchronizedBlocksForNode(Collection<InstructionInfo> synchronizedBlocks, final CGNode cgNode, final SynchronizedBlockKind synchronizedBlockKind) {
AnalysisUtils.collect(javaProject, synchronizedBlocks, cgNode, new InstructionFilter() {
@Override
public boolean accept(InstructionInfo instructionInfo) {
SSAInstruction instruction = instructionInfo.getInstruction();
if (AnalysisUtils.isMonitorEnter(instruction)) {
SSAMonitorInstruction monitorEnterInstruction = (SSAMonitorInstruction) instruction;
if (synchronizedBlockKind == SynchronizedBlockKind.SAFE) {
return isSafe(cgNode, monitorEnterInstruction);
} else {
return !isSafe(cgNode, monitorEnterInstruction);
}
}
return false;
}
});
}
boolean isSafe(CGNode cgNode, SSAMonitorInstruction monitorInstruction) {
if (!monitorInstruction.isMonitorEnter()) {
throw new AssertionError("Expected a monitor enter instruction.");
}
return isSafeLock(cgNode, monitorInstruction.getRef());
}
private boolean isSafeLock(CGNode cgNode, int lockValueNumber) {
PointerKey lockPointer = getPointerForValueNumber(cgNode, lockValueNumber);
Collection<InstanceKey> lockPointedInstances = getPointedInstances(lockPointer);
if (lockPointedInstances.isEmpty() || !instancesPointedByStaticFields.containsAll(lockPointedInstances)) {
return false;
}
return true;
}
private void populateAllInstancesPointedByStaticFields() {
for (IField staticField : getAllStaticFields()) {
Logger.log("Static field: " + staticField);
PointerKey staticFieldPointer = basicAnalysisData.heapModel.getPointerKeyForStaticField(staticField);
Collection<InstanceKey> pointedInstances = getPointedInstances(staticFieldPointer);
for (InstanceKey instance : pointedInstances) {
Logger.log("Pointed instance: " + instance);
}
instancesPointedByStaticFields.addAll(pointedInstances);
}
}
private Set<IField> getAllStaticFields() {
Set<IField> staticFields = new HashSet<IField>();
Iterator<IClass> classIterator = basicAnalysisData.classHierarchy.iterator();
while (classIterator.hasNext()) {
IClass klass = classIterator.next();
if (!isIgnoredClass(klass)) {
staticFields.addAll(klass.getAllStaticFields());
}
}
return staticFields;
}
private boolean isIgnoredClass(IClass klass) {
//TODO: Should we look for bugs in JDK usage as well?
//TODO: !!!What about other bytecodes, e.g. from the libraries, which will not allow to get the source position?
return AnalysisUtils.isJDKClass(klass);
}
private CodePosition getPosition(CGNode cgNode) {
IMethod method = cgNode.getMethod();
return AnalysisUtils.getPosition(javaProject, method, 0);
}
public boolean isSafeSynchronized(CGNode cgNode) {
return cgNode.getMethod().isSynchronized() && isSafeMethod(cgNode);
}
private boolean isUnsafeSynchronized(CGNode cgNode) {
return cgNode.getMethod().isSynchronized() && !isSafeMethod(cgNode);
}
private boolean isSafeMethod(CGNode cgNode) {
return cgNode.getMethod().isStatic() || isSafeLock(cgNode, AnalysisUtils.THIS_VALUE_NUMBER);
}
}