/**
* This file is licensed under the University of Illinois/NCSA Open Source License. See LICENSE.TXT for details.
*/
package edu.illinois.keshmesh.detector.util;
import java.util.Collection;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import com.ibm.wala.classLoader.IClass;
import com.ibm.wala.classLoader.IField;
import com.ibm.wala.classLoader.IMethod;
import com.ibm.wala.classLoader.ShrikeCTMethod;
import com.ibm.wala.ipa.callgraph.CGNode;
import com.ibm.wala.ipa.cha.IClassHierarchy;
import com.ibm.wala.shrikeCT.InvalidClassFileException;
import com.ibm.wala.ssa.DefUse;
import com.ibm.wala.ssa.IR;
import com.ibm.wala.ssa.SSAFieldAccessInstruction;
import com.ibm.wala.ssa.SSAGetInstruction;
import com.ibm.wala.ssa.SSAInstruction;
import com.ibm.wala.ssa.SSAInvokeInstruction;
import com.ibm.wala.ssa.SSAMonitorInstruction;
import com.ibm.wala.types.MemberReference;
import com.ibm.wala.types.TypeName;
import com.ibm.wala.types.TypeReference;
import com.ibm.wala.util.strings.Atom;
import edu.illinois.keshmesh.detector.BasicAnalysisData;
import edu.illinois.keshmesh.detector.InstructionFilter;
import edu.illinois.keshmesh.detector.InstructionInfo;
import edu.illinois.keshmesh.detector.bugs.CodePosition;
/**
*
* @author Mohsen Vakilian
* @author Stas Negara
*
*/
public class AnalysisUtils {
public static final String APPLICATION_CLASSLOADER_NAME = "Application"; //$NON-NLS-1$
public static final String EXTENSION_CLASSLOADER_NAME = "Extension"; //$NON-NLS-1$
public static final String PRIMORDIAL_CLASSLOADER_NAME = "Primordial"; //$NON-NLS-1$
private static final String OBJECT_GETCLASS_SIGNATURE = "java.lang.Object.getClass()Ljava/lang/Class;"; //$NON-NLS-1$
/**
* The value number of "this" is meaningful only for instance methods.
*/
public static final int THIS_VALUE_NUMBER = 1;
public static IPath getWorkspaceLocation() {
return ResourcesPlugin.getWorkspace().getRoot().getLocation();
}
public static boolean isProtectedByAnySynchronizedBlock(Collection<InstructionInfo> safeSynchronizedBlocks, InstructionInfo instruction) {
for (InstructionInfo safeSynchronizedBlock : safeSynchronizedBlocks) {
if (instruction.isInside(safeSynchronizedBlock)) {
return true;
}
}
return false;
}
public static void collect(IJavaProject javaProject, Collection<InstructionInfo> instructionInfos, CGNode cgNode, InstructionFilter instructionFilter) {
if (instructionInfos == null) {
throw new RuntimeException("Expected a valid collection to store the results in.");
}
IR ir = cgNode.getIR();
if (ir == null) {
return;
}
SSAInstruction[] instructions = ir.getInstructions();
for (int instructionIndex = 0; instructionIndex < instructions.length; instructionIndex++) {
SSAInstruction instruction = instructions[instructionIndex];
InstructionInfo instructionInfo = new InstructionInfo(javaProject, cgNode, instructionIndex);
if (instruction != null && (instructionFilter == null || instructionFilter.accept(instructionInfo))) {
instructionInfos.add(instructionInfo);
}
}
}
/**
* Remove the code duplication in
*
* {@link #collect(IJavaProject, Collection, CGNode, InstructionFilter)}
*
* and
*
* {@link #contains(IJavaProject, CGNode, InstructionFilter)}.
*/
public static boolean contains(IJavaProject javaProject, CGNode cgNode, InstructionFilter instructionFilter) {
IR ir = cgNode.getIR();
if (ir == null) {
return false;
}
SSAInstruction[] instructions = ir.getInstructions();
for (int instructionIndex = 0; instructionIndex < instructions.length; instructionIndex++) {
SSAInstruction instruction = instructions[instructionIndex];
InstructionInfo instructionInfo = new InstructionInfo(javaProject, cgNode, instructionIndex);
if (instruction != null && (instructionFilter == null || instructionFilter.accept(instructionInfo))) {
return true;
}
}
return false;
}
/**
* Findbugs needs the name of the class that contains the bug. The class
* name that WALA returns includes some additional information such as the
* method name in case of anonymous classes. But, Findbugs expects names
* that follow the standard Java bytecode convention. This method takes a
* class name as reported by WALA and returns the name of the innermost
* enclosing non-anonymous class of it. See issue #5 for more details.
*
* @param typeName
* @return
*/
public static String getEnclosingNonanonymousClassName(TypeName typeName) {
String packageName = typeName.getPackage().toString().replaceAll("/", ".");
int indexOfOpenParen = packageName.indexOf('(');
if (indexOfOpenParen != -1) {
int indexOfLastPackageSeparator = packageName.lastIndexOf('.', indexOfOpenParen);
return packageName.substring(0, indexOfLastPackageSeparator);
}
String className = typeName.getClassName().toString();
int indexOfDollarSign = className.indexOf('$');
if (indexOfDollarSign != -1) {
className = className.substring(0, indexOfDollarSign);
}
return packageName + "." + className;
}
private static boolean isExtension(Atom classLoaderName) {
return classLoaderName.toString().equals(EXTENSION_CLASSLOADER_NAME);
}
private static boolean isPrimordial(Atom classLoaderName) {
return classLoaderName.toString().equals(PRIMORDIAL_CLASSLOADER_NAME);
}
private static boolean isApplication(Atom classLoaderName) {
return classLoaderName.toString().equals(APPLICATION_CLASSLOADER_NAME);
}
public static boolean isApplicationClass(IClass klass) {
return isApplication(klass.getClassLoader().getName());
}
public static boolean isLibraryClass(IClass klass) {
return isExtension(klass.getClassLoader().getName());
}
public static boolean isLibraryClass(TypeReference typeReference) {
return isExtension(typeReference.getClassLoader().getName());
}
public static boolean isJDKClass(IClass klass) {
return isPrimordial(klass.getClassLoader().getName());
}
public static boolean isJDKClass(TypeReference typeReference) {
return isPrimordial(typeReference.getClassLoader().getName());
}
public static boolean isObjectGetClass(MemberReference memberReference) {
return isObjectGetClass(memberReference.getSignature());
}
public static boolean isObjectGetClass(IMethod method) {
return isObjectGetClass(method.getSignature());
}
private static boolean isObjectGetClass(String methodSignature) {
return methodSignature.equals(OBJECT_GETCLASS_SIGNATURE);
}
public static String walaTypeNameToJavaName(TypeName typeName) {
String fullyQualifiedName = typeName.getPackage() + "." + typeName.getClassName();
//WALA uses $ to refers to inner classes. We have to replace "$" by "." to make it a valid class name in Java source code.
return fullyQualifiedName.replace("$", ".").replace("/", ".");
}
public static CodePosition getPosition(IJavaProject javaProject, IMethod method, int instructionIndex) {
String enclosingClassName = getEnclosingNonanonymousClassName(method.getDeclaringClass().getName());
if (method instanceof ShrikeCTMethod) {
ShrikeCTMethod shrikeMethod = (ShrikeCTMethod) method;
try {
IType enclosingType = javaProject.findType(enclosingClassName, new NullProgressMonitor());
//FIXME: The following check is a workaround for issue #41.
if (enclosingType == null || enclosingType.getCompilationUnit() == null) {
String message = "Position not found. Could not find the type corresponding to %s in the Java project.";
System.err.println(String.format(message, enclosingClassName));
throw new RuntimeException(message);
}
IPath fullPath = getWorkspaceLocation().append(enclosingType.getCompilationUnit().getPath());
int lineNumber = shrikeMethod.getLineNumber(shrikeMethod.getBytecodeIndex(instructionIndex));
return new CodePosition(lineNumber, lineNumber, fullPath, enclosingClassName);
} catch (JavaModelException e) {
throw new RuntimeException(e);
} catch (InvalidClassFileException e) {
throw new RuntimeException(e);
}
}
throw new RuntimeException("Unexpected method class: " + method.getClass());
}
public static boolean isMonitorEnter(SSAInstruction ssaInstruction) {
return ssaInstruction instanceof SSAMonitorInstruction && ((SSAMonitorInstruction) ssaInstruction).isMonitorEnter();
}
public static boolean isMonitorExit(SSAInstruction ssaInstruction) {
return ssaInstruction instanceof SSAMonitorInstruction && !((SSAMonitorInstruction) ssaInstruction).isMonitorEnter();
}
public static boolean doesAllowPropagation(InstructionInfo instructionInfo, IClassHierarchy classHierarchy) {
if (!(instructionInfo.getInstruction() instanceof SSAInvokeInstruction)) {
throw new RuntimeException("Expected an SSAInvokeInstruction.");
}
SSAInvokeInstruction invokeInstruction = (SSAInvokeInstruction) instructionInfo.getInstruction();
for (int argumentIndex = 0; argumentIndex < invokeInstruction.getNumberOfUses(); ++argumentIndex) {
int argumentValueNumber = invokeInstruction.getUse(argumentIndex);
if (doesAllowPropagation(argumentValueNumber, instructionInfo.getCGNode(), classHierarchy)) {
return true;
}
}
return false;
}
/**
*
* @param valueNumber
* @param cgNode
* @return true if the given value number is one of the parameters of the
* method corresponding to the given CGNode. Note that the receiver
* (if the method is not static) is the first parameter of the
* method.
*/
private static boolean isParameterOf(int valueNumber, CGNode cgNode) {
int numberOfParametersOfCaller = cgNode.getMethod().getNumberOfParameters();
return valueNumber <= numberOfParametersOfCaller;
}
private static boolean isDefUseReachableFromParameterOrStaticField(int valueNumber, CGNode enclosingCGNode, IClassHierarchy classHierarchy) {
if (isParameterOf(valueNumber, enclosingCGNode)) {
return false;
}
DefUse defUse = enclosingCGNode.getDU();
SSAInstruction instructionDefiningTheValue = defUse.getDef(valueNumber);
if (instructionDefiningTheValue instanceof SSAGetInstruction) {
SSAGetInstruction getInstruction = (SSAGetInstruction) instructionDefiningTheValue;
if (getInstruction.isStatic()) {
return false; // indirectly accesses a static field
}
return isDefUseReachableFromParameterOrStaticField(getInstruction.getRef(), enclosingCGNode, classHierarchy);
}
return true;
}
private static boolean doesAllowPropagation(int valueNumber, CGNode enclosingCGNode, IClassHierarchy classHierarchy) {
return !isDefUseReachableFromParameterOrStaticField(valueNumber, enclosingCGNode, classHierarchy);
}
public static IField getAccessedField(BasicAnalysisData basicAnalysisData, SSAFieldAccessInstruction fieldAccessInstruction) {
return basicAnalysisData.classHierarchy.resolveField(fieldAccessInstruction.getDeclaredField());
}
}